valid8r.integrations.pydantic

Pydantic integration for valid8r parsers.

This module provides utilities to convert valid8r parsers (which return Maybe[T]) into Pydantic field validators, enabling seamless integration with FastAPI and other Pydantic-based frameworks.

The integration supports: - Simple field validation with type parsing and validation - Nested model validation with field path error reporting - List[Model] validation with per-item error reporting - Dict[K, V] validation with per-value validation - Optional fields and complex structures

Example - Simple Field Validation:
>>> from pydantic import BaseModel, field_validator
>>> from valid8r.core import parsers, validators
>>> from valid8r.integrations.pydantic import validator_from_parser
>>>
>>> class User(BaseModel):
...     age: int
...
...     @field_validator('age', mode='before')
...     @classmethod
...     def validate_age(cls, v):
...         return validator_from_parser(
...             parsers.parse_int & validators.between(0, 120)
...         )(v)
Example - Nested Model Validation:
>>> from valid8r.core.parsers import PhoneNumber
>>>
>>> class Address(BaseModel):
...     phone: PhoneNumber
...
...     @field_validator('phone', mode='before')
...     @classmethod
...     def validate_phone(cls, v):
...         return validator_from_parser(parsers.parse_phone)(v)
>>>
>>> class User(BaseModel):
...     name: str
...     address: Address
>>>
>>> # Validation errors include full field path (e.g., 'address.phone')
>>> user = User(name='Alice', address={'phone': '(206) 234-5678'})
Example - List of Models:
>>> class LineItem(BaseModel):
...     quantity: int
...
...     @field_validator('quantity', mode='before')
...     @classmethod
...     def validate_quantity(cls, v):
...         def parser(value):
...             return parsers.parse_int(value).bind(validators.minimum(1))
...         return validator_from_parser(parser)(v)
>>>
>>> class Order(BaseModel):
...     items: list[LineItem]
>>>
>>> # Validation errors include list index (e.g., 'items[1].quantity')
>>> order = Order(items=[{'quantity': '5'}, {'quantity': '10'}])
Example - Dict Value Validation:
>>> class Config(BaseModel):
...     ports: dict[str, int]
...
...     @field_validator('ports', mode='before')
...     @classmethod
...     def validate_ports(cls, v):
...         if not isinstance(v, dict):
...             raise ValueError('ports must be a dict')
...         return {k: validator_from_parser(parsers.parse_int)(val) for k, val in v.items()}
>>>
>>> config = Config(ports={'http': '80', 'https': '443'})

Functions

validator_from_parser(parser, *[, error_prefix])

Convert a valid8r parser into a Pydantic field validator.

make_after_validator(parser)

Create a Pydantic AfterValidator from a valid8r parser.

make_wrap_validator(parser)

Create a Pydantic WrapValidator from a valid8r parser.

Module Contents

valid8r.integrations.pydantic.validator_from_parser(parser, *, error_prefix=None)[source]

Convert a valid8r parser into a Pydantic field validator.

This function takes a valid8r parser (any callable that returns Maybe[T]) and converts it into a function suitable for use with Pydantic’s field_validator decorator.

Works seamlessly with: - Simple fields (str, int, custom types) - Nested models (User -> Address -> phone) - Lists of models (Order with list[LineItem]) - Dicts with validated values (Config with dict[str, int]) - Optional fields (field: Model | None)

Pydantic automatically handles field path reporting for nested structures, so validation errors will include the full path (e.g., ‘address.phone’ or ‘items[1].quantity’).

Parameters:
Returns:

A validator function that returns T on success or raises ValueError on failure.

Raises:

ValueError – When the parser returns a Failure with the error message.

Return type:

collections.abc.Callable[[Any], T]

Example

>>> from valid8r.core import parsers
>>> validator = validator_from_parser(parsers.parse_int)
>>> validator('42')
42
>>> validator('invalid')
Traceback (most recent call last):
    ...
ValueError: ...
>>> # With custom error prefix
>>> validator = validator_from_parser(parsers.parse_int, error_prefix='User ID')
>>> validator('invalid')
Traceback (most recent call last):
    ...
ValueError: User ID: ...
>>> # Nested model validation
>>> from pydantic import BaseModel, field_validator
>>> from valid8r.core.parsers import EmailAddress
>>>
>>> class Contact(BaseModel):
...     email: EmailAddress
...
...     @field_validator('email', mode='before')
...     @classmethod
...     def validate_email(cls, v):
...         return validator_from_parser(parsers.parse_email)(v)
>>>
>>> contact = Contact(email='user@example.com')
valid8r.integrations.pydantic.make_after_validator(parser)[source]

Create a Pydantic AfterValidator from a valid8r parser.

AfterValidator runs after Pydantic’s type conversion, allowing you to use valid8r parsers with Pydantic’s Annotated type system for cleaner model definitions.

This function wraps a valid8r parser (which returns Maybe[T]) into a function suitable for use with Pydantic’s AfterValidator. For optional fields (e.g., str | None), None values are passed through without validation.

Parameters:

parser (collections.abc.Callable[[Any], valid8r.core.maybe.Maybe[T]]) – A valid8r parser function that returns Maybe[T].

Returns:

A validator function that returns T on success, None for None inputs, or raises ValueError on failure.

Raises:

ValueError – When the parser returns a Failure with the error message.

Return type:

collections.abc.Callable[[Any], T | None]

Example

>>> from pydantic import BaseModel, AfterValidator
>>> from typing_extensions import Annotated
>>> from valid8r.core import parsers
>>>
>>> email_validator = make_after_validator(parsers.parse_email)
>>>
>>> class User(BaseModel):
...     email: Annotated[str, AfterValidator(email_validator)]
>>>
>>> user = User(email='alice@example.com')
>>> # Optional field example
>>> class Contact(BaseModel):
...     email: Annotated[str | None, AfterValidator(email_validator)] = None
>>>
>>> contact = Contact(email=None)
>>> contact.email is None
True
valid8r.integrations.pydantic.make_wrap_validator(parser)[source]

Create a Pydantic WrapValidator from a valid8r parser.

WrapValidator runs before Pydantic’s type conversion, receiving raw input values. This allows full control over validation and pre-processing.

This function wraps a valid8r parser (which returns Maybe[T]) into a function suitable for use with Pydantic’s WrapValidator.

Parameters:

parser (collections.abc.Callable[[Any], valid8r.core.maybe.Maybe[T]]) – A valid8r parser function that returns Maybe[T].

Returns:

A wrap validator function that returns T on success or raises ValueError on failure. The function signature is (value, handler) -> T, though handler is not used since the parser handles all validation.

Raises:

ValueError – When the parser returns a Failure with the error message.

Return type:

collections.abc.Callable[[Any, Any], T]

Example

>>> from pydantic import BaseModel, WrapValidator
>>> from typing_extensions import Annotated
>>> from valid8r.core import parsers
>>>
>>> int_validator = make_wrap_validator(parsers.parse_int)
>>>
>>> class Data(BaseModel):
...     value: Annotated[int, WrapValidator(int_validator)]
>>>
>>> data = Data(value='42')