Skip to content

Commit

Permalink
Fixed compatibility with Python 3.10
Browse files Browse the repository at this point in the history
Fixes #164.
  • Loading branch information
agronholm committed Feb 16, 2021
1 parent b3cec2f commit 16fa163
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 52 deletions.
4 changes: 4 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ Version history

This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-versioning-200>`_.

**UNRELEASED**

- Fixed compatibility with Python 3.10

**2.11.0** (2021-02-13)

- Added support for type checking class properties (PR by Ethan Pronovost)
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ classifiers =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10

[options]
packages = typeguard
Expand Down
79 changes: 31 additions & 48 deletions tests/test_typeguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,36 @@
# 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', 'Child')
TIntStr = TypeVar('TIntStr', int, str)
TIntCollection = TypeVar('TIntCollection', int, Collection)
TParent = TypeVar('TParent', bound='Parent')
TChild = TypeVar('TChild', bound='Child')
TCovariant = TypeVar('TCovariant', covariant=True)
TContravariant = TypeVar('TContravariant', contravariant=True)
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
Expand Down Expand Up @@ -411,16 +431,12 @@ def foo(a: Tuple[int, ...]):
'type of argument "a"[2] must be int; got str instead')

def test_namedtuple(self):
Employee = NamedTuple('Employee', [('name', str), ('id', int)])

def foo(bar: Employee):
assert check_argument_types()

foo(Employee('bob', 1))

def test_namedtuple_type_mismatch(self):
Employee = NamedTuple('Employee', [('name', str), ('id', int)])

def foo(bar: Employee):
assert check_argument_types()

Expand All @@ -429,8 +445,6 @@ def foo(bar: Employee):
r'(test_typeguard\.)?Employee; got tuple instead')

def test_namedtuple_wrong_field_type(self):
Employee = NamedTuple('Employee', [('name', str), ('id', int)])

def foo(bar: Employee):
assert check_argument_types()

Expand Down Expand Up @@ -466,89 +480,69 @@ def foo(a: Union[str, int]):
('aa', 'bb')
], ids=['int', 'str'])
def test_typevar_constraints(self, values):
T = TypeVar('T', int, str)

def foo(a: T, b: T):
def foo(a: TIntStr, b: TIntStr):
assert check_argument_types()

foo(*values)

def test_typevar_constraints_fail_typing_type(self):
T = TypeVar('T', int, Collection)

def foo(a: T, b: T):
def foo(a: TIntCollection, b: TIntCollection):
assert check_argument_types()

with pytest.raises(TypeError):
foo('aa', 'bb')

def test_typevar_constraints_fail(self):
T = TypeVar('T', int, str)

def foo(a: T, b: T):
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 be one of (int, str); got float '
'instead')

def test_typevar_bound(self):
T = TypeVar('T', bound=Parent)

def foo(a: T, b: T):
def foo(a: TParent, b: TParent):
assert check_argument_types()

foo(Child(), Child())

def test_typevar_bound_fail(self):
T = TypeVar('T', bound=Child)

def foo(a: T, b: T):
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 test_typeguard.Child or one of '
'its subclasses; got test_typeguard.Parent instead')

def test_typevar_invariant_fail(self):
T = TypeVar('T', int, str)

def foo(a: T, b: T):
def foo(a: TIntStr, b: TIntStr):
assert check_argument_types()

exc = pytest.raises(TypeError, foo, 2, 3.6)
assert str(exc.value) == 'type of argument "b" must be exactly int; got float instead'

def test_typevar_covariant(self):
T = TypeVar('T', covariant=True)

def foo(a: T, b: T):
def foo(a: TCovariant, b: TCovariant):
assert check_argument_types()

foo(Parent(), Child())

def test_typevar_covariant_fail(self):
T = TypeVar('T', covariant=True)

def foo(a: T, b: T):
def foo(a: TCovariant, b: TCovariant):
assert check_argument_types()

exc = pytest.raises(TypeError, foo, Child(), Parent())
assert str(exc.value) == ('type of argument "b" must be test_typeguard.Child or one of '
'its subclasses; got test_typeguard.Parent instead')

def test_typevar_contravariant(self):
T = TypeVar('T', contravariant=True)

def foo(a: T, b: T):
def foo(a: TContravariant, b: TContravariant):
assert check_argument_types()

foo(Child(), Parent())

def test_typevar_contravariant_fail(self):
T = TypeVar('T', contravariant=True)

def foo(a: T, b: T):
def foo(a: TContravariant, b: TContravariant):
assert check_argument_types()

exc = pytest.raises(TypeError, foo, Parent(), Child())
Expand Down Expand Up @@ -647,24 +641,13 @@ def foo(**kwargs: int):
exc.match(r'type of argument "kwargs"\[\'b\'\] must be int; got str instead')

def test_generic(self):
T_Foo = TypeVar('T_Foo')

class FooGeneric(Generic[T_Foo]):
pass

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):
try:
from typing import NewType
except ImportError:
pytest.skip('Skipping newtype test since no NewType in current typing library')

myint = NewType("myint", int)

def foo(a: myint) -> int:
assert check_argument_types()
return 42
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
minversion = 3.3.0
envlist = pypy3, py35, py36, py37, py38, py39, flake8
envlist = pypy3, py35, py36, py37, py38, py39, py310, flake8
skip_missing_interpreters = true
isolated_build = true

Expand Down
14 changes: 11 additions & 3 deletions typeguard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ def __init__(self, globals: Dict[str, Any], locals: Dict[str, Any]):
self.typevars = {} # type: Dict[Any, type]


def _strip_annotation(annotation):
if isinstance(annotation, str):
return annotation.strip("'")
else:
return annotation


class _CallMemo(_TypeCheckMemo):
__slots__ = 'func', 'func_name', 'arguments', 'is_generator', 'type_hints'

Expand Down Expand Up @@ -126,7 +133,7 @@ def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None

typename = str(exc).split("'", 2)[1]
for param in signature.parameters.values():
if param.annotation == typename:
if _strip_annotation(param.annotation) == typename:
break
else:
raise
Expand All @@ -135,10 +142,11 @@ def __init__(self, func: Callable, frame_locals: Optional[Dict[str, Any]] = None
if forward_refs_policy is ForwardRefPolicy.GUESS:
if param.name in self.arguments:
argtype = self.arguments[param.name].__class__
if param.annotation == argtype.__qualname__:
stripped = _strip_annotation(param.annotation)
if stripped == argtype.__qualname__:
func.__annotations__[param.name] = argtype
msg = ('Replaced forward declaration {!r} in {} with {!r}'
.format(param.annotation, func_name, argtype))
.format(stripped, func_name, argtype))
warn(TypeHintWarning(msg))
continue

Expand Down

0 comments on commit 16fa163

Please sign in to comment.