Skip to content

Fields API

Field type definitions with validation constraints.

Base Field

Field(default=_MISSING, *, primary_key=False, nullable=False, description=None, unique=False, index=False, autoincrement=None, gt=None, ge=None, lt=None, le=None, multiple_of=None, min_length=None, max_length=None, pattern=None)

Declare field metadata and constraints for Pydantic-style schema definitions.

Use this function with type annotations to define schema fields with constraints, similar to Pydantic's Field() function.

Parameters:

Name Type Description Default
default Any

Default value for the field. Can be provided as first positional argument.

_MISSING
primary_key bool

Mark this field as the primary key (for database operations).

False
nullable bool

Allow None values for this field.

False
description str

Human-readable description of this field.

None
unique bool

Enforce uniqueness constraint (for database operations).

False
index bool

Create an index on this field (for database operations).

False
autoincrement bool

Enable auto-increment for integer fields.

None
gt numeric

Value must be greater than this (for int, float, datetime fields).

None
ge numeric

Value must be greater than or equal to this.

None
lt numeric

Value must be less than this.

None
le numeric

Value must be less than or equal to this.

None
multiple_of int

Value must be a multiple of this (for integer fields).

None
min_length int

Minimum string length (for string fields).

None
max_length int

Maximum string length (for string fields).

None
pattern str

Regex pattern the string must match (for string fields).

None

Returns:

Type Description
FieldInfo

A FieldInfo instance that will be processed by the Schema metaclass.

Examples:

Basic usage with type annotations:

>>> from flycatcher import Schema, Field
>>> from datetime import datetime
>>> class UserSchema(Schema):
...     # Simple fields - just annotations
...     name: str
...     created_at: datetime
...
...     # Fields with defaults
...     is_active: bool = True
...
...     # Nullable fields
...     bio: str | None = None
...
...     # Fields with constraints
...     age: int = Field(ge=0, le=120)
...     email: str = Field(pattern=r'^[^@]+@[^@]+\.[^@]+$')
...
...     # Database-specific options
...     id: int = Field(primary_key=True, autoincrement=True)

With default value as positional argument:

>>> class ConfigSchema(Schema):
...     timeout: int = Field(default=30, ge=1, le=300)
...     retries: int = Field(default=3, ge=0)
Source code in src/flycatcher/fields.py
def Field(  # noqa: N802 - Capitalized to match Pydantic's Field() API
    default: Any = _MISSING,
    *,
    # Base field options
    primary_key: bool = False,
    nullable: bool = False,
    description: str | None = None,
    unique: bool = False,
    index: bool = False,
    autoincrement: bool | None = None,
    # Numeric constraints (Integer, Float, Datetime)
    gt: int | float | datetime | None = None,
    ge: int | float | datetime | None = None,
    lt: int | float | datetime | None = None,
    le: int | float | datetime | None = None,
    multiple_of: int | None = None,
    # String constraints
    min_length: int | None = None,
    max_length: int | None = None,
    pattern: str | None = None,
) -> Any:
    """
    Declare field metadata and constraints for Pydantic-style schema definitions.

    Use this function with type annotations to define schema fields with
    constraints, similar to Pydantic's Field() function.

    Parameters
    ----------
    default : Any, optional
        Default value for the field. Can be provided as first positional argument.
    primary_key : bool, default False
        Mark this field as the primary key (for database operations).
    nullable : bool, default False
        Allow None values for this field.
    description : str, optional
        Human-readable description of this field.
    unique : bool, default False
        Enforce uniqueness constraint (for database operations).
    index : bool, default False
        Create an index on this field (for database operations).
    autoincrement : bool, optional
        Enable auto-increment for integer fields.
    gt : numeric, optional
        Value must be greater than this (for int, float, datetime fields).
    ge : numeric, optional
        Value must be greater than or equal to this.
    lt : numeric, optional
        Value must be less than this.
    le : numeric, optional
        Value must be less than or equal to this.
    multiple_of : int, optional
        Value must be a multiple of this (for integer fields).
    min_length : int, optional
        Minimum string length (for string fields).
    max_length : int, optional
        Maximum string length (for string fields).
    pattern : str, optional
        Regex pattern the string must match (for string fields).

    Returns
    -------
    FieldInfo
        A FieldInfo instance that will be processed by the Schema metaclass.

    Examples
    --------
    Basic usage with type annotations:

        >>> from flycatcher import Schema, Field
        >>> from datetime import datetime
        >>> class UserSchema(Schema):
        ...     # Simple fields - just annotations
        ...     name: str
        ...     created_at: datetime
        ...
        ...     # Fields with defaults
        ...     is_active: bool = True
        ...
        ...     # Nullable fields
        ...     bio: str | None = None
        ...
        ...     # Fields with constraints
        ...     age: int = Field(ge=0, le=120)
        ...     email: str = Field(pattern=r'^[^@]+@[^@]+\\.[^@]+$')
        ...
        ...     # Database-specific options
        ...     id: int = Field(primary_key=True, autoincrement=True)

    With default value as positional argument:

        >>> class ConfigSchema(Schema):
        ...     timeout: int = Field(default=30, ge=1, le=300)
        ...     retries: int = Field(default=3, ge=0)
    """
    return FieldInfo(
        primary_key=primary_key,
        nullable=nullable,
        default=default,
        description=description,
        unique=unique,
        index=index,
        autoincrement=autoincrement,
        gt=gt,
        ge=ge,
        lt=lt,
        le=le,
        multiple_of=multiple_of,
        min_length=min_length,
        max_length=max_length,
        pattern=pattern,
    )

