@@ -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