From 6a08abee2d8c9edf16bd1297134eed2ba9532762 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 27 Jul 2024 12:27:20 -0700 Subject: [PATCH 1/2] Add tests for metaclasses and typing_extensions.get_annotations Tests from python/cpython#122074. We don't have to use the base descriptor approach here because we find the annotations directly in the `__dict__` for the class, which avoids metaclass problems. --- src/test_typing_extensions.py | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 868e7938..978be63d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -8,6 +8,7 @@ import importlib import inspect import io +import itertools import pickle import re import subprocess @@ -7647,6 +7648,71 @@ def f(x: int): self.assertEqual(get_annotations(f), {"x": str}) +class TestGetAnnotationsMetaclasses(BaseTestCase): + def test_annotated_meta(self): + class Meta(type): + a: int + + class X(metaclass=Meta): + pass + + class Y(metaclass=Meta): + b: float + + self.assertEqual(get_annotations(Meta), {"a": int}) + self.assertEqual(get_annotations(X), {}) + self.assertEqual(get_annotations(Y), {"b": float}) + + def test_unannotated_meta(self): + class Meta(type): pass + + class X(metaclass=Meta): + a: str + + class Y(X): pass + + self.assertEqual(get_annotations(Meta), {}) + self.assertEqual(get_annotations(Y), {}) + self.assertEqual(get_annotations(X), {"a": str}) + + def test_ordering(self): + # Based on a sample by David Ellis + # https://discuss.python.org/t/pep-749-implementing-pep-649/54974/38 + + def make_classes(): + class Meta(type): + a: int + expected_annotations = {"a": int} + + class A(type, metaclass=Meta): + b: float + expected_annotations = {"b": float} + + class B(metaclass=A): + c: str + expected_annotations = {"c": str} + + class C(B): + expected_annotations = {} + + class D(metaclass=Meta): + expected_annotations = {} + + return Meta, A, B, C, D + + classes = make_classes() + class_count = len(classes) + for order in itertools.permutations(range(class_count), class_count): + names = ", ".join(classes[i].__name__ for i in order) + with self.subTest(names=names): + classes = make_classes() # Regenerate classes + for i in order: + get_annotations(classes[i]) + for c in classes: + with self.subTest(c=c): + self.assertEqual(get_annotations(c), c.expected_annotations) + + @skipIf(STRINGIZED_ANNOTATIONS_PEP_695 is None, "PEP 695 has yet to be") class TestGetAnnotationsWithPEP695(BaseTestCase): @classmethod From 4ca9d8753f1c06d920053f9c64fcf2e25c1ed1f3 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 9 Sep 2024 16:41:27 -0700 Subject: [PATCH 2/2] Silence Ruff --- pyproject.toml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3388d553..51276151 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,9 +81,19 @@ select = [ "W", ] -# Ignore various "modernization" rules that tell you off for importing/using -# deprecated things from the typing module, etc. -ignore = ["UP006", "UP007", "UP013", "UP014", "UP019", "UP035", "UP038"] +ignore = [ + # Ignore various "modernization" rules that tell you off for importing/using + # deprecated things from the typing module, etc. + "UP006", + "UP007", + "UP013", + "UP014", + "UP019", + "UP035", + "UP038", + # Not relevant here + "RUF012", +] [tool.ruff.lint.per-file-ignores] "!src/typing_extensions.py" = [