8000 gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib by JelleZijlstra · Pull Request #124415 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib #124415

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

Merged
merged 12 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-119180: Add VALUE_WITH_FAKE_GLOBALS format to annotationlib
  • Loading branch information
JelleZijlstra committed Sep 24, 2024
commit ddacdb7d177b4a8bf2415f4fefee756bb8f94e31
11 changes: 11 additions & 0 deletions Doc/library/annotationlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ Classes

The exact values of these strings may change in future versions of Python.

.. attribute:: VALUE_WITH_FAKE_GLOBALS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is internal only, maybe it should start with an _?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not quite internal-only, because external users who write their own __annotate__ functions need to care about this value.

:value: 4
Copy link
Contributor

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?

Copy link
Member Author

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.


Special value used to signal that an annotate function is being
evaluated in a special environment with fake globals. When passed this
value, annotate functions should either return the same value as for
the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
to signal that they do not support execution in this environment.
This format is only used internally and should not be passed to
the functions in this module.

.. versionadded:: 3.14

.. class:: ForwardRef
Expand Down
7 changes: 5 additions & 2 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Format(enum.IntEnum):
VALUE = 1
FORWARDREF = 2
SOURCE = 3
VALUE_WITH_FAKE_GLOBALS = 4


_Union = None
Expand Down Expand Up @@ -459,6 +460,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")
Copy link
Contributor

Choose a reason for hiding this comment

The 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 "The ". The text should read "VALUES_WITH_FAKE_GLOBALS format is for internal use only".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no meaningful value to return for VALUE_WITH_FAKE_GLOBALS; it's only useful within an annotate function.

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:
Expand Down Expand Up @@ -492,7 +495,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 {
Expand Down Expand Up @@ -552,7 +555,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
return result
Expand Down
14 changes: 13 additions & 1 deletion Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,18 @@ def f2(a: undefined):
annotationlib.get_annotations(f1, format=0)

with self.assertRaises(ValueError):
annotationlib.get_annotations(f1, format=42)

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=4)

def test_custom_object_with_annotations(self):
Expand Down Expand Up @@ -840,7 +852,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 != 1 and format != 4:
raise exc
return undefined

Expand Down
15 changes: 10 additions & 5 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2945,10 +2945,13 @@ def _make_eager_annotate(types):
checked_types = {key: _type_check(val, f"field {key} annotation must be a type")
for key, val in types.items()}
def annotate(format):
if format in (annotationlib.Format.VALUE, annotationlib.Format.FORWARDREF):
return checked_types
else:
return _convert_to_source(types)
match format:
case annotationlib.Format.VALUE | annotationlib.Format.FORWARDREF:
return checked_types
case annotationlib.Format.SOURCE:
return _convert_to_source(types)
case _:
raise NotImplementedError(format)
return annotate


Expand Down Expand Up @@ -3242,8 +3245,10 @@ def __annotate__(format):
}
elif format == annotationlib.Format.SOURCE:
own = _convert_to_source(own_annotations)
else:
elif format in (annotationlib.Format.FORWARDREF, annotationlib.Format.VALUE):
own = own_checked_annotations
else:
raise NotImplementedError(format)
annos.update(own)
return annos

Expand Down
10 changes: 9 additions & 1 deletion Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -655,13 +655,21 @@ codegen_setup_annotations_scope(compiler *c, location loc,
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
key, loc.lineno, NULL, &umd));

// if .format != 1: raise NotImplementedError
// if .format != 1 and .format != 4: raise NotImplementedError
_Py_DECLARE_STR(format, ".format");
ADDOP_I(c, loc, LOAD_FAST, 0);
ADDOP_LOAD_CONST(c, loc, _PyLong_GetOne());
ADDOP_I(c, loc, COMPARE_OP, (Py_NE << 5) | compare_masks[Py_NE]);
NEW_JUMP_TARGET_LABEL(c, body);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, body);

ADDOP_I(c, loc, LOAD_FAST, 0);
PyObject *four = PyLong_FromLong(4);
assert(four != NULL);
ADDOP_LOAD_CONST(c, loc, four);
ADDOP_I(c, loc, COMPARE_OP, (Py_NE << 5) | compare_masks[Py_NE]);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, body);

ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_NOTIMPLEMENTEDERROR);
ADDOP_I(c, loc, RAISE_VARARGS, 1);
USE_LABEL(c, body);
Expand Down
Loading
0