annotated-types
annotated-types provides reusable constraint metadata objects—such as Gt, Lt, Len, MultipleOf, Timezone, Predicate, and more—to be used with typing.Annotated (PEP 593). It does not enforce constraints itself; enforcement is left to consuming libraries like Pydantic, Hypothesis, or custom validators. Current version is 0.7.0 (released 2024). The project follows an irregular, feature-driven release cadence and was created at PyCon 2022 by the Pydantic and Hypothesis maintainers.
Common errors
-
ModuleNotFoundError: No module named 'annotated_types'
cause The 'annotated-types' package is not installed in the Python environment.fixInstall the package using 'pip install annotated-types'. -
ImportError: cannot import name 'Gt' from 'annotated_types'
cause The 'Gt' class is not available in the installed version of 'annotated-types'.fixEnsure you have the latest version by running 'pip install --upgrade annotated-types'. -
TypeError: 'Len' object is not callable
cause Attempting to call 'Len' as a function instead of using it as a type annotation.fixUse 'Len' within 'Annotated' for type annotations, e.g., 'Annotated[list[int], Len(0, 10)]'. -
AttributeError: module 'annotated_types' has no attribute 'Predicate'
cause The 'Predicate' attribute is not present in the 'annotated-types' module.fixVerify the module's documentation for the correct usage or alternative implementations. -
NameError: name 'Annotated' is not defined
cause The 'Annotated' type is not imported from the 'typing' module.fixAdd 'from typing import Annotated' at the beginning of your script.
Warnings
- breaking Import module name uses underscores: `annotated_types`, not `annotated-types`. Using a hyphen causes a SyntaxError.
- breaking In v0.4.0, Len's kwargs were renamed and the semantics of the upper bound changed: `min_inclusive` → `min_length` (same meaning), `max_exclusive` → `max_length` (now INCLUSIVE). Code using old kwargs or assuming exclusive upper bound will silently produce wrong constraints.
- breaking `IsDigit` was renamed to `IsDigits` in v0.7.0. Importing `IsDigit` raises an ImportError on 0.7.0+.
- breaking Python 3.7 support was dropped in v0.6.0. The minimum required Python version is now 3.8.
- gotcha annotated-types does NOT enforce constraints at runtime itself. Metadata is purely declarative; enforcement depends entirely on the consuming library (e.g. Pydantic, Hypothesis). Annotating a field does not validate values without an active consumer.
- gotcha MultipleOf has two semantically different interpretations: Python modulo (`value % multiple_of == 0`) vs. JSONSchema (`int(value / multiple_of) == value / multiple_of`). For floats, these can silently diverge due to floating-point imprecision.
- gotcha Using lambda functions in Predicate prevents consuming libraries from introspecting the predicate for schema generation or targeted optimisations. Libraries may silently ignore or mishandle opaque lambdas.
- gotcha The test output does not indicate a failure of the `annotated-types` library; it shows successful definition of constraints and includes unrelated `pip` warnings/notices.
Install
-
pip install annotated-types
Imports
- Gt, Lt, Ge, Le, Interval
from annotated-types import Gt
from annotated_types import Gt, Lt, Ge, Le, Interval
- Len, MinLen, MaxLen
from annotated_types import Len; Len(min_inclusive=0, max_exclusive=10)
from annotated_types import Len, MinLen, MaxLen
- Predicate
from annotated_types import Predicate
- Timezone
from annotated_types import Timezone
- IsDigits
from annotated_types import IsDigit
from annotated_types import IsDigits
- Unit
from annotated_types import Unit
- GroupedMetadata
from annotated_types import GroupedMetadata
Quickstart
from typing import Annotated, get_args, get_origin
import math
from annotated_types import Gt, Lt, Len, MinLen, MaxLen, MultipleOf, Predicate, Interval, Timezone, IsDigits
from datetime import datetime, timezone
# Scalar bounds
PositiveInt = Annotated[int, Gt(0)]
SmallFloat = Annotated[float, Interval(ge=0.0, le=1.0)]
# Collection length (both bounds inclusive since v0.4.0)
ShortList = Annotated[list, Len(1, 10)]
NonEmptyStr = Annotated[str, MinLen(1)]
CappedStr = Annotated[str, MaxLen(255)]
# Predicate — prefer introspectable callables over lambdas
FiniteFloat = Annotated[float, Predicate(math.isfinite)]
DigitOnly = Annotated[str, IsDigits]
# Timezone (v0.7.0+: accepts tzinfo objects)
UTCDatetime = Annotated[datetime, Timezone(timezone.utc)]
AwareDatetime = Annotated[datetime, Timezone(...)]
NaiveDatetime = Annotated[datetime, Timezone(None)]
# Reading metadata back (for library authors)
def show_constraints(tp):
if get_origin(tp) is Annotated:
base, *metadata = get_args(tp)
print(f"Base type: {base}, constraints: {metadata}")
show_constraints(Annotated[int, Gt(0), Lt(100)])