import collections.abc import sys import types from contextlib import nullcontext from datetime import timedelta from functools import partial from io import BytesIO, StringIO from pathlib import Path from typing import ( IO, AbstractSet, Annotated, Any, AnyStr, BinaryIO, Callable, Collection, ContextManager, Dict, ForwardRef, FrozenSet, Iterable, Iterator, List, Literal, Mapping, MutableMapping, Optional, Protocol, Sequence, Set, Sized, TextIO, Tuple, Type, TypeVar, Union, ) import pytest from typing_extensions import LiteralString from typeguard import ( CollectionCheckStrategy, ForwardRefPolicy, TypeCheckError, TypeCheckMemo, TypeHintWarning, check_type, check_type_internal, suppress_type_checks, ) from typeguard._checkers import is_typeddict from typeguard._utils import qualified_name from . import ( Child, Employee, JSONType, Parent, TChild, TIntStr, TParent, TTypingConstrained, myint, mylist, ) if sys.version_info >= (3, 11): SubclassableAny = Any else: from typing_extensions import Any as SubclassableAny if sys.version_info >= (3, 10): from typing import Concatenate, ParamSpec, TypeGuard else: from typing_extensions import Concatenate, ParamSpec, TypeGuard P = ParamSpec("P") @pytest.mark.skipif( sys.version_info >= (3, 13), reason="AnyStr is deprecated on Python 3.13" ) class TestAnyStr: @pytest.mark.parametrize( "value", [pytest.param("bar", id="str"), pytest.param(b"bar", id="bytes")] ) def test_valid(self, value): check_type(value, AnyStr) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 4, AnyStr).match( r"does not match any of the constraints \(bytes, str\)" ) class TestBytesLike: @pytest.mark.parametrize( "value", [ pytest.param(b"test", id="bytes"), pytest.param(bytearray(b"test"), id="bytearray"), pytest.param(memoryview(b"test"), id="memoryview"), ], ) def test_valid(self, value): check_type(value, bytes) def test_fail(self): pytest.raises(TypeCheckError, check_type, "test", bytes).match( r"str is not bytes-like" ) class TestFloat: @pytest.mark.parametrize( "value", [pytest.param(3, id="int"), pytest.param(3.87, id="float")] ) def test_valid(self, value): check_type(value, float) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, "foo", float).match( r"str is neither float or int" ) class TestComplexNumber: @pytest.mark.parametrize( "value", [ pytest.param(3, id="int"), pytest.param(3.87, id="float"), pytest.param(3.87 + 8j, id="complex"), ], ) def test_valid(self, value): check_type(value, complex) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, "foo", complex).match( "str is neither complex, float or int" ) class TestCallable: def test_any_args(self): def some_callable(x: int, y: str) -> int: pass check_type(some_callable, Callable[..., int]) def test_exact_arg_count(self): def some_callable(x: int, y: str) -> int: pass check_type(some_callable, Callable[[int, str], int]) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, Callable[..., int]).match( "is not callable" ) def test_too_few_arguments(self): def some_callable(x: int) -> int: pass pytest.raises( TypeCheckError, check_type, some_callable, Callable[[int, str], int] ).match( r"has too few arguments in its declaration; expected 2 but 1 argument\(s\) " r"declared" ) def test_too_many_arguments(self): def some_callable(x: int, y: str, z: float) -> int: pass pytest.raises( TypeCheckError, check_type, some_callable, Callable[[int, str], int] ).match( r"has too many mandatory positional arguments in its declaration; expected " r"2 but 3 mandatory positional argument\(s\) declared" ) def test_mandatory_kwonlyargs(self): def some_callable(x: int, y: str, *, z: float, bar: str) -> int: pass pytest.raises( TypeCheckError, check_type, some_callable, Callable[[int, str], int] ).match(r"has mandatory keyword-only arguments in its declaration: z, bar") def test_class(self): """ Test that passing a class as a callable does not count the "self" argument against the ones declared in the Callable specification. """ class SomeClass: def __init__(self, x: int, y: str): pass check_type(SomeClass, Callable[[int, str], Any]) def test_plain(self): def callback(a): pass check_type(callback, Callable) def test_partial_class(self): """ Test that passing a bound method as a callable does not count the "self" argument against the ones declared in the Callable specification. """ class SomeClass: def __init__(self, x: int, y: str): pass check_type(partial(SomeClass, y="foo"), Callable[[int], Any]) def test_bound_method(self): """ Test that passing a bound method as a callable does not count the "self" argument against the ones declared in the Callable specification. """ check_type(Child().method, Callable[[int], Any]) def test_partial_bound_method(self): """ Test that passing a bound method as a callable does not count the "self" argument against the ones declared in the Callable specification. """ check_type(partial(Child().method, 1), Callable[[], Any]) def test_defaults(self): """ Test that a callable having "too many" arguments don't raise an error if the extra arguments have default values. """ def some_callable(x: int, y: str, z: float = 1.2) -> int: pass check_type(some_callable, Callable[[int, str], Any]) def test_builtin(self): """ Test that checking a Callable annotation against a builtin callable does not raise an error. """ check_type([].append, Callable[[int], Any]) def test_concatenate(self): """Test that ``Concatenate`` in the arglist is ignored.""" check_type([].append, Callable[Concatenate[object, P], Any]) def test_positional_only_arg_with_default(self): def some_callable(x: int = 1, /) -> None: pass check_type(some_callable, Callable[[int], Any]) class TestLiteral: def test_literal_union(self): annotation = Union[str, Literal[1, 6, 8]] check_type(6, annotation) pytest.raises(TypeCheckError, check_type, 4, annotation).match( r"int did not match any element in the union:\n" r" str: is not an instance of str\n" r" Literal\[1, 6, 8\]: is not any of \(1, 6, 8\)$" ) def test_literal_nested(self): annotation = Literal[1, Literal["x", "a", Literal["z"]], 6, 8] check_type("z", annotation) pytest.raises(TypeCheckError, check_type, 4, annotation).match( r"int is not any of \(1, 'x', 'a', 'z', 6, 8\)$" ) def test_literal_int_as_bool(self): pytest.raises(TypeCheckError, check_type, 0, Literal[False]) pytest.raises(TypeCheckError, check_type, 1, Literal[True]) def test_literal_illegal_value(self): pytest.raises(TypeError, check_type, 4, Literal[1, 1.1]).match( r"Illegal literal value: 1.1$" ) class TestMapping: class DummyMapping(collections.abc.Mapping): _values = {"a": 1, "b": 10, "c": 100} def __getitem__(self, index: str): return self._values[index] def __iter__(self): return iter(self._values) def __len__(self) -> int: return len(self._values) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, Mapping[str, int]).match( "is not a mapping" ) def test_bad_key_type(self): pytest.raises( TypeCheckError, check_type, TestMapping.DummyMapping(), Mapping[int, int] ).match( f"key 'a' of {__name__}.TestMapping.DummyMapping is not an instance of int" ) def test_bad_value_type(self): pytest.raises( TypeCheckError, check_type, TestMapping.DummyMapping(), Mapping[str, str] ).match( f"value of key 'a' of {__name__}.TestMapping.DummyMapping is not an " f"instance of str" ) def test_bad_key_type_full_check(self): pytest.raises( TypeCheckError, check_type, {"x": 1, 3: 2}, Mapping[str, int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("key 3 of dict is not an instance of str") def test_bad_value_type_full_check(self): pytest.raises( TypeCheckError, check_type, {"x": 1, "y": "a"}, Mapping[str, int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("value of key 'y' of dict is not an instance of int") def test_any_value_type(self): check_type(TestMapping.DummyMapping(), Mapping[str, Any]) class TestMutableMapping: class DummyMutableMapping(collections.abc.MutableMapping): _values = {"a": 1, "b": 10, "c": 100} def __getitem__(self, index: str): return self._values[index] def __setitem__(self, key, value): self._values[key] = value def __delitem__(self, key): del self._values[key] def __iter__(self): return iter(self._values) def __len__(self) -> int: return len(self._values) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, MutableMapping[str, int]).match( "is not a mutable mapping" ) def test_bad_key_type(self): pytest.raises( TypeCheckError, check_type, TestMutableMapping.DummyMutableMapping(), MutableMapping[int, int], ).match( f"key 'a' of {__name__}.TestMutableMapping.DummyMutableMapping is not an " f"instance of int" ) def test_bad_value_type(self): pytest.raises( TypeCheckError, check_type, TestMutableMapping.DummyMutableMapping(), MutableMapping[str, str], ).match( f"value of key 'a' of {__name__}.TestMutableMapping.DummyMutableMapping " f"is not an instance of str" ) class TestDict: def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, Dict[str, int]).match( "int is not a dict" ) def test_bad_key_type(self): pytest.raises(TypeCheckError, check_type, {1: 2}, Dict[str, int]).match( "key 1 of dict is not an instance of str" ) def test_bad_value_type(self): pytest.raises(TypeCheckError, check_type, {"x": "a"}, Dict[str, int]).match( "value of key 'x' of dict is not an instance of int" ) def test_bad_key_type_full_check(self): pytest.raises( TypeCheckError, check_type, {"x": 1, 3: 2}, Dict[str, int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("key 3 of dict is not an instance of str") def test_bad_value_type_full_check(self): pytest.raises( TypeCheckError, check_type, {"x": 1, "y": "a"}, Dict[str, int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("value of key 'y' of dict is not an instance of int") def test_custom_dict_generator_items(self): class CustomDict(dict): def items(self): for key in self: yield key, self[key] check_type(CustomDict(a=1), Dict[str, int]) class TestTypedDict: @pytest.mark.parametrize( "value, total, error_re", [ pytest.param({"x": 6, "y": "foo"}, True, None, id="correct"), pytest.param( {"y": "foo"}, True, r'dict is missing required key\(s\): "x"', id="missing_x", ), pytest.param( {"x": 6, "y": 3}, True, "dict is not an instance of str", id="wrong_y" ), pytest.param( {"x": 6}, True, r'is missing required key\(s\): "y"', id="missing_y_error", ), pytest.param({"x": 6}, False, None, id="missing_y_ok"), pytest.param( {"x": "abc"}, False, "dict is not an instance of int", id="wrong_x" ), pytest.param( {"x": 6, "foo": "abc"}, False, r'dict has unexpected extra key\(s\): "foo"', id="unknown_key", ), pytest.param( None, True, "is not a dict", id="not_dict", ), ], ) def test_typed_dict( self, value, total: bool, error_re: Optional[str], typing_provider ): class DummyDict(typing_provider.TypedDict, total=total): x: int y: str if error_re: pytest.raises(TypeCheckError, check_type, value, DummyDict).match(error_re) else: check_type(value, DummyDict) def test_inconsistent_keys_invalid(self, typing_provider): class DummyDict(typing_provider.TypedDict): x: int pytest.raises( TypeCheckError, check_type, {"x": 1, "y": 2, b"z": 3}, DummyDict ).match(r'dict has unexpected extra key\(s\): "y", "b\'z\'"') def test_notrequired_pass(self, typing_provider): try: NotRequired = typing_provider.NotRequired except AttributeError: pytest.skip(f"'NotRequired' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict): x: int y: NotRequired[int] z: "NotRequired[int]" check_type({"x": 8}, DummyDict) def test_notrequired_fail(self, typing_provider): try: NotRequired = typing_provider.NotRequired except AttributeError: pytest.skip(f"'NotRequired' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict): x: int y: NotRequired[int] z: "NotRequired[int]" with pytest.raises( TypeCheckError, match=r"value of key 'y' of dict is not an instance of int" ): check_type({"x": 1, "y": "foo"}, DummyDict) with pytest.raises( TypeCheckError, match=r"value of key 'z' of dict is not an instance of int" ): check_type({"x": 1, "y": 6, "z": "foo"}, DummyDict) def test_required_pass(self, typing_provider): try: Required = typing_provider.Required except AttributeError: pytest.skip(f"'Required' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict, total=False): x1: Required[int] x2: "Required[int]" y: int check_type({"x1": 1, "x2": 2}, DummyDict) check_type({"x1": 1, "x2": 2, "y": 3}, DummyDict) def test_required_missing(self, typing_provider): try: Required = typing_provider.Required except AttributeError: pytest.skip(f"'Required' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict, total=False): x1: Required[int] x2: "Required[int]" y: int with pytest.raises(TypeCheckError, match=r'is missing required key\(s\): "x1"'): check_type({"x2": 2, "y": 3}, DummyDict) with pytest.raises(TypeCheckError, match=r'is missing required key\(s\): "x2"'): check_type({"x1": 1, "y": 3}, DummyDict) def test_required_wrong_type(self, typing_provider): try: Required = typing_provider.Required except AttributeError: pytest.skip(f"'Required' not found in {typing_provider.__name__!r}") class DummyDict(typing_provider.TypedDict, total=False): x1: Required[int] x2: "Required[int]" y: int # Ensure inner type is validated correctly (regression test for #533) with pytest.raises( TypeCheckError, match=r"value of key 'x1' of dict is not an instance of int" ): check_type({"x1": "foo", "x2": 2, "y": 3}, DummyDict) with pytest.raises( TypeCheckError, match=r"value of key 'x2' of dict is not an instance of int" ): check_type({"x1": 1, "x2": "foo", "y": 3}, DummyDict) def test_is_typeddict(self, typing_provider): # Ensure both typing.TypedDict and typing_extensions.TypedDict are recognized class DummyDict(typing_provider.TypedDict): x: int assert is_typeddict(DummyDict) assert not is_typeddict(dict) def test_typed_dict_with_forward_ref_from_external_module(self): """Regression test for #536: forward ref NameError in Python 3.14.""" import tests.dummymodule # Only import the TypedDict, NOT ModuleLocalClass - this tests that forward # references resolve using the type's module namespace, not the caller's TypedDictWithForwardRef = tests.dummymodule.TypedDictWithForwardRef # Should not raise NameError for forward ref to ModuleLocalClass check_type({"x": tests.dummymodule.ModuleLocalClass()}, TypedDictWithForwardRef) # Should still enforce types correctly with pytest.raises( TypeCheckError, match=r"is not an instance of .*ModuleLocalClass" ): check_type({"x": "not a ModuleLocalClass"}, TypedDictWithForwardRef) def test_extra_items_positive(self, typing_provider): try: class DummyDict(typing_provider.TypedDict, extra_items=Union[str, int]): x: int except TypeError as exc: if "unexpected keyword argument 'extra_items'" in str(exc): pytest.skip( f"typing provider {typing_provider!r} does not support extra_items in TypedDict" ) else: raise check_type({"x": 6, "y": 7, "z": "foo"}, DummyDict) def test_extra_items_bad_type(self, typing_provider): try: class DummyDict(typing_provider.TypedDict, extra_items=Union[str, int]): x: int except TypeError as exc: if "unexpected keyword argument 'extra_items'" in str(exc): pytest.skip( f"typing provider {typing_provider!r} does not support extra_items in TypedDict" ) else: raise with pytest.raises( TypeCheckError, match=r"value of key 'z' of dict did not match any element in the union", ): check_type({"x": 6, "y": 7, "z": None}, DummyDict) class TestList: def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, List[int]).match( "int is not a list" ) def test_first_check_success(self): check_type(["aa", "bb", 1], List[str]) def test_first_check_empty(self): check_type([], List[str]) def test_first_check_fail(self): pytest.raises(TypeCheckError, check_type, ["bb"], List[int]).match( "list is not an instance of int" ) def test_full_check_fail(self): pytest.raises( TypeCheckError, check_type, [1, 2, "bb"], List[int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("list is not an instance of int") class TestSequence: def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, Sequence[int]).match( "int is not a sequence" ) @pytest.mark.parametrize( "value", [pytest.param([1, "bb"], id="list"), pytest.param((1, "bb"), id="tuple")], ) def test_first_check_success(self, value): check_type(value, Sequence[int]) def test_first_check_empty(self): check_type([], Sequence[int]) def test_first_check_fail(self): pytest.raises(TypeCheckError, check_type, ["bb"], Sequence[int]).match( "list is not an instance of int" ) def test_full_check_fail(self): pytest.raises( TypeCheckError, check_type, [1, 2, "bb"], Sequence[int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("list is not an instance of int") class TestAbstractSet: def test_custom_type(self): class DummySet(AbstractSet[int]): def __contains__(self, x: object) -> bool: return x == 1 def __len__(self) -> int: return 1 def __iter__(self) -> Iterator[int]: yield 1 check_type(DummySet(), AbstractSet[int]) def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, AbstractSet[int]).match( "int is not a set" ) def test_first_check_fail(self, sample_set): # Create a set which, when iterated, returns "bb" as the first item pytest.raises(TypeCheckError, check_type, sample_set, AbstractSet[int]).match( "set is not an instance of int" ) def test_full_check_fail(self): pytest.raises( TypeCheckError, check_type, {1, 2, "bb"}, AbstractSet[int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("set is not an instance of int") class TestSet: def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, Set[int]).match("int is not a set") def test_valid(self): check_type({1, 2}, Set[int]) def test_first_check_empty(self): check_type(set(), Set[int]) def test_first_check_fail(self, sample_set: set): pytest.raises(TypeCheckError, check_type, sample_set, Set[int]).match( "set is not an instance of int" ) def test_full_check_fail(self): pytest.raises( TypeCheckError, check_type, {1, 2, "bb"}, Set[int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("set is not an instance of int") class TestFrozenSet: def test_bad_type(self): pytest.raises(TypeCheckError, check_type, 5, FrozenSet[int]).match( "int is not a frozenset" ) def test_valid(self): check_type(frozenset({1, 2}), FrozenSet[int]) def test_first_check_empty(self): check_type(frozenset(), FrozenSet[int]) def test_first_check_fail(self, sample_set: set): pytest.raises( TypeCheckError, check_type, frozenset(sample_set), FrozenSet[int] ).match("set is not an instance of int") def test_full_check_fail(self): pytest.raises( TypeCheckError, check_type, frozenset({1, 2, "bb"}), FrozenSet[int], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("set is not an instance of int") def test_set_against_frozenset(self, sample_set: set): pytest.raises(TypeCheckError, check_type, sample_set, FrozenSet[int]).match( "set is not a frozenset" ) @pytest.mark.parametrize( "annotated_type", [ pytest.param(Tuple, id="typing"), pytest.param( tuple, id="builtin", marks=[ pytest.mark.skipif( sys.version_info < (3, 9), reason="builtins.tuple is not parametrizable before Python 3.9", ) ], ), ], ) class TestTuple: def test_bad_type(self, annotated_type: Any): pytest.raises(TypeCheckError, check_type, 5, annotated_type[int]).match( "int is not a tuple" ) def test_first_check_empty(self, annotated_type: Any): check_type((), annotated_type[int, ...]) def test_unparametrized_tuple(self, annotated_type: Any): check_type((5, "foo"), annotated_type) def test_unparametrized_tuple_fail(self, annotated_type: Any): pytest.raises(TypeCheckError, check_type, 5, annotated_type).match( "int is not a tuple" ) def test_too_many_elements(self, annotated_type: Any): pytest.raises( TypeCheckError, check_type, (1, "aa", 2), annotated_type[int, str] ).match(r"tuple has wrong number of elements \(expected 2, got 3 instead\)") def test_too_few_elements(self, annotated_type: Any): pytest.raises(TypeCheckError, check_type, (1,), annotated_type[int, str]).match( r"tuple has wrong number of elements \(expected 2, got 1 instead\)" ) def test_bad_element(self, annotated_type: Any): pytest.raises( TypeCheckError, check_type, (1, 2), annotated_type[int, str] ).match("tuple is not an instance of str") def test_ellipsis_bad_element(self, annotated_type: Any): pytest.raises( TypeCheckError, check_type, ("blah",), annotated_type[int, ...] ).match("tuple is not an instance of int") def test_ellipsis_bad_element_full_check(self, annotated_type: Any): pytest.raises( TypeCheckError, check_type, (1, 2, "blah"), annotated_type[int, ...], collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS, ).match("tuple is not an instance of int") def test_empty_tuple(self, annotated_type: Any): check_type((), annotated_type[()]) def test_empty_tuple_fail(self, annotated_type: Any): pytest.raises(TypeCheckError, check_type, (1,), annotated_type[()]).match( "tuple is not an empty tuple" ) class TestNamedTuple: def test_valid(self): check_type(Employee("bob", 1), Employee) def test_type_mismatch(self): pytest.raises(TypeCheckError, check_type, ("bob", 1), Employee).match( r"tuple is not a named tuple of type __tests__.Employee" ) def test_wrong_field_type(self): pytest.raises(TypeCheckError, check_type, Employee(2, 1), Employee).match( r"Employee is not an instance of str" ) class TestUnion: @pytest.mark.parametrize( "value", [pytest.param(6, id="int"), pytest.param("aa", id="str")] ) def test_valid(self, value): check_type(value, Union[str, int]) def test_typing_type_fail(self): pytest.raises(TypeCheckError, check_type, 1, Union[str, Collection]).match( "int did not match any element in the union:\n" " str: is not an instance of str\n" " Collection: is not an instance of collections.abc.Collection" ) @pytest.mark.parametrize( "annotation", [ pytest.param(Union[str, int], id="pep484"), pytest.param( ForwardRef("str | int"), id="pep604", marks=[ pytest.mark.skipif( sys.version_info < (3, 10), reason="Requires Python 3.10+" ) ], ), ], ) @pytest.mark.parametrize( "value", [pytest.param(6.5, id="float"), pytest.param(b"aa", id="bytes")] ) def test_union_fail(self, annotation, value): qualname = qualified_name(value) pytest.raises(TypeCheckError, check_type, value, annotation).match( f"{qualname} did not match any element in the union:\n" f" str: is not an instance of str\n" f" int: is not an instance of int" ) @pytest.mark.skipif( sys.implementation.name != "cpython", reason="Test relies on CPython's reference counting behavior", ) def test_union_reference_leak(self): class Leak: def __del__(self): nonlocal leaked leaked = False def inner1(): leak = Leak() # noqa: F841 check_type(b"asdf", Union[str, bytes]) leaked = True inner1() assert not leaked def inner2(): leak = Leak() # noqa: F841 check_type(b"asdf", Union[bytes, str]) leaked = True inner2() assert not leaked def inner3(): leak = Leak() # noqa: F841 with pytest.raises(TypeCheckError, match="any element in the union:"): check_type(1, Union[str, bytes]) leaked = True inner3() assert not leaked @pytest.mark.skipif( sys.implementation.name != "cpython", reason="Test relies on CPython's reference counting behavior", ) @pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType requires 3.10") def test_uniontype_reference_leak(self): class Leak: def __del__(self): nonlocal leaked leaked = False def inner1(): leak = Leak() # noqa: F841 check_type(b"asdf", str | bytes) leaked = True inner1() assert not leaked def inner2(): leak = Leak() # noqa: F841 check_type(b"asdf", bytes | str) leaked = True inner2() assert not leaked def inner3(): leak = Leak() # noqa: F841 with pytest.raises(TypeCheckError, match="any element in the union:"): check_type(1, Union[str, bytes]) leaked = True inner3() assert not leaked @pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType requires 3.10") def test_raw_uniontype_success(self): check_type(str | int, types.UnionType) @pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType requires 3.10") def test_raw_uniontype_fail(self): if sys.version_info < (3, 14): expected_type = r"\w+\.UnionType" else: expected_type = "Union" with pytest.raises( TypeCheckError, match=f"class str is not an instance of {expected_type}$" ): check_type(str, types.UnionType) class TestTypevar: def test_bound(self): check_type(Child(), TParent) def test_bound_fail(self): with pytest.raises(TypeCheckError, match="is not an instance of __tests__.Child"): check_type(Parent(), TChild) @pytest.mark.parametrize( "value", [pytest.param([6, 7], id="int"), pytest.param({"aa", "bb"}, id="str")] ) def test_collection_constraints(self, value): check_type(value, TTypingConstrained) def test_collection_constraints_fail(self): pytest.raises(TypeCheckError, check_type, {1, 2}, TTypingConstrained).match( r"set does not match any of the constraints \(List\[int\], " r"AbstractSet\[str\]\)" ) def test_constraints_fail(self): pytest.raises(TypeCheckError, check_type, 2.5, TIntStr).match( r"float does not match any of the constraints \(int, str\)" ) class TestNewType: def test_simple_valid(self): check_type(1, myint) def test_simple_bad_value(self): pytest.raises(TypeCheckError, check_type, "a", myint).match( r"str is not an instance of int" ) def test_generic_valid(self): check_type([1], mylist) def test_generic_bad_value(self): pytest.raises(TypeCheckError, check_type, ["a"], mylist).match( r"item 0 of list is not an instance of int" ) class TestType: @pytest.mark.parametrize("annotation", [pytest.param(Type), pytest.param(type)]) def test_unparametrized(self, annotation: Any): check_type(TestNewType, annotation) @pytest.mark.parametrize("annotation", [pytest.param(Type), pytest.param(type)]) def test_unparametrized_fail(self, annotation: Any): pytest.raises(TypeCheckError, check_type, 1, annotation).match( "int is not a class" ) @pytest.mark.parametrize( "value", [pytest.param(Parent, id="exact"), pytest.param(Child, id="subclass")] ) def test_parametrized(self, value): check_type(value, Type[Parent]) def test_parametrized_fail(self): pytest.raises(TypeCheckError, check_type, int, Type[str]).match( "class int is not a subclass of str" ) def test_parametrized_value(self): check_type(list[str], type[list[str]]) @pytest.mark.parametrize( "value", [pytest.param(str, id="str"), pytest.param(int, id="int")] ) def test_union(self, value): check_type(value, Type[Union[str, int, list]]) def test_union_any(self): check_type(list, Type[Union[str, int, Any]]) def test_any(self): check_type(list, Type[Any]) def test_union_fail(self): pytest.raises( TypeCheckError, check_type, dict, Type[Union[str, int, list]] ).match( "class dict did not match any element in the union:\n" " str: is not a subclass of str\n" " int: is not a subclass of int\n" " list: is not a subclass of list" ) def test_union_typevar(self): T = TypeVar("T", bound=Parent) check_type(Child, Type[T]) @pytest.mark.parametrize("check_against", [type, Type[Any]]) def test_generic_aliase(self, check_against): check_type(dict[str, str], check_against) check_type(Dict, check_against) check_type(Dict[str, str], check_against) class TestIO: @pytest.mark.parametrize( "annotation", [ pytest.param(BinaryIO, id="direct"), pytest.param(IO[bytes], id="parametrized"), ], ) def test_binary_valid(self, annotation): check_type(BytesIO(), annotation) @pytest.mark.parametrize( "annotation", [ pytest.param(BinaryIO, id="direct"), pytest.param(IO[bytes], id="parametrized"), ], ) def test_binary_fail(self, annotation): pytest.raises(TypeCheckError, check_type, StringIO(), annotation).match( "_io.StringIO is not a binary I/O object" ) def test_binary_real_file(self, tmp_path: Path): with tmp_path.joinpath("testfile").open("wb") as f: check_type(f, BinaryIO) @pytest.mark.parametrize( "annotation", [pytest.param(TextIO, id="direct"), pytest.param(IO[str], id="parametrized")], ) def test_text_valid(self, annotation): check_type(StringIO(), annotation) @pytest.mark.parametrize( "annotation", [pytest.param(TextIO, id="direct"), pytest.param(IO[str], id="parametrized")], ) def test_text_fail(self, annotation): pytest.raises(TypeCheckError, check_type, BytesIO(), annotation).match( "_io.BytesIO is not a text based I/O object" ) def test_text_real_file(self, tmp_path: Path): with tmp_path.joinpath("testfile").open("w") as f: check_type(f, TextIO) class TestIntersectingProtocol: SIT = TypeVar("SIT", covariant=True) class SizedIterable( Sized, Iterable[SIT], Protocol[SIT], ): ... @pytest.mark.parametrize( "subject, predicate_type", ( pytest.param( (), SizedIterable, id="empty_tuple_unspecialized", ), pytest.param( range(2), SizedIterable, id="range", ), pytest.param( (), SizedIterable[int], id="empty_tuple_int_specialized", ), pytest.param( (1, 2, 3), SizedIterable[int], id="tuple_int_specialized", ), pytest.param( ("1", "2", "3"), SizedIterable[str], id="tuple_str_specialized", ), ), ) def test_valid_member_passes(self, subject: object, predicate_type: type) -> None: for _ in range(2): # Makes sure that the cache is also exercised check_type(subject, predicate_type) xfail_nested_protocol_checks = pytest.mark.xfail( reason="false negative due to missing support for nested protocol checks", ) @pytest.mark.parametrize( "subject, predicate_type", ( pytest.param( (1 for _ in ()), SizedIterable, id="generator", ), pytest.param( range(2), SizedIterable[str], marks=xfail_nested_protocol_checks, id="range_str_specialized", ), pytest.param( (1, 2, 3), SizedIterable[str], marks=xfail_nested_protocol_checks, id="int_tuple_str_specialized", ), pytest.param( ("1", "2", "3"), SizedIterable[int], marks=xfail_nested_protocol_checks, id="str_tuple_int_specialized", ), ), ) def test_raises_for_non_member(self, subject: object, predicate_type: type) -> None: with pytest.raises(TypeCheckError): check_type(subject, predicate_type) class TestProtocol: @pytest.mark.parametrize( "instantiate", [pytest.param(True, id="instance"), pytest.param(False, id="class")], ) def test_success(self, typing_provider: Any, instantiate: bool) -> None: class MyProtocol(Protocol): member: int def noargs(self) -> None: pass def posonlyargs(self, a: int, b: str, /) -> None: pass def posargs(self, a: int, b: str, c: float = 2.0) -> None: pass def varargs(self, *args: Any) -> None: pass def varkwargs(self, **kwargs: Any) -> None: pass def varbothargs(self, *args: Any, **kwargs: Any) -> None: pass @staticmethod def my_static_method(x: int, y: str) -> None: pass @classmethod def my_class_method(cls, x: int, y: str) -> None: pass class Foo: member = 1 def noargs(self, x: int = 1) -> None: pass def posonlyargs(self, a: int, b: str, c: float = 2.0, /) -> None: pass def posargs(self, *args: Any) -> None: pass def varargs(self, *args: Any, kwarg: str = "foo") -> None: pass def varkwargs(self, **kwargs: Any) -> None: pass def varbothargs(self, *args: Any, **kwargs: Any) -> None: pass # These were intentionally reversed, as this is OK for mypy @classmethod def my_static_method(cls, x: int, y: str) -> None: pass @staticmethod def my_class_method(x: int, y: str) -> None: pass if instantiate: check_type(Foo(), MyProtocol) else: check_type(Foo, type[MyProtocol]) @pytest.mark.parametrize( "instantiate", [pytest.param(True, id="instance"), pytest.param(False, id="class")], ) @pytest.mark.parametrize("subject_class", [object, str, Parent]) def test_empty_protocol(self, instantiate: bool, subject_class: type[Any]): class EmptyProtocol(Protocol): pass if instantiate: check_type(subject_class(), EmptyProtocol) else: check_type(subject_class, type[EmptyProtocol]) @pytest.mark.parametrize("has_member", [True, False]) def test_member_checks(self, has_member: bool) -> None: class MyProtocol(Protocol): member: int class Foo: def __init__(self, member: int): if member: self.member = member if has_member: check_type(Foo(1), MyProtocol) else: pytest.raises(TypeCheckError, check_type, Foo(0), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because it has no attribute named " f"'member'" ) def test_missing_method(self) -> None: class MyProtocol(Protocol): def meth(self) -> None: pass class Foo: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because it has no method named " f"'meth'" ) def test_too_many_posargs(self) -> None: class MyProtocol(Protocol): def meth(self) -> None: pass class Foo: def meth(self, x: str) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method has too " f"many mandatory positional arguments" ) def test_wrong_posarg_name(self) -> None: class MyProtocol(Protocol): def meth(self, x: str) -> None: pass class Foo: def meth(self, y: str) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( rf"^{qualified_name(Foo)} is not compatible with the " rf"{MyProtocol.__qualname__} protocol because its 'meth' method has a " rf"positional argument \(y\) that should be named 'x' at this position" ) def test_too_few_posargs(self) -> None: class MyProtocol(Protocol): def meth(self, x: str) -> None: pass class Foo: def meth(self) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method has too " f"few positional arguments" ) def test_subject_method_no_positional_params(self) -> None: class ZeroArg: def __call__(self) -> None: return None class MyProtocol(Protocol): def meth(self, x: str) -> None: pass class Foo: meth = ZeroArg() pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method has too " f"few positional arguments" ) def test_no_varargs(self) -> None: class MyProtocol(Protocol): def meth(self, *args: Any) -> None: pass class Foo: def meth(self) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method should " f"accept variable positional arguments but doesn't" ) def test_no_kwargs(self) -> None: class MyProtocol(Protocol): def meth(self, **kwargs: Any) -> None: pass class Foo: def meth(self) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method should " f"accept variable keyword arguments but doesn't" ) def test_missing_kwarg(self) -> None: class MyProtocol(Protocol): def meth(self, *, x: str) -> None: pass class Foo: def meth(self) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method is " f"missing keyword-only arguments: x" ) def test_extra_kwarg(self) -> None: class MyProtocol(Protocol): def meth(self) -> None: pass class Foo: def meth(self, *, x: str) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method has " f"mandatory keyword-only arguments not present in the protocol: x" ) def test_instance_staticmethod_mismatch(self) -> None: class MyProtocol(Protocol): @staticmethod def meth() -> None: pass class Foo: def meth(self) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method should " f"be a static method but it's an instance method" ) def test_instance_classmethod_mismatch(self) -> None: class MyProtocol(Protocol): @classmethod def meth(cls) -> None: pass class Foo: def meth(self) -> None: pass pytest.raises(TypeCheckError, check_type, Foo(), MyProtocol).match( f"^{qualified_name(Foo)} is not compatible with the " f"{MyProtocol.__qualname__} protocol because its 'meth' method should " f"be a class method but it's an instance method" ) def test_builtin_signature_check(self) -> None: class MyProtocol(Protocol): def attr(self) -> None: pass class Foo: attr = timedelta # Foo.attr is incompatible but timedelta has not inspectable signature so the # check is skipped check_type(Foo(), MyProtocol) def test_inherited_classmethod(self) -> None: class MyProtocol(Protocol): @classmethod def class_meth(cls) -> None: pass class Base: @classmethod def class_meth(cls) -> None: pass class Sub(Base): pass check_type(Sub(), MyProtocol) check_type(Sub, type[MyProtocol]) def test_inherited_staticmethod(self) -> None: class MyProtocol(Protocol): @staticmethod def static_meth() -> None: pass class Base: @staticmethod def static_meth() -> None: pass class Sub(Base): pass check_type(Sub(), MyProtocol) check_type(Sub, type[MyProtocol]) class TestRecursiveType: def test_valid(self): check_type({"a": [1, 2, 3]}, JSONType) def test_fail(self): with pytest.raises( TypeCheckError, match=( "dict did not match any element in the union:\n" " str: is not an instance of str\n" " float: is neither float or int\n" " bool: is not an instance of bool\n" " NoneType: is not an instance of NoneType\n" " List\\[JSONType\\]: is not a list\n" " Dict\\[str, JSONType\\]: value of key 'a' did not match any element " "in the union:\n" " str: is not an instance of str\n" " float: is neither float or int\n" " bool: is not an instance of bool\n" " NoneType: is not an instance of NoneType\n" " List\\[JSONType\\]: is not a list\n" " Dict\\[str, JSONType\\]: is not a dict" ), ): check_type({"a": (1, 2, 3)}, JSONType) class TestAnnotated: def test_valid(self): check_type("aa", Annotated[str, "blah"]) def test_fail(self): pytest.raises(TypeCheckError, check_type, 1, Annotated[str, "blah"]).match( "int is not an instance of str" ) class TestLiteralString: def test_valid(self): check_type("aa", LiteralString) def test_fail(self): pytest.raises(TypeCheckError, check_type, 1, LiteralString).match( "int is not an instance of str" ) class TestTypeGuard: def test_valid(self): check_type(True, TypeGuard) def test_fail(self): pytest.raises(TypeCheckError, check_type, 1, TypeGuard).match( "int is not an instance of bool" ) @pytest.mark.parametrize( "policy, contextmanager", [ pytest.param(ForwardRefPolicy.ERROR, pytest.raises(NameError), id="error"), pytest.param(ForwardRefPolicy.WARN, pytest.warns(TypeHintWarning), id="warn"), pytest.param(ForwardRefPolicy.IGNORE, nullcontext(), id="ignore"), ], ) def test_forward_reference_policy( policy: ForwardRefPolicy, contextmanager: ContextManager ): with contextmanager: check_type(1, ForwardRef("Foo"), forward_ref_policy=policy) # noqa: F821 def test_any(): assert check_type("aa", Any) == "aa" def test_suppressed_checking(): with suppress_type_checks(): assert check_type("aa", int) == "aa" def test_suppressed_checking_exception(): with pytest.raises(RuntimeError), suppress_type_checks(): assert check_type("aa", int) == "aa" raise RuntimeError pytest.raises(TypeCheckError, check_type, "aa", int) def test_any_subclass(): class Foo(SubclassableAny): pass check_type(Foo(), int) def test_none(): check_type(None, None) def test_return_checked_value(): value = {"foo": 1} assert check_type(value, Dict[str, int]) is value def test_imported_str_forward_ref(): value = {"foo": 1} memo = TypeCheckMemo(globals(), locals()) pattern = r"Skipping type check against 'Dict\[str, int\]'" with pytest.warns(TypeHintWarning, match=pattern): check_type_internal(value, "Dict[str, int]", memo) def test_check_against_tuple_success(): check_type(1, (float, Union[str, int])) def test_check_against_tuple_failure(): pytest.raises(TypeCheckError, check_type, "aa", (int, bytes))