You can also get this one to fail on current Python if you also put in an annotation that will fail with a non-name error and force get_annotations into a fallback mode. (This also revealed a bug with conditional annotations and Format.STRING).
from annotationlib import get_annotations, Format
x = "global"
def outer(broken=False):
x = "nonlocal"
class Cls:
global x
ann: x
ref: undefined
if broken:
break_annotation: object.does_not_exist # force an AttributeError
Cls.x = "class"
return Cls
print(get_annotations(outer(False), format=Format.FORWARDREF)['ann']) # global
print(get_annotations(outer(True), format=Format.FORWARDREF)['ann']) # class
The ‘almost but not quite enough’ context is what’s being used here for the case where the annotations are broken by an attribute error. It’s also what I currently use for deferred annotations in reannotate, which is my current implementation of the deferred annotations I’ve mentioned before.
You need the namespace per annotation. Take the example of dataclasses __init__ that can contain annotations that come from different modules. If its __annotate__ function is to support the new format in a useful manner it needs to be able to provide the context for each annotation separately.
The DEFERRED format I’ve been speaking of is essentially designed for this. It’s a pairing of some arbitrary object - currently most commonly either an AST expression or a string - with an ‘EvaluationContext’ object required to evaluate it.
>>> from annotationlib import call_annotate_function, Format
>>> from reannotate import get_deferred_annotations, ReAnnotate
>>> class Example:
... a: int
... b: list[str]
... c: unknown
...
>>> annos = get_deferred_annotations(Example)
>>> print(annos)
{'a': DeferredAnnotation('int'), 'b': DeferredAnnotation('list[str]'), 'c': DeferredAnnotation('unknown')}
>>> annos['a']._obj # String as it's just a name
'int'
>>> annos['b']._obj # AST expression
Subscript(value=Name(id='list', ctx=Load()), slice=Name(id='str', ctx=Load()), ctx=Load())
Now, in its current form it’s not enough for expressions not supported by Format.STRING but that could be an internal change rather than a public format.