valid8r

Valid8r: A clean, flexible input validation library for Python.

Submodules

Attributes

__version__

Classes

ErrorCode

Standard validation error codes for programmatic error handling.

ValidationError

Structured validation error with code, message, path, and context.

Failure

Represents a failed computation with an error message or ValidationError.

Maybe

Base class for the Maybe monad.

Success

Represents a successful computation with a value.

EmailAddress

Structured email address.

PhoneNumber

Structured North American phone number (NANP).

UrlParts

Structured URL components.

Field

Schema field definition with parser, validators, and required flag.

Schema

Schema for validating dict-like objects with error accumulation.

Functions

from_type(annotation)

Generate a parser from a Python type annotation.

Package Contents

valid8r.__version__ = '1.30.0'[source]
class valid8r.ErrorCode[source]

Standard validation error codes for programmatic error handling.

Error codes are organized by category to make it easy to find and use the appropriate code for different validation scenarios.

Usage:
>>> from valid8r.core.errors import ErrorCode, ValidationError
>>> error = ValidationError(
...     code=ErrorCode.INVALID_EMAIL,
...     message='Email format is invalid'
... )
>>> error.code == ErrorCode.INVALID_EMAIL
True
Categories:
  • Parsing: INVALID_TYPE, INVALID_FORMAT, PARSE_ERROR

  • Numeric: OUT_OF_RANGE, BELOW_MINIMUM, ABOVE_MAXIMUM

  • String: TOO_SHORT, TOO_LONG, PATTERN_MISMATCH, EMPTY_STRING

  • Collection: NOT_IN_SET, DUPLICATE_ITEMS, INVALID_SUBSET

  • Network: INVALID_EMAIL, INVALID_URL, INVALID_IP, INVALID_PHONE

  • Filesystem: PATH_NOT_FOUND, NOT_A_FILE, NOT_A_DIRECTORY, FILE_TOO_LARGE

  • DoS Protection: INPUT_TOO_LONG

  • Generic: CUSTOM_ERROR, VALIDATION_ERROR

INVALID_TYPE = 'INVALID_TYPE'

Type conversion failed (e.g., string to int)

INVALID_FORMAT = 'INVALID_FORMAT'

Input format does not match expected pattern

PARSE_ERROR = 'PARSE_ERROR'

General parsing failure

OUT_OF_RANGE = 'OUT_OF_RANGE'

Value is outside the allowed range

BELOW_MINIMUM = 'BELOW_MINIMUM'

Value is below the minimum allowed value

ABOVE_MAXIMUM = 'ABOVE_MAXIMUM'

Value is above the maximum allowed value

TOO_SHORT = 'TOO_SHORT'

String length is below minimum

TOO_LONG = 'TOO_LONG'

String length exceeds maximum

PATTERN_MISMATCH = 'PATTERN_MISMATCH'

String does not match required regex pattern

EMPTY_STRING = 'EMPTY_STRING'

String is empty when a value is required

NOT_IN_SET = 'NOT_IN_SET'

Value is not in the allowed set of values

DUPLICATE_ITEMS = 'DUPLICATE_ITEMS'

Collection contains duplicate items when uniqueness is required

INVALID_SUBSET = 'INVALID_SUBSET'

Collection is not a valid subset of allowed values

INVALID_EMAIL = 'INVALID_EMAIL'

Email address format is invalid

INVALID_URL = 'INVALID_URL'

URL format is invalid

INVALID_IP = 'INVALID_IP'

IP address format is invalid

INVALID_PHONE = 'INVALID_PHONE'

Phone number format is invalid

PATH_NOT_FOUND = 'PATH_NOT_FOUND'

File or directory path does not exist

NOT_A_FILE = 'NOT_A_FILE'

Path exists but is not a file

NOT_A_DIRECTORY = 'NOT_A_DIRECTORY'

Path exists but is not a directory

FILE_TOO_LARGE = 'FILE_TOO_LARGE'

File size exceeds maximum allowed size

INPUT_TOO_LONG = 'INPUT_TOO_LONG'

