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

advanced.mddocs/

Advanced Features

Advanced functionality including field set tracking, global settings configuration, method serialization, recursive type handling, and utility functions for complex use cases.

Capabilities

Field Set Tracking

Track which fields have been explicitly set on objects, useful for partial updates and distinguishing between default values and explicitly set values.

def with_fields_set(cls: Cls) -> Cls:
    """
    Class decorator to add field set tracking to dataclass.
    
    Adds tracking of which fields have been explicitly set vs. using defaults.
    
    Parameters:
    - cls: Dataclass to add field set tracking to
    
    Returns:
    Enhanced class with field set tracking capabilities
    """

def fields_set(obj: Any) -> AbstractSet[str]:
    """
    Get the set of field names that have been explicitly set on an object.
    
    Parameters:
    - obj: Object to check for set fields
    
    Returns:
    Set of field names that were explicitly set (not using defaults)
    """

def is_set(obj: T) -> T:
    """
    Return a proxy object for checking if fields are set.
    
    Parameters:
    - obj: Object to create proxy for
    
    Returns:
    Proxy object where field access returns True if field is set, False otherwise
    """

def set_fields(obj: T, *fields: Any, overwrite: bool = False) -> T:
    """
    Mark specific fields as set on an object.
    
    Parameters:
    - obj: Object to modify
    - fields: Field names to mark as set
    - overwrite: Whether to overwrite existing set status
    
    Returns:
    Modified object with updated field set status
    """

def unset_fields(obj: T, *fields: Any) -> T:
    """
    Mark specific fields as unset on an object.
    
    Parameters:
    - obj: Object to modify
    - fields: Field names to mark as unset
    
    Returns:
    Modified object with updated field set status
    """

Method Serialization

Include method results in serialization output for computed properties and derived values.

def serialized(
    alias: Optional[str] = None,
    *,
    conversion: Optional[AnyConversion] = None,
    error_handler: ErrorHandler = Undefined,
    order: Optional[Ordering] = None,
    schema: Optional[Schema] = None,
    owner: Optional[Type] = None,
) -> Callable:
    """
    Decorator to mark methods for inclusion in serialization output.
    
    Parameters:
    - alias: Alternative name for the method result in serialized data
    - conversion: Custom conversion for the method result
    - error_handler: How to handle errors during method execution
    - order: Ordering for the method result in output
    - schema: Schema metadata for the method result
    - owner: Owner class for the method
    
    Returns:
    Decorated method that will be included in serialization
    """

Global Settings Configuration

Configure global defaults for serialization, deserialization, and error handling behavior.

class settings:
    """
    Global configuration settings for apischema behavior.
    
    Provides default values for serialization, deserialization, and validation
    that can be overridden on a per-operation basis.
    """
    
    # Global settings
    additional_properties: bool = False
    aliaser: Aliaser = lambda s: s
    
    @property
    def camel_case(self) -> bool:
        """Enable automatic camelCase field name transformation."""
    
    @camel_case.setter  
    def camel_case(self, value: bool) -> None:
        """Set camelCase aliasing on/off."""
    
    class deserialization:
        """Default settings for deserialization operations."""
        coerce: bool = False
        default_conversion: DefaultConversion = default_deserialization
        fall_back_on_default: bool = False
        no_copy: bool = True
    
    class serialization:
        """Default settings for serialization operations."""  
        check_type: bool = False
        fall_back_on_any: bool = False
        exclude_defaults: bool = False
        exclude_none: bool = False
        exclude_unset: bool = True
        no_copy: bool = True
    
    class errors:
        """Error message templates for various constraint violations."""
        # Numeric constraints
        minimum: str = "less than {} (minimum)"
        maximum: str = "greater than {} (maximum)"
        exclusive_minimum: str = "less than or equal to {} (exclusive minimum)"
        exclusive_maximum: str = "greater than or equal to {} (exclusive maximum)"
        multiple_of: str = "not a multiple of {}"
        
        # String constraints
        min_length: str = "shorter than {} characters (minimum length)"
        max_length: str = "longer than {} characters (maximum length)"
        pattern: str = "does not match pattern '{}'"
        format: str = "invalid {} format"
        
        # Array constraints  
        min_items: str = "fewer than {} items (minimum)"
        max_items: str = "more than {} items (maximum)"
        unique_items: str = "duplicate items not allowed"
        
        # Object constraints
        min_properties: str = "fewer than {} properties (minimum)"
        max_properties: str = "more than {} properties (maximum)"
        required: str = "required field"
        additional_properties: str = "additional properties not allowed"
        
        # Type errors
        type_error: str = "{} expected"
        value_error: str = "invalid value"

