Skip to content

Commit

Permalink
Better error handling and skip compat test cases in 3.13+.
Browse files Browse the repository at this point in the history
This also fixes the lint errors.

Signed-off-by: Zixuan James Li <p359101898@gmail.com>
  • Loading branch information
PIG208 committed Feb 18, 2024
1 parent 572531b commit 1fb2c64
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 18 deletions.
36 changes: 22 additions & 14 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
# 3.12 changes the representation of Unpack[] (PEP 692)
TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0)

# 3.13 drops support for the keyword argument syntax of TypedDict
TYPING_3_13_0 = sys.version_info[:3] >= (3, 13, 0)

# https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10
# versions, but not all
HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters
Expand Down Expand Up @@ -3822,10 +3825,10 @@ class ChildWithInlineAndOptional(Untotal, Inline):

class Closed(TypedDict, closed=True):
__extra_items__: None

class Unclosed(TypedDict, closed=False):
...

class ChildUnclosed(Closed, Unclosed):
...

Expand All @@ -3834,7 +3837,7 @@ class ChildUnclosed(Closed, Unclosed):

class ChildClosed(Unclosed, Closed):
...

self.assertFalse(ChildClosed.__closed__)
self.assertEqual(ChildClosed.__extra_items__, type(None))

Expand Down Expand Up @@ -4195,14 +4198,14 @@ class AllTheThings(TypedDict):
self.assertEqual(AllTheThings.__optional_keys__, frozenset({'c', 'd'}))
self.assertEqual(AllTheThings.__readonly_keys__, frozenset({'a', 'b', 'c'}))
self.assertEqual(AllTheThings.__mutable_keys__, frozenset({'d'}))

def test_extra_keys_non_readonly(self):
class Base(TypedDict, closed=True):
__extra_items__: str

class Child(Base):
a: NotRequired[int]

self.assertEqual(Child.__required_keys__, frozenset({}))
self.assertEqual(Child.__optional_keys__, frozenset({'a'}))
self.assertEqual(Child.__readonly_keys__, frozenset({}))
Expand All @@ -4211,28 +4214,28 @@ class Child(Base):
def test_extra_keys_readonly(self):
class Base(TypedDict, closed=True):
__extra_items__: ReadOnly[str]

class Child(Base):
a: NotRequired[str]

self.assertEqual(Child.__required_keys__, frozenset({}))
self.assertEqual(Child.__optional_keys__, frozenset({'a'}))
self.assertEqual(Child.__readonly_keys__, frozenset({}))
self.assertEqual(Child.__mutable_keys__, frozenset({'a'}))

def test_extra_key_required(self):
with self.assertRaisesRegex(
TypeError,
"Special key __extra_items__ does not support Required and NotRequired"
"Special key __extra_items__ does not support Required"
):
TypedDict("A", {"__extra_items__": Required[int]}, closed=True)

with self.assertRaisesRegex(
TypeError,
"Special key __extra_items__ does not support Required and NotRequired"
"Special key __extra_items__ does not support NotRequired"
):
TypedDict("A", {"__extra_items__": NotRequired[int]}, closed=True)

def test_regular_extra_items(self):
class ExtraReadOnly(TypedDict):
__extra_items__: ReadOnly[str]
Expand Down Expand Up @@ -4263,7 +4266,7 @@ class ExtraNotRequired(TypedDict):
self.assertEqual(ExtraNotRequired.__mutable_keys__, frozenset({'__extra_items__'}))
self.assertEqual(ExtraNotRequired.__extra_items__, None)
self.assertFalse(ExtraNotRequired.__closed__)

def test_closed_inheritance(self):
class Base(TypedDict, closed=True):
__extra_items__: ReadOnly[Union[str, None]]
Expand Down Expand Up @@ -4298,7 +4301,7 @@ class GrandChild(Child, closed=True):
self.assertEqual(GrandChild.__annotations__, {"__extra_items__": int, "a": int})
self.assertEqual(GrandChild.__extra_items__, str)
self.assertTrue(GrandChild.__closed__)

def test_implicit_extra_items(self):
class Base(TypedDict):
a: int
Expand All @@ -4318,6 +4321,11 @@ class ChildB(Base, closed=True):
self.assertEqual(ChildB.__extra_items__, type(None))
self.assertTrue(ChildB.__closed__)

@skipIf(
TYPING_3_13_0,
"The keyword argument alternative to define a "
"TypedDict type using the functional syntax is no longer supported"
)
def test_backwards_compatibility(self):
with self.assertWarns(DeprecationWarning):
TD = TypedDict("TD", closed=int)
Expand Down
14 changes: 10 additions & 4 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,18 +930,24 @@ def __new__(cls, name, bases, ns, *, total=True, closed=False):
optional_keys.update(base_dict.get('__optional_keys__', ()))
readonly_keys.update(base_dict.get('__readonly_keys__', ()))
mutable_keys.update(base_dict.get('__mutable_keys__', ()))
if (base_extra_items_type := base_dict.get('__extra_items__', None)) is not None:
base_extra_items_type = base_dict.get('__extra_items__', None)
if base_extra_items_type is not None:
extra_items_type = base_extra_items_type

if closed and extra_items_type is None:
extra_items_type = Never
if closed and "__extra_items__" in own_annotations:
annotation_type = own_annotations.pop("__extra_items__")
qualifiers = set(_get_typeddict_qualifiers(annotation_type))
if Required in qualifiers or NotRequired in qualifiers:
if Required in qualifiers:
raise TypeError(
"Special key __extra_items__ does not support "
"Required"
)
if NotRequired in qualifiers:
raise TypeError(
"Special key __extra_items__ does not support "
"Required and NotRequired"
"NotRequired"
)
extra_items_type = annotation_type

Expand Down

0 comments on commit 1fb2c64

Please sign in to comment.