Skip to content

Commit

Permalink
Fixed Dict-derived classes being mistaken for TypedDict on Python 3.10+
Browse files Browse the repository at this point in the history
Fixes #216.
  • Loading branch information
agronholm committed Nov 22, 2021
1 parent 8cfbe29 commit 0ec3dad
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-v
**UNRELEASED**

- Fixed ``@typechecked`` replacing abstract properties with regular properties
- Fixed any generic type subclassing ``Dict`` being mistakenly checked as ``TypedDict`` on
Python 3.10

**2.13.0** (2021-10-11)

Expand Down
15 changes: 14 additions & 1 deletion src/typeguard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ def isasyncgenfunction(func):
from typing import _ForwardRef as ForwardRef
evaluate_forwardref = ForwardRef._eval_type

if sys.version_info >= (3, 10):
from typing import is_typeddict
elif sys.version_info >= (3, 9):
def is_typeddict(tp) -> bool:
from typing import _TypedDictMeta

return isinstance(tp, _TypedDictMeta)
else:
def is_typeddict(tp) -> bool:
from typing_extensions import _TypedDictMeta

return isinstance(tp, _TypedDictMeta)


if TYPE_CHECKING:
_F = TypeVar("_F")
Expand Down Expand Up @@ -750,7 +763,7 @@ def check_type(argname: str, value, expected_type, memo: Optional[_TypeCheckMemo
check_typevar(argname, value, expected_type, memo)
elif issubclass(expected_type, IO):
check_io(argname, value, expected_type)
elif issubclass(expected_type, dict) and hasattr(expected_type, '__annotations__'):
elif is_typeddict(expected_type):
check_typed_dict(argname, value, expected_type, memo)
elif getattr(expected_type, '_is_protocol', False):
check_protocol(argname, value, expected_type)
Expand Down
14 changes: 13 additions & 1 deletion tests/test_typeguard_py36.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import warnings
from typing import AsyncGenerator, AsyncIterable, AsyncIterator, Callable
from typing import Any, AsyncGenerator, AsyncIterable, AsyncIterator, Callable, Dict

import pytest
from typing_extensions import Protocol, runtime_checkable
Expand Down Expand Up @@ -112,6 +112,18 @@ def foo(arg: ChildDict):
foo({'x': 1})
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
Expand Down

0 comments on commit 0ec3dad

Please sign in to comment.