def test_get_hint_pep_typevars() -> None: ''' Test the :func:`beartype._util.hint.pep.utilhintpepget.get_hint_pep_typevars` getter. ''' # Defer heavyweight imports. from beartype._util.hint.pep.utilhintpepget import get_hint_pep_typevars from beartype_test.unit.data.hint.data_hint import NOT_HINTS_PEP from beartype_test.unit.data.hint.pep.data_hintpep import HINTS_PEP_META # Assert this getter returns... for hint_pep_meta in HINTS_PEP_META: # Tuple of all tupe variables returned by this function. hint_pep_typevars = get_hint_pep_typevars(hint_pep_meta.hint) # Returns one or more type variables for typevared PEP-compliant type # hints. if hint_pep_meta.is_typevared: assert isinstance(hint_pep_typevars, tuple) assert hint_pep_typevars # *NO* type variables for untypevared PEP-compliant type hints. else: assert hint_pep_typevars == () # Assert this getter returns *NO* type variables for non-"typing" hints. for not_hint_pep in NOT_HINTS_PEP: assert get_hint_pep_typevars(not_hint_pep) == ()
def is_hint_pep_subscripted(hint: object) -> bool: ''' ``True`` only if the passed object is a PEP-compliant type hint subscripted by one or more **arguments** (i.e., PEP-compliant child type hints) and/or **type variables** (i.e., instances of the :class:`TypeVar` class). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ---------- bool ``True`` only if this object is a PEP-compliant type hint subscripted by one or more arguments and/or type variables. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilhintpepget import ( get_hint_pep_args, get_hint_pep_typevars, ) # Return true only if this hint is either... return ( # A PEP-compliant subscripted generic under Python >= 3.9, including: # * A PEP 484- or 585-compliant subscripted generic. # * A PEP 585-compliant builtin type hint. # # Note this test is technically redundant, since all subscripted # generics *MUST* necessarily be subscripted by one or more arguments # and/or type variables, subsequently tested for. Nonetheless, this # test efficiently reduces to a builtin call and is thus preferable. isinstance(hint, HintGenericSubscriptedType) or # Any other PEP-compliant type hint subscripted by one or more # arguments and/or type variables. Note that this test is *NOT* # reducible to merely: # bool(get_hint_pep_args(hint) or get_hint_pep_typevars(hint)) # Frankly, we have no idea why. We suspect we'd probably have to # change the "or" operator in the above expression to the "+" operator, # at which point the resulting operation is likely to be substantially # slower than the simple series of tests performed here. bool(get_hint_pep_args(hint)) or bool(get_hint_pep_typevars(hint)) )
def get_hint_pep585_generic_typevars(hint: object) -> Tuple[type, ...]: ''' Tuple of all **unique type variables** (i.e., subscripted :class:`TypeVar` instances of the passed `PEP 585`_-compliant generic listed by the caller at hint declaration time ignoring duplicates) if any *or* the empty tuple otherwise. This getter is memoized for efficiency. Motivation ---------- The current implementation of `PEP 585`_ under at least Python 3.9 is fundamentally broken with respect to parametrized generics. While `PEP 484`_-compliant generics properly propagate type variables from pseudo-superclasses to subclasses, `PEP 585`_ fails to do so. This function "fills in the gaps" by recovering these type variables from parametrized `PEP 585`_-compliant generics by iteratively constructing a new tuple from the type variables parametrizing all pseudo-superclasses of this generic. Parameters ---------- hint : object Object to be inspected. Returns ---------- Tuple[TypeVar, ...] Either: * If this `PEP 585`_-compliant generic defines a ``__parameters__`` dunder attribute, the value of that attribute. * Else, the empty tuple. Raises ---------- BeartypeDecorHintPep585Exception If this hint is *not* a `PEP 585`_-compliant generic. .. _PEP 484: https://www.python.org/dev/peps/pep-0484 .. _PEP 585: https://www.python.org/dev/peps/pep-0585 ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilhintpepget import get_hint_pep_typevars # Tuple of all pseudo-superclasses of this PEP 585-compliant generic. hint_bases = get_hint_pep585_generic_bases_unerased(hint) # Set of all type variables parametrizing these pseudo-superclasses. # # Note the following inefficient iteration *CANNOT* be reduced to an # efficient set comprehension, as each get_hint_pep_typevars() call returns # a tuple of type variables rather than single type variable to be added to # this set. hint_typevars: Set[type] = set() # For each such pseudo-superclass, add all type variables parametrizing # this pseudo-superclass to this set. for hint_base in hint_bases: # print(f'hint_base_typevars: {hint_base} [{get_hint_pep_typevars(hint_base)}]') hint_typevars.update(get_hint_pep_typevars(hint_base)) # Return this set coerced into a tuple. return tuple(hint_typevars)
def is_hint_pep_typevared(hint: object) -> bool: ''' ``True`` only if the passed object is a PEP-compliant type hint parametrized by one or more **type variables** (i.e., instances of the :class:`TypeVar` class). This tester detects both: * **Direct parametrizations** (i.e., cases in which this object itself is directly parametrized by type variables). * **Superclass parametrizations** (i.e., cases in which this object is indirectly parametrized by one or more superclasses of its class being directly parametrized by type variables). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Semantics ---------- **Generics** (i.e., PEP-compliant type hints whose classes subclass one or more public :mod:`typing` pseudo-superclasses) are often but *not* always typevared. For example, consider the untypevared generic: >>> from typing import List >>> class UntypevaredGeneric(List[int]): pass >>> UntypevaredGeneric.__mro__ (__main__.UntypevaredGeneric, list, typing.Generic, object) >>> UntypevaredGeneric.__parameters__ () Likewise, typevared hints are often but *not* always generic. For example, consider the typevared non-generic: >>> from typing import List, TypeVar >>> TypevaredNongeneric = List[TypeVar('T')] >>> type(TypevaredNongeneric).__mro__ (typing._GenericAlias, typing._Final, object) >>> TypevaredNongeneric.__parameters__ (~T,) Parameters ---------- hint : object Object to be inspected. Returns ---------- bool ``True`` only if this object is a PEP-compliant type hint parametrized by one or more type variables. Examples ---------- >>> import typing >>> from beartype._util.hint.pep.utilhintpeptest import ( ... is_hint_pep_typevared) >>> T = typing.TypeVar('T') >>> class UserList(typing.List[T]): pass # Unparametrized type hint. >>> is_hint_pep_typevared(typing.List[int]) False # Directly parametrized type hint. >>> is_hint_pep_typevared(typing.List[T]) True # Superclass-parametrized type hint. >>> is_hint_pep_typevared(UserList) True ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilhintpepget import get_hint_pep_typevars # Return true only if this is a "typing" type parametrized by one or more # type variables, trivially detected by testing whether the tuple of all # type variables parametrizing this "typing" type if this type is a generic # alias (e.g., "typing._GenericAlias" subtype) *OR* the empty tuple # otherwise is non-empty. return len(get_hint_pep_typevars(hint)) > 0