FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-119180: Fix annotations lookup on classes with custom metaclasses by JelleZijlstra · Pull Request #120719 · python/cpython · GitHub

/ cpython Public
1 change: 1 addition & 0 deletions Include/internal/pycore_typeobject.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ extern PyObject* _Py_slot_tp_getattro(PyObject *self, PyObject *name);
extern PyObject* _Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);

extern PyTypeObject _PyBufferWrapper_Type;
extern PyTypeObject _PyAnnotationsDescriptor_Type;

PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
PyObject *name, int *meth_found);
Expand Down
1 change: 1 addition & 0 deletions Lib/doctest.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
# Recurse to methods, properties, and nested classes.
if ((inspect.isroutine(val) or inspect.isclass(val) or
isinstance(val, property)) and
valname != '__annotations__' and
self._from_module(module, val)):
valname = '%s.%s' % (name, valname)
self._find(tests, val, valname, module, source_lines,
Expand Down
4 changes: 3 additions & 1 deletion Lib/pydoc.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,10 @@ def visiblename(name, all=None, obj=None):
'__date__', '__doc__', '__file__', '__spec__',
'__loader__', '__module__', '__name__', '__package__',
'__path__', '__qualname__', '__slots__', '__version__',
'__static_attributes__', '__firstlineno__'}:
'__static_attributes__', '__firstlineno__', '__annotations__'}:
return 0
if name == '__annotate__' and getattr(obj, name, None) is None:
return False
# Private names are hidden, but special names are displayed.
if name.startswith('__') and name.endswith('__'): return 1
# Namedtuples have public fields and methods with a single leading underscore
Expand Down
8 changes: 5 additions & 3 deletions Lib/test/test_descr.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5123,7 +5123,8 @@ def test_iter_keys(self):
self.assertNotIsInstance(it, list)
keys = list(it)
keys.sort()
self.assertEqual(keys, ['__dict__', '__doc__', '__firstlineno__',
self.assertEqual(keys, ['__annotate__', '__annotations__',
'__dict__', '__doc__', '__firstlineno__',
'__module__',
'__static_attributes__', '__weakref__',
'meth'])
Expand All @@ -5135,7 +5136,7 @@ def test_iter_values(self):
it = self.C.__dict__.values()
self.assertNotIsInstance(it, list)
values = list(it)
self.assertEqual(len(values), 7)
self.assertEqual(len(values), 9)

@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'trace function introduces __local__')
Expand All @@ -5145,7 +5146,8 @@ def test_iter_items(self):
self.assertNotIsInstance(it, list)
keys = [item[0] for item in it]
keys.sort()
self.assertEqual(keys, ['__dict__', '__doc__', '__firstlineno__',
self.assertEqual(keys, ['__annotate__', '__annotations__',
'__dict__', '__doc__', '__firstlineno__',
'__module__',
'__static_attributes__', '__weakref__',
'meth'])
Expand Down
68 changes: 63 additions & 5 deletions Lib/test/test_type_annotations.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import itertools
import textwrap
import types
import unittest
Expand All @@ -11,12 +12,8 @@
class TypeAnnotationTests(unittest.TestCase):

def test_lazy_create_annotations(self):
# type objects lazy create their __annotations__ dict on demand.
# the annotations dict is stored in type.__dict__.
# a freshly created type shouldn't have an annotations dict yet.
foo = type("Foo", (), {})
for i in range(3):
self.assertFalse("__annotations__" in foo.__dict__)
d = foo.__annotations__
self.assertTrue("__annotations__" in foo.__dict__)
self.assertEqual(foo.__annotations__, d)
Expand All @@ -26,7 +23,6 @@ def test_lazy_create_annotations(self):
def test_setting_annotations(self):
foo = type("Foo", (), {})
for i in range(3):
self.assertFalse("__annotations__" in foo.__dict__)
d = {'a': int}
foo.__annotations__ = d
self.assertTrue("__annotations__" in foo.__dict__)
Expand Down Expand Up @@ -270,6 +266,68 @@ def check_annotations(self, f):
self.assertIs(f.__annotate__, None)


class MetaclassTests(unittest.TestCase):
def test_annotated_meta(self):
class Meta(type):
a: int

class X(metaclass=Meta):
pass

class Y(metaclass=Meta):
b: float

self.assertEqual(Meta.__annotations__, {"a": int})
self.assertEqual(Meta.__annotate__(1), {"a": int})

self.assertEqual(X.__annotations__, {})
self.assertIs(X.__annotate__, None)

self.assertEqual(Y.__annotations__, {"b": float})
self.assertEqual(Y.__annotate__(1), {"b": float})

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:
classes[i].__annotations__
for c in classes:
with self.subTest(c=c):
self.assertEqual(c.__annotations__, c.expected_annotations)
if c.expected_annotations:
self.assertEqual(c.__annotate__(1), c.expected_annotations)
else:
self.assertIs(c.__annotate__, None)


class DeferredEvaluationTests(unittest.TestCase):
def test_function(self):
def func(x: undefined, /, y: undefined, *args: undefined, z: undefined, **kwargs: undefined) -> undefined:
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make lookup of ``__annotate__`` and ``__annotations__`` on classes more
robust in the presence of metaclasses.
1 change: 1 addition & 0 deletions Objects/object.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,7 @@ static PyTypeObject* static_types[] = {
&PyZip_Type,
&Py_GenericAliasType,
&_PyAnextAwaitable_Type,
&_PyAnnotationsDescriptor_Type,
&_PyAsyncGenASend_Type,
&_PyAsyncGenAThrow_Type,
&_PyAsyncGenWrappedValue_Type,
Expand Down
Loading

Back | FazBrowse Home | New Git URL