Beispiel #1
0
    def get_hint_pep_args(hint: object) -> tuple:

        # Avoid circular import dependencies.
        from beartype._util.hint.pep.utilhintpeptest import is_hint_pep_typevar

        # If this hint is a poorly designed Python 3.6-specific "type alias",
        # this hint is a subscription of either the "typing.Match" or
        # "typing.Pattern" objects. In this case, this hint declares a
        # non-standard "type_var" instance variable whose value is either
        # "typing.AnyStr", "str", or "bytes". Since only the former is an
        # actual type variable, however, we further test that condition.
        if isinstance(hint, typing._TypeAlias):  # type: ignore[attr-defined]
            # If this value is a type variable, return the empty tuple.
            if is_hint_pep_typevar(hint.type_var):
                return ()
            # Else, this value is either the builtin "str" or "bytes" class. In
            # either case, return a new 1-tuple containing only this class.
            else:
                return (hint.type_var, )

        # Else, this hint is a poorly designed Python 3.6-specific "generic
        # meta." In this case, this hint declares the standard
        # "__parameters__" dunder instance variable in a non-standard way.
        # Specifically, the trailing "or ()" test below is needed to handle
        # undocumented edge cases under the Python 3.6-specific implementation
        # of the "typing" module:
        #       >>> import typing as t
        #       >>> t.Tuple.__args__   # yes, this is total bullocks
        #       None
        return getattr(hint, '__args__', ()) or ()
def test_is_hint_pep_typevar() -> None:
    '''
    Test the
    :func:`beartype._util.hint.pep.utilhintpeptest.is_hint_pep_typevar`
    tester.
    '''

    # Defer heavyweight imports.
    from beartype._util.hint.pep.utilhintpeptest import is_hint_pep_typevar
    from beartype_test.a00_unit.data.hint.pep.proposal.data_hintpep484 import T
    from typing import Optional

    # Assert that type variables are type variables.
    assert is_hint_pep_typevar(T) is True

    # Assert that "typing" types parametrized by type variables are *NOT* type
    # variables.
    assert is_hint_pep_typevar(Optional[T]) is False
