hikari.internal.fast_protocol
A utility for faster typing.Protocol
instance checks.
View Source
# -*- coding: utf-8 -*- # cython: language_level=3 # Copyright (c) 2020 Nekokatt # Copyright (c) 2021-present davfsa # # 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. """A utility for faster `typing.Protocol` instance checks.""" from __future__ import annotations __all__: typing.Sequence[str] = ("FastProtocolChecking",) import typing if typing.TYPE_CHECKING: _T = typing.TypeVar("_T") _Protocol: FastProtocolChecking = NotImplemented _IGNORED_ATTRS = typing.EXCLUDED_ATTRIBUTES + ["__qualname__", "__slots__"] def _check_if_ignored(name: str) -> bool: return name.startswith("_abc_") or name in _IGNORED_ATTRS # This metaclass needs to subclass the same type as `typing.Protocol` to be # able to overwrite it class _FastProtocolChecking(type(typing.Protocol)): _attributes_: typing.Tuple[str, ...] def __new__( cls: typing.Type[_T], cls_name: str, bases: typing.Tuple[typing.Type[typing.Any], ...], namespace: typing.Dict[str, typing.Any], ) -> _T: global _Protocol if _Protocol is NotImplemented: if cls_name != "FastProtocolChecking": raise TypeError("First instance of _FastProtocolChecking must be FastProtocolChecking") namespace["_attributes_"] = () # noinspection PyRedundantParentheses return (_Protocol := super().__new__(cls, cls_name, bases, namespace)) if _Protocol in bases: in_bases = True attributes = {attr for attr in namespace if not _check_if_ignored(attr)} attributes.update( annot for annot in namespace.get("__annotations__", {}).keys() if not _check_if_ignored(annot) ) for base in bases: if base in (typing.Protocol, _Protocol): continue if _Protocol not in base.__bases__: raise TypeError( f"FastProtocolChecking can only inherit from other fast checking protocols, got {base!r}" ) attributes.update(base._attributes_) namespace["_attributes_"] = tuple(attributes) else: in_bases = False cls = super().__new__(cls, cls_name, bases, namespace) if in_bases and not cls._is_protocol: raise TypeError("FastProtocolChecking can only be used with protocols") return cls def __instancecheck__(self: _T, other: typing.Any) -> bool: if not self._is_protocol: return super().__instancecheck__(other) for i in self._attributes_: if not hasattr(other, i): return False return True @typing.runtime_checkable class FastProtocolChecking(typing.Protocol, metaclass=_FastProtocolChecking): """An extension to make protocols with faster instance checks. .. note:: All protocols that subclass this class must be decorated with `@typing.runtime_checkable` to keep mypy happy. """ __slots__: typing.Sequence[str] = () __subclasshook__: typing.Callable[[typing.Type[typing.Any]], bool] def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None: # typing sets their own subclasshook if its not there. We want to # overwrite that one, but not any that was already defined, so we check # this before typing does anything to it. should_overwrite = "__subclasshook__" not in cls.__dict__ super().__init_subclass__(*args, **kwargs) if not should_overwrite: return def _subclass_hook(other: typing.Type[typing.Any]) -> bool: for i in cls._attributes_: if i not in other.__dict__: return NotImplemented return True cls.__subclasshook__ = _subclass_hook
View Source
@typing.runtime_checkable class FastProtocolChecking(typing.Protocol, metaclass=_FastProtocolChecking): """An extension to make protocols with faster instance checks. .. note:: All protocols that subclass this class must be decorated with `@typing.runtime_checkable` to keep mypy happy. """ __slots__: typing.Sequence[str] = () __subclasshook__: typing.Callable[[typing.Type[typing.Any]], bool] def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None: # typing sets their own subclasshook if its not there. We want to # overwrite that one, but not any that was already defined, so we check # this before typing does anything to it. should_overwrite = "__subclasshook__" not in cls.__dict__ super().__init_subclass__(*args, **kwargs) if not should_overwrite: return def _subclass_hook(other: typing.Type[typing.Any]) -> bool: for i in cls._attributes_: if i not in other.__dict__: return NotImplemented return True cls.__subclasshook__ = _subclass_hook
An extension to make protocols with faster instance checks.
Note: All protocols that subclass this class must be decorated with @typing.runtime_checkable
to keep mypy happy.
Methods
View Source
def _no_init_or_replace_init(self, *args, **kwargs): cls = type(self) if cls._is_protocol: raise TypeError('Protocols cannot be instantiated') # Already using a custom `__init__`. No need to calculate correct # `__init__` to call. This can lead to RecursionError. See bpo-45121. if cls.__init__ is not _no_init_or_replace_init: return # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. # The first instantiation of the subclass will call `_no_init_or_replace_init` which # searches for a proper new `__init__` in the MRO. The new `__init__` # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent # instantiation of the protocol subclass will thus use the new # `__init__` and no longer call `_no_init_or_replace_init`. for base in cls.__mro__: init = base.__dict__.get('__init__', _no_init_or_replace_init) if init is not _no_init_or_replace_init: cls.__init__ = init break else: # should not happen cls.__init__ = object.__init__ cls.__init__(self, *args, **kwargs)