Package: annotated-types
annotated-typesPEP-593addedtyping.Annotatedas a way of
adding context-specific metadata to existing types, and specifies thatAnnotated[T, x]shouldbe treated asTby any tool or library without special
logic forx.This package provides metadata objects which can be used to represent common
constraints such as upper and lower bounds on scalar values and collection sizes,
aPredicatemarker for runtime checks, and
descriptions of how we intend these metadata to be interpreted. In some cases,
we also note alternative representations which do not require this package.Installpipinstallannotated-typesExamplesfromtypingimportAnnotatedfromannotated_typesimportGt,Len,PredicateclassMyClass:age:Annotated[int,Gt(18)]# Valid: 19, 20, ...# Invalid: 17, 18, "19", 19.0, ...factors:list[Annotated[int,Predicate(is_prime)]]# Valid: 2, 3, 5, 7, 11, ...# Invalid: 4, 8, -2, 5.0, "prime", ...my_list:Annotated[list[int],Len(0,10)]# Valid: [], [10, 20, 30, 40, 50]# Invalid: (1, 2), ["abc"], [0] * 20DocumentationWhileannotated-typesavoids runtime checks for performance, users should not
construct invalid combinations such asMultipleOf("non-numeric")orAnnotated[int, Len(3)].
Downstream implementors may choose to raise an error, emit a warning, silently ignore
a metadata item, etc., if the metadata objects described below are used with an
incompatible type - or for any other reason!Gt, Ge, Lt, LeExpress inclusive and/or exclusive bounds on orderable values - which may be numbers,
dates, times, strings, sets, etc. Note that the boundary value need not be of the
same type that was annotated, so long as they can be compared:Annotated[int, Gt(1.5)]is fine, for example, and implies that the value is an integer x such thatx > 1.5.We suggest that implementors may also interpretfunctools.partial(operator.le, 1.5)as being equivalent toGt(1.5), for users who wish to avoid a runtime dependency on
theannotated-typespackage.To be explicit, these types have the following meanings:Gt(x)- value must be "Greater Than"x- equivalent to exclusive minimumGe(x)- value must be "Greater than or Equal" tox- equivalent to inclusive minimumLt(x)- value must be "Less Than"x- equivalent to exclusive maximumLe(x)- value must be "Less than or Equal" tox- equivalent to inclusive maximumIntervalInterval(gt, ge, lt, le)allows you to specify an upper and lower bound with a single
metadata object.Noneattributes should be ignored, and non-Noneattributes
treated as per the single bounds above.MultipleOfMultipleOf(multiple_of=x)might be interpreted in two ways:Python semantics, implyingvalue % multiple_of == 0, orJSONschema semantics,
whereint(value / multiple_of) == value / multiple_of.We encourage users to be aware of these two common interpretations and their
distinct behaviours, especially since very large or non-integer numbers make
it easy to cause silent data corruption due to floating-point imprecision.We encourage libraries to carefully document which interpretation they implement.MinLen, MaxLen, LenLen()implies thatmin_length <= len(value) <= max_length- lower and upper bounds are inclusive.As well asLen()which can optionally include upper and lower bounds, we also
provideMinLen(x)andMaxLen(y)which are equivalent toLen(min_length=x)andLen(max_length=y)respectively.Len,MinLen, andMaxLenmay be used with any type which supportslen(value).Examples of usage:Annotated[list, MaxLen(10)](orAnnotated[list, Len(max_length=10))) - list must have a length of 10 or lessAnnotated[str, MaxLen(10)]- string must have a length of 10 or lessAnnotated[list, MinLen(3))(orAnnotated[list, Len(min_length=3))) - list must have a length of 3 or moreAnnotated[list, Len(4, 6)]- list must have a length of 4, 5, or 6Annotated[list, Len(8, 8)]- list must have a length of exactly 8Changed in v0.4.0min_inclusivehas been renamed tomin_length, no change in meaningmax_exclusivehas been renamed tomax_length, upper bound is nowinclusiveinstead ofexclusiveThe recommendation that slices are interpreted asLenhas been removed due to ambiguity and different semantic
meaning of the upper bound in slices vs.LenSeeissue #23for discussion.TimezoneTimezonecan be used with adatetimeor atimeto express which timezones
are allowed.Annotated[datetime, Timezone(None)]must be a naive datetime.Timezone[...](literal ellipsis)
expresses that any timezone-aware datetime is allowed. You may also pass a specific
timezone string ortimezoneobject such asTimezone(timezone.utc)orTimezone("Africa/Abidjan")to express that you only allow a specific timezone,
though we note that this is often a symptom of fragile design.PredicatePredicate(func: Callable)expresses thatfunc(value)is truthy for valid values.
Users should prefer the statically inspectable metadata above, but if you need
the full power and flexibility of arbitrary runtime predicates... here it is.We provide a few predefined predicates for common string constraints:IsLower = Predicate(str.islower)IsUpper = Predicate(str.isupper)IsDigit = Predicate(str.isdigit)IsFinite = Predicate(math.isfinite)IsNotFinite = Predicate(Not(math.isfinite))IsNan = Predicate(math.isnan)IsNotNan = Predicate(Not(math.isnan))IsInfinite = Predicate(math.isinf)IsNotInfinite = Predicate(Not(math.isinf))Some libraries might have special logic to handle known or understandable predicates,
for example by checking forstr.isdigitand using its presence to both call custom
logic to enforce digit-only strings, and customise some generated external schema.
Users are therefore encouraged to avoid indirection likelambda s: s.lower(), in
favor of introspectable methods such asstr.lowerorre.compile("pattern").search.To enable basic negation of commonly used predicates likemath.isnanwithout introducing introspection that makes it impossible for implementers to introspect the predicate we provide aNotwrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.We do not specify what behaviour should be expected for predicates that raise
an exception.  For exampleAnnotated[int, Predicate(str.isdigit)]might silently
skip invalid constraints, or statically raise an error; or it might try calling it
and then propogate or discard the resultingTypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' objectexception.  We encourage libraries to document the behaviour they choose.Docdoc()can be used to add documentation information inAnnotated, for function and method parameters, variables, class attributes, return types, and any place whereAnnotatedcan be used.It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.It returns aDocInfoclass with a single attributedocumentationcontaining the value passed todoc().This is the early adopter's alternative form of thetyping-docproposal.Integrating downstream types withGroupedMetadataImplementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
This can help reduce verbosity and cognitive overhead for users.
For example, an implementer like Pydantic might provide aFieldorMetatype that accepts keyword arguments and transforms these into low-level metadata:fromdataclassesimportdataclassfromtypingimportIteratorfromannotated_typesimportGroupedMetadata,Ge@dataclassclassField(GroupedMetadata):ge:int|None=Nonedescription:str|None=Nonedef__iter__(self)->Iterator[object]:# Iterating over a GroupedMetadata object should yield annotated-types# constraint metadata objects which describe it as fully as possible,# and may include other unknown objects too.ifself.geisnotNone:yieldGe(self.ge)ifself.descriptionisnotNone:yieldDescription(self.description)Libraries consuming annotated-types constraints should check forGroupedMetadataand unpack it by iterating over the object and treating the results as if they had been "unpacked" in theAnnotatedtype.  The same logic should be applied to thePEP 646Unpacktype, so thatAnnotated[T, Field(...)],Annotated[T, Unpack[Field(...)]]andAnnotated[T, *Field(...)]are all treated consistently.Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking aGroupedMetadata, just like they ignore unrecognized metadata inAnnotateditself.Our ownannotated_types.Intervalclass is aGroupedMetadatawhich unpacks itself intoGt,Lt, etc., so this is not an abstract concern.  Similarly,annotated_types.Lenis aGroupedMetadatawhich unpacks itself intoMinLen(optionally) andMaxLen.Consuming metadataWe intend to not be prescriptive as tohowthe metadata and constraints are used, but as an example of how one might parse constraints from types annotations see ourimplementation intest_main.py.It is up to the implementer to determine how this metadata is used.
You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.Design & HistoryThis package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
and Hypothesis, with the goal of making it as easy as possible for end-users to
provide more informative annotations for use by runtime libraries.It is deliberately minimal, and following PEP-593 allows considerable downstream
discretion in what (if anything!) they choose to support. Nonetheless, we expect
that staying simple and coveringonlythe most common use-cases will give users
and maintainers the best experience we can. If you'd like more constraints for your
types - follow our lead, by defining them and documenting them downstream!