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) == ()
Exemple #2
0
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))
    )
Exemple #3
0
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)
Exemple #4
0
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