diff --git a/discord/role.py b/discord/role.py index 0d5c8bcd2..d0d6be062 100644 --- a/discord/role.py +++ b/discord/role.py @@ -22,19 +22,30 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import Optional, Union, overload +from __future__ import annotations +from typing import Any, List, Optional, TypeVar, Union, overload, TYPE_CHECKING from .permissions import Permissions from .errors import InvalidArgument from .colour import Colour from .mixins import Hashable -from .utils import snowflake_time, _get_as_snowflake +from .utils import snowflake_time, _get_as_snowflake, MISSING __all__ = ( 'RoleTags', 'Role', ) +if TYPE_CHECKING: + import datetime + from .types.role import ( + Role as RolePayload, + RoleTags as RoleTagPayload, + ) + from .guild import Guild + from .member import Member + from .state import ConnectionState + class RoleTags: """Represents tags on a role. @@ -61,34 +72,37 @@ class RoleTags: '_premium_subscriber', ) - def __init__(self, data): - self.bot_id = _get_as_snowflake(data, 'bot_id') - self.integration_id = _get_as_snowflake(data, 'integration_id') + def __init__(self, data: RoleTagPayload): + self.bot_id: Optional[int] = _get_as_snowflake(data, 'bot_id') + self.integration_id: Optional[int] = _get_as_snowflake(data, 'integration_id') # NOTE: The API returns "null" for this if it's valid, which corresponds to None. # This is different from other fields where "null" means "not there". # So in this case, a value of None is the same as True. - # Which means we would need a different sentinel. For this purpose I used ellipsis. - self._premium_subscriber = data.get('premium_subscriber', ...) + # Which means we would need a different sentinel. + self._premium_subscriber: Optional[Any] = data.get('premium_subscriber', MISSING) - def is_bot_managed(self): + def is_bot_managed(self) -> bool: """:class:`bool`: Whether the role is associated with a bot.""" return self.bot_id is not None - def is_premium_subscriber(self): + def is_premium_subscriber(self) -> bool: """:class:`bool`: Whether the role is the premium subscriber, AKA "boost", role for the guild.""" return self._premium_subscriber is None - def is_integration(self): + def is_integration(self) -> bool: """:class:`bool`: Whether the role is managed by an integration.""" return self.integration_id is not None - def __repr__(self): + def __repr__(self) -> str: return ( f'' ) +R = TypeVar('R', bound='Role') + + class Role(Hashable): """Represents a Discord role in a :class:`Guild`. @@ -171,19 +185,19 @@ class Role(Hashable): '_state', ) - def __init__(self, *, guild, state, data): - self.guild = guild - self._state = state - self.id = int(data['id']) + def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload): + self.guild: Guild = guild + self._state: ConnectionState = state + self.id: int = int(data['id']) self._update(data) - def __str__(self): + def __str__(self) -> str: return self.name - def __repr__(self): + def __repr__(self) -> str: return f'' - def __lt__(self, other): + def __lt__(self: R, other: R) -> bool: if not isinstance(other, Role) or not isinstance(self, Role): return NotImplemented @@ -204,99 +218,96 @@ class Role(Hashable): return False - def __le__(self, other): + def __le__(self: R, other: R) -> bool: r = Role.__lt__(other, self) if r is NotImplemented: return NotImplemented return not r - def __gt__(self, other): + def __gt__(self: R, other: R) -> bool: return Role.__lt__(other, self) - def __ge__(self, other): + def __ge__(self: R, other: R) -> bool: r = Role.__lt__(self, other) if r is NotImplemented: return NotImplemented return not r - def _update(self, data): - self.name = data['name'] - self._permissions = int(data.get('permissions', 0)) - self.position = data.get('position', 0) - self._colour = data.get('color', 0) - self.hoist = data.get('hoist', False) - self.managed = data.get('managed', False) - self.mentionable = data.get('mentionable', False) + def _update(self, data: RolePayload): + self.name: str = data['name'] + self._permissions: int = int(data.get('permissions', 0)) + self.position: int = data.get('position', 0) + self._colour: int = data.get('color', 0) + self.hoist: bool = data.get('hoist', False) + self.managed: bool = data.get('managed', False) + self.mentionable: bool = data.get('mentionable', False) + self.tags: Optional[RoleTags] try: self.tags = RoleTags(data['tags']) except KeyError: self.tags = None - def is_default(self): + def is_default(self) -> bool: """:class:`bool`: Checks if the role is the default role.""" return self.guild.id == self.id - def is_bot_managed(self): + def is_bot_managed(self) -> bool: """:class:`bool`: Whether the role is associated with a bot. .. versionadded:: 1.6 """ return self.tags is not None and self.tags.is_bot_managed() - def is_premium_subscriber(self): + def is_premium_subscriber(self) -> bool: """:class:`bool`: Whether the role is the premium subscriber, AKA "boost", role for the guild. .. versionadded:: 1.6 """ return self.tags is not None and self.tags.is_premium_subscriber() - def is_integration(self): + def is_integration(self) -> bool: """:class:`bool`: Whether the role is managed by an integration. .. versionadded:: 1.6 """ return self.tags is not None and self.tags.is_integration() - def is_assignable(self): + def is_assignable(self) -> bool: """:class:`bool`: Whether the role is able to be assigned or removed by the bot. .. versionadded:: 2.0 """ me = self.guild.me - return ( - not self.is_default() - and not self.managed - and (me.top_role > self or me.id == self.guild.owner_id) - ) + return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) @property - def permissions(self): + def permissions(self) -> Permissions: """:class:`Permissions`: Returns the role's permissions.""" return Permissions(self._permissions) @property - def colour(self): + def colour(self) -> Colour: """:class:`Colour`: Returns the role colour. An alias exists under ``color``.""" return Colour(self._colour) @property - def color(self): + def color(self) -> Colour: """:class:`Colour`: Returns the role color. An alias exists under ``colour``.""" return self.colour @property - def created_at(self): + def created_at(self) -> datetime.datetime: """:class:`datetime.datetime`: Returns the role's creation time in UTC.""" return snowflake_time(self.id) @property - def mention(self): + def mention(self) -> str: """:class:`str`: Returns a string that allows you to mention a role.""" return f'<@&{self.id}>' @property - def members(self): + def members(self) -> List[Member]: """List[:class:`Member`]: Returns all the members with this role.""" all_members = self.guild.members if self.is_default(): @@ -305,7 +316,7 @@ class Role(Hashable): role_id = self.id return [member for member in all_members if member._roles.has(role_id)] - async def _move(self, position, reason): + async def _move(self, position: int, reason: Optional[str]) -> None: if position <= 0: raise InvalidArgument("Cannot move role to position 0 or below") @@ -346,7 +357,7 @@ class Role(Hashable): async def edit(self) -> None: ... - async def edit(self, *, reason=None, **fields): + async def edit(self, *, reason=None, **fields) -> None: """|coro| Edits the role. @@ -412,7 +423,7 @@ class Role(Hashable): data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload) self._update(data) - async def delete(self, *, reason: Optional[str] = None): + async def delete(self, *, reason: Optional[str] = None) -> None: """|coro| Deletes the role.