def test_get_hint_pep_sign_fail() -> None: ''' Test unsuccessful usage of the :func:`beartype._util.hint.pep.utilhintpepget.get_hint_pep_sign` getter. ''' # Defer heavyweight imports. from beartype.roar import ( BeartypeDecorHintPepException, BeartypeDecorHintPepSignException, ) from beartype._util.hint.pep.utilhintpepget import get_hint_pep_sign from beartype_test.unit.data.hint.data_hint import (NOT_HINTS_PEP, NonPepCustomFakeTyping) # Assert this getter raises the expected exception for an instance of a # class erroneously masquerading as a "typing" class. with raises(BeartypeDecorHintPepSignException): # Localize this return value to simplify debugging. hint_pep_sign = get_hint_pep_sign(NonPepCustomFakeTyping()) # Assert this getter raises the expected exception for non-"typing" hints. for not_hint_pep in NOT_HINTS_PEP: with raises(BeartypeDecorHintPepException): # Localize this return value to simplify debugging. hint_pep_sign = get_hint_pep_sign(not_hint_pep)
def test_get_hint_pep_sign_pass() -> None: ''' Test successful usage of the :func:`beartype._util.hint.pep.utilhintpepget.get_hint_pep_sign` getter. ''' # Defer heavyweight imports. from beartype._util.hint.pep.utilhintpepget import get_hint_pep_sign from beartype_test.unit.data.hint.pep.data_hintpep import HINTS_PEP_META # Assert this getter returns the expected unsubscripted "typing" attribute # for all PEP-compliant type hints associated with such an attribute. for hint_pep_meta in HINTS_PEP_META: assert get_hint_pep_sign( hint_pep_meta.hint) == (hint_pep_meta.pep_sign)
def is_hint_pep_supported(hint: object) -> bool: ''' ``True`` only if the passed object is a **PEP-compliant supported type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs currently supported by the :func:`beartype.beartype` decorator). This tester is memoized for efficiency. Caveats ---------- **This tester only shallowly inspects this object.** If this object is a subscripted PEP-compliant type hint (e.g., ``Union[str, List[int]]``), this tester ignores all subscripted arguments (e.g., ``List[int]``) on this hint and may thus return false positives for hints that are directly supported but whose subscripted arguments are not. To deeply inspect this object, iteratively call this tester during a recursive traversal over each subscripted argument of this object. Parameters ---------- hint : object Object to be inspected. Returns ---------- bool ``True`` only if this object is a supported PEP-compliant type hint. ''' # If this hint is *NOT* PEP-compliant, immediately return false. if not is_hint_pep(hint): return False # Else, this hint is PEP-compliant. # Avoid circular import dependencies. from beartype._util.hint.pep.utilhintpepget import get_hint_pep_sign # Sign uniquely identifying this hint. hint_pep_sign = get_hint_pep_sign(hint) # Return true only if this sign is supported. return is_hint_pep_sign_supported(hint_pep_sign)
def __init__( self, func: object, pith: object, hint: object, cause_indent: str, exception_label: str, ) -> None: ''' Initialize this object. ''' assert callable(func), f'{repr(func)} not callable.' assert isinstance(cause_indent, str), (f'{repr(cause_indent)} not string.') assert isinstance(exception_label, str), (f'{repr(exception_label)} not string.') # Classify all passed parameters. self.func = func self.pith = pith self.hint = hint self.cause_indent = cause_indent self.exception_label = exception_label # Nullify all remaining parameters for safety. self.hint_sign = None self.hint_childs = None # ................{ REDUCTION }................ #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAVEATS: Synchronize changes here with the corresponding block of the # beartype._decor._code._pep._pephint.pep_code_check_hint() function. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # This logic reduces the currently visited hint to an arbitrary object # associated with this hint when this hint conditionally satisfies any # of various conditions. # # ................{ REDUCTION ~ pep 484 }................ # If this is a PEP 484-compliant new type hint, reduce this hint to the # user-defined class aliased by this hint. Although this logic could # also be performed below, doing so here simplifies matters. if is_hint_pep484_newtype(self.hint): self.hint = get_hint_pep484_newtype_class(self.hint) # ................{ REDUCTION ~ pep 544 }................ # If this is a PEP 484-compliant IO generic base class *AND* the active # Python interpreter targets at least Python >= 3.8 and thus supports # PEP 544-compliant protocols, reduce this functionally useless hint to # the corresponding functionally useful beartype-specific PEP # 544-compliant protocol implementing this hint. elif is_hint_pep544_io_generic(self.hint): self.hint = get_hint_pep544_io_protocol_from_generic(self.hint) # ................{ REDUCTION ~ pep 593 }................ # If this is a PEP 593-compliant type metahint, ignore all annotations # on this hint (i.e., "hint_curr.__metadata__" tuple) by reducing this # hint to its origin (e.g., "str" in "Annotated[str, 50, False]"). elif is_hint_pep593(self.hint): self.hint = get_hint_pep593_hint(self.hint) # ................{ REDUCTION ~ end }................ # If this hint is PEP-compliant... if is_hint_pep(self.hint): # Arbitrary object uniquely identifying this hint. self.hint_sign = get_hint_pep_sign(self.hint) # Tuple of either... self.hint_childs = ( # If this hint is a generic, the one or more unerased # pseudo-superclasses originally subclassed by this hint. get_hint_pep_generic_bases_unerased(self.hint) if is_hint_pep_generic(self.hint) else # Else, the zero or more arguments subscripting this hint. get_hint_pep_args(self.hint))
def hint(self, hint: Any) -> None: ''' Set the type hint to validate this object against. ''' # ................{ REDUCTION }................ #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAVEATS: Synchronize changes here with the corresponding block of the # beartype._decor._code._pep._pephint.pep_code_check_hint() function. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # This logic reduces the currently visited hint to an arbitrary object # associated with this hint when this hint conditionally satisfies any # of various conditions. # # ................{ REDUCTION ~ pep 484 }................ # If this is the PEP 484-compliant "None" singleton, reduce this hint # to the type of that singleton. While not explicitly defined by the # "typing" module, PEP 484 explicitly supports this singleton: # When used in a type hint, the expression None is considered # equivalent to type(None). if hint is None: hint = NoneType # If this is a PEP 484-compliant new type hint, reduce this hint to the # user-defined class aliased by this hint. Although this logic could # also be performed below, doing so here simplifies matters. elif is_hint_pep484_newtype(hint): hint = get_hint_pep484_newtype_class(hint) # ................{ REDUCTION ~ pep 544 }................ # If this is a PEP 484-compliant IO generic base class *AND* the active # Python interpreter targets at least Python >= 3.8 and thus supports # PEP 544-compliant protocols, reduce this functionally useless hint to # the corresponding functionally useful beartype-specific PEP # 544-compliant protocol implementing this hint. # # Note that PEP 484-compliant IO generic base classes are technically # usable under Python < 3.8 (e.g., by explicitly subclassing those # classes from third-party classes). Ergo, we can neither safely emit # warnings nor raise exceptions on visiting these classes under *ANY* # Python version. elif is_hint_pep544_io_generic(hint): hint = get_hint_pep544_io_protocol_from_generic(hint) # ................{ REDUCTION ~ pep 593 }................ # If this is a PEP 593-compliant type metahint, ignore all annotations # on this hint (i.e., "hint_curr.__metadata__" tuple) by reducing this # hint to its origin (e.g., "str" in "Annotated[str, 50, False]"). elif is_hint_pep593(hint): hint = get_hint_pep593_hint(hint) # ................{ REDUCTION ~ end }................ # If this hint is PEP-compliant... if is_hint_pep(hint): # Arbitrary object uniquely identifying this hint. self.hint_sign = get_hint_pep_sign(hint) # Tuple of either... self.hint_childs = ( # If this hint is a generic, the one or more unerased # pseudo-superclasses originally subclassed by this hint. get_hint_pep_generic_bases_unerased(hint) if is_hint_pep_generic(hint) else # Else, the zero or more arguments subscripting this hint. get_hint_pep_args(hint)) # Classify this hint *AFTER* all other assignments above. self._hint = hint
def is_hint_pep_ignorable(hint: object) -> bool: ''' ``True`` only if the passed object is a **deeply ignorable PEP-compliant type hint** (i.e., PEP-compliant type hint shown to be ignorable only after recursively inspecting the contents of this hint). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Object to be inspected. Returns ---------- bool ``True`` only if this object is a deeply ignorable PEP-compliant type hint. Warnings ---------- BeartypeDecorHintPepIgnorableDeepWarning If this object is a deeply ignorable PEP-compliant type hint. Why? Because deeply ignorable PEP-compliant type hints convey *no* meaningful semantics but superficially appear to do so. Consider ``Union[str, List[int], NewType('MetaType', Annotated[object, 53])]``, for example; this PEP-compliant type hint effectively reduces to ``typing.Any`` and thus conveys *no* meaningful semantics despite superficially appearing to do so. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilhintpepget import get_hint_pep_sign #FIXME: Remove this *AFTER* properly supporting type variables. For #now, ignoring type variables is required ta at least shallowly support #generics parametrized by one or more type variables. # If this hint is a type variable, return true. Type variables require # non-trivial and currently unimplemented decorator support. if is_hint_pep_typevar(hint): return True # Sign uniquely identifying this hint. hint_sign = get_hint_pep_sign(hint) # For each PEP-specific function testing whether this hint is an ignorable # type hint fully compliant with that PEP... for is_hint_pep_ignorable_tester in _IS_HINT_PEP_IGNORABLE_TESTERS: # True only if this hint is a ignorable under this PEP, False only if # this hint is unignorable under this PEP, and None if this hint is # *NOT* compliant with this PEP. is_hint_pep_ignorable_or_none = is_hint_pep_ignorable_tester( hint, hint_sign) # If this hint is compliant with this PEP... # print(f'{is_hint_pep_ignorable_or_none} = {is_hint_pep_ignorable_tester}({hint}, {hint_sign})') if is_hint_pep_ignorable_or_none is not None: #FIXME: Uncomment *AFTER* we properly support type variables. Since #we currently ignore type variables, uncommenting this now would #raise spurious warnings for otherwise unignorable and absolutely #unsuspicious generics and protocols parametrized by type #variables, which would be worse than the existing situation. # # If this hint is ignorable under this PEP, warn the user this hint # # is deeply ignorable. (See the docstring for justification.) # if is_hint_pep_ignorable_or_none: # warn( # ( # f'Ignorable PEP type hint {repr(hint)} ' # f'typically not intended to be ignored.' # ), # BeartypeDecorHintPepIgnorableDeepWarning, # ) # Return this boolean. return is_hint_pep_ignorable_or_none # Else, this hint is *NOT* compliant with this PEP. In this case, # silently continue to the next such tester. # Else, this hint is *NOT* deeply ignorable. In this case, return false. return False