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

metadata.mddocs/

Metadata and Configuration

Rich metadata system for controlling field behavior without modifying class definitions. Supports aliasing, ordering, conditional serialization, validation attachment, and schema constraints.

Capabilities

Schema Metadata

Define validation constraints and schema information for fields and types.

def schema(
    *,
    title: Optional[str] = None,
    description: Optional[str] = None,
    default: Any = Undefined,
    examples: Optional[Sequence[Any]] = None,
    deprecated: Optional[Deprecated] = None,
    # Number constraints
    min: Optional[Number] = None,
    max: Optional[Number] = None,
    exc_min: Optional[Number] = None,
    exc_max: Optional[Number] = None,
    mult_of: Optional[Number] = None,
    # String constraints
    format: Optional[str] = None,
    media_type: Optional[str] = None,
    encoding: Optional[ContentEncoding] = None,
    min_len: Optional[int] = None,
    max_len: Optional[int] = None,
    pattern: Optional[Union[str, Pattern]] = None,
    # Array constraints
    min_items: Optional[int] = None,
    max_items: Optional[int] = None,
    unique: Optional[bool] = None,
    # Object constraints
    min_props: Optional[int] = None,
    max_props: Optional[int] = None,
    # Extra data
    extra: Optional[Extra] = None,
    override: bool = False,
) -> Schema:
    """
    Create schema metadata with validation constraints and documentation.
    
    Parameters:
    - title: Human-readable title for the field/type
    - description: Detailed description
    - default: Default value factory
    - examples: Example values for documentation
    - deprecated: Deprecation information
    - min/max: Numeric range constraints (inclusive)
    - exc_min/exc_max: Numeric range constraints (exclusive)
    - mult_of: Multiple-of constraint for numbers
    - format: String format specification (e.g., "email", "uri")
    - media_type: Media type for content
    - encoding: Content encoding specification
    - min_len/max_len: String/array length constraints
    - pattern: Regex pattern for string validation
    - min_items/max_items: Array size constraints
    - unique: Array uniqueness constraint
    - min_props/max_props: Object property count constraints
    - extra: Additional schema data
    - override: Override inherited schema metadata
    
    Returns:
    Schema metadata object
    """

@dataclass(frozen=True)
class Schema(MetadataMixin):
    """
    Schema metadata for fields and types.
    
    Contains validation constraints, documentation, and schema generation options.
    """
    title: Optional[str] = None
    description: Optional[str] = None
    default: Optional[Callable[[], Any]] = None
    examples: Optional[Sequence[Any]] = None
    format: Optional[str] = None
    deprecated: Optional[Deprecated] = None
    media_type: Optional[str] = None
    encoding: Optional[ContentEncoding] = None
    constraints: Optional[Constraints] = None
    extra: Optional[Callable[[Dict[str, Any]], None]] = None
    override: bool = False
    child: Optional["Schema"] = None

Field Aliasing

Transform field names during serialization and deserialization without changing the Python field names.

def alias(alias_: str, *, override: bool = True) -> Metadata:
    """
    Create field alias metadata.
    
    Parameters:
    - alias_: Alternative name for the field in serialized data
    - override: Override inherited aliases
    
    Returns:
    Alias metadata object
    """

def alias(aliaser: Aliaser) -> Callable[[Cls], Cls]:
    """
    Class decorator for applying aliaser function to all fields.
    
    Parameters:
    - aliaser: Function that transforms field names
    
    Returns:
    Class decorator function
    """

Discriminator Configuration

Configure union type discrimination based on field values for polymorphic deserialization.

@dataclass(frozen=True, unsafe_hash=False)
class Discriminator(MetadataMixin):
    """
    Discriminator configuration for union types.
    
    Enables polymorphic deserialization based on discriminator field values.
    """
    alias: str  # Field name used for discrimination
    mapping: Union[
        Mapping[str, AnyType],
        Callable[[str, Sequence[AnyType]], Mapping[str, AnyType]]
    ] = default_discriminator_mapping
    override_implicit: bool = True
    
    def get_mapping(self, types: Sequence[AnyType]) -> Mapping[str, AnyType]:
        """Get discriminator value to type mapping."""

def discriminator(alias: str, mapping: Mapping[str, AnyType] = None) -> Discriminator:
    """
    Create discriminator metadata for union types.
    
    Parameters:
    - alias: Field name to use for discrimination
    - mapping: Optional explicit mapping of values to types
    
    Returns:
    Discriminator metadata object
    """

