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
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
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}')
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)
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}' )
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