Skip to main content

polspec

One schema for your Polars data. Generate it, validate it, watch it drift.

PyPI Python Tests License

Your tests need fixtures, and your pipeline needs a contract. Written separately, they drift apart. polspec lets you write the schema once, as a Polars class, and use it for both: realistic data to test against, and validation of the data that turns up.

import datetime as dt

import polars as pl
from polspec import ColSpec, FrameSpec

class Orders(FrameSpec):
    order_id = ColSpec(pl.Int64, bounds=(1, 10**9), unique=True)
    status   = ColSpec(pl.Enum(["NEW", "PAID", "SHIPPED"]), weights=[0.2, 0.5, 0.3])
    total    = ColSpec(pl.Float64, bounds=(0, 5_000), distribution="lognormal",
                       distribution_params={"mean": 4, "std": 1})
    placed   = ColSpec(pl.Date, bounds=(dt.date(2025, 1, 1), dt.date(2025, 12, 31)),
                       nullable=True, null_probability=0.05)
    email    = ColSpec(pl.String, format="email")

orders = Orders.generate(1_000_000, seed=42)   # a million rows in ~12 ms
Orders.validate(orders)                        # ~27 ms; raises on any breach
┌───────────┬────────┬────────────┬────────────┬───────────────────────┐
│ order_id  ┆ status ┆ total      ┆ placed     ┆ email                 │
│ i64       ┆ enum   ┆ f64        ┆ date       ┆ str                   │
╞═══════════╪════════╪════════════╪════════════╪═══════════════════════╡
│ 830916752 ┆ PAID   ┆ 9.187332   ┆ 2025-12-30 ┆ dupebca@xdcbivwq.dev  │
│ 366447256 ┆ PAID   ┆ 19.02604   ┆ 2025-11-28 ┆ hinro@ewzc.com        │
│ 922459406 ┆ PAID   ┆ 336.930617 ┆ 2025-04-11 ┆ jp5uo7@mgayhywoxn.com │
└───────────┴────────┴────────────┴────────────┴───────────────────────┘

What you get

  • Fast. The generator is a Rust extension that fills columns in parallel. validate() compiles every check on every column into one Polars aggregation, so a wide table costs about what a narrow one does. Timings here are from a laptop; more are in Comparison.
  • Lazy and streaming. scan() is a LazyFrame: filter and project it like any other. The sinks write Parquet, IPC, CSV or NDJSON in batches, never holding the frame in memory, and a unique column stays unique across every batch.
  • Every Polars dtype. Integers to 128 bits, Decimal, time-zoned Datetime, Enum, Categorical, and List, Array and Struct nested to any depth (plus Map on Polars 2). Bounds, choices with weights, distributions, formats such as email and uuid4, uniqueness, nulls and NaNs are all declared per column.
  • Tables that relate. Foreign keys within and across specs, conditional rules, composite keys and hierarchies are generated to hold, and validated. Checks written as Polars expressions are enforced at validation.
  • Validation as data. inspect() returns every finding at once, with the offending rows a lazy filter away, and splits a frame into what passed and what to quarantine.
  • Learn from real data. profile() turns a frame into a spec, finding formats, frequencies, distributions and null rates; synthesize() makes a stand-in with the same shape.
  • Drift with statistics behind it. A move is reported only when it is both significant and large, as breaking or compatible.
  • Specs as files and docs. Save and load specs as YAML or Python. Render a Markdown data dictionary or a Mermaid ER diagram.
  • A CLI for your pipeline. polspec validate, generate, drift and synthesize, with exit codes for CI.
  • Deterministic. The same seed gives the same data on every Polars from 1.39 to 2.x, checked case by case in CI.

A quick tour

Generate lazily

scan() returns a LazyFrame, so Polars pushes filters and projections into it:

paid = (
    Orders.scan(10_000_000, seed=1)
    .filter(pl.col("status") == "PAID")
    .select("order_id", "total")
    .collect()                                            # ~0.2 s
)
Orders.sink_parquet("orders.parquet", 1_000_000, seed=1)  # streamed in batches

Validate and quarantine

inspect() never raises: it reports every finding, with the rows behind it.

broken = orders.with_columns(
    pl.when(pl.col("order_id") % 1_000 == 0).then(-1.0).otherwise("total").alias("total")
)
report = Orders.inspect(broken)          # 1 finding: 1,053 totals out of bounds
clean = report.passing_rows().collect()  # the other 998,947 rows

Profile real data

from polspec import profile, synthesize

spec = profile(orders.head(100_000))
spec["total"].distribution                         # 'lognormal', fitted
fake = synthesize(orders.head(100_000), seed=1)    # a stand-in with the same shape

Catch drift