Field Ordering

Control the order of fields in serialization output and schema generation.

def order(__value: int) -> Ordering:
    """
    Create ordering metadata with numeric order.
    
    Parameters:
    - __value: Numeric order (lower numbers come first)
    
    Returns:
    Ordering metadata object
    """

def order(*, after: Any) -> Ordering:
    """
    Create ordering metadata to place field after another.
    
    Parameters:
    - after: Field to place this field after
    
    Returns:
    Ordering metadata object
    """

def order(*, before: Any) -> Ordering:
    """
    Create ordering metadata to place field before another.
    
    Parameters:
    - before: Field to place this field before
    
    Returns:
    Ordering metadata object
    """

@dataclass(frozen=True)
class Ordering(MetadataMixin):
    """
    Field ordering metadata.
    
    Controls the order of fields in serialization and schema generation.
    """
    order: Optional[int] = None
    after: Optional[Any] = None
    before: Optional[Any] = None

Properties Handling

Configure handling of additional properties in objects during serialization and deserialization.

def properties(pattern: Union[str, Pattern, ellipsis] = ...) -> Metadata:
    """
    Create properties metadata for additional properties handling.
    
    Parameters:
    - pattern: Pattern for additional property names (... = allow all)
    
    Returns:
    Properties metadata object
    """

@dataclass(frozen=True)
class PropertiesMetadata(MetadataMixin):
    """
    Additional properties configuration.
    
    Controls how extra properties are handled during serialization/deserialization.
    """
    pattern: Union[str, Pattern, ellipsis] = ...

Field Behavior Control

Metadata for controlling various field behaviors during serialization and deserialization.

# Conversion metadata
@dataclass(frozen=True)
class ConversionMetadata(MetadataMixin):
    """
    Conversion-specific metadata for fields.
    
    Attributes:
    - deserialization: Custom deserialization conversion
    - serialization: Custom serialization conversion
    """
    deserialization: Optional["AnyConversion"] = None
    serialization: Optional["AnyConversion"] = None

conversion = ConversionMetadata  # Convenience factory

# Skip metadata
@dataclass(frozen=True)
class SkipMetadata(MetadataMixin):
    """
    Configure field skipping behavior.
    
    Attributes:
    - deserialization: Skip during deserialization
    - serialization: Skip during serialization
    - serialization_default: Skip default values during serialization
    - serialization_if: Conditional serialization predicate
    """
    deserialization: bool = False
    serialization: bool = False
    serialization_default: bool = False
    serialization_if: Optional[Callable[[Any], Any]] = None

# Predefined skip behaviors
skip = SkipMetadata(deserialization=True, serialization=True)  # Skip completely
skip_serialization = SkipMetadata(serialization=True)  # Skip only serialization
skip_deserialization = SkipMetadata(deserialization=True)  # Skip only deserialization

# Field behavior flags
default_as_set: Metadata  # Mark field as set when using default value
fall_back_on_default: Metadata  # Use default when deserialization fails
flatten: Metadata  # Flatten nested object fields
none_as_undefined: Metadata  # Treat None as undefined
post_init: Metadata  # Call after __init__
required: Metadata  # Mark field as required regardless of default

Validation Metadata

Attach validators directly to fields through metadata.

def validators(*validator: Callable) -> ValidatorsMetadata:
    """
    Create validators metadata for field-level validation.
    
    Parameters:
    - validator: Validation functions to attach to field
    
    Returns:
    Validators metadata object
    """

@dataclass(frozen=True)
class ValidatorsMetadata(MetadataMixin):
    """
    Field-level validators metadata.
    
    Attributes:
    - validators: List of validation functions for the field
    """
    validators: Tuple[Callable, ...] = ()

Dependency Management

Define field dependencies where presence of one field requires others.

def dependent_required(
    fields: Mapping[Any, Collection[Any]],
    *groups: Collection[Any],
    owner: Optional[type] = None,
) -> Callable:
    """
    Create dependent field requirements.
    
    Parameters:
    - fields: Mapping of field to its required dependencies
    - groups: Groups of mutually dependent fields  
    - owner: Owner class for the dependencies
    
    Returns:
    Dependency configuration function
    """

Type Names

Define custom type names for schema generation in different contexts.

