annotated-types

raw JSON →
0.7.0 verified Tue May 12 auth: no python install: verified quickstart: verified

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.

pip install annotated-types
error ModuleNotFoundError: No module named 'annotated_types'
cause The 'annotated-types' package is not installed in the Python environment.
fix
Install the package using 'pip install annotated-types'.
error ImportError: cannot import name 'Gt' from 'annotated_types'
cause The 'Gt' class is not available in the installed version of 'annotated-types'.
fix
Ensure you have the latest version by running 'pip install --upgrade annotated-types'.
error TypeError: 'Len' object is not callable
cause Attempting to call 'Len' as a function instead of using it as a type annotation.
fix
Use 'Len' within 'Annotated' for type annotations, e.g., 'Annotated[list[int], Len(0, 10)]'.
error AttributeError: module 'annotated_types' has no attribute 'Predicate'
cause The 'Predicate' attribute is not present in the 'annotated-types' module.
fix
Verify the module's documentation for the correct usage or alternative implementations.
error NameError: name 'Annotated' is not defined
cause The 'Annotated' type is not imported from the 'typing' module.
fix
Add 'from typing import Annotated' at the beginning of your script.
breaking Import module name uses underscores: `annotated_types`, not `annotated-types`. Using a hyphen causes a SyntaxError.
fix Use `from annotated_types import ...` (underscore) in all import statements.
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.
fix Replace Len(min_inclusive=a, max_exclusive=b) with Len(min_length=a, max_length=b-1) if exclusive semantics were intended, or Len(min_length=a, max_length=b) if inclusive was the intent.
breaking `IsDigit` was renamed to `IsDigits` in v0.7.0. Importing `IsDigit` raises an ImportError on 0.7.0+.
fix Replace `from annotated_types import IsDigit` with `from annotated_types import IsDigits`.
breaking Python 3.7 support was dropped in v0.6.0. The minimum required Python version is now 3.8.
fix Upgrade to Python 3.8+ or pin annotated-types <0.6.0 for Python 3.7 environments.
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.
fix Pair annotated-types constraints with a runtime enforcement library such as Pydantic v2 or a custom get_args() inspector.
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.
fix Check which interpretation your consuming library implements. Avoid using MultipleOf with non-integer or very large float values unless the library's behavior is explicitly documented.
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.
fix Use introspectable callables such as `str.isdigit`, `math.isfinite`, or `re.compile(...).search` instead of `lambda` wrappers inside Predicate.
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.
fix Focus on the behavior of the `annotated-types` library for failure diagnosis, disregarding unrelated output from package managers like pip.
python os / libc status wheel install import disk
3.10 alpine (musl) - - 0.05s 17.8M
3.10 slim (glibc) - - 0.03s 18M
3.11 alpine (musl) - - 0.10s 19.7M
3.11 slim (glibc) - - 0.08s 20M
3.12 alpine (musl) - - 0.09s 11.6M
3.12 slim (glibc) - - 0.08s 12M
3.13 alpine (musl) - - 0.07s 11.2M
3.13 slim (glibc) - - 0.07s 12M
3.9 alpine (musl) - - 0.04s 17.3M
3.9 slim (glibc) - - 0.04s 18M

Demonstrates scalar bounds, collection length constraints, predicates, IsDigits, and Timezone usage, plus how to introspect annotations from Annotated types. No auth required.

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)])