Skip to content

Schema API

Core schema definition classes and decorators.

Schema

Base schema class for defining data models.

Define your schema by subclassing Schema and adding field definitions. The metaclass automatically collects fields and validators.

Fields are defined using Pydantic-style type annotations with optional Field() for constraints.

Examples:

Basic schema definition:

>>> from flycatcher import Schema, Field
>>> from datetime import datetime
>>> class UserSchema(Schema):
...     # Simple fields - just type annotations
...     name: str
...     created_at: datetime
...
...     # Nullable fields
...     bio: str | None = None
...
...     # Fields with defaults
...     is_active: bool = True
...
...     # Fields with constraints
...     age: int = Field(ge=0, le=120)
...     email: str = Field(min_length=5, max_length=100)
...
...     # Database options
...     id: int = Field(primary_key=True, autoincrement=True)

With cross-field validation:

>>> from flycatcher import Schema, Field, col, model_validator
>>> class BookingSchema(Schema):
...     check_in: datetime
...     check_out: datetime
...
...     @model_validator
...     def check_dates():
...         return (
...             col('check_out') > col('check_in'),
...             "Check-out must be after check-in"
...         )

Generate outputs:

>>> from datetime import datetime
>>> # Generate Pydantic model
>>> UserModel = UserSchema.to_pydantic()
>>> user = UserModel(
...     id=1,
...     name="Alice",
...     age=25,
...     email="alice@example.com",
...     created_at=datetime.now(),
... )
>>>
>>> # Generate Polars validator
>>> import polars as pl
>>> validator = UserSchema.to_polars_validator()
>>> df = pl.DataFrame({
...     "id": [1],
...     "name": ["Alice"],
...     "age": [25],
...     "email": ["alice@example.com"],
...     "created_at": [datetime.now()],
... })
>>> validated_df = validator.validate(df, strict=True)
>>>
>>> # Generate SQLAlchemy table
>>> table = UserSchema.to_sqlalchemy(table_name="users")

to_pydantic() classmethod

Generate a Pydantic BaseModel from this schema.

Returns:

Type Description
type

A dynamically created Pydantic BaseModel class.

Examples:

>>> from flycatcher import Field, Schema
>>> class UserSchema(Schema):
...     id: int = Field(primary_key=True)
...     name: str
>>> UserModel = UserSchema.to_pydantic()
>>> user = UserModel(id=1, name="Alice")
>>> user.model_dump()
{'id': 1, 'name': 'Alice'}
Source code in src/flycatcher/base.py
@classmethod
def to_pydantic(cls) -> type:
    """
    Generate a Pydantic BaseModel from this schema.

    Returns
    -------
    type
        A dynamically created Pydantic BaseModel class.

    Examples
    --------
        >>> from flycatcher import Field, Schema
        >>> class UserSchema(Schema):
        ...     id: int = Field(primary_key=True)
        ...     name: str
        >>> UserModel = UserSchema.to_pydantic()
        >>> user = UserModel(id=1, name="Alice")
        >>> user.model_dump()
        {'id': 1, 'name': 'Alice'}
    """
    from .generators.pydantic import create_pydantic_model

    return create_pydantic_model(cls)

to_polars_validator() classmethod

Generate a Polars validator from this schema.

Returns:

Type Description
PolarsValidator

A validator instance for validating Polars DataFrames.

Examples:

