or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced.mdconversions.mdindex.mdmetadata.mdschema-generation.mdserialization.mdvalidation.md
tile.json

conversions.mddocs/

Type Conversions

Extensible conversion system for custom serialization and deserialization logic. Register converters for any types including third-party libraries, ORM models, and custom transformations.

Capabilities

Deserializer Registration

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

Serializer 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
    """

Conversion Configuration

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]]

Utility Converters

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

Usage Examples

Basic Type Conversion

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}

Enum Conversions

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"}

Custom Object Conversion

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"}

Third-Party Library Integration

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

ORM Model Integration

# 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'>

Lazy Conversion Loading

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

Error Handling in Conversions

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 info

Conditional Conversions

from 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

Multiple Conversion Strategies

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

Conversion Management

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

Type Aliases

Converter = Callable[[Any], Any]  # Basic conversion function