Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Reusable constraint types to use with typing.Annotated

License

NotificationsYou must be signed in to change notification settings

annotated-types/annotated-types

Repository files navigation

CIpypiversionslicense

PEP-593 addedtyping.Annotated as a way ofadding context-specific metadata to existing types, and specifies thatAnnotated[T, x]should be treated asT by any tool or library without speciallogic forx.

This package provides metadata objects which can be used to represent commonconstraints such as upper and lower bounds on scalar values and collection sizes,aPredicate marker for runtime checks, anddescriptions of how we intend these metadata to be interpreted. In some cases,we also note alternative representations which do not require this package.

Install

pip install annotated-types

Examples

fromtypingimportAnnotatedfromannotated_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] * 20

Documentation

Whileannotated-types avoids runtime checks for performance, users should notconstruct invalid combinations such asMultipleOf("non-numeric") orAnnotated[int, Len(3)].Downstream implementors may choose to raise an error, emit a warning, silently ignorea metadata item, etc., if the metadata objects described below are used with anincompatible type - or for any other reason!

Gt, Ge, Lt, Le

Express 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 thesame 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 ontheannotated-types package.

To be explicit, these types have the following meanings:

  • Gt(x) - value must be "Greater Than"x - equivalent to exclusive minimum
  • Ge(x) - value must be "Greater than or Equal" tox - equivalent to inclusive minimum
  • Lt(x) - value must be "Less Than"x - equivalent to exclusive maximum
  • Le(x) - value must be "Less than or Equal" tox - equivalent to inclusive maximum

Interval

Interval(gt, ge, lt, le) allows you to specify an upper and lower bound with a singlemetadata object.None attributes should be ignored, and non-None attributestreated as per the single bounds above.

MultipleOf

MultipleOf(multiple_of=x) might be interpreted in two ways:

  1. Python semantics, implyingvalue % multiple_of == 0, or
  2. JSONschema semantics,whereint(value / multiple_of) == value / multiple_of.

We encourage users to be aware of these two common interpretations and theirdistinct behaviours, especially since very large or non-integer numbers makeit easy to cause silent data corruption due to floating-point imprecision.

We encourage libraries to carefully document which interpretation they implement.

MinLen, MaxLen, Len

Len() 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 alsoprovideMinLen(x) andMaxLen(y) which are equivalent toLen(min_length=x)andLen(max_length=y) respectively.

Len,MinLen, andMaxLen may 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 less
  • Annotated[str, MaxLen(10)] - string must have a length of 10 or less
  • Annotated[list, MinLen(3)] (orAnnotated[list, Len(min_length=3)]) - list must have a length of 3 or more
  • Annotated[list, Len(4, 6)] - list must have a length of 4, 5, or 6
  • Annotated[list, Len(8, 8)] - list must have a length of exactly 8

Changed in v0.4.0

  • min_inclusive has been renamed tomin_length, no change in meaning
  • max_exclusive has been renamed tomax_length, upper bound is nowinclusive instead ofexclusive
  • The recommendation that slices are interpreted asLen has been removed due to ambiguity and different semanticmeaning of the upper bound in slices vs.Len

Seeissue #23 for discussion.

Timezone

Timezone can be used with adatetime or atime to express which timezonesare 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 specifictimezone string ortzinfoobject such asTimezone(timezone.utc) orTimezone("Africa/Abidjan") to express that you onlyallow a specific timezone, though we note that this is often a symptom of fragile design.

Changed in v0.x.x

  • Timezone acceptstzinfo objects instead oftimezone, extending compatibility tozoneinfo and third party libraries.

Unit

Unit(unit: str) expresses that the annotated numeric value is the magnitude ofa quantity with the specified unit. For example,Annotated[float, Unit("m/s")]would be a float representing a velocity in meters per second.

Please note thatannotated_types itself makes no attempt to parse or validatethe unit string in any way. That is left entirely to downstream libraries,such aspint orastropy.units.

