@@ -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> `.
@@ -300,15 +300,13 @@ Functions
300300
301301   .. versionadded :: 3.14 
302302
303- .. function :: get_annotate_function(obj )
303+ .. function :: get_annotate_from_class_namespace(namespace )
304304
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.
305+    Retrieve the :term: `annotate function ` from a class namespace dictionary *namespace *.
306+    Return :const: `!None ` if the namespace does not contain an annotate function.
307+    This is primarily useful before the class has been fully created (e.g., in a metaclass);
308+    after the class exists, the annotate function can be retrieved with ``cls.__annotate__ ``.
309+    See :ref: `below  <annotationlib-metaclass >` for an example using this function in a metaclass.
312310
313311   .. versionadded :: 3.14 
314312
@@ -407,3 +405,76 @@ Functions
407405
408406   .. versionadded :: 3.14 
409407
408+ 
409+ Recipes
410+ ------- 
411+ 
412+ .. _annotationlib-metaclass :
413+ 
414+ Using annotations in a metaclass
415+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
416+ 
417+ A :ref: `metaclass  <metaclasses >` may want to inspect or even modify the annotations
418+ in a class body during class creation. Doing so requires retrieving annotations
419+ from the class namespace dictionary. For classes created with
420+ ``from __future__ import annotations ``, the annotations will be in the ``__annotations__ ``
421+ key of the dictionary. For other classes with annotations,
422+ :func: `get_annotate_from_class_namespace ` can be used to get the
423+ annotate function, and :func: `call_annotate_function ` can be used to call it and
424+ retrieve the annotations. Using the :attr: `~Format.FORWARDREF ` format will usually
425+ be best, because this allows the annotations to refer to names that cannot yet be
426+ resolved when the class is created.
427+ 
428+ To modify the annotations, it is best to create a wrapper annotate function
429+ that calls the original annotate function, makes any necessary adjustments, and
430+ returns the result.
431+ 
432+ Below is an example of a metaclass that filters out all :class: `typing.ClassVar `
433+ annotations from the class and puts them in a separate attribute:
434+ 
435+ .. code-block :: python 
436+ 
437+    import  annotationlib 
438+    import  typing 
439+ 
440+    class  ClassVarSeparator (type ): 
441+       def  __new__ (mcls , name , bases , ns ): 
442+          if  " __annotations__" in  ns:  #  from __future__ import annotations 
443+             annotations =  ns[" __annotations__"  
444+             classvar_keys =  { 
445+                key for  key, value in  annotations.items() 
446+                #  Use string comparison for simplicity; a more robust solution 
447+                #  could use annotationlib.ForwardRef.evaluate 
448+                if  value.startswith(" ClassVar"  
449+             } 
450+             classvars =  {key: annotations[key] for  key in  classvar_keys} 
451+             ns[" __annotations__" =  { 
452+                key: value for  key, value in  annotations.items() 
453+                if  key not  in  classvar_keys 
454+             } 
455+             wrapped_annotate =  None  
456+          elif  annotate :=  annotationlib.get_annotate_from_class_namespace(ns): 
457+             annotations =  annotationlib.call_annotate_function( 
458+                annotate, format = annotationlib.Format.FORWARDREF  
459+             ) 
460+             classvar_keys =  { 
461+                key for  key, value in  annotations.items() 
462+                if  typing.get_origin(value) is  typing.ClassVar 
463+             } 
464+             classvars =  {key: annotations[key] for  key in  classvar_keys} 
465+ 
466+             def  wrapped_annotate (format ): 
467+                annos =  annotationlib.call_annotate_function(annotate, format , owner = typ) 
468+                return  {key: value for  key, value in  annos.items() if  key not  in  classvar_keys} 
469+ 
470+          else :  #  no annotations 
471+             classvars =  {} 
472+             wrapped_annotate =  None  
473+          typ =  super ().__new__ (mcls, name, bases, ns) 
474+ 
475+          if  wrapped_annotate is  not  None : 
476+             #  Wrap the original __annotate__ with a wrapper that removes ClassVars 
477+             typ.__annotate__ =  wrapped_annotate 
478+          typ.classvars =  classvars  #  Store the ClassVars in a separate attribute 
479+          return  typ 
480+ 
0 commit comments