Skip to content

Validators API

Cross-field validation DSL for creating expressions that work with both Polars and Pydantic.

Field References

col(name)

Create a field reference for use in validator expressions.

Source code in src/flycatcher/validators/core.py
def col(name: str) -> FieldRef:
    """Create a field reference for use in validator expressions."""
    return FieldRef(name)

FieldRef(name)

Bases: _MathOpsMixin, _MembershipMixin

Reference to a field that can compile to Polars expressions and Python callables.

Source code in src/flycatcher/validators/core.py
def __init__(self, name: builtins.str):
    self.name = name

str property

Access string operations on this field.

dt property

Access datetime operations on this field.

to_polars()

Compile to Polars expression.

Source code in src/flycatcher/validators/core.py
def to_polars(self) -> pl.Expr:
    """Compile to Polars expression."""
    return pl.col(self.name)

to_python(values)

Evaluate in Python context.

Source code in src/flycatcher/validators/core.py
def to_python(self, values: Any) -> Any:
    """Evaluate in Python context."""
    if hasattr(values, self.name):
        return getattr(values, self.name)
    try:
        return values[self.name]
    except (KeyError, TypeError) as e:
        raise AttributeError(f"Field '{self.name}' not found in values") from e

is_null()

Check if the field value is null/None.

Source code in src/flycatcher/validators/core.py
def is_null(self) -> UnaryOp:
    """Check if the field value is null/None."""
    return UnaryOp("is_null", self)

is_not_null()

Check if the field value is not null/None.

Source code in src/flycatcher/validators/core.py
def is_not_null(self) -> UnaryOp:
    """Check if the field value is not null/None."""
    return UnaryOp("is_not_null", self)

abs()

Absolute value.

Source code in src/flycatcher/validators/core.py
def abs(self) -> UnaryOp:
    """Absolute value."""
    return UnaryOp("abs", self)

Expression Classes

BinaryOp(left, op, right)

Bases: _MathOpsMixin, _ExpressionMixin, _MembershipMixin

Binary operation that can compile to both Polars and Python.

Source code in src/flycatcher/validators/ops.py
def __init__(self, left: Any, op: builtins.str, right: Any):
    self.left = left
    self.op = op
    self.right = right

str property

Access string operations on this expression.

dt property

Access datetime operations on this expression.

to_polars()

Compile to Polars expression.

Source code in src/flycatcher/validators/ops.py
def to_polars(self) -> pl.Expr:
    """Compile to Polars expression."""
    left_expr = self._to_polars(self.left)
    right_expr = self._to_polars(self.right)
    return self.POLARS_OPS[self.op](left_expr, right_expr)

to_python(values)

Evaluate in Python context.

Source code in src/flycatcher/validators/ops.py
def to_python(self, values: Any) -> Any:
    """Evaluate in Python context."""
    left_val = self._to_python(self.left, values)
    right_val = self._to_python(self.right, values)
    return self.PYTHON_OPS[self.op](left_val, right_val)

abs()

Absolute value.

Source code in src/flycatcher/validators/ops.py
def abs(self) -> "UnaryOp":
    """Absolute value."""
    return UnaryOp("abs", self)

UnaryOp(op, operand, arg=None)

Bases: _MathOpsMixin, _ExpressionMixin, _MembershipMixin

Unary operation that can compile to both Polars and Python.

Source code in src/flycatcher/validators/ops.py
def __init__(self, op: builtins.str, operand: Any, arg: Any | None = None):
    self.op = op
    self.operand = operand
    self.arg = arg

str property

Access string operations on this expression.

dt property

Access datetime operations on this expression.

to_polars()

Compile to Polars expression.

Source code in src/flycatcher/validators/ops.py
def to_polars(self) -> pl.Expr:
    """Compile to Polars expression."""
    operand_expr = self._to_polars(self.operand)
    if self.op not in self.POLARS_OPS:
        raise ValueError(f"Unknown unary op: {self.op}")
    arg = self._prepare_polars_arg()
    return self.POLARS_OPS[self.op](operand_expr, arg)

to_python(values)

Evaluate in Python context.