>>> from flycatcher import Field, Schema
>>> import polars as pl
>>> class UserSchema(Schema):
...     id: int = Field(primary_key=True)
...     name: str = Field(min_length=1)
>>> validator = UserSchema.to_polars_validator()
>>> df = pl.DataFrame({"id": [1, 2], "name": ["Alice", "Bob"]})
>>> validated_df = validator.validate(df, strict=True)
Source code in src/flycatcher/base.py
@classmethod
def to_polars_validator(cls):
    """
    Generate a Polars validator from this schema.

    Returns
    -------
    PolarsValidator
        A validator instance for validating Polars DataFrames.

    Examples
    --------
        >>> from flycatcher import Field, Schema
        >>> import polars as pl
        >>> class UserSchema(Schema):
        ...     id: int = Field(primary_key=True)
        ...     name: str = Field(min_length=1)
        >>> validator = UserSchema.to_polars_validator()
        >>> df = pl.DataFrame({"id": [1, 2], "name": ["Alice", "Bob"]})
        >>> validated_df = validator.validate(df, strict=True)
    """
    from .generators.polars import create_polars_validator

    return create_polars_validator(cls)

to_sqlalchemy(table_name=None, metadata=None) classmethod

Generate a SQLAlchemy Table from this schema.

Parameters:

Name Type Description Default
table_name str

Name for the SQL table. If not provided, auto-generated from schema class name (removes "Schema" suffix, lowercases, adds "s").

None
metadata MetaData

MetaData instance to attach the table to. If not provided, a new MetaData instance is created.

None

Returns:

Type Description
Table

A SQLAlchemy Table object.

Examples:

>>> from flycatcher import Field, Schema
>>> from sqlalchemy import MetaData, create_engine
>>> class UserSchema(Schema):
...     id: int = Field(primary_key=True)
...     name: str
>>> metadata = MetaData()
>>> table = UserSchema.to_sqlalchemy(table_name="users", metadata=metadata)
>>> engine = create_engine("sqlite:///example.db")
>>> metadata.create_all(engine)
Source code in src/flycatcher/base.py
@classmethod
def to_sqlalchemy(cls, table_name: str | None = None, metadata=None):
    """
    Generate a SQLAlchemy Table from this schema.

    Parameters
    ----------
    table_name : str, optional
        Name for the SQL table. If not provided, auto-generated from
        schema class name (removes "Schema" suffix, lowercases, adds "s").
    metadata : sqlalchemy.MetaData, optional
        MetaData instance to attach the table to. If not provided,
        a new MetaData instance is created.

    Returns
    -------
    sqlalchemy.Table
        A SQLAlchemy Table object.

    Examples
    --------
        >>> from flycatcher import Field, Schema
        >>> from sqlalchemy import MetaData, create_engine
        >>> class UserSchema(Schema):
        ...     id: int = Field(primary_key=True)
        ...     name: str
        >>> metadata = MetaData()
        >>> table = UserSchema.to_sqlalchemy(table_name="users", metadata=metadata)
        >>> engine = create_engine("sqlite:///example.db")
        >>> metadata.create_all(engine)
    """
    from .generators.sqlalchemy import create_sqlalchemy_table

    return create_sqlalchemy_table(cls, table_name=table_name, metadata=metadata)

fields() classmethod

Return all fields defined in this schema.

Returns:

Type Description
dict[str, FieldBase]

Dictionary mapping field names to Field instances.

Examples:

>>> from flycatcher import Schema, Field
>>> class UserSchema(Schema):
...     id: int = Field(primary_key=True)
...     name: str
>>> fields = UserSchema.fields()
>>> list(fields.keys())
['id', 'name']
Source code in src/flycatcher/base.py
@classmethod
def fields(cls) -> dict[str, FieldBase]:
    """
    Return all fields defined in this schema.

    Returns
    -------
    dict[str, FieldBase]
        Dictionary mapping field names to Field instances.

    Examples
    --------
        >>> from flycatcher import Schema, Field
        >>> class UserSchema(Schema):
        ...     id: int = Field(primary_key=True)
        ...     name: str
        >>> fields = UserSchema.fields()
        >>> list(fields.keys())
        ['id', 'name']
    """
    return cls._fields.copy()

model_validators() classmethod

Return all model validators defined in this schema.

Returns:

Type Description
list[Callable]

List of validator functions decorated with @model_validator.

Examples:

>>> from flycatcher import Schema, col, model_validator
>>> class UserSchema(Schema):
...     age: int
...
...     @model_validator
...     def check_age():
...         return col('age') >= 18
>>> validators = UserSchema.model_validators()
>>> len(validators)
1
Source code in src/flycatcher/base.py
@classmethod
def model_validators(cls) -> list[Callable]:
    """
    Return all model validators defined in this schema.

    Returns
    -------
    list[Callable]
        List of validator functions decorated with @model_validator.

    Examples
    --------
        >>> from flycatcher import Schema, col, model_validator
        >>> class UserSchema(Schema):
        ...     age: int
        ...
        ...     @model_validator
        ...     def check_age():
        ...         return col('age') >= 18
        >>> validators = UserSchema.model_validators()
        >>> len(validators)
        1
    """
    return cls._model_validators.copy()

model_validator(func)

Decorator for cross-field validation.

Use this decorator to add custom validation logic that involves multiple fields. The validator function can return either:

  1. A DSL expression (recommended) - compiles to both Polars and Pydantic
  2. A dict with 'polars' and/or 'pydantic' keys for explicit implementations

The function can optionally accept a cls parameter, but it's not required for most use cases.

Parameters:

Name Type Description Default
func Callable

The validation function to decorate.

required

Returns:

Type Description
Callable

The decorated function, marked as a model validator.

Examples:

Simple DSL expression:

>>> from flycatcher import Schema, col, model_validator
>>> class BookingSchema(Schema):
...     check_in: int
...     check_out: int
...
...     @model_validator
...     def check_dates():
...         return col('check_out') > col('check_in')

With error message:

>>> class BookingSchema(Schema):
...     check_in: int
...     check_out: int
...
...     @model_validator
...     def check_dates():
...         return (
...             col('check_out') > col('check_in'),
...             "Check-out date must be after check-in date"
...         )

Complex validation with multiple conditions:

>>> from flycatcher import Field, Schema, col, model_validator
>>> class ProductSchema(Schema):
...     price: float
...     discount_price: float | None = None
...
...     @model_validator
...     def check_discount():
...         return (
...             (col('discount_price').is_null()) |
...             (col('discount_price') < col('price')),
...             "Discount price must be less than regular price"
...         )
Source code in src/flycatcher/base.py
def model_validator(func: Callable) -> Callable:
    """
    Decorator for cross-field validation.

    Use this decorator to add custom validation logic that involves multiple
    fields. The validator function can return either:

    1. A DSL expression (recommended) - compiles to both Polars and Pydantic
    2. A dict with 'polars' and/or 'pydantic' keys for explicit implementations

    The function can optionally accept a `cls` parameter, but it's not required
    for most use cases.

    Parameters
    ----------
    func : Callable
        The validation function to decorate.

    Returns
    -------
    Callable
        The decorated function, marked as a model validator.

    Examples
    --------
    Simple DSL expression:

        >>> from flycatcher import Schema, col, model_validator
        >>> class BookingSchema(Schema):
        ...     check_in: int
        ...     check_out: int
        ...
        ...     @model_validator
        ...     def check_dates():
        ...         return col('check_out') > col('check_in')

    With error message:

        >>> class BookingSchema(Schema):
        ...     check_in: int
        ...     check_out: int
        ...
        ...     @model_validator
        ...     def check_dates():
        ...         return (
        ...             col('check_out') > col('check_in'),
        ...             "Check-out date must be after check-in date"
        ...         )

    Complex validation with multiple conditions:

        >>> from flycatcher import Field, Schema, col, model_validator
        >>> class ProductSchema(Schema):
        ...     price: float
        ...     discount_price: float | None = None
        ...
        ...     @model_validator
        ...     def check_discount():
        ...         return (
        ...             (col('discount_price').is_null()) |
        ...             (col('discount_price') < col('price')),
        ...             "Discount price must be less than regular price"
        ...         )
    """
    # Mark the function as a model validator
    func._is_model_validator = True  # type: ignore[attr-defined]
    return func