Field Types

Integer(*, gt=None, ge=None, lt=None, le=None, multiple_of=None, **kwargs)

Bases: FieldBase

Integer field type with numeric constraints.

Parameters:

Name Type Description Default
gt int

Value must be greater than this number.

None
ge int

Value must be greater than or equal to this number.

None
lt int

Value must be less than this number.

None
le int

Value must be less than or equal to this number.

None
multiple_of int

Value must be a multiple of this number.

None
**kwargs

Additional arguments passed to Field (primary_key, nullable, etc.).

{}

Examples:

>>> from flycatcher import Field, Schema
>>> class UserSchema(Schema):
...     age: int = Field(ge=0, le=120)
...     score: int = Field(gt=0, multiple_of=10)
...     id: int = Field(primary_key=True, autoincrement=True)
Source code in src/flycatcher/fields.py
def __init__(
    self,
    *,
    gt: int | None = None,  # Greater than
    ge: int | None = None,  # Greater than or equal
    lt: int | None = None,  # Less than
    le: int | None = None,  # Less than or equal
    multiple_of: int | None = None,
    **kwargs,
):
    super().__init__(**kwargs)
    self.gt = gt
    self.ge = ge
    self.lt = lt
    self.le = le
    self.multiple_of = multiple_of

get_polars_constraints()

Generate Polars validation expressions.

Source code in src/flycatcher/fields.py
def get_polars_constraints(self) -> list[tuple[Any, str]]:
    """Generate Polars validation expressions."""
    constraints = list(super().get_polars_constraints())
    assert self.name is not None  # Checked by base class
    col = pl.col(self.name)

    # Range constraints
    if self.gt is not None:
        constraints.append((col > self.gt, f"{self.name} must be > {self.gt}"))
    if self.ge is not None:
        constraints.append((col >= self.ge, f"{self.name} must be >= {self.ge}"))
    if self.lt is not None:
        constraints.append((col < self.lt, f"{self.name} must be < {self.lt}"))
    if self.le is not None:
        constraints.append((col <= self.le, f"{self.name} must be <= {self.le}"))

    # Multiple of constraint
    if self.multiple_of is not None:
        constraints.append(
            (
                col % self.multiple_of == 0,
                f"{self.name} must be multiple of {self.multiple_of}",
            )
        )

    return constraints

get_pydantic_field_kwargs()

Return kwargs for Pydantic Field().

Source code in src/flycatcher/fields.py
def get_pydantic_field_kwargs(self) -> dict[str, Any]:
    """Return kwargs for Pydantic Field()."""
    kwargs = {}
    if self.gt is not None:
        kwargs["gt"] = self.gt
    if self.ge is not None:
        kwargs["ge"] = self.ge
    if self.lt is not None:
        kwargs["lt"] = self.lt
    if self.le is not None:
        kwargs["le"] = self.le
    if self.multiple_of is not None:
        kwargs["multiple_of"] = self.multiple_of
    return kwargs

Float(*, gt=None, ge=None, lt=None, le=None, **kwargs)

Bases: FieldBase

Float field type with numeric constraints.

Parameters:

Name Type Description Default
gt float

Value must be greater than this number.

None
ge float

Value must be greater than or equal to this number.

None
lt float

Value must be less than this number.

None
le float

Value must be less than or equal to this number.

