Python data validation library for validating nested data structures with comprehensive error reporting
—
Logical composition of multiple validators using boolean-like operations, union types with discriminant functions, and flexible validation count requirements. These composable validators enable complex validation logic through simple combinations.
Value must pass at least one of the provided validators. Uses the first validator that succeeds.
class Any:
def __init__(self, *validators, msg=None, required=False, discriminant=None):
"""
Value must pass at least one validator.
Parameters:
- validators: Validator functions, types, or values to try in order
- msg: Custom error message if all validators fail
- required: Whether this validator is required
- discriminant: Function to filter which validators to try based on input value
Returns:
Result from first successful validator
Raises:
AnyInvalid: If no validators pass
"""
# Alias for Any
Or = AnyUsage Examples:
from voluptuous import Schema, Any, Coerce
# Accept multiple types
flexible_schema = Schema({
'id': Any(int, str), # Can be integer or string
'active': Any(bool, Coerce(bool)), # Boolean or coercible to boolean
})
# Multiple validation options
email_or_phone = Any(Email(), Match(r'^\+?[\d\s-()]+$'))
# With custom message
age_schema = Schema(Any(
int,
Coerce(int),
msg="Age must be an integer or convertible to integer"
))Value must pass all validators in sequence. Output of each validator feeds to the next.
class All:
def __init__(self, *validators, msg=None, required=False, discriminant=None):
"""
Value must pass all validators in sequence.
Parameters:
- validators: Validator functions, types, or values to apply in order
- msg: Custom error message if any validator fails
- required: Whether this validator is required
- discriminant: Function to filter which validators to apply
Returns:
Result from final validator in chain
Raises:
AllInvalid: If any validator fails
"""
# Alias for All
And = AllUsage Examples:
from voluptuous import Schema, All, Range, Length, Coerce
# Chain multiple validations
username_schema = Schema(All(
str, # Must be string
Length(min=3, max=20), # Must be 3-20 characters
Match(r'^[a-zA-Z0-9_]+$'), # Must be alphanumeric + underscore
))
# Type coercion then validation
price_schema = Schema(All(
Coerce(float), # Convert to float
Range(min=0.01), # Must be positive
))
# Complex data transformation
normalized_email = Schema(All(
str,
Strip, # Remove whitespace
Lower, # Convert to lowercase
Email(), # Validate email format
))Like Any but with a discriminant function to select which validators to try based on the input value.
class Union:
def __init__(self, *validators, msg=None, discriminant=None):
"""
Select validators to try based on discriminant function.
Parameters:
- validators: Validator functions, types, or values
- msg: Custom error message
- discriminant: Function that takes input value and returns which validators to try
Returns:
Result from successful validator
Raises:
AnyInvalid: If no selected validators pass
"""
# Alias for Union
Switch = UnionUsage Examples:
from voluptuous import Schema, Union, Required
def api_discriminant(value):
"""Select validators based on API version."""
if isinstance(value, dict) and value.get('version') == 'v1':
return [v1_validator]
elif isinstance(value, dict) and value.get('version') == 'v2':
return [v2_validator]
return [v1_validator, v2_validator] # Try both
v1_validator = Schema({Required('name'): str})
v2_validator = Schema({Required('full_name'): str, Required('email'): str})
api_schema = Schema(Union(
v1_validator,
v2_validator,
discriminant=api_discriminant
))
# Uses v1 validator
api_schema({'version': 'v1', 'name': 'John'})
# Uses v2 validator
api_schema({'version': 'v2', 'full_name': 'John Doe', 'email': 'john@example.com'})Value must pass between a minimum and maximum number of validators from the provided set.
class SomeOf:
def __init__(self, validators, min_valid=None, max_valid=None):
"""
Value must pass between min and max validators.
Parameters:
- validators: List of validators to try
- min_valid: Minimum number of validators that must pass (default: 1)
- max_valid: Maximum number of validators that can pass (default: unlimited)
Returns:
Original input value if validation count requirements are met
Raises:
NotEnoughValid: If fewer than min_valid validators pass
TooManyValid: If more than max_valid validators pass
"""Usage Examples:
from voluptuous import Schema, SomeOf, Length, Match
# Password must satisfy at least 2 of 3 complexity rules
password_complexity = SomeOf([
Length(min=8), # At least 8 characters
Match(r'[A-Z]'), # Contains uppercase
Match(r'[0-9]'), # Contains numbers
], min_valid=2)
password_schema = Schema(password_complexity)
# Passes: has length and uppercase
password_schema('MyPassword')
# Passes: has length and numbers
password_schema('password123')
# Fails: only satisfies length requirement
# password_schema('password') # Raises NotEnoughValid
# Exactly one authentication method
auth_method = SomeOf([
Match(r'^user:'), # Username-based
Match(r'^token:'), # Token-based
Match(r'^key:'), # API key-based
], min_valid=1, max_valid=1)
# Security policy: exactly 2 of 3 factors
two_factor = SomeOf([
lambda x: 'password' in x, # Something you know
lambda x: 'device_id' in x, # Something you have
lambda x: 'biometric' in x, # Something you are
], min_valid=2, max_valid=2)Convenience validator that allows None or validates with the given validator. Equivalent to Any(None, validator).
def Maybe(validator, msg=None):
"""
Allow None or validate with given validator.
Parameters:
- validator: Validator to apply if value is not None
- msg: Custom error message
Returns:
None if input is None, otherwise result of validator
"""Usage Examples:
from voluptuous import Schema, Maybe, Email, Required
# Optional email field
user_schema = Schema({
Required('name'): str,
Required('email'): Maybe(Email()), # None or valid email
Required('website'): Maybe(Url()), # None or valid URL
})
# All valid:
user_schema({'name': 'John', 'email': None, 'website': None})
user_schema({'name': 'John', 'email': 'john@example.com', 'website': None})
user_schema({'name': 'John', 'email': None, 'website': 'https://example.com'})Common patterns for combining validators effectively.
Sequential Processing:
from voluptuous import Schema, All, Strip, Lower, Length, Match
# Clean and validate user input
clean_username = All(
str, # Ensure string
Strip, # Remove whitespace
Lower, # Convert to lowercase
Length(min=3, max=20), # Validate length
Match(r'^[a-z][a-z0-9_]*$'), # Validate format
)Flexible Type Handling:
from voluptuous import Schema, Any, All, Coerce, Range
# Accept various numeric representations
flexible_number = Any(
int, # Already an integer
All(str, Coerce(int)), # String that converts to int
All(float, lambda x: int(x) if x.is_integer() else None), # Whole number float
)
# Flexible price validation
price_validator = Any(
All(int, Range(min=0)), # Integer price (cents)
All(float, Range(min=0.0)), # Float price (dollars)
All(str, Coerce(float), Range(min=0.0)), # String price convertible to float
)Conditional Validation:
from voluptuous import Schema, Any, Required, Optional
def conditional_schema(data):
"""Different validation based on user type."""
if data.get('user_type') == 'admin':
return Schema({
Required('username'): str,
Required('permissions'): [str],
Optional('department'): str,
})
else:
return Schema({
Required('username'): str,
Required('email'): Email(),
})
# Usage in a validator
user_schema = Schema(lambda data: conditional_schema(data)(data))Error Aggregation:
from voluptuous import Schema, All, MultipleInvalid
def validate_all_fields(data):
"""Validate multiple fields and collect all errors."""
errors = []
field_schemas = {
'name': All(str, Length(min=1)),
'email': Email(),
'age': All(int, Range(min=0, max=150)),
}
validated = {}
for field, schema in field_schemas.items():
try:
if field in data:
validated[field] = schema(data[field])
except Invalid as e:
e.prepend([field])
errors.append(e)
if errors:
raise MultipleInvalid(errors)
return validatedInstall with Tessl CLI
npx tessl i tessl/pypi-voluptuous