Skip to content

Columns

What one column declares, and the pieces that make up a declaration.

ColSpec

ColSpec dataclass

ColSpec(dtype: DataType | type[DataType], col_name: str | None = None, seed_name: str | None = None, nullable: bool = False, bounds: Bound[Any] | tuple[Any, Any] | list[Any] | None = None, tags: str | Sequence[str] | None = (), unique: bool = False, null_probability: float = _DEFAULT_NULL_PROBABILITY, string_length: Bound[int] | tuple[int, int] | list[int] | None = None, list_length: Bound[int] | tuple[int, int] | list[int] | None = None, fields: Mapping[str, ColSpec] | None = None, format: str | None = None, pattern: str | None = None, distribution: str | None = None, distribution_params: dict[str, float] | None = None, choices: Sequence[Any] | dict[Any, float] | None = None, weights: Sequence[float] | None = None, rules: Sequence[ColRule] = (), validators: Check | Expr | Pred | Sequence[Check | Expr | Pred] | None = ())

One column's declaration: its type, and every claim made about its values.

A ColSpec is what generate() samples from and what validate() checks against, so each field below is a claim both sides read.

Parameters:

Name Type Description Default
dtype DataType | type[DataType]

The data type of the column.

required
col_name str | None

Overrides the column's name in the generated/validated DataFrame. Declaring columns as class attributes on a FrameSpec requires a valid Python identifier, which cannot contain spaces or other special characters -- col_name lets the attribute keep a clean Python name (unit_price) while the actual column is named whatever the data uses ("Unit Price"). Everything else that refers to this column by name -- ColRule, unique_together, tags lookups, validate() -- uses col_name, not the attribute name.

None
seed_name str | None

The name the column's seed is derived from, when it is not the column's own. Generation seeds each column from the frame seed and the column name, so renaming a column changes the values it produces; a column declared with seed_name="old" keeps producing the data it did as "old", its rules included. Nothing is promised across polspec versions. Two columns of one spec cannot share a seed name, nor may one name another column: they would draw identical values.

None
nullable bool

Whether the column allows null values.

False
bounds Bound | tuple | list | None

The inclusive range of values allowed in the column, as a Bound or a 2-sequence. Only supported for numeric and temporal data types. Either endpoint may be None to leave that side unconstrained -- bounds=(0, None) for a non-negative column, bounds=(None, 0) for a non-positive one.

An open end means different things to the two consumers of this field, deliberately. validate() treats it as genuinely unconstrained and omits that half of the check. generate() cannot sample an unbounded range, so it falls back to the same default it would use with no bounds at all -- bounds=(0, None) on Int64 generates 0..1,000,000 while validating any value >= 0. This mirrors how bounds=None already behaves rather than adding a third rule.

None
tags str | Sequence[str]

Tag or tags classifying the column, for later selection.

()
unique bool

Whether values in the column must be distinct. Generation draws the column without replacement; nulls are exempt. Cannot be combined with weights, a non-uniform distribution, or rules, none of which survive a draw without replacement.

False
null_probability float

Probability of a value being null. Must be between 0 and 1, and only has effect alongside nullable=True -- so that turning nullability off does not also require deleting the rate beside it. Declaring a rate of your own without nullable=True warns, since that reads as asking for nulls rather than as a leftover.

_DEFAULT_NULL_PROBABILITY
string_length Bound | tuple[int, int] | list[int] | None

The inclusive range of string lengths, where that applies.

None
list_length Bound | tuple[int, int] | list[int] | None

For a List column, the inclusive range of elements a value holds; generation defaults to 0..5. On a List or Array column every other field that describes a value -- bounds, choices, weights, format, pattern, string_length, distribution -- describes each element; nullable and null_probability describe the list itself, and generation never puts a null inside one. An Array takes its length from the dtype and refuses list_length.

None
fields Mapping[str, ColSpec] | None

For a Struct column, what is claimed about each field's values: a ColSpec per field, keyed by name. The dtype is the schema -- every field's name and type comes from it -- and fields is what is claimed about the values within it, so a struct of twenty fields where one needs bounds spells one field. A field the mapping omits is generated from its dtype alone.

A field is a value, not a column: unique, rules, validators, seed_name and col_name are refused on one, since each is a statement about a column among columns. Everything that describes a value is allowed -- nullable included, which says whether the field may be null inside a struct that is present -- and fields too, so a struct may nest to any depth. A finding about a field names it: point.lat.

A List or Array of a Struct takes fields too: it describes the element, as bounds and format already do.