Initialization Variables

Support for dataclass-like initialization variables that don't become instance attributes.

def init_var(tp: AnyType) -> Metadata:
    """
    Mark a field as an initialization variable (similar to dataclass InitVar).
    
    The field will be used during object construction but won't be stored
    as an instance attribute.
    
    Parameters:
    - tp: Type of the initialization variable
    
    Returns:
    Metadata marking the field as an init-only variable
    """

Utility Functions

Core utility functions used throughout the apischema system.

def identity(x: T) -> T:
    """
    Identity function that returns its argument unchanged.
    
    Used as a default transformation function in various contexts.
    
    Parameters:
    - x: Value to return unchanged
    
    Returns:
    The same value passed as input
    """

Core Types and Constants

Central type definitions and constant values used throughout apischema.

class UndefinedType:
    """
    Type for the Undefined singleton value.
    
    Used to distinguish between None and truly undefined values.
    """
    
    def __bool__(self) -> bool:
        return False
    
    def __repr__(self) -> str:
        return "Undefined"

Undefined: UndefinedType  # Singleton instance representing undefined values

class Unsupported(TypeError):
    """
    Exception raised when an operation is not supported for a given type.
    
    Indicates that apischema cannot handle a particular type or operation
    in the current context.
    """

Usage Examples

Field Set Tracking

from dataclasses import dataclass
from apischema.fields import with_fields_set, fields_set, is_set

@with_fields_set
@dataclass
class User:
    name: str
    email: str  
    age: int = 25  # Default value

# Create user with some fields set explicitly
user = User(name="John", email="john@example.com")  # age uses default

# Check which fields were set
print(fields_set(user))  # {"name", "email"}
print("age" in fields_set(user))  # False - used default

# Check field set status using proxy
is_set_proxy = is_set(user)
print(is_set_proxy.name)  # True
print(is_set_proxy.email)  # True  
print(is_set_proxy.age)   # False

# Modify field set status
from apischema.fields import set_fields, unset_fields

user = set_fields(user, "age")  # Mark age as explicitly set
print("age" in fields_set(user))  # True

user = unset_fields(user, "name")  # Mark name as not set
print("name" in fields_set(user))  # False

Partial Updates with Field Tracking

@with_fields_set
@dataclass
class UserUpdate:
    name: Optional[str] = None
    email: Optional[str] = None
    age: Optional[int] = None

def update_user(user_id: int, updates: UserUpdate) -> None:
    """Update only the fields that were explicitly set."""
    set_fields = fields_set(updates)
    
    update_data = {}
    if "name" in set_fields:
        update_data["name"] = updates.name
    if "email" in set_fields:  
        update_data["email"] = updates.email
    if "age" in set_fields:
        update_data["age"] = updates.age
    
    # Only update the explicitly set fields in database
    db.update_user(user_id, update_data)

# Deserialize partial update - only name is set
update_data = {"name": "Jane"}  # email and age not provided
updates = deserialize(UserUpdate, update_data)

print(fields_set(updates))  # {"name"}
update_user(123, updates)  # Only updates name field

Method Serialization

from apischema.serialization.serialized_methods import serialized

@dataclass
class Rectangle:
    width: float
    height: float
    
    @serialized  # Include in serialization output
    def area(self) -> float:
        """Calculate rectangle area."""
        return self.width * self.height
    
    @serialized(alias="perimeter")  # Use different name in output
    def get_perimeter(self) -> float:
        """Calculate rectangle perimeter."""
        return 2 * (self.width + self.height)
    
    @serialized(schema=schema(description="Aspect ratio of rectangle"))
    def aspect_ratio(self) -> float:
        """Calculate width to height ratio."""
        return self.width / self.height if self.height != 0 else float('inf')

