From 06908ba346e1d8104c8d55dab8f306be06e65d6d Mon Sep 17 00:00:00 2001 From: Nekokatt Date: Sun, 4 Oct 2020 14:15:20 +0100 Subject: [PATCH] Task/intflag (#265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Started IntFlagMeta. * _IntFlagNamespace getitem/setitem/contains operators. * Initial intflag implementation completed (other than operators). Average times: ``` Making highest priority, os.SCHED_RR sys.getswitchinterval 0.5 sched_getscheduler 2 sched_getparam posix.sched_param(sched_priority=1) sched_getaffinity {7} sched_getprioritymax 0 sched_getprioritymin 0 sched_rr_getinterval 0.09999999000000001 nice -20 warming up benchmark starts PyIntFlag.__call__(4) (existing member) 0.4027703850006219 µs BasicHikariEnum.__call__(4) (existing member) 0.11296135399970808 µs PyIntFlag.__call__(71) (new composite member) 18.099853999956395 µs BasicHikariEnum.__call__(71) (new composite member) 2.48106900107814 µs PyIntFlag.__call__(71) (existing composite member) 0.39969006100000115 µs BasicHikariEnum.__call__(71) (existing composite member) 0.26146456899914483 µs ``` * Added disabling of cache for large flag types like permissions which could use gigabytes of memory for each possible combination. * Adjusted caching strategy for int enums. * Adjusted caching strategy for int enums. * Refined IntFlag implementation, which is now a normal Flag and acts like a set. * Revert "Made flag enums lazily load, speeding up cache and event ops." This reverts commit aecccc5e7f1ed2cb36035cd95710c2b72981e9f2. * Fixed multiple flag bugs. * Flake8 * Fixed edge-case error. * Tidied up some documentation. * Finished testing enums. * Variables now show before methods in docs. * Fixed #256 -- issues with documentation function anchors. * PR feedback. * Fixed a MyPy issue and added an override for symmetricdifference. * Removed commented out lines from mako docs. --- docs/documentation.mako | 54 +- hikari/channels.py | 39 +- hikari/errors.py | 2 +- hikari/guilds.py | 33 +- hikari/impl/bot.py | 2 +- hikari/impl/entity_factory.py | 11 +- hikari/impl/event_factory.py | 4 +- hikari/intents.py | 4 +- hikari/internal/cache.py | 2 +- hikari/internal/enums.py | 656 ++++++++++++++++++-- hikari/internal/enums.pyi | 59 +- hikari/internal/flag.py | 169 ------ hikari/messages.py | 34 +- hikari/permissions.py | 6 +- hikari/presences.py | 21 +- hikari/users.py | 14 +- scripts/enum_benchmark.py | 4 +- scripts/flag_benchmark.py | 150 +++++ tests/hikari/internal/test_enums.py | 907 +++++++++++++++++++++++++++- tests/hikari/internal/test_flag.py | 197 ------ tests/hikari/test_channels.py | 4 +- tests/hikari/test_errors.py | 2 +- tests/hikari/test_permissions.py | 17 - 23 files changed, 1800 insertions(+), 591 deletions(-) delete mode 100644 hikari/internal/flag.py create mode 100644 scripts/flag_benchmark.py delete mode 100644 tests/hikari/internal/test_flag.py diff --git a/docs/documentation.mako b/docs/documentation.mako index 24a4ef8048..6b043eeb48 100644 --- a/docs/documentation.mako +++ b/docs/documentation.mako @@ -99,6 +99,9 @@ from pdoc.html_helpers import extract_toc, glimpse, to_html as _to_html, format_git_link + # Hikari Enum hack + from hikari.internal import enums + # Allow imports to resolve properly. typing.TYPE_CHECKING = True @@ -325,17 +328,17 @@ if hasattr(dobj.cls, "obj"): for cls in dobj.cls.obj.mro(): if (descriptor := cls.__dict__.get(dobj.name)) is not None: - is_descriptor = True + is_descriptor = hasattr(descriptor, "__get__") break - if is_descriptor: + if all(not c.isalpha() or c.isupper() for c in dobj.name): + prefix = f"{prefix}{QUAL_CONST} " + elif is_descriptor: qual = QUAL_CACHED_PROPERTY if isinstance(descriptor, functools.cached_property) else QUAL_PROPERTY prefix = f"{prefix}{qual} " elif dobj.module.name == "typing" or dobj.docstring and dobj.docstring.casefold().startswith(("type hint", "typehint", "type alias")): show_object = not simple_names prefix = f"{prefix}{QUAL_TYPEHINT} " - elif all(not c.isalpha() or c.isupper() for c in dobj.name): - prefix = f"{prefix}{QUAL_CONST} " else: prefix = f"{prefix}{QUAL_VAR} " @@ -347,9 +350,9 @@ elif issubclass(dobj.obj, type): qual += QUAL_METACLASS else: - if enum.Flag in dobj.obj.mro(): + if enums.Flag in dobj.obj.mro() or enum.Flag in dobj.obj.mro(): qual += QUAL_ENUM_FLAG - elif enum.Enum in dobj.obj.mro(): + elif enums.Enum in dobj.obj.mro() or enum.Enum in dobj.obj.mro(): qual += QUAL_ENUM elif hasattr(dobj.obj, "__attrs_attrs__"): qual += QUAL_DATACLASS @@ -401,10 +404,6 @@ extra = f" = {dobj.obj}" classes = [] - if dotted: - classes.append("dotted") - if css_classes: - classes.append(css_classes) class_str = " ".join(classes) if class_str.strip(): @@ -520,7 +519,17 @@ print(v.name, type(ex).__name__, ex) if value: - return_type += f" = {value}" + for enum_mapping in ("_value2member_map_", "_value_to_member_map_"): + if mapping := getattr(v.cls.obj, enum_mapping, None): + try: + real_value = getattr(v.cls.obj, v.name) + if real_value in mapping.values(): + return_type += f" = {real_value.value!r}" + break + except AttributeError: + pass + else: + return_type += f" = {value}" if hasattr(parent, "mro"): name = f"{parent.__module__}.{parent.__qualname__}.{v.name}" @@ -549,6 +558,7 @@ params = f.params(annotate=show_type_annotations, link=link) return_type = get_annotation(f.return_annotation, '->') qual = QUAL_ASYNC_DEF if f._is_async else QUAL_DEF + anchored_name = f'{f.name}' example_str = qual + f.name + "(" + ", ".join(params) + ")" + return_type @@ -557,15 +567,15 @@ if len(params) > 4 or len(params) > 0 and len(example_str) > 70: representation = "\n".join(( - qual + " " + f.name + "(", + qual + " " + anchored_name + "(", *(f" {p}," for p in params), ")" + return_type + ": ..." )) elif params: - representation = f"{qual} {f.name}({', '.join(params)}){return_type}: ..." + representation = f"{qual} {anchored_name}({', '.join(params)}){return_type}: ..." else: - representation = f"{qual} {f.name}(){return_type}: ..." + representation = f"{qual} {anchored_name}(){return_type}: ..." if f.module.name != f.obj.__module__: try: @@ -708,21 +718,21 @@
% endif - % if methods: -
Methods
+ % if variables: +
Variables and properties
- % for m in methods: - ${show_func(m)} + % for i in variables: + ${show_var(i)} % endfor
% endif - % if variables: -
Variables and properties
+ % if methods: +
Methods
- % for i in variables: - ${show_var(i)} + % for m in methods: + ${show_func(m)} % endfor
diff --git a/hikari/channels.py b/hikari/channels.py index 8b0a05bf8f..6b59959deb 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -255,37 +255,24 @@ class PermissionOverwrite(snowflakes.Unique): ) """The type of entity this overwrite targets.""" - # Flags are lazily loaded, due to the IntFlag mechanism being overly slow - # to execute. - _allow: int = attr.ib(default=0, eq=False, hash=False, repr=False) - _deny: int = attr.ib(default=0, eq=False, hash=False, repr=False) - - @property - def allow(self) -> permissions.Permissions: - """Return the permissions this overwrite allows. - - Returns - ------- - hikari.permissions.Permissions - Permissions explicitly allowed by this overwrite. - """ - return permissions.Permissions(self._allow) - - @property - def deny(self) -> permissions.Permissions: - """Return the permissions this overwrite denies. + allow: permissions.Permissions = attr.ib( + converter=permissions.Permissions, + default=permissions.Permissions.NONE, + eq=False, + hash=False, + repr=False, + ) + """The permissions this overwrite allows.""" - Returns - ------- - hikari.permissions.Permissions - Permissions explicitly denied by this overwrite. - """ - return permissions.Permissions(self._deny) + deny: permissions.Permissions = attr.ib( + converter=permissions.Permissions, default=permissions.Permissions.NONE, eq=False, hash=False, repr=False + ) + """The permissions this overwrite denies.""" @property def unset(self) -> permissions.Permissions: """Bitfield of all permissions not explicitly allowed or denied by this overwrite.""" - return permissions.Permissions(~(self.allow | self.deny)) + return ~(self.allow | self.deny) @attr_extensions.with_copy diff --git a/hikari/errors.py b/hikari/errors.py index 98c38e983f..6239cdbe0b 100644 --- a/hikari/errors.py +++ b/hikari/errors.py @@ -527,4 +527,4 @@ class MissingIntentError(HikariError, ValueError): """The combination of intents that are missing.""" def __str__(self) -> str: - return f"You are missing the following intent(s): {str(self.intents)}" + return "You are missing the following intent(s): " + ", ".join(map(str, self.intents.split())) diff --git a/hikari/guilds.py b/hikari/guilds.py index 9fc8ac4a80..ee457110e8 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -49,7 +49,6 @@ ] import abc -import enum import typing import attr @@ -60,7 +59,6 @@ from hikari import users from hikari.internal import attr_extensions from hikari.internal import enums -from hikari.internal import flag from hikari.internal import routes if typing.TYPE_CHECKING: @@ -220,9 +218,8 @@ def __str__(self) -> str: return self.name -@enum.unique @typing.final -class GuildSystemChannelFlag(flag.Flag): +class GuildSystemChannelFlag(enums.Flag): """Defines which features are suppressed in the system channel.""" NONE = 0 @@ -921,6 +918,17 @@ class Guild(PartialGuild, abc.ABC): splash_hash: typing.Optional[str] = attr.ib(eq=False, hash=False, repr=False) """The hash of the splash for the guild, if there is one.""" + system_channel_flags: GuildSystemChannelFlag = attr.ib(eq=False, hash=False, repr=False) + """Return flags for the guild system channel. + + These are used to describe which notifications are suppressed. + + Returns + ------- + GuildSystemChannelFlag + The system channel flags for this channel. + """ + system_channel_id: typing.Optional[snowflakes.Snowflake] = attr.ib(eq=False, hash=False, repr=False) """The ID of the system channel or `builtins.None` if it is not enabled. @@ -944,10 +952,6 @@ class Guild(PartialGuild, abc.ABC): this will be `builtins.None`. """ - # Flags are lazily loaded, due to the IntFlag mechanism being overly slow - # to execute. - _system_channel_flags: int = attr.ib(eq=False, hash=False, repr=False) - @property def banner_url(self) -> typing.Optional[files.URL]: """Banner for the guild, if set.""" @@ -985,19 +989,6 @@ def splash_url(self) -> typing.Optional[files.URL]: """Splash for the guild, if set.""" return self.format_splash() - @property - def system_channel_flags(self) -> GuildSystemChannelFlag: - """Return flags for the guild system channel. - - These are used to describe which notifications are suppressed. - - Returns - ------- - GuildSystemChannelFlag - The system channel flags for this channel. - """ - return GuildSystemChannelFlag(self._system_channel_flags) - def format_banner(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: """Generate the guild's banner image, if set. diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py index a48ab4399b..3fc185803f 100644 --- a/hikari/impl/bot.py +++ b/hikari/impl/bot.py @@ -869,7 +869,7 @@ async def start( await self.dispatch(lifetime_events.StartedEvent(app=self)) - _LOGGER.info("application started successfully in approx %.0f seconds", time.monotonic() - start_time) + _LOGGER.info("application started successfully in approx %.2f seconds", time.monotonic() - start_time) def stream( self, diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 066d54dd9d..2af988bd81 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -121,7 +121,7 @@ class _GuildFields(_PartialGuildFields): widget_channel_id: typing.Optional[snowflakes.Snowflake] = attr.ib() system_channel_id: typing.Optional[snowflakes.Snowflake] = attr.ib() is_widget_enabled: typing.Optional[bool] = attr.ib() - system_channel_flags: int = attr.ib() + system_channel_flags: guild_models.GuildSystemChannelFlag = attr.ib() rules_channel_id: typing.Optional[snowflakes.Snowflake] = attr.ib() max_video_channel_users: typing.Optional[int] = attr.ib() vanity_url_code: typing.Optional[str] = attr.ib() @@ -191,8 +191,8 @@ def __init__(self, app: traits.RESTAware) -> None: audit_log_models.AuditLogChangeKey.APPLICATION_ID: snowflakes.Snowflake, audit_log_models.AuditLogChangeKey.PERMISSIONS: permission_models.Permissions, audit_log_models.AuditLogChangeKey.COLOR: color_models.Color, - audit_log_models.AuditLogChangeKey.ALLOW: int, - audit_log_models.AuditLogChangeKey.DENY: int, + audit_log_models.AuditLogChangeKey.ALLOW: permission_models.Permissions, + audit_log_models.AuditLogChangeKey.DENY: permission_models.Permissions, audit_log_models.AuditLogChangeKey.CHANNEL_ID: snowflakes.Snowflake, audit_log_models.AuditLogChangeKey.INVITER_ID: snowflakes.Snowflake, audit_log_models.AuditLogChangeKey.MAX_USES: _deserialize_max_uses, @@ -1754,6 +1754,9 @@ def _set_user_attributes(payload: data_binding.JSONObject) -> _UserFields: def deserialize_user(self, payload: data_binding.JSONObject) -> user_models.User: user_fields = self._set_user_attributes(payload) + flags = ( + user_models.UserFlag(payload["public_flags"]) if "public_flags" in payload else user_models.UserFlag.NONE + ) return user_models.UserImpl( app=self._app, id=user_fields.id, @@ -1762,7 +1765,7 @@ def deserialize_user(self, payload: data_binding.JSONObject) -> user_models.User avatar_hash=user_fields.avatar_hash, is_bot=user_fields.is_bot, is_system=user_fields.is_system, - flags=int(payload.get("public_flags", 0)), + flags=flags, ) def deserialize_my_user(self, payload: data_binding.JSONObject) -> user_models.OwnUser: diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py index e4d7f1c092..cad471fc6e 100644 --- a/hikari/impl/event_factory.py +++ b/hikari/impl/event_factory.py @@ -319,9 +319,9 @@ def deserialize_presence_update_event( if len(user_payload) > 1: # PartialUser discriminator = user_payload["discriminator"] if "discriminator" in user_payload else undefined.UNDEFINED - flags: undefined.UndefinedOr[int] = undefined.UNDEFINED + flags: undefined.UndefinedOr[user_models.UserFlag] = undefined.UNDEFINED if "public_flags" in user_payload: - flags = int(user_payload["public_flags"]) + flags = user_models.UserFlag(user_payload["public_flags"]) user = user_models.PartialUserImpl( app=self._app, diff --git a/hikari/intents.py b/hikari/intents.py index 9b6f849064..7150baa91f 100644 --- a/hikari/intents.py +++ b/hikari/intents.py @@ -27,11 +27,11 @@ import typing -from hikari.internal import flag +from hikari.internal import enums @typing.final -class Intents(flag.Flag): +class Intents(enums.Flag): """Represents an intent on the gateway. This is a bitfield representation of all the categories of event diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py index fee8c62065..171e108c52 100644 --- a/hikari/internal/cache.py +++ b/hikari/internal/cache.py @@ -493,7 +493,7 @@ class RichActivityData(BaseData[presences.RichActivity]): assets: typing.Optional[presences.ActivityAssets] = attr.ib() secrets: typing.Optional[presences.ActivitySecret] = attr.ib() is_instance: typing.Optional[bool] = attr.ib() - flags: typing.Optional[int] = attr.ib() + flags: typing.Optional[presences.ActivityFlag] = attr.ib() @classmethod def build_from_entity(cls: typing.Type[RichActivityData], entity: presences.RichActivity) -> RichActivityData: diff --git a/hikari/internal/enums.py b/hikari/internal/enums.py index 314f52d42b..bc2f3f8395 100644 --- a/hikari/internal/enums.py +++ b/hikari/internal/enums.py @@ -19,17 +19,19 @@ # 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. -"""Implementation of parts of Python's `enum` protocol to be faster.""" +"""Implementation of parts of Python's `enum` protocol to be more performant.""" from __future__ import annotations -__all__: typing.List[str] = ["Enum"] +__all__: typing.List[str] = ["Enum", "Flag"] -import os +import functools +import operator import sys import types import typing _T = typing.TypeVar("_T") +_MAX_CACHED_MEMBERS: typing.Final[int] = 1 << 12 class _EnumNamespace(typing.Dict[str, typing.Any]): @@ -109,73 +111,51 @@ def __setitem__(self, name: str, value: typing.Any) -> None: _Enum = NotImplemented -def _attr_mutator(self, *_: typing.Any) -> typing.NoReturn: - raise TypeError("Cannot mutate enum members") - - class _EnumMeta(type): def __call__(cls, value: typing.Any) -> typing.Any: try: - return cls._value2member_map_[value] + return cls._value_to_member_map_[value] except KeyError: # If we cant find the value, just return what got casted in return value - def __dir__(cls) -> typing.List[str]: - members = ["__class__", "__doc__", "__members__", "__module__"] - try: - members += list(cls._name2member_map_) - finally: - return members - - def __getattr__(cls, name: str) -> typing.Any: - if name.startswith("_") and name.endswith("_"): - # Stop recursion errors by trying to look up _name2member_map_ - # recursively. - raise AttributeError(name) - try: - return cls._name2member_map_[name] - except KeyError: - try: - return super().__getattribute__(name) - except AttributeError: - raise AttributeError(name) from None - def __getitem__(cls, name: str) -> typing.Any: - return cls._name2member_map_[name] + return cls._name_to_member_map_[name] def __iter__(cls) -> typing.Iterator[str]: - yield cls._name2member_map_ + yield from cls._name_to_member_map_ @staticmethod def __new__( mcs: typing.Type[_T], name: str, bases: typing.Tuple[typing.Type[typing.Any], ...], - namespace: _EnumNamespace, + namespace: typing.Union[typing.Dict[str, typing.Any], _EnumNamespace], ) -> _T: global _Enum - if name == "Enum" and _Enum is NotImplemented: + if _Enum is NotImplemented: # noinspection PyRedundantParentheses return (_Enum := super().__new__(mcs, name, bases, namespace)) - try: - base, enum_type = bases - except ValueError: - raise TypeError("Expected two base classes for an enum") from None + assert isinstance(namespace, _EnumNamespace) - if not issubclass(enum_type, _Enum): - raise TypeError("second base type for enum must be derived from Enum") + base, enum_type = bases new_namespace = { "__objtype__": base, "__enumtype__": enum_type, - "_name2member_map_": (name2member := {}), - "_value2member_map_": (value2member := {}), + "_name_to_member_map_": (name_to_member := {}), + "_value_to_member_map_": (value_to_member := {}), + "_member_names_": (member_names := []), # Required to be immutable by enum API itself. "__members__": types.MappingProxyType(namespace.names_to_values), **namespace, + **{ + name: value + for name, value in Enum.__dict__.items() + if name not in ("__class__", "__module__", "__doc__") + }, } cls = super().__new__(mcs, name, bases, new_namespace) @@ -187,18 +167,24 @@ def __new__( # invoke cls.__init__ if we do this, so we end up with two function # calls. member = cls.__new__(cls, value) - member.name = name - member.value = value - name2member[name] = member - value2member[value] = member - - cls.__setattr__ = _attr_mutator - cls.__delattr__ = _attr_mutator + member._name_ = name + member._value_ = value + name_to_member[name] = member + value_to_member[value] = member + member_names.append(name) + setattr(cls, name, member) return cls @classmethod - def __prepare__(mcs, name: str, bases: typing.Tuple[typing.Type[typing.Any], ...] = ()) -> _EnumNamespace: + def __prepare__( + mcs, name: str, bases: typing.Tuple[typing.Type[typing.Any], ...] = () + ) -> typing.Union[typing.Dict[str, typing.Any], _EnumNamespace]: + if _Enum is NotImplemented: + if name != "Enum": + raise TypeError("First instance of _EnumMeta must be Enum") + return {} + try: # Fails if Enum is not defined. We check this in `__new__` properly. base, enum_type = bases @@ -208,9 +194,14 @@ def __prepare__(mcs, name: str, bases: typing.Tuple[typing.Type[typing.Any], ... if not isinstance(enum_type, _EnumMeta): raise TypeError("Second base to an enum must be the enum type (derived from _EnumMeta) to be used") + if not issubclass(enum_type, _Enum): + raise TypeError("second base type for enum must be derived from Enum") + return _EnumNamespace(base) except ValueError: - return _EnumNamespace(object) + if name == "Enum" and _Enum is NotImplemented: + return _EnumNamespace(object) + raise TypeError("Expected two base classes for an enum") from None def __repr__(cls) -> str: return f"" @@ -219,18 +210,567 @@ def __repr__(cls) -> str: class Enum(metaclass=_EnumMeta): - """Re-implementation of parts of Python's `enum` to be faster.""" - - def __getattr__(self, name: str) -> typing.Any: - return getattr(self.value, name) + """Clone of Python's `enum.Enum` implementation. + + This is designed to be faster and more efficient than Python's + implementation, while retaining the majority of the external interface + that Python's `enum.Enum` provides. + + An `Enum` is a simple class containing a discrete set of constant values + that can be used in place of this type. This acts as a type-safe way + of representing a set number of "things". + + !!! warning + Some semantics such as subtype checking and instance checking may + differ. It is recommended to compare these values using the + `==` operator rather than the `is` operator for safety reasons. + + Special Members on the class + ---------------------------- + * `__enumtype__` : + Always `Enum`. + * `__members__` : + An immutable `typing.Mapping` that maps each member name to the member + value. + * ` __objtype__` : + Always the first type that the enum is derived from. For example: + + ```py + >>> class UserType(str, Enum): + ... USER = "user" + ... PARTIAL = "partial" + ... MEMBER = "member" + >>> print(UserType.__objtype__) + + ``` + + Operators on the class + ---------------------- + * `EnumType["FOO"]` : + Return the member that has the name `FOO`, raising a `builtins.KeyError` + if it is not present. + * `EnumType.FOO` : + Return the member that has the name `FOO`, raising a + `builtins.AttributeError` if it is not present. + * `EnumType(x)` : + Attempt to cast `x` to the enum type by finding an existing member that + has the same __value__. If this fails, you should expect a + `builtins.ValueError` to be raised. + + Operators on each enum member + ----------------------------- + * `e1 == e2` : `builtins.bool` + Compare equality. + * `e1 != e2` : `builtins.bool` + Compare inequality. + * `builtins.repr(e)` : `builtins.str` + Get the machine readable representation of the enum member `e`. + * `builtins.str(e)` : `builtins.str` + Get the `builtins.str` name of the enum member `e`. + + Special properties on each enum member + -------------------------------------- + * `name` : `builtins.str` + The name of the member. + * `value` : + The value of the member. The type depends on the implementation type + of the enum you are using. + + All other methods and operators on enum members are inherited from the + member's __value__. For example, an enum extending `builtins.int` would + be able to be used as an `int` type outside these overridden definitions. + """ + + _name_to_member_map_: typing.Final[typing.ClassVar[typing.Mapping[str, Enum]]] + _value_to_member_map_: typing.Final[typing.ClassVar[typing.Mapping[int, Enum]]] + _member_names_: typing.Final[typing.ClassVar[typing.Sequence[str]]] + __members__: typing.Final[typing.ClassVar[typing.Mapping[str, Enum]]] + __objtype__: typing.Final[typing.ClassVar[typing.Type[typing.Any]]] + __enumtype__: typing.Final[typing.ClassVar[typing.Type[Enum]]] + _name_: typing.Final[str] + _value_: typing.Final[typing.Any] + + @property + def name(self) -> str: + """Return the name of the enum member as a `builtins.str`.""" + return self._name_ + + @property + @typing.no_type_check + def value(self): + """Return the value of the enum member.""" + return self._value_ def __repr__(self) -> str: - return f"<{type(self).__name__}.{self.name}: {self.value!r}>" + return f"<{type(self).__name__}.{self._name_}: {self._value_!r}>" def __str__(self) -> str: - return f"{type(self).__name__}.{self.name}" + return self._name_ or "NO_NAME" + + +_Flag = NotImplemented + + +def _name_resolver(members: typing.Dict[int, _Flag], value: int) -> typing.Generator[str, typing.Any, None]: + bit = 1 + has_yielded = False + remaining = value + while bit <= value: + if member := members.get(bit): + # Use ._value_ to prevent overhead of making new members each time. + # Also lets my testing logic for the cache size be more accurate. + if member._value_ & remaining == member._value_: + remaining ^= member._value_ + yield member.name + has_yielded = True + bit <<= 1 + + if not has_yielded: + yield f"UNKNOWN 0x{value:x}" + elif remaining: + yield hex(remaining) + + +class _FlagMeta(type): + def __call__(cls, value: typing.Any = 0) -> typing.Any: + try: + return cls._value_to_member_map_[value] + except KeyError: + # We only need this ability here usually, so overloading operators + # is an overkill and would add more overhead. + + if value < 0: + # Convert to a positive value instead. + return cls.__everything__ - ~value + + temp_members = cls._temp_members_ + # For huge enums, don't ever cache anything. We could consume masses of memory otherwise + # (e.g. Permissions) + try: + # Try to get a cached value. + return temp_members[value] + except KeyError: + # If we cant find the value, just return what got casted in by generating a pseudomember + # and caching it. We cant use weakref because int is not weak referenceable, annoyingly. + # TODO: make the cache update thread-safe by using setdefault instead of assignment. + pseudomember = cls.__new__(cls, value) + temp_members[value] = pseudomember + pseudomember._name_ = None + pseudomember._value_ = value + if len(temp_members) > _MAX_CACHED_MEMBERS: + temp_members.popitem() + + return pseudomember + + def __getitem__(cls, name: str) -> typing.Any: + return cls._name_to_member_map_[name] + + def __iter__(cls) -> typing.Iterator[str]: + yield from cls._name_to_member_map_.values() + + @classmethod + def __prepare__( + mcs, name: str, bases: typing.Tuple[typing.Type[typing.Any], ...] = () + ) -> typing.Union[typing.Dict[str, typing.Any], _EnumNamespace]: + if _Flag is NotImplemented: + if name != "Flag": + raise TypeError("First instance of _FlagMeta must be Flag") + return _EnumNamespace(object) + + try: + # Fails if Enum is not defined. + if len(bases) == 1 and bases[0] == Flag: + return _EnumNamespace(int) + except ValueError: + pass + raise TypeError("Cannot define another Flag base type") from None + + @staticmethod + def __new__( + mcs: typing.Type[_T], + name: str, + bases: typing.Tuple[typing.Type[typing.Any], ...], + namespace: typing.Union[typing.Dict[str, typing.Any], _EnumNamespace], + ) -> _T: + global _Flag + + if _Flag is NotImplemented: + # noinspection PyRedundantParentheses + return (_Flag := super().__new__(mcs, name, bases, namespace)) + + assert isinstance(namespace, _EnumNamespace) + new_namespace = { + "__objtype__": int, + "__enumtype__": _Flag, + "_name_to_member_map_": (name_to_member := {}), + "_value_to_member_map_": (value_to_member := {}), + "_powers_of_2_to_member_map_": (powers_of_2_map := {}), + # We cant weakref, as we inherit from int. Turns out that is significantly + # slower anyway, so it isn't important for now. We just manually limit + # the cache size. + # This also randomly ends up with a 0 value in it at the start + # during the next for loop. I cannot work out for the life of me + # why this happens. + "_temp_members_": {}, + "_member_names_": (member_names := []), + # Required to be immutable by enum API itself. + "__members__": types.MappingProxyType(namespace.names_to_values), + **namespace, + # This copies over all methods, including operator overloads. This + # has the effect of making pdoc aware of any methods or properties + # we defined on Flag. + **{ + name: value + for name, value in Flag.__dict__.items() + if name not in ("__class__", "__module__", "__doc__") + }, + } + + cls = super().__new__(mcs, name, (int, *bases), new_namespace) + for name, value in namespace.names_to_values.items(): + # Patching the member init call is around 100ns faster per call than + # using the default type.__call__ which would make us do the lookup + # in cls.__new__. Reason for this is that python will also always + # invoke cls.__init__ if we do this, so we end up with two function + # calls. + member = cls.__new__(cls, value) + member._name_ = name + member._value_ = value + name_to_member[name] = member + value_to_member[value] = member + member_names.append(name) + setattr(cls, name, member) + + if not (value & value - 1): + powers_of_2_map[value] = member + + all_bits = functools.reduce(operator.or_, value_to_member.keys()) + all_bits_member = cls.__new__(cls, all_bits) + all_bits_member._name_ = None + all_bits_member._value_ = all_bits + setattr(cls, "__everything__", all_bits_member) + + return cls -# We have to use this fallback, or Pdoc will fail to document some stuff correctly... -if os.getenv("PDOC3_GENERATING") == "1": # pragma: no cover - from enum import Enum # noqa: F811 - Redefinition intended + def __repr__(cls) -> str: + return f"" + + __str__ = __repr__ + + +class Flag(metaclass=_FlagMeta): + """Clone of Python's `enum.Flag` implementation. + + This is designed to be faster and more efficient than Python's + implementation, while retaining the majority of the external interface + that Python's `enum.Flag` provides. + + In simple terms, an `Flag` is a set of wrapped constant `builtins.int` + values that can be combined in any combination to make a special value. + This is a more efficient way of combining things like permissions together + into a single integral value, and works by setting individual `1`s and `0`s + on the binary representation of the integer. + + This implementation has extra features, in that it will actively behave + like a `builtins.set` as well. + + !!! warning + Despite wrapping `builtins.int` values, conceptually this does not + behave as if it were a subclass of `int`. + + !!! danger + Some semantics such as subtype checking and instance checking may + differ. It is recommended to compare these values using the + `==` operator rather than the `is` operator for safety reasons. + + Especially where pseudo-members created from combinations are cached, + results of using of `is` may not be deterministic. This is a side + effect of some internal performance improvements. + + Failing to observe this __will__ result in unexpected behaviour + occurring in your application! + + Special Members on the class + ---------------------------- + * `__enumtype__` : + Always `Flag`. + * `__everything__` : + A special member with all documented bits set. + * `__members__` : + An immutable `typing.Mapping` that maps each member name to the member + value. + * ` __objtype__` : + Always `builtins.int`. + + Operators on the class + ---------------------- + * `FlagType["FOO"]` : + Return the member that has the name `FOO`, raising a `builtins.KeyError` + if it is not present. + * `FlagType.FOO` : + Return the member that has the name `FOO`, raising a + `builtins.AttributeError` if it is not present. + * `FlagType(x)` : + Attempt to cast `x` to the enum type by finding an existing member that + has the same __value__. If this fails, then a special __composite__ + instance of the type is made. The name of this type is a combination of + all members that combine to make the bitwise value. + + Operators on each flag member + ----------------------------- + * `e1 & e2` : + Bitwise `AND` operation. Will return a member that contains all flags + that are common between both oprands on the values. This also works with + one of the oprands being an `builtins.int`eger. You may instead use + the `intersection` method. + * `e1 | e2` : + Bitwise `OR` operation. Will return a member that contains all flags + that appear on at least one of the oprands. This also works with + one of the oprands being an `builtins.int`eger. You may instead use + the `union` method. + * `e1 ^ e2` : + Bitwise `XOR` operation. Will return a member that contains all flags + that only appear on at least one and at most one of the oprands. + This also works with one of the oprands being an `builtins.int`eger. + You may instead use the `symmetric_difference` method. + * `~e` : + Return the inverse of this value. This is equivalent to disabling all + flags that are set on this value and enabling all flags that are + not set on this value. Note that this will behave slightly differently + to inverting a pure int value. You may instead use the `invert` method. + * `e1 - e2` : + Bitwise set difference operation. Returns all flags set on `e1` that are + not set on `e2` as well. You may instead use the `difference` + method. + * `bool(e)` : `builtins.bool` + Return `builtins.True` if `e` has a non-zero value, otherwise + `builtins.False`. + * `E.A in e`: `builtins.bool` + `builtins.True` if `E.A` is in `e`. This is functionally equivalent + to `E.A & e == E.A`. + * `iter(e)` : + Explode the value into a iterator of each __documented__ flag that can + be combined to make up the value `e`. Returns an iterator across all + well-defined flags that make up this value. This will only include the + flags explicitly defined on this `Flag` type and that are individual + powers of two (this means if converted to twos-compliment binary, + exactly one bit must be a `1`). In simple terms, this means that you + should not expect combination flags to be returned. + * `e1 == e2` : `builtins.bool` + Compare equality. + * `e1 != e2` : `builtins.bool` + Compare inequality. + * `e1 < e2` : `builtins.bool` + Compare by ordering. + * `builtins.int(e)` : `builtins.int` + Get the integer value of this flag + * `builtins.repr(e)` : `builtins.str` + Get the machine readable representation of the flag member `e`. + * `builtins.str(e)` : `builtins.str` + Get the `builtins.str` name of the flag member `e`. + + Special properties on each flag member + -------------------------------------- + * `e.name` : `builtins.str` + The name of the member. For composite members, this will be generated. + * `e.value` : `builtins.int` + The value of the member. + + Special members on each flag member + ----------------------------------- + * `e.all(E.A, E.B, E.C, ...)` : `builtins.bool` + Returns `builtins.True` if __all__ of `E.A`, `E.B`, `E.C`, et cetera + make up the value of `e`. + * `e.any(E.A, E.B, E.C, ...)` : `builtins.bool` + Returns `builtins.True` if __any__ of `E.A`, `E.B`, `E.C`, et cetera + make up the value of `e`. + * `e.none(E.A, E.B, E.C, ...)` : `builtins.bool` + Returns `builtins.True` if __none__ of `E.A`, `E.B`, `E.C`, et cetera + make up the value of `e`. + * `e.split()` : `typing.Sequence` + Explode the value into a sequence of each __documented__ flag that can + be combined to make up the value `e`. Returns a sorted sequence of each + power-of-two flag that makes up the value `e`. This is equivalent to + `list(iter(e))`. + + All other methods and operators on `Flag` members are inherited from the + member's __value__. + + !!! note + Due to limitations around how this is re-implemented, this class is not + considered a subclass of `Enum` at runtime, even if MyPy believes this + is possible + """ + + _name_to_member_map_: typing.Final[typing.ClassVar[typing.Mapping[str, Flag]]] + _value_to_member_map_: typing.Final[typing.ClassVar[typing.Mapping[int, Flag]]] + _powers_of_2_to_member_map_: typing.Final[typing.ClassVar[typing.Mapping[int, Flag]]] + _temp_members_: typing.Final[typing.ClassVar[typing.Mapping[int, Flag]]] + _member_names_: typing.Final[typing.ClassVar[typing.Sequence[str]]] + __members__: typing.Final[typing.ClassVar[typing.Mapping[str, Flag]]] + __objtype__: typing.Final[typing.ClassVar[typing.Type[int]]] + __enumtype__: typing.Final[typing.ClassVar[typing.Type[Flag]]] + _name_: typing.Final[str] + _value_: typing.Final[int] + + @property + def name(self) -> str: + """Return the name of the flag combination as a `builtins.str`.""" + if self._name_ is None: + self._name_ = "|".join(_name_resolver(self._value_to_member_map_, self._value_)) + return self._name_ + + @property + def value(self) -> int: + """Return the `builtins.int` value of the flag.""" + return self._value_ + + def all(self: _T, *flags: _T) -> bool: + """Check if all of the given flags are part of this value. + + Returns + ------- + builtins.bool + `builtins.True` if any of the given flags are part of this value. + Otherwise, return `builtins.False`. + """ + return all((flag & self) == flag for flag in flags) + + def any(self: _T, *flags: _T) -> bool: + """Check if any of the given flags are part of this value. + + Returns + ------- + builtins.bool + `builtins.True` if any of the given flags are part of this value. + Otherwise, return `builtins.False`. + """ + return any((flag & self) == flag for flag in flags) + + def difference(self: _T, other: typing.Union[_T, int]) -> _T: + """Perform a set difference with the other set. + + This will return all flags in this set that are not in the other value. + + Equivalent to using the subtraction `-` operator. + """ + return self.__class__(self & ~int(other)) + + def intersection(self: _T, other: typing.Union[_T, int]) -> _T: + """Return a combination of flags that are set for both given values. + + Equivalent to using the "AND" `&` operator. + """ + return self.__class__(self._value_ & int(other)) + + def invert(self: _T) -> _T: + """Return a set of all flags not in the current set.""" + return self.__class__(self.__class__.__everything__._value_ & ~self._value_) + + def is_disjoint(self: _T, other: typing.Union[_T, int]) -> bool: + """Return whether two sets have a intersection or not. + + If the two sets have an intersection, then this returns + `builtins.False`. If no common flag values exist between them, then + this returns `builtins.True`. + """ + return not (self & other) + + def is_subset(self: _T, other: typing.Union[_T, int]) -> bool: + """Return whether another set contains this set or not. + + Equivalent to using the "in" operator. + """ + return (self & other) == other + + def is_superset(self: _T, other: typing.Union[_T, int]) -> bool: + """Return whether this set contains another set or not.""" + return (self & other) == self + + def none(self: _T, *flags: _T) -> bool: + """Check if none of the given flags are part of this value. + + !!! note + This is essentially the opposite of `Flag.any`. + + Returns + ------- + builtins.bool + `builtins.True` if none of the given flags are part of this value. + Otherwise, return `builtins.False`. + """ + return not self.any(*flags) + + def split(self: _T) -> typing.Sequence[_T]: + """Return a list of all defined atomic values for this flag. + + Any unrecognised bits will be omitted for brevity. + + The result will be a name-sorted `typing.Sequence` of each membe + """ + return sorted( + (member for member in self.__class__._powers_of_2_to_member_map_.values() if member.value & self), + # Assumption: powers of 2 already have a cached value. + key=lambda m: m._name_, + ) + + def symmetric_difference(self: _T, other: typing.Union[_T, int]) -> _T: + """Return a set with the symmetric differences of two flag sets. + + Equivalent to using the "XOR" `^` operator. + + For `a ^ b`, this can be considered the same as `(a - b) | (b - a)`. + """ + return self.__class__(self._value_ ^ int(other)) + + def union(self: _T, other: typing.Union[_T, int]) -> _T: + """Return a combination of all flags in this set and the other set. + + Equivalent to using the "OR" `~` operator. + """ + return self.__class__(self._value_ | int(other)) + + isdisjoint = is_disjoint + issubset = is_subset + issuperset = is_superset + # Exists since Python's `set` type is inconsistent with naming, so this + # will prevent tripping people up unnecessarily because we do not + # name inconsistently. + + # This one isn't in Python's set, but the inconsistency is triggering my OCD + # so this is being defined anyway. + symmetricdifference = symmetric_difference + + def __bool__(self) -> bool: + return bool(self._value_) + + def __int__(self) -> int: + return self._value_ + + def __iter__(self: _T) -> typing.Iterator[_T]: + return iter(self.split()) + + def __len__(self) -> int: + return len(self.split()) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}.{self.name}: {self.value!r}>" + + def __rsub__(self: _T, other: typing.Union[int, _T]) -> _T: + # This logic has to be reversed to be correct, since order matters for + # a subtraction operator. This also ensures `int - _T -> _T` is a valid + # case for us. + if not isinstance(other, self.__class__): + other = self.__class__(other) + return other - self + + def __str__(self) -> str: + return self.name + + __contains__ = is_subset + __rand__ = __and__ = intersection + __ror__ = __or__ = union + __sub__ = difference + __rxor__ = __xor__ = symmetric_difference + __invert__ = invert diff --git a/hikari/internal/enums.pyi b/hikari/internal/enums.pyi index 9abb63a6cb..59a325cc4b 100644 --- a/hikari/internal/enums.pyi +++ b/hikari/internal/enums.pyi @@ -30,7 +30,64 @@ # we are using are just aliases from the enum types in the standard library. import enum as __enum +from typing import Any as __Any +from typing import Iterator as __Iterator +from typing import Sequence as __Sequence +from typing import Type as __Type +from typing import TypeVar as __TypeVar +from typing import Union as __Union Enum = __enum.Enum -__all__ = ["Enum"] +__FlagT = __TypeVar("__FlagT", bound=__enum.IntFlag) + + +class Flag(__enum.IntFlag): + def all(self: __FlagT, *flags: __FlagT) -> bool: + ... + def any(self: __FlagT, *flags: __FlagT) -> bool: + ... + def difference(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: + ... + def intersection(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: + ... + def invert(self: __FlagT) -> __FlagT: + ... + def is_disjoint(self: __FlagT, other: __Union[int, __FlagT]) -> bool: + ... + def is_subset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: + ... + def is_superset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: + ... + def none(self, *flags: __FlagT) -> bool: + ... + def split(self: __FlagT) -> __Sequence[__FlagT]: + ... + def symmetric_difference(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: + ... + def union(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: + ... + def __bool__(self) -> bool: + ... + def __int__(self) -> int: + ... + def __iter__(self: __FlagT) -> __Iterator[__FlagT]: + ... + def __len__(self) -> int: + ... + @staticmethod + def __new__(cls: __Type[__FlagT], value: __Any = 0) -> __FlagT: + ... + + isdisjoint = is_disjoint + issuperset = is_superset + symmetricdifference = symmetric_difference + __contains__ = issubset = is_subset + __rand__ = __and__ = intersection + __ror__ = __or__ = union + __rsub__ = __sub__ = difference + __rxor__ = __xor__ = symmetric_difference + __invert__ = invert + + +__all__ = ["Enum", "Flag"] diff --git a/hikari/internal/flag.py b/hikari/internal/flag.py deleted file mode 100644 index 52cef4ce2a..0000000000 --- a/hikari/internal/flag.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -# cython: language_level=3 -# Copyright (c) 2020 Nekokatt -# -# 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. -"""Stuff to make working with enum flags a bit easier.""" -from __future__ import annotations - -__all__: typing.List[str] = ["Flag"] - -import enum -import inspect -import math -import typing - -FlagT = typing.TypeVar("FlagT", bound="Flag") - - -class Flag(enum.IntFlag): - """Base type for an enum integer flag in Hikari. - - Provides a consistent way of producing human-readable strings, extra - inspection utilities, and injects some boilerplate documentation. This - should make the concept of using flags a little less daunting to those - who are not yet used to the idea. - - Example - ------- - - ```py - >>> class Permission(Flag): - ... CREATE = enum.auto() - ... READ = enum.auto() - ... UPDATE = enum.auto() - ... DELETE = enum.auto() - - >>> perms = Permissions.CREATE | Permissions.READ - >>> print(perms.split()) - [Permissions.CREATE, Permissions.READ] - ``` - - This also provides two operators for clearer semantics of combining and - removing flag members. - - ```py - perms = Permissions.CREATE + Permissions.READ - assert perms == Permissions.CREATE | Permissions.READ - - perms -= Permissions.CREATE - assert perms == Permissions.READ - ``` - - Members will be iterable if you wish to inspect each individual flag. - - ```py - for p in Permissions.CREATE + Permissions.READ: - print(p) - ``` - """ - - __slots__: typing.Sequence[str] = () - - def __init_subclass__(cls, **kwargs: typing.Any) -> None: - doc = inspect.getdoc(cls) or "" - - doc += "\n".join( - ( - "", - "", - "This flag type has several additional operations that can be used", - "compared to normal enum types. These are applied to instances of", - "this enum directly, or can be used as a class method by passing the", - "flag as the first parameter.", - "", - f" - `def split() -> typing.Sequence[{cls.__name__}]: ...`
", - " Will split the combined flag up into individual atomic flags and", - " return them in a `typing.Sequence`.", - "```py", - ">>> (FOO | BAR | BAZ).split()", - "[FOO, BAR, BAZ]", - "```", - "", - f" - `def has_any(*flags: {cls.__name__}) -> bool: ...`
", - " Returns `builtins.True` if any of the given flags are present", - " in the combined flag this is applied to. Otherwise, returns", - " `builtins.False` instead.", - "", - f" - `def has_all(*flags: {cls.__name__}) -> bool: ...`
", - " Returns `builtins.True` if all of the given flags are present", - " in the combined flag this is applied to. Otherwise, returns", - " `builtins.False` instead.", - "", - f" - `def has_none(*flags: {cls.__name__}) -> bool: ...`
", - " Returns `builtins.True` if none of the given flags are present", - " in the combined flag this is applied to. Otherwise, returns", - " `builtins.False` instead.", - "", - "In addition, new operators are overloaded. `+` will combine flags", - "in the same way `|` would usually, and `-` will remove specific ", - "flags from this instance. This is equivalent to using the `&` " "operator.", - "", - "Finally, combined flag types can be iterated across as if they", - "were a collection.", - "```py", - ">>> for f in FOO | BAR | BAZ:", - "... print(f)", - "FOO", - "BAR", - "BAZ", - "```", - ) - ) - cls.__doc__ = doc - - def split(self: FlagT) -> typing.Sequence[FlagT]: - """Return a list of all atomic values for this flag.""" - members: typing.List[FlagT] = [] - - for member in type(self).__members__.values(): - # Don't show `NONE` values, it breaks stuff and makes no sense here. - if not member.value: - continue - - # If it is not a combined value, and it is contained in the bitfield: - if math.log2(member.value).is_integer() and member & self: - members.append(member) - - return sorted(members, key=lambda m: m.name) - - def has_any(self, *flags: FlagT) -> bool: - return any((flag & self) == flag for flag in flags) - - def has_all(self, *flags: FlagT) -> bool: - return all((flag & self) == flag for flag in flags) - - def has_none(self, *flags: FlagT) -> bool: - return not self.has_any(*flags) - - def __add__(self: FlagT, other: typing.Union[int, FlagT]) -> FlagT: - return type(self)(self | other) - - __radd__ = __add__ - - def __sub__(self: FlagT, other: typing.Union[int, FlagT]) -> FlagT: - return type(self)(self & ~other) - - def __str__(self) -> str: - if hasattr(self, "name") and self.name is not None: - return self.name - return " | ".join(m.name for m in self.split()) - - def __iter__(self: FlagT) -> typing.Iterator[FlagT]: - return iter(self.split()) diff --git a/hikari/messages.py b/hikari/messages.py index 5a635bffa7..6906e2a3bc 100644 --- a/hikari/messages.py +++ b/hikari/messages.py @@ -35,7 +35,6 @@ "Message", ] -import enum import typing import attr @@ -46,7 +45,6 @@ from hikari import urls from hikari.internal import attr_extensions from hikari.internal import enums -from hikari.internal import flag if typing.TYPE_CHECKING: import datetime @@ -107,9 +105,8 @@ def __str__(self) -> str: return self.name -@enum.unique @typing.final -class MessageFlag(flag.Flag): +class MessageFlag(enums.Flag): """Additional flags for message options.""" NONE = 0 @@ -353,31 +350,15 @@ class PartialMessage(snowflakes.Unique): message_reference: undefined.UndefinedNoneOr[MessageCrosspost] = attr.ib(repr=False) """The message's cross-posted reference data.""" + flags: undefined.UndefinedNoneOr[MessageFlag] = attr.ib(repr=False) + """The message's flags.""" + nonce: undefined.UndefinedNoneOr[str] = attr.ib(repr=False) """The message nonce. This is a string used for validating a message was sent. """ - # Flags are lazily loaded, due to the IntFlag mechanism being overly slow - # to execute. - _flags: undefined.UndefinedNoneOr[int] = attr.ib(repr=False) - - @property - def flags(self) -> undefined.UndefinedNoneOr[MessageFlag]: - """Return flags for thge message if known. - - If no flags are set, this returns `builtins.None`. - - If unknown, this returns `hikari.undefined.UNDEFINED` - - Returns - ------- - hikari.undefined.UndefinedNoneOr[MessageFlag] - The message flags, if known and set. - """ - return MessageFlag(self._flags) if isinstance(self._flags, int) else self._flags - @property def link(self) -> str: """Jump link to the message. @@ -914,9 +895,8 @@ class Message(PartialMessage): message_reference: typing.Optional[MessageCrosspost] """The message crossposted reference data.""" - nonce: typing.Optional[str] - """The message nonce. This is a string used for validating a message was sent.""" - - _flags = typing.Optional[int] flags: typing.Optional[MessageFlag] """The message flags.""" + + nonce: typing.Optional[str] + """The message nonce. This is a string used for validating a message was sent.""" diff --git a/hikari/permissions.py b/hikari/permissions.py index 37d07c2de5..f6ab8509ea 100644 --- a/hikari/permissions.py +++ b/hikari/permissions.py @@ -25,15 +25,13 @@ __all__: typing.List[str] = ["Permissions"] -import enum import typing -from hikari.internal import flag +from hikari.internal import enums -@enum.unique @typing.final -class Permissions(flag.Flag): +class Permissions(enums.Flag): """Represents the permissions available in a given channel or guild. This enum is an `enum.IntFlag`. This means that you can **combine multiple diff --git a/hikari/presences.py b/hikari/presences.py index a0f038529b..49717d6c7d 100644 --- a/hikari/presences.py +++ b/hikari/presences.py @@ -37,7 +37,6 @@ "Status", ] -import enum import typing import attr @@ -45,7 +44,6 @@ from hikari import snowflakes from hikari.internal import attr_extensions from hikari.internal import enums -from hikari.internal import flag if typing.TYPE_CHECKING: import datetime @@ -160,9 +158,8 @@ class ActivitySecret: """The secret used for matching a party, if applicable.""" -@enum.unique @typing.final -class ActivityFlag(flag.Flag): +class ActivityFlag(enums.Flag): """Flags that describe what an activity includes. This can be more than one using bitwise-combinations. @@ -242,20 +239,8 @@ class RichActivity(Activity): is_instance: typing.Optional[bool] = attr.ib(repr=False) """Whether this activity is an instanced game session.""" - # Flags are lazily loaded, due to the IntFlag mechanism being overly slow - # to execute. - _flags: typing.Optional[int] = attr.ib(repr=False) - - @property - def flags(self) -> typing.Optional[ActivityFlag]: - """Return flags describing the activity type. - - Returns - ------- - typing.Optional[ActivityFlag] - Flags, if present, otherwise `builtins.None`. - """ - return ActivityFlag(self._flags) if self._flags is not None else None + flags: typing.Optional[ActivityFlag] = attr.ib(repr=False) + """Flags that describe what the activity includes, if present.""" @typing.final diff --git a/hikari/users.py b/hikari/users.py index dda20d8cd0..e1bfb24795 100644 --- a/hikari/users.py +++ b/hikari/users.py @@ -26,7 +26,6 @@ __all__: typing.List[str] = ["PartialUser", "User", "OwnUser", "UserFlag", "PremiumType"] import abc -import enum import typing import attr @@ -37,16 +36,14 @@ from hikari import urls from hikari.internal import attr_extensions from hikari.internal import enums -from hikari.internal import flag from hikari.internal import routes if typing.TYPE_CHECKING: from hikari import traits -@enum.unique @typing.final -class UserFlag(flag.Flag): +class UserFlag(enums.Flag): """The known user flags that represent account badges.""" NONE = 0 @@ -355,12 +352,8 @@ class PartialUserImpl(PartialUser): is_system: undefined.UndefinedOr[bool] = attr.ib(eq=False, hash=False, repr=False) """Whether this user is a system account.""" - _flags: undefined.UndefinedOr[int] = attr.ib(eq=False, hash=False) - - @property - def flags(self) -> undefined.UndefinedOr[UserFlag]: - """Public flags for this user.""" - return UserFlag(self._flags) if self._flags is not undefined.UNDEFINED else undefined.UNDEFINED + flags: undefined.UndefinedOr[UserFlag] = attr.ib(eq=False, hash=False) + """Public flags for this user.""" @property def mention(self) -> str: @@ -413,7 +406,6 @@ class UserImpl(PartialUserImpl, User): is_system: bool """`builtins.True` if this user is a system account, `builtins.False` otherwise.""" - _flags: int flags: UserFlag """The public flags for this user.""" diff --git a/scripts/enum_benchmark.py b/scripts/enum_benchmark.py index ef2b6b55dd..8e562d8233 100644 --- a/scripts/enum_benchmark.py +++ b/scripts/enum_benchmark.py @@ -94,7 +94,7 @@ class BasicHikariEnum(str, hikari_enum.Enum): "BasicPyEnum._value2member_map_['25']", number=1_000_000, globals=globals() ) hikari_enum_delegate_to_map_time = timeit.timeit( - "BasicHikariEnum._value2member_map_['25']", number=1_000_000, globals=globals() + "BasicHikariEnum._value_to_member_map_['25']", number=1_000_000, globals=globals() ) py_enum_getitem_time = timeit.timeit("BasicPyEnum['z']", number=1_000_000, globals=globals()) hikari_enum_getitem_time = timeit.timeit("BasicHikariEnum['z']", number=1_000_000, globals=globals()) @@ -102,7 +102,7 @@ class BasicHikariEnum(str, hikari_enum.Enum): print("BasicPyEnum.__call__('25')", py_enum_call_time, "µs") print("BasicHikariEnum.__call__('25')", hikari_enum_call_time, "µs") print("BasicPyEnum._value2member_map_['25']", py_enum_delegate_to_map_time, "µs") -print("BasicHikariEnum._value2member_map['25']", hikari_enum_delegate_to_map_time, "µs") +print("BasicHikariEnum._value_to_member_map['25']", hikari_enum_delegate_to_map_time, "µs") print("BasicPyEnum.__getitem__['z']", py_enum_getitem_time, "µs") print("BasicHikariEnum.__getitem__['z']", hikari_enum_getitem_time, "µs") diff --git a/scripts/flag_benchmark.py b/scripts/flag_benchmark.py new file mode 100644 index 0000000000..ad71bc1e29 --- /dev/null +++ b/scripts/flag_benchmark.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Nekokatt +# +# 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. + +import enum as py_enum +import os +import sys +import timeit + +from hikari.internal import enums as hikari_enum + +PyIntFlag = None +HikariIntFlag = None + + +def build_enums(): + global PyIntFlag + global HikariIntFlag + + class PyIntFlag(py_enum.IntFlag): + a = 1 + b = 2 + c = 4 + d = 8 + e = 16 + f = 32 + g = 64 + ab = 3 + cde = 28 + + class HikariIntFlag(hikari_enum.Flag): + a = 1 + b = 2 + c = 4 + d = 8 + e = 16 + f = 32 + g = 64 + ab = 3 + cde = 28 + + # we do this otherwise we never gc the old class objects before a new + # run, which takes up more and more RAM until none is left. Not ideal but + # hopefully given how nothing else is running in the process, this count + # will eventually be the same roughly on each iteration and be roughly the + # same for each average collected offset. + # gc.enable() + # gc.collect(0) + # gc.collect(1) + # gc.collect(2) + # gc.disable() + + +if os.name != "nt": + print("Making highest priority, os.SCHED_RR") + try: + pid = os.getpid() + niceValue = os.nice(-20) + sys.setswitchinterval(0.5) + print("sys.getswitchinterval", sys.getswitchinterval()) + os.sched_setaffinity(pid, [(os.cpu_count() or 1) - 1]) + os.sched_setscheduler(pid, os.SCHED_RR, os.sched_param(1)) + print("sched_getscheduler", os.sched_getscheduler(pid)) + print("sched_getparam", os.sched_getparam(pid)) + print("sched_getaffinity", os.sched_getaffinity(pid)) + print("sched_getprioritymax", os.sched_get_priority_max(0)) + print("sched_getprioritymin", os.sched_get_priority_min(0)) + print("sched_rr_getinterval", os.sched_rr_get_interval(pid)) + print("nice", os.nice(0)) + except PermissionError: + print("run as root to make top OS priority for more accurate results.") +else: + print("lol windows good luck") + +for i in range(5): + print("pass", i + 1) + + for j in range(1_000_000): + if sum(j for j in range(10)) < 0: + raise RuntimeError + + py_intflag_call_time_member = timeit.timeit( + setup="build_enums()", stmt="PyIntFlag(4)", number=10_000_000, globals=globals() + ) + hikari_intflag_call_time_member = timeit.timeit( + setup="build_enums()", stmt="HikariIntFlag(4)", number=10_000_000, globals=globals() + ) + + for j in range(1_000_000): + if sum(j for j in range(10)) < 0: + raise RuntimeError + + py_intflag_call_time_existing_composite = ( + timeit.timeit(stmt="PyIntFlag(71)", number=10_000_000, globals=globals()) / 10 + ) + hikari_intflag_call_time_existing_composite = ( + timeit.timeit(stmt="HikariIntFlag(71)", number=10_000_000, globals=globals()) / 10 + ) + + for j in range(1_000_000): + if sum(j for j in range(10)) < 0: + raise RuntimeError + + build_enums_time = timeit.timeit(stmt="build_enums()", number=10_000, globals=globals()) + py_intflag_call_time_new_composite = timeit.timeit( + stmt="build_enums(); PyIntFlag(71)", number=10_000, globals=globals() + ) + build_enums_time = min(timeit.timeit(stmt="build_enums()", number=10_000, globals=globals()), build_enums_time) + py_intflag_call_time_new_composite -= build_enums_time + py_intflag_call_time_new_composite *= 100 + + for j in range(1_000_000): + if sum(j for j in range(10)) < 0: + raise RuntimeError + + build_enums_time = timeit.timeit(stmt="build_enums()", number=10_000, globals=globals()) + hikari_intflag_call_time_new_composite = timeit.timeit( + stmt="build_enums(); HikariIntFlag(71)", number=10_000, globals=globals() + ) + build_enums_time = min(timeit.timeit(stmt="build_enums()", number=10_000, globals=globals()), build_enums_time) + hikari_intflag_call_time_new_composite -= build_enums_time + hikari_intflag_call_time_new_composite *= 100 + + print("PyIntFlag.__call__(4) (existing member)", py_intflag_call_time_member, "µs") + print("HikariIntFlag.__call__(4) (existing member)", hikari_intflag_call_time_member, "µs") + + print("PyIntFlag.__call__(71) (new composite member)", py_intflag_call_time_new_composite, "µs") + print("HikariIntFlag.__call__(71) (new composite member)", hikari_intflag_call_time_new_composite, "µs") + + print("PyIntFlag.__call__(71) (existing composite member)", py_intflag_call_time_existing_composite, "µs") + print("HikariIntFlag.__call__(71) (existing composite member)", hikari_intflag_call_time_existing_composite, "µs") + + print() diff --git a/tests/hikari/internal/test_enums.py b/tests/hikari/internal/test_enums.py index 94eb77ce7c..4eed26eaaf 100644 --- a/tests/hikari/internal/test_enums.py +++ b/tests/hikari/internal/test_enums.py @@ -132,7 +132,7 @@ def foo(self): assert isinstance(Enum.foo, property) - def test_init_enum_type_maps_names_in___members__(self): + def test_init_enum_type_maps_names_in_members(self): class Enum(int, enums.Enum): foo = 9 bar = 18 @@ -160,7 +160,7 @@ def p(self): assert Enum.__members__ == {"foo": 9, "bar": 18, "baz": 27} - def test___call___when_member(self): + def test_call_when_member(self): class Enum(int, enums.Enum): foo = 9 bar = 18 @@ -170,7 +170,7 @@ class Enum(int, enums.Enum): assert returned == Enum.foo assert type(returned) == Enum - def test___call___when_not_member(self): + def test_call_when_not_member(self): class Enum(int, enums.Enum): foo = 9 bar = 18 @@ -180,7 +180,7 @@ class Enum(int, enums.Enum): assert returned == 69 assert type(returned) != Enum - def test___getitem__(self): + def test_getitem(self): class Enum(int, enums.Enum): foo = 9 bar = 18 @@ -189,3 +189,902 @@ class Enum(int, enums.Enum): returned = Enum["foo"] assert returned == Enum.foo assert type(returned) == Enum + + +class TestIntFlag: + @mock.patch.object(enums, "_Flag", new=NotImplemented) + def test_init_first_flag_type_populates_Flag(self): + class Flag(metaclass=enums._FlagMeta): + a = 1 + + assert enums._Flag is Flag + + @mock.patch.object(enums, "_Flag", new=NotImplemented) + def test_init_first_flag_type_with_wrong_name_and_no_bases_raises_TypeError(self): + with pytest.raises(TypeError): + + class Potato(metaclass=enums._FlagMeta): + a = 1 + + assert enums._Flag is NotImplemented + + def test_init_second_flag_type_with_no_bases_does_not_change_Flag_attribute_and_raises_TypeError(self): + expect = enums._Flag + + with pytest.raises(TypeError): + + class Flag(metaclass=enums._FlagMeta): + a = 1 + + assert enums._Flag is expect + + def test_init_flag_type_default_docstring_set(self): + class Flag(enums.Flag): + a = 1 + + assert Flag.__doc__ == "An enumeration." + + def test_init_flag_type_disallows_objects_that_are_not_instances_int(self): + with pytest.raises(TypeError): + + class Flag(enums.Flag): + a = 1 + foo = "hi" + + def test_init_flag_type_disallows_other_bases(self): + with pytest.raises(TypeError): + + class Flag(float, enums.Flag): + a = 1 + + def test_init_flag_type_allows_any_object_if_it_has_a_dunder_name(self): + class Flag(enums.Flag): + __foo__ = 1 + __bar = 2 + a = 3 + + assert Flag is not None + + def test_init_flag_type_allows_any_object_if_it_has_a_sunder_name(self): + class Flag(enums.Flag): + _foo_ = 1 + _bar = 2 + a = 3 + + assert Flag is not None + + def test_init_flag_type_allows_methods(self): + class Flag(enums.Flag): + A = 0x1 + + def foo(self): + return "foo" + + assert Flag.foo(12) == "foo" + + def test_init_flag_type_allows_classmethods(self): + class Flag(enums.Flag): + A = 0x1 + + @classmethod + def foo(cls): + assert cls is Flag + return "foo" + + assert Flag.foo() == "foo" + + def test_init_flag_type_allows_staticmethods(self): + class Flag(enums.Flag): + A = 0x1 + + @staticmethod + def foo(): + return "foo" + + assert Flag.foo() == "foo" + + def test_init_flag_type_allows_descriptors(self): + class Flag(enums.Flag): + A = 0x1 + + @property + def foo(self): + return "foo" + + assert isinstance(Flag.foo, property) + + def test_name_to_member_map(self): + class Flag(enums.Flag): + foo = 9 + bar = 18 + baz = 27 + + @staticmethod + def sm(): + pass + + @classmethod + def cm(cls): + pass + + def m(self): + pass + + @property + def p(self): + pass + + assert Flag._name_to_member_map_["foo"].__class__ is Flag + assert Flag._name_to_member_map_["foo"] is Flag.foo + + assert Flag._name_to_member_map_["bar"].__class__ is Flag + assert Flag._name_to_member_map_["bar"] is Flag.bar + + assert Flag._name_to_member_map_["baz"].__class__ is Flag + assert Flag._name_to_member_map_["baz"] is Flag.baz + + assert len(Flag._name_to_member_map_) == 3 + + def test_value_to_member_map(self): + class Flag(enums.Flag): + foo = 9 + bar = 18 + baz = 27 + + @staticmethod + def sm(): + pass + + @classmethod + def cm(cls): + pass + + def m(self): + pass + + @property + def p(self): + pass + + assert Flag._value_to_member_map_[9].__class__ is Flag + assert Flag._value_to_member_map_[9] is Flag.foo + + assert Flag._value_to_member_map_[18].__class__ is Flag + assert Flag._value_to_member_map_[18] is Flag.bar + + assert Flag._value_to_member_map_[27].__class__ is Flag + assert Flag._value_to_member_map_[27] is Flag.baz + + def test_member_names(self): + class Flag(enums.Flag): + foo = 9 + bar = 18 + baz = 27 + + @staticmethod + def sm(): + pass + + @classmethod + def cm(cls): + pass + + def m(self): + pass + + @property + def p(self): + pass + + assert Flag._member_names_ == ["foo", "bar", "baz"] + + def test_members(self): + class Flag(enums.Flag): + foo = 9 + bar = 18 + baz = 27 + + @staticmethod + def sm(): + pass + + @classmethod + def cm(cls): + pass + + def m(self): + pass + + @property + def p(self): + pass + + assert Flag.__members__["foo"].__class__ is int + assert Flag.__members__["foo"] == 9 + + assert Flag.__members__["bar"].__class__ is int + assert Flag.__members__["bar"] == 18 + + assert Flag.__members__["baz"].__class__ is int + assert Flag.__members__["baz"] == 27 + + assert len(Flag.__members__) == 3 + + def test_call_on_existing_value(self): + class Flag(enums.Flag): + foo = 9 + bar = 18 + baz = 27 + + assert Flag(9) is Flag.foo + assert Flag(Flag.foo) is Flag.foo + + def test_call_on_composite_value(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 4 + + assert Flag(3) is Flag.foo | Flag.bar + assert Flag(Flag.foo | Flag.bar) is Flag.foo | Flag.bar + + def test_call_on_named_composite_value(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 3 + + assert Flag(3) is Flag.baz + assert Flag(Flag.foo | Flag.bar) is Flag.baz + + def test_call_on_invalid_value(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 3 + + assert Flag(4) == 4 + + def test_cache(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 4 + + assert Flag._temp_members_ == {} + # Cache something. Remember the dict is evaluated before the equality + # so this will populate the cache. + assert Flag._temp_members_ == {3: Flag.foo | Flag.bar} + assert Flag._temp_members_ == {3: Flag.foo | Flag.bar, 7: Flag.foo | Flag.bar | Flag.baz} + + # Shouldn't mutate for existing items. + assert Flag._temp_members_ == {3: Flag.foo | Flag.bar, 7: Flag.foo | Flag.bar | Flag.baz} + assert Flag._temp_members_ == {3: Flag.foo | Flag.bar, 7: Flag.foo | Flag.bar | Flag.baz} + + def test_bitwise_name(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 4 + + assert Flag.foo.name == "foo" + + def test_combined_bitwise_name(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 4 + + assert (Flag.foo | Flag.bar).name == "foo|bar" + + def test_combined_known_bitwise_name(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 3 + + assert (Flag.foo | Flag.bar).name == "baz" + + def test_combined_partially_known_name(self): + class Flag(enums.Flag): + doo = 1 + laa = 2 + dee = 3 + + # This is fine because it is not an identity or exact value. + assert (Flag.laa | 4 | Flag.doo).name == "doo|laa|0x4" + + def test_combined_partially_known_combined_bitwise_name(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 3 + + # This is fine because it is not an identity or exact value. + assert (Flag.baz | 4).name == "foo|bar|0x4" + + def test_unknown_name(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 3 + + assert Flag(4).name == "UNKNOWN 0x4" + + def test_value(self): + class Flag(enums.Flag): + foo = 1 + bar = 2 + baz = 3 + + assert Flag.foo.value.__class__ is int + assert Flag.foo.value == 1 + + assert Flag.bar.value.__class__ is int + assert Flag.bar.value == 2 + + assert Flag.baz.value.__class__ is int + assert Flag.baz.value == 3 + + def test_is_instance_of_declaring_type(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = QUX | BORK + + assert isinstance(TestFlag.BORK, TestFlag) + assert isinstance(TestFlag.BORK, int) + + assert isinstance(TestFlag.QUXX, TestFlag) + assert isinstance(TestFlag.QUXX, int) + + def test_and(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = QUX | BORK + + assert TestFlag.QUXX & TestFlag.QUX == TestFlag.QUX + assert TestFlag.QUXX & TestFlag.QUX == 0x8 + assert TestFlag.QUXX & 0x8 == 0x8 + assert isinstance(TestFlag.QUXX & TestFlag.QUX, TestFlag) + assert isinstance(TestFlag.QUXX & TestFlag.QUX, int) + + def test_rand(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = QUX | BORK + + assert 0x8 & TestFlag.QUXX == TestFlag.QUX + assert 0x8 & TestFlag.QUXX == 0x8 + assert isinstance(0x8 & TestFlag.QUXX, TestFlag) + assert isinstance(0x8 & TestFlag.QUXX, int) + + def test_all_positive_case(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val = TestFlag.BAZ | TestFlag.BORK + + assert val.all(TestFlag.FOO) + + assert val.all(TestFlag.FOO, TestFlag.BAR, TestFlag.BAZ, TestFlag.BORK) + + def test_all_negative_case(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = 0x10 + + val = TestFlag.BAZ | TestFlag.BORK + + assert not val.all(TestFlag.QUX) + assert not val.all(TestFlag.BAZ, TestFlag.QUX, TestFlag.QUXX) + + def test_any_positive_case(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val = TestFlag.BAZ | TestFlag.BORK + + assert val.any(TestFlag.FOO) + assert val.any(TestFlag.BAR) + assert val.any(TestFlag.BAZ) + assert val.any(TestFlag.BORK) + # All present + assert val.any(TestFlag.FOO, TestFlag.BAR, TestFlag.BAZ, TestFlag.BORK) + # One present, one not + assert val.any( + TestFlag.FOO, + TestFlag.QUX, + ) + + def test_any_negative_case(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = 0x10 + + val = TestFlag.BAZ | TestFlag.BORK + + assert not val.any(TestFlag.QUX) + + def test_bool(self): + class TestFlag(enums.Flag): + BLEH = 0x0 + FOO = 0x1 + BAR = 0x2 + + assert not TestFlag.BLEH + assert TestFlag.FOO + assert TestFlag.BAR + + def test_contains(self): + class TestFlag(enums.Flag): + BLEH = 0x1 + FOO = 0x2 + BAR = 0x4 + BAZ = 0x8 + + f = TestFlag.FOO | TestFlag.BLEH | TestFlag.BAZ + assert TestFlag.FOO in f + assert TestFlag.BLEH in f + assert TestFlag.BAZ in f + assert TestFlag.BAR not in f + + def test_difference(self): + class TestFlag(enums.Flag): + A = 0x1 + B = 0x2 + C = 0x4 + D = 0x8 + E = 0x10 + F = 0x20 + G = 0x40 + H = 0x80 + + a = TestFlag.A | TestFlag.B | TestFlag.D | TestFlag.F | TestFlag.G + b = TestFlag.A | TestFlag.B | TestFlag.E + c = 0x13 + expect_asubb = TestFlag.D | TestFlag.F | TestFlag.G + expect_bsuba = TestFlag.E + expect_asubc = 0x68 + + assert a.difference(b) == expect_asubb + assert b.difference(a) == expect_bsuba + assert a.difference(c) == expect_asubc + + assert isinstance(a.difference(b), int) + assert isinstance(a.difference(b), TestFlag) + assert isinstance(a.difference(c), int) + assert isinstance(a.difference(c), TestFlag) + + def test_int(self): + class TestFlag(enums.Flag): + BLEH = 0x0 + FOO = 0x1 + BAR = 0x2 + + assert int(TestFlag.BLEH) == 0x0 + assert int(TestFlag.FOO) == 0x1 + assert int(TestFlag.BAR) == 0x2 + + assert type(int(TestFlag.BAR)) is int + + def test_intersection(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = QUX | BORK + + assert TestFlag.QUXX.intersection(TestFlag.QUX) == TestFlag.QUX + assert TestFlag.QUXX.intersection(TestFlag.QUX) == 0x8 + assert TestFlag.QUXX.intersection(0x8) == 0x8 + assert isinstance(TestFlag.QUXX.intersection(TestFlag.QUX), TestFlag) + assert isinstance(TestFlag.QUXX.intersection(TestFlag.QUX), int) + + def test_invert(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + + assert TestFlag.BAR.invert() == TestFlag.FOO | TestFlag.BAZ + + def test_invert_op(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + + assert ~TestFlag.BAR == TestFlag.FOO | TestFlag.BAZ + + def test_is_disjoint(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + assert (TestFlag.FOO | TestFlag.BAR).is_disjoint(TestFlag.BAZ | TestFlag.BORK) + assert not (TestFlag.FOO | TestFlag.BAR).is_disjoint(TestFlag.BAR | TestFlag.BORK) + assert (TestFlag.FOO | TestFlag.BAR).is_disjoint(0xC) + assert not (TestFlag.FOO | TestFlag.BAR).is_disjoint(0xA) + + def test_isdisjoint(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + assert (TestFlag.FOO | TestFlag.BAR).isdisjoint(TestFlag.BAZ | TestFlag.BORK) + assert not (TestFlag.FOO | TestFlag.BAR).isdisjoint(TestFlag.BAR | TestFlag.BORK) + assert (TestFlag.FOO | TestFlag.BAR).isdisjoint(0xC) + assert not (TestFlag.FOO | TestFlag.BAR).isdisjoint(0xA) + + def test_is_subset(self): + class TestFlag(enums.Flag): + BLEH = 0x1 + FOO = 0x2 + BAR = 0x4 + BAZ = 0x8 + BORK = 0x10 + + f = TestFlag.FOO | TestFlag.BLEH | TestFlag.BAZ + assert f.is_subset(TestFlag.FOO) + assert f.is_subset(TestFlag.BLEH) + assert f.is_subset(TestFlag.BAZ) + assert not f.is_subset(TestFlag.BAR) + assert f.is_subset(0x2) + assert f.is_subset(0x1) + assert f.is_subset(0x8) + assert not f.is_subset(0x4) + assert f.is_subset(TestFlag.FOO | TestFlag.BLEH) + assert f.is_subset(0x3) + assert f.is_subset(TestFlag.FOO | TestFlag.BLEH) + assert not f.is_subset(TestFlag.BAR | TestFlag.BORK) + assert not f.is_subset(0x14) + + def test_issubset(self): + class TestFlag(enums.Flag): + BLEH = 0x1 + FOO = 0x2 + BAR = 0x4 + BAZ = 0x8 + BORK = 0x10 + + f = TestFlag.FOO | TestFlag.BLEH | TestFlag.BAZ + assert f.issubset(TestFlag.FOO) + assert f.issubset(TestFlag.BLEH) + assert f.issubset(TestFlag.BAZ) + assert not f.issubset(TestFlag.BAR) + assert f.issubset(0x2) + assert f.issubset(0x1) + assert f.issubset(0x8) + assert not f.issubset(0x4) + assert f.issubset(TestFlag.FOO | TestFlag.BLEH) + assert f.issubset(0x3) + assert not f.issubset(TestFlag.BAR | TestFlag.BORK) + assert not f.issubset(0x14) + + def test_is_superset(self): + class TestFlag(enums.Flag): + BLEH = 0x1 + FOO = 0x2 + BAR = 0x4 + BAZ = 0x8 + BORK = 0x10 + QUX = 0x10 + + f = TestFlag.FOO | TestFlag.BLEH | TestFlag.BAZ + + assert f.is_superset(TestFlag.BLEH | TestFlag.FOO | TestFlag.BAR | TestFlag.BAZ | TestFlag.BORK) + assert f.is_superset(0x1F) + assert not f.is_superset(TestFlag.QUX) + assert not f.is_superset(0x20) + + def test_issuperset(self): + class TestFlag(enums.Flag): + BLEH = 0x1 + FOO = 0x2 + BAR = 0x4 + BAZ = 0x8 + BORK = 0x10 + QUX = 0x10 + + f = TestFlag.FOO | TestFlag.BLEH | TestFlag.BAZ + + assert f.issuperset(TestFlag.BLEH | TestFlag.FOO | TestFlag.BAR | TestFlag.BAZ | TestFlag.BORK) + assert f.issuperset(0x1F) + assert not f.issuperset(TestFlag.QUX) + assert not f.issuperset(0x20) + + def test_iter(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val = TestFlag.BAZ | TestFlag.BORK + val_iter = iter(val) + assert next(val_iter) == TestFlag.BAR + assert next(val_iter) == TestFlag.BORK + assert next(val_iter) == TestFlag.FOO + with pytest.raises(StopIteration): + next(val_iter) + + def test_len(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val0 = TestFlag(0) + val1 = TestFlag.FOO + val2 = TestFlag.FOO | TestFlag.BORK + val3 = TestFlag.FOO | TestFlag.BAR | TestFlag.BORK + val3_comb = TestFlag.BAZ | TestFlag.BORK + + assert len(val0) == 0 + assert len(val1) == 1 + assert len(val2) == 2 + assert len(val3) == 3 + assert len(val3_comb) == 3 + + def test_or(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + assert isinstance(TestFlag.FOO | TestFlag.BAR, int) + assert isinstance(TestFlag.FOO | TestFlag.BAR, TestFlag) + assert isinstance(TestFlag.FOO | 0x2, int) + assert isinstance(TestFlag.FOO | 0x2, TestFlag) + + assert TestFlag.FOO | TestFlag.BAR == 0x3 + assert TestFlag.FOO | TestFlag.BAR == TestFlag(0x3) + assert TestFlag.FOO | 0x2 == 0x3 + assert TestFlag.FOO | 0x2 == TestFlag(0x3) + + def test_ror(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + assert isinstance(0x2 | TestFlag.FOO, int) + assert isinstance(0x2 | TestFlag.FOO, TestFlag) + + assert 0x2 | TestFlag.FOO == 0x3 + assert 0x2 | TestFlag.FOO == TestFlag(0x3) + + def test_none_positive_case(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + QUXX = 0x10 + + val = TestFlag.BAZ | TestFlag.BORK + + assert val.none(TestFlag.QUX) + + def test_none_negative_case(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val = TestFlag.BAZ | TestFlag.BORK + + assert not val.none(TestFlag.FOO) + assert not val.none(TestFlag.BAR) + assert not val.none(TestFlag.BAZ) + assert not val.none(TestFlag.BORK) + # All present + assert not val.none(TestFlag.FOO, TestFlag.BAR, TestFlag.BAZ, TestFlag.BORK) + # One present, one not + assert not val.none( + TestFlag.FOO, + TestFlag.QUX, + ) + + def test_split(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val = TestFlag.BAZ | TestFlag.BORK + + # Baz is a combined field technically, so we don't expect it to be output here + assert val.split() == [TestFlag.BAR, TestFlag.BORK, TestFlag.FOO] + + def test_str_operator(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x3 + BORK = 0x4 + QUX = 0x8 + + val = TestFlag.BAZ | TestFlag.BORK + + assert str(val) == "FOO|BAR|BORK" + + def test_symmetric_difference(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + a = TestFlag.FOO | TestFlag.BAR | TestFlag.BAZ + b = TestFlag.BAZ | TestFlag.BORK | TestFlag.QUX + + assert isinstance(a.symmetric_difference(b), int) + assert isinstance(a.symmetric_difference(b), TestFlag) + assert isinstance(a.symmetric_difference(0x1C), int) + assert isinstance(a.symmetric_difference(0x1C), TestFlag) + + assert a.symmetric_difference(b) == b.symmetric_difference(a) + assert a.symmetric_difference(a) == 0 + assert b.symmetric_difference(b) == 0 + + assert a.symmetric_difference(b) == TestFlag.FOO | TestFlag.BAR | TestFlag.BORK | TestFlag.QUX + assert a.symmetric_difference(b) == 0x1B + + def test_sub(self): + class TestFlag(enums.Flag): + A = 0x1 + B = 0x2 + C = 0x4 + D = 0x8 + E = 0x10 + F = 0x20 + G = 0x40 + H = 0x80 + + a = TestFlag.A | TestFlag.B | TestFlag.D | TestFlag.F | TestFlag.G + b = TestFlag.A | TestFlag.B | TestFlag.E + c = 0x13 + expect_asubb = TestFlag.D | TestFlag.F | TestFlag.G + expect_bsuba = TestFlag.E + expect_asubc = 0x68 + + assert a - b == expect_asubb + assert b - a == expect_bsuba + assert a - c == expect_asubc + + assert isinstance(a - b, int) + assert isinstance(a - b, TestFlag) + assert isinstance(a - c, int) + assert isinstance(a - c, TestFlag) + + def test_rsub(self): + class TestFlag(enums.Flag): + A = 0x1 + B = 0x2 + C = 0x4 + D = 0x8 + E = 0x10 + F = 0x20 + G = 0x40 + H = 0x80 + + a = TestFlag.A | TestFlag.B | TestFlag.D | TestFlag.F | TestFlag.G + c = 0x13 + expect_csuba = 0x10 + + assert c - a == expect_csuba + + assert isinstance(c - a, int) + assert isinstance(c - a, TestFlag) + + def test_union(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + assert isinstance(TestFlag.FOO.union(TestFlag.BAR), int) + assert isinstance(TestFlag.FOO.union(TestFlag.BAR), TestFlag) + assert isinstance(TestFlag.FOO.union(TestFlag.BAR).union(TestFlag.BAZ), int) + assert isinstance(TestFlag.FOO.union(TestFlag.BAR).union(TestFlag.BAZ), TestFlag) + assert isinstance(TestFlag.FOO.union(0x2).union(TestFlag.BAZ), int) + assert isinstance(TestFlag.FOO.union(0x2).union(TestFlag.BAZ), TestFlag) + assert isinstance(TestFlag.FOO.union(0x2), int) + assert isinstance(TestFlag.FOO.union(0x2), TestFlag) + + assert TestFlag.FOO.union(TestFlag.BAR) == 0x3 + assert TestFlag.FOO.union(TestFlag.BAR) == TestFlag(0x3) + assert TestFlag.FOO.union(0x2) == 0x3 + assert TestFlag.FOO.union(0x2) == TestFlag(0x3) + + def test_xor(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + a = TestFlag.FOO | TestFlag.BAR | TestFlag.BAZ + b = TestFlag.BAZ | TestFlag.BORK | TestFlag.QUX + + assert isinstance(a ^ b, int) + assert isinstance(a ^ b, TestFlag) + assert isinstance(a ^ 0x1C, int) + assert isinstance(a ^ 0x1C, TestFlag) + + assert a ^ b == b ^ a + assert a ^ a == 0 + assert b ^ b == 0 + + assert a ^ b == TestFlag.FOO | TestFlag.BAR | TestFlag.BORK | TestFlag.QUX + assert a ^ b == 0x1B + assert a ^ 0x1C == TestFlag.FOO | TestFlag.BAR | TestFlag.BORK | TestFlag.QUX + assert a ^ 0x1C == 0x1B + + def test_rxor(self): + class TestFlag(enums.Flag): + FOO = 0x1 + BAR = 0x2 + BAZ = 0x4 + BORK = 0x8 + QUX = 0x10 + + a = TestFlag.FOO | TestFlag.BAR | TestFlag.BAZ + + assert isinstance(0x1C ^ a, int) + assert isinstance(0x1C ^ a, TestFlag) + assert 0x1C ^ a == TestFlag.FOO | TestFlag.BAR | TestFlag.BORK | TestFlag.QUX + assert 0x1C ^ a == 0x1B diff --git a/tests/hikari/internal/test_flag.py b/tests/hikari/internal/test_flag.py deleted file mode 100644 index c5499c9006..0000000000 --- a/tests/hikari/internal/test_flag.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020 Nekokatt -# -# 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. -import pytest - -from hikari.internal import flag - - -class TestFlag: - def test_flag_is_IntFlag(self): - import enum - - TestFlagType = flag.Flag("TestFlagType", "a b c") - assert isinstance(TestFlagType, enum.EnumMeta) - assert issubclass(TestFlagType, enum.IntFlag) - - def test_split(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - - # Baz is a combined field technically, so we don't expect it to be output here - assert val.split() == [TestFlagType.BAR, TestFlagType.BORK, TestFlagType.FOO] - - def test_has_any_positive_case(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert val.has_any(TestFlagType.FOO) - assert val.has_any(TestFlagType.BAR) - assert val.has_any(TestFlagType.BAZ) - assert val.has_any(TestFlagType.BORK) - # All present - assert val.has_any(TestFlagType.FOO, TestFlagType.BAR, TestFlagType.BAZ, TestFlagType.BORK) - # One present, one not - assert val.has_any( - TestFlagType.FOO, - TestFlagType.QUX, - ) - - def test_has_any_negative_case(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - QUXX = 0x10 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert not val.has_any(TestFlagType.QUX) - - def test_has_all_positive_case(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert val.has_all(TestFlagType.FOO) - - assert val.has_all(TestFlagType.FOO, TestFlagType.BAR, TestFlagType.BAZ, TestFlagType.BORK) - - def test_has_all_negative_case(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - QUXX = 0x10 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert not val.has_all(TestFlagType.QUX) - assert not val.has_all(TestFlagType.BAZ, TestFlagType.QUX, TestFlagType.QUXX) - - def test_has_none_positive_case(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - QUXX = 0x10 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert val.has_none(TestFlagType.QUX) - - def test_has_none_negative_case(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert not val.has_none(TestFlagType.FOO) - assert not val.has_none(TestFlagType.BAR) - assert not val.has_none(TestFlagType.BAZ) - assert not val.has_none(TestFlagType.BORK) - # All present - assert not val.has_none(TestFlagType.FOO, TestFlagType.BAR, TestFlagType.BAZ, TestFlagType.BORK) - # One present, one not - assert not val.has_none( - TestFlagType.FOO, - TestFlagType.QUX, - ) - - def test_add_operator(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - assert TestFlagType.BAZ + TestFlagType.BORK == TestFlagType.BAZ | TestFlagType.BORK - assert TestFlagType.BORK + TestFlagType.BAZ == TestFlagType.BAZ | TestFlagType.BORK - assert TestFlagType.BAZ + 4 == TestFlagType.BAZ | TestFlagType.BORK - assert 4 + TestFlagType.BAZ == TestFlagType.BAZ | TestFlagType.BORK - - def test_sub_operator(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - assert val - TestFlagType.BAZ == TestFlagType.BORK - assert val - TestFlagType.QUX == val - assert (TestFlagType.BAZ | TestFlagType.QUX) - val == TestFlagType.QUX - - def test_str_operator(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - - assert str(val) == "BAR | BORK | FOO" - - def test_iter(self): - class TestFlagType(flag.Flag): - FOO = 0x1 - BAR = 0x2 - BAZ = 0x3 - BORK = 0x4 - QUX = 0x8 - - val = TestFlagType.BAZ | TestFlagType.BORK - val_iter = iter(val) - assert next(val_iter) == TestFlagType.BAR - assert next(val_iter) == TestFlagType.BORK - assert next(val_iter) == TestFlagType.FOO - with pytest.raises(StopIteration): - next(val_iter) diff --git a/tests/hikari/test_channels.py b/tests/hikari/test_channels.py index df597f25c6..3e67ea7be8 100644 --- a/tests/hikari/test_channels.py +++ b/tests/hikari/test_channels.py @@ -94,8 +94,8 @@ def test_unset(self): overwrite = channels.PermissionOverwrite( type=channels.PermissionOverwriteType.MEMBER, id=snowflakes.Snowflake(1234321) ) - overwrite._allow = permissions.Permissions.CREATE_INSTANT_INVITE - overwrite._deny = permissions.Permissions.CHANGE_NICKNAME + overwrite.allow = permissions.Permissions.CREATE_INSTANT_INVITE + overwrite.deny = permissions.Permissions.CHANGE_NICKNAME assert overwrite.unset == permissions.Permissions(-67108866) diff --git a/tests/hikari/test_errors.py b/tests/hikari/test_errors.py index a2c47aa038..59b79cbbef 100644 --- a/tests/hikari/test_errors.py +++ b/tests/hikari/test_errors.py @@ -78,4 +78,4 @@ def error(self): return errors.MissingIntentError(intents.Intents.GUILD_BANS | intents.Intents.GUILD_EMOJIS) def test_str(self, error): - assert str(error) == "You are missing the following intent(s): GUILD_BANS | GUILD_EMOJIS" + assert str(error) == "You are missing the following intent(s): GUILD_BANS, GUILD_EMOJIS" diff --git a/tests/hikari/test_permissions.py b/tests/hikari/test_permissions.py index f5fa51ce07..952b991062 100644 --- a/tests/hikari/test_permissions.py +++ b/tests/hikari/test_permissions.py @@ -18,20 +18,3 @@ # 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 hikari import permissions - - -def test_Permission_str_operator_on_zero_value(): - permission = permissions.Permissions.NONE - assert str(permission) == "NONE" - - -def test_Permission_str_operator(): - permission = permissions.Permissions.MANAGE_EMOJIS - assert str(permission) == "MANAGE_EMOJIS" - - -def test_combined_Permission_str_operator(): - permission = permissions.Permissions.MANAGE_CHANNELS | permissions.Permissions.MANAGE_EMOJIS - assert str(permission) == "MANAGE_CHANNELS | MANAGE_EMOJIS"