diff --git a/discord/flags.py b/discord/flags.py index 0ca6e34db..fea9f243b 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -42,6 +42,8 @@ BF = TypeVar('BF', bound='BaseFlags') class flag_value(Generic[BF]): + __slots__ = ('flag', '__doc__') + def __init__(self, func: Callable[[Any], int]): self.flag = func(None) self.__doc__ = func.__doc__ @@ -67,7 +69,7 @@ class flag_value(Generic[BF]): class alias_flag_value(flag_value): - pass + __slots__ = () def fill_with_flags(*, inverted: bool = False): diff --git a/discord/permissions.py b/discord/permissions.py index 442f93fc5..dc8f0feaa 100644 --- a/discord/permissions.py +++ b/discord/permissions.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 Callable, Any, ClassVar, Dict, Iterator, Set, TYPE_CHECKING, Tuple, Type, TypeVar, Optional from .flags import BaseFlags, flag_value, fill_with_flags, alias_flag_value __all__ = ( @@ -32,15 +35,20 @@ __all__ = ( # A permission alias works like a regular flag but is marked # So the PermissionOverwrite knows to work with it class permission_alias(alias_flag_value): - pass + __slots__ = ('alias',) + alias: str + -def make_permission_alias(alias): - def decorator(func): +def make_permission_alias(alias: str) -> Callable[[Callable[[Any], int]], permission_alias]: + def decorator(func: Callable[[Any], int]) -> permission_alias: ret = permission_alias(func) ret.alias = alias return ret + return decorator +P = TypeVar('P', bound='Permissions') + @fill_with_flags() class Permissions(BaseFlags): """Wraps up the Discord permission value. @@ -92,7 +100,7 @@ class Permissions(BaseFlags): __slots__ = () - def __init__(self, permissions=0, **kwargs): + def __init__(self, permissions: int = 0, **kwargs: bool): if not isinstance(permissions, int): raise TypeError(f'Expected int parameter, received {permissions.__class__.__name__} instead.') @@ -102,25 +110,25 @@ class Permissions(BaseFlags): raise TypeError(f'{key!r} is not a valid permission name.') setattr(self, key, value) - def is_subset(self, other): + def is_subset(self, other: Permissions) -> bool: """Returns ``True`` if self has the same or fewer permissions as other.""" if isinstance(other, Permissions): return (self.value & other.value) == self.value else: raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}") - def is_superset(self, other): + def is_superset(self, other: Permissions) -> bool: """Returns ``True`` if self has the same or more permissions as other.""" if isinstance(other, Permissions): return (self.value | other.value) == self.value else: raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}") - def is_strict_subset(self, other): + def is_strict_subset(self, other: Permissions) -> bool: """Returns ``True`` if the permissions on other are a strict subset of those on self.""" return self.is_subset(other) and self != other - def is_strict_superset(self, other): + def is_strict_superset(self, other: Permissions) -> bool: """Returns ``True`` if the permissions on other are a strict superset of those on self.""" return self.is_superset(other) and self != other @@ -130,20 +138,20 @@ class Permissions(BaseFlags): __gt__ = is_strict_superset @classmethod - def none(cls): + def none(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all permissions set to ``False``.""" return cls(0) @classmethod - def all(cls): + def all(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all permissions set to ``True``. """ return cls(0b111111111111111111111111111111111111) @classmethod - def all_channel(cls): + def all_channel(cls: Type[P]) -> P: """A :class:`Permissions` with all channel-specific permissions set to ``True`` and the guild-specific ones set to ``False``. The guild-specific permissions are currently: @@ -164,7 +172,7 @@ class Permissions(BaseFlags): return cls(0b10110011111101111111111101010001) @classmethod - def general(cls): + def general(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "General" permissions from the official Discord UI set to ``True``. @@ -177,7 +185,7 @@ class Permissions(BaseFlags): return cls(0b01110000000010000000010010110000) @classmethod - def membership(cls): + def membership(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Membership" permissions from the official Discord UI set to ``True``. @@ -186,7 +194,7 @@ class Permissions(BaseFlags): return cls(0b00001100000000000000000000000111) @classmethod - def text(cls): + def text(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Text" permissions from the official Discord UI set to ``True``. @@ -197,13 +205,13 @@ class Permissions(BaseFlags): return cls(0b10000000000001111111100001000000) @classmethod - def voice(cls): + def voice(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Voice" permissions from the official Discord UI set to ``True``.""" return cls(0b00000011111100000000001100000000) @classmethod - def stage(cls): + def stage(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Stage Channel" permissions from the official Discord UI set to ``True``. @@ -212,7 +220,7 @@ class Permissions(BaseFlags): return cls(1 << 32) @classmethod - def stage_moderator(cls): + def stage_moderator(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Stage Moderator" permissions from the official Discord UI set to ``True``. @@ -221,7 +229,7 @@ class Permissions(BaseFlags): return cls(0b100000001010000000000000000000000) @classmethod - def advanced(cls): + def advanced(cls: Type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Advanced" permissions from the official Discord UI set to ``True``. @@ -229,7 +237,7 @@ class Permissions(BaseFlags): """ return cls(1 << 3) - def update(self, **kwargs): + def update(self, **kwargs: bool) -> None: r"""Bulk updates this permission object. Allows you to set multiple attributes by using keyword @@ -245,7 +253,7 @@ class Permissions(BaseFlags): if key in self.VALID_FLAGS: setattr(self, key, value) - def handle_overwrite(self, allow, deny): + def handle_overwrite(self, allow: int, deny: int) -> None: # Basically this is what's happening here. # We have an original bit array, e.g. 1010 # Then we have another bit array that is 'denied', e.g. 1111 @@ -261,22 +269,22 @@ class Permissions(BaseFlags): self.value = (self.value & ~deny) | allow @flag_value - def create_instant_invite(self): + def create_instant_invite(self) -> int: """:class:`bool`: Returns ``True`` if the user can create instant invites.""" return 1 << 0 @flag_value - def kick_members(self): + def kick_members(self) -> int: """:class:`bool`: Returns ``True`` if the user can kick users from the guild.""" return 1 << 1 @flag_value - def ban_members(self): + def ban_members(self) -> int: """:class:`bool`: Returns ``True`` if a user can ban users from the guild.""" return 1 << 2 @flag_value - def administrator(self): + def administrator(self) -> int: """:class:`bool`: Returns ``True`` if a user is an administrator. This role overrides all other permissions. This also bypasses all channel-specific overrides. @@ -284,44 +292,44 @@ class Permissions(BaseFlags): return 1 << 3 @flag_value - def manage_channels(self): + def manage_channels(self) -> int: """:class:`bool`: Returns ``True`` if a user can edit, delete, or create channels in the guild. This also corresponds to the "Manage Channel" channel-specific override.""" return 1 << 4 @flag_value - def manage_guild(self): + def manage_guild(self) -> int: """:class:`bool`: Returns ``True`` if a user can edit guild properties.""" return 1 << 5 @flag_value - def add_reactions(self): + def add_reactions(self) -> int: """:class:`bool`: Returns ``True`` if a user can add reactions to messages.""" return 1 << 6 @flag_value - def view_audit_log(self): + def view_audit_log(self) -> int: """:class:`bool`: Returns ``True`` if a user can view the guild's audit log.""" return 1 << 7 @flag_value - def priority_speaker(self): + def priority_speaker(self) -> int: """:class:`bool`: Returns ``True`` if a user can be more easily heard while talking.""" return 1 << 8 @flag_value - def stream(self): + def stream(self) -> int: """:class:`bool`: Returns ``True`` if a user can stream in a voice channel.""" return 1 << 9 @flag_value - def read_messages(self): + def read_messages(self) -> int: """:class:`bool`: Returns ``True`` if a user can read messages from all or specific text channels.""" return 1 << 10 @make_permission_alias('read_messages') - def view_channel(self): + def view_channel(self) -> int: """:class:`bool`: An alias for :attr:`read_messages`. .. versionadded:: 1.3 @@ -329,17 +337,17 @@ class Permissions(BaseFlags): return 1 << 10 @flag_value - def send_messages(self): + def send_messages(self) -> int: """:class:`bool`: Returns ``True`` if a user can send messages from all or specific text channels.""" return 1 << 11 @flag_value - def send_tts_messages(self): + def send_tts_messages(self) -> int: """:class:`bool`: Returns ``True`` if a user can send TTS messages from all or specific text channels.""" return 1 << 12 @flag_value - def manage_messages(self): + def manage_messages(self) -> int: """:class:`bool`: Returns ``True`` if a user can delete or pin messages in a text channel. .. note:: @@ -349,32 +357,32 @@ class Permissions(BaseFlags): return 1 << 13 @flag_value - def embed_links(self): + def embed_links(self) -> int: """:class:`bool`: Returns ``True`` if a user's messages will automatically be embedded by Discord.""" return 1 << 14 @flag_value - def attach_files(self): + def attach_files(self) -> int: """:class:`bool`: Returns ``True`` if a user can send files in their messages.""" return 1 << 15 @flag_value - def read_message_history(self): + def read_message_history(self) -> int: """:class:`bool`: Returns ``True`` if a user can read a text channel's previous messages.""" return 1 << 16 @flag_value - def mention_everyone(self): + def mention_everyone(self) -> int: """:class:`bool`: Returns ``True`` if a user's @everyone or @here will mention everyone in the text channel.""" return 1 << 17 @flag_value - def external_emojis(self): + def external_emojis(self) -> int: """:class:`bool`: Returns ``True`` if a user can use emojis from other guilds.""" return 1 << 18 @make_permission_alias('external_emojis') - def use_external_emojis(self): + def use_external_emojis(self) -> int: """:class:`bool`: An alias for :attr:`external_emojis`. .. versionadded:: 1.3 @@ -382,7 +390,7 @@ class Permissions(BaseFlags): return 1 << 18 @flag_value - def view_guild_insights(self): + def view_guild_insights(self) -> int: """:class:`bool`: Returns ``True`` if a user can view the guild's insights. .. versionadded:: 1.3 @@ -390,47 +398,47 @@ class Permissions(BaseFlags): return 1 << 19 @flag_value - def connect(self): + def connect(self) -> int: """:class:`bool`: Returns ``True`` if a user can connect to a voice channel.""" return 1 << 20 @flag_value - def speak(self): + def speak(self) -> int: """:class:`bool`: Returns ``True`` if a user can speak in a voice channel.""" return 1 << 21 @flag_value - def mute_members(self): + def mute_members(self) -> int: """:class:`bool`: Returns ``True`` if a user can mute other users.""" return 1 << 22 @flag_value - def deafen_members(self): + def deafen_members(self) -> int: """:class:`bool`: Returns ``True`` if a user can deafen other users.""" return 1 << 23 @flag_value - def move_members(self): + def move_members(self) -> int: """:class:`bool`: Returns ``True`` if a user can move users between other voice channels.""" return 1 << 24 @flag_value - def use_voice_activation(self): + def use_voice_activation(self) -> int: """:class:`bool`: Returns ``True`` if a user can use voice activation in voice channels.""" return 1 << 25 @flag_value - def change_nickname(self): + def change_nickname(self) -> int: """:class:`bool`: Returns ``True`` if a user can change their nickname in the guild.""" return 1 << 26 @flag_value - def manage_nicknames(self): + def manage_nicknames(self) -> int: """:class:`bool`: Returns ``True`` if a user can change other user's nickname in the guild.""" return 1 << 27 @flag_value - def manage_roles(self): + def manage_roles(self) -> int: """:class:`bool`: Returns ``True`` if a user can create or edit roles less than their role's position. This also corresponds to the "Manage Permissions" channel-specific override. @@ -438,7 +446,7 @@ class Permissions(BaseFlags): return 1 << 28 @make_permission_alias('manage_roles') - def manage_permissions(self): + def manage_permissions(self) -> int: """:class:`bool`: An alias for :attr:`manage_roles`. .. versionadded:: 1.3 @@ -446,17 +454,17 @@ class Permissions(BaseFlags): return 1 << 28 @flag_value - def manage_webhooks(self): + def manage_webhooks(self) -> int: """:class:`bool`: Returns ``True`` if a user can create, edit, or delete webhooks.""" return 1 << 29 @flag_value - def manage_emojis(self): + def manage_emojis(self) -> int: """:class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis.""" return 1 << 30 @flag_value - def use_slash_commands(self): + def use_slash_commands(self) -> int: """:class:`bool`: Returns ``True`` if a user can use slash commands. .. versionadded:: 1.7 @@ -464,7 +472,7 @@ class Permissions(BaseFlags): return 1 << 31 @flag_value - def request_to_speak(self): + def request_to_speak(self) -> int: """:class:`bool`: Returns ``True`` if a user can request to speak in a stage channel. .. versionadded:: 1.7 @@ -472,7 +480,7 @@ class Permissions(BaseFlags): return 1 << 32 @flag_value - def manage_events(self): + def manage_events(self) -> int: """:class:`bool`: Returns ``True`` if a user can manage guild events. .. versionadded:: 2.0 @@ -480,7 +488,7 @@ class Permissions(BaseFlags): return 1 << 33 @flag_value - def manage_threads(self): + def manage_threads(self) -> int: """:class:`bool`: Returns ``True`` if a user can manage threads. .. versionadded:: 2.0 @@ -488,7 +496,7 @@ class Permissions(BaseFlags): return 1 << 34 @flag_value - def use_threads(self): + def use_threads(self) -> int: """:class:`bool`: Returns ``True`` if a user can create and participate in public threads. .. versionadded:: 2.0 @@ -496,15 +504,16 @@ class Permissions(BaseFlags): return 1 << 35 @flag_value - def use_private_threads(self): + def use_private_threads(self) -> int: """:class:`bool`: Returns ``True`` if a user can create and participate in private threads. .. versionadded:: 2.0 """ return 1 << 36 +PO = TypeVar('PO', bound='PermissionOverwrite') -def augment_from_permissions(cls): +def _augment_from_permissions(cls): cls.VALID_NAMES = set(Permissions.VALID_FLAGS) aliases = set() @@ -521,6 +530,7 @@ def augment_from_permissions(cls): # god bless Python def getter(self, x=key): return self._values.get(x) + def setter(self, value, x=key): self._set(x, value) @@ -530,7 +540,8 @@ def augment_from_permissions(cls): cls.PURE_FLAGS = cls.VALID_NAMES - aliases return cls -@augment_from_permissions + +@_augment_from_permissions class PermissionOverwrite: r"""A type that is used to represent a channel specific permission. @@ -565,8 +576,53 @@ class PermissionOverwrite: __slots__ = ('_values',) - def __init__(self, **kwargs): - self._values = {} + if TYPE_CHECKING: + VALID_NAMES: ClassVar[Set[str]] + PURE_FLAGS: ClassVar[Set[str]] + # I wish I didn't have to do this + create_instant_invite: Optional[bool] + kick_members: Optional[bool] + ban_members: Optional[bool] + administrator: Optional[bool] + manage_channels: Optional[bool] + manage_guild: Optional[bool] + add_reactions: Optional[bool] + view_audit_log: Optional[bool] + priority_speaker: Optional[bool] + stream: Optional[bool] + read_messages: Optional[bool] + view_channel: Optional[bool] + send_messages: Optional[bool] + send_tts_messages: Optional[bool] + manage_messages: Optional[bool] + embed_links: Optional[bool] + attach_files: Optional[bool] + read_message_history: Optional[bool] + mention_everyone: Optional[bool] + external_emojis: Optional[bool] + use_external_emojis: Optional[bool] + view_guild_insights: Optional[bool] + connect: Optional[bool] + speak: Optional[bool] + mute_members: Optional[bool] + deafen_members: Optional[bool] + move_members: Optional[bool] + use_voice_activation: Optional[bool] + change_nickname: Optional[bool] + manage_nicknames: Optional[bool] + manage_roles: Optional[bool] + manage_permissions: Optional[bool] + manage_webhooks: Optional[bool] + manage_emojis: Optional[bool] + use_slash_commands: Optional[bool] + request_to_speak: Optional[bool] + manage_events: Optional[bool] + manage_threads: Optional[bool] + use_threads: Optional[bool] + use_private_threads: Optional[bool] + + def __init__(self, **kwargs: Optional[bool]): + self._values: Dict[str, Optional[bool]] = {} for key, value in kwargs.items(): if key not in self.VALID_NAMES: @@ -574,10 +630,10 @@ class PermissionOverwrite: setattr(self, key, value) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return isinstance(other, PermissionOverwrite) and self._values == other._values - def _set(self, key, value): + def _set(self, key: str, value: Optional[bool]) -> None: if value not in (True, None, False): raise TypeError(f'Expected bool or NoneType, received {value.__class__.__name__}') @@ -601,7 +657,7 @@ class PermissionOverwrite: return allow, deny @classmethod - def from_pair(cls, allow, deny): + def from_pair(cls: Type[PO], allow: Permissions, deny: Permissions) -> PO: """Creates an overwrite from an allow/deny pair of :class:`Permissions`.""" ret = cls() for key, value in allow: @@ -614,7 +670,7 @@ class PermissionOverwrite: return ret - def is_empty(self): + def is_empty(self) -> bool: """Checks if the permission overwrite is currently empty. An empty permission overwrite is one that has no overwrites set @@ -627,7 +683,7 @@ class PermissionOverwrite: """ return len(self._values) == 0 - def update(self, **kwargs): + def update(self, **kwargs: bool) -> None: r"""Bulk updates this permission overwrite object. Allows you to set multiple attributes by using keyword @@ -645,6 +701,6 @@ class PermissionOverwrite: setattr(self, key, value) - def __iter__(self): + def __iter__(self) -> Iterator[Tuple[str, Optional[bool]]]: for key in self.PURE_FLAGS: yield key, self._values.get(key)