Exemple #1
0
def test_typistry_singleton_pass() -> None:
    '''
    Test successful usage of the
    :attr:`beartype._decor._typistry.bear_typistry` singleton.
    '''

    # Defer heavyweight imports.
    from beartype.roar import _BeartypeDecorBeartypistryException
    from beartype._decor._typistry import bear_typistry
    from beartype._util.utilobject import get_object_classname

    # Assert that dictionary syntax also implicitly registers a type. Since
    # this approach explicitly prohibits re-registration for safety, we define
    # a custom user-defined type guaranteed *NOT* to have been registered yet.
    class TestTypistrySingletonPassType(object):
        pass

    hint = TestTypistrySingletonPassType
    hint_name = get_object_classname(hint)
    bear_typistry[hint_name] = hint
    assert bear_typistry.get(hint_name) is hint

    # Assert that the same type is *NOT* re-registrable via dictionary syntax.
    with raises(_BeartypeDecorBeartypistryException):
        bear_typistry[hint_name] = hint
Exemple #2
0
def test_is_classname_builtin() -> None:
    '''
    Test the :func:`beartype._util.utilclass.is_classname_builtin` function.
    '''

    # Defer heavyweight imports.
    from beartype._util.utilclass import is_classname_builtin
    from beartype._util.utilobject import get_object_classname
    from beartype_test.unit.data.data_class import (CLASSES_BUILTIN,
                                                    CLASSES_NON_BUILTIN)

    # Assert this tester accepts the fully-qualified names of all builtin
    # types.
    for class_builtin in CLASSES_BUILTIN:
        assert is_classname_builtin(
            get_object_classname(class_builtin)) is True

    # Assert this tester rejects non-builtin types.
    for class_non_builtin in CLASSES_NON_BUILTIN:
        assert is_classname_builtin(
            get_object_classname(class_non_builtin)) is False
Exemple #3
0
def ignore_warnings(warning_cls: type) -> 'Callable':
    '''
    Decorate the passed test to ignore all warnings subclassing the passed
    :class:`Warning` class.

    Caveats
    ----------
    **This high-level decorator should always be called in lieu of the
    low-level** :func:`pytest.mark.filterwarnings` **decorator,** whose syntax
    is fragile, poorly documented, and likely to silently fail.

    Parameters
    ----------
    warning_cls : type
        :class:`Warning` class to be ignored when running the decorated test.

    Returns
    ----------
    Callable
        This test decorated to ignore all warnings of this class.

    Raises
    ----------
    BeartypeTestMarkException
        If this object is either:

        * *Not* a class.
        * A class that is either *not* the builtin :class:`Warning` class or a
          subclass of that class.
    '''

    # Defer heavyweight imports.
    from beartype._util.utilobject import get_object_classname

    # If this object is *NOT* a class, raise an exception.
    if not isinstance(warning_cls, type):
        raise BeartypeTestMarkException(f'{repr(warning_cls)} not type.')
    # Else, this object is a class.
    #
    # If this class is *NOT* a "Warning" subclass, raise an exception.
    if not issubclass(warning_cls, Warning):
        raise BeartypeTestMarkException(
            f'{repr(warning_cls)} not {repr(Warning)} subclass.')
    # Else, this class is a "Warning" subclass.

    # Fully-qualified name of this class.
    warning_classname = get_object_classname(warning_cls)

    # Return the low-level pytest mark decorator ignoring all warnings of this
    # "Warning" subclass with a filter adhering to Python's peculiar warning
    # filter syntax. See also:
    #     https://docs.python.org/3/library/warnings.html#describing-warning-filters
    return pytest.mark.filterwarnings(f'ignore::{warning_classname}')
