Skip to content

Commit d93860a

Browse files
committed
allow TypedDict as a type argument
1 parent b07d245 commit d93860a

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Unreleased
2+
3+
- Fix usage of `typing_extensions.TypedDict` nested inside other types
4+
(e.g., `typing.Type[typing_extensions.TypedDict]`). This is not allowed by the
5+
type system but worked on older versions, so we maintain support.
6+
17
# Release 4.14.0 (June 2, 2025)
28

39
Changes since 4.14.0rc1:

src/test_typing_extensions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4202,6 +4202,12 @@ def test_basics_functional_syntax(self):
42024202
self.assertEqual(Emp.__annotations__, {'name': str, 'id': int})
42034203
self.assertEqual(Emp.__total__, True)
42044204

4205+
def test_allowed_as_type_argument(self):
4206+
# https://github.com/python/typing_extensions/issues/613
4207+
obj = typing.Type[typing_extensions.TypedDict]
4208+
self.assertIs(typing_extensions.get_origin(obj), type)
4209+
self.assertEqual(typing_extensions.get_args(obj), (typing_extensions.TypedDict,))
4210+
42054211
@skipIf(sys.version_info < (3, 13), "Change in behavior in 3.13")
42064212
def test_keywords_syntax_raises_on_3_13(self):
42074213
with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning):

src/typing_extensions.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,50 @@ def _create_typeddict(
12231223
td.__orig_bases__ = (TypedDict,)
12241224
return td
12251225

1226-
class _TypedDictSpecialForm(_ExtensionsSpecialForm, _root=True):
1226+
# Cannot inherit from typing._SpecialForm, because then typing._type_check
1227+
# would reject TypedDict as a type, and we need that to work for compatibility.
1228+
if hasattr(typing, "_NotIterable"):
1229+
_special_form_bases = (typing._Final, typing._NotIterable)
1230+
else:
1231+
_special_form_bases = (typing._Final,)
1232+
1233+
class _TypedDictSpecialForm(*_special_form_bases, _root=True):
1234+
__slots__ = ('_name', '__doc__', '_getitem')
1235+
1236+
def __init__(self, getitem):
1237+
self._getitem = getitem
1238+
self._name = getitem.__name__
1239+
self.__doc__ = getitem.__doc__
1240+
1241+
def __getattr__(self, item):
1242+
if item in {'__name__', '__qualname__'}:
1243+
return self._name
1244+
1245+
raise AttributeError(item)
1246+
1247+
def __repr__(self):
1248+
return 'typing_extensions.' + self._name
1249+
1250+
def __reduce__(self):
1251+
return self._name
1252+
1253+
if sys.version_info >= (3, 10):
1254+
def __or__(self, other):
1255+
return typing.Union[self, other]
1256+
1257+
def __ror__(self, other):
1258+
return typing.Union[other, self]
1259+
1260+
def __instancecheck__(self, obj):
1261+
raise TypeError(f"{self} cannot be used with isinstance()")
1262+
1263+
def __subclasscheck__(self, cls):
1264+
raise TypeError(f"{self} cannot be used with issubclass()")
1265+
1266+
@typing._tp_cache
1267+
def __getitem__(self, parameters):
1268+
return self._getitem(self, parameters)
1269+
12271270
def __call__(
12281271
self,
12291272
typename,

0 commit comments

Comments
 (0)