Beispiel #3
0
    def get_cause_or_none(self) -> 'Optional[str]':
        '''
        Human-readable string describing the failure of this pith to satisfy
        this PEP-compliant type hint if this pith fails to satisfy this pith
        *or* ``None`` otherwise (i.e., if this pith satisfies this hint).

        Design
        ----------
        This getter is intentionally generalized to support objects both
        satisfying and *not* satisfying hints as equally valid use cases. While
        the parent :func:`.peperror.raise_pep_call_exception` function
        calling this getter is *always* passed an object *not* satisfying the
        passed hint, this getter is under no such constraints. Why? Because
        this getter is also called to find which of an arbitrary number of
        objects transitively nested in the object passed to
        :func:`.peperror.raise_pep_call_exception` fails to satisfy the
        corresponding hint transitively nested in the hint passed to that
        function.

        For example, consider the PEP-compliant type hint ``List[Union[int,
        str]]`` describing a list whose items are either integers or strings
        and the list ``list(range(256)) + [False,]`` consisting of the integers
        0 through 255 followed by boolean ``False``. Since this list is a
        standard sequence, the
        :func:`._peperrorsequence.get_cause_or_none_sequence_standard`
        function must decide the cause of this list's failure to comply with
        this hint by finding the list item that is neither an integer nor a
        string, implemented by by iteratively passing each list item to the
        :func:`._peperrorunion.get_cause_or_none_union` function. Since
        the first 256 items of this list are integers satisfying this hint,
        :func:`._peperrorunion.get_cause_or_none_union` returns
        ``None`` to
        :func:`._peperrorsequence.get_cause_or_none_sequence_standard`
        before finally finding the non-compliant boolean item and returning the
        human-readable cause.

        Returns
        ----------
        Optional[str]
            Either:

            * If this object fails to satisfy this hint, human-readable string
            describing the failure of this object to do so.
            * Else, ``None``.

        Raises
        ----------
        _BeartypeCallHintPepRaiseException
            If this type hint is either:

            * PEP-noncompliant (e.g., tuple union).
            * PEP-compliant but no getter function has been implemented to
              handle this category of PEP-compliant type hint yet.
        '''

        # Getter function returning the desired string.
        get_cause_or_none = None

        # If this hint is ignorable, all possible objects satisfy this hint,
        # implying this hint *CANNOT* by definition be the cause of this
        # failure. In this case, immediately report None.
        if is_hint_ignorable(self.hint):
            return None
        # Else, this hint is unignorable.
        #
        # If *NO* sign uniquely identifies this hint, this hint is
        # PEP-noncompliant. In this case...
        elif self.hint_sign is None:
            # Avoid circular import dependencies.
            from beartype._decor._code._pep._error._peperrortype import (
                get_cause_or_none_type)

            # Defer to the getter function supporting non-"typing" classes.
            get_cause_or_none = get_cause_or_none_type
        # Else, this hint is PEP-compliant.
        #
        # If this PEP-compliant hint is its own unsubscripted "typing"
        # attribute (e.g., "typing.List" rather than "typing.List[str]") and is
        # thus subscripted by *NO* child hints...
        elif self.hint is self.hint_sign:
            # If this hint is the non-standard "typing.NoReturn" type hint
            # specific to return values...
            if self.hint is NoReturn:
                # Avoid circular import dependencies.
                from beartype._decor._code._pep._error._peperrorreturn import (
                    get_cause_or_none_noreturn)

                # Defer to the getter function specific to this hint.
                get_cause_or_none = get_cause_or_none_noreturn
            # Else, this hint is a standard PEP-compliant type hint supported
            # by both parameters and return values. In this case, we assume
            # this hint to originate from an origin type.
            else:
                # Avoid circular import dependencies.
                from beartype._decor._code._pep._error._peperrortype import (
                    get_cause_or_none_type_origin)

                # Defer to the getter function supporting hints originating
                # from origin types.
                get_cause_or_none = get_cause_or_none_type_origin
        # Else, this PEP-compliant hint is *NOT* its own unsubscripted "typing"
        # attribute. In this case...
        else:
            # If this hint is neither...
            if not (
                    # Subscripted by no child hints *NOR*...
                    self.hint_childs or
                    # An empty fixed-length tuple hint, whose PEP 585 (but *NOT*
                    # PEP 484)-compliant implementation is subscripted by no child
                    # hints *NOR*...
                    is_hint_pep_tuple_empty(self.hint) or
                    # A forward reference nor type variable, whose designs reside
                    # well outside the standard "typing" dunder variable API and
                    # are thus *NEVER* subscripted by child hints...
                    is_hint_forwardref(self.hint)
                    or is_hint_pep_typevar(self.hint)):
                # Then this hint should have been subscripted by one or more child
                # hints but wasn't. In this case, raise an exception.
                raise _BeartypeCallHintPepRaiseException(
                    f'{self.exception_label} PEP type hint '
                    f'{repr(self.hint)} unsubscripted.')
            # Else, thus subscripted by one or more child hints (e.g.,
            # "typing.List[str]" rather than "typing.List")
            #
            # Else, this hint is subscripted by one or more child hints.

            # Avoid circular import dependencies.
            from beartype._decor._code._pep._error.peperror import (
                PEP_HINT_SIGN_TO_GET_CAUSE_FUNC)

            # Getter function returning the desired string for this attribute
            # if any *OR* "None" otherwise.
            get_cause_or_none = PEP_HINT_SIGN_TO_GET_CAUSE_FUNC.get(
                self.hint_sign, None)

            # If no such function has been implemented to handle this attribute
            # yet, raise an exception.
            if get_cause_or_none is None:
                raise _BeartypeCallHintPepRaiseException(
                    f'{self.exception_label} PEP type hint '
                    f'{repr(self.hint)} unsupported (i.e., no '
                    f'"get_cause_or_none_"-prefixed getter function defined '
                    f'for this category of hint).')
            # Else, a getter function has been implemented to handle this
            # attribute.

        # Call this getter function with ourselves and return the string
        # returned by this getter.
        return get_cause_or_none(self)
