Skip to content

Commit ad144bf

Browse files
Fix TypedDict qualifier inheritance with same name
1 parent 9215c95 commit ad144bf

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

src/test_typing_extensions.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4638,6 +4638,47 @@ class ChildWithInlineAndOptional(Untotal, Inline):
46384638
class Wrong(*bases):
46394639
pass
46404640

4641+
def test_keys_inheritance_with_same_name(self):
4642+
class NotTotal(TypedDict, total=False):
4643+
a: int
4644+
4645+
class Total(NotTotal):
4646+
a: int
4647+
4648+
self.assertEqual(NotTotal.__required_keys__, frozenset())
4649+
self.assertEqual(NotTotal.__optional_keys__, frozenset(['a']))
4650+
self.assertEqual(Total.__required_keys__, frozenset(['a']))
4651+
self.assertEqual(Total.__optional_keys__, frozenset())
4652+
4653+
class Base(TypedDict):
4654+
a: NotRequired[int]
4655+
b: Required[int]
4656+
4657+
class Child(Base):
4658+
a: Required[int]
4659+
b: NotRequired[int]
4660+
4661+
self.assertEqual(Base.__required_keys__, frozenset(['b']))
4662+
self.assertEqual(Base.__optional_keys__, frozenset(['a']))
4663+
self.assertEqual(Child.__required_keys__, frozenset(['a']))
4664+
self.assertEqual(Child.__optional_keys__, frozenset(['b']))
4665+
4666+
def test_multiple_inheritance_with_same_key(self):
4667+
class Base1(TypedDict):
4668+
a: NotRequired[int]
4669+
4670+
class Base2(TypedDict):
4671+
a: Required[str]
4672+
4673+
class Child(Base1, Base2):
4674+
pass
4675+
4676+
# Last base wins
4677+
self.assertEqual(Child.__annotations__, {'a': Required[str]})
4678+
self.assertEqual(Child.__required_keys__, frozenset(['a']))
4679+
self.assertEqual(Child.__optional_keys__, frozenset())
4680+
4681+
46414682
def test_closed_values(self):
46424683
class Implicit(TypedDict): ...
46434684
class ExplicitTrue(TypedDict, closed=True): ...

src/typing_extensions.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,8 +1182,14 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
11821182

11831183
if sys.version_info <= (3, 14):
11841184
annotations.update(base_dict.get('__annotations__', {}))
1185-
required_keys.update(base_dict.get('__required_keys__', ()))
1186-
optional_keys.update(base_dict.get('__optional_keys__', ()))
1185+
base_required = base_dict.get('__required_keys__', set())
1186+
required_keys |= base_required
1187+
optional_keys -= base_required
1188+
1189+
base_optional = base_dict.get('__optional_keys__', set())
1190+
required_keys -= base_optional
1191+
optional_keys |= base_optional
1192+
11871193
readonly_keys.update(base_dict.get('__readonly_keys__', ()))
11881194
mutable_keys.update(base_dict.get('__mutable_keys__', ()))
11891195

@@ -1211,13 +1217,19 @@ def __new__(cls, name, bases, ns, *, total=True, closed=None,
12111217
qualifiers = set(_get_typeddict_qualifiers(annotation_type))
12121218

12131219
if Required in qualifiers:
1214-
required_keys.add(annotation_key)
1220+
is_required = True
12151221
elif NotRequired in qualifiers:
1216-
optional_keys.add(annotation_key)
1217-
elif total:
1222+
is_required = False
1223+
else:
1224+
is_required = total
1225+
1226+
if is_required:
12181227
required_keys.add(annotation_key)
1228+
optional_keys.discard(annotation_key)
12191229
else:
12201230
optional_keys.add(annotation_key)
1231+
required_keys.discard(annotation_key)
1232+
12211233
if ReadOnly in qualifiers:
12221234
mutable_keys.discard(annotation_key)
12231235
readonly_keys.add(annotation_key)

0 commit comments

Comments
 (0)