diff --git a/discord/client.py b/discord/client.py index 1e46be052..587137efd 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 a3343686d..c5ba0d1a2 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 3c0617503..d8bd554fe 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'' ) - 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 af2d0dc4d..5b9ad871c 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'