CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-voluptuous

Python data validation library for validating nested data structures with comprehensive error reporting

Pending
Overview
Eval results
Files

validation-composers.mddocs/

Validation Composers

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.

Capabilities

Any (Or) Validator

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 = Any

Usage 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"
))

All (And) Validator

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 = All

Usage 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
))

Union (Switch) Validator

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 = Union

Usage 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'})

SomeOf Validator

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)

Maybe Validator

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'})

Composition Patterns

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 validated

Install with Tessl CLI

npx tessl i tessl/pypi-voluptuous

docs

core-schema.md

error-handling.md

index.md

range-collection-validators.md

string-pattern-validators.md

type-validators.md

utility-transformers.md

validation-composers.md

tile.json