None
**kwargs

Additional arguments passed to Field (primary_key, nullable, etc.).

{}

Examples:

>>> from flycatcher import Field, Schema
>>> class ProductSchema(Schema):
...     price: float = Field(gt=0.0)
...     discount: float | None = Field(default=None, ge=0.0, le=1.0)
Source code in src/flycatcher/fields.py
def __init__(
    self,
    *,
    gt: float | None = None,
    ge: float | None = None,
    lt: float | None = None,
    le: float | None = None,
    **kwargs,
):
    super().__init__(**kwargs)
    self.gt = gt
    self.ge = ge
    self.lt = lt
    self.le = le

get_polars_constraints()

Generate Polars validation expressions.

Source code in src/flycatcher/fields.py
def get_polars_constraints(self) -> list[tuple[Any, str]]:
    """Generate Polars validation expressions."""
    constraints = list(super().get_polars_constraints())
    assert self.name is not None  # Checked by base class
    col = pl.col(self.name)

    if self.gt is not None:
        constraints.append((col > self.gt, f"{self.name} must be > {self.gt}"))
    if self.ge is not None:
        constraints.append((col >= self.ge, f"{self.name} must be >= {self.ge}"))
    if self.lt is not None:
        constraints.append((col < self.lt, f"{self.name} must be < {self.lt}"))
    if self.le is not None:
        constraints.append((col <= self.le, f"{self.name} must be <= {self.le}"))

    return constraints

get_pydantic_field_kwargs()

Return kwargs for Pydantic Field().

Source code in src/flycatcher/fields.py
def get_pydantic_field_kwargs(self) -> dict[str, Any]:
    """Return kwargs for Pydantic Field()."""
    kwargs = {}
    if self.gt is not None:
        kwargs["gt"] = self.gt
    if self.ge is not None:
        kwargs["ge"] = self.ge
    if self.lt is not None:
        kwargs["lt"] = self.lt
    if self.le is not None:
        kwargs["le"] = self.le
    return kwargs

String(*, max_length=None, min_length=None, pattern=None, **kwargs)

Bases: FieldBase

String field type with length and pattern constraints.

Parameters:

Name Type Description Default
min_length int

Minimum length of the string (inclusive).

None
max_length int

Maximum length of the string (inclusive).

None
pattern str

Regular expression pattern that the string must match.

None
**kwargs

Additional arguments passed to Field (primary_key, nullable, etc.).

{}

Examples:

>>> from flycatcher import Field, Schema
>>> class UserSchema(Schema):
...     name: str = Field(min_length=1, max_length=100)
...     email: str = Field(pattern=r'^[^@]+@[^@]+\.[^@]+$')
...     bio: str | None = Field(default=None, max_length=500)
Source code in src/flycatcher/fields.py
def __init__(
    self,
    *,
    max_length: int | None = None,
    min_length: int | None = None,
    pattern: str | None = None,
    **kwargs,
):
    super().__init__(**kwargs)
    self.max_length = max_length
    self.min_length = min_length
    self.pattern = pattern

get_polars_constraints()

Generate Polars validation expressions.

Source code in src/flycatcher/fields.py
def get_polars_constraints(self) -> list[tuple[Any, str]]:
    """Generate Polars validation expressions."""
    constraints = list(super().get_polars_constraints())
    assert self.name is not None  # Checked by base class
    col = pl.col(self.name)

    # Length constraints
    if self.min_length is not None:
        constraints.append(
            (
                col.str.len_chars() >= self.min_length,
                f"{self.name} must have at least {self.min_length} characters",
            )
        )
    if self.max_length is not None:
        constraints.append(
            (
                col.str.len_chars() <= self.max_length,
                f"{self.name} must have at most {self.max_length} characters",
            )
        )

    # Regex pattern
    if self.pattern is not None:
        constraints.append(
            (
                col.str.contains(self.pattern),
                f"{self.name} must match pattern: {self.pattern}",
            )
        )

    return constraints

get_pydantic_field_kwargs()

Return kwargs for Pydantic Field().

Source code in src/flycatcher/fields.py
def get_pydantic_field_kwargs(self) -> dict[str, Any]:
    """Return kwargs for Pydantic Field()."""
    kwargs: dict[str, Any] = {}
    if self.min_length is not None:
        kwargs["min_length"] = self.min_length
    if self.max_length is not None:
        kwargs["max_length"] = self.max_length
    if self.pattern is not None:
        kwargs["pattern"] = self.pattern
    return kwargs

