From cf980d689586e61933bbeca68f35135fb3b6ba14 Mon Sep 17 00:00:00 2001 From: David Foster Date: Sat, 11 Nov 2023 09:06:38 -0500 Subject: [PATCH] Fix to recognize TypedDict values with extra keys Fixes #19 --- tests.py | 34 +++++++++++++++++++++++++++++----- trycast.py | 5 ++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/tests.py b/tests.py index fd18cea..d8cd155 100644 --- a/tests.py +++ b/tests.py @@ -940,20 +940,39 @@ class Point3D(RichTypedDict): z: int # Point2D + self.assertTryCastFailure(Point2D, {"x": 1}) self.assertTryCastSuccess(Point2D, {"x": 1, "y": 1}) - self.assertTryCastFailure(Point2D, {"x": 1, "y": 1, "z": 1}) + self.assertTryCastFailure(Point2D, {"x": 1, "y": "string"}) + self.assertTryCastSuccess(Point2D, {"x": 1, "y": 1, "z": 1}) + self.assertTryCastSuccess(Point2D, {"x": 1, "y": 1, "z": "string"}) # PartialPoint2D self.assertTryCastSuccess(PartialPoint2D, {"x": 1, "y": 1}) + self.assertTryCastFailure(PartialPoint2D, {"x": 1, "y": "string"}) self.assertTryCastSuccess(PartialPoint2D, {"y": 1}) self.assertTryCastSuccess(PartialPoint2D, {"x": 1}) self.assertTryCastSuccess(PartialPoint2D, {}) - self.assertTryCastFailure(PartialPoint2D, {"x": 1, "y": 1, "z": 1}) + self.assertTryCastSuccess(PartialPoint2D, {"x": 1, "y": 1, "z": 1}) + self.assertTryCastSuccess(PartialPoint2D, {"x": 1, "y": 1, "z": "string"}) # Point3D self.assertTryCastFailure(Point3D, {"x": 1, "y": 1}) self.assertTryCastSuccess(Point3D, {"x": 1, "y": 1, "z": 1}) + def test_typeddict_with_extra_keys(self) -> None: + class Packet(NativeTypedDict): + type: str + payload: str + + class PacketWithExtra(Packet): + extra: str + + p = PacketWithExtra(type="hello", payload="v1", extra="english") + p2: Packet = p + + self.assertTryCastSuccess(PacketWithExtra, p) + self.assertTryCastSuccess(Packet, p2) + def test_typeddict_single_inheritance(self) -> None: _1 = _BookBasedMovie( name="Blade Runner", @@ -1182,7 +1201,7 @@ def __getitem__(self, key: str) -> object: if key == "name": return self._name else: - raise AttributeError # pragma: no cover + raise KeyError # pragma: no cover def __len__(self) -> int: return 1 # pragma: no cover @@ -1828,16 +1847,21 @@ class TaggedMaybePoint1D(MaybePoint1D): name: str self.assertTryCastSuccess(Point2D, {"x": 1, "y": 2}, strict=False) + self.assertTryCastFailure(Point2D, {"x": 1, "y": "string"}, strict=False) self.assertTryCastFailure(Point2D, {"x": 1}, strict=False) self.assertTryCastSuccess(Point3D, {"x": 1, "y": 2, "z": 3}, strict=False) + self.assertTryCastFailure( + Point3D, {"x": 1, "y": 2, "z": "string"}, strict=False + ) self.assertTryCastSuccess(Point3D, {"x": 1, "y": 2}, strict=False) self.assertTryCastSuccess(Point3D, {"x": 1}, strict=False) # surprise! - self.assertTryCastFailure(Point3D, {"q": 1}, strict=False) + self.assertTryCastSuccess(Point3D, {"q": 1}, strict=False) # surprise! self.assertTryCastSuccess(MaybePoint1D, {"x": 1}, strict=False) + self.assertTryCastFailure(MaybePoint1D, {"x": "string"}, strict=False) self.assertTryCastSuccess(MaybePoint1D, {}, strict=False) - self.assertTryCastFailure(MaybePoint1D, {"q": 1}, strict=False) + self.assertTryCastSuccess(MaybePoint1D, {"q": 1}, strict=False) # surprise! self.assertTryCastSuccess( TaggedMaybePoint1D, {"x": 1, "name": "one"}, strict=False diff --git a/trycast.py b/trycast.py index bfe284b..60cd5e1 100644 --- a/trycast.py +++ b/trycast.py @@ -699,7 +699,10 @@ def _trycast_inner(tp, value, failure, options): for (k, v) in value.items(): # type: ignore[attribute-error] # pytype V = resolved_annotations.get(k, _MISSING) - if V is _MISSING or _trycast_inner(V, v, _FAILURE, options) is _FAILURE: + if ( + V is not _MISSING + and _trycast_inner(V, v, _FAILURE, options) is _FAILURE + ): return failure for k in required_keys: