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

error-handling.mddocs/

Error Handling

Comprehensive exception hierarchy for validation errors with path tracking, multiple error aggregation, and detailed error reporting. Voluptuous provides structured error handling for complex validation scenarios.

Capabilities

Base Exception Classes

Foundation exception classes for all validation errors.

class Error(Exception):
    """
    Base validation exception class.
    
    All voluptuous exceptions inherit from this class, making it easy
    to catch any validation-related error.
    """

class SchemaError(Error):
    """
    Schema definition errors.
    
    Raised when there are problems with the schema definition itself,
    not with the data being validated.
    
    Examples:
    - Invalid schema structure
    - Conflicting marker requirements
    - Malformed validation rules
    """

Primary Validation Exceptions

Core exception classes for validation failures.

class Invalid(Error):
    def __init__(self, message, path=None, error_message=None, error_type=None):
        """
        Base class for validation errors.
        
        Parameters:
        - message: Error message to display
        - path: List of keys representing path to error location
        - error_message: Original error message (before path formatting)
        - error_type: Type of error for categorization
        
        Properties:
        - msg: The error message
        - path: Path to error location as list of keys
        - error_message: Original error message
        """
    
    @property
    def msg(self):
        """Get the error message."""
    
    @property
    def path(self):
        """Get the path to error location as list of keys."""
    
    @property
    def error_message(self):
        """Get the original error message."""
    
    def prepend(self, path):
        """
        Add path prefix to error location.
        
        Parameters:
        - path: List of keys to prepend to current path
        """

class MultipleInvalid(Invalid):
    def __init__(self, errors=None):
        """
        Container for multiple validation errors.
        
        Parameters:
        - errors: List of Invalid exceptions
        
        This exception aggregates multiple validation errors
        and provides access to individual errors while presenting
        as a single exception.
        """
    
    def add(self, error):
        """
        Add another error to the collection.
        
        Parameters:
        - error: Invalid exception to add
        """

Usage Examples:

from voluptuous import Schema, Invalid, MultipleInvalid, Required, All, Length, Range

# Basic error handling
user_schema = Schema({
    Required('name'): All(str, Length(min=1)),
    Required('age'): All(int, Range(min=0, max=150)),
})

try:
    result = user_schema({'name': '', 'age': -5})
except MultipleInvalid as e:
    print("Multiple validation errors:")
    for error in e.errors:
        print(f"  {error.path}: {error.msg}")
except Invalid as e:
    print(f"Validation error at {e.path}: {e.msg}")

# Error path tracking
nested_schema = Schema({
    'users': [{
        Required('name'): str,
        Required('email'): Email(),
    }]
})

try:
    nested_schema({
        'users': [
            {'name': 'John', 'email': 'invalid-email'},
            {'name': '', 'email': 'jane@example.com'},
        ]
    })
except MultipleInvalid as e:
    for error in e.errors:
        print(f"Error at {error.path}: {error.msg}")
        # Output might be:
        # Error at ['users', 0, 'email']: not a valid email address
        # Error at ['users', 1, 'name']: length of value must be at least 1

Specific Exception Types

Detailed exception classes for specific validation failure types.

Schema Structure Errors:

class RequiredFieldInvalid(Invalid):
    """Required field was missing from input data."""

class ObjectInvalid(Invalid):
    """Expected an object but received a different type."""

class DictInvalid(Invalid):
    """Expected a dictionary but received a different type."""

class ExclusiveInvalid(Invalid):
    """Multiple values found in exclusion group (only one allowed)."""

class InclusiveInvalid(Invalid):
    """Not all values found in inclusion group (all or none required)."""

class SequenceTypeInvalid(Invalid):
    """Expected a sequence type but received something else."""

Type and Value Errors:

class TypeInvalid(Invalid):
    """Value was not of the required type."""

class ValueInvalid(Invalid):
    """Value was found invalid by evaluation function."""

class ScalarInvalid(Invalid):
    """Scalar values did not match expected values."""

class CoerceInvalid(Invalid):
    """Impossible to coerce value to target type."""

class LiteralInvalid(Invalid):
    """Literal value did not match expected literal."""

Validation Logic Errors:

class AnyInvalid(Invalid):
    """Value did not pass any validator in Any() composition."""

class AllInvalid(Invalid):
    """Value did not pass all validators in All() composition."""

class NotEnoughValid(Invalid):
    """Value did not pass enough validations in SomeOf()."""

class TooManyValid(Invalid):
    """Value passed more than expected validations in SomeOf()."""

Pattern and Format Errors:

class MatchInvalid(Invalid):
    """Value does not match the given regular expression."""

class RangeInvalid(Invalid):
    """Value is not in the specified range."""

class LengthInvalid(Invalid):
    """Length of value is not within specified bounds."""

class DatetimeInvalid(Invalid):
    """Value is not a valid datetime string."""

class DateInvalid(Invalid):
    """Value is not a valid date string."""

Boolean Validation Errors:

class TrueInvalid(Invalid):
    """Value is not True when True was required."""

class FalseInvalid(Invalid):
    """Value is not False when False was required."""

class BooleanInvalid(Invalid):
    """Value is not a valid boolean representation."""

Network and File System Errors:

class UrlInvalid(Invalid):
    """Value is not a valid URL format."""

class EmailInvalid(Invalid):
    """Value is not a valid email address."""

class FileInvalid(Invalid):
    """Value is not a valid file path or file does not exist."""

class DirInvalid(Invalid):
    """Value is not a valid directory path or directory does not exist."""

class PathInvalid(Invalid):
    """Value is not a valid path or path does not exist."""

Collection Errors:

class ContainsInvalid(Invalid):
    """Required item not found in sequence."""

class InInvalid(Invalid):
    """Value not found in allowed container."""

class NotInInvalid(Invalid):
    """Value found in forbidden container."""

class ExactSequenceInvalid(Invalid):
    """Sequence does not match expected exact structure."""

Error Handling Patterns

Common patterns for handling validation errors effectively.

Granular Error Handling:

from voluptuous import Schema, Invalid, MultipleInvalid, Required, Email, Range

def validate_user_data(data):
    """Validate user data with specific error handling."""
    schema = Schema({
        Required('name'): str,
        Required('email'): Email(),
        Required('age'): Range(min=18, max=120),
    })
    
    try:
        return schema(data)
    except EmailInvalid:
        raise ValueError("Please provide a valid email address")
    except RangeInvalid as e:
        if 'age' in str(e.path):
            raise ValueError("Age must be between 18 and 120")
        raise
    except RequiredFieldInvalid as e:
        field_name = e.path[-1] if e.path else 'unknown field'
        raise ValueError(f"Required field '{field_name}' is missing")
    except Invalid as e:
        raise ValueError(f"Validation error: {e}")

Error Collection and Reporting:

from voluptuous import Schema, MultipleInvalid, Invalid

def collect_all_errors(schema, data):
    """Collect all validation errors for comprehensive reporting."""
    try:
        return schema(data), []
    except MultipleInvalid as e:
        errors = []
        for error in e.errors:
            errors.append({
                'path': error.path,
                'message': error.msg,
                'type': type(error).__name__,
            })
        return None, errors
    except Invalid as e:
        return None, [{
            'path': e.path,
            'message': e.msg,
            'type': type(e).__name__,
        }]

# Usage
schema = Schema({
    Required('users'): [{
        Required('name'): All(str, Length(min=1)),
        Required('email'): Email(),
    }]
})

data = {
    'users': [
        {'name': '', 'email': 'invalid'},
        {'name': 'John'},  # Missing email
    ]
}

result, errors = collect_all_errors(schema, data)
if errors:
    for error in errors:
        print(f"Error at {'.'.join(map(str, error['path']))}: {error['message']}")

Custom Error Messages:

from voluptuous import Schema, message, All, Length, Range, Email

# Custom error messages with decorators
@message("Username must be 3-20 characters long")
def username_length(value):
    return Length(min=3, max=20)(value)

@message("Age must be between 13 and 120", ValueError)
def valid_age(value):
    return Range(min=13, max=120)(value)

# Schema with custom messages
user_schema = Schema({
    'username': All(str, username_length),
    'age': All(int, valid_age),
    'email': message(Email(), "Please enter a valid email address"),
})

try:
    user_schema({'username': 'ab', 'age': 5, 'email': 'invalid'})
except MultipleInvalid as e:
    for error in e.errors:
        print(error.msg)  # Uses custom messages

Error Recovery and Partial Validation:

from voluptuous import Schema, Invalid, Required, Optional