Source code in src/flycatcher/validators/ops.py
def to_python(self, values: Any) -> Any:
    """Evaluate in Python context."""
    operand_val = self._to_python(self.operand, values)
    if self.op not in self.PYTHON_OPS:
        raise ValueError(f"Unknown unary op: {self.op}")
    arg_val = self._prepare_python_arg(values)
    return self.PYTHON_OPS[self.op](operand_val, arg_val)

abs()

Absolute value.

Source code in src/flycatcher/validators/ops.py
def abs(self) -> "UnaryOp":
    """Absolute value."""
    return UnaryOp("abs", self)

StringAccessor(expr)

Accessor for string operations on expressions.

Source code in src/flycatcher/validators/string.py
def __init__(self, expr: Any):
    self.expr = expr

StringOp(op, operand, arg=None)

Bases: _ExpressionMixin, _MembershipMixin

String operation that can compile to both Polars and Python.

Source code in src/flycatcher/validators/string.py
def __init__(self, op: builtins.str, operand: Any, arg: Any = None):
    self.op = op
    self.operand = operand
    self.arg = arg

str property

Access string operations on this expression (for chaining).

dt property

Access datetime operations on this expression (for chaining).

to_polars()

Compile to Polars expression.

Source code in src/flycatcher/validators/string.py
def to_polars(self) -> pl.Expr:
    """Compile to Polars expression."""
    operand_expr = self._to_polars(self.operand)
    if self.op not in self.POLARS_OPS:
        raise ValueError(f"Unknown string op: {self.op}")
    return self.POLARS_OPS[self.op](operand_expr, self.arg)

to_python(values)

Evaluate in Python context.

Source code in src/flycatcher/validators/string.py
def to_python(self, values: Any) -> Any:
    """Evaluate in Python context."""
    operand_val = self._to_python(self.operand, values)
    if self.op not in self.PYTHON_OPS:
        raise ValueError(f"Unknown string op: {self.op}")
    return self.PYTHON_OPS[self.op](operand_val, self.arg)

abs()

Absolute value (for numeric results like len_chars).

Source code in src/flycatcher/validators/string.py
def abs(self) -> UnaryOp:
    """Absolute value (for numeric results like len_chars)."""
    return UnaryOp("abs", self)

DateTimeAccessor(expr)

Accessor for datetime operations on expressions.

Source code in src/flycatcher/validators/datetime.py
def __init__(self, expr: Any):
    self.expr = expr

year()

Extract the year component from a datetime or date value.

Source code in src/flycatcher/validators/datetime.py
def year(self) -> "DateTimeOp":
    """Extract the year component from a datetime or date value."""
    return DateTimeOp("year", self.expr, None)

month()

Extract the month component (1-12) from a datetime or date value.

Source code in src/flycatcher/validators/datetime.py
def month(self) -> "DateTimeOp":
    """Extract the month component (1-12) from a datetime or date value."""
    return DateTimeOp("month", self.expr, None)

day()

Extract the day component (1-31) from a datetime or date value.

Source code in src/flycatcher/validators/datetime.py
def day(self) -> "DateTimeOp":
    """Extract the day component (1-31) from a datetime or date value."""
    return DateTimeOp("day", self.expr, None)

hour()

Extract the hour component (0-23) from a datetime value.

Source code in src/flycatcher/validators/datetime.py
def hour(self) -> "DateTimeOp":
    """Extract the hour component (0-23) from a datetime value."""
    return DateTimeOp("hour", self.expr, None)

minute()

Extract the minute component (0-59) from a datetime value.

Source code in src/flycatcher/validators/datetime.py
def minute(self) -> "DateTimeOp":
    """Extract the minute component (0-59) from a datetime value."""
    return DateTimeOp("minute", self.expr, None)

second()

Extract the second component (0-59) from a datetime value.

Source code in src/flycatcher/validators/datetime.py
def second(self) -> "DateTimeOp":
    """Extract the second component (0-59) from a datetime value."""
    return DateTimeOp("second", self.expr, None)

total_days(other)

Calculate the difference in days between this value and another.

Parameters:

Name Type Description Default
other datetime, date, or FieldRef

The value to compare against.

required

Returns:

Type Description
DateTimeOp

An expression evaluating to the number of days difference (float), positive if this value is later.