None
format str | None

The shape a String column's values take, by name: "uuid4", "email", "ipv4", "ipv6", "mac", "hostname", "iso_country" or "iso_currency". Generation fills the column from that format's own sampler and validation checks every value against it, so a column carrying format="email" is generated to satisfy its own spec. A format owns the column's whole domain: it cannot be combined with choices or string_length, and only a String column can carry one. What it promises is syntax -- an address that is well-formed, not one that is deliverable.

None
pattern str | None

A regular expression every value of a String column must match, checked by validation only. Generation does not read it: a String column with a pattern is filled with ordinary random text, and the round trip that holds for every other field does not hold here -- the same boundary as validators. Prefer format for a shape polspec can generate; use pattern for a shape it cannot. Cannot be combined with format, which already is a pattern with a sampler. Polars regex syntax; a pattern that does not compile is refused at declaration.

None
distribution str | None

The name of the probability distribution for the column's values (e.g. "uniform", "normal").

None
distribution_params dict[str, float] | None

Parameters specific to the chosen distribution.

None
choices tuple | list | dict | None

A finite set of allowed values. A dict maps each choice to its weight.

None
weights tuple[float, ...] | list[float] | None

Weights associated with choices, biasing selection probabilities.

None
rules tuple[ColRule, ...]

Rules (ColRule) that overwrite the column's values on the rows their condition matches.

()
validators Check | Expr | Pred | Sequence[...] | None

A single-column business rule, or several: each either a pl.Expr boolean predicate (referencing only this column) or a Check (for a custom name, description or null handling). Unlike FrameSpec.__checks__, these travel with the column's own declaration.

()

Examples:

>>> ColSpec(pl.Int64, bounds=(1, 100), nullable=True, null_probability=0.1)
>>> ColSpec(pl.String, choices=["NEW", "PAID"], weights=[3.0, 1.0])

value_dtype property

value_dtype: DataType

The dtype each value has: the element's for a List or Array column, the column's own otherwise. Every field that describes a value is checked against this.

Bound

Bound dataclass

Bound(min: T | None, max: T | None)

An inclusive [min, max] range, used for numeric bounds, temporal ranges, and string lengths.

Either endpoint may be None, meaning that side is unconstrained -- see ColSpec.bounds, the only field that accepts an open end.

is_open property

is_open: bool

True when either endpoint is unconstrained.

is_open_both property

is_open_both: bool

True when neither endpoint constrains anything.

closed

closed() -> tuple[T, T]

Both endpoints of a bound that has both, as string_length does.

ColRule

ColRule dataclass

ColRule(when: Pred, choices: tuple, weights: tuple[float, ...] | None = None)

Restricts a column's generated values on rows where when matches.

Applied as a pass over the generated frame: rows where when matches get a value resampled uniformly (or according to weights) from choices instead of whatever was freely generated for them. Multiple rules on the same column are checked in declaration order, first match wins (like SQL CASE/WHEN).

when is evaluated against the frame as it stands when the rule runs, and the passes run in dependency order: a rule keyed on a column that another rule or a foreign key rewrites sees the rewritten values -- the same values validation checks the rule against. Two columns whose rules each read what the other writes have no such order and are rejected at declaration.

when is a predicate built with polspec.col(), not an arbitrary polars expression, so that every rule can round-trip through a spec file:

ColRule(when=col("region") == "UK", choices=["RoyalMail"])
ColRule(when=col("region").is_in(["US", "EU"]) & (col("qty") > 10), choices=["UPS"])

Check

Check dataclass

Check(expr: Expr | Pred, name: str | None = None, description: str | None = None, ignore_nulls: bool = True, pred: Pred | None = None)

A declarative multi-column validation constraint evaluated as a Polars boolean expression.

Parameters:

Name Type Description Default
expr Expr | Pred

The boolean condition each row must satisfy: a Polars expression, or a predicate built with polspec.col(). A predicate can be written to a YAML or Python spec file; a raw expression cannot.

required
name str | None

A human-readable identifier for the check constraint (e.g. 'total_gte_subtotal'). If omitted, defaults to the string representation of the expression.

None
description str | None

An optional description detailing the business logic or rationale for this check.

None
ignore_nulls bool

Whether rows evaluating to null in the check condition are considered valid (standard SQL CHECK constraint semantics). If False, null results are treated as failures.

True

Examples:

>>> check = Check(pl.col("total") >= pl.col("subtotal"), name="total_gte_subtotal")