annotationlib can inspect a forward annotation as text even when the referenced class is unavailable.
Python deferred annotations: inspect forward names without resolving them
Operation contract
A repository method names a future Receipt class that is not defined in the fixture. Its function definition succeeds. STRING inspection returns the written type name without requiring the class to exist; VALUE inspection raises NameError. A framework should choose the format that matches its job instead of assuming annotation access is inert.
Failure boundary
Even STRING inspection may execute annotation-related code in some expressions. Treat third-party annotations as executable input, not a safe serialization format. Runtime type checking is a separate concern. Behavior also differs for code using the older future-annotations mode, so test the project's supported Python versions.
Working program
from annotationlib import Format, get_annotations
def load_receipt(receipt_id: str) -> FutureReceipt:
raise NotImplementedError
written = get_annotations(load_receipt, format=Format.STRING)
print("return_name", written["return"])
try:
get_annotations(load_receipt, format=Format.VALUE)
except NameError:
print("value_unresolved", True)Output
return_name FutureReceipt
value_unresolved TrueCosts and limits
Deferred evaluation shifts work from function definition to the reader that inspects annotations. Repeated inspection may execute user code and should not sit on an unbounded hot path.
Common Mistakes
- An annotation is not runtime input validation.
- VALUE format can fail when a forward name remains undefined.
- Do not treat annotation introspection as safe for untrusted plugin code.