An example of how a library might use this metadata:

fromannotated_typesimportUnitfromtypingimportAnnotated,TypeVar,Callable,Any,get_origin,get_args# given a type annotated with a unit:Meters=Annotated[float,Unit("m")]# you can cast the annotation to a specific unit type with any# callable that accepts a string and returns the desired typeT=TypeVar("T")defcast_unit(tp:Any,unit_cls:Callable[[str],T])->T|None:ifget_origin(tp)isAnnotated:forarginget_args(tp):ifisinstance(arg,Unit):returnunit_cls(arg.unit)returnNone# using `pint`importpintpint_unit=cast_unit(Meters,pint.Unit)# using `astropy.units`importastropy.unitsasuastropy_unit=cast_unit(Meters,u.Unit)

Predicate

Predicate(func: Callable) expresses thatfunc(value) is truthy for valid values.Users should prefer the statically inspectable metadata above, but if you needthe full power and flexibility of arbitrary runtime predicates... here it is.

For some common constraints, we provide generic types:

  • LowerCase = Annotated[T, Predicate(str.islower)]
  • UpperCase = Annotated[T, Predicate(str.isupper)]
  • IsDigit = Annotated[T, Predicate(str.isdigit)]
  • IsFinite = Annotated[T, Predicate(math.isfinite)]
  • IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]
  • IsNan = Annotated[T, Predicate(math.isnan)]
  • IsNotNan = Annotated[T, Predicate(Not(math.isnan))]
  • IsInfinite = Annotated[T, Predicate(math.isinf)]
  • IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]

so that you can write e.g.x: IsFinite[float] = 2.0 instead of the longer(but exactly equivalent)x: Annotated[float, Predicate(math.isfinite)] = 2.0.

Some libraries might have special logic to handle known or understandable predicates,for example by checking forstr.isdigit and using its presence to both call customlogic to enforce digit-only strings, and customise some generated external schema.Users are therefore encouraged to avoid indirection likelambda s: s.lower(), infavor of introspectable methods such asstr.lower orre.compile("pattern").search.

To enable basic negation of commonly used predicates likemath.isnan without introducing introspection that makes it impossible for implementers to introspect the predicate we provide aNot wrapper 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 raisean exception. For exampleAnnotated[int, Predicate(str.isdigit)] might silentlyskip invalid constraints, or statically raise an error; or it might try calling itand then propagate 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.

Doc

doc() can be used to add documentation information inAnnotated, for function and method parameters, variables, class attributes, return types, and any place whereAnnotated can 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 aDocInfo class with a single attributedocumentation containing the value passed todoc().

This is the early adopter's alternative form of thetyping-doc proposal.

Integrating downstream types withGroupedMetadata

Implementers 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 aField orMeta type 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 forGroupedMetadata and unpack it by iterating over the object and treating the results as if they had been "unpacked" in theAnnotated type. The same logic should be applied to thePEP 646Unpack type, 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 inAnnotated itself.

Our ownannotated_types.Interval class is aGroupedMetadata which unpacks itself intoGt,Lt, etc., so this is not an abstract concern. Similarly,annotated_types.Len is aGroupedMetadata which unpacks itself intoMinLen (optionally) andMaxLen.

Consuming metadata

We intend to not be prescriptive as tohow the 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 & History

This package was designed at the PyCon 2022 sprints by the maintainers of Pydanticand Hypothesis, with the goal of making it as easy as possible for end-users toprovide more informative annotations for use by runtime libraries.

It is deliberately minimal, and following PEP-593 allows considerable downstreamdiscretion in what (if anything!) they choose to support. Nonetheless, we expectthat staying simple and coveringonly the most common use-cases will give usersand maintainers the best experience we can. If you'd like more constraints for yourtypes - follow our lead, by defining them and documenting them downstream!


[8]ページ先頭

©2009-2025 Movatter.jp