|
111 | 111 | TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0)
|
112 | 112 |
|
113 | 113 | # 3.12 changes the representation of Unpack[] (PEP 692)
|
| 114 | +# and adds PEP 695 to CPython's grammar |
114 | 115 | TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0)
|
115 | 116 |
|
116 | 117 | # 3.13 drops support for the keyword argument syntax of TypedDict
|
@@ -268,6 +269,7 @@ class UnannotatedClass:
|
268 | 269 |
|
269 | 270 | def unannotated_function(a, b, c): pass
|
270 | 271 | """
|
| 272 | + |
271 | 273 | STRINGIZED_ANNOTATIONS = """
|
272 | 274 | from __future__ import annotations
|
273 | 275 |
|
@@ -304,13 +306,110 @@ class MyClassWithLocalAnnotations:
|
304 | 306 | mytype = int
|
305 | 307 | x: mytype
|
306 | 308 | """
|
| 309 | + |
307 | 310 | STRINGIZED_ANNOTATIONS_2 = """
|
308 | 311 | from __future__ import annotations
|
309 | 312 |
|
310 | 313 |
|
311 | 314 | def foo(a, b, c): pass
|
312 | 315 | """
|
313 | 316 |
|
| 317 | +if TYPING_3_12_0: |
| 318 | + STRINGIZED_ANNOTATIONS_PEP_695 = textwrap.dedent( |
| 319 | + """ |
| 320 | + from __future__ import annotations |
| 321 | + from typing import Callable, Unpack |
| 322 | +
|
| 323 | +
|
| 324 | + class A[T, *Ts, **P]: |
| 325 | + x: T |
| 326 | + y: tuple[*Ts] |
| 327 | + z: Callable[P, str] |
| 328 | +
|
| 329 | +
|
| 330 | + class B[T, *Ts, **P]: |
| 331 | + T = int |
| 332 | + Ts = str |
| 333 | + P = bytes |
| 334 | + x: T |
| 335 | + y: Ts |
| 336 | + z: P |
| 337 | +
|
| 338 | +
|
| 339 | + Eggs = int |
| 340 | + Spam = str |
| 341 | +
|
| 342 | +
|
| 343 | + class C[Eggs, **Spam]: |
| 344 | + x: Eggs |
| 345 | + y: Spam |
| 346 | +
|
| 347 | +
|
| 348 | + def generic_function[T, *Ts, **P]( |
| 349 | + x: T, *y: Unpack[Ts], z: P.args, zz: P.kwargs |
| 350 | + ) -> None: ... |
| 351 | +
|
| 352 | +
|
| 353 | + def generic_function_2[Eggs, **Spam](x: Eggs, y: Spam): pass |
| 354 | +
|
| 355 | +
|
| 356 | + class D: |
| 357 | + Foo = int |
| 358 | + Bar = str |
| 359 | +
|
| 360 | + def generic_method[Foo, **Bar]( |
| 361 | + self, x: Foo, y: Bar |
| 362 | + ) -> None: ... |
| 363 | +
|
| 364 | + def generic_method_2[Eggs, **Spam](self, x: Eggs, y: Spam): pass |
| 365 | +
|
| 366 | +
|
| 367 | + # Eggs is `int` in globals, a TypeVar in type_params, and `str` in locals: |
| 368 | + class E[Eggs]: |
| 369 | + Eggs = str |
| 370 | + x: Eggs |
| 371 | +
|
| 372 | +
|
| 373 | +
|
| 374 | + def nested(): |
| 375 | + from types import SimpleNamespace |
| 376 | + from typing_extensions import get_annotations |
| 377 | +
|
| 378 | + Eggs = bytes |
| 379 | + Spam = memoryview |
| 380 | +
|
| 381 | +
|
| 382 | + class F[Eggs, **Spam]: |
| 383 | + x: Eggs |
| 384 | + y: Spam |
| 385 | +
|
| 386 | + def generic_method[Eggs, **Spam](self, x: Eggs, y: Spam): pass |
| 387 | +
|
| 388 | +
|
| 389 | + def generic_function[Eggs, **Spam](x: Eggs, y: Spam): pass |
| 390 | +
|
| 391 | +
|
| 392 | + # Eggs is `int` in globals, `bytes` in the function scope, |
| 393 | + # a TypeVar in the type_params, and `str` in locals: |
| 394 | + class G[Eggs]: |
| 395 | + Eggs = str |
| 396 | + x: Eggs |
| 397 | +
|
| 398 | +
|
| 399 | + return SimpleNamespace( |
| 400 | + F=F, |
| 401 | + F_annotations=get_annotations(F, eval_str=True), |
| 402 | + F_meth_annotations=get_annotations(F.generic_method, eval_str=True), |
| 403 | + G_annotations=get_annotations(G, eval_str=True), |
| 404 | + generic_func=generic_function, |
| 405 | + generic_func_annotations=get_annotations(generic_function, eval_str=True) |
| 406 | + ) |
| 407 | + """ |
| 408 | + ) |
| 409 | +else: |
| 410 | + STRINGIZED_ANNOTATIONS_PEP_695 = None |
| 411 | + |
| 412 | + |
314 | 413 | class BaseTestCase(TestCase):
|
315 | 414 | def assertIsSubclass(self, cls, class_or_tuple, msg=None):
|
316 | 415 | if not issubclass(cls, class_or_tuple):
|
@@ -7489,6 +7588,134 @@ def f(x: int):
|
7489 | 7588 | self.assertEqual(get_annotations(f), {"x": str})
|
7490 | 7589 |
|
7491 | 7590 |
|
| 7591 | +@skipIf(STRINGIZED_ANNOTATIONS_PEP_695 is None, "PEP 695 has yet to be") |
| 7592 | +class TestGetAnnotationsWithPEP695(BaseTestCase): |
| 7593 | + @classmethod |
| 7594 | + def setUpClass(cls): |
| 7595 | + with tempfile.TemporaryDirectory() as tempdir: |
| 7596 | + sys.path.append(tempdir) |
| 7597 | + Path(tempdir, "inspect_stringized_annotations_pep_695.py").write_text(STRINGIZED_ANNOTATIONS_PEP_695) |
| 7598 | + cls.inspect_stringized_annotations_pep_695 = importlib.import_module( |
| 7599 | + "inspect_stringized_annotations_pep_695" |
| 7600 | + ) |
| 7601 | + sys.path.pop() |
| 7602 | + |
| 7603 | + @classmethod |
| 7604 | + def tearDownClass(cls): |
| 7605 | + del cls.inspect_stringized_annotations_pep_695 |
| 7606 | + del sys.modules["inspect_stringized_annotations_pep_695"] |
| 7607 | + |
| 7608 | + def test_pep695_generic_class_with_future_annotations(self): |
| 7609 | + ann_module695 = self.inspect_stringized_annotations_pep_695 |
| 7610 | + A_annotations = get_annotations(ann_module695.A, eval_str=True) |
| 7611 | + A_type_params = ann_module695.A.__type_params__ |
| 7612 | + self.assertIs(A_annotations["x"], A_type_params[0]) |
| 7613 | + self.assertEqual(A_annotations["y"].__args__[0], Unpack[A_type_params[1]]) |
| 7614 | + self.assertIs(A_annotations["z"].__args__[0], A_type_params[2]) |
| 7615 | + |
| 7616 | + def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self): |
| 7617 | + B_annotations = get_annotations( |
| 7618 | + self.inspect_stringized_annotations_pep_695.B, eval_str=True |
| 7619 | + ) |
| 7620 | + self.assertEqual(B_annotations, {"x": int, "y": str, "z": bytes}) |
| 7621 | + |
| 7622 | + def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(self): |
| 7623 | + ann_module695 = self.inspect_stringized_annotations_pep_695 |
| 7624 | + C_annotations = get_annotations(ann_module695.C, eval_str=True) |
| 7625 | + self.assertEqual( |
| 7626 | + set(C_annotations.values()), |
| 7627 | + set(ann_module695.C.__type_params__) |
| 7628 | + ) |
| 7629 | + |
| 7630 | + def test_pep_695_generic_function_with_future_annotations(self): |
| 7631 | + ann_module695 = self.inspect_stringized_annotations_pep_695 |
| 7632 | + generic_func_annotations = get_annotations( |
| 7633 | + ann_module695.generic_function, eval_str=True |
| 7634 | + ) |
| 7635 | + func_t_params = ann_module695.generic_function.__type_params__ |
| 7636 | + self.assertEqual( |
| 7637 | + generic_func_annotations.keys(), {"x", "y", "z", "zz", "return"} |
| 7638 | + ) |
| 7639 | + self.assertIs(generic_func_annotations["x"], func_t_params[0]) |
| 7640 | + self.assertEqual(generic_func_annotations["y"], Unpack[func_t_params[1]]) |
| 7641 | + self.assertIs(generic_func_annotations["z"].__origin__, func_t_params[2]) |
| 7642 | + self.assertIs(generic_func_annotations["zz"].__origin__, func_t_params[2]) |
| 7643 | + |
| 7644 | + def test_pep_695_generic_function_with_future_annotations_name_clash_with_global_vars(self): |
| 7645 | + self.assertEqual( |
| 7646 | + set( |
| 7647 | + get_annotations( |
| 7648 | + self.inspect_stringized_annotations_pep_695.generic_function_2, |
| 7649 | + eval_str=True |
| 7650 | + ).values() |
| 7651 | + ), |
| 7652 | + set( |
| 7653 | + self.inspect_stringized_annotations_pep_695.generic_function_2.__type_params__ |
| 7654 | + ) |
| 7655 | + ) |
| 7656 | + |
| 7657 | + def test_pep_695_generic_method_with_future_annotations(self): |
| 7658 | + ann_module695 = self.inspect_stringized_annotations_pep_695 |
| 7659 | + generic_method_annotations = get_annotations( |
| 7660 | + ann_module695.D.generic_method, eval_str=True |
| 7661 | + ) |
| 7662 | + params = { |
| 7663 | + param.__name__: param |
| 7664 | + for param in ann_module695.D.generic_method.__type_params__ |
| 7665 | + } |
| 7666 | + self.assertEqual( |
| 7667 | + generic_method_annotations, |
| 7668 | + {"x": params["Foo"], "y": params["Bar"], "return": None} |
| 7669 | + ) |
| 7670 | + |
| 7671 | + def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_vars(self): |
| 7672 | + self.assertEqual( |
| 7673 | + set( |
| 7674 | + get_annotations( |
| 7675 | + self.inspect_stringized_annotations_pep_695.D.generic_method_2, |
| 7676 | + eval_str=True |
| 7677 | + ).values() |
| 7678 | + ), |
| 7679 | + set( |
| 7680 | + self.inspect_stringized_annotations_pep_695.D.generic_method_2.__type_params__ |
| 7681 | + ) |
| 7682 | + ) |
| 7683 | + |
| 7684 | + def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_and_local_vars(self): |
| 7685 | + self.assertEqual( |
| 7686 | + get_annotations( |
| 7687 | + self.inspect_stringized_annotations_pep_695.E, eval_str=True |
| 7688 | + ), |
| 7689 | + {"x": str}, |
| 7690 | + ) |
| 7691 | + |
| 7692 | + def test_pep_695_generics_with_future_annotations_nested_in_function(self): |
| 7693 | + results = self.inspect_stringized_annotations_pep_695.nested() |
| 7694 | + |
| 7695 | + self.assertEqual( |
| 7696 | + set(results.F_annotations.values()), |
| 7697 | + set(results.F.__type_params__) |
| 7698 | + ) |
| 7699 | + self.assertEqual( |
| 7700 | + set(results.F_meth_annotations.values()), |
| 7701 | + set(results.F.generic_method.__type_params__) |
| 7702 | + ) |
| 7703 | + self.assertNotEqual( |
| 7704 | + set(results.F_meth_annotations.values()), |
| 7705 | + set(results.F.__type_params__) |
| 7706 | + ) |
| 7707 | + self.assertEqual( |
| 7708 | + set(results.F_meth_annotations.values()).intersection(results.F.__type_params__), |
| 7709 | + set() |
| 7710 | + ) |
| 7711 | + |
| 7712 | + self.assertEqual(results.G_annotations, {"x": str}) |
| 7713 | + |
| 7714 | + self.assertEqual( |
| 7715 | + set(results.generic_func_annotations.values()), |
| 7716 | + set(results.generic_func.__type_params__) |
| 7717 | + ) |
| 7718 | + |
7492 | 7719 |
|
7493 | 7720 | if __name__ == '__main__':
|
7494 | 7721 | main()
|
0 commit comments