Skip to content

Validate API Reference

Auto-generated API documentation for the validate module.

validate

Zero-dependency runtime validator for TypedDict and dataclass types.

Part of zerodep: https://github.com/Oaklight/zerodep Copyright (c) 2026 Peng Ding. MIT License.

Validate arbitrary data against stdlib type annotations (TypedDict, dataclass, Annotated constraints) and generate JSON Schema from the same type definitions.

Basic usage::

from validate import validate, json_schema, ValidationError

class User(TypedDict):
    name: str
    age: int

validate({"name": "Alice", "age": 30}, User)   # ok
validate({"name": "Alice", "age": "x"}, User)   # raises ValidationError

schema = json_schema(User)  # {"type": "object", "properties": ...}

Annotated constraints::

from validate import Gt, MinLen
from typing import Annotated

class Item(TypedDict):
    name: Annotated[str, MinLen(1)]
    price: Annotated[float, Gt(0)]

Field validators (transform + validate)::

from validate import FieldValidator

def strip_lower(v: str) -> str:
    v = v.strip().lower()
    if not v:
        raise ValueError("must not be empty")
    return v

class User(TypedDict):
    name: Annotated[str, FieldValidator(strip_lower)]

Model validators (cross-field validation)::

from validate import model_validator

class RegisterForm(TypedDict):
    password: str
    confirm: str

@model_validator(RegisterForm)
def passwords_match(data: dict) -> dict:
    if data["password"] != data["confirm"]:
        raise ValueError("passwords do not match")
    return data

Gt dataclass

Value must be strictly greater than val.

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class Gt:
    """Value must be strictly greater than *val*."""

    val: float

    def check(self, value: Any) -> bool:
        return value > self.val

    def schema_kw(self) -> dict[str, Any]:
        return {"exclusiveMinimum": self.val}

    def __str__(self) -> str:
        return f"> {self.val}"

Ge dataclass

Value must be greater than or equal to val.

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class Ge:
    """Value must be greater than or equal to *val*."""

    val: float

    def check(self, value: Any) -> bool:
        return value >= self.val

    def schema_kw(self) -> dict[str, Any]:
        return {"minimum": self.val}

    def __str__(self) -> str:
        return f">= {self.val}"

Lt dataclass

Value must be strictly less than val.

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class Lt:
    """Value must be strictly less than *val*."""

    val: float

    def check(self, value: Any) -> bool:
        return value < self.val

    def schema_kw(self) -> dict[str, Any]:
        return {"exclusiveMaximum": self.val}

    def __str__(self) -> str:
        return f"< {self.val}"

Le dataclass

Value must be less than or equal to val.

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class Le:
    """Value must be less than or equal to *val*."""

    val: float

    def check(self, value: Any) -> bool:
        return value <= self.val

    def schema_kw(self) -> dict[str, Any]:
        return {"maximum": self.val}

    def __str__(self) -> str:
        return f"<= {self.val}"

MinLen dataclass

Length must be at least val.

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class MinLen:
    """Length must be at least *val*."""

    val: int

    def check(self, value: Any) -> bool:
        return len(value) >= self.val

    def schema_kw(self) -> dict[str, Any]:
        if isinstance(self.val, int):
            return {"minLength": self.val}
        return {"minLength": self.val}

    def schema_kw_array(self) -> dict[str, Any]:
        return {"minItems": self.val}

    def __str__(self) -> str:
        return f"len >= {self.val}"

MaxLen dataclass

Length must be at most val.

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class MaxLen:
    """Length must be at most *val*."""

    val: int

    def check(self, value: Any) -> bool:
        return len(value) <= self.val

    def schema_kw(self) -> dict[str, Any]:
        return {"maxLength": self.val}

    def schema_kw_array(self) -> dict[str, Any]:
        return {"maxItems": self.val}

    def __str__(self) -> str:
        return f"len <= {self.val}"

Match dataclass

Value must match pattern (via re.fullmatch).

Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class Match:
    """Value must match *pattern* (via ``re.fullmatch``)."""

    pattern: str

    def check(self, value: Any) -> bool:
        return re.fullmatch(self.pattern, value) is not None

    def schema_kw(self) -> dict[str, Any]:
        return {"pattern": self.pattern}

    def __str__(self) -> str:
        return f"match({self.pattern!r})"

Predicate dataclass

Value must satisfy a custom predicate function.

Parameters:

Name Type Description Default
fn Callable[[Any], bool]

A callable (value) -> bool.

required
description str

Human-readable description for error messages.

'custom predicate'
Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class Predicate:
    """Value must satisfy a custom predicate function.

    Args:
        fn: A callable ``(value) -> bool``.
        description: Human-readable description for error messages.
    """

    fn: Callable[[Any], bool]
    description: str = "custom predicate"

    def check(self, value: Any) -> bool:
        return self.fn(value)

    def schema_kw(self) -> dict[str, Any]:
        return {}

    def __str__(self) -> str:
        return self.description

FieldValidator dataclass

Custom validator that can transform the field value.

Unlike Predicate (which returns bool), the function receives the validated value and returns a (possibly transformed) value. Raise ValueError or AssertionError to signal failure.

Parameters:

Name Type Description Default
fn Callable[[Any], Any]

A callable (value) -> value. Raise on failure.

required
description str

Human-readable description for error messages.

