CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cattrs

Composable complex class support for attrs and dataclasses with (un)structuring and validation.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

error-handling.mddocs/

Error Handling and Validation

Comprehensive error handling system with detailed validation errors, structured exception hierarchies, and error transformation utilities. Cattrs provides rich error information to help debug data structuring issues and provide meaningful feedback to users.

Capabilities

Exception Hierarchy

Structured exception classes that inherit from Python's ExceptionGroup for detailed error reporting.

from cattrs.errors import (
    BaseValidationError,
    ClassValidationError,
    IterableValidationError,
    AttributeValidationNote,
    IterableValidationNote,
    ForbiddenExtraKeysError,
    StructureHandlerNotFoundError
)

class BaseValidationError(ExceptionGroup):
    """
    Base class for validation errors (ExceptionGroup subclass).
    
    Provides structured error information with nested exception details
    for complex data validation scenarios.
    """
    def __init__(self, message, exceptions, cl=None):
        """
        Initialize validation error.
        
        Parameters:
        - message: Human-readable error message
        - exceptions: List of nested exceptions
        - cl: The class being validated (optional)
        """

class ClassValidationError(BaseValidationError):
    """
    Raised when validating a class with invalid attributes.
    
    Contains detailed information about which attributes failed validation
    and why, including nested errors for complex data structures.
    """
    def __init__(self, message, exceptions, cl):
        """
        Initialize class validation error.
        
        Parameters:
        - message: Error message describing the validation failure
        - exceptions: List of attribute-specific exceptions
        - cl: The class that failed validation
        """

class IterableValidationError(BaseValidationError):
    """
    Raised when structuring an iterable fails.
    
    Provides information about which elements in the iterable failed
    to structure and the specific errors for each element.
    """
    def __init__(self, message, exceptions, cl):
        """Initialize iterable validation error."""

class AttributeValidationNote:
    """
    Note attached to exceptions when attribute structuring fails.
    
    Provides context about which attribute caused the error and
    additional metadata for debugging.
    """
    def __init__(self, name, cl, converter):
        """
        Initialize attribute validation note.
        
        Parameters:
        - name: Name of the attribute that failed
        - cl: Class containing the attribute
        - converter: Converter instance used
        """
        self.name = name
        self.cl = cl
        self.converter = converter

class IterableValidationNote:
    """
    Note attached to exceptions when iterable element structuring fails.
    
    Provides context about which element index caused the error.
    """
    def __init__(self, index, cl, converter):
        """
        Initialize iterable validation note.
        
        Parameters:
        - index: Index of the element that failed
        - cl: Target type for the iterable elements
        - converter: Converter instance used
        """
        self.index = index
        self.cl = cl
        self.converter = converter

class ForbiddenExtraKeysError(Exception):
    """
    Raised when extra keys are found and forbidden.
    
    Occurs when a converter is configured with forbid_extra_keys=True
    and the input data contains keys not present in the target class.
    """
    def __init__(self, message, cl, extra_keys):
        """
        Initialize forbidden extra keys error.
        
        Parameters:
        - message: Error message
        - cl: Target class
        - extra_keys: Set of forbidden keys found
        """
        self.cl = cl
        self.extra_keys = extra_keys
        super().__init__(message)

class StructureHandlerNotFoundError(Exception):
    """
    Raised when no structure handler found for a type.
    
    Occurs when the converter doesn't know how to structure a particular
    type and no appropriate hook has been registered.
    """
    def __init__(self, message, type_):
        """
        Initialize structure handler not found error.
        
        Parameters:
        - message: Error message
        - type_: The type that couldn't be handled
        """
        self.type = type_
        super().__init__(message)

Error Transformation

Utilities for transforming validation errors into user-friendly formats.

from cattrs.v import transform_error, format_exception

def transform_error(
    exc,
    path=None,
    **kwargs
):
    """
    Transform validation errors into detailed error message lists.
    
    Converts complex nested validation errors into a flat list of
    human-readable error messages with path information.
    
    Parameters:
    - exc: The exception to transform
    - path: Current path context for nested errors
    - **kwargs: Additional transformation options
    
    Returns:
    List of transformed error messages with path information
    """

def format_exception(exc, path=None):
    """
    Format exceptions into readable error messages.
    
    Provides a human-readable representation of validation errors
    with context about where in the data structure the error occurred.
    
    Parameters:
    - exc: Exception to format
    - path: Path context for the error
    
    Returns:
    Formatted error message string
    """

Usage Examples

Basic Error Handling

from cattrs import structure, ClassValidationError
from attrs import define

@define
class User:
    name: str
    age: int
    email: str

try:
    # Invalid data - age should be int, not string
    invalid_data = {"name": "Alice", "age": "not-a-number", "email": "alice@example.com"}
    user = structure(invalid_data, User)
except ClassValidationError as e:
    print(f"Validation failed for {e.cl.__name__}")
    for exc in e.exceptions:
        print(f"  - {exc}")
    # Output shows detailed information about the age field validation failure

Nested Structure Error Handling

from cattrs import structure, ClassValidationError
from attrs import define
from typing import List

@define
class Address:
    street: str
    zip_code: int

