|
52 | 52 | # 3.12 changes the representation of Unpack[] (PEP 692)
|
53 | 53 | TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0)
|
54 | 54 |
|
| 55 | +# 3.13 drops support for the keyword argument syntax of TypedDict |
| 56 | +TYPING_3_13_0 = sys.version_info[:3] >= (3, 13, 0) |
| 57 | + |
55 | 58 | # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10
|
56 | 59 | # versions, but not all
|
57 | 60 | HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters
|
@@ -3820,6 +3823,24 @@ class ChildWithInlineAndOptional(Untotal, Inline):
|
3820 | 3823 | {'inline': bool, 'untotal': str, 'child': bool},
|
3821 | 3824 | )
|
3822 | 3825 |
|
| 3826 | + class Closed(TypedDict, closed=True): |
| 3827 | + __extra_items__: None |
| 3828 | + |
| 3829 | + class Unclosed(TypedDict, closed=False): |
| 3830 | + ... |
| 3831 | + |
| 3832 | + class ChildUnclosed(Closed, Unclosed): |
| 3833 | + ... |
| 3834 | + |
| 3835 | + self.assertFalse(ChildUnclosed.__closed__) |
| 3836 | + self.assertEqual(ChildUnclosed.__extra_items__, type(None)) |
| 3837 | + |
| 3838 | + class ChildClosed(Unclosed, Closed): |
| 3839 | + ... |
| 3840 | + |
| 3841 | + self.assertFalse(ChildClosed.__closed__) |
| 3842 | + self.assertEqual(ChildClosed.__extra_items__, type(None)) |
| 3843 | + |
3823 | 3844 | wrong_bases = [
|
3824 | 3845 | (One, Regular),
|
3825 | 3846 | (Regular, One),
|
@@ -4178,6 +4199,139 @@ class AllTheThings(TypedDict):
|
4178 | 4199 | self.assertEqual(AllTheThings.__readonly_keys__, frozenset({'a', 'b', 'c'}))
|
4179 | 4200 | self.assertEqual(AllTheThings.__mutable_keys__, frozenset({'d'}))
|
4180 | 4201 |
|
| 4202 | + def test_extra_keys_non_readonly(self): |
| 4203 | + class Base(TypedDict, closed=True): |
| 4204 | + __extra_items__: str |
| 4205 | + |
| 4206 | + class Child(Base): |
| 4207 | + a: NotRequired[int] |
| 4208 | + |
| 4209 | + self.assertEqual(Child.__required_keys__, frozenset({})) |
| 4210 | + self.assertEqual(Child.__optional_keys__, frozenset({'a'})) |
| 4211 | + self.assertEqual(Child.__readonly_keys__, frozenset({})) |
| 4212 | + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) |
| 4213 | + |
| 4214 | + def test_extra_keys_readonly(self): |
| 4215 | + class Base(TypedDict, closed=True): |
| 4216 | + __extra_items__: ReadOnly[str] |
| 4217 | + |
| 4218 | + class Child(Base): |
| 4219 | + a: NotRequired[str] |
| 4220 | + |
| 4221 | + self.assertEqual(Child.__required_keys__, frozenset({})) |
| 4222 | + self.assertEqual(Child.__optional_keys__, frozenset({'a'})) |
| 4223 | + self.assertEqual(Child.__readonly_keys__, frozenset({})) |
| 4224 | + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) |
| 4225 | + |
| 4226 | + def test_extra_key_required(self): |
| 4227 | + with self.assertRaisesRegex( |
| 4228 | + TypeError, |
| 4229 | + "Special key __extra_items__ does not support Required" |
| 4230 | + ): |
| 4231 | + TypedDict("A", {"__extra_items__": Required[int]}, closed=True) |
| 4232 | + |
| 4233 | + with self.assertRaisesRegex( |
| 4234 | + TypeError, |
| 4235 | + "Special key __extra_items__ does not support NotRequired" |
| 4236 | + ): |
| 4237 | + TypedDict("A", {"__extra_items__": NotRequired[int]}, closed=True) |
| 4238 | + |
| 4239 | + def test_regular_extra_items(self): |
| 4240 | + class ExtraReadOnly(TypedDict): |
| 4241 | + __extra_items__: ReadOnly[str] |
| 4242 | + |
| 4243 | + self.assertEqual(ExtraReadOnly.__required_keys__, frozenset({'__extra_items__'})) |
| 4244 | + self.assertEqual(ExtraReadOnly.__optional_keys__, frozenset({})) |
| 4245 | + self.assertEqual(ExtraReadOnly.__readonly_keys__, frozenset({'__extra_items__'})) |
| 4246 | + self.assertEqual(ExtraReadOnly.__mutable_keys__, frozenset({})) |
| 4247 | + self.assertEqual(ExtraReadOnly.__extra_items__, None) |
| 4248 | + self.assertFalse(ExtraReadOnly.__closed__) |
| 4249 | + |
| 4250 | + class ExtraRequired(TypedDict): |
| 4251 | + __extra_items__: Required[str] |
| 4252 | + |
| 4253 | + self.assertEqual(ExtraRequired.__required_keys__, frozenset({'__extra_items__'})) |
| 4254 | + self.assertEqual(ExtraRequired.__optional_keys__, frozenset({})) |
| 4255 | + self.assertEqual(ExtraRequired.__readonly_keys__, frozenset({})) |
| 4256 | + self.assertEqual(ExtraRequired.__mutable_keys__, frozenset({'__extra_items__'})) |
| 4257 | + self.assertEqual(ExtraRequired.__extra_items__, None) |
| 4258 | + self.assertFalse(ExtraRequired.__closed__) |
| 4259 | + |
| 4260 | + class ExtraNotRequired(TypedDict): |
| 4261 | + __extra_items__: NotRequired[str] |
| 4262 | + |
| 4263 | + self.assertEqual(ExtraNotRequired.__required_keys__, frozenset({})) |
| 4264 | + self.assertEqual(ExtraNotRequired.__optional_keys__, frozenset({'__extra_items__'})) |
| 4265 | + self.assertEqual(ExtraNotRequired.__readonly_keys__, frozenset({})) |
| 4266 | + self.assertEqual(ExtraNotRequired.__mutable_keys__, frozenset({'__extra_items__'})) |
| 4267 | + self.assertEqual(ExtraNotRequired.__extra_items__, None) |
| 4268 | + self.assertFalse(ExtraNotRequired.__closed__) |
| 4269 | + |
| 4270 | + def test_closed_inheritance(self): |
| 4271 | + class Base(TypedDict, closed=True): |
| 4272 | + __extra_items__: ReadOnly[Union[str, None]] |
| 4273 | + |
| 4274 | + self.assertEqual(Base.__required_keys__, frozenset({})) |
| 4275 | + self.assertEqual(Base.__optional_keys__, frozenset({})) |
| 4276 | + self.assertEqual(Base.__readonly_keys__, frozenset({})) |
| 4277 | + self.assertEqual(Base.__mutable_keys__, frozenset({})) |
| 4278 | + self.assertEqual(Base.__annotations__, {}) |
| 4279 | + self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]]) |
| 4280 | + self.assertTrue(Base.__closed__) |
| 4281 | + |
| 4282 | + class Child(Base): |
| 4283 | + a: int |
| 4284 | + __extra_items__: int |
| 4285 | + |
| 4286 | + self.assertEqual(Child.__required_keys__, frozenset({'a', "__extra_items__"})) |
| 4287 | + self.assertEqual(Child.__optional_keys__, frozenset({})) |
| 4288 | + self.assertEqual(Child.__readonly_keys__, frozenset({})) |
| 4289 | + self.assertEqual(Child.__mutable_keys__, frozenset({'a', "__extra_items__"})) |
| 4290 | + self.assertEqual(Child.__annotations__, {"__extra_items__": int, "a": int}) |
| 4291 | + self.assertEqual(Child.__extra_items__, ReadOnly[Union[str, None]]) |
| 4292 | + self.assertFalse(Child.__closed__) |
| 4293 | + |
| 4294 | + class GrandChild(Child, closed=True): |
| 4295 | + __extra_items__: str |
| 4296 | + |
| 4297 | + self.assertEqual(GrandChild.__required_keys__, frozenset({'a', "__extra_items__"})) |
| 4298 | + self.assertEqual(GrandChild.__optional_keys__, frozenset({})) |
| 4299 | + self.assertEqual(GrandChild.__readonly_keys__, frozenset({})) |
| 4300 | + self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a', "__extra_items__"})) |
| 4301 | + self.assertEqual(GrandChild.__annotations__, {"__extra_items__": int, "a": int}) |
| 4302 | + self.assertEqual(GrandChild.__extra_items__, str) |
| 4303 | + self.assertTrue(GrandChild.__closed__) |
| 4304 | + |
| 4305 | + def test_implicit_extra_items(self): |
| 4306 | + class Base(TypedDict): |
| 4307 | + a: int |
| 4308 | + |
| 4309 | + self.assertEqual(Base.__extra_items__, None) |
| 4310 | + self.assertFalse(Base.__closed__) |
| 4311 | + |
| 4312 | + class ChildA(Base, closed=True): |
| 4313 | + ... |
| 4314 | + |
| 4315 | + self.assertEqual(ChildA.__extra_items__, Never) |
| 4316 | + self.assertTrue(ChildA.__closed__) |
| 4317 | + |
| 4318 | + class ChildB(Base, closed=True): |
| 4319 | + __extra_items__: None |
| 4320 | + |
| 4321 | + self.assertEqual(ChildB.__extra_items__, type(None)) |
| 4322 | + self.assertTrue(ChildB.__closed__) |
| 4323 | + |
| 4324 | + @skipIf( |
| 4325 | + TYPING_3_13_0, |
| 4326 | + "The keyword argument alternative to define a " |
| 4327 | + "TypedDict type using the functional syntax is no longer supported" |
| 4328 | + ) |
| 4329 | + def test_backwards_compatibility(self): |
| 4330 | + with self.assertWarns(DeprecationWarning): |
| 4331 | + TD = TypedDict("TD", closed=int) |
| 4332 | + self.assertFalse(TD.__closed__) |
| 4333 | + self.assertEqual(TD.__annotations__, {"closed": int}) |
| 4334 | + |
4181 | 4335 |
|
4182 | 4336 | class AnnotatedTests(BaseTestCase):
|
4183 | 4337 |
|
|
0 commit comments