@@ -40,7 +40,7 @@ The :func:`get_annotations` function is the main entry point for
4040retrieving annotations. Given a function, class, or module, it returns
4141an annotations dictionary in the requested format. This module also provides
4242functionality for working directly with the :term: `annotate function `
43- that is used to evaluate annotations, such as :func: `get_annotate_function `
43+ that is used to evaluate annotations, such as :func: `get_annotate_from_class_namespace `
4444and :func: `call_annotate_function `, as well as the
4545:func: `call_evaluate_function ` function for working with
4646:term: `evaluate functions <evaluate function> `.
@@ -132,7 +132,7 @@ Classes
132132
133133 Values are real annotation values (as per :attr: `Format.VALUE ` format)
134134 for defined values, and :class: `ForwardRef ` proxies for undefined
135- values. Real objects may contain references to, :class: `ForwardRef `
135+ values. Real objects may contain references to :class: `ForwardRef `
136136 proxy objects.
137137
138138 .. attribute :: STRING
@@ -172,14 +172,21 @@ Classes
172172 :class: `~ForwardRef `. The string may not be exactly equivalent
173173 to the original source.
174174
175- .. method :: evaluate(*, owner=None, globals=None, locals=None, type_params=None)
175+ .. method :: evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE )
176176
177177 Evaluate the forward reference, returning its value.
178178
179- This may throw an exception, such as :exc: `NameError `, if the forward
179+ If the *format * argument is :attr: `~Format.VALUE ` (the default),
180+ this method may throw an exception, such as :exc: `NameError `, if the forward
180181 reference refers to a name that cannot be resolved. The arguments to this
181182 method can be used to provide bindings for names that would otherwise
182- be undefined.
183+ be undefined. If the *format * argument is :attr: `~Format.FORWARDREF `,
184+ the method will never throw an exception, but may return a :class: `~ForwardRef `
185+ instance. For example, if the forward reference object contains the code
186+ ``list[undefined] ``, where ``undefined `` is a name that is not defined,
187+ evaluating it with the :attr: `~Format.FORWARDREF ` format will return
188+ ``list[ForwardRef('undefined')] ``. If the *format * argument is
189+ :attr: `~Format.STRING `, the method will return :attr: `~ForwardRef.__forward_arg__ `.
183190
184191 The *owner * parameter provides the preferred mechanism for passing scope
185192 information to this method. The owner of a :class: `~ForwardRef ` is the
@@ -300,15 +307,13 @@ Functions
300307
301308 .. versionadded :: 3.14
302309
303- .. function :: get_annotate_function(obj )
310+ .. function :: get_annotate_from_class_namespace(namespace )
304311
305- Retrieve the :term: `annotate function ` for *obj *. Return :const: `!None `
306- if *obj * does not have an annotate function. *obj * may be a class, function,
307- module, or a namespace dictionary for a class. The last case is useful during
308- class creation, e.g. in the ``__new__ `` method of a metaclass.
309-
310- This is usually equivalent to accessing the :attr: `~object.__annotate__ `
311- attribute of *obj *, but access through this public function is preferred.
312+ Retrieve the :term: `annotate function ` from a class namespace dictionary *namespace *.
313+ Return :const: `!None ` if the namespace does not contain an annotate function.
314+ This is primarily useful before the class has been fully created (e.g., in a metaclass);
315+ after the class exists, the annotate function can be retrieved with ``cls.__annotate__ ``.
316+ See :ref: `below <annotationlib-metaclass >` for an example using this function in a metaclass.
312317
313318 .. versionadded :: 3.14
314319
@@ -407,3 +412,76 @@ Functions
407412
408413 .. versionadded :: 3.14
409414
415+
416+ Recipes
417+ -------
418+
419+ .. _annotationlib-metaclass :
420+
421+ Using annotations in a metaclass
422+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
423+
424+ A :ref: `metaclass <metaclasses >` may want to inspect or even modify the annotations
425+ in a class body during class creation. Doing so requires retrieving annotations
426+ from the class namespace dictionary. For classes created with
427+ ``from __future__ import annotations ``, the annotations will be in the ``__annotations__ ``
428+ key of the dictionary. For other classes with annotations,
429+ :func: `get_annotate_from_class_namespace ` can be used to get the
430+ annotate function, and :func: `call_annotate_function ` can be used to call it and
431+ retrieve the annotations. Using the :attr: `~Format.FORWARDREF ` format will usually
432+ be best, because this allows the annotations to refer to names that cannot yet be
433+ resolved when the class is created.
434+
435+ To modify the annotations, it is best to create a wrapper annotate function
436+ that calls the original annotate function, makes any necessary adjustments, and
437+ returns the result.
438+
439+ Below is an example of a metaclass that filters out all :class: `typing.ClassVar `
440+ annotations from the class and puts them in a separate attribute:
441+
442+ .. code-block :: python
443+
444+ import annotationlib
445+ import typing
446+
447+ class ClassVarSeparator (type ):
448+ def __new__ (mcls , name , bases , ns ):
449+ if " __annotations__" in ns: # from __future__ import annotations
450+ annotations = ns[" __annotations__" ]
451+ classvar_keys = {
452+ key for key, value in annotations.items()
453+ # Use string comparison for simplicity; a more robust solution
454+ # could use annotationlib.ForwardRef.evaluate
455+ if value.startswith(" ClassVar" )
456+ }
457+ classvars = {key: annotations[key] for key in classvar_keys}
458+ ns[" __annotations__" ] = {
459+ key: value for key, value in annotations.items()
460+ if key not in classvar_keys
461+ }
462+ wrapped_annotate = None
463+ elif annotate := annotationlib.get_annotate_from_class_namespace(ns):
464+ annotations = annotationlib.call_annotate_function(
465+ annotate, format = annotationlib.Format.FORWARDREF
466+ )
467+ classvar_keys = {
468+ key for key, value in annotations.items()
469+ if typing.get_origin(value) is typing.ClassVar
470+ }
471+ classvars = {key: annotations[key] for key in classvar_keys}
472+
473+ def wrapped_annotate (format ):
474+ annos = annotationlib.call_annotate_function(annotate, format , owner = typ)
475+ return {key: value for key, value in annos.items() if key not in classvar_keys}
476+
477+ else : # no annotations
478+ classvars = {}
479+ wrapped_annotate = None
480+ typ = super ().__new__ (mcls, name, bases, ns)
481+
482+ if wrapped_annotate is not None :
483+ # Wrap the original __annotate__ with a wrapper that removes ClassVars
484+ typ.__annotate__ = wrapped_annotate
485+ typ.classvars = classvars # Store the ClassVars in a separate attribute
486+ return typ
487+
0 commit comments