rect = Rectangle(width=10, height=5)
result = serialize(Rectangle, rect)
print(result)
# {
#   "width": 10,
#   "height": 5,
#   "area": 50.0,
#   "perimeter": 30.0,
#   "aspect_ratio": 2.0
# }

Global Settings Configuration

from apischema import settings

# Configure global defaults
settings.additional_properties = True  # Allow extra properties globally
settings.camel_case = True  # Use camelCase aliasing by default

# Configure deserialization defaults  
settings.deserialization.coerce = True  # Enable type coercion
settings.deserialization.fall_back_on_default = True

# Configure serialization defaults
settings.serialization.exclude_defaults = True  # Skip default values
settings.serialization.exclude_none = True  # Skip None values

# Configure custom error messages
settings.errors.minimum = "value is too small (minimum: {})"
settings.errors.pattern = "value doesn't match required pattern: {}"

@dataclass
class Product:
    product_name: str  # Will be serialized as "productName" due to camel_case
    price: float = 0.0
    description: Optional[str] = None

# Global settings are applied automatically
data = {"productName": "Widget", "price": "29.99"}  # price as string
product = deserialize(Product, data)  # Coercion converts string to float

result = serialize(Product, product)  # Excludes price (default) and description (None)
print(result)  # {"productName": "Widget"}

Custom Settings Context

# Override global settings for specific operations
custom_product = deserialize(
    Product, 
    data,
    coerce=False,  # Override global coerce setting
    additional_properties=False  # Override global additional_properties
)

custom_result = serialize(
    Product,
    product,
    exclude_defaults=False,  # Include defaults for this serialization
    aliaser=lambda s: s.upper()  # Custom aliaser overrides camel_case
)

Initialization Variables

from apischema.metadata import init_var

@dataclass
class User:
    name: str
    email: str
    password: str = field(metadata=init_var(str))  # Init-only field
    
    def __post_init__(self, password: str):
        """Process password during initialization."""
        self.password_hash = hash_password(password)
        # password is not stored as an attribute

# During deserialization, password is used but not stored
user_data = {"name": "John", "email": "john@example.com", "password": "secret"}
user = deserialize(User, user_data)

print(hasattr(user, 'password'))  # False - not stored
print(hasattr(user, 'password_hash'))  # True - computed in __post_init__

# Serialization doesn't include init-only fields
result = serialize(User, user)
print("password" in result)  # False

Error Handling with Undefined

from apischema.types import Undefined

@dataclass
class Config:
    name: str
    timeout: int = 30
    retries: int = Undefined  # Distinguish from None

def process_config(config: Config):
    """Process configuration with undefined handling."""
    if config.retries is Undefined:
        print("Retries not specified - using adaptive strategy")
    elif config.retries is None:
        print("Retries explicitly disabled")
    else:
        print(f"Using {config.retries} retries")

# Different ways to specify retries
config1 = Config(name="test")  # retries = Undefined
config2 = deserialize(Config, {"name": "test", "retries": None})  # retries = None  
config3 = deserialize(Config, {"name": "test", "retries": 3})  # retries = 3

process_config(config1)  # "Retries not specified..."
process_config(config2)  # "Retries explicitly disabled"
process_config(config3)  # "Using 3 retries"

Advanced Type Handling

class CustomUnsupportedError(Unsupported):
    """Custom unsupported operation error."""
    pass

def handle_complex_type(value: Any) -> Any:
    """Handle complex types with fallback."""
    try:
        return serialize(type(value), value)
    except Unsupported:
        # Fall back to string representation
        return str(value)
    except CustomUnsupportedError:
        # Custom handling for specific unsupported types
        return {"type": type(value).__name__, "repr": repr(value)}

# Use with various types
result1 = handle_complex_type(datetime.now())  # Uses datetime serializer
result2 = handle_complex_type(lambda x: x)  # Falls back to str()
result3 = handle_complex_type(CustomType())  # Custom error handling

Type Aliases

T = TypeVar("T")  # Generic type variable
Cls = TypeVar("Cls")  # Class type variable  
ErrorHandler = Union[Callable[[Exception], Any], UndefinedType]  # Error handling function
DefaultConversion = Any  # Default conversion strategy type
AnyConversion = Union[Conversion, Callable, None]  # Any conversion type
Aliaser = Callable[[str], str]  # Field name transformation function