I think you misunderstood me slightly here, I didn’t mean a unique namespace per annotation. Just a reference to the appropriate context for the annotation function the annotation originated from. This ‘context’ object itself would need to have references to all of the relevant namespaces (globals, locals etc) in order to be able to evaluate forward references correctly.
Essentially having an format that’s something more like this:
def __annotate__(format, /):
match format:
case Format.DEFERRED:
context = _capture_annotation_context()
annotations = {
"a": DeferredAnnotation(<data object>, context),
"b": DeferredAnnotation(<data object>, context),
}
return annotations
...
This is roughly what reannotate tries to do on current Python except it is limited in that it can only currently retrieve annotations that Format.STRING would support. So error_level: 1 | 2 | 3 is still DeferredAnnotation('3').
from annotationlib import Format
from reannotate import get_deferred_annotations
class Example:
a: list[undefined]
b: int
annos = get_deferred_annotations(Example)
# Annotations from the same CPython generated __annotate__ share context
assert annos['a'].evaluation_context is annos['b'].evaluation_context
print("Annotation:", annos['a'])
print("Internal Object:", annos['a']._obj)
print("Context Object:", annos['a'].evaluation_context)
print("As ForwardRef:", annos['a'].evaluate(format=Format.FORWARDREF))
# Now we define 'undefined' and the annotation will evaluate, the same as `ForwardRef`.
undefined = str
print("As VALUE:", annos['a'].evaluate())
Annotation: DeferredAnnotation('list[undefined]')
Internal Object: Subscript(value=Name(id='list', ctx=Load()), slice=Name(id='undefined', ctx=Load()), ctx=Load())
Context Object: <reannotate.EvaluationContext object at 0x1056a87c0>
As ForwardRef: list[ForwardRef('undefined', is_class=True, owner=<class '__main__.Example'>)]
As VALUE: list[str]
Working like this would also make it possible to essentially backport the format to current Python as is, and then improve the correctness of the string support when the underlying generated __annotate__ functions are supported.
Out of curiosity, what’s the memory usage/performance like if you do both? If the __annotate__ functions look more like.
def __annotate__(format, /):
match format:
case Format.VALUE | Format.VALUE_WITH_FAKE_GLOBALS:
return {
"a": int,
"b": some_module.MyClass,
}
case Format.AST/DEFERRED:
return { ... }
case _:
raise NotImplementedError(format)