import asyncio import importlib import sys import warnings from importlib import import_module from importlib.util import cache_from_source from pathlib import Path import pytest from pytest import FixtureRequest from typeguard import TypeCheckError, install_import_hook, suppress_type_checks from typeguard._importhook import OPTIMIZATION pytestmark = pytest.mark.filterwarnings("error:no type annotations present") this_dir = Path(__file__).parent dummy_module_path = this_dir / "dummymodule.py" instrumented_cached_module_path = Path( cache_from_source(str(dummy_module_path), optimization=OPTIMIZATION) ) cached_module_path = Path(cache_from_source(str(dummy_module_path))) # This block here is to test the recipe mentioned in the user guide if "pytest" in sys.modules: from typeguard import typechecked else: from typing import TypeVar _T = TypeVar("_T") def typechecked(target: _T, **kwargs) -> _T: return target if target else typechecked @pytest.fixture(scope="module", params=["typechecked", "importhook"]) def method(request: FixtureRequest) -> str: return request.param def _fixture_module(name: str, method: str): # config.debug_instrumentation = True sys.path.insert(0, str(this_dir)) try: # sys.modules.pop(name, None) if method == "typechecked": if cached_module_path.exists(): cached_module_path.unlink() if name in sys.modules: module = import_module(name) importlib.reload(module) else: module = import_module(name) return module if instrumented_cached_module_path.exists(): instrumented_cached_module_path.unlink() with install_import_hook([name]): with warnings.catch_warnings(): warnings.filterwarnings("error", module="typeguard") if name in sys.modules: module = import_module(name) importlib.reload(module) else: module = import_module(name) return module finally: sys.path.remove(str(this_dir)) @pytest.fixture(scope="module") def dummymodule(method: str): return _fixture_module("dummymodule", method) @pytest.fixture(scope="module") def deferredannos(method: str): if sys.version_info < (3, 14): raise pytest.skip("Deferred annotations are only supported in Python 3.14+") return _fixture_module("deferredannos", method) @pytest.fixture(scope="module") def dummymodule_py312(method: str): if sys.version_info < (3, 12): raise pytest.skip("This test requires Python 3.12+") return _fixture_module("dummymodule_py312", method) def test_type_checked_func(dummymodule): assert dummymodule.type_checked_func(2, 3) == 6 def test_type_checked_func_error(dummymodule): pytest.raises(TypeCheckError, dummymodule.type_checked_func, 2, "3").match( r'argument "y" \(str\) is not an instance of int' ) def test_non_type_checked_func(dummymodule): assert dummymodule.non_type_checked_func("bah", 9) == "foo" def test_non_type_checked_decorated_func(dummymodule): assert dummymodule.non_type_checked_func("bah", 9) == "foo" def test_typeguard_ignored_func(dummymodule): assert dummymodule.non_type_checked_func("bah", 9) == "foo" def test_type_checked_method(dummymodule): instance = dummymodule.DummyClass() pytest.raises(TypeCheckError, instance.type_checked_method, "bah", 9).match( r'argument "x" \(str\) is not an instance of int' ) def test_type_checked_classmethod(dummymodule): pytest.raises( TypeCheckError, dummymodule.DummyClass.type_checked_classmethod, "bah", 9 ).match(r'argument "x" \(str\) is not an instance of int') def test_type_checked_staticmethod(dummymodule): pytest.raises( TypeCheckError, dummymodule.DummyClass.type_checked_staticmethod, "bah", 9 ).match(r'argument "x" \(str\) is not an instance of int') @pytest.mark.xfail(reason="No workaround for this has been implemented yet") def test_inner_class_method(dummymodule): retval = dummymodule.Outer().create_inner() assert retval.__class__.__qualname__ == "Outer.Inner" @pytest.mark.xfail(reason="No workaround for this has been implemented yet") def test_inner_class_classmethod(dummymodule): retval = dummymodule.Outer.create_inner_classmethod() assert retval.__class__.__qualname__ == "Outer.Inner" @pytest.mark.xfail(reason="No workaround for this has been implemented yet") def test_inner_class_staticmethod(dummymodule): retval = dummymodule.Outer.create_inner_staticmethod() assert retval.__class__.__qualname__ == "Outer.Inner" def test_local_class_instance(dummymodule): instance = dummymodule.create_local_class_instance() assert ( instance.__class__.__qualname__ == "create_local_class_instance..Inner" ) def test_contextmanager(dummymodule): with dummymodule.dummy_context_manager() as value: assert value == 1 def test_overload(dummymodule): dummymodule.overloaded_func(1) dummymodule.overloaded_func("x") pytest.raises(TypeCheckError, dummymodule.overloaded_func, b"foo") def test_async_func(dummymodule): pytest.raises(TypeCheckError, asyncio.run, dummymodule.async_func(b"foo")) def test_generator_valid(dummymodule): gen = dummymodule.generator_func(6, "foo") assert gen.send(None) == 6 try: gen.send(None) except StopIteration as exc: assert exc.value == "foo" else: pytest.fail("Generator did not exit") def test_generator_bad_yield_type(dummymodule): gen = dummymodule.generator_func("foo", "foo") pytest.raises(TypeCheckError, gen.send, None).match( r"yielded value \(str\) is not an instance of int" ) gen.close() def test_generator_bad_return_type(dummymodule): gen = dummymodule.generator_func(6, 6) assert gen.send(None) == 6 pytest.raises(TypeCheckError, gen.send, None).match( r"return value \(int\) is not an instance of str" ) gen.close() def test_asyncgen_valid(dummymodule): gen = dummymodule.asyncgen_func(6) assert asyncio.run(gen.asend(None)) == 6 def test_asyncgen_bad_yield_type(dummymodule): gen = dummymodule.asyncgen_func("foo") pytest.raises(TypeCheckError, asyncio.run, gen.asend(None)).match( r"yielded value \(str\) is not an instance of int" ) def test_missing_return(dummymodule): pytest.raises(TypeCheckError, dummymodule.missing_return).match( r"the return value \(None\) is not an instance of int" ) def test_pep_604_union_args(dummymodule): pytest.raises(TypeCheckError, dummymodule.pep_604_union_args, 1.1).match( r'argument "x" \(float\) did not match any element in the union:' r"\n Callable\[list, Literal\[-1\]\]: is not callable" r"\n Callable\[ellipsis, Union\[int, str\]\]: is not callable" ) def test_pep_604_union_retval(dummymodule): pytest.raises(TypeCheckError, dummymodule.pep_604_union_retval, 1.1).match( r"the return value \(float\) did not match any element in the union:" r"\n str: is not an instance of str" r"\n int: is not an instance of int" ) def test_builtin_generic_collections(dummymodule): pytest.raises(TypeCheckError, dummymodule.builtin_generic_collections, 1.1).match( r'argument "x" \(float\) is not a list' ) def test_paramspec(dummymodule): def foo(a: int, b: str, *, c: bytes) -> None: pass dummymodule.paramspec_function(foo, (1, "bar"), {"c": b"abc"}) def test_augmented_assign(dummymodule): assert dummymodule.aug_assign() == 2 def test_multi_assign_single_value(dummymodule): assert dummymodule.multi_assign_single_value() == (6, 6, 6) def test_multi_assign_iterable(dummymodule): assert dummymodule.multi_assign_iterable() == ([6, 7], [6, 7], [6, 7]) def test_unpacking_assign(dummymodule): assert dummymodule.unpacking_assign() == (1, "foo") def test_unpacking_assign_single_item_tuple(dummymodule): assert dummymodule.unpacking_assign_single_item_tuple() == "foo" def test_unpacking_assign_from_generator(dummymodule): assert dummymodule.unpacking_assign_generator() == (1, "foo") def test_unpacking_assign_star_with_annotation(dummymodule): assert dummymodule.unpacking_assign_star_with_annotation() == ( 1, [b"abc", b"bah"], "foo", ) def test_unpacking_assign_star_no_annotation_success(dummymodule): assert dummymodule.unpacking_assign_star_no_annotation( (1, b"abc", b"bah", b"xyzzy", b"1234", "foo") ) == ( 1, [b"abc", b"bah", b"xyzzy", b"1234"], "foo", ) def test_attribute_assign_unpacking(dummymodule): foo = dummymodule.DummyClass() dummymodule.attribute_assign_unpacking(foo) def test_unpacking_assign_star_no_annotation_fail(dummymodule): with pytest.raises( TypeCheckError, match=r"value assigned to z \(bytes\) is not an instance of str" ): dummymodule.unpacking_assign_star_no_annotation((1, b"abc", b"bah", b"foo")) class TestOptionsOverride: def test_forward_ref_policy(self, dummymodule): with pytest.raises(NameError, match="name 'NonexistentType' is not defined"): dummymodule.override_forward_ref_policy(6) def test_typecheck_fail_callback(self, dummymodule, capsys): dummymodule.override_typecheck_fail_callback("foo") assert capsys.readouterr().out == ( 'argument "value" (str) is not an instance of int\n' ) def test_override_collection_check_strategy(self, dummymodule): with pytest.raises( TypeCheckError, match=r'item 1 of argument "value" \(list\) is not an instance of int', ): dummymodule.override_collection_check_strategy([1, "foo"]) def test_outer_class_typecheck_fail_callback(self, dummymodule, capsys): dummymodule.OverrideClass().override_typecheck_fail_callback("foo") assert capsys.readouterr().out == ( 'argument "value" (str) is not an instance of int\n' ) def test_inner_class_no_overrides(self, dummymodule): with pytest.raises(TypeCheckError): dummymodule.OverrideClass.Inner().override_typecheck_fail_callback("foo") class TestVariableArguments: def test_success(self, dummymodule): assert dummymodule.typed_variable_args("foo", "bar", a=1, b=8) == ( ("foo", "bar"), {"a": 1, "b": 8}, ) def test_args_fail(self, dummymodule): with pytest.raises( TypeCheckError, match=r'item 0 of argument "args" \(tuple\) is not an instance of str', ): dummymodule.typed_variable_args(1, a=1, b=8) def test_kwargs_fail(self, dummymodule): with pytest.raises( TypeCheckError, match=r'value of key \'a\' of argument "kwargs" \(dict\) is not an ' r"instance of int", ): dummymodule.typed_variable_args("foo", "bar", a="baz") class TestGuardedType: def test_plain(self, dummymodule): assert dummymodule.guarded_type_hint_plain("foo") == "foo" def test_subscript_toplevel(self, dummymodule): assert dummymodule.guarded_type_hint_subscript_toplevel("foo") == "foo" def test_subscript_nested(self, dummymodule): assert dummymodule.guarded_type_hint_subscript_nested(["foo"]) == ["foo"] def test_literal(dummymodule): assert dummymodule.literal("foo") == "foo" def test_literal_in_union(dummymodule): """Regression test for #372.""" assert dummymodule.literal_in_union("foo") == "foo" def test_typevar_forwardref(dummymodule): print(f"id of typevar_forwardref: {id(dummymodule.typevar_forwardref):x}") instance = dummymodule.typevar_forwardref(dummymodule.DummyClass) assert isinstance(instance, dummymodule.DummyClass) def test_suppress_annotated_assignment(dummymodule): with suppress_type_checks(): assert dummymodule.literal_in_union("foo") == "foo" def test_suppress_annotated_multi_assignment(dummymodule): with suppress_type_checks(): assert dummymodule.multi_assign_single_value() == (6, 6, 6) class TestUsesForwardRef: def test_success(self, deferredannos): obj = deferredannos.NotYetDefined() assert deferredannos.uses_forwardref(obj) is obj def test_failure(self, deferredannos): with pytest.raises( TypeCheckError, match=r'argument "x" \(int\) is not an instance of deferredannos.NotYetDefined', ): deferredannos.uses_forwardref(1) class TestParametrized: def test_success_func(self, dummymodule_py312): assert dummymodule_py312.parametrized_func(1, "2") == 1 def test_success_method(self, dummymodule_py312): assert dummymodule_py312.ParametrizedClass[int]().method(1, "2") == 1 def test_failure_func(self, dummymodule_py312): with pytest.raises( TypeCheckError, match=r'argument "y" \(int\) is not an instance of str', ): dummymodule_py312.parametrized_func(1, 2) def test_failure_method(self, dummymodule_py312): with pytest.raises( TypeCheckError, match=r'argument "y" \(int\) is not an instance of str', ): dummymodule_py312.ParametrizedClass[int]().method("str", 2) class TestTypeAlias: def test_success(self, dummymodule_py312): assert dummymodule_py312.func_using_type_alias([1, 2]) == 1 def test_failure(self, dummymodule_py312): with pytest.raises( TypeCheckError, match=r'item 0 of argument "x" \(list\) is not an instance of int', ): dummymodule_py312.func_using_type_alias(["foo"]) def test_type_arg_success(self, dummymodule_py312): assert dummymodule_py312.func_using_type_of_type_alias(list) is list def test_type_arg_failure(self, dummymodule_py312): with pytest.raises( TypeCheckError, match=r'argument "x" \(class dict\) is not a subclass of list', ): dummymodule_py312.func_using_type_of_type_alias(dict)