summaryrefslogtreecommitdiffstats
path: root/contrib/python/typeguard/tests
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-12-02 18:05:40 +0300
committerrobot-piglet <[email protected]>2025-12-02 18:27:29 +0300
commitb3dbd1a8804e4ff048b31dcc8e9a4cbac206b4bc (patch)
tree5550c45ff0f0cebd785d47aa28bdb24adbb6efc4 /contrib/python/typeguard/tests
parentd191c438a6de7f1a0a7079cdab06e27377dbcbff (diff)
Intermediate changes
commit_hash:5d7bb0b3a16601d453badbe45dcb76f582024a93
Diffstat (limited to 'contrib/python/typeguard/tests')
-rw-r--r--contrib/python/typeguard/tests/__init__.py44
-rw-r--r--contrib/python/typeguard/tests/conftest.py39
-rw-r--r--contrib/python/typeguard/tests/deferredannos.py10
-rw-r--r--contrib/python/typeguard/tests/dummymodule.py311
-rw-r--r--contrib/python/typeguard/tests/mypy/negative.py23
-rw-r--r--contrib/python/typeguard/tests/mypy/positive.py9
-rw-r--r--contrib/python/typeguard/tests/mypy/test_type_annotations.py46
-rw-r--r--contrib/python/typeguard/tests/pep695.py12
-rw-r--r--contrib/python/typeguard/tests/test_checkers.py1526
-rw-r--r--contrib/python/typeguard/tests/test_importhook.py117
-rw-r--r--contrib/python/typeguard/tests/test_instrumentation.py419
-rw-r--r--contrib/python/typeguard/tests/test_plugins.py26
-rw-r--r--contrib/python/typeguard/tests/test_pytest_plugin.py77
-rw-r--r--contrib/python/typeguard/tests/test_suppression.py68
-rw-r--r--contrib/python/typeguard/tests/test_transformer.py1972
-rw-r--r--contrib/python/typeguard/tests/test_typechecked.py711
-rw-r--r--contrib/python/typeguard/tests/test_typeguard.py1548
-rw-r--r--contrib/python/typeguard/tests/test_typeguard_py36.py189
-rw-r--r--contrib/python/typeguard/tests/test_utils.py22
-rw-r--r--contrib/python/typeguard/tests/test_warn_on_error.py28
-rw-r--r--contrib/python/typeguard/tests/ya.make15
21 files changed, 5320 insertions, 1892 deletions
diff --git a/contrib/python/typeguard/tests/__init__.py b/contrib/python/typeguard/tests/__init__.py
new file mode 100644
index 00000000000..b48bd69cb98
--- /dev/null
+++ b/contrib/python/typeguard/tests/__init__.py
@@ -0,0 +1,44 @@
+from typing import (
+ AbstractSet,
+ Collection,
+ Dict,
+ Generic,
+ List,
+ NamedTuple,
+ NewType,
+ TypeVar,
+ Union,
+)
+
+T_Foo = TypeVar("T_Foo")
+
+TBound = TypeVar("TBound", bound="Parent")
+TConstrained = TypeVar("TConstrained", "Parent", int)
+TTypingConstrained = TypeVar("TTypingConstrained", List[int], AbstractSet[str])
+TIntStr = TypeVar("TIntStr", int, str)
+TIntCollection = TypeVar("TIntCollection", int, Collection[int])
+TParent = TypeVar("TParent", bound="Parent")
+TChild = TypeVar("TChild", bound="Child")
+
+
+class Employee(NamedTuple):
+ name: str
+ id: int
+
+
+JSONType = Union[str, float, bool, None, List["JSONType"], Dict[str, "JSONType"]]
+myint = NewType("myint", int)
+mylist = NewType("mylist", List[int])
+
+
+class FooGeneric(Generic[T_Foo]):
+ pass
+
+
+class Parent:
+ pass
+
+
+class Child(Parent):
+ def method(self, a: int) -> None:
+ pass
diff --git a/contrib/python/typeguard/tests/conftest.py b/contrib/python/typeguard/tests/conftest.py
index 2a3132bc4f7..ef8731f022c 100644
--- a/contrib/python/typeguard/tests/conftest.py
+++ b/contrib/python/typeguard/tests/conftest.py
@@ -1,12 +1,45 @@
+import random
import re
+import string
import sys
+import typing
+from itertools import count
+from pathlib import Path
-version_re = re.compile(r'_py(\d)(\d)\.py$')
+import pytest
+import typing_extensions
+version_re = re.compile(r"_py(\d)(\d)\.py$")
+pytest_plugins = ["pytester"]
-def pytest_ignore_collect(path, config):
- match = version_re.search(path.basename)
+
+def pytest_ignore_collect(
+ collection_path: Path, config: pytest.Config
+) -> typing.Optional[bool]:
+ match = version_re.search(collection_path.name)
if match:
version = tuple(int(x) for x in match.groups())
if sys.version_info < version:
return True
+
+ return None
+
+
+def sample_set() -> set:
+ # Create a set which, when iterated, returns "bb" as the first item
+ for num in count():
+ letter = random.choice(string.ascii_lowercase)
+ dummy_set = {letter, num}
+ if next(iter(dummy_set)) == letter:
+ return dummy_set
+
+
+ params=[
+ pytest.param(typing, id="typing"),
+ pytest.param(typing_extensions, id="typing_extensions"),
+ ]
+)
+def typing_provider(request):
+ return request.param
diff --git a/contrib/python/typeguard/tests/deferredannos.py b/contrib/python/typeguard/tests/deferredannos.py
new file mode 100644
index 00000000000..0d2a1672902
--- /dev/null
+++ b/contrib/python/typeguard/tests/deferredannos.py
@@ -0,0 +1,10 @@
+from typeguard import typechecked
+
+
+@typechecked
+def uses_forwardref(x: NotYetDefined) -> NotYetDefined: # noqa: F821
+ return x
+
+
+class NotYetDefined:
+ pass
diff --git a/contrib/python/typeguard/tests/dummymodule.py b/contrib/python/typeguard/tests/dummymodule.py
index 7578976a1a7..17ca2263cde 100644
--- a/contrib/python/typeguard/tests/dummymodule.py
+++ b/contrib/python/typeguard/tests/dummymodule.py
@@ -1,47 +1,82 @@
"""Module docstring."""
-from __future__ import absolute_import, division
-from typing import no_type_check, no_type_check_decorator
+import sys
+from contextlib import contextmanager
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncGenerator,
+ Callable,
+ Dict,
+ Generator,
+ List,
+ Literal,
+ Sequence,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ no_type_check,
+ no_type_check_decorator,
+ overload,
+)
-from typeguard import typeguard_ignore
+from typeguard import (
+ CollectionCheckStrategy,
+ ForwardRefPolicy,
+ typechecked,
+ typeguard_ignore,
+)
+if sys.version_info >= (3, 10):
+ from typing import ParamSpec
+else:
+ from typing_extensions import ParamSpec
-@no_type_check_decorator
-def dummy_decorator(func):
- return func
+if TYPE_CHECKING:
+ from nonexistent import Imaginary
+T = TypeVar("T", bound="DummyClass")
+P = ParamSpec("P")
+
+if sys.version_info <= (3, 13):
+
+ @no_type_check_decorator
+ def dummy_decorator(func):
+ return func
+
+ @dummy_decorator
+ def non_type_checked_decorated_func(x: int, y: str) -> 6:
+ # This is to ensure that we avoid using a local variable that's already in use
+ _call_memo = "foo" # noqa: F841
+ return "foo"
+
+
+@typechecked
def type_checked_func(x: int, y: int) -> int:
return x * y
@no_type_check
def non_type_checked_func(x: int, y: str) -> 6:
- return 'foo'
-
-
-@dummy_decorator
-def non_type_checked_decorated_func(x: int, y: str) -> 6:
- return 'foo'
+ return "foo"
@typeguard_ignore
def non_typeguard_checked_func(x: int, y: str) -> 6:
- return 'foo'
-
-
-def dynamic_type_checking_func(arg, argtype, return_annotation):
- def inner(x: argtype) -> return_annotation:
- return str(x)
-
- return inner(arg)
+ return "foo"
class Metaclass(type):
pass
+@typechecked
class DummyClass(metaclass=Metaclass):
+ bar: str
+ baz: int
+
def type_checked_method(self, x: int, y: int) -> int:
return x * y
@@ -67,26 +102,252 @@ class DummyClass(metaclass=Metaclass):
def outer():
+ @typechecked
class Inner:
- pass
+ def get_self(self) -> "Inner":
+ return self
- def create_inner() -> 'Inner':
+ def create_inner() -> "Inner":
return Inner()
return create_inner
+@typechecked
class Outer:
class Inner:
pass
- def create_inner(self) -> 'Inner':
+ def create_inner(self) -> "Inner":
return Outer.Inner()
@classmethod
- def create_inner_classmethod(cls) -> 'Inner':
+ def create_inner_classmethod(cls) -> "Inner":
return Outer.Inner()
@staticmethod
- def create_inner_staticmethod() -> 'Inner':
+ def create_inner_staticmethod() -> "Inner":
return Outer.Inner()
+
+
+@contextmanager
+@typechecked
+def dummy_context_manager() -> Generator[int, None, None]:
+ yield 1
+
+
+@overload
+def overloaded_func(a: int) -> int: ...
+
+
+@overload
+def overloaded_func(a: str) -> str: ...
+
+
+@typechecked
+def overloaded_func(a: Union[str, int]) -> Union[str, int]:
+ return a
+
+
+@typechecked
+def missing_return() -> int:
+ pass
+
+
+def get_inner_class() -> type:
+ @typechecked
+ class InnerClass:
+ def get_self(self) -> "InnerClass":
+ return self
+
+ return InnerClass
+
+
+def create_local_class_instance() -> object:
+ class Inner:
+ pass
+
+ @typechecked
+ def get_instance() -> "Inner":
+ return instance
+
+ instance = Inner()
+ return get_instance()
+
+
+@typechecked
+async def async_func(a: int) -> str:
+ return str(a)
+
+
+@typechecked
+def generator_func(yield_value: Any, return_value: Any) -> Generator[int, Any, str]:
+ yield yield_value
+ return return_value
+
+
+@typechecked
+async def asyncgen_func(yield_value: Any) -> AsyncGenerator[int, Any]:
+ yield yield_value
+
+
+@typechecked
+def pep_604_union_args(
+ x: "Callable[[], Literal[-1]] | Callable[..., Union[int, str]]",
+) -> None:
+ pass
+
+
+@typechecked
+def pep_604_union_retval(x: Any) -> "str | int":
+ return x
+
+
+@typechecked
+def builtin_generic_collections(x: "list[set[int]]") -> Any:
+ return x
+
+
+@typechecked
+def paramspec_function(func: P, args: P.args, kwargs: P.kwargs) -> None:
+ pass
+
+
+@typechecked
+def aug_assign() -> int:
+ x: int = 1
+ x += 1
+ return x
+
+
+@typechecked
+def multi_assign_single_value() -> Tuple[int, float, complex]:
+ x: int
+ y: float
+ z: complex
+ x = y = z = 6
+ return x, y, z
+
+
+@typechecked
+def multi_assign_iterable() -> Tuple[Sequence[int], Sequence[float], Sequence[complex]]:
+ x: Sequence[int]
+ y: Sequence[float]
+ z: Sequence[complex]
+ x = y = z = [6, 7]
+ return x, y, z
+
+
+@typechecked
+def unpacking_assign() -> Tuple[int, str]:
+ x: int
+ x, y = (1, "foo")
+ return x, y
+
+
+@typechecked
+def unpacking_assign_generator() -> Tuple[int, str]:
+ def genfunc():
+ yield 1
+ yield "foo"
+
+ x: int
+ x, y = genfunc()
+ return x, y
+
+
+@typechecked
+def unpacking_assign_star_with_annotation() -> Tuple[int, List[bytes], str]:
+ x: int
+ z: str
+ x, *y, z = (1, b"abc", b"bah", "foo")
+ return x, y, z
+
+
+@typechecked
+def unpacking_assign_star_no_annotation(value: Any) -> Tuple[int, List[bytes], str]:
+ x: int
+ y: List[bytes]
+ z: str
+ x, *y, z = value
+ return x, y, z
+
+
+@typechecked
+def attribute_assign_unpacking(obj: DummyClass) -> None:
+ obj.bar, obj.baz = "foo", 123123
+
+
+@typechecked(forward_ref_policy=ForwardRefPolicy.ERROR)
+def override_forward_ref_policy(value: "NonexistentType") -> None: # noqa: F821
+ pass
+
+
+@typechecked(typecheck_fail_callback=lambda exc, memo: print(exc))
+def override_typecheck_fail_callback(value: int) -> None:
+ pass
+
+
+@typechecked(collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS)
+def override_collection_check_strategy(value: List[int]) -> None:
+ pass
+
+
+@typechecked(typecheck_fail_callback=lambda exc, memo: print(exc))
+class OverrideClass:
+ def override_typecheck_fail_callback(self, value: int) -> None:
+ pass
+
+ class Inner:
+ @typechecked
+ def override_typecheck_fail_callback(self, value: int) -> None:
+ pass
+
+
+@typechecked
+def typed_variable_args(
+ *args: str, **kwargs: int
+) -> Tuple[Tuple[str, ...], Dict[str, int]]:
+ return args, kwargs
+
+
+@typechecked
+def guarded_type_hint_plain(x: "Imaginary") -> "Imaginary":
+ y: Imaginary = x
+ return y
+
+
+@typechecked
+def guarded_type_hint_subscript_toplevel(x: "Imaginary[int]") -> "Imaginary[int]":
+ y: Imaginary[int] = x
+ return y
+
+
+@typechecked
+def guarded_type_hint_subscript_nested(
+ x: List["Imaginary[int]"],
+) -> List["Imaginary[int]"]:
+ y: List[Imaginary[int]] = x
+ return y
+
+
+@typechecked
+def literal(x: Literal["foo"]) -> Literal["foo"]:
+ y: Literal["foo"] = x
+ return y
+
+
+@typechecked
+def literal_in_union(x: Union[Literal["foo"],]) -> Literal["foo"]:
+ y: Literal["foo"] = x
+ return y
+
+
+@typechecked
+def typevar_forwardref(x: Type[T]) -> T:
+ return x()
+
+
+def never_called(x: List["NonExistentType"]) -> List["NonExistentType"]: # noqa: F821
+ """Regression test for #335."""
+ return x
diff --git a/contrib/python/typeguard/tests/mypy/negative.py b/contrib/python/typeguard/tests/mypy/negative.py
index 6db0eb2a35b..ec93022f57b 100644
--- a/contrib/python/typeguard/tests/mypy/negative.py
+++ b/contrib/python/typeguard/tests/mypy/negative.py
@@ -1,4 +1,4 @@
-from typeguard import check_argument_types, check_return_type, typechecked, typeguard_ignore
+from typeguard import typechecked, typeguard_ignore
@typechecked
@@ -8,28 +8,31 @@ def foo(x: int) -> int:
@typechecked
def bar(x: int) -> int:
- return str(x) # error: Incompatible return value type (got "str", expected "int")
+ return str(x) # noqa: E501 # error: Incompatible return value type (got "str", expected "int") [return-value]
@typeguard_ignore
def non_typeguard_checked_func(x: int) -> int:
- return str(x) # error: Incompatible return value type (got "str", expected "int")
+ return str(x) # noqa: E501 # error: Incompatible return value type (got "str", expected "int") [return-value]
+@typechecked
def returns_str() -> str:
- return bar(0) # error: Incompatible return value type (got "int", expected "str")
+ return bar(0) # noqa: E501 # error: Incompatible return value type (got "int", expected "str") [return-value]
+@typechecked
def arg_type(x: int) -> str:
- return check_argument_types() # noqa: E501 # error: Incompatible return value type (got "bool", expected "str")
+ return True # noqa: E501 # error: Incompatible return value type (got "bool", expected "str") [return-value]
+@typechecked
def ret_type() -> str:
- return check_return_type(False) # noqa: E501 # error: Incompatible return value type (got "bool", expected "str")
+ return True # noqa: E501 # error: Incompatible return value type (got "bool", expected "str") [return-value]
-_ = arg_type(foo) # noqa: E501 # error: Argument 1 to "arg_type" has incompatible type "Callable[[int], int]"; expected "int"
-_ = foo("typeguard") # error: Argument 1 to "foo" has incompatible type "str"; expected "int"
+_ = arg_type(foo) # noqa: E501 # error: Argument 1 to "arg_type" has incompatible type "Callable[[int], int]"; expected "int" [arg-type]
+_ = foo("typeguard") # noqa: E501 # error: Argument 1 to "foo" has incompatible type "str"; expected "int" [arg-type]
@typechecked
@@ -49,5 +52,5 @@ def create_myclass(x: int) -> MyClass:
return MyClass(x)
-_ = get_value("foo") # noqa: E501 # error: Argument 1 to "get_value" has incompatible type "str"; expected "MyClass"
-_ = MyClass(returns_str()) # noqa: E501 # error: Argument 1 to "MyClass" has incompatible type "str"; expected "int"
+_ = get_value("foo") # noqa: E501 # error: Argument 1 to "get_value" has incompatible type "str"; expected "MyClass" [arg-type]
+_ = MyClass(returns_str()) # noqa: E501 # error: Argument 1 to "MyClass" has incompatible type "str"; expected "int" [arg-type]
diff --git a/contrib/python/typeguard/tests/mypy/positive.py b/contrib/python/typeguard/tests/mypy/positive.py
index 2f01bebf362..dc8a350aa47 100644
--- a/contrib/python/typeguard/tests/mypy/positive.py
+++ b/contrib/python/typeguard/tests/mypy/positive.py
@@ -1,6 +1,6 @@
from typing import Callable
-from typeguard import check_argument_types, check_return_type, typechecked
+from typeguard import typechecked
@typechecked
@@ -15,18 +15,17 @@ def takes_callable(f: Callable[[str], str]) -> str:
takes_callable(foo)
-def has_valid_arguments(x: int, y: str) -> bool:
- return check_argument_types()
+@typechecked
+def has_valid_arguments(x: int, y: str) -> None:
+ pass
def has_valid_return_type(y: str) -> str:
- check_return_type(y)
return y
@typechecked
class MyClass:
-
def __init__(self, x: int) -> None:
self.x = x
diff --git a/contrib/python/typeguard/tests/mypy/test_type_annotations.py b/contrib/python/typeguard/tests/mypy/test_type_annotations.py
index 50ddc50686e..a7ae7c58593 100644
--- a/contrib/python/typeguard/tests/mypy/test_type_annotations.py
+++ b/contrib/python/typeguard/tests/mypy/test_type_annotations.py
@@ -1,21 +1,23 @@
+import json
import os
import platform
-import re
import subprocess
-from typing import Dict, List
import pytest
POSITIVE_FILE = "positive.py"
NEGATIVE_FILE = "negative.py"
-LINE_PATTERN = NEGATIVE_FILE + ":([0-9]+):"
-pytestmark = [pytest.mark.skipif(platform.python_implementation() == 'PyPy',
- reason='MyPy does not work with PyPy yet')]
+pytestmark = [
+ pytest.mark.skipif(
+ platform.python_implementation() == "PyPy",
+ reason="MyPy does not work with PyPy yet",
+ )
+]
-def get_mypy_cmd(filename: str) -> List[str]:
- return ["mypy", "--strict", filename]
+def get_mypy_cmd(filename: str) -> list[str]:
+ return ["mypy", "-O", "json", "--strict", filename]
def get_negative_mypy_output() -> str:
@@ -30,7 +32,7 @@ def get_negative_mypy_output() -> str:
return output
-def get_expected_errors() -> Dict[int, str]:
+def get_expected_errors() -> dict[int, str]:
"""
Extract the expected errors from comments in the negative examples file.
"""
@@ -42,14 +44,14 @@ def get_expected_errors() -> Dict[int, str]:
for idx, line in enumerate(lines):
line = line.rstrip()
if "# error" in line:
- expected[idx + 1] = line[line.index("# error") + 2:]
+ expected[idx + 1] = line[line.index("# error") + 9 :]
# Sanity check. Should update if negative.py changes.
assert len(expected) == 9
return expected
-def get_mypy_errors() -> Dict[int, str]:
+def get_mypy_errors() -> dict[int, str]:
"""
Extract the errors from running mypy on the negative examples file.
"""
@@ -57,10 +59,8 @@ def get_mypy_errors() -> Dict[int, str]:
got = {}
for line in mypy_output.splitlines():
- m = re.match(LINE_PATTERN, line)
- if m is None:
- continue
- got[int(m.group(1))] = line[len(m.group(0)) + 1:]
+ error = json.loads(line)
+ got[error["line"]] = f"{error['message']} [{error['code']}]"
return got
@@ -75,6 +75,8 @@ def chdir_local() -> None:
os.chdir(os.path.dirname(__file__))
+# Этот тест ожидает "mypy" в PATH
@pytest.mark.usefixtures("chdir_local")
def test_positive() -> None:
"""
@@ -83,6 +85,8 @@ def test_positive() -> None:
subprocess.check_call(get_mypy_cmd(POSITIVE_FILE))
+# Этот тест ожидает "mypy" в PATH
@pytest.mark.usefixtures("chdir_local")
def test_negative() -> None:
"""
@@ -94,8 +98,8 @@ def test_negative() -> None:
if set(got_errors) != set(expected_errors):
raise RuntimeError(
- "Expected error lines {} does not ".format(set(expected_errors)) +
- "match mypy error lines {}.".format(set(got_errors))
+ f"Expected error lines {set(expected_errors)} does not "
+ + f"match mypy error lines {set(got_errors)}."
)
mismatches = [
@@ -103,12 +107,8 @@ def test_negative() -> None:
for idx in expected_errors
if expected_errors[idx] != got_errors[idx]
]
- for (idx, expected, got) in mismatches:
- print(
- "Line {}".format(idx),
- "Expected: {}".format(expected),
- "Got: {}".format(got),
- sep="\n\t"
- )
+ for idx, expected, got in mismatches:
+ print(f"Line {idx}", f"Expected: {expected}", f"Got: {got}", sep="\n\t")
+
if mismatches:
raise RuntimeError("Error messages changed")
diff --git a/contrib/python/typeguard/tests/pep695.py b/contrib/python/typeguard/tests/pep695.py
new file mode 100644
index 00000000000..b0a29291839
--- /dev/null
+++ b/contrib/python/typeguard/tests/pep695.py
@@ -0,0 +1,12 @@
+from typeguard import typechecked
+
+
+@typechecked
+class ParametrizedClass[T]:
+ def method(self, x: T, y: str) -> T:
+ return x
+
+
+@typechecked
+def parametrized_func[T](x: T, y: str) -> T:
+ return x
diff --git a/contrib/python/typeguard/tests/test_checkers.py b/contrib/python/typeguard/tests/test_checkers.py
new file mode 100644
index 00000000000..cc019c7b1f2
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_checkers.py
@@ -0,0 +1,1526 @@
+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")
+
+
+ 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_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)
+
+
+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"
+ )
+
+
+ "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_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)
+
+
+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"
+ )
+
+
+ "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))
diff --git a/contrib/python/typeguard/tests/test_importhook.py b/contrib/python/typeguard/tests/test_importhook.py
index b0f28278da0..7c8de7c8740 100644
--- a/contrib/python/typeguard/tests/test_importhook.py
+++ b/contrib/python/typeguard/tests/test_importhook.py
@@ -6,106 +6,40 @@ from pathlib import Path
import pytest
-from typeguard.importhook import TypeguardFinder, install_import_hook
+from typeguard import TypeCheckError, TypeguardFinder, install_import_hook
+from typeguard._importhook import OPTIMIZATION
-import yatest.common as yc
+pytestmark = pytest.mark.filterwarnings("error:no type annotations present")
+this_dir = Path(__file__).parent
+dummy_module_path = this_dir / "dummymodule.py"
+cached_module_path = Path(
+ cache_from_source(str(dummy_module_path), optimization=OPTIMIZATION)
+)
-this_dir = Path(yc.test_source_path())
-dummy_module_path = this_dir / 'dummymodule.py'
-cached_module_path = Path(cache_from_source(str(dummy_module_path), optimization='typeguard'))
-
[email protected](scope='module')
-def dummymodule():
+def import_dummymodule():
if cached_module_path.exists():
cached_module_path.unlink()
sys.path.insert(0, str(this_dir))
try:
- with install_import_hook('dummymodule'):
+ with install_import_hook(["dummymodule"]):
with warnings.catch_warnings():
- warnings.filterwarnings('error', module='typeguard')
- module = import_module('dummymodule')
+ warnings.filterwarnings("error", module="typeguard")
+ module = import_module("dummymodule")
return module
finally:
sys.path.remove(str(this_dir))
-def test_cached_module(dummymodule):
- assert cached_module_path.is_file()
-
-
-def test_type_checked_func(dummymodule):
- assert dummymodule.type_checked_func(2, 3) == 6
-
-
-def test_type_checked_func_error(dummymodule):
- pytest.raises(TypeError, dummymodule.type_checked_func, 2, '3').\
- match('"y" must be int; got str instead')
-
-
-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_decorated_func('bah', 9) == 'foo'
-
-
-def test_typeguard_ignored_func(dummymodule):
- assert dummymodule.non_typeguard_checked_func('bah', 9) == 'foo'
-
-
-def test_type_checked_method(dummymodule):
- instance = dummymodule.DummyClass()
- pytest.raises(TypeError, instance.type_checked_method, 'bah', 9).\
- match('"x" must be int; got str instead')
-
-
-def test_type_checked_classmethod(dummymodule):
- pytest.raises(TypeError, dummymodule.DummyClass.type_checked_classmethod, 'bah', 9).\
- match('"x" must be int; got str instead')
-
-
-def test_type_checked_staticmethod(dummymodule):
- pytest.raises(TypeError, dummymodule.DummyClass.type_checked_classmethod, 'bah', 9).\
- match('"x" must be int; got str instead')
-
-
[email protected]('argtype, returntype, error', [
- (int, str, None),
- (str, str, '"x" must be str; got int instead'),
- (int, int, 'type of the return value must be int; got str instead')
-], ids=['correct', 'bad_argtype', 'bad_returntype'])
-def test_dynamic_type_checking_func(dummymodule, argtype, returntype, error):
- if error:
- exc = pytest.raises(TypeError, dummymodule.dynamic_type_checking_func, 4, argtype,
- returntype)
- exc.match(error)
- else:
- assert dummymodule.dynamic_type_checking_func(4, argtype, returntype) == '4'
-
-
-def test_class_in_function(dummymodule):
- create_inner = dummymodule.outer()
- retval = create_inner()
- assert retval.__class__.__qualname__ == 'outer.<locals>.Inner'
-
-
-def test_inner_class_method(dummymodule):
- retval = dummymodule.Outer().create_inner()
- assert retval.__class__.__qualname__ == 'Outer.Inner'
-
-
-def test_inner_class_classmethod(dummymodule):
- retval = dummymodule.Outer.create_inner_classmethod()
- assert retval.__class__.__qualname__ == 'Outer.Inner'
-
-
-def test_inner_class_staticmethod(dummymodule):
- retval = dummymodule.Outer.create_inner_staticmethod()
- assert retval.__class__.__qualname__ == 'Outer.Inner'
+def test_blanket_import():
+ dummymodule = import_dummymodule()
+ try:
+ pytest.raises(TypeCheckError, dummymodule.type_checked_func, 2, "3").match(
+ r'argument "y" \(str\) is not an instance of int'
+ )
+ finally:
+ del sys.modules["dummymodule"]
def test_package_name_matching():
@@ -123,3 +57,14 @@ def test_package_name_matching():
assert not finder.should_instrument("spam")
assert not finder.should_instrument("ha")
assert not finder.should_instrument("spam_eggs")
+
+
[email protected](sys.version_info < (3, 9), reason="Requires ast.unparse()")
+def test_debug_instrumentation(monkeypatch, capsys):
+ monkeypatch.setattr("typeguard.config.debug_instrumentation", True)
+ import_dummymodule()
+ out, err = capsys.readouterr()
+ path_str = str(dummy_module_path)
+ # в ya make "path_str" разрешается в подкаталог ~/.ya/build/build_root/...
+ assert f"{path_str!r} after instrumentation:"[1:] in err
+ assert "class DummyClass" in err
diff --git a/contrib/python/typeguard/tests/test_instrumentation.py b/contrib/python/typeguard/tests/test_instrumentation.py
new file mode 100644
index 00000000000..4d9929cbac8
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_instrumentation.py
@@ -0,0 +1,419 @@
+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
+
+
[email protected](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))
+
+
[email protected](scope="module")
+def dummymodule(method: str):
+ return _fixture_module("dummymodule", method)
+
+
[email protected](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)
+
+
[email protected](scope="module")
+def pep695(method: str):
+ if sys.version_info < (3, 12):
+ raise pytest.skip("PEP 695 type parameter syntax requires Python 3.12+")
+
+ return _fixture_module("pep695", 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')
+
+
[email protected](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"
+
+
[email protected](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"
+
+
[email protected](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.<locals>.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_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", "foo")
+ ) == (
+ 1,
+ [b"abc", b"bah"],
+ "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, pep695):
+ assert pep695.parametrized_func(1, "2") == 1
+
+ def test_success_method(self, pep695):
+ assert pep695.ParametrizedClass[int]().method(1, "2") == 1
+
+ def test_failure_func(self, pep695):
+ with pytest.raises(
+ TypeCheckError,
+ match=r'argument "y" \(int\) is not an instance of str',
+ ):
+ pep695.parametrized_func(1, 2)
+
+ def test_failure_method(self, pep695):
+ with pytest.raises(
+ TypeCheckError,
+ match=r'argument "y" \(int\) is not an instance of str',
+ ):
+ pep695.ParametrizedClass[int]().method("str", 2)
diff --git a/contrib/python/typeguard/tests/test_plugins.py b/contrib/python/typeguard/tests/test_plugins.py
new file mode 100644
index 00000000000..f01a07419b8
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_plugins.py
@@ -0,0 +1,26 @@
+from pytest import MonkeyPatch
+
+from typeguard import load_plugins
+
+
+def test_custom_type_checker(monkeypatch: MonkeyPatch) -> None:
+ def lookup_func(origin_type, args, extras):
+ pass
+
+ class FakeEntryPoint:
+ name = "test"
+
+ def load(self):
+ return lookup_func
+
+ def fake_entry_points(group):
+ assert group == "typeguard.checker_lookup"
+ return [FakeEntryPoint()]
+
+ checker_lookup_functions = []
+ monkeypatch.setattr("typeguard._checkers.entry_points", fake_entry_points)
+ monkeypatch.setattr(
+ "typeguard._checkers.checker_lookup_functions", checker_lookup_functions
+ )
+ load_plugins()
+ assert checker_lookup_functions[0] is lookup_func
diff --git a/contrib/python/typeguard/tests/test_pytest_plugin.py b/contrib/python/typeguard/tests/test_pytest_plugin.py
new file mode 100644
index 00000000000..0c5b04d0ba6
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_pytest_plugin.py
@@ -0,0 +1,77 @@
+from textwrap import dedent
+
+import pytest
+from pytest import MonkeyPatch, Pytester
+
+from typeguard import CollectionCheckStrategy, ForwardRefPolicy, TypeCheckConfiguration
+
+
+def config(monkeypatch: MonkeyPatch) -> TypeCheckConfiguration:
+ config = TypeCheckConfiguration()
+ monkeypatch.setattr("typeguard._pytest_plugin.global_config", config)
+ return config
+
+
+def test_config_options(pytester: Pytester, config: TypeCheckConfiguration) -> None:
+ pytester.makepyprojecttoml(
+ '''
+ [tool.pytest.ini_options]
+ typeguard-packages = """
+ mypackage
+ otherpackage"""
+ typeguard-debug-instrumentation = true
+ typeguard-typecheck-fail-callback = "mypackage:failcallback"
+ typeguard-forward-ref-policy = "ERROR"
+ typeguard-collection-check-strategy = "ALL_ITEMS"
+ '''
+ )
+ pytester.makepyfile(
+ mypackage=(
+ dedent(
+ """
+ def failcallback():
+ pass
+ """
+ )
+ )
+ )
+
+ pytester.plugins = ["typeguard"]
+ pytester.syspathinsert()
+ pytestconfig = pytester.parseconfigure()
+ assert pytestconfig.getini("typeguard-packages") == ["mypackage", "otherpackage"]
+ assert config.typecheck_fail_callback.__name__ == "failcallback"
+ assert config.debug_instrumentation is True
+ assert config.forward_ref_policy is ForwardRefPolicy.ERROR
+ assert config.collection_check_strategy is CollectionCheckStrategy.ALL_ITEMS
+
+
+def test_commandline_options(
+ pytester: Pytester, config: TypeCheckConfiguration
+) -> None:
+ pytester.makepyfile(
+ mypackage=(
+ dedent(
+ """
+ def failcallback():
+ pass
+ """
+ )
+ )
+ )
+
+ pytester.plugins = ["typeguard"]
+ pytester.syspathinsert()
+ pytestconfig = pytester.parseconfigure(
+ "--typeguard-packages=mypackage,otherpackage",
+ "--typeguard-typecheck-fail-callback=mypackage:failcallback",
+ "--typeguard-debug-instrumentation",
+ "--typeguard-forward-ref-policy=ERROR",
+ "--typeguard-collection-check-strategy=ALL_ITEMS",
+ )
+ assert pytestconfig.getoption("typeguard_packages") == "mypackage,otherpackage"
+ assert config.typecheck_fail_callback.__name__ == "failcallback"
+ assert config.debug_instrumentation is True
+ assert config.forward_ref_policy is ForwardRefPolicy.ERROR
+ assert config.collection_check_strategy is CollectionCheckStrategy.ALL_ITEMS
diff --git a/contrib/python/typeguard/tests/test_suppression.py b/contrib/python/typeguard/tests/test_suppression.py
new file mode 100644
index 00000000000..47c433c8a28
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_suppression.py
@@ -0,0 +1,68 @@
+import pytest
+
+from typeguard import TypeCheckError, check_type, suppress_type_checks, typechecked
+
+
+def test_contextmanager_typechecked():
+ @typechecked
+ def foo(x: str) -> None:
+ pass
+
+ with suppress_type_checks():
+ foo(1)
+
+
+def test_contextmanager_check_type():
+ with suppress_type_checks():
+ check_type(1, str)
+
+
+def test_contextmanager_nesting():
+ with suppress_type_checks(), suppress_type_checks():
+ check_type(1, str)
+
+ pytest.raises(TypeCheckError, check_type, 1, str)
+
+
+def test_contextmanager_exception():
+ """
+ Test that type check suppression stops even if an exception is raised within the
+ context manager block.
+
+ """
+ with pytest.raises(RuntimeError):
+ with suppress_type_checks():
+ raise RuntimeError
+
+ pytest.raises(TypeCheckError, check_type, 1, str)
+
+
+@suppress_type_checks
+def test_decorator_typechecked():
+ @typechecked
+ def foo(x: str) -> None:
+ pass
+
+ foo(1)
+
+
+@suppress_type_checks
+def test_decorator_check_type():
+ check_type(1, str)
+
+
+def test_decorator_exception():
+ """
+ Test that type check suppression stops even if an exception is raised from a
+ decorated function.
+
+ """
+
+ @suppress_type_checks
+ def foo():
+ raise RuntimeError
+
+ with pytest.raises(RuntimeError):
+ foo()
+
+ pytest.raises(TypeCheckError, check_type, 1, str)
diff --git a/contrib/python/typeguard/tests/test_transformer.py b/contrib/python/typeguard/tests/test_transformer.py
new file mode 100644
index 00000000000..3fb04627c9c
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_transformer.py
@@ -0,0 +1,1972 @@
+import sys
+from ast import parse, unparse
+from textwrap import dedent
+
+import pytest
+
+from typeguard._transformer import TypeguardTransformer
+
+
+def test_arguments_only() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x: int) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+
+ def foo(x: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, int)}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_return_only() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x) -> int:
+ return 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_return_type
+
+ def foo(x) -> int:
+ memo = TypeCheckMemo(globals(), locals())
+ return check_return_type('foo', 6, int, memo)
+ """
+ ).strip()
+ )
+
+
+class TestGenerator:
+ def test_yield(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from collections.abc import Generator
+ from typing import Any
+
+ def foo(x) -> Generator[int, Any, str]:
+ yield 2
+ yield 6
+ return 'test'
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_return_type, check_yield_type
+ from collections.abc import Generator
+ from typing import Any
+
+ def foo(x) -> Generator[int, Any, str]:
+ memo = TypeCheckMemo(globals(), locals())
+ yield check_yield_type('foo', 2, int, memo)
+ yield check_yield_type('foo', 6, int, memo)
+ return check_return_type('foo', 'test', str, memo)
+ """
+ ).strip()
+ )
+
+ def test_no_return_type_check(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from collections.abc import Generator
+
+ def foo(x) -> Generator[int, None, None]:
+ yield 2
+ yield 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_send_type, check_yield_type
+ from collections.abc import Generator
+
+ def foo(x) -> Generator[int, None, None]:
+ memo = TypeCheckMemo(globals(), locals())
+ check_send_type('foo', (yield check_yield_type('foo', 2, int, \
+memo)), None, memo)
+ check_send_type('foo', (yield check_yield_type('foo', 6, int, \
+memo)), None, memo)
+ """
+ ).strip()
+ )
+
+ def test_no_send_type_check(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+ from collections.abc import Generator
+
+ def foo(x) -> Generator[int, Any, Any]:
+ yield 2
+ yield 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_yield_type
+ from typing import Any
+ from collections.abc import Generator
+
+ def foo(x) -> Generator[int, Any, Any]:
+ memo = TypeCheckMemo(globals(), locals())
+ yield check_yield_type('foo', 2, int, memo)
+ yield check_yield_type('foo', 6, int, memo)
+ """
+ ).strip()
+ )
+
+
+class TestAsyncGenerator:
+ def test_full(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from collections.abc import AsyncGenerator
+
+ async def foo(x) -> AsyncGenerator[int, None]:
+ yield 2
+ yield 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_send_type, check_yield_type
+ from collections.abc import AsyncGenerator
+
+ async def foo(x) -> AsyncGenerator[int, None]:
+ memo = TypeCheckMemo(globals(), locals())
+ check_send_type('foo', (yield check_yield_type('foo', 2, int, \
+memo)), None, memo)
+ check_send_type('foo', (yield check_yield_type('foo', 6, int, \
+memo)), None, memo)
+ """
+ ).strip()
+ )
+
+ def test_no_yield_type_check(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+ from collections.abc import AsyncGenerator
+
+ async def foo() -> AsyncGenerator[Any, None]:
+ yield 2
+ yield 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_send_type
+ from typing import Any
+ from collections.abc import AsyncGenerator
+
+ async def foo() -> AsyncGenerator[Any, None]:
+ memo = TypeCheckMemo(globals(), locals())
+ check_send_type('foo', (yield 2), None, memo)
+ check_send_type('foo', (yield 6), None, memo)
+ """
+ ).strip()
+ )
+
+ def test_no_send_type_check(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+ from collections.abc import AsyncGenerator
+
+ async def foo() -> AsyncGenerator[int, Any]:
+ yield 2
+ yield 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_yield_type
+ from typing import Any
+ from collections.abc import AsyncGenerator
+
+ async def foo() -> AsyncGenerator[int, Any]:
+ memo = TypeCheckMemo(globals(), locals())
+ yield check_yield_type('foo', 2, int, memo)
+ yield check_yield_type('foo', 6, int, memo)
+ """
+ ).strip()
+ )
+
+
+def test_pass_only() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def foo(x) -> None:
+ pass
+ """
+ ).strip()
+ )
+
+
+ "import_line, decorator",
+ [
+ pytest.param("from typing import no_type_check", "@no_type_check"),
+ pytest.param("from typeguard import typeguard_ignore", "@typeguard_ignore"),
+ pytest.param("import typing", "@typing.no_type_check"),
+ pytest.param("import typeguard", "@typeguard.typeguard_ignore"),
+ ],
+)
+def test_no_type_check_decorator(import_line: str, decorator: str) -> None:
+ node = parse(
+ dedent(
+ f"""
+ {import_line}
+
+ {decorator}
+ def foo(x: int) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ {import_line}
+
+ {decorator}
+ def foo(x: int) -> int:
+ return x
+ """
+ ).strip()
+ )
+
+
+ "import_line, annotation",
+ [
+ pytest.param("from typing import Any", "Any"),
+ pytest.param("from typing import Any as AlterAny", "AlterAny"),
+ pytest.param("from typing_extensions import Any", "Any"),
+ pytest.param("from typing_extensions import Any as AlterAny", "AlterAny"),
+ pytest.param("import typing", "typing.Any"),
+ pytest.param("import typing as typing_alter", "typing_alter.Any"),
+ pytest.param("import typing_extensions as typing_alter", "typing_alter.Any"),
+ ],
+)
+def test_any_only(import_line: str, annotation: str) -> None:
+ node = parse(
+ dedent(
+ f"""
+ {import_line}
+
+ def foo(x, y: {annotation}) -> {annotation}:
+ return 1
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ {import_line}
+
+ def foo(x, y: {annotation}) -> {annotation}:
+ return 1
+ """
+ ).strip()
+ )
+
+
+def test_any_in_union() -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any, Union
+
+ def foo(x, y: Union[Any, None]) -> Union[Any, None]:
+ return 1
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Any, Union
+
+ def foo(x, y: Union[Any, None]) -> Union[Any, None]:
+ return 1
+ """
+ ).strip()
+ )
+
+
+def test_any_in_pep_604_union() -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+
+ def foo(x, y: Any | None) -> Any | None:
+ return 1
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Any
+
+ def foo(x, y: Any | None) -> Any | None:
+ return 1
+ """
+ ).strip()
+ )
+
+
+def test_any_in_nested_dict() -> None:
+ # Regression test for #373
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+
+ def foo(x: dict[str, dict[str, Any]]) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ from typing import Any
+
+ def foo(x: dict[str, dict[str, Any]]) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, dict[str, dict[str, Any]])}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_avoid_global_names() -> None:
+ node = parse(
+ dedent(
+ """
+ memo = TypeCheckMemo = check_argument_types = check_return_type = None
+
+ def func1(x: int) -> int:
+ dummy = (memo,)
+ return x
+
+ def func2(x: int) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo as TypeCheckMemo_
+ from typeguard._functions import \
+check_argument_types as check_argument_types_, check_return_type as check_return_type_
+ memo = TypeCheckMemo = check_argument_types = check_return_type = None
+
+ def func1(x: int) -> int:
+ memo_ = TypeCheckMemo_(globals(), locals())
+ check_argument_types_('func1', {'x': (x, int)}, memo_)
+ dummy = (memo,)
+ return check_return_type_('func1', x, int, memo_)
+
+ def func2(x: int) -> int:
+ memo_ = TypeCheckMemo_(globals(), locals())
+ check_argument_types_('func2', {'x': (x, int)}, memo_)
+ return check_return_type_('func2', x, int, memo_)
+ """
+ ).strip()
+ )
+
+
+def test_avoid_local_names() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x: int) -> int:
+ memo = TypeCheckMemo = check_argument_types = check_return_type = None
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def foo(x: int) -> int:
+ from typeguard import TypeCheckMemo as TypeCheckMemo_
+ from typeguard._functions import \
+check_argument_types as check_argument_types_, check_return_type as check_return_type_
+ memo_ = TypeCheckMemo_(globals(), locals())
+ check_argument_types_('foo', {'x': (x, int)}, memo_)
+ memo = TypeCheckMemo = check_argument_types = check_return_type = None
+ return check_return_type_('foo', x, int, memo_)
+ """
+ ).strip()
+ )
+
+
+def test_avoid_nonlocal_names() -> None:
+ node = parse(
+ dedent(
+ """
+ def outer():
+ memo = TypeCheckMemo = check_argument_types = check_return_type = None
+
+ def foo(x: int) -> int:
+ return x
+
+ return foo
+ """
+ )
+ )
+ TypeguardTransformer(["outer", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def outer():
+ memo = TypeCheckMemo = check_argument_types = check_return_type = None
+
+ def foo(x: int) -> int:
+ from typeguard import TypeCheckMemo as TypeCheckMemo_
+ from typeguard._functions import \
+check_argument_types as check_argument_types_, check_return_type as check_return_type_
+ memo_ = TypeCheckMemo_(globals(), locals())
+ check_argument_types_('outer.<locals>.foo', {'x': (x, int)}, memo_)
+ return check_return_type_('outer.<locals>.foo', x, int, memo_)
+ return foo
+ """
+ ).strip()
+ )
+
+
+def test_method() -> None:
+ node = parse(
+ dedent(
+ """
+ class Foo:
+ def foo(self, x: int) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["Foo", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ class Foo:
+
+ def foo(self, x: int) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals(), self_type=self.__class__)
+ check_argument_types('Foo.foo', {'x': (x, int)}, memo)
+ return check_return_type('Foo.foo', x, int, memo)
+ """
+ ).strip()
+ )
+
+
+def test_method_posonlyargs() -> None:
+ node = parse(
+ dedent(
+ """
+ class Foo:
+ def foo(self, x: int, /, y: str) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["Foo", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ class Foo:
+
+ def foo(self, x: int, /, y: str) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals(), self_type=self.__class__)
+ check_argument_types('Foo.foo', {'x': (x, int), 'y': (y, str)}, memo)
+ return check_return_type('Foo.foo', x, int, memo)
+ """
+ ).strip()
+ )
+
+
+def test_classmethod() -> None:
+ node = parse(
+ dedent(
+ """
+ class Foo:
+ @classmethod
+ def foo(cls, x: int) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["Foo", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ class Foo:
+
+ @classmethod
+ def foo(cls, x: int) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals(), self_type=cls)
+ check_argument_types('Foo.foo', {'x': (x, int)}, memo)
+ return check_return_type('Foo.foo', x, int, memo)
+ """
+ ).strip()
+ )
+
+
+def test_classmethod_posonlyargs() -> None:
+ node = parse(
+ dedent(
+ """
+ class Foo:
+ @classmethod
+ def foo(cls, x: int, /, y: str) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["Foo", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ class Foo:
+
+ @classmethod
+ def foo(cls, x: int, /, y: str) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals(), self_type=cls)
+ check_argument_types('Foo.foo', {'x': (x, int), 'y': (y, str)}, \
+memo)
+ return check_return_type('Foo.foo', x, int, memo)
+ """
+ ).strip()
+ )
+
+
+def test_staticmethod() -> None:
+ node = parse(
+ dedent(
+ """
+ class Foo:
+ @staticmethod
+ def foo(x: int) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["Foo", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ class Foo:
+
+ @staticmethod
+ def foo(x: int) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('Foo.foo', {'x': (x, int)}, memo)
+ return check_return_type('Foo.foo', x, int, memo)
+ """
+ ).strip()
+ )
+
+
+def test_new_with_self() -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Self
+
+ class Foo:
+ def __new__(cls) -> Self:
+ return super().__new__(cls)
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_return_type
+ from typing import Self
+
+ class Foo:
+
+ def __new__(cls) -> Self:
+ Foo = cls
+ memo = TypeCheckMemo(globals(), locals(), self_type=cls)
+ return check_return_type('Foo.__new__', super().__new__(cls), \
+Self, memo)
+ """
+ ).strip()
+ )
+
+
+def test_new_with_explicit_class_name() -> None:
+ # Regression test for #398
+ node = parse(
+ dedent(
+ """
+ class A:
+
+ def __new__(cls) -> 'A':
+ return object.__new__(cls)
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_return_type
+
+ class A:
+
+ def __new__(cls) -> 'A':
+ A = cls
+ memo = TypeCheckMemo(globals(), locals(), self_type=cls)
+ return check_return_type('A.__new__', object.__new__(cls), A, memo)
+ """
+ ).strip()
+ )
+
+
+def test_local_function() -> None:
+ node = parse(
+ dedent(
+ """
+ def wrapper():
+ def foo(x: int) -> int:
+ return x
+
+ def foo2(x: int) -> int:
+ return x
+
+ return foo
+ """
+ )
+ )
+ TypeguardTransformer(["wrapper", "foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def wrapper():
+
+ def foo(x: int) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('wrapper.<locals>.foo', {'x': (x, int)}, memo)
+ return check_return_type('wrapper.<locals>.foo', x, int, memo)
+
+ def foo2(x: int) -> int:
+ return x
+ return foo
+ """
+ ).strip()
+ )
+
+
+def test_function_local_class_method() -> None:
+ node = parse(
+ dedent(
+ """
+ def wrapper():
+
+ class Foo:
+
+ class Bar:
+
+ def method(self, x: int) -> int:
+ return x
+
+ def method2(self, x: int) -> int:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer(["wrapper", "Foo", "Bar", "method"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def wrapper():
+
+ class Foo:
+
+ class Bar:
+
+ def method(self, x: int) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_return_type
+ memo = TypeCheckMemo(globals(), locals(), \
+self_type=self.__class__)
+ check_argument_types('wrapper.<locals>.Foo.Bar.method', \
+{'x': (x, int)}, memo)
+ return check_return_type(\
+'wrapper.<locals>.Foo.Bar.method', x, int, memo)
+
+ def method2(self, x: int) -> int:
+ return x
+ """
+ ).strip()
+ )
+
+
+def test_keyword_only_argument() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(*, x: int) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+
+ def foo(*, x: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, int)}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_positional_only_argument() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x: int, /) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+
+ def foo(x: int, /) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, int)}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_variable_positional_argument() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(*args: int) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+
+ def foo(*args: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'args': (args, tuple[int, ...])}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_variable_keyword_argument() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(**kwargs: int) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+
+ def foo(**kwargs: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'kwargs': (kwargs, dict[str, int])}, memo)
+ """
+ ).strip()
+ )
+
+
+class TestTypecheckingImport:
+ """
+ Test that annotations imported conditionally on typing.TYPE_CHECKING are not used in
+ run-time checks.
+ """
+
+ def test_direct_references(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import TYPE_CHECKING
+ if TYPE_CHECKING:
+ import typing
+ from typing import Hashable, Sequence
+
+ def foo(x: Hashable, y: typing.Collection, *args: Hashable, \
+**kwargs: typing.Collection) -> Sequence:
+ bar: typing.Collection
+ baz: Hashable = 1
+ return (1, 2)
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import TYPE_CHECKING
+ if TYPE_CHECKING:
+ import typing
+ from typing import Hashable, Sequence
+
+ def foo(x: Hashable, y: typing.Collection, *args: Hashable, \
+**kwargs: typing.Collection) -> Sequence:
+ bar: typing.Collection
+ baz: Hashable = 1
+ return (1, 2)
+ """
+ ).strip()
+ )
+
+ def test_collection_parameter(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import TYPE_CHECKING
+ if TYPE_CHECKING:
+ from nonexistent import FooBar
+
+ def foo(x: list[FooBar]) -> list[FooBar]:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, check_return_type
+ from typing import TYPE_CHECKING
+ if TYPE_CHECKING:
+ from nonexistent import FooBar
+
+ def foo(x: list[FooBar]) -> list[FooBar]:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, list)}, memo)
+ return check_return_type('foo', x, list, memo)
+ """
+ ).strip()
+ )
+
+ def test_variable_annotations(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any, TYPE_CHECKING
+ if TYPE_CHECKING:
+ from nonexistent import FooBar
+
+ def foo(x: Any) -> None:
+ y: FooBar = x
+ z: list[FooBar] = [y]
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from typing import Any, TYPE_CHECKING
+ if TYPE_CHECKING:
+ from nonexistent import FooBar
+
+ def foo(x: Any) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ y: FooBar = x
+ z: list[FooBar] = check_variable_assignment([y], [[('z', list)]], \
+memo)
+ """
+ ).strip()
+ )
+
+ def test_generator_function(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any, TYPE_CHECKING
+ from collections.abc import Generator
+ if TYPE_CHECKING:
+ import typing
+ from typing import Hashable, Sequence
+
+ def foo(x: Hashable, y: typing.Collection) -> Generator[Hashable, \
+typing.Collection, Sequence]:
+ yield 'foo'
+ return (1, 2)
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Any, TYPE_CHECKING
+ from collections.abc import Generator
+ if TYPE_CHECKING:
+ import typing
+ from typing import Hashable, Sequence
+
+ def foo(x: Hashable, y: typing.Collection) -> Generator[Hashable, \
+typing.Collection, Sequence]:
+ yield 'foo'
+ return (1, 2)
+ """
+ ).strip()
+ )
+
+ def test_optional(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any, Optional, TYPE_CHECKING
+ if TYPE_CHECKING:
+ from typing import Hashable
+
+ def foo(x: Optional[Hashable]) -> Optional[Hashable]:
+ return x
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Any, Optional, TYPE_CHECKING
+ if TYPE_CHECKING:
+ from typing import Hashable
+
+ def foo(x: Optional[Hashable]) -> Optional[Hashable]:
+ return x
+ """
+ ).strip()
+ )
+
+ def test_optional_nested(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any, List, Optional
+
+ def foo(x: List[Optional[int]]) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ from typing import Any, List, Optional
+
+ def foo(x: List[Optional[int]]) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, List[Optional[int]])}, memo)
+ """
+ ).strip()
+ )
+
+ def test_subscript_within_union(self) -> None:
+ # Regression test for #397
+ node = parse(
+ dedent(
+ """
+ from typing import Any, Iterable, Union, TYPE_CHECKING
+ if TYPE_CHECKING:
+ from typing import Hashable
+
+ def foo(x: Union[Iterable[Hashable], str]) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ from typing import Any, Iterable, Union, TYPE_CHECKING
+ if TYPE_CHECKING:
+ from typing import Hashable
+
+ def foo(x: Union[Iterable[Hashable], str]) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, Union[Iterable, str])}, memo)
+ """
+ ).strip()
+ )
+
+ def test_pep604_union(self) -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import TYPE_CHECKING
+ if TYPE_CHECKING:
+ from typing import Hashable
+
+ def foo(x: Hashable | str) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import TYPE_CHECKING
+ if TYPE_CHECKING:
+ from typing import Hashable
+
+ def foo(x: Hashable | str) -> None:
+ pass
+ """
+ ).strip()
+ )
+
+
+class TestAssign:
+ def test_annotated_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> None:
+ x: int = otherfunc()
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int = check_variable_assignment(otherfunc(), [[('x', int)]], \
+memo)
+ """
+ ).strip()
+ )
+
+ def test_varargs_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(*args: int) -> None:
+ args = (5,)
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_variable_assignment
+
+ def foo(*args: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'args': (args, \
+tuple[int, ...])}, memo)
+ args = check_variable_assignment((5,), \
+[[('args', tuple[int, ...])]], memo)
+ """
+ ).strip()
+ )
+
+ def test_kwargs_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(**kwargs: int) -> None:
+ kwargs = {'a': 5}
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_variable_assignment
+
+ def foo(**kwargs: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'kwargs': (kwargs, \
+dict[str, int])}, memo)
+ kwargs = check_variable_assignment({'a': 5}, \
+[[('kwargs', dict[str, int])]], memo)
+ """
+ ).strip()
+ )
+
+ @pytest.mark.skipif(sys.version_info >= (3, 10), reason="Requires Python < 3.10")
+ def test_pep604_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ Union = None
+
+ def foo() -> None:
+ x: int | str = otherfunc()
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from typing import Union as Union_
+ Union = None
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int | str = check_variable_assignment(otherfunc(), \
+[[('x', Union_[int, str])]], memo)
+ """
+ ).strip()
+ )
+
+ def test_multi_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> None:
+ x: int
+ z: bytes
+ x, y, z = otherfunc()
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ target = "x, y, z" if sys.version_info >= (3, 11) else "(x, y, z)"
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from typing import Any
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int
+ z: bytes
+ {target} = check_variable_assignment(otherfunc(), \
+[[('x', int), ('y', Any), ('z', bytes)]], memo)
+ """
+ ).strip()
+ )
+
+ def test_star_multi_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> None:
+ x: int
+ z: bytes
+ x, *y, z = otherfunc()
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ target = "x, *y, z" if sys.version_info >= (3, 11) else "(x, *y, z)"
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from typing import Any
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int
+ z: bytes
+ {target} = check_variable_assignment(otherfunc(), \
+[[('x', int), ('*y', Any), ('z', bytes)]], memo)
+ """
+ ).strip()
+ )
+
+ def test_complex_multi_assign(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> None:
+ x: int
+ z: bytes
+ all = x, *y, z = otherfunc()
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ target = "x, *y, z" if sys.version_info >= (3, 11) else "(x, *y, z)"
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from typing import Any
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int
+ z: bytes
+ all = {target} = check_variable_assignment(otherfunc(), \
+[[('all', Any)], [('x', int), ('*y', Any), ('z', bytes)]], memo)
+ """
+ ).strip()
+ )
+
+ def test_unpacking_assign_to_self(self) -> None:
+ node = parse(
+ dedent(
+ """
+ class Foo:
+
+ def foo(self) -> None:
+ x: int
+ (x, self.y) = 1, 'test'
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ target = "x, self.y" if sys.version_info >= (3, 11) else "(x, self.y)"
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from typing import Any
+
+ class Foo:
+
+ def foo(self) -> None:
+ memo = TypeCheckMemo(globals(), locals(), \
+self_type=self.__class__)
+ x: int
+ {target} = check_variable_assignment((1, 'test'), \
+[[('x', int), ('self.y', Any)]], memo)
+ """
+ ).strip()
+ )
+
+ def test_assignment_annotated_argument(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x: int) -> None:
+ x = 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_variable_assignment
+
+ def foo(x: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, int)}, memo)
+ x = check_variable_assignment(6, [[('x', int)]], memo)
+ """
+ ).strip()
+ )
+
+ def test_assignment_expr(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> None:
+ x: int
+ if x := otherfunc():
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int
+ if (x := check_variable_assignment(otherfunc(), [[('x', int)]], \
+memo)):
+ pass
+ """
+ ).strip()
+ )
+
+ def test_assignment_expr_annotated_argument(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x: int) -> None:
+ if x := otherfunc():
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_variable_assignment
+
+ def foo(x: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, int)}, memo)
+ if (x := check_variable_assignment(otherfunc(), [[('x', int)]], memo)):
+ pass
+ """
+ ).strip()
+ )
+
+ @pytest.mark.parametrize(
+ "operator, function",
+ [
+ pytest.param("+=", "iadd", id="add"),
+ pytest.param("-=", "isub", id="subtract"),
+ pytest.param("*=", "imul", id="multiply"),
+ pytest.param("@=", "imatmul", id="matrix_multiply"),
+ pytest.param("/=", "itruediv", id="div"),
+ pytest.param("//=", "ifloordiv", id="floordiv"),
+ pytest.param("**=", "ipow", id="power"),
+ pytest.param("<<=", "ilshift", id="left_bitshift"),
+ pytest.param(">>=", "irshift", id="right_bitshift"),
+ pytest.param("&=", "iand", id="and"),
+ pytest.param("^=", "ixor", id="xor"),
+ pytest.param("|=", "ior", id="or"),
+ ],
+ )
+ def test_augmented_assignment(self, operator: str, function: str) -> None:
+ node = parse(
+ dedent(
+ f"""
+ def foo() -> None:
+ x: int
+ x {operator} 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ f"""
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_variable_assignment
+ from operator import {function}
+
+ def foo() -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ x: int
+ x = check_variable_assignment({function}(x, 6), [[('x', int)]], \
+memo)
+ """
+ ).strip()
+ )
+
+ def test_augmented_assignment_non_annotated(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> None:
+ x = 1
+ x += 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def foo() -> None:
+ x = 1
+ x += 6
+ """
+ ).strip()
+ )
+
+ def test_augmented_assignment_annotated_argument(self) -> None:
+ node = parse(
+ dedent(
+ """
+ def foo(x: int) -> None:
+ x += 6
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, \
+check_variable_assignment
+ from operator import iadd
+
+ def foo(x: int) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, int)}, memo)
+ x = check_variable_assignment(iadd(x, 6), [[('x', int)]], memo)
+ """
+ ).strip()
+ )
+
+
+def test_argname_typename_conflicts() -> None:
+ node = parse(
+ dedent(
+ """
+ from collections.abc import Generator
+
+ def foo(x: kwargs, /, y: args, *args: x, baz: x, **kwargs: y) -> \
+Generator[args, x, kwargs]:
+ yield y
+ return x
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from collections.abc import Generator
+
+ def foo(x: kwargs, /, y: args, *args: x, baz: x, **kwargs: y) -> \
+Generator[args, x, kwargs]:
+ yield y
+ return x
+ """
+ ).strip()
+ )
+
+
+def test_local_assignment_typename_conflicts() -> None:
+ node = parse(
+ dedent(
+ """
+ def foo() -> int:
+ int = 6
+ return int
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ def foo() -> int:
+ int = 6
+ return int
+ """
+ ).strip()
+ )
+
+
+def test_local_ann_assignment_typename_conflicts() -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+
+ def foo() -> int:
+ int: Any = 6
+ return int
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Any
+
+ def foo() -> int:
+ int: Any = 6
+ return int
+ """
+ ).strip()
+ )
+
+
+def test_local_named_expr_typename_conflicts() -> None:
+ node = parse(
+ dedent(
+ """
+ from typing import Any
+
+ def foo() -> int:
+ if (int := 6):
+ pass
+ return int
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Any
+
+ def foo() -> int:
+ if (int := 6):
+ pass
+ return int
+ """
+ ).strip()
+ )
+
+
+def test_dont_leave_empty_ast_container_nodes() -> None:
+ # Regression test for #352
+ node = parse(
+ dedent(
+ """
+ if True:
+
+ class A:
+ ...
+
+ def func():
+ ...
+
+ def foo(x: str) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ if True:
+ pass
+
+ def foo(x: str) -> None:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, str)}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_dont_leave_empty_ast_container_nodes_2() -> None:
+ # Regression test for #352
+ node = parse(
+ dedent(
+ """
+ try:
+
+ class A:
+ ...
+
+ def func():
+ ...
+
+ except:
+
+ class A:
+ ...
+
+ def func():
+ ...
+
+
+ def foo(x: str) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ try:
+ pass
+ except:
+ pass
+
+ def foo(x: str) -> None:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, str)}, memo)
+ """
+ ).strip()
+ )
+
+
+class TestTypeShadowedByArgument:
+ def test_typing_union(self) -> None:
+ # Regression test for #394
+ node = parse(
+ dedent(
+ """
+ from __future__ import annotations
+ from typing import Union
+
+ class A:
+ ...
+
+ def foo(A: Union[A, None]) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from __future__ import annotations
+ from typing import Union
+
+ def foo(A: Union[A, None]) -> None:
+ pass
+ """
+ ).strip()
+ )
+
+ def test_pep604_union(self) -> None:
+ # Regression test for #395
+ node = parse(
+ dedent(
+ """
+ from __future__ import annotations
+
+ class A:
+ ...
+
+ def foo(A: A | None) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from __future__ import annotations
+
+ def foo(A: A | None) -> None:
+ pass
+ """
+ ).strip()
+ )
+
+
+def test_dont_parse_annotated_2nd_arg() -> None:
+ # Regression test for #352
+ node = parse(
+ dedent(
+ """
+ from typing import Annotated
+
+ def foo(x: Annotated[str, 'foo bar']) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typing import Annotated
+
+ def foo(x: Annotated[str, 'foo bar']) -> None:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, Annotated[str, 'foo bar'])}, memo)
+ """
+ ).strip()
+ )
+
+
+def test_respect_docstring() -> None:
+ # Regression test for #359
+ node = parse(
+ dedent(
+ '''
+ def foo() -> int:
+ """This is a docstring."""
+ return 1
+ '''
+ )
+ )
+ TypeguardTransformer(["foo"]).visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ '''
+ def foo() -> int:
+ """This is a docstring."""
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_return_type
+ memo = TypeCheckMemo(globals(), locals())
+ return check_return_type('foo', 1, int, memo)
+ '''
+ ).strip()
+ )
+
+
+def test_respect_future_import() -> None:
+ # Regression test for #385
+ node = parse(
+ dedent(
+ '''
+ """module docstring"""
+ from __future__ import annotations
+
+ def foo() -> int:
+ return 1
+ '''
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ '''
+ """module docstring"""
+ from __future__ import annotations
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_return_type
+
+ def foo() -> int:
+ memo = TypeCheckMemo(globals(), locals())
+ return check_return_type('foo', 1, int, memo)
+ '''
+ ).strip()
+ )
+
+
+def test_literal() -> None:
+ # Regression test for #399
+ node = parse(
+ dedent(
+ """
+ from typing import Literal
+
+ def foo(x: Literal['a', 'b']) -> None:
+ pass
+ """
+ )
+ )
+ TypeguardTransformer().visit(node)
+ assert (
+ unparse(node)
+ == dedent(
+ """
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types
+ from typing import Literal
+
+ def foo(x: Literal['a', 'b']) -> None:
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('foo', {'x': (x, Literal['a', 'b'])}, memo)
+ """
+ ).strip()
+ )
diff --git a/contrib/python/typeguard/tests/test_typechecked.py b/contrib/python/typeguard/tests/test_typechecked.py
new file mode 100644
index 00000000000..770b68bcff6
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_typechecked.py
@@ -0,0 +1,711 @@
+import asyncio
+import subprocess
+import sys
+from contextlib import contextmanager
+from pathlib import Path
+from textwrap import dedent
+from typing import (
+ Any,
+ AsyncGenerator,
+ AsyncIterable,
+ AsyncIterator,
+ Dict,
+ Generator,
+ Iterable,
+ Iterator,
+ List,
+)
+from unittest.mock import Mock
+
+import pytest
+
+from typeguard import TypeCheckError, typechecked
+
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ from typing_extensions import Self
+
+
+class TestCoroutineFunction:
+ def test_success(self):
+ @typechecked
+ async def foo(a: int) -> str:
+ return "test"
+
+ assert asyncio.run(foo(1)) == "test"
+
+ def test_bad_arg(self):
+ @typechecked
+ async def foo(a: int) -> str:
+ return "test"
+
+ with pytest.raises(
+ TypeCheckError, match=r'argument "a" \(str\) is not an instance of int'
+ ):
+ asyncio.run(foo("foo"))
+
+ def test_bad_return(self):
+ @typechecked
+ async def foo(a: int) -> str:
+ return 1
+
+ with pytest.raises(
+ TypeCheckError, match=r"return value \(int\) is not an instance of str"
+ ):
+ asyncio.run(foo(1))
+
+ def test_any_return(self):
+ @typechecked
+ async def foo() -> Any:
+ return 1
+
+ assert asyncio.run(foo()) == 1
+
+
+class TestGenerator:
+ def test_generator_bare(self):
+ @typechecked
+ def genfunc() -> Generator:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ gen = genfunc()
+ with pytest.raises(StopIteration) as exc:
+ value = next(gen)
+ while True:
+ value = gen.send(str(value))
+ assert isinstance(value, int)
+
+ assert exc.value.value == ["2", "3", "4"]
+
+ def test_generator_annotated(self):
+ @typechecked
+ def genfunc() -> Generator[int, str, List[str]]:
+ val1 = yield 2
+ val2 = yield 3
+ val3 = yield 4
+ return [val1, val2, val3]
+
+ gen = genfunc()
+ with pytest.raises(StopIteration) as exc:
+ value = next(gen)
+ while True:
+ value = gen.send(str(value))
+ assert isinstance(value, int)
+
+ assert exc.value.value == ["2", "3", "4"]
+
+ def test_generator_iterable_bare(self):
+ @typechecked
+ def genfunc() -> Iterable:
+ yield 2
+ yield 3
+ yield 4
+
+ values = list(genfunc())
+ assert values == [2, 3, 4]
+
+ def test_generator_iterable_annotated(self):
+ @typechecked
+ def genfunc() -> Iterable[int]:
+ yield 2
+ yield 3
+ yield 4
+
+ values = list(genfunc())
+ assert values == [2, 3, 4]
+
+ def test_generator_iterator_bare(self):
+ @typechecked
+ def genfunc() -> Iterator:
+ yield 2
+ yield 3
+ yield 4
+
+ values = list(genfunc())
+ assert values == [2, 3, 4]
+
+ def test_generator_iterator_annotated(self):
+ @typechecked
+ def genfunc() -> Iterator[int]:
+ yield 2
+ yield 3
+ yield 4
+
+ values = list(genfunc())
+ assert values == [2, 3, 4]
+
+ def test_bad_yield_as_generator(self):
+ @typechecked
+ def genfunc() -> Generator[int, str, None]:
+ yield "foo"
+
+ gen = genfunc()
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen)
+
+ exc.match(r"the yielded value \(str\) is not an instance of int")
+
+ def test_bad_yield_as_iterable(self):
+ @typechecked
+ def genfunc() -> Iterable[int]:
+ yield "foo"
+
+ gen = genfunc()
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen)
+
+ exc.match(r"the yielded value \(str\) is not an instance of int")
+
+ def test_bad_yield_as_iterator(self):
+ @typechecked
+ def genfunc() -> Iterator[int]:
+ yield "foo"
+
+ gen = genfunc()
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen)
+
+ exc.match(r"the yielded value \(str\) is not an instance of int")
+
+ def test_generator_bad_send(self):
+ @typechecked
+ def genfunc() -> Generator[int, str, None]:
+ yield 1
+ yield 2
+
+ pass
+ gen = genfunc()
+ next(gen)
+ with pytest.raises(TypeCheckError) as exc:
+ gen.send(2)
+
+ exc.match(r"value sent to generator \(int\) is not an instance of str")
+
+ def test_generator_bad_return(self):
+ @typechecked
+ def genfunc() -> Generator[int, str, str]:
+ yield 1
+ return 6
+
+ gen = genfunc()
+ next(gen)
+ with pytest.raises(TypeCheckError) as exc:
+ gen.send("foo")
+
+ exc.match(r"return value \(int\) is not an instance of str")
+
+ def test_return_generator(self):
+ @typechecked
+ def genfunc() -> Generator[int, None, None]:
+ yield 1
+
+ @typechecked
+ def foo() -> Generator[int, None, None]:
+ return genfunc()
+
+ foo()
+
+
+class TestAsyncGenerator:
+ def test_async_generator_bare(self):
+ @typechecked
+ async def genfunc() -> AsyncGenerator:
+ values.append((yield 2))
+ values.append((yield 3))
+ values.append((yield 4))
+
+ async def run_generator():
+ gen = genfunc()
+ value = await gen.asend(None)
+ with pytest.raises(StopAsyncIteration):
+ while True:
+ value = await gen.asend(str(value))
+ assert isinstance(value, int)
+
+ values = []
+ asyncio.run(run_generator())
+ assert values == ["2", "3", "4"]
+
+ def test_async_generator_annotated(self):
+ @typechecked
+ async def genfunc() -> AsyncGenerator[int, str]:
+ values.append((yield 2))
+ values.append((yield 3))
+ values.append((yield 4))
+
+ async def run_generator():
+ gen = genfunc()
+ value = await gen.asend(None)
+ with pytest.raises(StopAsyncIteration):
+ while True:
+ value = await gen.asend(str(value))
+ assert isinstance(value, int)
+
+ values = []
+ asyncio.run(run_generator())
+ assert values == ["2", "3", "4"]
+
+ def test_generator_iterable_bare(self):
+ @typechecked
+ async def genfunc() -> AsyncIterable:
+ yield 2
+ yield 3
+ yield 4
+
+ async def run_generator():
+ return [value async for value in genfunc()]
+
+ assert asyncio.run(run_generator()) == [2, 3, 4]
+
+ def test_generator_iterable_annotated(self):
+ @typechecked
+ async def genfunc() -> AsyncIterable[int]:
+ yield 2
+ yield 3
+ yield 4
+
+ async def run_generator():
+ return [value async for value in genfunc()]
+
+ assert asyncio.run(run_generator()) == [2, 3, 4]
+
+ def test_generator_iterator_bare(self):
+ @typechecked
+ async def genfunc() -> AsyncIterator:
+ yield 2
+ yield 3
+ yield 4
+
+ async def run_generator():
+ return [value async for value in genfunc()]
+
+ assert asyncio.run(run_generator()) == [2, 3, 4]
+
+ def test_generator_iterator_annotated(self):
+ @typechecked
+ async def genfunc() -> AsyncIterator[int]:
+ yield 2
+ yield 3
+ yield 4
+
+ async def run_generator():
+ return [value async for value in genfunc()]
+
+ assert asyncio.run(run_generator()) == [2, 3, 4]
+
+ def test_async_bad_yield_as_generator(self):
+ @typechecked
+ async def genfunc() -> AsyncGenerator[int, str]:
+ yield "foo"
+
+ gen = genfunc()
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen.__anext__().__await__())
+
+ exc.match(r"the yielded value \(str\) is not an instance of int")
+
+ def test_async_bad_yield_as_iterable(self):
+ @typechecked
+ async def genfunc() -> AsyncIterable[int]:
+ yield "foo"
+
+ gen = genfunc()
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen.__anext__().__await__())
+
+ exc.match(r"the yielded value \(str\) is not an instance of int")
+
+ def test_async_bad_yield_as_iterator(self):
+ @typechecked
+ async def genfunc() -> AsyncIterator[int]:
+ yield "foo"
+
+ gen = genfunc()
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen.__anext__().__await__())
+
+ exc.match(r"the yielded value \(str\) is not an instance of int")
+
+ def test_async_generator_bad_send(self):
+ @typechecked
+ async def genfunc() -> AsyncGenerator[int, str]:
+ yield 1
+ yield 2
+
+ gen = genfunc()
+ pytest.raises(StopIteration, next, gen.__anext__().__await__())
+ with pytest.raises(TypeCheckError) as exc:
+ next(gen.asend(2).__await__())
+
+ exc.match(r"the value sent to generator \(int\) is not an instance of str")
+
+ def test_return_async_generator(self):
+ @typechecked
+ async def genfunc() -> AsyncGenerator[int, None]:
+ yield 1
+
+ @typechecked
+ def foo() -> AsyncGenerator[int, None]:
+ return genfunc()
+
+ foo()
+
+ def test_async_generator_iterate(self):
+ @typechecked
+ async def asyncgenfunc() -> AsyncGenerator[int, None]:
+ yield 1
+
+ asyncgen = asyncgenfunc()
+ aiterator = asyncgen.__aiter__()
+ exc = pytest.raises(StopIteration, aiterator.__anext__().send, None)
+ assert exc.value.value == 1
+
+
+class TestSelf:
+ def test_return_valid(self):
+ class Foo:
+ @typechecked
+ def method(self) -> Self:
+ return self
+
+ Foo().method()
+
+ def test_return_invalid(self):
+ class Foo:
+ @typechecked
+ def method(self) -> Self:
+ return 1
+
+ foo = Foo()
+ pytest.raises(TypeCheckError, foo.method).match(
+ rf"the return value \(int\) is not an instance of the self type "
+ rf"\({__name__}\.{self.__class__.__name__}\.test_return_invalid\."
+ rf"<locals>\.Foo\)"
+ )
+
+ def test_classmethod_return_valid(self):
+ class Foo:
+ @classmethod
+ @typechecked
+ def method(cls) -> Self:
+ return Foo()
+
+ Foo.method()
+
+ def test_classmethod_return_invalid(self):
+ class Foo:
+ @classmethod
+ @typechecked
+ def method(cls) -> Self:
+ return 1
+
+ pytest.raises(TypeCheckError, Foo.method).match(
+ rf"the return value \(int\) is not an instance of the self type "
+ rf"\({__name__}\.{self.__class__.__name__}\."
+ rf"test_classmethod_return_invalid\.<locals>\.Foo\)"
+ )
+
+ def test_arg_valid(self):
+ class Foo:
+ @typechecked
+ def method(self, another: Self) -> None:
+ pass
+
+ foo = Foo()
+ foo2 = Foo()
+ foo.method(foo2)
+
+ def test_arg_invalid(self):
+ class Foo:
+ @typechecked
+ def method(self, another: Self) -> None:
+ pass
+
+ foo = Foo()
+ pytest.raises(TypeCheckError, foo.method, 1).match(
+ rf'argument "another" \(int\) is not an instance of the self type '
+ rf"\({__name__}\.{self.__class__.__name__}\.test_arg_invalid\."
+ rf"<locals>\.Foo\)"
+ )
+
+ def test_classmethod_arg_valid(self):
+ class Foo:
+ @classmethod
+ @typechecked
+ def method(cls, another: Self) -> None:
+ pass
+
+ foo = Foo()
+ Foo.method(foo)
+
+ def test_classmethod_arg_invalid(self):
+ class Foo:
+ @classmethod
+ @typechecked
+ def method(cls, another: Self) -> None:
+ pass
+
+ foo = Foo()
+ pytest.raises(TypeCheckError, foo.method, 1).match(
+ rf'argument "another" \(int\) is not an instance of the self type '
+ rf"\({__name__}\.{self.__class__.__name__}\."
+ rf"test_classmethod_arg_invalid\.<locals>\.Foo\)"
+ )
+
+ def test_self_type_valid(self):
+ class Foo:
+ @typechecked
+ def method(cls, subclass: type[Self]) -> None:
+ pass
+
+ class Bar(Foo):
+ pass
+
+ Foo().method(Bar)
+
+ def test_self_type_invalid(self):
+ class Foo:
+ @typechecked
+ def method(cls, subclass: type[Self]) -> None:
+ pass
+
+ pytest.raises(TypeCheckError, Foo().method, int).match(
+ rf'argument "subclass" \(class int\) is not a subclass of the self type '
+ rf"\({__name__}\.{self.__class__.__name__}\."
+ rf"test_self_type_invalid\.<locals>\.Foo\)"
+ )
+
+
+class TestMock:
+ def test_mock_argument(self):
+ @typechecked
+ def foo(x: int) -> None:
+ pass
+
+ foo(Mock())
+
+ def test_return_mock(self):
+ @typechecked
+ def foo() -> int:
+ return Mock()
+
+ foo()
+
+
+def test_decorator_before_classmethod():
+ class Foo:
+ @typechecked
+ @classmethod
+ def method(cls, x: int) -> None:
+ pass
+
+ pytest.raises(TypeCheckError, Foo().method, "bar").match(
+ r'argument "x" \(str\) is not an instance of int'
+ )
+
+
+def test_classmethod():
+ @typechecked
+ class Foo:
+ @classmethod
+ def method(cls, x: int) -> None:
+ pass
+
+ pytest.raises(TypeCheckError, Foo().method, "bar").match(
+ r'argument "x" \(str\) is not an instance of int'
+ )
+
+
+def test_decorator_before_staticmethod():
+ class Foo:
+ @typechecked
+ @staticmethod
+ def method(x: int) -> None:
+ pass
+
+ pytest.raises(TypeCheckError, Foo().method, "bar").match(
+ r'argument "x" \(str\) is not an instance of int'
+ )
+
+
+def test_staticmethod():
+ @typechecked
+ class Foo:
+ @staticmethod
+ def method(x: int) -> None:
+ pass
+
+ pytest.raises(TypeCheckError, Foo().method, "bar").match(
+ r'argument "x" \(str\) is not an instance of int'
+ )
+
+
+def test_retain_dunder_attributes():
+ @typechecked
+ def foo(x: int, y: str = "foo") -> None:
+ """This is a docstring."""
+
+ assert foo.__module__ == __name__
+ assert foo.__name__ == "foo"
+ assert foo.__qualname__ == "test_retain_dunder_attributes.<locals>.foo"
+ assert foo.__doc__ == "This is a docstring."
+ assert foo.__defaults__ == ("foo",)
+
+
[email protected](sys.version_info < (3, 9), reason="Requires ast.unparse()")
+def test_debug_instrumentation(monkeypatch, capsys):
+ monkeypatch.setattr("typeguard.config.debug_instrumentation", True)
+
+ @typechecked
+ def foo(a: str) -> int:
+ return 6
+
+ out, err = capsys.readouterr()
+ assert err == dedent(
+ """\
+ Source code of test_debug_instrumentation.<locals>.foo() after instrumentation:
+ ----------------------------------------------
+ def foo(a: str) -> int:
+ from typeguard import TypeCheckMemo
+ from typeguard._functions import check_argument_types, check_return_type
+ memo = TypeCheckMemo(globals(), locals())
+ check_argument_types('test_debug_instrumentation.<locals>.foo', \
+{'a': (a, str)}, memo)
+ return check_return_type('test_debug_instrumentation.<locals>.foo', 6, \
+int, memo)
+ ----------------------------------------------
+ """
+ )
+
+
+def test_keyword_argument_default():
+ # Regression test for #305
+ @typechecked
+ def foo(*args, x: "int | None" = None):
+ pass
+
+ foo()
+
+
+def test_return_type_annotation_refers_to_nonlocal():
+ class Internal:
+ pass
+
+ @typechecked
+ def foo() -> Internal:
+ return Internal()
+
+ assert isinstance(foo(), Internal)
+
+
+def test_existing_method_decorator():
+ @typechecked
+ class Foo:
+ @contextmanager
+ def method(self, x: int) -> None:
+ yield x + 1
+
+ with Foo().method(6) as value:
+ assert value == 7
+
+
+# Этот тест не укладывается в 300s
+ "flags, expected_return_code",
+ [
+ pytest.param([], 1, id="debug"),
+ pytest.param(["-O"], 0, id="O"),
+ pytest.param(["-OO"], 0, id="OO"),
+ ],
+)
+def test_typechecked_disabled_in_optimized_mode(
+ tmp_path: Path, flags: List[str], expected_return_code: int
+):
+ code = dedent(
+ """
+ from typeguard import typechecked
+
+ @typechecked
+ def foo(x: int) -> None:
+ pass
+
+ foo("a")
+ """
+ )
+ script_path = tmp_path / "code.py"
+ script_path.write_text(code)
+ process = subprocess.run(
+ [sys.executable, *flags, str(script_path)], capture_output=True
+ )
+ assert process.returncode == expected_return_code
+ if process.returncode == 1:
+ assert process.stderr.strip().endswith(
+ b'typeguard.TypeCheckError: argument "x" (str) is not an instance of int'
+ )
+
+
+def test_reference_imported_name_from_method() -> None:
+ # Regression test for #362
+ @typechecked
+ class A:
+ def foo(self) -> Dict[str, Any]:
+ return {}
+
+ A().foo()
+
+
+def test_getter_setter():
+ """Regression test for #355."""
+
+ @typechecked
+ class Foo:
+ def __init__(self, x: int):
+ self._x = x
+
+ @property
+ def x(self) -> int:
+ return self._x
+
+ @x.setter
+ def x(self, value: int) -> None:
+ self._x = value
+
+ f = Foo(1)
+ f.x = 2
+ assert f.x == 2
+ with pytest.raises(TypeCheckError):
+ f.x = "foo"
+
+
+def test_duplicate_method():
+ class Foo:
+ def x(self) -> str:
+ return "first"
+
+ @typechecked()
+ def x(self, value: int) -> str: # noqa: F811
+ return "second"
+
+ assert Foo().x(1) == "second"
+ with pytest.raises(TypeCheckError):
+ Foo().x("wrong")
+
+
+def test_duplicate_function():
+ @typechecked
+ def foo() -> list[int]: # noqa: F811
+ return [x for x in range(5)]
+
+ foo1 = foo
+
+ @typechecked
+ def foo() -> list[int]: # noqa: F811
+ return [x for x in range(5, 10)]
+
+ assert foo1() == [0, 1, 2, 3, 4]
+ assert foo() == [5, 6, 7, 8, 9]
diff --git a/contrib/python/typeguard/tests/test_typeguard.py b/contrib/python/typeguard/tests/test_typeguard.py
deleted file mode 100644
index 6e7e9cda8bb..00000000000
--- a/contrib/python/typeguard/tests/test_typeguard.py
+++ /dev/null
@@ -1,1548 +0,0 @@
-import gc
-import sys
-import traceback
-import warnings
-from abc import abstractproperty
-from concurrent.futures import ThreadPoolExecutor
-from functools import lru_cache, partial, wraps
-from io import BytesIO, StringIO
-from typing import (
- AbstractSet, Any, AnyStr, BinaryIO, Callable, Container, Dict, Generator, Generic, Iterable,
- Iterator, List, NamedTuple, Sequence, Set, TextIO, Tuple, Type, TypeVar, Union, TypedDict)
-from unittest.mock import MagicMock, Mock
-
-import pytest
-from typing_extensions import Literal, NoReturn, Protocol, runtime_checkable
-
-from typeguard import (
- ForwardRefPolicy, TypeChecker, TypeHintWarning, TypeWarning, check_argument_types,
- check_return_type, check_type, function_name, qualified_name, typechecked)
-
-try:
- from typing import Collection
-except ImportError:
- # Python 3.6.0+
- Collection = None
-
-try:
- from typing import NewType
-except ImportError:
- myint = None
-else:
- myint = NewType("myint", int)
-
-
-TBound = TypeVar('TBound', bound='Parent')
-TConstrained = TypeVar('TConstrained', 'Parent', int)
-TTypingConstrained = TypeVar('TTypingConstrained', List[int], AbstractSet[str])
-TIntStr = TypeVar('TIntStr', int, str)
-TIntCollection = TypeVar('TIntCollection', int, Collection)
-TParent = TypeVar('TParent', bound='Parent')
-TChild = TypeVar('TChild', bound='Child')
-T_Foo = TypeVar('T_Foo')
-JSONType = Union[str, int, float, bool, None, List['JSONType'], Dict[str, 'JSONType']]
-
-DummyDict = TypedDict('DummyDict', {'x': int}, total=False)
-issue_42059 = pytest.mark.xfail(bool(DummyDict.__required_keys__),
- reason='Fails due to upstream bug BPO-42059')
-del DummyDict
-
-Employee = NamedTuple('Employee', [('name', str), ('id', int)])
-
-
-class FooGeneric(Generic[T_Foo]):
- pass
-
-
-class Parent:
- pass
-
-
-class Child(Parent):
- def method(self, a: int):
- pass
-
-
-class StaticProtocol(Protocol):
- def meth(self) -> None:
- ...
-
-
-@runtime_checkable
-class RuntimeProtocol(Protocol):
- def meth(self) -> None:
- ...
-
-
[email protected](params=[Mock, MagicMock], ids=['mock', 'magicmock'])
-def mock_class(request):
- return request.param
-
-
[email protected]('inputval, expected', [
- (qualified_name, 'function'),
- (Child(), '__tests__.test_typeguard.Child'),
- (int, 'int')
-], ids=['func', 'instance', 'builtintype'])
-def test_qualified_name(inputval, expected):
- assert qualified_name(inputval) == expected
-
-
-def test_function_name():
- assert function_name(function_name) == 'typeguard.function_name'
-
-
-def test_check_type_no_memo():
- check_type('foo', [1], List[int])
-
-
-def test_check_type_bytes():
- pytest.raises(TypeError, check_type, 'foo', 7, bytes).\
- match(r'type of foo must be bytes-like; got int instead')
-
-
-def test_check_type_no_memo_fail():
- pytest.raises(TypeError, check_type, 'foo', ['a'], List[int]).\
- match(r'type of foo\[0\] must be int; got str instead')
-
-
[email protected]('value', ['bar', b'bar'], ids=['str', 'bytes'])
-def test_check_type_anystr(value):
- check_type('foo', value, AnyStr)
-
-
-def test_check_type_anystr_fail():
- pytest.raises(TypeError, check_type, 'foo', int, AnyStr).\
- match(r'type of foo must match one of the constraints \(bytes, str\); got type instead')
-
-
-def test_check_return_type():
- def foo() -> int:
- assert check_return_type(0)
- return 0
-
- foo()
-
-
-def test_check_return_type_fail():
- def foo() -> int:
- assert check_return_type('foo')
- return 1
-
- pytest.raises(TypeError, foo).match('type of the return value must be int; got str instead')
-
-
-def test_check_return_notimplemented():
- class Foo:
- def __eq__(self, other) -> bool:
- assert check_return_type(NotImplemented)
- return NotImplemented
-
- assert Foo().__eq__(1) is NotImplemented
-
-
-def test_check_recursive_type():
- check_type('foo', {'a': [1, 2, 3]}, JSONType)
- pytest.raises(TypeError, check_type, 'foo', {'a': (1, 2, 3)}, JSONType, globals=globals()).\
- match(r'type of foo must be one of \(str, int, float, (bool, )?NoneType, '
- r'List\[JSONType\], Dict\[str, JSONType\]\); got dict instead')
-
-
-def test_exec_no_namespace():
- from textwrap import dedent
-
- exec(dedent("""
- from typeguard import typechecked
-
- @typechecked
- def f() -> None:
- pass
-
- """), {})
-
-
-class TestCheckArgumentTypes:
- def test_any_type(self):
- def foo(a: Any):
- assert check_argument_types()
-
- foo('aa')
-
- def test_mock_value(self, mock_class):
- def foo(a: str, b: int, c: dict, d: Any) -> int:
- assert check_argument_types()
-
- foo(mock_class(), mock_class(), mock_class(), mock_class())
-
- def test_callable_exact_arg_count(self):
- def foo(a: Callable[[int, str], int]):
- assert check_argument_types()
-
- def some_callable(x: int, y: str) -> int:
- pass
-
- foo(some_callable)
-
- def test_callable_bad_type(self):
- def foo(a: Callable[..., int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == 'argument "a" must be a callable'
-
- def test_callable_too_few_arguments(self):
- def foo(a: Callable[[int, str], int]):
- assert check_argument_types()
-
- def some_callable(x: int) -> int:
- pass
-
- exc = pytest.raises(TypeError, foo, some_callable)
- assert str(exc.value) == (
- 'callable passed as argument "a" has too few arguments in its declaration; expected 2 '
- 'but 1 argument(s) declared')
-
- def test_callable_too_many_arguments(self):
- def foo(a: Callable[[int, str], int]):
- assert check_argument_types()
-
- def some_callable(x: int, y: str, z: float) -> int:
- pass
-
- exc = pytest.raises(TypeError, foo, some_callable)
- assert str(exc.value) == (
- 'callable passed as argument "a" has too many arguments in its declaration; expected '
- '2 but 3 argument(s) declared')
-
- def test_callable_mandatory_kwonlyargs(self):
- def foo(a: Callable[[int, str], int]):
- assert check_argument_types()
-
- def some_callable(x: int, y: str, *, z: float, bar: str) -> int:
- pass
-
- exc = pytest.raises(TypeError, foo, some_callable)
- assert str(exc.value) == (
- 'callable passed as argument "a" has mandatory keyword-only arguments in its '
- 'declaration: z, bar')
-
- def test_callable_class(self):
- """
- Test that passing a class as a callable does not count the "self" argument "a"gainst the
- ones declared in the Callable specification.
-
- """
- def foo(a: Callable[[int, str], Any]):
- assert check_argument_types()
-
- class SomeClass:
- def __init__(self, x: int, y: str):
- pass
-
- foo(SomeClass)
-
- def test_callable_plain(self):
- def foo(a: Callable):
- assert check_argument_types()
-
- def callback(a):
- pass
-
- foo(callback)
-
- def test_callable_partial_class(self):
- """
- Test that passing a bound method as a callable does not count the "self" argument "a"gainst
- the ones declared in the Callable specification.
-
- """
- def foo(a: Callable[[int], Any]):
- assert check_argument_types()
-
- class SomeClass:
- def __init__(self, x: int, y: str):
- pass
-
- foo(partial(SomeClass, y='foo'))
-
- def test_callable_bound_method(self):
- """
- Test that passing a bound method as a callable does not count the "self" argument "a"gainst
- the ones declared in the Callable specification.
-
- """
- def foo(callback: Callable[[int], Any]):
- assert check_argument_types()
-
- foo(Child().method)
-
- def test_callable_partial_bound_method(self):
- """
- Test that passing a bound method as a callable does not count the "self" argument "a"gainst
- the ones declared in the Callable specification.
-
- """
- def foo(callback: Callable[[], Any]):
- assert check_argument_types()
-
- foo(partial(Child().method, 1))
-
- def test_callable_defaults(self):
- """
- Test that a callable having "too many" arguments don't raise an error if the extra
- arguments have default values.
-
- """
- def foo(callback: Callable[[int, str], Any]):
- assert check_argument_types()
-
- def some_callable(x: int, y: str, z: float = 1.2) -> int:
- pass
-
- foo(some_callable)
-
- def test_callable_builtin(self):
- """
- Test that checking a Callable annotation against a builtin callable does not raise an
- error.
-
- """
- def foo(callback: Callable[[int], Any]):
- assert check_argument_types()
-
- foo([].append)
-
- def test_dict_bad_type(self):
- def foo(a: Dict[str, int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == (
- 'type of argument "a" must be a dict; got int instead')
-
- def test_dict_bad_key_type(self):
- def foo(a: Dict[str, int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, {1: 2})
- assert str(exc.value) == 'type of keys of argument "a" must be str; got int instead'
-
- def test_dict_bad_value_type(self):
- def foo(a: Dict[str, int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, {'x': 'a'})
- assert str(exc.value) == "type of argument \"a\"['x'] must be int; got str instead"
-
- def test_list_bad_type(self):
- def foo(a: List[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == (
- 'type of argument "a" must be a list; got int instead')
-
- def test_list_bad_element(self):
- def foo(a: List[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, [1, 2, 'bb'])
- assert str(exc.value) == (
- 'type of argument "a"[2] must be int; got str instead')
-
- def test_sequence_bad_type(self):
- def foo(a: Sequence[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == (
- 'type of argument "a" must be a sequence; got int instead')
-
- def test_sequence_bad_element(self):
- def foo(a: Sequence[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, [1, 2, 'bb'])
- assert str(exc.value) == (
- 'type of argument "a"[2] must be int; got str instead')
-
- def test_abstractset_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
-
- def foo(a: AbstractSet[int]):
- assert check_argument_types()
-
- foo(DummySet())
-
- def test_abstractset_bad_type(self):
- def foo(a: AbstractSet[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == 'type of argument "a" must be a set; got int instead'
-
- def test_set_bad_type(self):
- def foo(a: Set[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == 'type of argument "a" must be a set; got int instead'
-
- def test_abstractset_bad_element(self):
- def foo(a: AbstractSet[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, {1, 2, 'bb'})
- assert str(exc.value) == (
- 'type of elements of argument "a" must be int; got str instead')
-
- def test_set_bad_element(self):
- def foo(a: Set[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, {1, 2, 'bb'})
- assert str(exc.value) == (
- 'type of elements of argument "a" must be int; got str instead')
-
- def test_tuple_bad_type(self):
- def foo(a: Tuple[int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 5)
- assert str(exc.value) == (
- 'type of argument "a" must be a tuple; got int instead')
-
- def test_tuple_too_many_elements(self):
- def foo(a: Tuple[int, str]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, (1, 'aa', 2))
- assert str(exc.value) == ('argument "a" has wrong number of elements (expected 2, got 3 '
- 'instead)')
-
- def test_tuple_too_few_elements(self):
- def foo(a: Tuple[int, str]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, (1,))
- assert str(exc.value) == ('argument "a" has wrong number of elements (expected 2, got 1 '
- 'instead)')
-
- def test_tuple_bad_element(self):
- def foo(a: Tuple[int, str]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, (1, 2))
- assert str(exc.value) == (
- 'type of argument "a"[1] must be str; got int instead')
-
- def test_tuple_ellipsis_bad_element(self):
- def foo(a: Tuple[int, ...]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, (1, 2, 'blah'))
- assert str(exc.value) == (
- 'type of argument "a"[2] must be int; got str instead')
-
- def test_namedtuple(self):
- def foo(bar: Employee):
- assert check_argument_types()
-
- foo(Employee('bob', 1))
-
- def test_namedtuple_type_mismatch(self):
- def foo(bar: Employee):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, ('bob', 1)).\
- match('type of argument "bar" must be a named tuple of type '
- r'(__tests__\.test_typeguard\.)?Employee; got tuple instead')
-
- def test_namedtuple_wrong_field_type(self):
- def foo(bar: Employee):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, Employee(2, 1)).\
- match('type of argument "bar".name must be str; got int instead')
-
- @pytest.mark.parametrize('value', [6, 'aa'])
- def test_union(self, value):
- def foo(a: Union[str, int]):
- assert check_argument_types()
-
- foo(value)
-
- def test_union_typing_type(self):
- def foo(a: Union[str, Collection]):
- assert check_argument_types()
-
- with pytest.raises(TypeError):
- foo(1)
-
- @pytest.mark.parametrize('value', [6.5, b'aa'])
- def test_union_fail(self, value):
- def foo(a: Union[str, int]):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, value)
- assert str(exc.value) == (
- 'type of argument "a" must be one of (str, int); got {} instead'.
- format(value.__class__.__name__))
-
- @pytest.mark.parametrize('values', [
- (6, 7),
- ('aa', 'bb')
- ], ids=['int', 'str'])
- def test_typevar_constraints(self, values):
- def foo(a: TIntStr, b: TIntStr):
- assert check_argument_types()
-
- foo(*values)
-
- @pytest.mark.parametrize('value', [
- [6, 7],
- {'aa', 'bb'}
- ], ids=['int', 'str'])
- def test_typevar_collection_constraints(self, value):
- def foo(a: TTypingConstrained):
- assert check_argument_types()
-
- foo(value)
-
- def test_typevar_collection_constraints_fail(self):
- def foo(a: TTypingConstrained):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, {1, 2}).\
- match(r'type of argument "a" must match one of the constraints \(List\[int\], '
- r'AbstractSet\[str\]\); got set instead')
-
- def test_typevar_constraints_fail(self):
- def foo(a: TIntStr, b: TIntStr):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 2.5, 'aa')
- assert str(exc.value) == ('type of argument "a" must match one of the constraints '
- '(int, str); got float instead')
-
- def test_typevar_bound(self):
- def foo(a: TParent, b: TParent):
- assert check_argument_types()
-
- foo(Child(), Child())
-
- def test_typevar_bound_fail(self):
- def foo(a: TChild, b: TChild):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, Parent(), Parent())
- assert str(exc.value) == ('type of argument "a" must be __tests__.test_typeguard.Child or one of '
- 'its subclasses; got __tests__.test_typeguard.Parent instead')
-
- @pytest.mark.skipif(Type is List, reason='typing.Type could not be imported')
- def test_class_bad_subclass(self):
- def foo(a: Type[Child]):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, Parent).match(
- '"a" must be a subclass of __tests__.test_typeguard.Child; got __tests__.test_typeguard.Parent instead')
-
- def test_class_any(self):
- def foo(a: Type[Any]):
- assert check_argument_types()
-
- foo(str)
-
- def test_class_union(self):
- def foo(a: Type[Union[str, int]]):
- assert check_argument_types()
-
- foo(str)
- foo(int)
- pytest.raises(TypeError, foo, tuple).\
- match(r'"a" must match one of the following: \(str, int\); got tuple instead')
-
- def test_wrapped_function(self):
- def decorator(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- return func(*args, **kwargs)
- return wrapper
-
- @decorator
- def foo(a: 'Child'):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, Parent())
- assert str(exc.value) == ('type of argument "a" must be __tests__.test_typeguard.Child; '
- 'got __tests__.test_typeguard.Parent instead')
-
- def test_mismatching_default_type(self):
- def foo(a: str = 1):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo).match('type of argument "a" must be str; got int instead')
-
- def test_implicit_default_none(self):
- """
- Test that if the default value is ``None``, a ``None`` argument can be passed.
-
- """
- def foo(a: str = None):
- assert check_argument_types()
-
- foo()
-
- def test_generator(self):
- """Test that argument type checking works in a generator function too."""
- def generate(a: int):
- assert check_argument_types()
- yield a
- yield a + 1
-
- gen = generate(1)
- next(gen)
-
- def test_wrapped_generator_no_return_type_annotation(self):
- """Test that return type checking works in a generator function too."""
- @typechecked
- def generate(a: int):
- yield a
- yield a + 1
-
- gen = generate(1)
- next(gen)
-
- def test_varargs(self):
- def foo(*args: int):
- assert check_argument_types()
-
- foo(1, 2)
-
- def test_varargs_fail(self):
- def foo(*args: int):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, 1, 'a')
- exc.match(r'type of argument "args"\[1\] must be int; got str instead')
-
- def test_kwargs(self):
- def foo(**kwargs: int):
- assert check_argument_types()
-
- foo(a=1, b=2)
-
- def test_kwargs_fail(self):
- def foo(**kwargs: int):
- assert check_argument_types()
-
- exc = pytest.raises(TypeError, foo, a=1, b='a')
- exc.match(r'type of argument "kwargs"\[\'b\'\] must be int; got str instead')
-
- def test_generic(self):
- def foo(a: FooGeneric[str]):
- assert check_argument_types()
-
- foo(FooGeneric[str]())
-
- @pytest.mark.skipif(myint is None, reason='NewType is not present in the typing module')
- def test_newtype(self):
- def foo(a: myint) -> int:
- assert check_argument_types()
- return 42
-
- assert foo(1) == 42
- exc = pytest.raises(TypeError, foo, "a")
- assert str(exc.value) == 'type of argument "a" must be int; got str instead'
-
- @pytest.mark.skipif(Collection is None, reason='typing.Collection is not available')
- def test_collection(self):
- def foo(a: Collection):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, True).match(
- 'type of argument "a" must be collections.abc.Collection; got bool instead')
-
- def test_binary_io(self):
- def foo(a: BinaryIO):
- assert check_argument_types()
-
- foo(BytesIO())
-
- def test_text_io(self):
- def foo(a: TextIO):
- assert check_argument_types()
-
- foo(StringIO())
-
- def test_binary_io_fail(self):
- def foo(a: TextIO):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, BytesIO()).match('must be a text based I/O')
-
- def test_text_io_fail(self):
- def foo(a: BinaryIO):
- assert check_argument_types()
-
- pytest.raises(TypeError, foo, StringIO()).match('must be a binary I/O')
-
- def test_binary_io_real_file(self, tmpdir):
- def foo(a: BinaryIO):
- assert check_argument_types()
-
- with tmpdir.join('testfile').open('wb') as f:
- foo(f)
-
- def test_text_io_real_file(self, tmpdir):
- def foo(a: TextIO):
- assert check_argument_types()
-
- with tmpdir.join('testfile').open('w') as f:
- foo(f)
-
- def test_recursive_type(self):
- def foo(arg: JSONType) -> None:
- assert check_argument_types()
-
- foo({'a': [1, 2, 3]})
- pytest.raises(TypeError, foo, {'a': (1, 2, 3)}).\
- match(r'type of argument "arg" must be one of \(str, int, float, (bool, )?NoneType, '
- r'List\[Union\[str, int, float, (bool, )?NoneType, List\[JSONType\], '
- r'Dict\[str, JSONType\]\]\], '
- r'Dict\[str, Union\[str, int, float, (bool, )?NoneType, List\[JSONType\], '
- r'Dict\[str, JSONType\]\]\]\); got dict instead')
-
-
-class TestTypeChecked:
- def test_typechecked(self):
- @typechecked
- def foo(a: int, b: str) -> str:
- return 'abc'
-
- assert foo(4, 'abc') == 'abc'
-
- def test_typechecked_always(self):
- @typechecked(always=True)
- def foo(a: int, b: str) -> str:
- return 'abc'
-
- assert foo(4, 'abc') == 'abc'
-
- def test_typechecked_arguments_fail(self):
- @typechecked
- def foo(a: int, b: str) -> str:
- return 'abc'
-
- exc = pytest.raises(TypeError, foo, 4, 5)
- assert str(exc.value) == 'type of argument "b" must be str; got int instead'
-
- def test_typechecked_return_type_fail(self):
- @typechecked
- def foo(a: int, b: str) -> str:
- return 6
-
- exc = pytest.raises(TypeError, foo, 4, 'abc')
- assert str(exc.value) == 'type of the return value must be str; got int instead'
-
- def test_typechecked_return_typevar_fail(self):
- T = TypeVar('T', int, float)
-
- @typechecked
- def foo(a: T, b: T) -> T:
- return 'a'
-
- pytest.raises(TypeError, foo, 4, 2).\
- match(r'type of the return value must match one of the constraints \(int, float\); '
- r'got str instead')
-
- def test_typechecked_no_annotations(self, recwarn):
- def foo(a, b):
- pass
-
- typechecked(foo)
-
- func_name = function_name(foo)
- assert len(recwarn) == 1
- assert str(recwarn[0].message) == (
- 'no type annotations present -- not typechecking {}'.format(func_name))
-
- def test_return_type_none(self):
- """Check that a declared return type of None is respected."""
- @typechecked
- def foo() -> None:
- return 'a'
-
- exc = pytest.raises(TypeError, foo)
- assert str(exc.value) == 'type of the return value must be NoneType; got str instead'
-
- def test_return_type_magicmock(self, mock_class):
- @typechecked
- def foo() -> str:
- return mock_class()
-
- foo()
-
- @pytest.mark.parametrize('typehint', [
- Callable[..., int],
- Callable
- ], ids=['parametrized', 'unparametrized'])
- def test_callable(self, typehint):
- @typechecked
- def foo(a: typehint):
- pass
-
- def some_callable() -> int:
- pass
-
- foo(some_callable)
-
- @pytest.mark.parametrize('typehint', [
- List[int],
- List,
- list,
- ], ids=['parametrized', 'unparametrized', 'plain'])
- def test_list(self, typehint):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo([1, 2])
-
- @pytest.mark.parametrize('typehint', [
- Dict[str, int],
- Dict,
- dict
- ], ids=['parametrized', 'unparametrized', 'plain'])
- def test_dict(self, typehint):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo({'x': 2})
-
- @pytest.mark.parametrize('typehint, value', [
- (Dict, {'x': 2, 6: 4}),
- (List, ['x', 6]),
- (Sequence, ['x', 6]),
- (Set, {'x', 6}),
- (AbstractSet, {'x', 6}),
- (Tuple, ('x', 6)),
- ], ids=['dict', 'list', 'sequence', 'set', 'abstractset', 'tuple'])
- def test_unparametrized_types_mixed_values(self, typehint, value):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(value)
-
- @pytest.mark.parametrize('typehint', [
- Sequence[str],
- Sequence
- ], ids=['parametrized', 'unparametrized'])
- @pytest.mark.parametrize('value', [('a', 'b'), ['a', 'b'], 'abc'],
- ids=['tuple', 'list', 'str'])
- def test_sequence(self, typehint, value):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(value)
-
- @pytest.mark.parametrize('typehint', [
- Iterable[str],
- Iterable
- ], ids=['parametrized', 'unparametrized'])
- @pytest.mark.parametrize('value', [('a', 'b'), ['a', 'b'], 'abc'],
- ids=['tuple', 'list', 'str'])
- def test_iterable(self, typehint, value):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(value)
-
- @pytest.mark.parametrize('typehint', [
- Container[str],
- Container
- ], ids=['parametrized', 'unparametrized'])
- @pytest.mark.parametrize('value', [('a', 'b'), ['a', 'b'], 'abc'],
- ids=['tuple', 'list', 'str'])
- def test_container(self, typehint, value):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(value)
-
- @pytest.mark.parametrize('typehint', [
- AbstractSet[int],
- AbstractSet,
- Set[int],
- Set,
- set
- ], ids=['abstract_parametrized', 'abstract', 'parametrized', 'unparametrized', 'plain'])
- @pytest.mark.parametrize('value', [set(), {6}])
- def test_set(self, typehint, value):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(value)
-
- @pytest.mark.parametrize('typehint', [
- Tuple[int, int],
- Tuple[int, ...],
- Tuple,
- tuple
- ], ids=['parametrized', 'ellipsis', 'unparametrized', 'plain'])
- def test_tuple(self, typehint):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo((1, 2))
-
- def test_empty_tuple(self):
- @typechecked
- def foo(a: Tuple[()]):
- pass
-
- foo(())
-
- @pytest.mark.skipif(Type is List, reason='typing.Type could not be imported')
- @pytest.mark.parametrize('typehint', [
- Type[Parent],
- Type[TypeVar('UnboundType')], # noqa: F821
- Type[TypeVar('BoundType', bound=Parent)], # noqa: F821
- Type,
- type
- ], ids=['parametrized', 'unbound-typevar', 'bound-typevar', 'unparametrized', 'plain'])
- def test_class(self, typehint):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(Child)
-
- @pytest.mark.skipif(Type is List, reason='typing.Type could not be imported')
- def test_class_not_a_class(self):
- @typechecked
- def foo(a: Type[dict]):
- pass
-
- exc = pytest.raises(TypeError, foo, 1)
- exc.match('type of argument "a" must be a type; got int instead')
-
- @pytest.mark.parametrize('typehint, value', [
- (complex, complex(1, 5)),
- (complex, 1.0),
- (complex, 1),
- (float, 1.0),
- (float, 1)
- ], ids=['complex-complex', 'complex-float', 'complex-int', 'float-float', 'float-int'])
- def test_numbers(self, typehint, value):
- @typechecked
- def foo(a: typehint):
- pass
-
- foo(value)
-
- def test_coroutine_correct_return_type(self):
- @typechecked
- async def foo() -> str:
- return 'foo'
-
- coro = foo()
- pytest.raises(StopIteration, coro.send, None)
-
- def test_coroutine_wrong_return_type(self):
- @typechecked
- async def foo() -> str:
- return 1
-
- coro = foo()
- pytest.raises(TypeError, coro.send, None).\
- match('type of the return value must be str; got int instead')
-
- def test_bytearray_bytes(self):
- """Test that a bytearray is accepted where bytes are expected."""
- @typechecked
- def foo(x: bytes) -> None:
- pass
-
- foo(bytearray([1]))
-
- def test_bytearray_memoryview(self):
- """Test that a bytearray is accepted where bytes are expected."""
- @typechecked
- def foo(x: bytes) -> None:
- pass
-
- foo(memoryview(b'foo'))
-
- def test_class_decorator(self):
- @typechecked
- class Foo:
- @staticmethod
- def staticmethod() -> int:
- return 'foo'
-
- @classmethod
- def classmethod(cls) -> int:
- return 'foo'
-
- def method(self) -> int:
- return 'foo'
-
- @property
- def prop(self) -> int:
- return 'foo'
-
- @property
- def prop2(self) -> int:
- return 'foo'
-
- @prop2.setter
- def prop2(self, value: int) -> None:
- pass
-
- pattern = 'type of the return value must be int; got str instead'
- pytest.raises(TypeError, Foo.staticmethod).match(pattern)
- pytest.raises(TypeError, Foo.classmethod).match(pattern)
- pytest.raises(TypeError, Foo().method).match(pattern)
-
- with pytest.raises(TypeError) as raises:
- Foo().prop
-
- assert raises.value.args[0] == pattern
-
- with pytest.raises(TypeError) as raises:
- Foo().prop2
-
- assert raises.value.args[0] == pattern
-
- with pytest.raises(TypeError) as raises:
- Foo().prop2 = 'foo'
-
- assert raises.value.args[0] == 'type of argument "value" must be int; got str instead'
-
- @pytest.mark.parametrize('annotation', [
- Generator[int, str, List[str]],
- Generator,
- Iterable[int],
- Iterable,
- Iterator[int],
- Iterator
- ], ids=['generator', 'bare_generator', 'iterable', 'bare_iterable', 'iterator',
- 'bare_iterator'])
- def test_generator(self, annotation):
- @typechecked
- def genfunc() -> annotation:
- val1 = yield 2
- val2 = yield 3
- val3 = yield 4
- return [val1, val2, val3]
-
- gen = genfunc()
- with pytest.raises(StopIteration) as exc:
- value = next(gen)
- while True:
- value = gen.send(str(value))
- assert isinstance(value, int)
-
- assert exc.value.value == ['2', '3', '4']
-
- @pytest.mark.parametrize('annotation', [
- Generator[int, str, None],
- Iterable[int],
- Iterator[int]
- ], ids=['generator', 'iterable', 'iterator'])
- def test_generator_bad_yield(self, annotation):
- @typechecked
- def genfunc() -> annotation:
- yield 'foo'
-
- gen = genfunc()
- with pytest.raises(TypeError) as exc:
- next(gen)
-
- exc.match('type of value yielded from generator must be int; got str instead')
-
- def test_generator_bad_send(self):
- @typechecked
- def genfunc() -> Generator[int, str, None]:
- yield 1
- yield 2
-
- gen = genfunc()
- next(gen)
- with pytest.raises(TypeError) as exc:
- gen.send(2)
-
- exc.match('type of value sent to generator must be str; got int instead')
-
- def test_generator_bad_return(self):
- @typechecked
- def genfunc() -> Generator[int, str, str]:
- yield 1
- return 6
-
- gen = genfunc()
- next(gen)
- with pytest.raises(TypeError) as exc:
- gen.send('foo')
-
- exc.match('type of return value must be str; got int instead')
-
- def test_return_generator(self):
- @typechecked
- def genfunc() -> Generator[int, None, None]:
- yield 1
-
- @typechecked
- def foo() -> Generator[int, None, None]:
- return genfunc()
-
- foo()
-
- def test_builtin_decorator(self):
- @typechecked
- @lru_cache()
- def func(x: int) -> None:
- pass
-
- func(3)
- func(3)
- pytest.raises(TypeError, func, 'foo').\
- match('type of argument "x" must be int; got str instead')
-
- # Make sure that @lru_cache is still being used
- cache_info = func.__wrapped__.cache_info()
- assert cache_info.hits == 1
-
- def test_local_class(self):
- @typechecked
- class LocalClass:
- class Inner:
- pass
-
- def create_inner(self) -> 'Inner':
- return self.Inner()
-
- retval = LocalClass().create_inner()
- assert isinstance(retval, LocalClass.Inner)
-
- def test_local_class_async(self):
- @typechecked
- class LocalClass:
- class Inner:
- pass
-
- async def create_inner(self) -> 'Inner':
- return self.Inner()
-
- coro = LocalClass().create_inner()
- exc = pytest.raises(StopIteration, coro.send, None)
- retval = exc.value.value
- assert isinstance(retval, LocalClass.Inner)
-
- def test_callable_nonmember(self):
- class CallableClass:
- def __call__(self):
- pass
-
- @typechecked
- class LocalClass:
- some_callable = CallableClass()
-
- def test_inherited_class_method(self):
- @typechecked
- class Parent:
- @classmethod
- def foo(cls, x: str) -> str:
- return cls.__name__
-
- @typechecked
- class Child(Parent):
- pass
-
- assert Child.foo('bar') == 'Child'
- pytest.raises(TypeError, Child.foo, 1)
-
- def test_class_property(self):
- @typechecked
- class Foo:
- def __init__(self) -> None:
- self.foo = 'foo'
-
- @property
- def prop(self) -> int:
- """My property."""
- return 4
-
- @property
- def prop2(self) -> str:
- return self.foo
-
- @prop2.setter
- def prop2(self, value: str) -> None:
- self.foo = value
-
- assert Foo.__dict__["prop"].__doc__.strip() == "My property."
- f = Foo()
- assert f.prop == 4
- assert f.prop2 == 'foo'
- f.prop2 = 'bar'
- assert f.prop2 == 'bar'
-
- with pytest.raises(TypeError) as raises:
- f.prop2 = 3
-
- assert raises.value.args[0] == 'type of argument "value" must be str; got int instead'
-
- def test_decorator_factory_no_annotations(self):
- class CallableClass:
- def __call__(self):
- pass
-
- def decorator_factory():
- def decorator(f):
- cmd = CallableClass()
- return cmd
-
- return decorator
-
- with pytest.warns(UserWarning):
- @typechecked
- @decorator_factory()
- def foo():
- pass
-
- @pytest.mark.skipif(sys.version_info >= (3, 12), reason="Fail wint Python 3.12")
- @pytest.mark.parametrize('annotation', [TBound, TConstrained], ids=['bound', 'constrained'])
- def test_typevar_forwardref(self, annotation):
- @typechecked
- def func(x: annotation) -> None:
- pass
-
- func(Parent())
- func(Child())
- pytest.raises(TypeError, func, 'foo')
-
- @pytest.mark.parametrize('protocol_cls', [RuntimeProtocol, StaticProtocol])
- def test_protocol(self, protocol_cls):
- @typechecked
- def foo(arg: protocol_cls) -> None:
- pass
-
- class Foo:
- def meth(self) -> None:
- pass
-
- foo(Foo())
-
- def test_protocol_fail(self):
- @typechecked
- def foo(arg: RuntimeProtocol) -> None:
- pass
-
- pytest.raises(TypeError, foo, object()).\
- match(r'type of argument "arg" \(object\) is not compatible with the RuntimeProtocol '
- 'protocol')
-
- def test_noreturn(self):
- @typechecked
- def foo() -> NoReturn:
- pass
-
- pytest.raises(TypeError, foo).match(r'foo\(\) was declared never to return but it did')
-
- def test_recursive_type(self):
- @typechecked
- def foo(arg: JSONType) -> None:
- pass
-
- foo({'a': [1, 2, 3]})
- pytest.raises(TypeError, foo, {'a': (1, 2, 3)}).\
- match(r'type of argument "arg" must be one of \(str, int, float, (bool, )?NoneType, '
- r'List\[Union\[str, int, float, (bool, )?NoneType, List\[JSONType\], '
- r'Dict\[str, JSONType\]\]\], '
- r'Dict\[str, Union\[str, int, float, (bool, )?NoneType, List\[JSONType\], '
- r'Dict\[str, JSONType\]\]\]\); got dict instead')
-
- def test_literal(self):
- from http import HTTPStatus
-
- @typechecked
- def foo(a: Literal[1, True, 'x', b'y', HTTPStatus.ACCEPTED]):
- pass
-
- foo(HTTPStatus.ACCEPTED)
- pytest.raises(TypeError, foo, 4).match(r"must be one of \(1, True, 'x', b'y', "
- r"<HTTPStatus.ACCEPTED: 202>\); got 4 instead$")
-
- def test_literal_union(self):
- @typechecked
- def foo(a: Union[str, Literal[1, 6, 8]]):
- pass
-
- foo(6)
- pytest.raises(TypeError, foo, 4).\
- match(r'must be one of \(str, Literal\[1, 6, 8\]\); got int instead$')
-
- def test_literal_nested(self):
- @typechecked
- def foo(a: Literal[1, Literal['x', 'a', Literal['z']], 6, 8]):
- pass
-
- foo('z')
- pytest.raises(TypeError, foo, 4).match(r"must be one of \(1, 'x', 'a', 'z', 6, 8\); "
- r"got 4 instead$")
-
- def test_literal_illegal_value(self):
- @typechecked
- def foo(a: Literal[1, 1.1]):
- pass
-
- pytest.raises(TypeError, foo, 4).match(r"Illegal literal value: 1.1$")
-
- @pytest.mark.parametrize('value, total, error_re', [
- pytest.param({'x': 6, 'y': 'foo'}, True, None, id='correct'),
- pytest.param({'y': 'foo'}, True, r'required key\(s\) \("x"\) missing from argument "arg"',
- id='missing_x'),
- pytest.param({'x': 6, 'y': 3}, True,
- 'type of dict item "y" for argument "arg" must be str; got int instead',
- id='wrong_y'),
- pytest.param({'x': 6}, True, r'required key\(s\) \("y"\) missing from argument "arg"',
- id='missing_y_error'),
- pytest.param({'x': 6}, False, None, id='missing_y_ok', marks=[issue_42059]),
- pytest.param({'x': 'abc'}, False,
- 'type of dict item "x" for argument "arg" must be int; got str instead',
- id='wrong_x', marks=[issue_42059]),
- pytest.param({'x': 6, 'foo': 'abc'}, False, r'extra key\(s\) \("foo"\) in argument "arg"',
- id='unknown_key')
- ])
- def test_typed_dict(self, value, total, error_re):
- DummyDict = TypedDict('DummyDict', {'x': int, 'y': str}, total=total)
-
- @typechecked
- def foo(arg: DummyDict):
- pass
-
- if error_re:
- pytest.raises(TypeError, foo, value).match(error_re)
- else:
- foo(value)
-
- def test_class_abstract_property(self):
- """Regression test for #206."""
-
- @typechecked
- class Foo:
- @abstractproperty
- def dummyproperty(self):
- pass
-
- assert isinstance(Foo.dummyproperty, abstractproperty)
-
-
-class TestTypeChecker:
- @pytest.fixture
- def executor(self):
- executor = ThreadPoolExecutor(1)
- yield executor
- executor.shutdown()
-
- @pytest.fixture
- def checker(self):
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- return TypeChecker(__name__)
-
- @staticmethod
- def generatorfunc() -> Generator[int, None, None]:
- yield 1
-
- @staticmethod
- def bad_generatorfunc() -> Generator[int, None, None]:
- yield 1
- yield 'foo'
-
- @staticmethod
- def error_function() -> float:
- return 1 / 0
-
- def test_check_call_args(self, checker: TypeChecker):
- def foo(a: int):
- pass
-
- with checker, pytest.warns(TypeWarning) as record:
- assert checker.active
- foo(1)
- foo('x')
-
- assert not checker.active
- foo('x')
-
- assert len(record) == 1
- warning = record[0].message
- assert warning.error == 'type of argument "a" must be int; got str instead'
- assert warning.func is foo
- assert isinstance(warning.stack, list)
- buffer = StringIO()
- warning.print_stack(buffer)
- assert len(buffer.getvalue()) > 100
-
- def test_check_return_value(self, checker: TypeChecker):
- def foo() -> int:
- return 'x'
-
- with checker, pytest.warns(TypeWarning) as record:
- foo()
-
- assert len(record) == 1
- assert record[0].message.error == 'type of the return value must be int; got str instead'
-
- def test_threaded_check_call_args(self, checker: TypeChecker, executor):
- def foo(a: int):
- pass
-
- with checker, pytest.warns(TypeWarning) as record:
- executor.submit(foo, 1).result()
- executor.submit(foo, 'x').result()
-
- executor.submit(foo, 'x').result()
-
- assert len(record) == 1
- warning = record[0].message
- assert warning.error == 'type of argument "a" must be int; got str instead'
- assert warning.func is foo
-
- def test_double_start(self, checker: TypeChecker):
- """Test that the same type checker can't be started twice while running."""
- with checker:
- pytest.raises(RuntimeError, checker.start).match('type checker already running')
-
- def test_nested(self):
- """Test that nesting of type checker context managers works as expected."""
- def foo(a: int):
- pass
-
- with warnings.catch_warnings(record=True):
- warnings.simplefilter('ignore', DeprecationWarning)
- parent = TypeChecker(__name__)
- child = TypeChecker(__name__)
-
- with parent, pytest.warns(TypeWarning) as record:
- foo('x')
- with child:
- foo('x')
-
- assert len(record) == 3
-
- def test_existing_profiler(self, checker: TypeChecker):
- """
- Test that an existing profiler function is chained with the type checker and restored after
- the block is exited.
-
- """
- def foo(a: int):
- pass
-
- def profiler(frame, event, arg):
- nonlocal profiler_run_count
- if event in ('call', 'return'):
- profiler_run_count += 1
-
- if old_profiler:
- old_profiler(frame, event, arg)
-
- profiler_run_count = 0
- old_profiler = sys.getprofile()
- sys.setprofile(profiler)
- try:
- with checker, pytest.warns(TypeWarning) as record:
- foo(1)
- foo('x')
-
- assert sys.getprofile() is profiler
- finally:
- sys.setprofile(old_profiler)
-
- assert profiler_run_count
- assert len(record) == 1
-
- def test_generator(self, checker):
- with checker, pytest.warns(None) as record:
- gen = self.generatorfunc()
- assert next(gen) == 1
-
- assert len(record) == 0
-
- def test_generator_wrong_yield(self, checker):
- with checker, pytest.warns(TypeWarning) as record:
- gen = self.bad_generatorfunc()
- assert list(gen) == [1, 'foo']
-
- assert len(record) == 1
- assert 'type of yielded value must be int; got str instead' in str(record[0].message)
-
- def test_exception(self, checker):
- with checker, pytest.warns(None) as record:
- pytest.raises(ZeroDivisionError, self.error_function)
-
- assert len(record) == 0
-
- @pytest.mark.parametrize('policy', [ForwardRefPolicy.WARN, ForwardRefPolicy.GUESS],
- ids=['warn', 'guess'])
- def test_forward_ref_policy_resolution_fails(self, checker, policy):
- def unresolvable_annotation(x: 'OrderedDict'): # noqa
- pass
-
- checker.annotation_policy = policy
- gc.collect() # prevent find_function() from finding more than one instance of the function
- with checker, pytest.warns(TypeHintWarning) as record:
- unresolvable_annotation({})
-
- assert len(record) == 1
- assert ("unresolvable_annotation: name 'OrderedDict' is not defined"
- in str(record[0].message))
- assert 'x' not in unresolvable_annotation.__annotations__
-
- def test_forward_ref_policy_guess(self, checker):
- import collections
-
- def unresolvable_annotation(x: 'OrderedDict'): # noqa
- pass
-
- checker.annotation_policy = ForwardRefPolicy.GUESS
- with checker, pytest.warns(TypeHintWarning) as record:
- unresolvable_annotation(collections.OrderedDict())
-
- assert len(record) == 1
- assert str(record[0].message).startswith("Replaced forward declaration 'OrderedDict' in")
- assert unresolvable_annotation.__annotations__['x'] is collections.OrderedDict
-
-
-class TestTracebacks:
- def test_short_tracebacks(self):
- def foo(a: Callable[..., int]):
- assert check_argument_types()
-
- try:
- foo(1)
- except TypeError:
- _, _, tb = sys.exc_info()
- parts = traceback.extract_tb(tb)
- typeguard_lines = [part for part in parts
- if part.filename.endswith("typeguard/__init__.py")]
- assert len(typeguard_lines) == 1
diff --git a/contrib/python/typeguard/tests/test_typeguard_py36.py b/contrib/python/typeguard/tests/test_typeguard_py36.py
deleted file mode 100644
index 383f7f3353f..00000000000
--- a/contrib/python/typeguard/tests/test_typeguard_py36.py
+++ /dev/null
@@ -1,189 +0,0 @@
-import sys
-import warnings
-from typing import Any, AsyncGenerator, AsyncIterable, AsyncIterator, Callable, Dict
-
-import pytest
-from typing_extensions import Protocol, runtime_checkable
-
-from typeguard import TypeChecker, typechecked
-
-try:
- from typing import TypedDict
-except ImportError:
- from typing_extensions import TypedDict
-
-
-@runtime_checkable
-class RuntimeProtocol(Protocol):
- member: int
-
- def meth(self) -> None:
- ...
-
-
-class TestTypeChecked:
- @pytest.mark.parametrize('annotation', [
- AsyncGenerator[int, str],
- AsyncIterable[int],
- AsyncIterator[int]
- ], ids=['generator', 'iterable', 'iterator'])
- def test_async_generator(self, annotation):
- async def run_generator():
- @typechecked
- async def genfunc() -> annotation:
- values.append((yield 2))
- values.append((yield 3))
- values.append((yield 4))
-
- gen = genfunc()
-
- value = await gen.asend(None)
- with pytest.raises(StopAsyncIteration):
- while True:
- value = await gen.asend(str(value))
- assert isinstance(value, int)
-
- values = []
- coro = run_generator()
- try:
- for elem in coro.__await__():
- print(elem)
- except StopAsyncIteration as exc:
- values = exc.value
-
- assert values == ['2', '3', '4']
-
- @pytest.mark.parametrize('annotation', [
- AsyncGenerator[int, str],
- AsyncIterable[int],
- AsyncIterator[int]
- ], ids=['generator', 'iterable', 'iterator'])
- def test_async_generator_bad_yield(self, annotation):
- @typechecked
- async def genfunc() -> annotation:
- yield 'foo'
-
- gen = genfunc()
- with pytest.raises(TypeError) as exc:
- next(gen.__anext__().__await__())
-
- exc.match('type of value yielded from generator must be int; got str instead')
-
- def test_async_generator_bad_send(self):
- @typechecked
- async def genfunc() -> AsyncGenerator[int, str]:
- yield 1
- yield 2
-
- gen = genfunc()
- pytest.raises(StopIteration, next, gen.__anext__().__await__())
- with pytest.raises(TypeError) as exc:
- next(gen.asend(2).__await__())
-
- exc.match('type of value sent to generator must be str; got int instead')
-
- def test_return_async_generator(self):
- @typechecked
- async def genfunc() -> AsyncGenerator[int, None]:
- yield 1
-
- @typechecked
- def foo() -> AsyncGenerator[int, None]:
- return genfunc()
-
- foo()
-
- def test_async_generator_iterate(self):
- asyncgen = typechecked(asyncgenfunc)()
- aiterator = asyncgen.__aiter__()
- exc = pytest.raises(StopIteration, aiterator.__anext__().send, None)
- assert exc.value.value == 1
-
- def test_typeddict_inherited(self):
- class ParentDict(TypedDict):
- x: int
-
- class ChildDict(ParentDict, total=False):
- y: int
-
- @typechecked
- def foo(arg: ChildDict):
- pass
-
- foo({'x': 1})
- if sys.version_info[:2] != (3, 8):
- # TypedDict is unusable for runtime checking on Python 3.8
- pytest.raises(TypeError, foo, {'y': 1})
-
- def test_mapping_is_not_typeddict(self):
- """Regression test for #216."""
-
- class Foo(Dict[str, Any]):
- pass
-
- @typechecked
- def foo(arg: Foo):
- pass
-
- foo(Foo({'x': 1}))
-
-
-async def asyncgenfunc() -> AsyncGenerator[int, None]:
- yield 1
-
-
-async def asyncgeniterablefunc() -> AsyncIterable[int]:
- yield 1
-
-
-async def asyncgeniteratorfunc() -> AsyncIterator[int]:
- yield 1
-
-
-class TestTypeChecker:
- @pytest.fixture
- def checker(self):
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- return TypeChecker(__name__)
-
- @pytest.mark.parametrize('func', [asyncgenfunc, asyncgeniterablefunc, asyncgeniteratorfunc],
- ids=['generator', 'iterable', 'iterator'])
- def test_async_generator(self, checker, func):
- """Make sure that the type checker does not complain about the None return value."""
- with checker, pytest.warns(None) as record:
- func()
-
- assert len(record) == 0
-
- def test_callable(self):
- class command:
- # we need an __annotations__ attribute to trigger the code path
- whatever: float
-
- def __init__(self, function: Callable[[int], int]):
- self.function = function
-
- def __call__(self, arg: int) -> None:
- self.function(arg)
-
- @typechecked
- @command
- def function(arg: int) -> None:
- pass
-
- function(1)
-
-
-def test_protocol_non_method_members():
- @typechecked
- def foo(a: RuntimeProtocol):
- pass
-
- class Foo:
- member = 1
-
- def meth(self) -> None:
- pass
-
- foo(Foo())
diff --git a/contrib/python/typeguard/tests/test_utils.py b/contrib/python/typeguard/tests/test_utils.py
new file mode 100644
index 00000000000..acde4bbdf4e
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_utils.py
@@ -0,0 +1,22 @@
+import pytest
+
+from typeguard._utils import function_name, qualified_name
+
+from . import Child
+
+
+ "inputval, add_class_prefix, expected",
+ [
+ pytest.param(qualified_name, False, "function", id="func"),
+ pytest.param(Child(), False, "__tests__.Child", id="instance"),
+ pytest.param(int, False, "int", id="builtintype"),
+ pytest.param(int, True, "class int", id="builtintype_classprefix"),
+ ],
+)
+def test_qualified_name(inputval, add_class_prefix, expected):
+ assert qualified_name(inputval, add_class_prefix=add_class_prefix) == expected
+
+
+def test_function_name():
+ assert function_name(function_name) == "typeguard._utils.function_name"
diff --git a/contrib/python/typeguard/tests/test_warn_on_error.py b/contrib/python/typeguard/tests/test_warn_on_error.py
new file mode 100644
index 00000000000..184b93b27e6
--- /dev/null
+++ b/contrib/python/typeguard/tests/test_warn_on_error.py
@@ -0,0 +1,28 @@
+from typing import List
+
+import pytest
+
+from typeguard import TypeCheckWarning, check_type, config, typechecked, warn_on_error
+
+
+def test_check_type(recwarn):
+ with pytest.warns(TypeCheckWarning) as warning:
+ check_type(1, str, typecheck_fail_callback=warn_on_error)
+
+ assert len(warning.list) == 1
+ assert warning.list[0].filename == __file__
+ assert warning.list[0].lineno == test_check_type.__code__.co_firstlineno + 2
+
+
+def test_typechecked(monkeypatch, recwarn):
+ @typechecked
+ def foo() -> List[int]:
+ return ["aa"] # type: ignore[list-item]
+
+ monkeypatch.setattr(config, "typecheck_fail_callback", warn_on_error)
+ with pytest.warns(TypeCheckWarning) as warning:
+ foo()
+
+ assert len(warning.list) == 1
+ assert warning.list[0].filename == __file__
+ assert warning.list[0].lineno == test_typechecked.__code__.co_firstlineno + 3
diff --git a/contrib/python/typeguard/tests/ya.make b/contrib/python/typeguard/tests/ya.make
index 0649ac4e9f7..a91224bb36b 100644
--- a/contrib/python/typeguard/tests/ya.make
+++ b/contrib/python/typeguard/tests/ya.make
@@ -7,10 +7,19 @@ PEERDIR(
TEST_SRCS(
conftest.py
- dummymodule.py
+ __init__.py
+ mypy/test_type_annotations.py
+ pep695.py
+ test_checkers.py
test_importhook.py
- test_typeguard.py
- test_typeguard_py36.py
+ test_instrumentation.py
+ test_plugins.py
+ test_pytest_plugin.py
+ test_suppression.py
+ test_transformer.py
+ test_typechecked.py
+ test_utils.py
+ test_warn_on_error.py
)
DATA(