@define
class Person:
    name: str
    addresses: List[Address]

try:
    # Invalid nested data
    invalid_data = {
        "name": "Bob",
        "addresses": [
            {"street": "123 Main St", "zip_code": 12345},
            {"street": "456 Oak Ave", "zip_code": "invalid"}  # Invalid zip_code
        ]
    }
    person = structure(invalid_data, Person)
except ClassValidationError as e:
    print(f"Person validation failed:")
    for exc in e.exceptions:
        if hasattr(exc, 'exceptions'):  # Nested validation error
            print(f"  Address list error:")
            for nested_exc in exc.exceptions:
                print(f"    - {nested_exc}")
        else:
            print(f"  - {exc}")

Error Transformation for User Feedback

from cattrs import structure, ClassValidationError
from cattrs.v import transform_error

@define
class Config:
    host: str
    port: int
    ssl_enabled: bool

def validate_config(config_data):
    try:
        return structure(config_data, Config)
    except ClassValidationError as e:
        # Transform errors into user-friendly messages
        error_messages = transform_error(e)
        
        print("Configuration validation failed:")
        for error_msg in error_messages:
            print(f"  - {error_msg}")
        
        return None

# Usage
invalid_config = {
    "host": "localhost",
    "port": "not-a-number",  # Invalid
    "ssl_enabled": "yes"     # Invalid - should be boolean
}

config = validate_config(invalid_config)
# Outputs detailed, user-friendly error messages

Handling Extra Keys

from cattrs import Converter, ForbiddenExtraKeysError
from attrs import define

@define
class Settings:
    debug: bool
    log_level: str

# Configure converter to forbid extra keys
converter = Converter(forbid_extra_keys=True)

try:
    # Data with extra keys
    data_with_extra = {
        "debug": True,
        "log_level": "INFO",
        "unknown_setting": "value"  # This will cause an error
    }
    settings = converter.structure(data_with_extra, Settings)
except ForbiddenExtraKeysError as e:
    print(f"Extra keys not allowed in {e.cl.__name__}: {e.extra_keys}")
    # Output: Extra keys not allowed in Settings: {'unknown_setting'}

Custom Error Handling with Hooks

from cattrs import Converter, register_structure_hook
from datetime import datetime

def safe_datetime_structure(value, _):
    if isinstance(value, str):
        try:
            return datetime.fromisoformat(value)
        except ValueError as e:
            raise ValueError(f"Invalid datetime format: {value}. Expected ISO format.") from e
    elif isinstance(value, (int, float)):
        try:
            return datetime.fromtimestamp(value)
        except (ValueError, OSError) as e:
            raise ValueError(f"Invalid timestamp: {value}") from e
    else:
        raise TypeError(f"Cannot convert {type(value)} to datetime")

converter = Converter()
converter.register_structure_hook(datetime, safe_datetime_structure)

@define
class Event:
    name: str
    timestamp: datetime

try:
    # This will provide a clear error message
    invalid_event = {"name": "Meeting", "timestamp": "not-a-datetime"}
    event = converter.structure(invalid_event, Event)
except ClassValidationError as e:
    print("Event validation failed:")
    for exc in e.exceptions:
        print(f"  - {exc}")
    # Output includes custom error message about datetime format

Comprehensive Error Reporting

from cattrs import structure, ClassValidationError
from cattrs.v import transform_error
from attrs import define
from typing import List, Optional

@define
class Contact:
    email: str
    phone: Optional[str] = None

@define
class Company:
    name: str
    employees: List[Contact]
    founded_year: int

def validate_company_data(data):
    try:
        return structure(data, Company)
    except ClassValidationError as e:
        print(f"\n=== Validation Report for {e.cl.__name__} ===")
        
        # Use transform_error for detailed path information
        errors = transform_error(e)
        
        for i, error in enumerate(errors, 1):
            print(f"{i}. {error}")
        
        print(f"\nTotal errors: {len(errors)}")
        return None

# Test with complex invalid data
complex_invalid_data = {
    "name": "",  # Invalid - empty string
    "employees": [
        {"email": "valid@example.com"},  # Valid
        {"email": "invalid-email", "phone": "123-456-7890"},  # Invalid email
        {"email": "", "phone": None}  # Invalid - empty email
    ],
    "founded_year": "not-a-year"  # Invalid - should be int
}

company = validate_company_data(complex_invalid_data)
# Provides comprehensive error report with paths and specific issues

Integration with Detailed Validation

Cattrs converters can be configured for detailed validation to provide enhanced error information:

from cattrs import Converter

# Enable detailed validation (default in newer versions)
converter = Converter(detailed_validation=True)

# This provides more context in error messages and better path tracking
# for nested data structures

Types

from typing import List, Any, Type, Set, Optional

# Error transformation result type
ErrorMessage = str
ErrorPath = str
TransformedErrors = List[ErrorMessage]

# Exception context types
ExceptionContext = Any
ValidationPath = List[str]

Install with Tessl CLI

npx tessl i tessl/pypi-cattrs

docs

code-generation.md

core-api.md

error-handling.md

index.md

preconf-converters.md

strategies.md

tile.json