def test_decor_noop_unhinted() -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on **unannotated callables** (i.e., callables with *no* annotations). ''' # Defer heavyweight imports. from beartype import beartype # Undecorated unannotated function. def khorne(gork, mork): return gork + mork # Decorated unannotated function. khorne_typed = beartype(khorne) # Assert that @beartype efficiently reduces to a noop (i.e., the identity # decorator) when decorating this function. assert khorne_typed is khorne # Call this function and assert the expected return value. assert khorne_typed('WAAAGH!', '!HGAAAW') == 'WAAAGH!!HGAAAW'
def test_pep484_decor_no_type_check() -> None: ''' Test the :func:`beartype.beartype` decorator against all edge cases of the `PEP 484`_-compliant :attr:`typing.no_type_check` decorator. .. _PEP 484: https://www.python.org/dev/peps/pep-0484 ''' # Defer heavyweight imports. from beartype import beartype # Callable decorated by @typing.no_type_check whose otherwise PEP-compliant # type hints *SHOULD* be subsequently ignored by @beartype. @no_type_check def of_beechen_green(and_shadows_numberless: Union[int, str]) -> str: return and_shadows_numberless # The same callable additionally decorated by @beartype. of_beechen_green_beartyped = beartype(of_beechen_green) # Assert these two callables to be the same, implying @beartype silently # reduced to a noop by returning this callable undecorated. assert of_beechen_green is of_beechen_green_beartyped
def test_codemain() -> None: ''' Test the :func:`beartype.beartype` decorator with respect to type-checking code dynamically generated by the :mod:`beartype._decor._code.codemain` submodule. This unit test effectively acts as a functional test and is thus the core test exercising decorator functionality from the end user perspective -- the only perspective that matters in the end. Unsurprisingly, this test is mildly more involved than most. *Whatevah.* This test additionally attempts to avoid similar issues to a `prior issue <issue #5_>`__ of this decorator induced by repeated :func:`beartype.beartype` decorations of different callables annotated by one or more of the same PEP-compliant type hints. .. _issue #5: https://github.com/beartype/beartype/issues/5 ''' # Defer heavyweight imports. from beartype import beartype from beartype.roar import ( BeartypeCallHintPepException, # BeartypeDecorHintPepDeprecatedWarning, ) from beartype._util.utilobject import is_object_context_manager from beartype._util.hint.data.pep.utilhintdatapep import ( HINT_PEP_SIGNS_DEPRECATED) from beartype_test.a00_unit.data.hint.data_hintmeta import ( PepHintMetadata, PepHintPithSatisfiedMetadata, PepHintPithUnsatisfiedMetadata, ) from beartype_test.a00_unit.data.hint.nonpep.data_hintnonpep import ( HINTS_NONPEP_META) from beartype_test.a00_unit.data.hint.pep.data_hintpep import HINTS_PEP_META # Tuple of all PEP-compliant type hint metadata to be tested -- regardless # of whether those hints are uniquely identifiable by a sign or not. HINTS_META = HINTS_PEP_META + HINTS_NONPEP_META # Tuple of two arbitrary values used to trivially iterate twice below. RANGE_2 = (None, None) # For each predefined PEP-compliant type hint and associated metadata... for hint_meta in HINTS_META: # If this hint is currently unsupported, continue to the next. if not hint_meta.is_supported: continue # Else, this hint is currently supported. # Repeat the following logic twice. Why? To exercise memoization across # repeated @beartype decorations on different callables annotated by # the same PEP hints. for _ in RANGE_2: # Undecorated callable both accepting a single parameter and # returning a value annotated by this hint whose implementation # trivially reduces to the identity function. def func_untyped(hint_param: hint_meta.hint) -> hint_meta.hint: return hint_param # Decorated callable declared below. func_typed = None # If this is a deprecated PEP-compliant type hint, declare this # decorated callable under a context manager asserting this # declaration to emit non-fatal deprecation warnings. if ( isinstance(hint_meta, PepHintMetadata) and hint_meta.pep_sign in HINT_PEP_SIGNS_DEPRECATED ): #FIXME: For unknown and probably uninteresting reasons, the #pytest.warns() context manager appears to be broken on our #local machine. We have no recourse but to unconditionally #ignore this warning at the module level. So much rage! #FIXME: It's likely this has something to do with the fact that #Python filters deprecation warnings by default. This is almost #certainly a pytest issue. Since this has become fairly #unctuous, we should probably submit a pytest issue report. # with pytest.warns(BeartypeDecorHintPepDeprecatedWarning): # func_typed = beartype(func_untyped) func_typed = beartype(func_untyped) # Else, this is *NOT* a deprecated PEP-compliant type hint. In this # case, declare this decorated callable as is. else: func_typed = beartype(func_untyped) # For each pith satisfying this hint... for pith_satisfied_meta in hint_meta.piths_satisfied_meta: # Assert this metadata is an instance of the desired dataclass. assert isinstance( pith_satisfied_meta, PepHintPithSatisfiedMetadata) # print('PEP-testing {!r} against {!r}...'.format(hint_pep, pith_satisfied)) # Pith to be type-checked against this hint, defined as... pith = ( # If this pith is actually a pith factory (i.e., callable # accepting *NO* parameters and dynamically creating and # returning the value to be used as the desired pith), call # this factory and localize its return value. pith_satisfied_meta.pith() if pith_satisfied_meta.is_pith_factory else # Else, localize this pith as is. pith_satisfied_meta.pith ) # If... if ( # This pith is a context manager *AND*... is_object_context_manager(pith) and # This pith should be safely opened and closed as a # context rather than preserved as a context manager... not pith_satisfied_meta.is_context_manager # Then with this pith safely opened and closed as a context... ): with pith as pith_context: # Assert this wrapper function successfully type-checks # this context against this hint *WITHOUT* modifying # this context. assert func_typed(pith_context) is pith_context # Else, this object is *NOT* a context manager and thus safely # passable and returnable as is. else: # Assert this wrapper function successfully type-checks # this object against this hint *WITHOUT* modifying this # object. assert func_typed(pith) is pith # For each pith *NOT* satisfying this hint... for pith_unsatisfied_meta in hint_meta.piths_unsatisfied_meta: # Assert this metadata is an instance of the desired dataclass. assert isinstance( pith_unsatisfied_meta, PepHintPithUnsatisfiedMetadata) # Assert that iterables of uncompiled regular expression # expected to match and *NOT* match this message are *NOT* # strings, as commonly occurs when accidentally omitting a # trailing comma in tuples containing only one string: e.g., # * "('This is a tuple, yo.',)" is a 1-tuple containing one # string. # * "('This is a string, bro.')" is a string *NOT* contained in # a 1-tuple. assert not isinstance( pith_unsatisfied_meta.exception_str_match_regexes, str) assert not isinstance( pith_unsatisfied_meta.exception_str_not_match_regexes, str) # Assert this wrapper function raises the expected exception # when type-checking this pith against this hint. with raises_uncached(BeartypeCallHintPepException) as ( exception_info): func_typed(pith_unsatisfied_meta.pith) # Exception message raised by this wrapper function. exception_str = str(exception_info.value) # print('exception message: {}'.format(exception_str)) # Exception type localized for debuggability. Sadly, the # pytest.ExceptionInfo.__repr__() dunder method fails to # usefully describe its exception metadata. exception_type = exception_info.type # Assert this exception metadata describes the expected # exception as a sanity check against upstream pytest issues # and/or issues with our raises_uncached() context manager. assert issubclass(exception_type, BeartypeCallHintPepException) # For each uncompiled regular expression expected to match this # message, assert this expression actually does so. # # Note that the re.search() rather than re.match() function is # called. The latter is rooted at the start of the string and # thus *ONLY* matches prefixes, while the former is *NOT* # rooted at any string position and thus matches arbitrary # substrings as desired. for exception_str_match_regex in ( pith_unsatisfied_meta.exception_str_match_regexes): assert search( exception_str_match_regex, exception_str) is not None # For each uncompiled regular expression expected to *NOT* # match this message, assert this expression actually does so. for exception_str_not_match_regex in ( pith_unsatisfied_meta.exception_str_not_match_regexes): assert search( exception_str_not_match_regex, exception_str) is None