Beispiel #4
0
def get_hint_pep_sign(hint: Any) -> object:
    '''
    **Sign** (i.e., arbitrary object) uniquely identifying the passed
    PEP-compliant type hint if this hint is PEP-compliant *or* raise an
    exception otherwise (i.e., if this hint is *not* PEP-compliant).

    This getter function associates the passed hint with a public attribute of
    the :mod:`typing` module effectively acting as a superclass of this hint
    and thus uniquely identifying the "type" of this hint in the broadest sense
    of the term "type". These attributes are typically *not* actual types, as
    most actual :mod:`typing` types are private, fragile, and prone to extreme
    violation (or even removal) between major Python versions. Nonetheless,
    these attributes are sufficiently unique to enable callers to distinguish
    between numerous broad categories of :mod:`typing` behaviour and logic.

    Specifically, this function returns either:

    * If this hint is a `PEP 585`_-compliant **builtin** (e.g., C-based type
      hint instantiated by subscripting either a concrete builtin container
      class like :class:`list` or :class:`tuple` *or* an abstract base class
      (ABC) declared by the :mod:`collections.abc` submodule like
      :class:`collections.abc.Iterable` or :class:`collections.abc.Sequence`),
      :class:`beartype.cave.HintGenericSubscriptedType`.
    * If this hint is a **generic** (i.e., subclasses of the
      :class:`typing.Generic` abstract base class (ABC)),
      :class:`typing.Generic`. Note this includes `PEP 544`-compliant
      **protocols** (i.e., subclasses of the :class:`typing.Protocol` ABC),
      which implicitly subclass the :class:`typing.Generic` ABC as well.
    * If this hint is any other class declared by either the :mod:`typing`
      module (e.g., :class:`typing.TypeVar`) *or* the :mod:`beartype.cave`
      submodule (e.g., :class:`beartype.cave.HintGenericSubscriptedType`), that class.
    * If this hint is a **forward reference** (i.e., string or instance of the
      concrete :class:`typing.ForwardRef` class), :class:`typing.ForwardRef`.
    * If this hint is a **type variable** (i.e., instance of the concrete
      :class:`typing.TypeVar` class), :class:`typing.TypeVar`.
    * Else, the unsubscripted :mod:`typing` attribute dynamically retrieved by
      inspecting this hint's **object representation** (i.e., the
      non-human-readable string returned by the :func:`repr` builtin).

    This getter function is memoized for efficiency.

    Motivation
    ----------
    Both `PEP 484`_ and the :mod:`typing` module implementing `PEP 484`_ are
    functionally deficient with respect to their public APIs. Neither provide
    external callers any means of deciding the categories of arbitrary
    PEP-compliant type hints. For example, there exists no general-purpose
    means of identifying a parametrized subtype (e.g., ``typing.List[int]``) as
    a parametrization of its unparameterized base type (e.g., ``type.List``).
    Thus this function, which "fills in the gaps" by implementing this
    oversight.

    Parameters
    ----------
    hint : object
        Object to be inspected.

    Returns
    ----------
    dict
        Sign uniquely identifying this hint.

    Raises
    ----------
    AttributeError
        If this object's representation is prefixed by the substring
        ``"typing."``, but the remainder of that representation up to but *not*
        including the first "[" character in that representation (e.g.,
        ``"Dict"`` for the object ``typing.Dict[str, Tuple[int, ...]]``) is
        *not* an attribute of the :mod:`typing` module.
    BeartypeDecorHintPepException
        If this object is *not* a PEP-compliant type hint.
    BeartypeDecorHintPepSignException
        If this object is a PEP-compliant type hint *not* uniquely identifiable
        by a sign.

    Examples
    ----------
        >>> import typing
        >>> from beartype._util.hint.pep.utilhintpepget import (
        ...     get_hint_pep_sign)
        >>> get_hint_pep_sign(typing.Any)
        typing.Any
        >>> get_hint_pep_sign(typing.Union[str, typing.Sequence[int]])
        typing.Union
        >>> T = typing.TypeVar('T')
        >>> get_hint_pep_sign(T)
        typing.TypeVar
        >>> class Genericity(typing.Generic[T]): pass
        >>> get_hint_pep_sign(Genericity)
        typing.Generic
        >>> class Duplicity(typing.Iterable[T], typing.Container[T]): pass
        >>> get_hint_pep_sign(Duplicity)
        typing.Iterable

    .. _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.utilhinttest import is_hint_forwardref
    from beartype._util.hint.pep.utilhintpeptest import (
        die_unless_hint_pep,
        is_hint_pep_generic,
        is_hint_pep_typevar,
    )

    # If this hint is *NOT* PEP-compliant, raise an exception.
    #
    # Note that we intentionally avoid calling the
    # die_if_hint_pep_unsupported() function here, which calls the
    # is_hint_pep_supported() function, which calls this function.
    die_unless_hint_pep(hint)
    # Else, this hint is PEP-compliant.

    # Note that these tests are intentionally ordered in descending specificity
    # (i.e., ascending genericity) rather than descending likelihood of a
    # match, which would return erroneous signs in common cases.
    #
    # If this hint is the PEP 484-compliant "None" singleton, return the type
    # of that singleton.
    if hint is None:
        return NoneType
    # Else, this hint is *NOT* the PEP 484-compliant "None" singleton.
    #
    # If this hint is a PEP-compliant generic (i.e., class
    # superficially subclassing at least one non-class PEP-compliant object),
    # return the "typing.Generic" abstract base class (ABC) generically -- get
    # it? -- identifying PEP-compliant generics. Note that:
    #
    # * Generics *CANNOT* be detected by the general-purpose logic performed
    #   below, as this ABC does *NOT* define a __repr__() dunder method
    #   returning a string prefixed by the "typing." substring. Ergo, we
    #   necessarily detect generics with an explicit test instead.
    # * *ALL* PEP 484-compliant generics and PEP 544-compliant protocols are
    #   guaranteed by the "typing" module to subclass this ABC regardless of
    #   whether those generics originally did so explicitly. How? By type
    #   erasure, the gift that keeps on giving:
    #     >>> import typing as t
    #     >>> class MuhList(t.List): pass
    #     >>> MuhList.__orig_bases__
    #     (typing.List)
    #     >>> MuhList.__mro__
    #     (__main__.MuhList, list, typing.Generic, object)
    # * *NO* PEP 585-compliant generics subclass this ABC unless those generics
    #   are also either PEP 484- or 544-compliant. Indeed, PEP 585-compliant
    #   generics subclass *NO* common superclass.
    #
    # Ergo, this ABC uniquely identifies many but *NOT* all generics. Although
    # non-ideal, the failure of PEP 585-compliant generics to subclass a common
    # superclass leaves us with little alternative.
    elif is_hint_pep_generic(hint):
        return Generic
    # Else, this hint is *NOT* a generic.
    #
    # If this hint is a PEP 585-compliant type hint, return the origin type
    # originating this hint (e.g., "list" for "list[str]").
    #
    # Note that the get_hint_pep_stdlib_type() getter is intentionally *NOT*
    # called here. Why? Because doing so would induce an infinite recursive
    # loop, since that function internally calls this function. *sigh*
    elif is_hint_pep585_builtin(hint):
        return hint.__origin__
    # Else, this hint is *NOT* a PEP 585-compliant type hint.
    #
    # If this hint is...
    elif (
            # A class...
            isinstance(hint, type) and
            # *NOT* subscripted by one or more child hints or type variables...
            not (get_hint_pep_args(hint) or get_hint_pep_typevars(hint))
            # Then this class is a standard class. In this case...
            #
            # Note that this is principally useful under Python 3.6, which
            # idiosyncratically defines subscriptions of "typing" objects as classes:
            #     >>> import typing
            #     >>> isinstance(typing.List[int], type)
            #     True     # <-- this is balls cray-cray, too.
            #
            # While we *COULD* technically fence this conditional behind a Python 3.6
            # check, doing so would render this getter less robust against unwarranted
            # stdlib changes. Ergo, we preserve this as is for forward-proofing.
    ):
        # If this class is *NOT* explicitly allowed as a sign, raise an
        # exception.
        if hint not in HINT_PEP_SIGNS_TYPE:
            raise BeartypeDecorHintPepSignException(
                f'Unsubscripted non-generic class {repr(hint)} invalid as '
                f'sign (i.e., not in {repr(HINT_PEP_SIGNS_TYPE)}).')
        # Else, this class is explicitly allowed as a sign.

        # Return this class as is.
        return hint
    # Else, this hint is either not a class *OR* or class subscripted by one or
    # more child hints or type variables and is thus *NOT* a standard class. In
    # either case, continue (i.e., attempt to handle this class as a PEP
    # 484-compliant type hint defined by the "typing" module).
    #
    # If this hint is a forward reference, return the class of all PEP
    # 484-compliant (but *NOT* PEP 585-compliant) forward references.
    #
    # Note that PEP 484-compliant forward references *CANNOT* be detected by
    # the general-purpose logic performed below, as the ForwardRef.__repr__()
    # dunder method returns a standard representation rather than a string
    # prefixed by the module name "typing." -- unlike most "typing" objects:
    #     >>> import typing as t
    #     >>> repr(t.ForwardRef('str'))
    #     "ForwardRef('str')"
    elif is_hint_forwardref(hint):
        return HINT_PEP484_BASE_FORWARDREF
    # If this hint is a PEP 484-compliant new type, return the closure factory
    # function responsible for creating these types.
    #
    # Note that these types *CANNOT* be detected by the general-purpose logic
    # performed below, as the __repr__() dunder methods of the closures created
    # and returned by the NewType() closure factory function returns a
    # standard representation rather than a string prefixed by the module name
    # "typing." -- unlike most "typing" objects:
    #     >>> import typing as t
    #     >>> repr(t.NewType('FakeStr', str))
    #     '<function NewType.<locals>.new_type at 0x7fca39388050>'
    elif is_hint_pep484_newtype(hint):
        return NewType
    # If this hint is a type variable, return the class of all type variables.
    #
    # Note that type variables *CANNOT* be detected by the general-purpose
    # logic performed below, as the TypeVar.__repr__() dunder method insanely
    # returns a string prefixed by the non-human-readable character "~" rather
    # than the module name "typing." -- unlike most "typing" objects:
    #     >>> import typing as t
    #     >>> repr(t.TypeVar('T'))
    #     ~T
    #
    # Of course, that brazenly violates Pythonic standards. __repr__() is
    # generally assumed to return an evaluatable Python expression that, when
    # evaluated, instantiates an object equal to the original object. Instead,
    # this API was designed by incorrigible monkeys who profoundly hate the
    # Python language. This is why we can't have sane things.
    elif is_hint_pep_typevar(hint):
        return TypeVar
    #FIXME: Drop this like hot lead after dropping Python 3.6 support.
    # If the active Python interpreter targets Python 3.6 *AND* this hint is a
    # poorly designed Python 3.6-specific "type alias", this hint is a
    # subscription of either the "typing.Match" or "typing.Pattern" objects. In
    # this case, this hint declares a non-standard "name" instance variable
    # whose value is either the literal string "Match" or "Pattern". Return the
    # "typing" attribute with this name *OR* implicitly raise an
    # "AttributeError" exception if something goes horribly awry.
    #
    # Gods... this is horrible. Thanks for nuthin', Python 3.6.
    elif IS_PYTHON_3_6 and isinstance(
            hint, typing._TypeAlias):  # type: ignore[attr-defined]
        return getattr(typing, hint.name)
    # Else, this hint *MUST* be a standard PEP 484-compliant type hint defined
    # by the "typing" module.

    # Machine-readable string representation of this hint also serving as the
    # fully-qualified name of the public "typing" attribute uniquely associated
    # with this hint (e.g., "typing.Tuple[str, ...]").
    #
    # Although the "typing" module provides *NO* sane public API, it does
    # reliably implement the __repr__() dunder method across most objects and
    # types to return a string prefixed "typing." regardless of Python version.
    # Ergo, this string is effectively the *ONLY* sane means of deciding which
    # broad category of behaviour an arbitrary PEP 484-compliant type hint
    # conforms to.
    sign_name = repr(hint)

    # If this representation is *NOT* prefixed by "typing,", this hint does
    # *NOT* originate from the "typing" module and is thus *NOT* PEP-compliant.
    # But by the validation above, this hint is PEP-compliant. Since this
    # invokes a world-shattering paradox, raise an exception
    if not sign_name.startswith('typing.'):
        raise BeartypeDecorHintPepSignException(
            f'PEP 484 type hint {repr(hint)} '
            f'representation "{sign_name}" not prefixed by "typing.".')

    # Strip the now-harmful "typing." prefix from this representation.
    # Preserving this prefix would raise an "AttributeError" exception from
    # the subsequent call to the getattr() builtin.
    sign_name = sign_name[7:]  # hardcode us up the bomb

    # 0-based index of the first "[" delimiter in this representation if
    # any *OR* -1 otherwise.
    sign_name_bracket_index = sign_name.find('[')

    # If this representation contains such a delimiter, this is a subscripted
    # type hint. In this case, reduce this representation to its unsubscripted
    # form by truncating the suffixing parametrization from this representation
    # (e.g., from "typing.Union[str, typing.Sequence[int]]" to merely
    # "typing.Union").
    #
    # Note that this is the common case and thus explicitly tested first.
    if sign_name_bracket_index > 0:
        sign_name = sign_name[:sign_name_bracket_index]
    # Else, this representation contains no such delimiter and is thus already
    # unsubscripted. In this case, preserve this representation as is.

    # If this name erroneously refers to a non-existing "typing" attribute,
    # rewrite this name to refer to the actual existing "typing" attribute
    # corresponding to this sign (e.g., from the non-existing
    # "typing.AbstractContextManager" attribute to the existing
    # "typing.ContextManager" attribute).
    sign_name = _HINT_PEP_TYPING_NAME_BAD_TO_GOOD.get(sign_name, sign_name)

    # "typing" attribute with this name if any *OR* "None" otherwise.
    sign = getattr(typing, sign_name, None)

    # If this "typing" attribute does *NOT* exist...
    if sign is None:
        raise BeartypeDecorHintPepSignException(
            f'PEP 484 type hint {repr(hint)} '
            f'attribute "typing.{sign_name}" not found.')
    # Else, this "typing" attribute exists.

    # Return this "typing" attribute.
    return sign