Exemplo n.º 1
0
    def is_hint_pep484_generic(hint: object) -> bool:

        # Avoid circular import dependencies.
        from beartype._util.hint.pep.utilhintpepget import (
            get_hint_pep_generic_type_or_none)

        # If this hint is *NOT* a class, this hint is *NOT* an unsubscripted
        # generic but could still be a subscripted generic (i.e., generic
        # subscripted by one or more PEP-compliant child type hints). To
        # decide, reduce this hint to the object originating this hint if any,
        # enabling the subsequent test to test whether this origin object is an
        # unsubscripted generic, which would then imply this hint to be a
        # subscripted generic. If this strikes you as insane, you're not alone.
        hint = get_hint_pep_generic_type_or_none(hint)

        # Return true only if this hint is a subclass of the "typing.Generic"
        # abstract base class (ABC), in which case this hint is a user-defined
        # generic.
        #
        # Note that this test is robust against edge case, as the "typing"
        # module guarantees all user-defined classes subclassing one or more
        # "typing" pseudo-superclasses to subclass the "typing.Generic"
        # abstract base class (ABC) regardless of whether those classes
        # originally did so explicitly. How? By type erasure, of course, the
        # malicious 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)
        #
        # Note that this issubclass() call implicitly performs a surprisingly
        # inefficient search over the method resolution order (MRO) of all
        # superclasses of this hint. In theory, the cost of this search might
        # be circumventable by observing that this ABC is expected to reside at
        # the second-to-last index of the tuple exposing this MRO far all
        # generics by virtue of fragile implementation details violating
        # privacy encapsulation. In practice, this codebase is fragile enough.
        #
        # Note lastly that the following logic superficially appears to
        # implement the same test *WITHOUT* the onerous cost of a search:
        #     return len(get_hint_pep484_generic_bases_unerased_or_none(hint)) > 0
        #
        # Why didn't we opt for that, then? Because this tester is routinely
        # passed objects that *CANNOT* be guaranteed to be PEP-compliant.
        # Indeed, the high-level is_hint_pep() tester establishing the
        # PEP-compliance of arbitrary objects internally calls this lower-level
        # tester to do so. Since the
        # get_hint_pep484_generic_bases_unerased_or_none() getter internally
        # reduces to returning the tuple of the general-purpose
        # "__orig_bases__" dunder attribute formalized by PEP 560, testing
        # whether that tuple is non-empty or not in no way guarantees this
        # object to be a PEP-compliant generic.
        return is_object_subclass(hint, Generic)  # type: ignore[arg-type]
Exemplo n.º 2
0
    def is_hint_pep544_protocol(hint: object) -> bool:

        # Return true only if this hint is...
        return (
            # A PEP 544-compliant protocol *AND*...
            is_object_subclass(hint, Protocol) and  # type: ignore[arg-type]
            # *NOT* a builtin type. For unknown reasons, some but *NOT* all
            # builtin types erroneously present themselves to be PEP
            # 544-compliant protocols under Python >= 3.8: e.g.,
            #     >>> from typing import Protocol
            #     >>> isinstance(str, Protocol)
            #     False        # <--- this makes sense
            #     >>> isinstance(int, Protocol)
            #     True         # <--- this makes no sense whatsoever
            #
            # Since builtin types are obviously *NOT* PEP 544-compliant
            # protocols, explicitly exclude all such types. Why, Guido? Why?
            not (isinstance(hint, type) and is_type_builtin(hint)))
Exemplo n.º 3
0
def test_get_hint_pep544_io_protocol_from_generic() -> None:
    '''
    Test the
    :func:`beartype._util.hint.pep.proposal.utilhintpep544.get_hint_pep544_io_protocol_from_generic`
    tester.
    '''

    # Defer heavyweight imports.
    from beartype.roar import BeartypeDecorHintPep544Exception
    from beartype._util.hint.pep.proposal.utilhintpep544 import (
        get_hint_pep544_io_protocol_from_generic)
    from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_8
    from beartype._util.utilobject import is_object_subclass
    from typing import BinaryIO, IO, TextIO, Union

    # Set of all PEP 484-compliant "typing" IO generic base classes.
    TYPING_IO_GENERICS = {BinaryIO, IO, TextIO}

    for typing_io_generic in TYPING_IO_GENERICS:
        # If the active Python interpreter targets at least Python >= 3.8 and
        # thus supports PEP 544...
        if IS_PYTHON_AT_LEAST_3_8:
            # Defer version-dependent imports.
            from typing import Protocol

            # Beartype-specific PEP 544-compliant protocol implementing this
            # PEP 484-compliant "typing" IO generic base class.
            io_protocol = get_hint_pep544_io_protocol_from_generic(
                typing_io_generic)

            # Assert this function returns a protocol.
            assert is_object_subclass(io_protocol, Protocol)
        # Else, assert this function raises an exception.
        else:
            with raises(BeartypeDecorHintPep544Exception):
                get_hint_pep544_io_protocol_from_generic(typing_io_generic)

    # Assert this function rejects standard type hints in either case.
    with raises(BeartypeDecorHintPep544Exception):
        get_hint_pep544_io_protocol_from_generic(Union[int, str])