Exemple #4
0
    def __setitem__(self, hint_name: str, hint: object) -> None:
        '''
        Dunder method explicitly called by the superclass on setting the passed
        key-value pair with``[``- and ``]``-delimited syntax, mapping the
        passed string uniquely identifying the passed PEP-noncompliant type
        hint to that hint.

        Parameters
        ----------
        hint_name: str : str
            String uniquely identifying this hint in a manner dependent on the
            type of this hint. Specifically, if this hint is:

            * A non-:mod:`typing` type, this is the fully-qualified classname
              of the module attribute defining this type.
            * A tuple of non-:mod:`typing` types, this is a string:

              * Prefixed by the :data:`_TYPISTRY_HINT_NAME_TUPLE_PREFIX`
                substring distinguishing this string from fully-qualified
                classnames.
              * Hash of these types (ignoring duplicate types and type order in
                this tuple).

        Raises
        ----------
        TypeError
            If this hint is **unhashable** (i.e., *not* hashable by the builtin
            :func:`hash` function and thus unusable in hash-based containers
            like dictionaries and sets). All supported type hints are hashable.
        _BeartypeDecorBeartypistryException
            If either:

            * This name either:

              * *Not* a string.
              * Is an existing string key of this dictionary, implying this
                name has already been registered, implying a key collision
                between the type or tuple already registered under this key and
                this passed type or tuple to be reregistered under this key.
                Since the high-level :func:`register_typistry_type` and
                :func:`register_typistry_tuple` functions implicitly calling
                this low-level dunder method are memoized *and* since the
                latter function explicitly avoids key collisions by detecting
                and uniquifying colliding keys, every call to this method
                should be passed a unique key.

            * This hint is either:

              * A type but either:

                * This name is *not* the fully-qualified classname of this
                  type.
                * This type is **PEP-compliant** (i.e., either a class defined
                  by the :mod:`typing` module *or* subclass of such a class and
                  thus a PEP-compliant type hint, which all violate standard
                  type semantics and thus require PEP-specific handling).

              * A tuple but either:

                * This name is *not* prefixed by the magic substring
                  :data:`_TYPISTRY_HINT_NAME_TUPLE_PREFIX`.
                * This tuple contains one or more items that are either:

                  * *Not* types.
                  * PEP-compliant types.
        '''

        # If this name is *NOT* a string, raise an exception.
        if not isinstance(hint_name, str):
            raise _BeartypeDecorBeartypistryException(
                f'Beartypistry key {repr(hint_name)} not string.')
        # Else, this name is a string.
        #
        # If this name is an existing key of this dictionary, this name has
        # already been registered, implying a key collision between the type or
        # tuple already registered under this key and the passed type or
        # tuple to be reregistered under this key. In this case, raise an
        # exception.
        elif hint_name in self:
            raise _BeartypeDecorBeartypistryException(
                f'Beartypistry key "{hint_name}" already registered '
                f'(i.e., key collision between '
                f'prior registered value {repr(self[hint_name])} and '
                f'newly registered value {repr(hint)}).')
        # Else, this name is *NOT* an existing key of this dictionary.
        #
        # If this hint is a class...
        #
        # Note that although *MOST* classes are PEP-noncompliant (e.g., the
        # builtin "str" type), some classes are PEP-compliant (e.g., the
        # stdlib "typing.SupportsInt" protocol). Since both PEP-noncompliant
        # and -compliant classes are shallowly type-checkable via the
        # isinnstance() builtin, there exists no demonstrable benefit to
        # distinguishing between either here.
        elif isinstance(hint, type):
            # Fully-qualified classname of this type as declared by this type.
            hint_clsname = get_object_classname(hint)

            # If...
            if (
                # The passed name is not this classname *AND*...
                hint_name != hint_clsname and
                # This classname does not imply this type to be a builtin...
                #
                # Note that builtin types are registered under their
                # unqualified basenames (e.g., "list" rather than
                # "builtins.list") for runtime efficiency, a core optimization
                # requiring manual whitelisting here.
                not is_classname_builtin(hint_clsname)
            # Then raise an exception.
            ):
                raise _BeartypeDecorBeartypistryException(
                    f'Beartypistry key "{hint_name}" not '
                    f'fully-qualified classname "{hint_clsname}" of '
                    f'type {repr(hint)}.'
                )
        # Else, this hint is *NOT* a class.
        #
        # If this hint is a tuple...
        elif isinstance(hint, tuple):
            # If this tuple is *NOT* PEP-noncompliant (e.g., due to containing
            # PEP-compliant type hints), raise an exception.
            die_unless_hint_nonpep(
                hint=hint,
                hint_label='Beartypistry value',

                #FIXME: Actually, we eventually want to permit this to enable
                #trivial resolution of forward references. For now, this is fine.
                is_str_valid=False,

                # Raise a decoration- rather than call-specific exception, as
                # this setter should *ONLY* be called at decoration time (e.g.,
                # by registration functions defined above).
                exception_cls=_BeartypeDecorBeartypistryException,
            )

            # If this tuple's name is *NOT* prefixed by a magic substring
            # uniquely identifying this hint as a tuple, raise an exception.
            #
            # Ideally, this block would strictly validate this name to be the
            # concatenation of this prefix followed by this tuple's hash.
            # Sadly, Python fails to cache tuple hashes (for largely spurious
            # reasons, like usual):
            #     https://bugs.python.org/issue9685
            #
            # Potentially introducing a performance bottleneck for mostly
            # redundant validation is a bad premise, given that we mostly
            # trust callers to call the higher-level
            # :func:`register_typistry_tuple` function instead, which already
            # guarantees this constraint to be the case.
            if not hint_name.startswith(_TYPISTRY_HINT_NAME_TUPLE_PREFIX):
                raise _BeartypeDecorBeartypistryException(
                    f'Beartypistry key "{hint_name}" not '
                    f'prefixed by "{_TYPISTRY_HINT_NAME_TUPLE_PREFIX}" for '
                    f'tuple {repr(hint)}.'
                )
        # Else, this hint is neither a class nor a tuple. In this case,
        # something has gone terribly awry. Pour out an exception.
        else:
            raise _BeartypeDecorBeartypistryException(
                f'Beartypistry key "{hint_name}" value {repr(hint)} invalid '
                f'(i.e., neither type nor tuple).'
            )

        # Cache this object under this name.
        super().__setitem__(hint_name, hint)