Input exceeds maximum length (DoS protection)

CUSTOM_ERROR = 'CUSTOM_ERROR'

User-defined custom validation error

VALIDATION_ERROR = 'VALIDATION_ERROR'

Generic validation failure

class valid8r.ValidationError[source]

Structured validation error with code, message, path, and context.

ValidationError provides a machine-readable error representation that includes: - Error code for programmatic handling - Human-readable error message - Field path for multi-field validation - Additional context for debugging

The error is immutable (frozen) to prevent accidental modification after creation.

code

Machine-readable error code (e.g., ‘INVALID_EMAIL’, ‘OUT_OF_RANGE’)

message

Human-readable error message describing the failure

path

JSON path to the field that failed (e.g., ‘.user.email’, ‘.items[0].name’)

context

Additional context dict with debugging information (e.g., {‘min’: 0, ‘max’: 100, ‘value’: 150})

Examples

Basic error with code and message:

>>> error = ValidationError(code='PARSE_ERROR', message='Failed to parse input')
>>> error.code
'PARSE_ERROR'
>>> error.message
'Failed to parse input'

Error with field path:

>>> error = ValidationError(
...     code='INVALID_EMAIL',
...     message='Email address format is invalid',
...     path='.user.email'
... )
>>> str(error)
'.user.email: Email address format is invalid'

Error with validation context:

>>> error = ValidationError(
...     code='OUT_OF_RANGE',
...     message='Value must be between 0 and 100',
...     path='.user.age',
...     context={'value': 150, 'min': 0, 'max': 100}
... )
>>> error.to_dict()
{'code': 'OUT_OF_RANGE', 'message': 'Value must be between 0 and 100',
 'path': '.user.age', 'context': {'value': 150, 'min': 0, 'max': 100}}
code: str
message: str
path: str = ''
context: dict[str, Any] | None = None
__str__()[source]

Return human-readable representation with optional path prefix.

Returns:

message’ if path is present, otherwise just ‘message’

Return type:

String in format ‘path

Examples

>>> error = ValidationError(code='TEST', message='Error message', path='.field')
>>> str(error)
'.field: Error message'
>>> error = ValidationError(code='TEST', message='Error message')
>>> str(error)
'Error message'
to_dict()[source]

Convert error to dictionary for JSON serialization.

Returns empty dict for context if None to ensure consistent JSON structure.

Returns:

code, message, path, context

Return type:

Dictionary with keys

Examples

>>> error = ValidationError(
...     code='INVALID_TYPE',
...     message='Expected integer',
...     path='.age',
...     context={'input': 'abc'}
... )
>>> error.to_dict()
{'code': 'INVALID_TYPE', 'message': 'Expected integer', 'path': '.age', 'context': {'input': 'abc'}}
>>> error = ValidationError(code='PARSE_ERROR', message='Failed to parse')
>>> error.to_dict()
{'code': 'PARSE_ERROR', 'message': 'Failed to parse', 'path': '', 'context': {}}
class valid8r.Failure(error)[source]

Bases: Maybe[T]

Represents a failed computation with an error message or ValidationError.

Failure now accepts both string error messages (backward compatible) and ValidationError instances (new structured error support).

When a string is provided, it’s automatically wrapped in a ValidationError with code=’VALIDATION_ERROR’ for consistent internal handling.

Examples

Backward compatible string error:

>>> failure = Failure('Something went wrong')
>>> failure.error_or('')
'Something went wrong'

New structured error:

>>> from valid8r.core.errors import ValidationError, ErrorCode
>>> error = ValidationError(code=ErrorCode.INVALID_EMAIL, message='Bad email')
>>> failure = Failure(error)
>>> failure.validation_error.code
'INVALID_EMAIL'
Parameters:

error (str | valid8r.core.errors.ValidationError)

__match_args__ = ('error',)
property error: str

Get the error message string (backward compatible for pattern matching).

This property returns the message string to maintain backward compatibility with existing pattern matching code: case Failure(error): assert error == "message"

For structured error access, use the validation_error property instead.

Returns:

Error message string

