valid8r
Valid8r: A clean, flexible input validation library for Python.
Submodules
Attributes
Classes
Standard validation error codes for programmatic error handling. |
|
Structured validation error with code, message, path, and context. |
|
Represents a failed computation with an error message or ValidationError. |
|
Base class for the Maybe monad. |
|
Represents a successful computation with a value. |
|
Structured email address. |
|
Structured North American phone number (NANP). |
|
Structured URL components. |
|
Schema field definition with parser, validators, and required flag. |
|
Schema for validating dict-like objects with error accumulation. |
Functions
|
Generate a parser from a Python type annotation. |
Package Contents
- 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}}
- __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_errorproperty instead.- Returns:
Error message string
- Return type:
- 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:
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_errorproperty, but follows the RFC-001 specification for the public API.For backward compatibility, both
error_detail()andvalidation_errorproperty are maintained.- Returns:
ValidationError instance with code, message, path, and context
- Return type:
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'
- 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
- 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:
- to_optional()[source]
Return None since Failure has no value.
For Failure, always returns None.
- Return type:
T | None
- 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 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.
- 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:
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
- 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
- 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:
- 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')
- 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')
- 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:
- 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:
- 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')
- 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]]
- 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.
- 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:
- Returns:
Validated and parsed data if all fields pass Failure[list[ValidationError]]: List of all validation errors
- Return type:
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:
- Returns:
Validated and parsed data if all fields pass Failure[list[ValidationError]]: List of all validation errors
- Return type:
- 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