'custom validator'
Source code in validate/validate.py
@dataclasses.dataclass(frozen=True, slots=True)
class FieldValidator:
    """Custom validator that can transform the field value.

    Unlike ``Predicate`` (which returns bool), the function receives the
    validated value and returns a (possibly transformed) value.  Raise
    ``ValueError`` or ``AssertionError`` to signal failure.

    Args:
        fn: A callable ``(value) -> value``.  Raise on failure.
        description: Human-readable description for error messages.
    """

    fn: Callable[[Any], Any]
    description: str = "custom validator"

    def validate(self, value: Any) -> Any:
        return self.fn(value)

    def schema_kw(self) -> dict[str, Any]:
        return {}

    def __str__(self) -> str:
        return self.description

ErrorDetail dataclass

A single validation error.

Attributes:

Name Type Description
path str

Dotted/bracketed path to the failing field (e.g. "items[2].name").

expected str

Expected type or constraint description.

actual str

Actual type or value description.

message str

Human-readable error message.

Source code in validate/validate.py
@dataclasses.dataclass
class ErrorDetail:
    """A single validation error.

    Attributes:
        path: Dotted/bracketed path to the failing field (e.g. ``"items[2].name"``).
        expected: Expected type or constraint description.
        actual: Actual type or value description.
        message: Human-readable error message.
    """

    path: str
    expected: str
    actual: str
    message: str

ValidationError

Bases: Exception

Raised when validation fails.

Attributes:

Name Type Description
errors

List of all validation errors found.

Source code in validate/validate.py
class ValidationError(Exception):
    """Raised when validation fails.

    Attributes:
        errors: List of all validation errors found.
    """

    def __init__(self, errors: list[ErrorDetail]) -> None:
        self.errors = errors
        msgs = "; ".join(e.message for e in errors[:5])
        if len(errors) > 5:
            msgs += f" ... and {len(errors) - 5} more"
        super().__init__(f"{len(errors)} validation error(s): {msgs}")

model_validator(tp)

Register a model-level validator for a TypedDict or dataclass type.

The validator receives the full data dict after all field validation passes. It should return the (possibly modified) dict, or raise ValueError / AssertionError on failure.

Parameters:

Name Type Description Default
tp type

The TypedDict or dataclass type to attach the validator to.

required

Returns:

Type Description
Callable[[Callable], Callable]

A decorator that registers the function and returns it unchanged.

Example::

class RegisterForm(TypedDict):
    password: str
    confirm: str

@model_validator(RegisterForm)
def passwords_match(data: dict) -> dict:
    if data["password"] != data["confirm"]:
        raise ValueError("passwords do not match")
    return data
Source code in validate/validate.py
def model_validator(tp: type) -> Callable[[Callable], Callable]:
    """Register a model-level validator for a TypedDict or dataclass type.

    The validator receives the full data dict after all field validation
    passes.  It should return the (possibly modified) dict, or raise
    ``ValueError`` / ``AssertionError`` on failure.

    Args:
        tp: The TypedDict or dataclass type to attach the validator to.

    Returns:
        A decorator that registers the function and returns it unchanged.

    Example::

        class RegisterForm(TypedDict):
            password: str
            confirm: str

        @model_validator(RegisterForm)
        def passwords_match(data: dict) -> dict:
            if data["password"] != data["confirm"]:
                raise ValueError("passwords do not match")
            return data
    """

    def decorator(fn: Callable) -> Callable:
        _MODEL_VALIDATORS.setdefault(tp, []).append(fn)
        return fn

    return decorator

validate(data, tp, *, coerce=False)

Validate data against type annotation tp.

Parameters:

Name Type Description Default
data Any

The data to validate.

required
tp Any

A TypedDict class, dataclass class, or any type annotation.

required
coerce bool

If True, attempt type coercion (e.g. str to int).

False

Returns:

Type Description
Any

The validated (and possibly coerced) data.

Raises:

Type Description
ValidationError

If validation fails, with all errors collected.

Source code in validate/validate.py
def validate(data: Any, tp: Any, *, coerce: bool = False) -> Any:
    """Validate *data* against type annotation *tp*.

    Args:
        data: The data to validate.
        tp: A TypedDict class, dataclass class, or any type annotation.
        coerce: If True, attempt type coercion (e.g. str to int).

    Returns:
        The validated (and possibly coerced) data.

    Raises:
        ValidationError: If validation fails, with all errors collected.
    """
    errors: list[ErrorDetail] = []
    result = _validate(data, tp, "", errors, coerce)
    if errors:
        raise ValidationError(errors)
    return result

json_schema(tp, *, title=None)

Generate a JSON Schema dict from a type annotation.

Parameters:

Name Type Description Default
tp Any

A TypedDict class, dataclass class, or any type annotation.

required
title str | None

Optional title for the schema root.

None

Returns:

Type Description
dict[str, Any]

A JSON Schema dict (draft 2020-12 compatible subset).

Source code in validate/validate.py
def json_schema(tp: Any, *, title: str | None = None) -> dict[str, Any]:
    """Generate a JSON Schema dict from a type annotation.

    Args:
        tp: A TypedDict class, dataclass class, or any type annotation.
        title: Optional title for the schema root.

    Returns:
        A JSON Schema dict (draft 2020-12 compatible subset).
    """
    schema = _type_to_schema(tp)
    if title is not None:
        schema["title"] = title
    elif isinstance(tp, type) and hasattr(tp, "__name__"):
        schema["title"] = tp.__name__
    return schema