Return type:

str

property validation_error: valid8r.core.errors.ValidationError

Get the structured ValidationError instance.

Use this property to access the full structured error with code, path, and context.

Returns:

ValidationError instance

Return type:

valid8r.core.errors.ValidationError

Examples

>>> from valid8r.core.errors import ValidationError, ErrorCode
>>> error = ValidationError(code=ErrorCode.INVALID_EMAIL, message='Bad email', path='.email')
>>> failure = Failure(error)
>>> failure.validation_error.code
'INVALID_EMAIL'
>>> failure.validation_error.path
'.email'
error_detail()[source]

Get the structured ValidationError instance (RFC-001 Phase 2).

This method provides access to the full structured error with code, path, and context. It returns the same object as the validation_error property, but follows the RFC-001 specification for the public API.

For backward compatibility, both error_detail() and validation_error property are maintained.

Returns:

ValidationError instance with code, message, path, and context

Return type:

valid8r.core.errors.ValidationError

Examples

Access structured error from string failure:

>>> failure = Failure('Invalid input')
>>> error = failure.error_detail()
>>> error.code
'VALIDATION_ERROR'
>>> error.message
'Invalid input'

Access structured error from ValidationError failure:

>>> from valid8r.core.errors import ValidationError, ErrorCode
>>> error = ValidationError(code=ErrorCode.OUT_OF_RANGE, message='Too high', path='.value')
>>> failure = Failure(error)
>>> detail = failure.error_detail()
>>> detail.code
'OUT_OF_RANGE'
>>> detail.path
'.value'
is_success()[source]

Check if the Maybe is a Success.

Return type:

bool

is_failure()[source]

Check if the Maybe is a Failure.

Return type:

bool

bind(_f)[source]

Chain operations that might fail.

Function is unused in Failure case as we always propagate the error.

Parameters:

_f (collections.abc.Callable[[T], Maybe[U]])

Return type:

Maybe[U]

and_then(_f)[source]

Chain operations that might fail. Python-friendly alias for bind().

Function is unused in Failure case as we always propagate the error.

Parameters:

_f (collections.abc.Callable[[T], Maybe[U]])

Return type:

Maybe[U]

async bind_async(_f)[source]

Async version of bind for composing async validators.

Function is unused in Failure case as we always propagate the error.

Parameters:

_f (collections.abc.Callable[[T], collections.abc.Awaitable[Maybe[U]]])

Return type:

Maybe[U]

map(_f)[source]

Transform the value if present.

Function is unused in Failure case as we always propagate the error.

Parameters:

_f (collections.abc.Callable[[T], U])

Return type:

Maybe[U]

value_or(default)[source]

Return the provided default for Failure.

Parameters:

default (T)

Return type:

T

error_or(default)[source]

Return the error message string (backward compatible).

Returns:

Error message from ValidationError, or default if message is empty

Parameters:

default (str)

Return type:

str

get_error()[source]

Get the error message string (backward compatible).

Returns:

Error message from ValidationError

Return type:

str | None

unwrap()[source]

Raise UnwrapError since Failure has no value.

Raises:

UnwrapError – Always, with the error message from this Failure

Return type:

T

expect(msg)[source]

Raise UnwrapError with custom message.

Parameters:

msg (str) – Custom error message to include in the exception

Raises:

UnwrapError – Always, with the custom message

Return type:

T

unwrap_err()[source]

Extract the error message.

For Failure, always returns the error message string.

Return type:

str

to_optional()[source]

Return None since Failure has no value.

For Failure, always returns None.

Return type:

T | None

__str__()[source]

Get a string representation.

Returns:

String showing error message (backward compatible format)

Return type:

str

__repr__()[source]

Get a repr representation for debugging and doctests.

Returns:

String showing error message (backward compatible format)

Return type:

str

class valid8r.Maybe[source]

Bases: abc.ABC, Generic[T]

Base class for the Maybe monad.

static success(value)[source]

Create a Success containing a value.

Parameters:

value (T)

Return type:

Success[T]

static failure(error)[source]

