From d74a4f05819cbd6d9efcc89d92ecaba33e375e53 Mon Sep 17 00:00:00 2001 From: leycec Date: Fri, 8 Jul 2022 03:16:00 -0400 Subject: [PATCH] `beartype.door` API tests x 2. This commit is the next in a commit chain repairing currently failing tests pertaining to the `beartype.door` subpackage, recently implemented by Harvard microscopist and general genius @tlambert03 in PR #136. Specifically, this commit: * Renames `beartype.math` to `beartype.door`. N-n-now... hear me out here. I came up with a ludicrous acronym and we're going to have to learn to live with it: the **D**ecidedly **O**bject-**O**rientedly **R**ecursive (DOOR) API. Or, `beartype.door` for short. Open the door to a whole new type-hinting world, everyone. * Refactored `beartype_test.test_is_subtype` to defer unsafe instantiation of non-trivial global variables to test-time via `pytest` fixtures. * Shifted `beartype_test.test_is_subtype` to `beartype_test.a00_unit.a60_api.door.test_door` for disambiguity and testability. Since `beartype.door` remains critically broken under both Python 3.7 and 3.8, tests remain disabled until the Typing Doctor gets in. (*Hazardous hazmats are arduous!*) --- README.rst | 10 +- beartype/abby/__init__.py | 1 - beartype/door/__init__.py | 27 + .../{math/_mathcls.py => door/_doorcls.py} | 468 ++++++++++-------- beartype/door/_doordata.py | 15 + beartype/math/__init__.py | 3 - beartype/roar/_roarexc.py | 2 +- .../a00_unit/a60_api/door/__init__.py | 0 .../a00_unit/a60_api/door/test_door.py | 403 +++++++++++++++ beartype_test/test_is_subtype.py | 315 ------------ 10 files changed, 709 insertions(+), 535 deletions(-) create mode 100644 beartype/door/__init__.py rename beartype/{math/_mathcls.py => door/_doorcls.py} (75%) create mode 100644 beartype/door/_doordata.py delete mode 100644 beartype/math/__init__.py create mode 100644 beartype_test/a00_unit/a60_api/door/__init__.py create mode 100644 beartype_test/a00_unit/a60_api/door/test_door.py delete mode 100644 beartype_test/test_is_subtype.py diff --git a/README.rst b/README.rst index 3fcb4e02..edf59503 100644 --- a/README.rst +++ b/README.rst @@ -2522,6 +2522,10 @@ Let's chart current and future compliance with Python's `typing`_ landscape: +--------------------+-----------------------------------------+-------------------------------+---------------------------+ | | die_if_unbearable_ | **0.10.0**\ —\ *current* | **0.10.0**\ —\ *current* | +--------------------+-----------------------------------------+-------------------------------+---------------------------+ +| beartype.door | TypeHint | **0.11.0**\ —\ *current* | **0.11.0**\ —\ *current* | ++--------------------+-----------------------------------------+-------------------------------+---------------------------+ +| | is_subhint | **0.10.0**\ —\ *current* | **0.10.0**\ —\ *current* | ++--------------------+-----------------------------------------+-------------------------------+---------------------------+ | beartype.typing | *all* | **0.10.0**\ —\ *current* | **0.10.0**\ —\ *current* | +--------------------+-----------------------------------------+-------------------------------+---------------------------+ | beartype.vale_ | Is_ | **0.7.0**\ —\ *current* | **0.7.0**\ —\ *current* | @@ -2776,7 +2780,9 @@ Let's chart current and future compliance with Python's `typing`_ landscape: +--------------------+-----------------------------------------+-------------------------------+---------------------------+ | typing_extensions_ | *all attributes* | **0.8.0**\ —\ *current* | **0.8.0**\ —\ *current* | +--------------------+-----------------------------------------+-------------------------------+---------------------------+ -| PEP | `484 `__ | **0.2.0**\ —\ *current* | *none* | +| PEP | `362 `__ | *none* | *none* | ++--------------------+-----------------------------------------+-------------------------------+---------------------------+ +| | `484 `__ | **0.2.0**\ —\ *current* | *none* | +--------------------+-----------------------------------------+-------------------------------+---------------------------+ | | `544 `__ | **0.4.0**\ —\ *current* | **0.4.0**\ —\ *current* | +--------------------+-----------------------------------------+-------------------------------+---------------------------+ @@ -5026,6 +5032,8 @@ rather than Python runtime) include: https://www.python.org/dev/peps .. _PEP 20: https://www.python.org/dev/peps/pep-0020 +.. _PEP 362: + https://www.python.org/dev/peps/pep-0362 .. _PEP 483: https://www.python.org/dev/peps/pep-0483 .. _PEP 526: diff --git a/beartype/abby/__init__.py b/beartype/abby/__init__.py index 6d72adac..67772c48 100644 --- a/beartype/abby/__init__.py +++ b/beartype/abby/__init__.py @@ -25,7 +25,6 @@ # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# ....................{ IMPORTS }.................... from beartype.abby._abbytest import ( die_if_unbearable as die_if_unbearable, is_bearable as is_bearable, diff --git a/beartype/door/__init__.py b/beartype/door/__init__.py new file mode 100644 index 00000000..7694559b --- /dev/null +++ b/beartype/door/__init__.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# --------------------( LICENSE )-------------------- +# Copyright (c) 2014-2022 Beartype authors. +# See "LICENSE" for further details. + +''' +**Beartype Decidedly Object-Orientedly Recursive (DOOR) API.** + +This subpackage provides an object-oriented type hint class hierarchy, +encapsulating the crude non-object-oriented type hint declarative API +standardized by the :mod:`typing` module. +''' + +# ....................{ TODO }.................... +#FIXME: Publicly document everything in "README.rst", please. *sigh* + +# ....................{ IMPORTS }.................... +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# WARNING: To avoid polluting the public module namespace, external attributes +# should be locally imported at module scope *ONLY* under alternate private +# names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather +# than merely "from argparse import ArgumentParser"). +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +from beartype.door._doorcls import ( + TypeHint as TypeHint, + is_subhint as is_subhint, +) diff --git a/beartype/math/_mathcls.py b/beartype/door/_doorcls.py similarity index 75% rename from beartype/math/_mathcls.py rename to beartype/door/_doorcls.py index d33aef57..981a4964 100644 --- a/beartype/math/_mathcls.py +++ b/beartype/door/_doorcls.py @@ -1,6 +1,21 @@ -import contextlib -from abc import ABC +#!/usr/bin/env python3 +# --------------------( LICENSE )-------------------- +# Copyright (c) 2014-2022 Beartype authors. +# See "LICENSE" for further details. + +''' +**Beartype Decidedly Object-Orientedly Recursive (DOOR) class hierarchy** (i.e., +object-oriented type hint class hierarchy, encapsulating the crude +non-object-oriented type hint API standardized by the :mod:`typing` module). + +This private submodule is *not* intended for importation by downstream callers. +''' +# ....................{ TODO }.................... +#FIXME: Split into submodules for maintainability, please. \o/ + +# ....................{ IMPORTS }.................... +from abc import ABC from beartype._data.hint.pep.sign import datapepsigns as signs from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsignset import HINT_SIGNS_UNION @@ -20,80 +35,89 @@ from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype.roar import BeartypeMathException from beartype.typing import Any, Dict, Iterable, Tuple, Type +from contextlib import suppress if IS_PYTHON_AT_LEAST_3_10: from beartype.typing import ParamSpec as _ParamSpec else: _ParamSpec = None # type: ignore # noqa +# ....................{ TESTERS }.................... +#FIXME: Shift into a new "_doortest" submodule, please. +def is_subhint(subhint: object, superhint: object) -> bool: + ''' + ``True`` only if the passed subhint is a subhint of the passed superhint. -def is_subtype(subtype: object, supertype: object) -> bool: - """Return True if ``subtype`` is a subtype of ``supertype``. - - This supports PEP-compliant type hints, as well as types. + This tester supports *all* types and *most* PEP-compliant type hints. Parameters ---------- - subtype : object + subhint : object Any PEP-compliant type hint or type. - supertype : object + superhint : object Any PEP-compliant type hint or type. Returns ------- bool - ``True`` if ``subtype`` is a subtype of ``supertype``. + ``True`` only if this subhint is a subhint of this superhint. Examples -------- - >>> from beartype.math import is_subtype - >>> is_subtype(int, int) - True - >>> is_subtype(Callable[[], list], Callable[..., Sequence[Any]]) - True - >>> is_subtype(Callable[[], list], Callable[..., Sequence[int]]) - False - """ - return TypeHint(subtype).is_subtype(TypeHint(supertype)) - - + >>> from beartype.door import is_subhint + >>> is_subhint(int, int) + True + >>> is_subhint(Callable[[], list], Callable[..., Sequence[Any]]) + True + >>> is_subhint(Callable[[], list], Callable[..., Sequence[int]]) + False + ''' + + return TypeHint(subhint).is_subhint(TypeHint(superhint)) + +# ....................{ SUPERCLASSES }.................... class TypeHint(ABC): - """ + ''' Abstract base class (ABC) of all **partially ordered type hint** (i.e., high-level object encapsulating a low-level type hint augmented with all rich comparison ordering methods). - Instances of this class are partially ordered with respect to one another. - Equivalently, instances of this class support all binary comparators (i.e., - ``==``, ``!=``, ``<``, ``<=``, ``>``, and ``>=``) according such that for - any three instances ``a``, ``b`, and ``c`` of this class: + Type hints are partially ordered with respect to one another. Equivalently, + type hints support all binary comparators (i.e., ``==``, ``!=``, ``<``, + ``<=``, ``>``, and ``>=``) according such that for any three instances + ``a``, ``b`, and ``c`` of this class: - * ``a ≤ a`` (i.e., reflexivity). - * If ``a ≤ b`` and ``b ≤ c``, then ``a ≤ c`` (i.e., transitivity). - * If ``a ≤ b`` and ``b ≤ a``, then ``a == b`` (i.e., antisymmetry). - * Either ``a ≤ b`` or ``b ≤ a`` (i.e., totality). + * ``a ≤ a`` (i.e., **reflexivity**). + * If ``a ≤ b`` and ``b ≤ c``, then ``a ≤ c`` (i.e., **transitivity**). + * If ``a ≤ b`` and ``b ≤ a``, then ``a == b`` (i.e., **antisymmetry**). - Instances of this class are thus usable in algorithms and data structures - requiring a partial ordering across their input. + Type hints are thus usable in algorithms and data structures requiring a + partial ordering across their input. + + Caveats + -------- + **Type hints are not totally ordered.** Like unordered sets, type hints do + *not* satisfy **totality** (i.e., either ``a ≤ b`` or ``b ≤ a`` is *not* + necessarily the case). Examples -------- - >>> from beartype.math import TypeHint - >>> hint_a = TypeHint(Callable[[str], list]) - >>> hint_b = TypeHint(Callable[Union[int, str], Sequence[Any]]) - >>> hint_a <= hint_b - True - >>> hint_a > hint_b - False - >>> hint_a.is_subtype(hint_b) - True - >>> list(hint_b) - [TypeHint(typing.Union[int, str]), TypeHint(typing.Sequence[typing.Any])] - """ + >>> from beartype.door import TypeHint + >>> hint_a = TypeHint(Callable[[str], list]) + >>> hint_b = TypeHint(Callable[Union[int, str], Sequence[Any]]) + >>> hint_a <= hint_b + True + >>> hint_a > hint_b + False + >>> hint_a.is_subhint(hint_b) + True + >>> list(hint_b) + [TypeHint(typing.Union[int, str]), TypeHint(typing.Sequence[typing.Any])] + ''' @callable_cached def __new__(cls, hint: object) -> "TypeHint": - """ + ''' Factory constructor magically instantiating and returning an instance of the private concrete subclass of this public abstract base class (ABC) appropriate for handling the passed low-level unordered type hint. @@ -115,7 +139,7 @@ def __new__(cls, hint: object) -> "TypeHint": If this class does *not* currently support the passed hint. BeartypeDecorHintPepSignException If the passed hint is *not* actually a PEP-compliant type hint. - """ + ''' # TypeHint(TypeHint(hint)) is TypeHint(hint) if isinstance(hint, TypeHint): return hint @@ -126,7 +150,7 @@ def __new__(cls, hint: object) -> "TypeHint": # Private concrete subclass of this ABC handling this hint if any *OR* # "None" otherwise (i.e., if no such subclass has been authored yet). - TypeHintSubclass = _HINT_SIGN_TO_TypeHint.get(hint_sign) + TypeHintSubclass = HINT_SIGN_TO_TYPEHINT.get(hint_sign) # if a subscriptable type has no args, all we care about is the origin if TypeHintSubclass and not get_hint_pep_args(hint): @@ -139,7 +163,7 @@ def __new__(cls, hint: object) -> "TypeHint": else: raise BeartypeMathException( f"Type hint {repr(hint)} currently unsupported by " - f'class "beartype.math.TypeHint".' + f'class "beartype.door.TypeHint".' ) # Else, this hint is supported. @@ -147,7 +171,7 @@ def __new__(cls, hint: object) -> "TypeHint": return super().__new__(TypeHintSubclass) def __init__(self, hint: object) -> None: - """ + ''' Initialize this high-level partially ordered type hint against the passed low-level unordered type hint. @@ -156,7 +180,7 @@ def __init__(self, hint: object) -> None: hint : object Lower-level unordered type hint to be encapsulated by this higher-level partially ordered type hint. - """ + ''' # TypeHint(TypeHint(hint)) == TypeHint(hint) if isinstance(hint, TypeHint): return @@ -178,21 +202,21 @@ def __init__(self, hint: object) -> None: self._hints_child_ordered = self._wrap_children(self._args) def _validate(self): - """Used by subclasses to validate self._args and self._origin""" + '''Used by subclasses to validate self._args and self._origin''' pass def _wrap_children(self, unordered_children: tuple) -> Tuple["TypeHint", ...]: - """Wrap type hint paremeters in TypeHint instances. + '''Wrap type hint paremeters in TypeHint instances. Gives subclasses an opportunity modify - """ + ''' return tuple( TypeHint(unordered_child) for unordered_child in unordered_children ) @callable_cached - def is_subtype(self, other: object) -> bool: - """ + def is_subhint(self, other: object) -> bool: + ''' ``True`` only if this partially ordered type hint is **compatible** with the passed partially ordered type hint, where compatibility implies that the unordered type hint encapsulated by this partially ordered type hint @@ -202,25 +226,25 @@ def is_subtype(self, other: object) -> bool: This method is memoized and thus enjoys ``O(1)`` amortized worst-case time complexity across all calls to this method. - """ + ''' # If the passed object is *NOT* a partially ordered type hint, raise an # exception. - other_hint = _die_unless_TypeHint(other) + other_hint = die_unless_typehint(other) # Else, that object is a partially ordered type hint. # For each branch of the passed union if that hint is a union *OR* that # hint as is otherwise... return any(self._is_le_branch(branch) for branch in other_hint.branches) - def is_supertype(self, other: object) -> bool: - """Return true if self is a supertype of other.""" - other = _die_unless_TypeHint(other) - return other.is_subtype(self) + def is_superhint(self, other: object) -> bool: + '''Return true if self is a superhint of other.''' + other = die_unless_typehint(other) + return other.is_subhint(self) @property def branches(self) -> Iterable["TypeHint"]: - """ + ''' Immutable iterable of all **branches** (i.e., high-level partially ordered type hints encapsulating all low-level unordered child type hints subscripting (indexing) the low-level unordered parent type hint @@ -233,20 +257,20 @@ def branches(self) -> Iterable["TypeHint"]: :pep:`604`-compliant unions (e.g., :attr:`typing.Union`, :attr:`typing.Optional`, and ``|``-delimited type objects) to be handled transparently *without* special cases in subclass implementations. - """ + ''' # Default to returning the 1-tuple containing only this instance, as # *ALL* subclasses except "_HintTypeUnion" require this default. return (self,) def __iter__(self) -> Iterable["TypeHint"]: - """ + ''' Immutable iterable of all **children** (i.e., high-level partially ordered type hints encapsulating all low-level unordered child type hints subscripting (indexing) the low-level unordered parent type hint encapsulated by this high-level partially ordered parent type hint) of this partially ordered parent type hint. - """ + ''' yield from self._hints_child_ordered def __hash__(self) -> int: @@ -287,34 +311,34 @@ def __ne__(self, other: object) -> bool: return not self.__eq__(other) def __le__(self, other: object) -> bool: - """Return true if self is a subtype of other.""" + '''Return true if self is a subhint of other.''' if not isinstance(other, TypeHint): return NotImplemented - return self.is_subtype(other) + return self.is_subhint(other) def __lt__(self, other: object) -> bool: - """Return true if self is a strict subtype of other.""" + '''Return true if self is a strict subhint of other.''' if not isinstance(other, TypeHint): return NotImplemented - return self.is_subtype(other) and self != other + return self.is_subhint(other) and self != other def __ge__(self, other: object) -> bool: - """Return true if self is a supertype of other.""" + '''Return true if self is a superhint of other.''' if not isinstance(other, TypeHint): return NotImplemented - return self.is_supertype(other) + return self.is_superhint(other) def __gt__(self, other: object) -> bool: - """Return true if self is a strict supertype of other.""" + '''Return true if self is a strict superhint of other.''' if not isinstance(other, TypeHint): return NotImplemented - return self.is_supertype(other) and self != other + return self.is_superhint(other) and self != other # not using abstractclass here because we use `TypeHint(stuff)`` everywhere... # the __new__ method makes sure to instantiate the correct subclass, but # mypy doesn't know that. def _is_le_branch(self, branch: "TypeHint") -> bool: - """ + ''' ``True`` only if this partially ordered type hint is **compatible** with the passed branch of another partially ordered type hint passed to the parent call of the :meth:`__le__` dunder method. @@ -323,7 +347,7 @@ def _is_le_branch(self, branch: "TypeHint") -> bool: ---------- :meth:`__le__` Further details. - """ + ''' raise NotImplementedError( "Subclasses must implement this method." ) # pragma: no cover @@ -336,7 +360,7 @@ def __repr__(self) -> str: # mypy doesn't know that. @property def _is_just_an_origin(self) -> bool: - """Flag that indicates this hint can be evaluating only using the origin. + '''Flag that indicates this hint can be evaluating only using the origin. This is useful for parametrized type hints with no arguments or with "Any"-type placeholder arguments, like `Tuple[Any, ...]` or @@ -353,23 +377,48 @@ def _is_just_an_origin(self) -> bool: ``` In this case, using `_is_just_an_origin` lets us simplify the comparison. - """ + ''' raise NotImplementedError( "Subclasses must implement this method." ) # pragma: no cover +# ....................{ TESTERS ~ more }.................... +#FIXME: Shift into a new "_doortest" submodule, please. +def die_unless_typehint(obj: object) -> TypeHint: + ''' + Raise an exception unless the passed object is a **partially ordered type + hint** (i.e., :class:`TypeHint` instance). + Parameters + ---------- + obj : object + Arbitrary object to be validated. + + Raises + ---------- + BeartypeMathException + If this object is *not* a partially ordered type hint. + ''' + + # If this object is *NOT* a partially ordered type hint, raise an exception. + if not isinstance(obj, TypeHint): + raise BeartypeMathException( + f"{repr(obj)} is not a 'beartype.door.TypeHint' instance.", + ) + return obj + +# ....................{ SUBCLASSES }.................... class _TypeHintClass(TypeHint): - """ - **partially ordered class type hint** (i.e., high-level object encapsulating a - low-level PEP-compliant type hint that is, in fact, a simple class). - """ + ''' + **Partially ordered class type hint** (i.e., high-level object encapsulating + a low-level PEP-compliant type hint that is, in fact, a simple class). + ''' _hint: type @property def _is_just_an_origin(self) -> bool: - """Plain types are their origin.""" + '''Plain types are their origin.''' return True def _is_le_branch(self, branch: TypeHint) -> bool: @@ -385,12 +434,13 @@ def _is_le_branch(self, branch: TypeHint) -> bool: return True # Return true only if... - return branch._is_just_an_origin and issubclass(self._origin, branch._origin) + return branch._is_just_an_origin and issubclass( + self._origin, branch._origin) class _TypeHintSubscripted(TypeHint): - """ - **partially ordered subscripted type hint** (i.e., high-level object + ''' + **Partially ordered subscripted type hint** (i.e., high-level object encapsulating a low-level parent type hint subscripted (indexed) by one or more equally low-level children type hints). @@ -402,7 +452,7 @@ class _TypeHintSubscripted(TypeHint): _hints_child_ordered : tuple[TypeHint] Tuple of all high-level partially ordered children type hints of this high-level partially ordered parent type hint. - """ + ''' _required_nargs: int = -1 @@ -446,13 +496,13 @@ def _is_le_branch(self, branch: TypeHint) -> bool: class _TypeHintOriginIsinstanceableArgs1(_TypeHintSubscripted): - """ + ''' **partially ordered single-argument isinstanceable type hint** (i.e., high-level object encapsulating a low-level PEP-compliant type hint subscriptable by only one child type hint originating from an isinstanceable class such that *all* objects satisfying that hint are instances of that class). - """ + ''' _required_nargs: int = 1 @@ -463,7 +513,10 @@ class _TypeHintOriginIsinstanceableArgs2(_TypeHintSubscripted): class _TypeHintCallable(_TypeHintSubscripted): def _validate(self): - """Perform argument validation for a callable.""" + ''' + Perform argument validation for a callable. + ''' + self._takes_any_args = False if len(self._args) == 0: # pragma: no cover # e.g. `Callable` without any arguments @@ -473,7 +526,8 @@ def _validate(self): self._args = (Any,) # returns any else: self._call_args = get_hint_pep484585_callable_args(self._hint) - if IS_PYTHON_AT_LEAST_3_10 and isinstance(self._call_args, _ParamSpec): + if IS_PYTHON_AT_LEAST_3_10 and isinstance( + self._call_args, _ParamSpec): raise NotImplementedError("ParamSpec not yet implemented.") if self._call_args is Ellipsis: @@ -529,14 +583,19 @@ def _is_le_branch(self, branch: TypeHint) -> bool: or len(self.arg_types) != len(branch.arg_types) or any( self_arg > branch_arg - for self_arg, branch_arg in zip(self.arg_types, branch.arg_types) + for self_arg, branch_arg in zip( + self.arg_types, branch.arg_types) ) ) ): return False if not branch.returns_any: - return False if self.returns_any else self.return_type <= branch.return_type + return ( + False + if self.returns_any else + self.return_type <= branch.return_type + ) return True @@ -549,11 +608,11 @@ class _TypeHintTuple(_TypeHintSubscripted): _is_empty_tuple: bool = False def _validate(self): - """Perform argument validation for a tuple. + '''Perform argument validation for a tuple. Specifically, remove any PEP-noncompliant type hints from the arguments, and set internal flags accordingly. - """ + ''' if len(self._args) == 0: # pragma: no cover # e.g. `Tuple` without any arguments # this may be unreachable, (since a bare Tuple will go to _TypeHintClass) @@ -624,8 +683,8 @@ def _wrap_children(self, _: tuple) -> Tuple["TypeHint", ...]: return () @callable_cached - def is_subtype(self, other: object) -> bool: - other_hint = _die_unless_TypeHint(other) + def is_subhint(self, other: object) -> bool: + other_hint = die_unless_typehint(other) # If the other hint is also a literal if isinstance(other_hint, _TypeHintLiteral): @@ -663,22 +722,22 @@ def _is_just_an_origin(self) -> bool: def _is_le_branch(self, branch: TypeHint) -> bool: # If the other type is not annotated, we ignore annotations on this - # one and just check that the metahint is a subtype of the other. + # one and just check that the metahint is a subhint of the other. # e.g. Annotated[t.List[int], 'meta'] <= List[int] if not isinstance(branch, _TypeHintAnnotated): - return self._metahint.is_subtype(branch) + return self._metahint.is_subhint(branch) # Else, that hint is a "typing.Annotated[...]" type hint. # If either... if ( - # The child type hint annotated by this parent hint does not subtype + # The child type hint annotated by this parent hint does not subhint # the child type hint annotated by that parent hint *OR*... self._metahint > branch._metahint or # These hints are annotated by a differing number of objects... len(self._metadata) != len(branch._metadata) ): - # This hint *CANNOT* be a subtype of that hint. Return false. + # This hint *CANNOT* be a subhint of that hint. Return false. return False # Attempt to... @@ -687,16 +746,16 @@ def _is_le_branch(self, branch: TypeHint) -> bool: # arbitrary caller-defined objects. Since these comparisons may raise # arbitrary caller-defined exceptions, we silently squelch any such # exceptions that arise by returning false below instead. - with contextlib.suppress(Exception): + with suppress(Exception): # Return true only if these hints are annotated by equivalent - # objects. We avoid testing for a subtype relation here (e.g., with + # objects. We avoid testing for a subhint relation here (e.g., with # the "<=" operator), as arbitrary caller-defined objects are *MUCH* # more likely to define a relevant equality comparison than a # relevant less-than-or-equal-to comparison. return self._metadata == branch._metadata # Else, one or more objects annotating these hints are incomparable. So, - # this hint *CANNOT* be a subtype of that hint. Return false. + # this hint *CANNOT* be a subhint of that hint. Return false. return False # pragma: no cover def __eq__(self, other: object) -> bool: @@ -710,23 +769,23 @@ def __eq__(self, other: object) -> bool: class _TypeHintUnion(_TypeHintSubscripted): - """ - **partially ordered union type hint** (i.e., high-level object encapsulating a - low-level PEP-compliant union type hint, including both :pep:`484`-compliant - :attr:`typing.Union` and :attr:`typing.Optional` unions *and* - :pep:`604`-compliant ``|``-delimited type unions). - """ + ''' + **Partially ordered union type hint** (i.e., high-level object encapsulating + a low-level PEP-compliant union type hint, including both + :pep:`484`-compliant :attr:`typing.Union` and :attr:`typing.Optional` unions + *and* :pep:`604`-compliant ``|``-delimited type unions). + ''' @property def branches(self) -> Iterable[TypeHint]: return self._hints_child_ordered @callable_cached - def is_subtype(self, other: object) -> bool: + def is_subhint(self, other: object) -> bool: # If the passed object is *NOT* a partially ordered type hint, raise an # exception. - other_hint = _die_unless_TypeHint(other) + other_hint = die_unless_typehint(other) # If that hint is *NOT* a partially ordered union type hint, return false. if not isinstance(other_hint, _TypeHintUnion): @@ -751,134 +810,115 @@ def _is_le_branch(self, branch: TypeHint) -> bool: "_TypeHintUnion._is_le_branch() unsupported." ) # pragma: no cover +# ....................{ DICTS }.................... +#FIXME: Shift into a new "_doordata" submodule, please. Note that doing so +#requires as a prerequisite that we first split this submodule into smaller +#submodules, which "_doordata" will then import individually from as needed. -def _die_unless_TypeHint(obj: object) -> TypeHint: - """ - Raise an exception unless the passed object is a **partially ordered type - hint** (i.e., :class:`TypeHint` instance). - - Parameters - ---------- - obj : object - Arbitrary object to be validated. - - Raises - ---------- - BeartypeMathException - If this object is *not* a partially ordered type hint. - """ - - # If this object is *NOT* a partially ordered type hint, raise an exception. - if not isinstance(obj, TypeHint): - raise BeartypeMathException( - f"{repr(obj)} is not a 'beartype.math.TypeHint' instance.", - ) - return obj - - -_HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1 = frozenset( - ( - signs.HintSignAbstractSet, - signs.HintSignAsyncContextManager, - signs.HintSignAsyncIterable, - signs.HintSignAsyncIterator, - signs.HintSignAwaitable, - signs.HintSignCollection, - signs.HintSignContainer, - signs.HintSignContextManager, - signs.HintSignCounter, - signs.HintSignDeque, - signs.HintSignFrozenSet, - signs.HintSignIterable, - signs.HintSignIterator, - signs.HintSignKeysView, - signs.HintSignList, - signs.HintSignMatch, - signs.HintSignMappingView, - signs.HintSignMutableSequence, - signs.HintSignMutableSet, - signs.HintSignPattern, - signs.HintSignReversible, - signs.HintSignSequence, - signs.HintSignSet, - signs.HintSignType, - signs.HintSignValuesView, - ) -) - -""" +# Further initialized below by the _init() function. +HINT_SIGN_TO_TYPEHINT: Dict[HintSign, Type[TypeHint]] = { + signs.HintSignTuple: _TypeHintTuple, + signs.HintSignCallable: _TypeHintCallable, + signs.HintSignLiteral: _TypeHintLiteral, + signs.HintSignAnnotated: _TypeHintAnnotated, +} +''' +Dictionary mapping from each sign uniquely identifying PEP-compliant type hints +to the :class:`TypeHint` subclass handling those hints. +''' + +# ....................{ PRIVATE ~ frozensets }.................... +# Note that the following frozensets are externally imported by unit tests and +# thus intentionally declared as globals rather isolated to the _init() function +# defined below. + +#FIXME: Shift into a new "_doordata" submodule, please. +_HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1 = frozenset(( + signs.HintSignAbstractSet, + signs.HintSignAsyncContextManager, + signs.HintSignAsyncIterable, + signs.HintSignAsyncIterator, + signs.HintSignAwaitable, + signs.HintSignCollection, + signs.HintSignContainer, + signs.HintSignContextManager, + signs.HintSignCounter, + signs.HintSignDeque, + signs.HintSignFrozenSet, + signs.HintSignIterable, + signs.HintSignIterator, + signs.HintSignKeysView, + signs.HintSignList, + signs.HintSignMatch, + signs.HintSignMappingView, + signs.HintSignMutableSequence, + signs.HintSignMutableSet, + signs.HintSignPattern, + signs.HintSignReversible, + signs.HintSignSequence, + signs.HintSignSet, + signs.HintSignType, + signs.HintSignValuesView, +)) +''' Frozen set of all signs uniquely identifying **single-argument PEP-compliant type hints** (i.e., type hints subscriptable by only one child type hint) originating from an **isinstanceable origin type** (i.e., isinstanceable class such that *all* objects satisfying this hint are instances of this class). -Note that the corresponding types in the typing module will have an `_nparams` +Note that the corresponding types in the typing module will have an ``_nparams`` attribute with a value equal to 1. -""" - -_HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2 = frozenset( - ( - signs.HintSignAsyncGenerator, - # signs.HintSignCallable, # defined explicitly below - signs.HintSignChainMap, - signs.HintSignDefaultDict, - signs.HintSignDict, - signs.HintSignItemsView, - signs.HintSignMapping, - signs.HintSignMutableMapping, - signs.HintSignOrderedDict, - ) -) -""" +''' + + +_HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2 = frozenset(( + signs.HintSignAsyncGenerator, + # signs.HintSignCallable, # defined explicitly below + signs.HintSignChainMap, + signs.HintSignDefaultDict, + signs.HintSignDict, + signs.HintSignItemsView, + signs.HintSignMapping, + signs.HintSignMutableMapping, + signs.HintSignOrderedDict, +)) +''' Frozen set of all signs uniquely identifying **two-argument PEP-compliant type hints** (i.e., type hints subscriptable by exactly two child type hints) -Note that the corresponding types in the typing module will have an `_nparams` +Note that the corresponding types in the typing module will have an ``_nparams`` attribute with a value equal to 2. -""" +''' -_HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3 = frozenset( - ( - signs.HintSignCoroutine, - signs.HintSignGenerator, - ) -) -""" + +_HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3 = frozenset(( + signs.HintSignCoroutine, + signs.HintSignGenerator, +)) +''' Frozen set of all signs uniquely identifying **three-argument PEP-compliant type hints** (i.e., type hints subscriptable by exactly three child type hints) -Note that the corresponding types in the typing module will have an `_nparams` +Note that the corresponding types in the typing module will have an ``_nparams`` attribute with a value equal to 3. -""" - - -# Futher initialized below by the _init() function. -_HINT_SIGN_TO_TypeHint: Dict[HintSign, Type[TypeHint]] = { - signs.HintSignTuple: _TypeHintTuple, - signs.HintSignCallable: _TypeHintCallable, - signs.HintSignLiteral: _TypeHintLiteral, - signs.HintSignAnnotated: _TypeHintAnnotated, -} -""" -Dictionary mapping from each sign uniquely identifying PEP-compliant type hints -to the :class:`TypeHint` subclass handling those hints. -""" - +''' +# ....................{ PRIVATE ~ initializers }.................... +#FIXME: Shift into a new "_doordata" submodule, please. def _init() -> None: - """ + ''' Initialize this submodule. - """ + ''' - # Fully initialize the "_HINT_SIGN_TO_TypeHint" dictionary declared above. + # Fully initialize the "HINT_SIGN_TO_TYPEHINT" dictionary declared above. for sign in _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1: - _HINT_SIGN_TO_TypeHint[sign] = _TypeHintOriginIsinstanceableArgs1 + HINT_SIGN_TO_TYPEHINT[sign] = _TypeHintOriginIsinstanceableArgs1 for sign in _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2: - _HINT_SIGN_TO_TypeHint[sign] = _TypeHintOriginIsinstanceableArgs2 + HINT_SIGN_TO_TYPEHINT[sign] = _TypeHintOriginIsinstanceableArgs2 for sign in _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3: - _HINT_SIGN_TO_TypeHint[sign] = _TypeHintOriginIsinstanceableArgs3 + HINT_SIGN_TO_TYPEHINT[sign] = _TypeHintOriginIsinstanceableArgs3 for sign in HINT_SIGNS_UNION: - _HINT_SIGN_TO_TypeHint[sign] = _TypeHintUnion + HINT_SIGN_TO_TYPEHINT[sign] = _TypeHintUnion # Initialize this submodule. diff --git a/beartype/door/_doordata.py b/beartype/door/_doordata.py new file mode 100644 index 00000000..528ff77f --- /dev/null +++ b/beartype/door/_doordata.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# --------------------( LICENSE )-------------------- +# Copyright (c) 2014-2022 Beartype authors. +# See "LICENSE" for further details. + +''' +**Beartype Decidedly Object-Orientedly Recursive (DOOR) data** (i.e., global +constants internally required throughout the :mod:`beartype.door` subpackage). + +This private submodule is *not* intended for importation by downstream callers. +''' + +# ....................{ TODO }.................... + +# ....................{ IMPORTS }.................... diff --git a/beartype/math/__init__.py b/beartype/math/__init__.py deleted file mode 100644 index b14735a5..00000000 --- a/beartype/math/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from beartype.math._mathcls import TypeHint, is_subtype - -__all__ = ['TypeHint', 'is_subtype'] diff --git a/beartype/roar/_roarexc.py b/beartype/roar/_roarexc.py index 194134b0..60f46834 100644 --- a/beartype/roar/_roarexc.py +++ b/beartype/roar/_roarexc.py @@ -616,7 +616,7 @@ class BeartypeMathException(BeartypeException): Abstract base class of all **beartype math exceptions.** Instances of subclasses of this exception are raised at call time from the - callables and classes published by the :func:`beartype.math` subpackage. + callables and classes published by the :func:`beartype.door` subpackage. ''' pass diff --git a/beartype_test/a00_unit/a60_api/door/__init__.py b/beartype_test/a00_unit/a60_api/door/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/beartype_test/a00_unit/a60_api/door/test_door.py b/beartype_test/a00_unit/a60_api/door/test_door.py new file mode 100644 index 00000000..560a9fe6 --- /dev/null +++ b/beartype_test/a00_unit/a60_api/door/test_door.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python3 +# --------------------( LICENSE )-------------------- +# Copyright (c) 2014-2022 Beartype authors. +# See "LICENSE" for further details. + +''' +**Beartype Decidedly Object-Orientedly Recursive (DOOR) API unit tests.** + +This submodule unit tests the public API of the public +:mod:`beartype.door` subpackage. +''' + +# ....................{ IMPORTS }.................... +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# WARNING: To raise human-readable test errors, avoid importing from +# package-specific submodules at module scope. +#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +# ....................{ TODO }.................... + +# ....................{ IMPORTS }.................... +from beartype_test.util.mark.pytskip import skip +from pytest import fixture + +#FIXME: Isolate to tests below, please. +# from beartype.door._doorcls import ( +# _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1, +# _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2, +# _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3, +# TypeHint, +# is_subhint, +# ) +# from beartype.roar import BeartypeMathException + +# ....................{ FIXTURES }.................... +#FIXME: Consider shifting into a new "conftest" plugin of this subpackage. +@fixture(scope='session') +def subhint_superhint_is_cases() -> 'Iterable[Tuple[object, object, bool]]': + ''' + Session-scoped fixture returning an iterable efficiently cached across *all* + tests requiring this fixture. + + This iterable is intentionally defined by the return of this fixture rather + than as a global constant of this submodule. Why? Because the former safely + defers all heavyweight imports required to define this iterable to the call + of the first unit test requiring this fixture, whereas the latter unsafely + performs those imports at pytest test collection time. + + Returns + -------- + Iterable[Tuple[object, object, bool]] + Iterable of one or more 3-tuples ``(subhint, superhint, is_subhint)``, + where: + + * ``subhint`` is the PEP-compliant type hint to be passed as the first + parameter to the :func:`beartype.door.is_subhint` tester. + * ``superhint`` is the PEP-compliant type hint to be passed as the + second parameter to the :func:`beartype.door.is_subhint` tester. + * ``is_subhint`` is ``True`` only if that subhint is actually a subhint + of that superhint according to that tester. + ''' + + # ..................{ IMPORTS }.................. + from beartype import typing as bt + from beartype._util.py.utilpyversion import ( + IS_PYTHON_AT_LEAST_3_9, + IS_PYTHON_AT_LEAST_3_8, + ) + from collections import abc + import typing as t + + # # ..................{ TYPEVARS }.................. + # T = t.TypeVar('T') + # P = t.ParamSpec('P') + + # ..................{ CLASSES }.................. + class MuhThing: + def muh_method(self): + ... + + + class MuhNutherThing: + def __len__(self) -> int: + ... + + + class MuhDict(t.TypedDict): + thing_one: str + thing_two: int + + + class MuhTuple(t.NamedTuple): + thing_one: str + thing_two: int + + # ..................{ LISTS }.................. + SUBHINT_SUPERHINT_IS_CASES = [ + # things are subclasses of themselves + (list, list, True), + (list, t.List, True), + # a few standard, ordered types + (t.Sequence, t.List, False), + (t.Sequence, list, False), + (t.List, t.Sequence, True), + (list, t.Sequence, True), + (list, abc.Sequence, True), + (list, abc.Collection, True), + # a few types that take no params + (str, t.Hashable, True), + (MuhNutherThing, t.Sized, True), + (bytes, t.ByteString, True), + # everything is a subtype of Any + (t.Any, t.Any, True), + (MuhThing, t.Any, True), + (t.Union[int, MuhThing], t.Any, True), + # numeric tower + (float, int, True), + (complex, int, True), + (complex, float, True), + (int, float, False), + (float, complex, False), + # subscripted types + # lists, + (t.List[int], t.List[int], True), + (t.List[int], t.Sequence[int], True), + (t.Sequence[int], t.Iterable[int], True), + (t.Iterable[int], t.Sequence[int], False), + (t.Sequence[int], t.Reversible[int], True), + (t.Sequence[int], t.Reversible[str], False), + (t.Collection[int], t.Sized, True), + (t.List[int], t.List, True), # if the super is un-subscripted, assume t.Any + (t.List[int], t.List[t.Any], True), + (t.Awaitable, t.Awaitable[str], False), + (t.List[int], t.List[str], False), + # maps + (dict, t.Dict, True), + (t.Dict[str, int], t.Dict, True), + (dict, t.Dict[str, int], False), + ( + t.DefaultDict[str, t.Sequence[int]], + t.Mapping[t.Union[str, int], t.Iterable[t.Union[int, str]]], + True, + ), + # callable + (t.Callable, t.Callable[..., t.Any], True), + (t.Callable[[], int], t.Callable[..., t.Any], True), + (t.Callable[[int, str], t.List[int]], t.Callable, True), + (t.Callable[[int, str], t.List[int]], t.Callable, True), + ( + t.Callable[[float, t.List[str]], int], + t.Callable[[int, t.Sequence[str]], int], + True, + ), + (t.Callable[[t.Sequence], int], t.Callable[[list], int], False), + (t.Callable[[], int], t.Callable[..., None], False), + (t.Callable[..., t.Any], t.Callable[..., None], False), + (t.Callable[[float], None], t.Callable[[float, int], None], False), + (t.Callable[[], int], t.Sequence[int], False), + (t.Callable[[int, str], int], t.Callable[[int, str], t.Any], True), + # (types.FunctionType, t.Callable, True), # FIXME + # tuples + (tuple, t.Tuple, True), + (t.Tuple, t.Tuple, True), + (bt.Tuple, t.Tuple, True), + (tuple, t.Tuple[t.Any, ...], True), + (tuple, t.Tuple[()], False), + (bt.Tuple[()], t.Tuple[()], True), + (t.Tuple[()], tuple, True), + (t.Tuple[int, str], t.Tuple[int, str], True), + (t.Tuple[int, str], t.Tuple[int, str, int], False), + (t.Tuple[int, str], t.Tuple[int, t.Union[int, list]], False), + (t.Tuple[t.Union[int, str], ...], t.Tuple[int, str], False), + (t.Tuple[int, str], t.Tuple[str, ...], False), + (t.Tuple[int, str], t.Tuple[t.Union[int, str], ...], True), + (t.Tuple[t.Union[int, str], ...], t.Tuple[t.Union[int, str], ...], True), + (t.Tuple[int], t.Dict[str, int], False), + (t.Tuple[t.Any, ...], t.Tuple[str, int], False), + # unions + (int, t.Union[int, str], True), + (t.Union[int, str], t.Union[list, int, str], True), + (t.Union[str, int], t.Union[int, str, list], True), # order doesn't matter + (t.Union[str, list], t.Union[str, int], False), + (t.Union[int, str, list], list, False), + (t.Union[int, str, list], t.Union[int, str], False), + (int, t.Optional[int], True), + (t.Optional[int], int, False), + (list, t.Optional[t.Sequence], True), + # literals + (t.Literal[1], int, True), + (t.Literal["a"], str, True), + (t.Literal[1, 2, "3"], t.Union[int, str], True), + (t.Literal[1, 2, "3"], t.Union[list, int], False), + (int, t.Literal[1], False), + (t.Literal[1, 2], t.Literal[1, 2, 3], True), + # moar nestz + (t.List[int], t.Union[str, t.List[t.Union[int, str]]], True), + # not really types: + (MuhDict, dict, True), + (MuhTuple, tuple, True), + ] + + # If the active Python interpreter targets Python >= 3.8 and thus supports + # PEP 544... + if IS_PYTHON_AT_LEAST_3_8: + # Arbitrary caching @beartype protocol. + class MuhThingP(bt.Protocol): + def muh_method(self): + ... + + # Append cases exercising subhint protocol relations. + SUBHINT_SUPERHINT_IS_CASES.extend(( + (MuhThing, MuhThingP, True), + (MuhNutherThing, MuhThingP, False), + (MuhThingP, MuhThing, False), + )) + + # If the active Python interpreter targets Python >= 3.9 and thus + # supports PEP 593... + if IS_PYTHON_AT_LEAST_3_9: + # Append cases exercising subhint annotated relations. + SUBHINT_SUPERHINT_IS_CASES.extend(( + (t.Annotated[int, "a note"], int, True), # annotated is subtype of unannotated + (int, t.Annotated[int, "a note"], False), # but not vice versa + (t.Annotated[list, True], t.Annotated[t.Sequence, True], True), + (t.Annotated[list, False], t.Annotated[t.Sequence, True], False), + (t.Annotated[list, 0, 0], t.Annotated[list, 0], False), # must have same num args + (t.Annotated[t.List[int], "metadata"], t.List[int], True), + )) + + # Return this mutable list coerced into an immutable tuple for safety. + return tuple(SUBHINT_SUPERHINT_IS_CASES) + +# ....................{ TESTS ~ testers }.................... +#FIXME: Resolve, please. It looks like Python 3.7 and 3.8 are failing hard. +@skip('Currently known to be broken. *sigh*') +def test_is_subhint( + subhint_superhint_is_cases: 'Iterable[Tuple[object, object, bool]]'): + ''' + Test the :func:`beartype.door.is_subhint` tester. + + Parameters + ---------- + subhint_superhint_is_cases : Iterable[Tuple[object, object, bool]] + Iterable of one or more 3-tuples ``(subhint, superhint, + is_subhint)`` as defined by :func:`subhint_superhint_is_cases`. + ''' + + # Defer heavyweight imports. + from beartype.door import is_subhint + + # For each subhint relation to be tested... + for subhint, superhint, IS_SUBHINT in subhint_superhint_is_cases: + # Assert this tester returns the expected boolean. + assert is_subhint(subhint, superhint) is IS_SUBHINT + + +#FIXME: Currently disabled due to failing tests under at least Python 3.7 and +#3.8. See also relevant commentary at: +# https://github.com/beartype/beartype/pull/136#issuecomment-1175841494 +# EQUALITY_CASES = [ +# (tuple, t.Tuple, True), +# (list, list, True), +# (list, t.List, True), +# (list, t.List[t.Any], True), +# (t.Union[int, str], t.Union[str, int], True), +# (t.Union[int, str], t.Union[str, list], False), +# (tuple, t.Tuple[t.Any, ...], True), +# (t.Annotated[int, "hi"], t.Annotated[int, "hi"], True), +# (t.Annotated[int, "hi"], t.Annotated[int, "low"], False), +# (t.Annotated[int, "hi"], t.Annotated[int, "low"], False), +# (abc.Awaitable[abc.Sequence[int]], t.Awaitable[t.Sequence[int]], True), +# ] +# if IS_PYTHON_AT_LEAST_3_9: +# EQUALITY_CASES.extend( +# [ +# (tuple[str, ...], t.Tuple[str, ...], True), +# (list[str], t.List[str], True), +# ] +# ) +# +# +# @pytest.mark.parametrize("type_a, type_b, expected", EQUALITY_CASES) +# def test_type_equality(type_a, type_b, expected): +# """test that things with the same sign and arguments are equal.""" +# hint_a = TypeHint(type_a) +# hint_b = TypeHint(type_b) +# assert (hint_a == hint_b) is expected +# +# # smoke test +# assert hint_a != TypeHint(t.Generator[t.Union[list, str], str, None]) +# +# assert hint_a != "notahint" +# with pytest.raises(TypeError, match="not supported between"): +# hint_a <= "notahint" +# +# +# def test_type_hint_singleton(): +# """Recreating a type hint with the same input should yield the same type hint.""" +# assert TypeHint(t.List[t.Any]) is TypeHint(t.List[t.Any]) +# assert TypeHint(int) is TypeHint(int) +# assert TypeHint(TypeHint(int)) is TypeHint(int) +# +# # alas, this is not true: +# assert TypeHint(t.List) is not TypeHint(list) +# # leaving here for future reference, and so we know if we've fixed it. +# +# +# def test_typehint_fail(): +# with pytest.raises(BeartypeMathException): +# TypeHint(1) +# +# +# @pytest.mark.parametrize( +# "nparams, sign_group", +# [ +# (1, _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1), +# (2, _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2), +# (3, _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3), +# ], +# ) +# def test_arg_nparams(nparams, sign_group): +# """Make sure that our hint sign groups are consistent with the typing module.""" +# for sign in sign_group: +# actual = getattr(t, sign.name)._nparams +# assert ( +# actual == nparams +# ), f"{sign.name} has {actual} params, should have {nparams}" +# +# +# def test_callable_takes_args(): +# assert TypeHint(t.Callable[[], t.Any]).takes_no_args is True +# assert TypeHint(t.Callable[[int], t.Any]).takes_no_args is False +# assert TypeHint(t.Callable[..., t.Any]).takes_no_args is False +# +# assert TypeHint(t.Callable[..., t.Any]).takes_any_args is True +# assert TypeHint(t.Callable[[int], t.Any]).takes_any_args is False +# assert TypeHint(t.Callable[[], t.Any]).takes_any_args is False +# +# +# def test_empty_tuple(): +# assert TypeHint(t.Tuple[()]).is_empty_tuple +# +# +# def test_hint_iterable(): +# assert list(TypeHint(t.Union[int, str])) == [TypeHint(int), TypeHint(str)] +# assert not list(TypeHint(int)) +# +# +# def test_hint_ordered_comparison(): +# a = TypeHint(t.Callable[[], list]) +# b = TypeHint(t.Callable[..., t.Sequence[t.Any]]) +# +# assert a <= b +# assert a < b +# assert a != b +# assert not a > b +# assert not a >= b +# +# with pytest.raises(TypeError, match="not supported between"): +# a <= 1 +# with pytest.raises(TypeError, match="not supported between"): +# a < 1 +# with pytest.raises(TypeError, match="not supported between"): +# a >= 1 +# with pytest.raises(TypeError, match="not supported between"): +# a > 1 +# +# +# def test_hint_repr(): +# annotation = t.Callable[[], list] +# hint = TypeHint(annotation) +# assert repr(annotation) in repr(hint) +# +# +# def test_types_that_are_just_origins(): +# assert TypeHint(t.Callable)._is_just_an_origin +# assert TypeHint(t.Callable[..., t.Any])._is_just_an_origin +# assert TypeHint(t.Tuple)._is_just_an_origin +# assert TypeHint(t.Tuple[t.Any, ...])._is_just_an_origin +# assert TypeHint(int)._is_just_an_origin +# +# +# def test_invalid_subtype_comparison(): +# hint = TypeHint(t.Callable[[], list]) +# with pytest.raises( +# BeartypeMathException, match="not a 'beartype.door.TypeHint' instance" +# ): +# hint.is_subhint(int) +# +# +# def test_callable_param_spec(): +# # TODO +# with pytest.raises(NotImplementedError): +# TypeHint(t.Callable[t.ParamSpec("P"), t.TypeVar("T")]) +# +# +# def test_generic(): +# # TODO +# class MyGeneric(t.Generic[T]): +# ... +# +# with pytest.raises(BeartypeMathException, match="currently unsupported by class"): +# TypeHint(MyGeneric[int]) diff --git a/beartype_test/test_is_subtype.py b/beartype_test/test_is_subtype.py deleted file mode 100644 index 0d394926..00000000 --- a/beartype_test/test_is_subtype.py +++ /dev/null @@ -1,315 +0,0 @@ -#FIXME: Currently disabled due to failing tests under at least Python 3.7 and -#3.8. See also relevant commentary at: -# https://github.com/beartype/beartype/pull/136#issuecomment-1175841494 - -# import typing as t -# from collections import abc -# -# import beartype.typing as bt -# import pytest -# from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 -# from beartype.math._mathcls import ( -# _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1, -# _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2, -# _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3, -# TypeHint, -# is_subtype, -# ) -# from beartype.roar import BeartypeMathException -# -# -# class MuhThingP(bt.Protocol): -# def muh_method(self): -# ... -# -# -# class MuhThing: -# def muh_method(self): -# ... -# -# -# class MuhNutherThing: -# def __len__(self) -> int: -# ... -# -# -# class MuhDict(t.TypedDict): -# thing_one: str -# thing_two: int -# -# -# class MuhTuple(t.NamedTuple): -# thing_one: str -# thing_two: int -# -# -# T = t.TypeVar("T") -# P = t.ParamSpec("P") -# -# SUBTYPE_CASES = [ -# # things are subclasses of themselves -# (list, list, True), -# (list, t.List, True), -# # a few standard, ordered types -# (t.Sequence, t.List, False), -# (t.Sequence, list, False), -# (t.List, t.Sequence, True), -# (list, t.Sequence, True), -# (list, abc.Sequence, True), -# (list, abc.Collection, True), -# # a few types that take no params -# (str, t.Hashable, True), -# (MuhNutherThing, t.Sized, True), -# (bytes, t.ByteString, True), -# # everything is a subtype of Any -# (t.Any, t.Any, True), -# (MuhThing, t.Any, True), -# (t.Union[int, MuhThing], t.Any, True), -# # numeric tower -# (float, int, True), -# (complex, int, True), -# (complex, float, True), -# (int, float, False), -# (float, complex, False), -# # subscripted types -# # lists, -# (t.List[int], t.List[int], True), -# (t.List[int], t.Sequence[int], True), -# (t.Sequence[int], t.Iterable[int], True), -# (t.Iterable[int], t.Sequence[int], False), -# (t.Sequence[int], t.Reversible[int], True), -# (t.Sequence[int], t.Reversible[str], False), -# (t.Collection[int], t.Sized, True), -# (t.List[int], t.List, True), # if the super is un-subscripted, assume t.Any -# (t.List[int], t.List[t.Any], True), -# (t.Awaitable, t.Awaitable[str], False), -# (t.List[int], t.List[str], False), -# # maps -# (dict, t.Dict, True), -# (t.Dict[str, int], t.Dict, True), -# (dict, t.Dict[str, int], False), -# ( -# t.DefaultDict[str, t.Sequence[int]], -# t.Mapping[t.Union[str, int], t.Iterable[t.Union[int, str]]], -# True, -# ), -# # callable -# (t.Callable, t.Callable[..., t.Any], True), -# (t.Callable[[], int], t.Callable[..., t.Any], True), -# (t.Callable[[int, str], t.List[int]], t.Callable, True), -# (t.Callable[[int, str], t.List[int]], t.Callable, True), -# ( -# t.Callable[[float, t.List[str]], int], -# t.Callable[[int, t.Sequence[str]], int], -# True, -# ), -# (t.Callable[[t.Sequence], int], t.Callable[[list], int], False), -# (t.Callable[[], int], t.Callable[..., None], False), -# (t.Callable[..., t.Any], t.Callable[..., None], False), -# (t.Callable[[float], None], t.Callable[[float, int], None], False), -# (t.Callable[[], int], t.Sequence[int], False), -# (t.Callable[[int, str], int], t.Callable[[int, str], t.Any], True), -# # (types.FunctionType, t.Callable, True), # FIXME -# # tuples -# (tuple, t.Tuple, True), -# (t.Tuple, t.Tuple, True), -# (bt.Tuple, t.Tuple, True), -# (tuple, t.Tuple[t.Any, ...], True), -# (tuple, t.Tuple[()], False), -# (bt.Tuple[()], t.Tuple[()], True), -# (t.Tuple[()], tuple, True), -# (t.Tuple[int, str], t.Tuple[int, str], True), -# (t.Tuple[int, str], t.Tuple[int, str, int], False), -# (t.Tuple[int, str], t.Tuple[int, t.Union[int, list]], False), -# (t.Tuple[t.Union[int, str], ...], t.Tuple[int, str], False), -# (t.Tuple[int, str], t.Tuple[str, ...], False), -# (t.Tuple[int, str], t.Tuple[t.Union[int, str], ...], True), -# (t.Tuple[t.Union[int, str], ...], t.Tuple[t.Union[int, str], ...], True), -# (t.Tuple[int], t.Dict[str, int], False), -# (t.Tuple[t.Any, ...], t.Tuple[str, int], False), -# # unions -# (int, t.Union[int, str], True), -# (t.Union[int, str], t.Union[list, int, str], True), -# (t.Union[str, int], t.Union[int, str, list], True), # order doesn't matter -# (t.Union[str, list], t.Union[str, int], False), -# (t.Union[int, str, list], list, False), -# (t.Union[int, str, list], t.Union[int, str], False), -# (int, t.Optional[int], True), -# (t.Optional[int], int, False), -# (list, t.Optional[t.Sequence], True), -# # literals -# (t.Literal[1], int, True), -# (t.Literal["a"], str, True), -# (t.Literal[1, 2, "3"], t.Union[int, str], True), -# (t.Literal[1, 2, "3"], t.Union[list, int], False), -# (int, t.Literal[1], False), -# (t.Literal[1, 2], t.Literal[1, 2, 3], True), -# # protocols -# (MuhThing, MuhThingP, True), -# (MuhNutherThing, MuhThingP, False), -# (MuhThingP, MuhThing, False), -# # moar nestz -# (t.List[int], t.Union[str, t.List[t.Union[int, str]]], True), -# # not really types: -# (MuhDict, dict, True), -# (MuhTuple, tuple, True), -# # annotated: -# (t.Annotated[int, "a note"], int, True), # annotated is subtype of unannotated -# (int, t.Annotated[int, "a note"], False), # but not vice versa -# (t.Annotated[list, True], t.Annotated[t.Sequence, True], True), -# (t.Annotated[list, False], t.Annotated[t.Sequence, True], False), -# (t.Annotated[list, 0, 0], t.Annotated[list, 0], False), # must have same num args -# (t.Annotated[t.List[int], "metadata"], t.List[int], True), -# ] -# -# -# @pytest.mark.parametrize("subt, supert, expected_result", SUBTYPE_CASES) -# def test_is_subtype(subt, supert, expected_result): -# """Test all the subtype cases.""" -# assert is_subtype(subt, supert) is expected_result -# -# -# EQUALITY_CASES = [ -# (tuple, t.Tuple, True), -# (list, list, True), -# (list, t.List, True), -# (list, t.List[t.Any], True), -# (t.Union[int, str], t.Union[str, int], True), -# (t.Union[int, str], t.Union[str, list], False), -# (tuple, t.Tuple[t.Any, ...], True), -# (t.Annotated[int, "hi"], t.Annotated[int, "hi"], True), -# (t.Annotated[int, "hi"], t.Annotated[int, "low"], False), -# (t.Annotated[int, "hi"], t.Annotated[int, "low"], False), -# (abc.Awaitable[abc.Sequence[int]], t.Awaitable[t.Sequence[int]], True), -# ] -# if IS_PYTHON_AT_LEAST_3_9: -# EQUALITY_CASES.extend( -# [ -# (tuple[str, ...], t.Tuple[str, ...], True), -# (list[str], t.List[str], True), -# ] -# ) -# -# -# @pytest.mark.parametrize("type_a, type_b, expected", EQUALITY_CASES) -# def test_type_equality(type_a, type_b, expected): -# """test that things with the same sign and arguments are equal.""" -# hint_a = TypeHint(type_a) -# hint_b = TypeHint(type_b) -# assert (hint_a == hint_b) is expected -# -# # smoke test -# assert hint_a != TypeHint(t.Generator[t.Union[list, str], str, None]) -# -# assert hint_a != "notahint" -# with pytest.raises(TypeError, match="not supported between"): -# hint_a <= "notahint" -# -# -# def test_type_hint_singleton(): -# """Recreating a type hint with the same input should yield the same type hint.""" -# assert TypeHint(t.List[t.Any]) is TypeHint(t.List[t.Any]) -# assert TypeHint(int) is TypeHint(int) -# assert TypeHint(TypeHint(int)) is TypeHint(int) -# -# # alas, this is not true: -# assert TypeHint(t.List) is not TypeHint(list) -# # leaving here for future reference, and so we know if we've fixed it. -# -# -# def test_typehint_fail(): -# with pytest.raises(BeartypeMathException): -# TypeHint(1) -# -# -# @pytest.mark.parametrize( -# "nparams, sign_group", -# [ -# (1, _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1), -# (2, _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2), -# (3, _HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3), -# ], -# ) -# def test_arg_nparams(nparams, sign_group): -# """Make sure that our hint sign groups are consistent with the typing module.""" -# for sign in sign_group: -# actual = getattr(t, sign.name)._nparams -# assert ( -# actual == nparams -# ), f"{sign.name} has {actual} params, should have {nparams}" -# -# -# def test_callable_takes_args(): -# assert TypeHint(t.Callable[[], t.Any]).takes_no_args is True -# assert TypeHint(t.Callable[[int], t.Any]).takes_no_args is False -# assert TypeHint(t.Callable[..., t.Any]).takes_no_args is False -# -# assert TypeHint(t.Callable[..., t.Any]).takes_any_args is True -# assert TypeHint(t.Callable[[int], t.Any]).takes_any_args is False -# assert TypeHint(t.Callable[[], t.Any]).takes_any_args is False -# -# -# def test_empty_tuple(): -# assert TypeHint(t.Tuple[()]).is_empty_tuple -# -# -# def test_hint_iterable(): -# assert list(TypeHint(t.Union[int, str])) == [TypeHint(int), TypeHint(str)] -# assert not list(TypeHint(int)) -# -# -# def test_hint_ordered_comparison(): -# a = TypeHint(t.Callable[[], list]) -# b = TypeHint(t.Callable[..., t.Sequence[t.Any]]) -# -# assert a <= b -# assert a < b -# assert a != b -# assert not a > b -# assert not a >= b -# -# with pytest.raises(TypeError, match="not supported between"): -# a <= 1 -# with pytest.raises(TypeError, match="not supported between"): -# a < 1 -# with pytest.raises(TypeError, match="not supported between"): -# a >= 1 -# with pytest.raises(TypeError, match="not supported between"): -# a > 1 -# -# -# def test_hint_repr(): -# annotation = t.Callable[[], list] -# hint = TypeHint(annotation) -# assert repr(annotation) in repr(hint) -# -# -# def test_types_that_are_just_origins(): -# assert TypeHint(t.Callable)._is_just_an_origin -# assert TypeHint(t.Callable[..., t.Any])._is_just_an_origin -# assert TypeHint(t.Tuple)._is_just_an_origin -# assert TypeHint(t.Tuple[t.Any, ...])._is_just_an_origin -# assert TypeHint(int)._is_just_an_origin -# -# -# def test_invalid_subtype_comparison(): -# hint = TypeHint(t.Callable[[], list]) -# with pytest.raises( -# BeartypeMathException, match="not a 'beartype.math.TypeHint' instance" -# ): -# hint.is_subtype(int) -# -# -# def test_callable_param_spec(): -# # TODO -# with pytest.raises(NotImplementedError): -# TypeHint(t.Callable[t.ParamSpec("P"), t.TypeVar("T")]) -# -# -# def test_generic(): -# # TODO -# class MyGeneric(t.Generic[T]): -# ... -# -# with pytest.raises(BeartypeMathException, match="currently unsupported by class"): -# TypeHint(MyGeneric[int])