Skip to content

Commit

Permalink
Make protocol __slots__ more type-friendly
Browse files Browse the repository at this point in the history
See python/mypy#11013 for details, but we may impose downstream heartache on those
deriving from our protocols if we don't explicitly set the type of `__slots__` to `Any`.

This *also* corrects for my previously botched metaclass TypeVar in accordance with
[this recommendation](python/mypy#9282 (comment)).
Could I have included that fix as a separate PR? Yes. Should I have? Yes. Did I? No.
`#GottaGoFast` or something the kids say. (Translation: Because I'm lazy.)
  • Loading branch information
posita committed Apr 5, 2022
1 parent acf8d94 commit f5b1c1f
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 21 deletions.
27 changes: 12 additions & 15 deletions beartype/typing/_typingpep544.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,18 @@
SupportsInt as _SupportsIntSlow,
SupportsRound as _SupportsRoundSlow,
TypeVar,
Union,
runtime_checkable,
)

# Note that this branch is intentionally tested first, despite the
# resulting negation. Why? Because mypy quietly defecates all over itself
# if the order of these two branches is reversed. It's as bad as it sounds.
if not IS_PYTHON_AT_LEAST_3_9:
from typing import Dict, Iterable, Tuple, Type # type: ignore[misc]
from typing import Dict, Tuple, Type # type: ignore[misc]
# Else, the active Python interpreter targets Python >= 3.9 and thus
# supports PEP 585. In this case, embrace non-deprecated PEP 585-compliant
# type hints.
else:
from collections.abc import Iterable

Dict = dict # type: ignore[misc]
Tuple = tuple # type: ignore[assignment]
Type = type # type: ignore[assignment]
Expand Down Expand Up @@ -98,7 +95,7 @@
'''


_TT = TypeVar("_TT", bound=type)
_TT = TypeVar("_TT", bound="_CachingProtocolMeta")
'''
Arbitrary type variable bound (i.e., confined) to classes.
'''
Expand Down Expand Up @@ -188,7 +185,7 @@ def __new__(
) -> _TT:

# See <https://github.com/python/mypy/issues/9282>
cls = super().__new__(mcls, name, bases, namespace, **kw) # type: ignore[misc]
cls = super().__new__(mcls, name, bases, namespace, **kw)

# Mark this protocol class as a runtime protocol. By default,
# "typing.Protocol" subclasses are only static-time. Although
Expand All @@ -215,7 +212,7 @@ def __new__(
# 'beartype.typing._typingpep544.SupportsAbs'>
#
# We lie to tell the truth.
cls._is_protocol = True
cls._is_protocol = True # type: ignore[attr-defined]

# Prefixing this class member with "_abc_" is necessary to prevent
# it from being considered part of the Protocol. See also:
Expand Down Expand Up @@ -384,7 +381,7 @@ class Protocol(
'''

# ................{ CLASS VARIABLES }................
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()

# ................{ DUNDERS }................
@callable_cached
Expand Down Expand Up @@ -495,52 +492,52 @@ class SupportsAbs(_SupportsAbsSlow[_T_co], Protocol, Generic[_T_co]):
Caching variant of :class:`typing.SupportsAbs`.
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()


class SupportsBytes(_SupportsBytesSlow, Protocol):
'''
Caching variant of :class:`typing.SupportsBytes`.
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()


class SupportsComplex(_SupportsComplexSlow, Protocol):
'''
Caching variant of :class:`typing.SupportsComplex`.
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()


class SupportsFloat(_SupportsFloatSlow, Protocol):
'''
Caching variant of :class:`typing.SupportsFloat`."
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()


class SupportsInt(_SupportsIntSlow, Protocol):
'''
Caching variant of :class:`typing.SupportsInt`.
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()


class SupportsIndex(_SupportsIndexSlow, Protocol):
'''
Caching variant of :class:`typing.SupportsIndex`.
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()


class SupportsRound(_SupportsRoundSlow[_T_co], Protocol, Generic[_T_co]):
'''
Caching variant of :class:`typing.SupportsRound`.
'''
__module__: str = 'beartype.typing'
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()
10 changes: 4 additions & 6 deletions beartype_test/a00_unit/a60_api/typing/test_typingpep544.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ def test_typingpep544_metaclass() -> None:
# Defer heavyweight imports.
from abc import abstractmethod
from beartype.typing import (
Iterable,
Any,
Protocol,
TypeVar,
Union,
runtime_checkable,
)
from beartype.typing._typingpep544 import _CachingProtocolMeta
Expand All @@ -45,7 +44,7 @@ def test_typingpep544_metaclass() -> None:
# Can we really have it all?!
@runtime_checkable # <-- unnecessary at runtime, but Mypy is confused without it
class SupportsRoundFromScratch(Protocol[_T_co]):
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()
@abstractmethod
def __round__(self, ndigits: int = 0) -> _T_co:
pass
Expand Down Expand Up @@ -310,10 +309,9 @@ def test_typingpep544_protocol_custom_direct_typevar() -> None:
from abc import abstractmethod
from beartype import beartype
from beartype.typing import (
Iterable,
Any,
Protocol,
TypeVar,
Union,
runtime_checkable,
)

Expand All @@ -323,7 +321,7 @@ def test_typingpep544_protocol_custom_direct_typevar() -> None:
# Arbitrary direct protocol subscripted by this type variable.
@runtime_checkable
class SupportsAbsToo(Protocol[_T_co]):
__slots__: Union[str, Iterable[str]] = ()
__slots__: Any = ()

@abstractmethod
def __abs__(self) -> _T_co:
Expand Down

0 comments on commit f5b1c1f

Please sign in to comment.