Create a Failure containing an error message or ValidationError.

Parameters:

error (str | valid8r.core.errors.ValidationError) – Error message string or ValidationError instance

Returns:

Failure instance with the error

Return type:

Failure[T]

abstractmethod is_success()[source]

Check if the Maybe is a Success.

Return type:

bool

abstractmethod is_failure()[source]

Check if the Maybe is a Failure.

Return type:

bool

abstractmethod bind(f)[source]

Chain operations that might fail.

Parameters:

f (collections.abc.Callable[[T], Maybe[U]])

Return type:

Maybe[U]

abstractmethod and_then(f)[source]

Chain operations that might fail. Python-friendly alias for bind().

This method is functionally identical to bind() but uses naming more familiar to Python developers. Use whichever name fits your style.

Parameters:

f (collections.abc.Callable[[T], Maybe[U]]) – Function that takes a value and returns Maybe[U]

Returns:

Result of applying f to the value if Success,

or propagated Failure if already failed

Return type:

Maybe[U]

Examples

Chain operations using and_then:

>>> result = Maybe.success(5).and_then(lambda x: Maybe.success(x * 2))
>>> result.value_or(0)
10

Propagates failure:

>>> result = Maybe.failure('error').and_then(lambda x: Maybe.success(x * 2))
>>> result.is_failure()
True
abstractmethod bind_async(f)[source]
Async:

Parameters:

f (collections.abc.Callable[[T], collections.abc.Awaitable[Maybe[U]]])

Return type:

Maybe[U]

Async version of bind for composing async validators.

This method enables chaining async operations that might fail, similar to bind() but for async functions.

Parameters:

f (collections.abc.Callable[[T], collections.abc.Awaitable[Maybe[U]]]) – Async function that takes a value and returns Maybe[U]

Returns:

Result of applying f to the value if Success,

or propagated Failure if already failed

Return type:

Maybe[U]

Examples

Async validation:

>>> import asyncio
>>> async def async_double(x: int) -> Maybe[int]:
...     await asyncio.sleep(0.001)
...     return Maybe.success(x * 2)
>>> result = asyncio.run(Maybe.success(21).bind_async(async_double))
>>> result.value_or(None)
42

Chaining async validators:

>>> async def async_validator(x: int) -> Maybe[int]:
...     await asyncio.sleep(0.001)
...     if x < 0:
...         return Maybe.failure('must be non-negative')
...     return Maybe.success(x)
>>> result = asyncio.run(Maybe.success(-5).bind_async(async_validator))
>>> result.is_failure()
True
abstractmethod map(f)[source]

Transform the value if present.

Parameters:

f (collections.abc.Callable[[T], U])

Return type:

Maybe[U]

abstractmethod value_or(default)[source]

Return the contained value or the provided default if this is a Failure.

Parameters:

default (T)

Return type:

T

abstractmethod error_or(default)[source]

Return the error message or the provided default if this is a Success.

Parameters:

default (str)

Return type:

str

abstractmethod get_error()[source]

Get the error message if present, otherwise None.

Return type:

str | None

abstractmethod unwrap()[source]

Extract value or raise UnwrapError if Failure.

This method provides type-safe value extraction when you’re confident the Maybe is a Success. Unlike value_or(), it doesn’t require a default value and the return type is T (not T | default_type).

Returns:

The contained value of type T

Raises:

UnwrapError – If called on a Failure

Return type:

T

Examples

Safe extraction after validation:

>>> result = Maybe.success(42)
>>> result.unwrap()
42

Raises on Failure:

>>> result = Maybe.failure('invalid input')
>>> result.unwrap()
Traceback (most recent call last):
    ...
UnwrapError: Called unwrap() on Failure: invalid input
abstractmethod expect(msg)[source]

Extract value or raise UnwrapError with custom message if Failure.

Similar to unwrap(), but allows providing a custom error message that is more meaningful in the context where the extraction happens.

Parameters:

msg (str) – Custom error message to use if this is a Failure

Returns:

The contained value of type T

Raises:

UnwrapError – If called on a Failure, with the custom message