def type_name(
    ref: Optional[NameOrFactory] = None,
    *,
    json_schema: Optional[NameOrFactory] = None,
    graphql: Optional[NameOrFactory] = None,
) -> TypeNameFactory:
    """
    Create type naming metadata for schema generation.
    
    Parameters:
    - ref: Default reference name
    - json_schema: Specific name for JSON Schema generation
    - graphql: Specific name for GraphQL schema generation
    
    Returns:
    Type name factory function
    """

class TypeName(NamedTuple):
    """
    Type name configuration for different schema contexts.
    
    Attributes:
    - json_schema: Name to use in JSON Schema
    - graphql: Name to use in GraphQL schema
    """
    json_schema: Optional[str] = None
    graphql: Optional[str] = None

Usage Examples

Schema Constraints

from dataclasses import dataclass, field
from apischema import schema

@dataclass
class User:
    username: str = field(metadata=schema(
        min_len=3,
        max_len=20,
        pattern=r"^[a-zA-Z0-9_]+$",
        description="Alphanumeric username between 3-20 characters"
    ))
    
    email: str = field(metadata=schema(
        format="email",
        description="Valid email address"
    ))
    
    age: int = field(metadata=schema(
        min=13,
        max=120,
        description="Age in years"
    ))
    
    score: float = field(metadata=schema(
        min=0.0,
        max=100.0,
        mult_of=0.1,
        description="Score as percentage with one decimal place"
    ))

# Schema constraints are enforced during deserialization
try:
    user = deserialize(User, {
        "username": "xy",  # Too short
        "email": "invalid-email",  # Invalid format
        "age": 10,  # Too young
        "score": 150.0  # Too high
    })
except ValidationError as err:
    for error in err.errors:
        print(f"{error['loc']}: {error['err']}")

Field Aliasing

@dataclass
class APIUser:
    user_id: int = field(metadata=alias("userId"))
    first_name: str = field(metadata=alias("firstName"))
    last_name: str = field(metadata=alias("lastName"))
    is_active: bool = field(metadata=alias("isActive"))

# JSON uses camelCase, Python uses snake_case
json_data = {
    "userId": 123,
    "firstName": "John", 
    "lastName": "Doe",
    "isActive": True
}

user = deserialize(APIUser, json_data)
print(user.user_id)  # 123
print(user.first_name)  # "John"

result = serialize(APIUser, user)
print(result)  # {"userId": 123, "firstName": "John", "lastName": "Doe", "isActive": true}

Global Aliasing

from apischema import alias, settings

# Apply camelCase aliasing to entire class
@alias(lambda name: ''.join(word.capitalize() if i > 0 else word 
                          for i, word in enumerate(name.split('_'))))
@dataclass
class CamelCaseUser:
    user_id: int
    full_name: str
    email_address: str

# Or use global settings
settings.camel_case = True  # Automatically applies camelCase aliasing

Union Type Discrimination

from typing import Union
from apischema import discriminator

@dataclass
class Dog:
    type: str = field(default="dog", metadata=discriminator("type"))
    breed: str
    good_boy: bool = True

@dataclass  
class Cat:
    type: str = field(default="cat", metadata=discriminator("type"))
    breed: str
    lives_remaining: int = 9

Animal = Union[Dog, Cat]

# Discriminator field determines which type to deserialize
dog_data = {"type": "dog", "breed": "Golden Retriever", "good_boy": True}
cat_data = {"type": "cat", "breed": "Siamese", "lives_remaining": 7}

dog = deserialize(Animal, dog_data)  # Returns Dog instance
cat = deserialize(Animal, cat_data)  # Returns Cat instance

print(type(dog))  # <class 'Dog'>
print(type(cat))  # <class 'Cat'>

Field Ordering

@dataclass
class OrderedData:
    id: int = field(metadata=order(1))  # First
    name: str = field(metadata=order(2))  # Second  
    metadata: dict = field(metadata=order(99))  # Last
    created_at: str = field(metadata=order(3))  # Third

# Serialization respects field ordering
data = OrderedData(
    metadata={"version": "1.0"},
    created_at="2023-01-01", 
    name="Test",
    id=1
)

result = serialize(OrderedData, data)
# Output preserves order: {"id": 1, "name": "Test", "created_at": "2023-01-01", "metadata": {...}}

Conditional Serialization

