Extensible conversion system for custom serialization and deserialization logic. Register converters for any types including third-party libraries, ORM models, and custom transformations.
Register custom deserialization converters that transform data during the deserialization process.
def deserializer(
deserializer: Optional[Deserializer] = None,
*,
lazy: Optional[Callable[[], Union[Converter, Conversion]]] = None,
target: Optional[Type] = None,
) -> Callable:
"""
Register a deserialization converter.
Parameters:
- deserializer: Function that converts data to target type
- lazy: Lazy-loaded converter function (for avoiding import cycles)
- target: Target type for the converter (inferred if not provided)
Returns:
Decorator function for registration
"""Register custom serialization converters that transform objects during the serialization process.
def serializer(
serializer: Optional[Serializer] = None,
*,
lazy: Optional[Callable[[], Union[Converter, Conversion]]] = None,
source: Optional[Type] = None,
) -> Callable:
"""
Register a serialization converter.
Parameters:
- serializer: Function that converts object to serializable data
- lazy: Lazy-loaded converter function (for avoiding import cycles)
- source: Source type for the converter (inferred if not provided)
Returns:
Decorator function for registration
"""Classes for configuring conversion behavior with detailed options and lazy loading.
@dataclass(frozen=True)
class Conversion:
"""
Conversion configuration with converter function and type information.
Attributes:
- converter: Function that performs the conversion
- source: Source type (for serialization converters)
- target: Target type (for deserialization converters)
- inherited: Whether conversion applies to subclasses
"""
converter: Callable
source: Optional[AnyType] = None
target: Optional[AnyType] = None
inherited: Optional[bool] = None
@dataclass(frozen=True)
class LazyConversion:
"""
Lazy-loaded conversion configuration.
Attributes:
- func: Function that returns Conversion or Converter when called
"""
func: Callable[[], Union[Conversion, Callable]]Built-in utility functions for common conversion patterns.
def catch_value_error(func: Func) -> Func:
"""
Decorator that catches ValueError and re-raises as ValidationError.
Parameters:
- func: Function to wrap
Returns:
Wrapped function that converts ValueError to ValidationError
"""
def as_str(cls: Cls) -> Cls:
"""
Register string conversion for enum classes.
Automatically registers serialization to string (enum.value or enum.name)
and deserialization from string.
Parameters:
- cls: Enum class to register converters for
Returns:
Enum class (for use as decorator)
"""
def as_names(cls: EnumCls, aliaser: Callable[[str], str] = lambda s: s) -> EnumCls:
"""
Register name-based conversion for enum classes with optional aliasing.
Parameters:
- cls: Enum class to register converters for
- aliaser: Function to transform enum names (e.g., snake_case to camelCase)
Returns:
Enum class (for use as decorator)
"""from datetime import datetime
from typing import NewType
from apischema import deserializer, serializer
# Custom type for timestamps
Timestamp = NewType("Timestamp", int)
@deserializer
def deserialize_timestamp(timestamp: int) -> datetime:
"""Convert Unix timestamp to datetime."""
return datetime.fromtimestamp(timestamp)
@serializer
def serialize_timestamp(dt: datetime) -> int:
"""Convert datetime to Unix timestamp."""
return int(dt.timestamp())
# Now datetime objects can be serialized/deserialized as timestamps
@dataclass
class Event:
name: str
created_at: datetime
data = {"name": "Meeting", "created_at": 1672531200} # Unix timestamp
event = deserialize(Event, data)
print(event.created_at) # datetime(2023, 1, 1, 0, 0)
result = serialize(Event, event)
print(result) # {"name": "Meeting", "created_at": 1672531200}from enum import Enum
from apischema.conversions import as_str, as_names
@as_str
class Status(Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
@as_names # Use enum names instead of values
class Priority(Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
@dataclass
class Task:
title: str
status: Status
priority: Priority
# Serialization uses enum values/names
data = {"title": "Review", "status": "approved", "priority": "HIGH"}
task = deserialize(Task, data)
print(task.status) # Status.APPROVED
print(task.priority) # Priority.HIGH
result = serialize(Task, task)
print(result) # {"title": "Review", "status": "approved", "priority": "HIGH"}from decimal import Decimal
@deserializer
def deserialize_decimal(value: str) -> Decimal:
"""Convert string to Decimal for precise arithmetic."""
return Decimal(value)
@serializer
def serialize_decimal(value: Decimal) -> str:
"""Convert Decimal to string representation."""
return str(value)
@dataclass
class Price:
amount: Decimal
currency: str
# Decimal values are handled as strings in JSON
data = {"amount": "99.99", "currency": "USD"}
price = deserialize(Price, data)
print(type(price.amount)) # <class 'decimal.Decimal'>
result = serialize(Price, price)
print(result) # {"amount": "99.99", "currency": "USD"}import uuid
from dataclasses import dataclass
@deserializer
def deserialize_uuid(value: str) -> uuid.UUID:
"""Convert string to UUID object."""
return uuid.UUID(value)
@serializer
def serialize_uuid(value: uuid.UUID) -> str:
"""Convert UUID to string representation."""
return str(value)
@dataclass
class User:
id: uuid.UUID
name: str
# UUIDs are handled as strings in JSON
data = {"id": "123e4567-e89b-12d3-a456-426614174000", "name": "John"}
user = deserialize(User, data)
print(type(user.id)) # <class 'uuid.UUID'># Example with SQLAlchemy models
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class UserModel(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
@deserializer
def deserialize_user_model(data: dict) -> UserModel:
"""Convert dict to SQLAlchemy model instance."""
return UserModel(**data)
@serializer
def serialize_user_model(user: UserModel) -> dict:
"""Convert SQLAlchemy model to dict."""
return {
"id": user.id,
"name": user.name,
"email": user.email
}
# Now UserModel can be used in apischema serialization
@dataclass
class UserResponse:
user: UserModel
metadata: dict
data = {
"user": {"id": 1, "name": "John", "email": "john@example.com"},
"metadata": {"source": "database"}
}
response = deserialize(UserResponse, data)
print(type(response.user)) # <class 'UserModel'># Avoid import cycles with lazy loading
@deserializer(lazy=lambda: datetime_converter)
def lazy_datetime_deserializer():
"""Lazy-loaded datetime converter."""
pass
def datetime_converter(timestamp: int) -> datetime:
"""Actual conversion function loaded lazily."""
return datetime.fromtimestamp(timestamp)from apischema.conversions import catch_value_error
@catch_value_error
@deserializer
def deserialize_positive_int(value: str) -> int:
"""Convert string to positive integer."""
result = int(value)
if result <= 0:
raise ValueError("Value must be positive")
return result
# ValueError is automatically converted to ValidationError
try:
deserialize(int, "-5") # Uses the converter
except ValidationError as err:
print(err.errors) # Structured error with location infofrom typing import Union
@deserializer
def deserialize_flexible_number(value: Union[str, int, float]) -> float:
"""Convert various number formats to float."""
if isinstance(value, str):
if value.endswith('%'):
return float(value[:-1]) / 100
return float(value)
return float(value)
@dataclass
class Statistics:
growth_rate: float # Can accept "15.5%", "0.155", 15.5, etc.
data = {"growth_rate": "15.5%"}
stats = deserialize(Statistics, data)
print(stats.growth_rate) # 0.155# Register different converters for different contexts
@serializer
def serialize_datetime_iso(dt: datetime) -> str:
"""Serialize datetime as ISO string."""
return dt.isoformat()
@serializer
def serialize_datetime_timestamp(dt: datetime) -> int:
"""Serialize datetime as Unix timestamp."""
return int(dt.timestamp())
# Choose converter at call time
event_data = serialize(Event, event, conversion=serialize_datetime_iso)
timestamp_data = serialize(Event, event, conversion=serialize_datetime_timestamp)Functions for managing and clearing registered conversions.
def reset_deserializers(cls: Type) -> None:
"""
Clear all registered deserializers for a given type.
Parameters:
- cls: The type to clear deserializers for
"""
def reset_serializer(cls: Type) -> None:
"""
Clear all registered serializers for a given type.
Parameters:
- cls: The type to clear serializers for
"""Converter = Callable[[Any], Any] # Basic conversion function