Return type:

T

Examples

With meaningful context:

>>> result = Maybe.success(42)
>>> result.expect('User age is required')
42

Custom error message on failure:

>>> result = Maybe.failure('parse error')
>>> result.expect('Failed to load user profile')
Traceback (most recent call last):
    ...
UnwrapError: Failed to load user profile
abstractmethod unwrap_err()[source]

Extract error message or raise UnwrapError if Success.

This method provides type-safe error extraction when you’re confident the Maybe is a Failure. Useful in error handling code paths.

Returns:

The error message string

Raises:

UnwrapError – If called on a Success

Return type:

str

Examples

Extract error for logging:

>>> result = Maybe.failure('validation failed')
>>> result.unwrap_err()
'validation failed'

Raises on Success:

>>> result = Maybe.success(42)
>>> result.unwrap_err()
Traceback (most recent call last):
    ...
UnwrapError: Called unwrap_err() on Success
abstractmethod to_optional()[source]

Convert Maybe to an optional value.

Returns the contained value if Success, or None if Failure. This is useful for interoperability with code that uses Optional[T].

Returns:

The contained value if Success, None if Failure

Return type:

T | None

Examples

Convert Success to optional:

>>> result = Maybe.success(42)
>>> result.to_optional()
42

Convert Failure to optional:

>>> result = Maybe.failure('error')
>>> result.to_optional() is None
True
static from_optional(value, error_msg='Value was None')[source]

Convert an optional value to Maybe.

Returns Success(value) if value is not None, otherwise Failure with the provided error message. This is useful for interoperability with code that uses Optional[T].

Note: This method distinguishes only None from non-None values. Falsy values like 0, ‘’, [], and False are treated as valid values and wrapped in Success.

Parameters:
  • value (T | None) – The optional value to convert

  • error_msg (str) – Error message to use if value is None (default: ‘Value was None’)

Returns:

Success(value) if value is not None, Failure(error_msg) otherwise

Return type:

Maybe[T]

Examples

Convert non-None value:

>>> result = Maybe.from_optional(42)
>>> result.value_or(0)
42

Convert None value:

>>> result = Maybe.from_optional(None)
>>> result.is_failure()
True

Custom error message:

>>> result = Maybe.from_optional(None, error_msg='User ID is required')
>>> result.error_or('')
'User ID is required'

Falsy values are valid:

>>> Maybe.from_optional(0).value_or(-1)
0
>>> Maybe.from_optional('').value_or('default')
''
class valid8r.Success(value)[source]

Bases: Maybe[T]

Represents a successful computation with a value.

Parameters:

value (T)

__match_args__ = ('value',)
value
is_success()[source]

Check if the Maybe is a Success.

Return type:

bool

is_failure()[source]

Check if the Maybe is a Failure.

Return type:

bool

bind(f)[source]

Chain operations that might fail.

Parameters:

f (collections.abc.Callable[[T], Maybe[U]])

Return type:

Maybe[U]

and_then(f)[source]

Chain operations that might fail. Python-friendly alias for bind().

Parameters:

f (collections.abc.Callable[[T], Maybe[U]])

Return type:

Maybe[U]

async bind_async(f)[source]

Async version of bind for composing async validators.

Parameters:

f (collections.abc.Callable[[T], collections.abc.Awaitable[Maybe[U]]])

Return type:

Maybe[U]

map(f)[source]

Transform the value.

Parameters:

f (collections.abc.Callable[[T], U])

Return type:

Maybe[U]

value_or(_default)[source]

Return the contained value (default is ignored for Success).

Parameters:

_default (T)

Return type:

T

error_or(default)[source]

Return the provided default since Success has no error.

Parameters:

default (str)

Return type:

str

get_error()[source]

Get None since Success has no error.

Return type:

str | None

unwrap()[source]

Extract the contained value.

For Success, always returns the contained value.

Return type:

T

expect(_msg)[source]

Extract the contained value, ignoring the message.

For Success, always returns the contained value. The message parameter is ignored since extraction always succeeds.

Parameters:

_msg (str)

Return type:

T

unwrap_err()[source]

Raise UnwrapError since Success has no error.

Raises:

UnwrapError – Always, since Success doesn’t contain an error

Return type:

str

to_optional()[source]

Return the contained value.

For Success, always returns the contained value.

Return type:

T | None

__str__()[source]

Get a string representation.

Return type:

str

__repr__()[source]

Get a repr representation for debugging and doctests.

Return type:

str

class valid8r.EmailAddress[source]

Structured email address.

local

Local part (preserves original case).

domain

Domain part lowercased.

Examples

>>> from valid8r.core.maybe import Success
>>> match parse_email('First.Last+tag@Example.COM'):
...     case Success(addr):
...         (addr.local, addr.domain)
...     case _:
...         ()
('First.Last+tag', 'example.com')
local: str
domain: str
class valid8r.PhoneNumber[source]

Structured North American phone number (NANP).

Represents a parsed and validated phone number in the North American Numbering Plan (United States, Canada, and other NANP territories).

area_code

Three-digit area code (NPA).

exchange

Three-digit exchange code (NXX).

subscriber

Four-digit subscriber number.

country_code

Country code (always ‘1’ for NANP).

region

Two-letter region code (‘US’, ‘CA’, etc.).

extension

Optional extension number.

Examples

>>> from valid8r.core.maybe import Success
>>> match parse_phone('(415) 555-2671'):
...     case Success(phone):
...         (phone.area_code, phone.exchange, phone.subscriber)
...     case _:
...         ()
('415', '555', '2671')
area_code: str
exchange: str
subscriber: str
country_code: str
region: str
extension: str | None
property e164: str

E.164 international format (+14155552671).

The E.164 format is the international standard for phone numbers. It includes the country code prefix and no formatting separators.

Returns:

Phone number in E.164 format, with extension if present.

Return type:

str

property national: str

National format ((415) 555-2671).

The national format is the standard format for displaying phone numbers within a country, without the country code.

Returns:

Phone number in national format, with extension if present.

Return type:

str

property international: str

International format (+1 415-555-2671).

The international format includes the country code and uses dashes as separators.

Returns:

Phone number in international format, with extension if present.

Return type:

str

property raw_digits: str

Raw digits with country code (14155552671).

Returns all digits including the country code, with no formatting. Does not include the extension.

Returns:

All digits as a string without any formatting.

Return type:

str

class valid8r.UrlParts[source]

Structured URL components.

scheme

Lowercased scheme (e.g. “http”).

username

Username from userinfo, if present.

password

Password from userinfo, if present.

host

Lowercased host or IPv6 literal without brackets, or None when not provided and not required.

port

Explicit port if present, otherwise None.

path

Path component as-is (no normalization).

query

Query string without leading ‘?’.

fragment

Fragment without leading ‘#’.

Examples

>>> from valid8r.core.maybe import Success
>>> match parse_url('https://alice:pw@example.com:8443/x?q=1#top'):
...     case Success(u):
...         (u.scheme, u.username, u.password, u.host, u.port, u.path, u.query, u.fragment)
...     case _:
...         ()
('https', 'alice', 'pw', 'example.com', 8443, '/x', 'q=1', 'top')
scheme: str
username: str | None
password: str | None
host: str | None
port: int | None
path: str
query: str
fragment: str
class valid8r.Field[source]

Schema field definition with parser, validators, and required flag.

A Field represents a single field in a schema, specifying how to parse and validate the field value.

parser

Function that parses/validates the raw value, returns Maybe[T]

validators

Optional list of validation functions to apply after parsing

required

Whether the field must be present in the input

Examples

Required field with just a parser:

>>> from valid8r.core import parsers
>>> field = Field(parser=parsers.parse_int, required=True)
>>> field.required
True

Optional field:

>>> field = Field(parser=parsers.parse_str, required=False)
>>> field.required
False

Field with parser and validators:

>>> from valid8r.core import validators
>>> field = Field(
...     parser=parsers.parse_int,
...     validators=[validators.minimum(0), validators.maximum(100)],
...     required=True
... )
>>> len(field.validators)
2
parser: collections.abc.Callable[[Any], valid8r.core.maybe.Maybe[Any]]
required: bool
validators: list[collections.abc.Callable[[Any], valid8r.core.maybe.Maybe[Any]]] | None = None
class valid8r.Schema(*, fields, strict=False)[source]

Schema for validating dict-like objects with error accumulation.

A Schema defines the structure and validation rules for a dict-like object, accumulating all validation errors across all fields instead of stopping at the first failure.

Parameters:
fields

Dictionary mapping field names to Field definitions

strict

If True, reject inputs with fields not defined in the schema

Examples

Basic schema validation:

>>> from valid8r.core import parsers
>>> s = Schema(fields={
...     'age': Field(parser=parsers.parse_int, required=True),
...     'name': Field(parser=parsers.parse_str, required=True),
... })
>>> result = s.validate({'age': '25', 'name': 'Alice'})
>>> result.is_success()
True

Error accumulation:

>>> from valid8r.core.maybe import Failure
>>> result = s.validate({'age': 'bad', 'name': ''})
>>> result.is_failure()
True

Strict mode:

>>> strict_schema = Schema(
...     fields={'name': Field(parser=parsers.parse_str, required=True)},
...     strict=True
... )
>>> result = strict_schema.validate({'name': 'Alice', 'extra': 'field'})
>>> result.is_failure()
True
fields
strict = False
validate(data, path='')[source]

Validate data against the schema, accumulating all errors.

This method validates the input data against all field definitions, collecting all validation errors instead of stopping at the first failure. Field paths are tracked for nested validation (e.g., “.user.email”).

Parameters:
  • data (dict[str, Any] | Any) – Input data to validate (must be dict-like)

  • path (str) – Current field path for nested validation (internal use)

Returns:

Validated and parsed data if all fields pass Failure[list[ValidationError]]: List of all validation errors

Return type:

Success[dict]

Examples

Successful validation:

>>> from valid8r.core import parsers
>>> from valid8r.core.maybe import Success
>>> s = Schema(fields={
...     'age': Field(parser=parsers.parse_int, required=True),
... })
>>> result = s.validate({'age': '30'})
>>> match result:
...     case Success(data):
...         data['age']
...     case _:
...         None
30

Multiple errors:

>>> s = Schema(fields={
...     'age': Field(parser=parsers.parse_int, required=True),
...     'email': Field(parser=parsers.parse_email, required=True),
... })
>>> result = s.validate({'age': 'bad', 'email': 'bad'})
>>> result.is_failure()
True
async validate_async(data, path='', *, timeout=None)[source]

Validate data against the schema asynchronously with async validators.

This method validates input data supporting both sync and async validators. Sync validators are run first for fail-fast behavior, then async validators are run concurrently for better performance.

Parameters:
  • data (dict[str, Any] | Any) – Input data to validate (must be dict-like)

  • path (str) – Current field path for nested validation (internal use)

  • timeout (float | None) – Optional timeout in seconds for async operations

Returns:

Validated and parsed data if all fields pass Failure[list[ValidationError]]: List of all validation errors

Return type:

Success[dict]

Raises:

asyncio.TimeoutError – If validation exceeds the timeout

Examples

Basic async validation:

>>> import asyncio
>>> from valid8r.core import parsers, schema
>>> from valid8r.core.maybe import Maybe
>>> async def async_validator(val: str) -> Maybe[str]:
...     await asyncio.sleep(0.001)
...     return Maybe.success(val)
>>> s = schema.Schema(fields={
...     'field': schema.Field(
...         parser=parsers.parse_str,
...         validators=[async_validator],
...         required=True
...     ),
... })
>>> result = asyncio.run(s.validate_async({'field': 'value'}))
>>> result.is_success()
True

With timeout:

>>> async def slow_validator(val: str) -> Maybe[str]:
...     await asyncio.sleep(2.0)
...     return Maybe.success(val)
>>> s = schema.Schema(fields={
...     'field': schema.Field(
...         parser=parsers.parse_str,
...         validators=[slow_validator],
...         required=True
...     ),
... })
>>> try:
...     result = asyncio.run(s.validate_async({'field': 'value'}, timeout=0.1))
... except asyncio.TimeoutError:
...     print("Timed out")
Timed out
valid8r.from_type(annotation)[source]

Generate a parser from a Python type annotation.

This function uses match/case pattern matching to introspect type annotations and automatically generate appropriate parser functions. Supports basic types, generics, unions, literals, enums, and nested structures.

Parameters:

annotation (type[T] | Any) – A Python type annotation (int, str, Optional[int], list[str], etc.)

Returns:

A parser function that takes a string and returns Maybe[T]

Raises:
  • ValueError – If annotation is None or unsupported type

  • TypeError – If annotation is not a valid type

Return type:

collections.abc.Callable[[str], valid8r.core.maybe.Maybe[T]]

Supported Types:
  • Basic types: int, str, float, bool

  • Optional types: Optional[T] treats empty string as None

  • Collections: list[T], dict[K,V], set[T] (with element validation)

  • Union types: Union[int, str] tries alternatives in order

  • Literal types: Literal[‘red’, ‘green’, ‘blue’] restricts to specific values

  • Enum types: Python Enum classes with case-insensitive matching

  • Annotated types: Annotated[int, validators.minimum(0)] chains validators

  • Nested types: list[dict[str, int]], dict[str, list[int]], etc.

Examples

Basic type parsing:

>>> from valid8r.core.type_adapters import from_type
>>> parser = from_type(int)
>>> result = parser('42')
>>> result.value_or(None)
42

Optional type handling:

>>> from typing import Optional, Union
>>> parser = from_type(Optional[int])
>>> parser('').value_or('not none')  # Empty string becomes None
>>> parser('42').value_or(None)
42

Collection parsing with validation:

>>> parser = from_type(list[int])
>>> result = parser('[1, 2, 3]')
>>> result.value_or([])
[1, 2, 3]
>>> parser('[1, "invalid", 3]').is_failure()
True

Dictionary with typed keys and values:

>>> parser = from_type(dict[str, int])
>>> result = parser('{"age": 30, "count": 5}')
>>> result.value_or({})
{'age': 30, 'count': 5}

Union types try alternatives:

>>> parser = from_type(Union[int, float, str])
>>> parser('42').value_or(None)  # Parses as int
42
>>> parser('3.14').value_or(None)  # Parses as float
3.14
>>> parser('hello').value_or(None)  # Parses as str
'hello'

Literal types restrict values:

>>> from typing import Literal
>>> parser = from_type(Literal['red', 'green', 'blue'])
>>> parser('red').value_or(None)
'red'
>>> parser('yellow').is_failure()
True

Enum types with case-insensitive matching:

>>> from enum import Enum
>>> class Status(Enum):
...     ACTIVE = 'active'
...     INACTIVE = 'inactive'
>>> parser = from_type(Status)
>>> parser('ACTIVE').value_or(None)
<Status.ACTIVE: 'active'>
>>> parser('active').value_or(None)  # Case-insensitive
<Status.ACTIVE: 'active'>

Annotated types with validators:

>>> from typing import Annotated
>>> from valid8r import validators
>>> parser = from_type(Annotated[int, validators.minimum(0), validators.maximum(100)])
>>> parser('50').value_or(None)
50
>>> parser('150').is_failure()  # Exceeds maximum
True
>>> parser('-5').is_failure()  # Below minimum
True

Nested structures:

>>> parser = from_type(dict[str, list[int]])
>>> result = parser('{"scores": [95, 87, 92]}')
>>> result.value_or({})
{'scores': [95, 87, 92]}

Notes

  • Collection parsers expect JSON format: ‘[1, 2, 3]’ for lists, ‘{“key”: “value”}’ for dicts

  • Nested structures are fully validated at each level

  • Union types return the first successful parse (order matters)

  • Enum matching is case-insensitive by default

  • Annotated validators are chained using bind() for composition