-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib #124415
base: main
Are you sure you want to change the base?
Changes from 8 commits
ddacdb7
5bb1d1e
ab05089
3ba132a
26f1c7c
2012bb4
f77be37
80818c6
4081f8f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,9 @@ | |
|
||
class Format(enum.IntEnum): | ||
VALUE = 1 | ||
FORWARDREF = 2 | ||
STRING = 3 | ||
VALUE_WITH_FAKE_GLOBALS = 2 | ||
FORWARDREF = 3 | ||
STRING = 4 | ||
|
||
|
||
_Union = None | ||
|
@@ -495,6 +496,8 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False): | |
on the generated ForwardRef objects. | ||
|
||
""" | ||
if format == Format.VALUE_WITH_FAKE_GLOBALS: | ||
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was surprised to see this exception. I haven't given the matter a lot of thought, and I don't have a strongly held opinion yet. Can you tell me why we should raise this exception here? (My knee-jerk instinct: "special cases aren't special enough to break the rules.") p.s. if we do keep these assertions, please drop There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no meaningful value to return for Concretely, raising an exception here means that if a user-provided annotate function delegates to an API in annotationlib without further thought, it will raise an exception for VALUE_WITH_FAKE_GLOBALS, which is what we'd want. |
||
try: | ||
return annotate(format) | ||
except NotImplementedError: | ||
|
@@ -528,7 +531,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False): | |
argdefs=annotate.__defaults__, | ||
kwdefaults=annotate.__kwdefaults__, | ||
) | ||
annos = func(Format.VALUE) | ||
annos = func(Format.VALUE_WITH_FAKE_GLOBALS) | ||
if _is_evaluate: | ||
return annos if isinstance(annos, str) else repr(annos) | ||
return { | ||
|
@@ -588,7 +591,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False): | |
argdefs=annotate.__defaults__, | ||
kwdefaults=annotate.__kwdefaults__, | ||
) | ||
result = func(Format.VALUE) | ||
result = func(Format.VALUE_WITH_FAKE_GLOBALS) | ||
for obj in globals.stringifiers: | ||
obj.__class__ = ForwardRef | ||
if isinstance(obj.__ast_node__, str): | ||
|
@@ -708,6 +711,8 @@ def get_annotations( | |
# But if we didn't get it, we use __annotations__ instead. | ||
ann = _get_dunder_annotations(obj) | ||
return annotations_to_string(ann) | ||
case Format.VALUE_WITH_FAKE_GLOBALS: | ||
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only") | ||
case _: | ||
raise ValueError(f"Unsupported format {format!r}") | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,11 +42,14 @@ def test_enum(self): | |
self.assertEqual(Format.VALUE.value, 1) | ||
self.assertEqual(Format.VALUE, 1) | ||
|
||
self.assertEqual(Format.FORWARDREF.value, 2) | ||
self.assertEqual(Format.FORWARDREF, 2) | ||
self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS.value, 2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See related comments, I don't think the fact that |
||
self.assertEqual(Format.VALUE_WITH_FAKE_GLOBALS, 2) | ||
|
||
self.assertEqual(Format.STRING.value, 3) | ||
self.assertEqual(Format.STRING, 3) | ||
self.assertEqual(Format.FORWARDREF.value, 3) | ||
self.assertEqual(Format.FORWARDREF, 3) | ||
|
||
self.assertEqual(Format.STRING.value, 4) | ||
self.assertEqual(Format.STRING, 4) | ||
|
||
|
||
class TestForwardRefFormat(unittest.TestCase): | ||
|
@@ -413,19 +416,28 @@ def f2(a: undefined): | |
annotationlib.get_annotations(f2, format=Format.FORWARDREF), | ||
{"a": fwd}, | ||
) | ||
self.assertEqual(annotationlib.get_annotations(f2, format=2), {"a": fwd}) | ||
self.assertEqual(annotationlib.get_annotations(f2, format=3), {"a": fwd}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The intent was to test that the function accepts both the integer value and the enum. If you think it's OK to accept only the enum, I can switch to that model. |
||
|
||
self.assertEqual( | ||
annotationlib.get_annotations(f1, format=Format.STRING), | ||
{"a": "int"}, | ||
) | ||
self.assertEqual(annotationlib.get_annotations(f1, format=3), {"a": "int"}) | ||
self.assertEqual(annotationlib.get_annotations(f1, format=4), {"a": "int"}) | ||
|
||
with self.assertRaises(ValueError): | ||
annotationlib.get_annotations(f1, format=0) | ||
annotationlib.get_annotations(f1, format=42) | ||
|
||
with self.assertRaises(ValueError): | ||
annotationlib.get_annotations(f1, format=4) | ||
with self.assertRaisesRegex( | ||
ValueError, | ||
r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only", | ||
): | ||
annotationlib.get_annotations(f1, format=Format.VALUE_WITH_FAKE_GLOBALS) | ||
|
||
with self.assertRaisesRegex( | ||
ValueError, | ||
r"The VALUE_WITH_FAKE_GLOBALS format is for internal use only", | ||
): | ||
annotationlib.get_annotations(f1, format=2) | ||
|
||
def test_custom_object_with_annotations(self): | ||
class C: | ||
|
@@ -459,6 +471,8 @@ def foo(a: int, b: str): | |
|
||
foo.__annotations__ = {"a": "foo", "b": "str"} | ||
for format in Format: | ||
if format is Format.VALUE_WITH_FAKE_GLOBALS: | ||
continue | ||
with self.subTest(format=format): | ||
self.assertEqual( | ||
annotationlib.get_annotations(foo, format=format), | ||
|
@@ -756,6 +770,8 @@ def __annotations__(self): | |
|
||
wa = WeirdAnnotations() | ||
for format in Format: | ||
if format is Format.VALUE_WITH_FAKE_GLOBALS: | ||
continue | ||
with ( | ||
self.subTest(format=format), | ||
self.assertRaisesRegex( | ||
|
@@ -944,7 +960,7 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self): | |
class TestCallEvaluateFunction(unittest.TestCase): | ||
def test_evaluation(self): | ||
def evaluate(format, exc=NotImplementedError): | ||
if format != 1: | ||
if format > 2: | ||
raise exc | ||
return undefined | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -168,7 +168,7 @@ constevaluator_call(PyObject *self, PyObject *args, PyObject *kwargs) | |
return NULL; | ||
} | ||
PyObject *value = ((constevaluatorobject *)self)->value; | ||
if (format == 3) { // STRING | ||
if (format == 4) { // STRING | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The formats should be defined in C too. Macros are fine. I'm expecting |
||
PyUnicodeWriter *writer = PyUnicodeWriter_Create(5); // cannot be <5 | ||
if (writer == NULL) { | ||
return NULL; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VALUE_WITH_FAKE_GLOBALS
is now 2. But why publish their values in the first place?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A wise man wrote these values in PEP 649 (https://peps.python.org/pep-0649/#overview). They are also relevant if you write an annotate function in C. But yes, we can probably do without them in Python code.