print(Orders.drift(orders.filter(pl.col("status") != "NEW")))
Drift: 0 breaking, 2 compatible, DataFrame against 'Orders'
  - [compatible] Column 'status': 1 of 3 declared value(s) never appear: ['NEW']
  - [compatible] Column 'status': frequencies moved by 20.0% over 800,231 value(s): ...

From the command line

polspec schema infer orders.csv -o orders_spec.py
polspec validate orders_spec.py orders.csv --failing rejected.csv
polspec drift orders_spec.py orders.csv --markdown
polspec generate orders_spec.py -n 1000000 -o fixture.parquet --seed 1

Install

uv add polspec           # or: pip install polspec

polspec works with Polars 1.39 and later, Polars 2 included, on Python 3.12 to 3.14. Wheels are published for Linux (x86_64, aarch64), macOS (Intel and Apple silicon) and Windows (x86_64), so you don't need a Rust toolchain.

Pre-1.0. The API is settling toward a 1.0 with written stability promises. Until then, see Roadmap and stability, and Known limitations for what polspec does not do. Each limitation there is backed by a test that fails the moment it stops being true.

Documentation

maxwellb13.github.io/polspec: start with Getting started, find a task among the how-to guides, or look a name up in the API reference.

Contributing

Setting up a checkout, the checks CI runs and the release process are in CONTRIBUTING.md. What changed, and when, is in CHANGELOG.md.

License

MIT.

Metadata

Release files for polspec 0.18.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polspec 0.18.0
File Size Uploaded Provenance
polspec-0.18.0.tar.gz 704.2 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for polspec 0.18.0
File
polspec-0.18.0-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details
polspec-0.18.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 abi3 Linux glibc 2.17+ x86-64 Details
polspec-0.18.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 abi3 Linux glibc 2.17+ ARM64 Details
polspec-0.18.0-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details
polspec-0.18.0-cp312-abi3-macosx_10_12_x86_64.whl CPython 3.12 abi3 macOS 10.12+ x86-64 Details

Total release size: 30.2 MB

Release files / polspec-0.18.0.tar.gz

Download URL polspec-0.18.0.tar.gz
Size 704.2 kB
Tags Source
SHA-256 checksum
How to use checksums
4c9ba54944d42e0954328bb1a4e53ce657713a65751a6a128f2635925d044c8e
BLAKE2b-256 checksum
How to use checksums
5656eaccb27b590d09dc2bb77e6f2cd98f56815edaf336adde14134bd87431a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / polspec-0.18.0-cp312-abi3-win_amd64.whl

Download URL polspec-0.18.0-cp312-abi3-win_amd64.whl
Size 6.4 MB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
b008ec5abe990d5befbf1aa844e5ab56f9c8b580ba0fb37ca16b5503bbb96d43
BLAKE2b-256 checksum
How to use checksums
4bdddee0b1618c2d624331677b3992a76c5798bee99cfe70a87cf7b05dde8bde
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / polspec-0.18.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL polspec-0.18.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 6.1 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
07ae5e86253fe145b90df6e3455f1e725b4471d7be382a1ed4b54102dd3563da
BLAKE2b-256 checksum
How to use checksums
f4e6824cd2b1bddd01d0306eb3ff1e79d68e4c54c108d039ca862c4366b1b97a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / polspec-0.18.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL polspec-0.18.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 5.6 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
18f37e67da2d81ba2393812c9cf240a678466544bedd70dd94e64cd4bc0428d2
BLAKE2b-256 checksum
How to use checksums
43a522e8a88808cc91d1c563b0fd3b0430e5adb949110d8cc51cbbab10a2f403
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / polspec-0.18.0-cp312-abi3-macosx_11_0_arm64.whl

Download URL polspec-0.18.0-cp312-abi3-macosx_11_0_arm64.whl
Size 5.4 MB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
02b873e937b5731b1179e6b67126207815ff41abd3bb2da4d4e7125b8c125909
BLAKE2b-256 checksum
How to use checksums
1c8372ab0bedd195da1fdc6eedf03e61fec99839afce0d82bd35b46705b95cff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / polspec-0.18.0-cp312-abi3-macosx_10_12_x86_64.whl

Download URL polspec-0.18.0-cp312-abi3-macosx_10_12_x86_64.whl
Size 6.0 MB
Tags CPython 3.12 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
1b0c413ee5a43b40b53a598284e72d3d55cbf3746055781df4ea725a7b1f059e
BLAKE2b-256 checksum
How to use checksums
72a9499f20a22b1e00fc8e0836186d40fd9bf9ce39cdee97714e4f3e769a7444
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Security for polspec 0.18.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for polspec-0.18.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.18.0 This release

6 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page