def partial_validate(schema, data):
    """Validate data and return partial results with errors."""
    validated = {}
    errors = {}
    
    if isinstance(schema.schema, dict):
        for key, validator in schema.schema.items():
            field_name = key.schema if hasattr(key, 'schema') else key
            
            try:
                if field_name in data:
                    validated[field_name] = validator(data[field_name])
                elif isinstance(key, Required):
                    if hasattr(key, 'default') and key.default is not UNDEFINED:
                        validated[field_name] = key.default() if callable(key.default) else key.default
                    else:
                        errors[field_name] = "Required field missing"
            except Invalid as e:
                errors[field_name] = str(e)
    
    return validated, errors

# Usage for form validation with partial success
form_schema = Schema({
    Required('name'): All(str, Length(min=1)),
    Required('email'): Email(),
    Optional('age'): All(int, Range(min=0, max=150)),
})

form_data = {
    'name': 'John Doe',      # Valid
    'email': 'invalid-email', # Invalid
    'age': 25,               # Valid
}

validated, errors = partial_validate(form_schema, form_data)
print("Validated fields:", validated)  # {'name': 'John Doe', 'age': 25}
print("Error fields:", errors)         # {'email': 'not a valid email address'}

Contextual Error Enhancement:

from voluptuous import Schema, Invalid, MultipleInvalid

def enhance_errors(errors, context=None):
    """Add contextual information to validation errors."""
    enhanced = []
    
    for error in (errors.errors if isinstance(errors, MultipleInvalid) else [errors]):
        enhanced_msg = error.msg
        
        # Add field-specific context
        if error.path:
            field_name = error.path[-1]
            if field_name == 'email':
                enhanced_msg += " (example: user@example.com)"
            elif field_name == 'phone':
                enhanced_msg += " (example: +1-555-123-4567)"
            elif field_name == 'age':
                enhanced_msg += " (must be a number between 0 and 150)"
        
        # Add context information
        if context:
            enhanced_msg += f" [Context: {context}]"
        
        enhanced.append({
            'path': error.path,
            'message': enhanced_msg,
            'original': error.msg,
            'type': type(error).__name__,
        })
    
    return enhanced

# Usage
try:
    schema({'email': 'invalid', 'age': 'not-a-number'})
except (Invalid, MultipleInvalid) as e:
    enhanced = enhance_errors(e, context="User registration form")
    for error in enhanced:
        print(f"{error['path']}: {error['message']}")

Humanized Error Messages

Voluptuous includes utilities for creating more user-friendly error messages in the voluptuous.humanize module.

from voluptuous.humanize import humanize_error, validate_with_humanized_errors

def humanize_error(data, validation_error, max_sub_error_length=500):
    """
    Provide more helpful validation error messages.
    
    Parameters:
    - data: Original data that failed validation
    - validation_error: Invalid or MultipleInvalid exception
    - max_sub_error_length: Maximum length for displaying offending values
    
    Returns:
    Human-readable error message string including offending values
    """

def validate_with_humanized_errors(data, schema, max_sub_error_length=500):
    """
    Validate data and raise Error with humanized message on failure.
    
    Parameters:
    - data: Data to validate
    - schema: Schema to validate against
    - max_sub_error_length: Maximum length for error values
    
    Returns:
    Validated data if successful
    
    Raises:
    Error: With humanized error message if validation fails
    """

Usage Examples:

from voluptuous import Schema, Required, Email, Range, All, Length, Error
from voluptuous.humanize import humanize_error, validate_with_humanized_errors

schema = Schema({
    Required('users'): [{
        Required('name'): All(str, Length(min=1)),
        Required('email'): Email(),
        Required('age'): Range(min=0, max=150),
    }]
})

data = {
    'users': [
        {'name': '', 'email': 'not-an-email', 'age': 200}
    ]
}

try:
    result = validate_with_humanized_errors(data, schema)
except Error as e:
    print(e)
    # Output includes the actual offending values:
    # "not a valid email address @ data['users'][0]['email']. Got 'not-an-email'"
    # "value must be at most 150 @ data['users'][0]['age']. Got 200"

# Or manually humanize errors
try:
    schema(data)
except (Invalid, MultipleInvalid) as e:
    friendly_message = humanize_error(data, e)
    print(friendly_message)

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