@dataclass
class UserProfile:
    username: str
    email: str
    password_hash: str = field(metadata=skip_serialization)  # Never serialize
    
    # Conditionally serialize based on value
    admin_notes: str = field(
        default="",
        metadata=SkipMetadata(
            serialization_if=lambda x: len(x.strip()) > 0  # Only if not empty
        )
    )

user = UserProfile(
    username="john",
    email="john@example.com", 
    password_hash="hashed_password",
    admin_notes=""  # Empty, will be skipped
)

result = serialize(UserProfile, user)
print(result)  # {"username": "john", "email": "john@example.com"}
# password_hash and admin_notes are excluded

Field-Level Validation

def validate_positive(value: int) -> int:
    if value <= 0:
        raise ValueError("Value must be positive")
    return value

def validate_email_domain(email: str) -> str:
    if not email.endswith("@company.com"):
        raise ValueError("Must be company email")
    return email

@dataclass
class Employee:
    id: int = field(metadata=validators(validate_positive))
    email: str = field(metadata=validators(validate_email_domain))
    salary: float = field(metadata=validators(validate_positive))

# Field validators are applied during deserialization
try:
    employee = deserialize(Employee, {
        "id": -1,  # Fails positive validation
        "email": "user@gmail.com",  # Fails domain validation
        "salary": 50000
    })
except ValidationError as err:
    print(err.errors)  # Shows validation failures for id and email

Complex Metadata Combination

@dataclass
class Product:
    # Combine multiple metadata types
    name: str = field(metadata=[
        schema(min_len=1, max_len=100, description="Product name"),
        alias("productName"),
        order(1),
        validators(lambda x: x.strip())  # Remove whitespace
    ])
    
    price: float = field(metadata=[
        schema(min=0, description="Price in USD"),
        order(2),
        validators(validate_positive)
    ])
    
    # Use factory pattern for multiple metadata
    description: str = field(
        default="",
        metadata=schema(
            max_len=1000,
            description="Product description",
            examples=["High-quality product", "Best in class"]
        )
    )

Additional Metadata Functions

Additional field behavior control functions for specialized use cases.

def default_as_set() -> Metadata:
    """
    Mark field as set when using default value.
    
    Used to ensure that fields with defaults are treated as explicitly set
    rather than unset during serialization/deserialization.
    """

def fall_back_on_default() -> Metadata:
    """
    Fall back to default value when deserialization fails.
    
    Instead of raising an error, use the field's default value
    when deserialization encounters problems.
    """

def flatten() -> Metadata:
    """
    Flatten nested object fields into parent object.
    
    Aliases: flattened, merged
    Used to merge fields from nested objects into the parent structure.
    """

def none_as_undefined() -> Metadata:
    """
    Treat None values as undefined during serialization.
    
    Causes None values to be omitted from serialized output
    rather than explicitly serialized as null.
    """

def post_init() -> Metadata:  
    """
    Mark field for post-initialization processing.
    
    Indicates that field should be processed after object construction
    is complete, useful for computed or derived fields.
    """

def required() -> Metadata:
    """
    Mark field as required during deserialization.
    
    Ensures that the field must be present in input data,
    overriding any default value or optional typing.
    """

def init_var(tp: AnyType) -> Metadata:
    """
    Mark field as initialization variable.
    
    Parameters:
    - tp: The type of the initialization variable
    
    Similar to dataclass init_var, used for fields that are needed
    during initialization but not stored in the final object.
    """

def validators(*validator: Callable) -> Metadata:
    """
    Attach validators to a field via metadata.
    
    Parameters:
    - validator: One or more validator functions
    
    Used with Annotated types to attach field-specific validators:
    Annotated[str, validators(check_email_format)]
    """

Type Aliases

Metadata = Any  # Generic metadata type
MetadataMixin = Any  # Base class for metadata objects
Aliaser = Callable[[str], str]  # Field name transformation function
NameOrFactory = Union[str, Callable[[], str]]  # Name or name factory function
TypeNameFactory = Callable[[AnyType], TypeName]  # Type name factory function
Number = Union[int, float]  # Numeric type for constraints
Pattern = Any  # Compiled regex pattern type
ContentEncoding = str  # Content encoding specification
Extra = Callable[[Dict[str, Any]], None]  # Extra schema data function
Deprecated = Union[bool, str]  # Deprecation flag or message