aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNadir Chowdhury <[email protected]>2021-04-10 07:55:10 +0100
committerGitHub <[email protected]>2021-04-10 02:55:10 -0400
commit1efdef3ac34ebed98d643a6a1c273f3b176e8837 (patch)
treee835ade2564fe61dead5dee5ad67d67cca569c18
parentAdd typings for audit logs, integrations, and webhooks (diff)
downloaddiscord.py-1efdef3ac34ebed98d643a6a1c273f3b176e8837.tar.xz
discord.py-1efdef3ac34ebed98d643a6a1c273f3b176e8837.zip
Add typings for invites, templates, and bans
-rw-r--r--discord/client.py9
-rw-r--r--discord/guild.py14
-rw-r--r--discord/invite.py57
-rw-r--r--discord/template.py33
-rw-r--r--discord/types/guild.py6
-rw-r--r--discord/types/invite.py60
-rw-r--r--discord/types/template.py44
7 files changed, 185 insertions, 38 deletions
diff --git a/discord/client.py b/discord/client.py
index 1e46be05..587137ef 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -27,6 +27,7 @@ import logging
import signal
import sys
import traceback
+from typing import Any, Optional, Union
import aiohttp
@@ -1070,7 +1071,7 @@ class Client:
"""
code = utils.resolve_template(code)
data = await self.http.get_template(code)
- return Template(data=data, state=self._connection)
+ return Template(data=data, state=self._connection) # type: ignore
async def fetch_guild(self, guild_id):
"""|coro|
@@ -1106,7 +1107,7 @@ class Client:
data = await self.http.get_guild(guild_id)
return Guild(data=data, state=self._connection)
- async def create_guild(self, name, region=None, icon=None, *, code=None):
+ async def create_guild(self, name: str, region: Optional[VoiceRegion] = None, icon: Any = None, *, code: str = None):
"""|coro|
Creates a :class:`.Guild`.
@@ -1155,7 +1156,7 @@ class Client:
# Invite management
- async def fetch_invite(self, url, *, with_counts=True):
+ async def fetch_invite(self, url: Union[Invite, str], *, with_counts: bool = True) -> Invite:
"""|coro|
Gets an :class:`.Invite` from a discord.gg URL or ID.
@@ -1192,7 +1193,7 @@ class Client:
data = await self.http.get_invite(invite_id, with_counts=with_counts)
return Invite.from_incomplete(state=self._connection, data=data)
- async def delete_invite(self, invite):
+ async def delete_invite(self, invite: Union[Invite, str]) -> None:
"""|coro|
Revokes an :class:`.Invite`, URL, or ID to an invite.
diff --git a/discord/guild.py b/discord/guild.py
index a3343686..c5ba0d1a 100644
--- a/discord/guild.py
+++ b/discord/guild.py
@@ -24,6 +24,7 @@ DEALINGS IN THE SOFTWARE.
import copy
from collections import namedtuple
+from typing import List, TYPE_CHECKING
from . import utils
from .role import Role
@@ -48,6 +49,11 @@ __all__ = (
'Guild',
)
+if TYPE_CHECKING:
+ from .types.guild import (
+ Ban as BanPayload
+ )
+
BanEntry = namedtuple('BanEntry', 'reason user')
_GuildLimit = namedtuple('_GuildLimit', 'emoji bitrate filesize')
@@ -1429,7 +1435,7 @@ class Guild(Hashable):
:class:`BanEntry`
The :class:`BanEntry` object for the specified user.
"""
- data = await self._state.http.get_ban(user.id, self.id)
+ data: BanPayload = await self._state.http.get_ban(user.id, self.id)
return BanEntry(
user=User(state=self._state, data=data['user']),
reason=data['reason']
@@ -1456,7 +1462,7 @@ class Guild(Hashable):
A list of :class:`BanEntry` objects.
"""
- data = await self._state.http.get_bans(self.id)
+ data: List[BanPayload] = await self._state.http.get_bans(self.id)
return [BanEntry(user=User(state=self._state, data=e['user']),
reason=e['reason'])
for e in data]
@@ -1606,7 +1612,7 @@ class Guild(Hashable):
data = await self._state.http.estimate_pruned_members(self.id, days, roles)
return data['pruned']
- async def invites(self):
+ async def invites(self) -> List[Invite]:
"""|coro|
Returns a list of all active instant invites from the guild.
@@ -2056,7 +2062,7 @@ class Guild(Hashable):
"""
await self._state.http.unban(user.id, self.id, reason=reason)
- async def vanity_invite(self):
+ async def vanity_invite(self) -> Invite:
"""|coro|
Returns the guild's special vanity invite.
diff --git a/discord/invite.py b/discord/invite.py
index 3c061750..d8bd554f 100644
--- a/discord/invite.py
+++ b/discord/invite.py
@@ -22,6 +22,9 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
+from __future__ import annotations
+
+from typing import Optional, TYPE_CHECKING
from .asset import Asset
from .utils import parse_time, snowflake_time, _get_as_snowflake
from .object import Object
@@ -34,6 +37,16 @@ __all__ = (
'Invite',
)
+if TYPE_CHECKING:
+ from .types.invite import (
+ Invite as InvitePayload,
+ InviteGuild as InviteGuildPayload,
+ )
+ from .types.channel import (
+ PartialChannel as PartialChannelPayload,
+ )
+ import datetime
+
class PartialInviteChannel:
"""Represents a "partial" invite channel.
@@ -72,7 +85,7 @@ class PartialInviteChannel:
__slots__ = ('id', 'name', 'type')
def __init__(self, **kwargs):
- self.id = kwargs.pop('id')
+ self.id = int(kwargs.pop('id'))
self.name = kwargs.pop('name')
self.type = kwargs.pop('type')
@@ -139,7 +152,7 @@ class PartialInviteGuild:
__slots__ = ('_state', 'features', 'icon', 'banner', 'id', 'name', 'splash', 'verification_level', 'description')
- def __init__(self, state, data, id):
+ def __init__(self, state, data: InviteGuildPayload, id: int):
self._state = state
self.id = id
self.name = data['name']
@@ -150,33 +163,33 @@ class PartialInviteGuild:
self.verification_level = try_enum(VerificationLevel, data.get('verification_level'))
self.description = data.get('description')
- def __str__(self):
+ def __str__(self) -> str:
return self.name
- def __repr__(self):
+ def __repr__(self) -> str:
return (
f'<{self.__class__.__name__} id={self.id} name={self.name!r} features={self.features} '
f'description={self.description!r}>'
)
@property
- def created_at(self):
+ def created_at(self) -> datetime.datetime:
""":class:`datetime.datetime`: Returns the guild's creation time in UTC."""
return snowflake_time(self.id)
@property
- def icon_url(self):
+ def icon_url(self) -> Asset:
""":class:`Asset`: Returns the guild's icon asset."""
return self.icon_url_as()
- def is_icon_animated(self):
+ def is_icon_animated(self) -> bool:
""":class:`bool`: Returns ``True`` if the guild has an animated icon.
.. versionadded:: 1.4
"""
return bool(self.icon and self.icon.startswith('a_'))
- def icon_url_as(self, *, format=None, static_format='webp', size=1024):
+ def icon_url_as(self, *, format=None, static_format='webp', size=1024) -> Asset:
"""The same operation as :meth:`Guild.icon_url_as`.
Returns
@@ -187,11 +200,11 @@ class PartialInviteGuild:
return Asset._from_guild_icon(self._state, self, format=format, static_format=static_format, size=size)
@property
- def banner_url(self):
+ def banner_url(self) -> Asset:
""":class:`Asset`: Returns the guild's banner asset."""
return self.banner_url_as()
- def banner_url_as(self, *, format='webp', size=2048):
+ def banner_url_as(self, *, format='webp', size=2048) -> Asset:
"""The same operation as :meth:`Guild.banner_url_as`.
Returns
@@ -202,11 +215,11 @@ class PartialInviteGuild:
return Asset._from_guild_image(self._state, self.id, self.banner, 'banners', format=format, size=size)
@property
- def splash_url(self):
+ def splash_url(self) -> Asset:
""":class:`Asset`: Returns the guild's invite splash asset."""
return self.splash_url_as()
- def splash_url_as(self, *, format='webp', size=2048):
+ def splash_url_as(self, *, format='webp', size=2048) -> Asset:
"""The same operation as :meth:`Guild.splash_url_as`.
Returns
@@ -313,13 +326,13 @@ class Invite(Hashable):
BASE = 'https://discord.gg'
- def __init__(self, *, state, data):
+ def __init__(self, *, state, data: InvitePayload):
self._state = state
self.max_age = data.get('max_age')
- self.code = data.get('code')
+ self.code = data['code']
self.guild = data.get('guild')
self.revoked = data.get('revoked')
- self.created_at = parse_time(data.get('created_at'))
+ self.created_at: Optional[datetime.datetime] = parse_time(data.get('created_at')) # type: ignore
self.temporary = data.get('temporary')
self.uses = data.get('uses')
self.max_uses = data.get('max_uses')
@@ -346,7 +359,7 @@ class Invite(Hashable):
# As far as I know, invites always need a channel
# So this should never raise.
- channel_data = data['channel']
+ channel_data: PartialChannelPayload = data['channel']
channel_id = int(channel_data['id'])
channel_type = try_enum(ChannelType, channel_data['type'])
channel = PartialInviteChannel(id=channel_id, name=channel_data['name'], type=channel_type)
@@ -373,30 +386,30 @@ class Invite(Hashable):
data['channel'] = channel
return cls(state=state, data=data)
- def __str__(self):
+ def __str__(self) -> str:
return self.url
- def __repr__(self):
+ def __repr__(self) -> str:
return (
f'<Invite code={self.code!r} guild={self.guild!r} '
f'online={self.approximate_presence_count} '
f'members={self.approximate_member_count}>'
)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash(self.code)
@property
- def id(self):
+ def id(self) -> str:
""":class:`str`: Returns the proper code portion of the invite."""
return self.code
@property
- def url(self):
+ def url(self) -> str:
""":class:`str`: A property that retrieves the invite URL."""
return self.BASE + '/' + self.code
- async def delete(self, *, reason=None):
+ async def delete(self, *, reason: Optional[str] = None):
"""|coro|
Revokes the instant invite.
diff --git a/discord/template.py b/discord/template.py
index af2d0dc4..5b9ad871 100644
--- a/discord/template.py
+++ b/discord/template.py
@@ -22,6 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
+from typing import Any, Optional, TYPE_CHECKING, overload
from .utils import parse_time, _get_as_snowflake, _bytes_to_base64_data
from .enums import VoiceRegion
from .guild import Guild
@@ -30,6 +31,9 @@ __all__ = (
'Template',
)
+if TYPE_CHECKING:
+ from .types.template import Template as TemplatePayload
+
class _FriendlyHttpAttributeErrorHelper:
__slots__ = ()
@@ -101,11 +105,11 @@ class Template:
The source guild.
"""
- def __init__(self, *, state, data):
+ def __init__(self, *, state, data: TemplatePayload):
self._state = state
self._store(data)
- def _store(self, data):
+ def _store(self, data: TemplatePayload):
self.code = data['code']
self.uses = data['usage_count']
self.name = data['name']
@@ -120,7 +124,7 @@ class Template:
guild = self._state._get_guild(id)
- if guild is None:
+ if guild is None and id:
source_serialised = data['serialized_source_guild']
source_serialised['id'] = id
state = _PartialTemplateState(state=self._state)
@@ -128,13 +132,13 @@ class Template:
self.source_guild = guild
- def __repr__(self):
+ def __repr__(self) -> str:
return (
f'<Template code={self.code!r} uses={self.uses} name={self.name!r}'
f' creator={self.creator!r} source_guild={self.source_guild!r}>'
)
- async def create_guild(self, name, region=None, icon=None):
+ async def create_guild(self, name: str, region: Optional[VoiceRegion] = None, icon: Any = None):
"""|coro|
Creates a :class:`.Guild` using the template.
@@ -174,7 +178,7 @@ class Template:
data = await self._state.http.create_from_template(self.code, name, region_value, icon)
return Guild(data=data, state=self._state)
- async def sync(self):
+ async def sync(self) -> None:
"""|coro|
Sync the template to the guild's current state.
@@ -197,7 +201,20 @@ class Template:
data = await self._state.http.sync_template(self.source_guild.id, self.code)
self._store(data)
- async def edit(self, **kwargs):
+ @overload
+ async def edit(
+ self,
+ *,
+ name: Optional[str] = ...,
+ description: Optional[str] = ...,
+ ) -> None:
+ ...
+
+ @overload
+ async def edit(self) -> None:
+ ...
+
+ async def edit(self, **kwargs) -> None:
"""|coro|
Edit the template metadata.
@@ -226,7 +243,7 @@ class Template:
data = await self._state.http.edit_template(self.source_guild.id, self.code, kwargs)
self._store(data)
- async def delete(self):
+ async def delete(self) -> None:
"""|coro|
Delete the template.
diff --git a/discord/types/guild.py b/discord/types/guild.py
index d78cb8a3..83c69916 100644
--- a/discord/types/guild.py
+++ b/discord/types/guild.py
@@ -31,6 +31,12 @@ from .activity import PartialPresenceUpdate
from .role import Role
from .member import Member
from .emoji import Emoji
+from .user import User
+
+
+class Ban(TypedDict):
+ reason: Optional[str]
+ user: User
class _UnavailableGuildOptional(TypedDict, total=False):
diff --git a/discord/types/invite.py b/discord/types/invite.py
new file mode 100644
index 00000000..25466158
--- /dev/null
+++ b/discord/types/invite.py
@@ -0,0 +1,60 @@
+"""
+The MIT License (MIT)
+
+Copyright (c) 2015-present Rapptz
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+"""
+
+from __future__ import annotations
+
+from typing import Literal, TypedDict
+
+from .guild import InviteGuild, _GuildPreviewUnique
+from .channel import PartialChannel
+from .user import PartialUser
+
+TargetUserType = Literal[1]
+
+
+class _InviteOptional(TypedDict, total=False):
+ guild: InviteGuild
+ inviter: PartialUser
+ target_user: PartialUser
+ target_user_type: TargetUserType
+
+
+class _InviteMetadata(TypedDict, total=False):
+ uses: int
+ max_uses: int
+ temporary: bool
+ created_at: str
+
+
+class IncompleteInvite(_InviteMetadata):
+ code: str
+ channel: PartialChannel
+
+
+class Invite(IncompleteInvite, _InviteOptional):
+ ...
+
+
+class InviteWithCounts(Invite, _GuildPreviewUnique):
+ ...
diff --git a/discord/types/template.py b/discord/types/template.py
new file mode 100644
index 00000000..eaf5e756
--- /dev/null
+++ b/discord/types/template.py
@@ -0,0 +1,44 @@
+"""
+The MIT License (MIT)
+
+Copyright (c) 2015-present Rapptz
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+"""
+
+from __future__ import annotations
+
+from typing import Optional, TypedDict
+from .snowflake import Snowflake
+from .user import User
+from .guild import Guild
+
+
+class Template(TypedDict):
+ code: str
+ name: str
+ description: Optional[str]
+ usage_count: int
+ creator_id: Snowflake
+ creator: User
+ created_at: str
+ updated_at: str
+ source_guild_id: Snowflake
+ serialized_source_guild: Guild
+ is_dirty: Optional[bool]