Source code in src/flycatcher/validators/datetime.py
def total_days(self, other: Any) -> "DateTimeOp":
    """
    Calculate the difference in days between this value and another.

    Parameters
    ----------
    other : datetime, date, or FieldRef
        The value to compare against.

    Returns
    -------
    DateTimeOp
        An expression evaluating to the number of days difference (float),
        positive if this value is later.
    """
    return DateTimeOp("total_days", self.expr, other)

DateTimeOp(op, operand, arg=None)

Bases: _ExpressionMixin, _MembershipMixin

Datetime operation that can compile to both Polars and Python.

This class represents datetime operations (like extracting year, month, or calculating differences) that work seamlessly in both Polars DataFrame validation and Pydantic row-level validation contexts.

Source code in src/flycatcher/validators/datetime.py
def __init__(self, op: str, operand: Any, arg: Any = None):
    self.op = op
    self.operand = operand
    self.arg = arg

dt property

Access datetime operations on this expression (for chaining).

abs()

Absolute value (for numeric results like differences).

Source code in src/flycatcher/validators/datetime.py
def abs(self) -> UnaryOp:
    """Absolute value (for numeric results like differences)."""
    return UnaryOp("abs", self)

ValidatorResult(result)

Wrapper for validator results supporting multiple formats.

Source code in src/flycatcher/validators/core.py
def __init__(self, result: Any):
    self.result = result

get_polars_validator()

Extract Polars validator as (expression, message) tuple.

Source code in src/flycatcher/validators/core.py
def get_polars_validator(self) -> tuple[pl.Expr, str]:
    """Extract Polars validator as (expression, message) tuple."""
    if isinstance(self.result, dict):
        if "polars" not in self.result:
            raise ValueError(
                "Dict validator must have 'polars' key. "
                f"Got keys: {list(self.result.keys())}"
            )
        polars_val = self.result["polars"]
        if isinstance(polars_val, tuple):
            return polars_val
        return (polars_val, "Validation failed")
    elif isinstance(self.result, tuple) and len(self.result) == 2:
        expr, msg = self.result
        if hasattr(expr, "to_polars"):
            return (expr.to_polars(), msg)
        elif isinstance(expr, pl.Expr):
            return (expr, msg)
        else:
            raise ValueError(
                f"Invalid expression in tuple: {type(expr).__name__}. "
                "Expected DSL expression or pl.Expr."
            )
    elif hasattr(self.result, "to_polars"):
        expr = self.result.to_polars()
        return (expr, "Validation failed")
    else:
        raise ValueError(
            f"Invalid validator result type: {type(self.result).__name__}. "
            "Expected dict, tuple of (expr, msg), or object with "
            "'to_polars' method."
        )

get_pydantic_validator()

Extract Pydantic validator callable, or None if not available.

Source code in src/flycatcher/validators/core.py
def get_pydantic_validator(self) -> Any | None:
    """Extract Pydantic validator callable, or None if not available."""
    if isinstance(self.result, dict):
        if "pydantic" not in self.result:
            logger.warning(
                "Dict validator does not have 'pydantic' key. "
                "This validator will only be used for Polars validation."
            )
            return None
        return self.result["pydantic"]
    elif isinstance(self.result, tuple) and len(self.result) == 2:
        expr, msg = self.result
        if hasattr(expr, "to_python"):

            def validator(values: Any) -> Any:
                try:
                    result = expr.to_python(values)
                    if not result:
                        raise ValueError(msg)
                    return values
                except ValueError:
                    raise
                except Exception as e:
                    raise ValueError(f"{msg}: {e}") from e

            return validator
        else:
            return None
    elif hasattr(self.result, "to_python"):

        def validator(values: Any) -> Any:
            try:
                result = self.result.to_python(values)
                if not result:
                    raise ValueError("Validation failed")
                return values
            except Exception as e:
                raise ValueError(f"Validation failed: {e}") from e

        return validator
    else:
        return None

has_pydantic_validator()

Check if Pydantic validator is available.

Source code in src/flycatcher/validators/core.py
def has_pydantic_validator(self) -> bool:
    """Check if Pydantic validator is available."""
    return self.get_pydantic_validator() is not None