This PEP proposes a mechanism for type checking metadata that usesthetyping.Annotated type. Metadata objects that implementthe new__supports_annotated_base__ protocol will be type checked by statictype checkers to ensure that the metadata is valid for the given type.
PEP 593 introducedAnnotated as a way to attach runtime metadata to types.In general, the metadata is not meant for static type checkers, but even so,it is often useful to be able to check that the metadata makes sense for the giventype.
Take the first example inPEP 593, which usesAnnotated to attachserialization information to a field:
classStudent(struct2.Packed):name:Annotated[str,struct2.ctype("<10s")]
Here, thestruct2.ctype("<10s") metadata is meant to be used by a serializationlibrary to serialize the field. Such libraries can only serialize a subset of types:it would not make sense to write, for example,Annotated[list[str],struct2.ctype("<10s")].Yet the type system provides no way to enforce this. The metadata are completelyignored by type checkers.
This use case comes up in libraries likepydantic andmsgspec, which useAnnotated to attach validation and conversion information to fields orfastapi,which usesAnnotated to mark parameters as extracted from headers, query strings ordependency injection.
This PEP introduces a protocol that can be used by static and runtime type checkers to validatethe consistency betweenAnnotated metadata and a given type.Objects that implement this protocol have an attribute called__supports_annotated_base__that specifies whether the metadata is valid for a given type:
classInt64:__supports_annotated_base__:int
The attribute may also be marked as aClassVar to avoid interaction with dataclasses:
fromdataclassesimportdataclassfromtypingimportClassVar@dataclassclassGt:value:int__supports_annotated_base__:ClassVar[int]
When a static type checker encounters a type expression of the formAnnotated[T,M1,M2,...],it should enforce that for each metadata element inM1,M2,..., one of the following is true:
__supports_annotated_base__ attribute; orM that has a__supports_annotated_base__ attribute;andT is assignable to the type ofM.__supports_annotated_base__.To support genericGt metadata, one might write:
fromtypingimportProtocolclassSupportsGt[T](Protocol):def__gt__(self,__other:T)->bool:...classGt[T]:__supports_annotated_base__:ClassVar[SupportsGt[T]]def__init__(self,value:T)->None:self.value=valuex1:Annotated[int,Gt(0)]=1# OKx2:Annotated[str,Gt(0)]=0# type checker error: str is not assignable to SupportsGt[int]x3:Annotated[int,Gt(1)]=0# OK for static type checkers; runtime type checkers may flag this
Metadata that does not implement the protocol will be considered valid for all types,so no breaking changes are introduced for existing code. The new checks only applyto metadata objects that explicitly implement the protocol specified by this PEP.
None.
This protocol is intended mostly for libraries that provideAnnotated metadata;end users of those libraries are unlikely to need to implement the protocol themselves.The protocol should be mentioned in the documentation fortyping.Annotated andin the typing specification.
None yet.
We considered using a special type variable,AnnotatedT=TypeVar("AnnotatedT"),to represent the typeT of the inner type inAnnotated; metadata would betype checked against this type variable. However, this would require using the oldtype variable syntax (beforePEP 695), which is now a discouraged feature.In addition, this would use type variables in an unusual way that does not fit wellwith the rest of the type system.
typing.py that all metadata objects should subclassA previous version of this PEP suggested adding a new generic base class,TypedMetadata[U],that metadata objects would subclass. If a metadata object is a subclass ofTypedMetadata[U],then type checkers would check that the annotation’s base type is assignable toU.However, this mechanism does not integrate as well with the rest of the language; Pythondoes not generally use marker base classes. In addition, it provides less flexibility thanthe current proposal: it would not allow overloads, and it would require metadata objectsto add a new base class, which may make their runtime implementation more complex.
__supports_annotated_base__We considered using a method instead of an attribute for the protocol, so that this method can be usedat runtime to check the validity of the metadata and to support overloads or returning boolean literals.However, using a method adds boilerplate to the implementation and the value of the runtime use cases ormore complex scenarios involving overloads and returning boolean literals was not clear.
We thank Eric Traut for suggesting the idea of using a protocol and implementing provisional support in Pyright.Thank you to Jelle Zijlstra for sponsoring this PEP.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0746.rst
Last modified:2025-05-06 20:54:28 GMT