Boolean(*, primary_key=False, nullable=False, default=_MISSING, description=None, unique=False, index=False, autoincrement=None)

Bases: FieldBase

Boolean field type.

Examples:

>>> from flycatcher import Field, Schema
>>> class UserSchema(Schema):
...     is_active: bool = True
...     is_verified: bool | None = None
Source code in src/flycatcher/fields.py
def __init__(
    self,
    *,
    primary_key: bool = False,
    nullable: bool = False,
    default: Any = _MISSING,
    description: str | None = None,
    unique: bool = False,
    index: bool = False,
    autoincrement: bool | None = None,
):
    self.primary_key = primary_key
    self.nullable = nullable
    self.default = default
    self.description = description
    self.unique = unique
    self.index = index
    self.autoincrement = autoincrement
    self.name: str | None = None  # Set by Schema metaclass

    # Warn about ambiguous configuration
    if nullable and default is not _MISSING:
        # Defer warning until name is set by metaclass
        self._needs_warning = True
    else:
        self._needs_warning = False

    # Custom validators
    self.validators: list[Callable] = []

Datetime(*, gt=None, ge=None, lt=None, le=None, **kwargs)

Bases: FieldBase

Datetime field type for datetime.datetime values.

Examples:

>>> from datetime import datetime
>>> from flycatcher import Schema
>>> class EventSchema(Schema):
...     created_at: datetime
...     updated_at: datetime | None = None
Source code in src/flycatcher/fields.py
def __init__(
    self,
    *,
    gt: datetime | None = None,  # Greater than
    ge: datetime | None = None,  # Greater than or equal
    lt: datetime | None = None,  # Less than
    le: datetime | None = None,  # Less than or equal
    **kwargs,
):
    super().__init__(**kwargs)
    self.gt = gt
    self.ge = ge
    self.lt = lt
    self.le = le

get_polars_constraints()

Generate Polars validation expressions.

Source code in src/flycatcher/fields.py
def get_polars_constraints(self) -> list[tuple[Any, str]]:
    """Generate Polars validation expressions."""
    constraints = list(super().get_polars_constraints())
    assert self.name is not None  # Checked by base class
    col = pl.col(self.name)

    if self.gt is not None:
        constraints.append(
            (col > self.gt, f"{self.name} must be > {self.gt.isoformat()}")
        )
    if self.ge is not None:
        constraints.append(
            (col >= self.ge, f"{self.name} must be >= {self.ge.isoformat()}")
        )
    if self.lt is not None:
        constraints.append(
            (col < self.lt, f"{self.name} must be < {self.lt.isoformat()}")
        )
    if self.le is not None:
        constraints.append(
            (col <= self.le, f"{self.name} must be <= {self.le.isoformat()}")
        )

    return constraints

get_pydantic_field_kwargs()

Return kwargs for Pydantic Field().

Source code in src/flycatcher/fields.py
def get_pydantic_field_kwargs(self) -> dict[str, Any]:
    """Return kwargs for Pydantic Field()."""
    kwargs: dict[str, Any] = {}
    if self.gt is not None:
        kwargs["gt"] = self.gt
    if self.ge is not None:
        kwargs["ge"] = self.ge
    if self.lt is not None:
        kwargs["lt"] = self.lt
    if self.le is not None:
        kwargs["le"] = self.le
    return kwargs

Date(*, primary_key=False, nullable=False, default=_MISSING, description=None, unique=False, index=False, autoincrement=None)

Bases: FieldBase

Date field type for datetime.date values.

Examples:

>>> from datetime import date
>>> from flycatcher import Schema, Date
>>> class BookingSchema(Schema):
...     check_in: date
...     check_out: date
Source code in src/flycatcher/fields.py
def __init__(
    self,
    *,
    primary_key: bool = False,
    nullable: bool = False,
    default: Any = _MISSING,
    description: str | None = None,
    unique: bool = False,
    index: bool = False,
    autoincrement: bool | None = None,
):
    self.primary_key = primary_key
    self.nullable = nullable
    self.default = default
    self.description = description
    self.unique = unique
    self.index = index
    self.autoincrement = autoincrement
    self.name: str | None = None  # Set by Schema metaclass

    # Warn about ambiguous configuration
    if nullable and default is not _MISSING:
        # Defer warning until name is set by metaclass
        self._needs_warning = True
    else:
        self._needs_warning = False

    # Custom validators
    self.validators: list[Callable] = []