Exemple #5
0
def register_typistry_type(hint: type) -> str:
    '''
    Register the passed **PEP-noncompliant type** (i.e., class neither defined
    by the :mod:`typing` module *nor* subclassing such a class) with the
    beartypistry singleton *and* return a Python expression evaluating to this
    type when accessed via the private ``__beartypistry`` parameter implicitly
    passed to all wrapper functions generated by the :func:`beartype.beartype`
    decorator.

    This function is syntactic sugar improving consistency throughout the
    codebase, but is otherwise roughly equivalent to:

        >>> from beartype._decor._typistry import bear_typistry
        >>> from beartype._util.utilobject import get_object_classname
        >>> bear_typistry[get_object_classname(hint)] = hint

    This function is memoized for both efficiency *and* safety, preventing
    accidental reregistration.

    Parameters
    ----------
    hint : type
        PEP-noncompliant type to be registered.

    Returns
    ----------
    str
        Python expression evaluating to this type when accessed via the private
        ``__beartypistry`` parameter implicitly passed to all wrapper functions
        generated by the :func:`beartype.beartype` decorator.

    Raises
    ----------
    _BeartypeDecorBeartypistryException
        If this object is either:

        * *Not* a type.
        * **PEP-compliant** (i.e., either a class defined by the :mod:`typing`
          module *or* subclass of such a class and thus a PEP-compliant type
          hint, which all violate standard type semantics and thus require
          PEP-specific handling).
    '''

    # If this object is *NOT* a type, raise an exception.
    die_unless_class(hint)
    # Else, this object is a type.
    #
    # Note that we defer all further validation of this type to the
    # Beartypistry.__setitem__() method implicitly invoked on subsequently
    # assigning this type as a "bear_typistry" key.

    # If this type is a builtin (i.e., globally accessible C-based type
    # requiring *no* explicit importation), this type requires no registration.
    # In this case, return the unqualified basename of this type as is.
    if is_class_builtin(hint):
        return get_object_class_basename(hint)
    # Else, this type is *NOT* a builtin and thus requires registration.
    # assert hint_basename != 'NoneType'

    # Fully-qualified name of this type.
    hint_classname = get_object_classname(hint)

    # If this type has yet to be registered with the beartypistry singleton, do
    # so.
    #
    # Note that the beartypistry singleton's __setitem__() dunder method
    # intentionally raises exceptions on attempts to re-register the same
    # object twice, as tuple re-registration requires special handling to avoid
    # hash collisions. Nonetheless, this is a non-issue. Why? Since this
    # function is memoized, re-registration should *NEVER* happen.
    bear_typistry[hint_classname] = hint

    # Return a Python expression evaluating to this type.
    return (
        f'{_CODE_TYPISTRY_HINT_NAME_TO_HINT_PREFIX}{repr(hint_classname)}'
        f'{_CODE_TYPISTRY_HINT_NAME_TO_HINT_SUFFIX}'
    )
Exemple #6
0
def label_class(cls: type) -> str:
    '''
    Human-readable label describing the passed class.

    Parameters
    ----------
    cls : type
        Class to be labelled.

    Returns
    ----------
    str
        Human-readable label describing this class.
    '''
    assert isinstance(cls, type), f'{repr(cls)} not class.'

    # Avoid circular import dependencies.
    from beartype._util.hint.pep.proposal.utilhintpep544 import (
        is_hint_pep544_protocol)

    # Label to be returned, initialized to this class' fully-qualified name.
    classname = get_object_classname(cls)

    # If this name contains *NO* periods, this class is actually a builtin type
    # (e.g., "list"). Since builtin types are well-known and thus
    # self-explanatory, this name requires no additional labelling. In this
    # case, return this name as is.
    if '.' not in classname:
        pass
    # If this name is that of a builtin type uselessly prefixed by the name of
    # the module declaring all builtin types (e.g., "builtins.list"), reduce
    # this name to the unqualified basename of this type (e.g., "list").
    elif is_classname_builtin(classname):
        classname = cls.__name__
    # Else, this is a non-builtin class. Non-builtin classes are *NOT*
    # well-known and thus benefit from additional labelling.
    #
    # If this class is a PEP 544-compliant protocol supporting structural
    # subtyping, label this protocol.
    elif is_hint_pep544_protocol(cls):
        classname = f'<protocol "{classname}">'
    # Else if this class is a standard abstract base class (ABC) defined by a
    # stdlib submodule also known to support structural subtyping (e.g.,
    # "collections.abc.Hashable", "contextlib.AbstractContextManager"),
    # label this ABC as a protocol.
    #
    # Note that user-defined ABCs do *NOT* generally support structural
    # subtyping. Doing so requires esoteric knowledge of undocumented and
    # mostly private "abc.ABCMeta" metaclass internals unlikely to be
    # implemented by third-party developers. Thanks to the lack of both
    # publicity and standardization, there exists *NO* general-purpose means of
    # detecting whether an arbitrary class supports structural subtyping.
    elif (classname.startswith('collections.abc.')
          or classname.startswith('contextlib.')):
        classname = f'<protocol ABC "{classname}">'
    # Else, this is a standard class. In this case, label this class as such.
    else:
        classname = f'<class "{classname}">'

    # Return this labelled classname.
    return classname