Composable complex class support for attrs and dataclasses with (un)structuring and validation.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
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)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
"""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 failurefrom 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}")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 messagesfrom 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'}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 formatfrom 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 issuesCattrs 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 structuresfrom 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