At 3 a.m., a scheduled data pipeline fails with an AssertionError, and the traceback can’t tell you whether the cause is one stray row or a systematic bleed that should halt the entire run. Pointblank, a Python data-validation library, sharpens that decision: it checks a Polars or pandas DataFrame against rules you declare, and each rule accumulates failures until it crosses a threshold you set.
When a rule trips, you learn its name, the offending column, and the fraction of rows that failed, and Pointblank’s pb command-line tool wires that signal into a continuous integration step.
By the end of this tutorial, you’ll understand that:
- A validation plan chains
col_vals_*()checks ontopb.Validateand runs when you call.interrogate(). Thresholdsturn a pass/fail check into warning, error, and critical tiers based on the fraction of failing rows.Actionstrigger messages or callbacks when a step crosses a threshold, letting a policy decide the outcome.get_sundered_data()routes the passing or failing rows out of an interrogated plan, depending on itstypeargument.- A plan stored as YAML runs through the
pbcommand-line tool, where--fail-on criticalreturns a nonzero exit code for CI.
Pointblank can also validate SQL tables in DuckDB or PostgreSQL directly, so it complements parse-time schema tools such as Pydantic rather than replacing them. In this tutorial, you’ll see where Pointblank overlaps with Pandera and Great Expectations, two other Python data-validation libraries, and when to choose each one.
To get the most out of this tutorial, you should feel comfortable reading basic Polars or pandas DataFrame code, running Python scripts from the command line, and working in a terminal with a package manager like uv or pip. A virtual environment helps keep those installs isolated.
To follow along, you’ll validate a small built-in dataset, add thresholds and actions, sunder a messier table of atom data, and finally move the whole plan into YAML.
Get Your Code: Click here to download the free sample code you’ll use to validate DataFrames with Pointblank, from severity thresholds and row sundering to YAML-driven checks.
Get Started With Pointblank in Python
Start by running one validation plan against a built-in dataset.
The examples use uv because it runs self-contained scripts that declare and manage their own dependencies through inline script metadata.
If you don’t already have uv, you can install it using the standalone installer by running the command below:
Now that you have uv, the script below declares pointblank[pl] as a dependency.
The [pl] suffix is a pip extra that pulls in Polars alongside Pointblank, so uv run pointblank_quickstart.py builds an isolated environment and runs the file in one step.
Note: If you’d rather not use uv, install the dependencies with python -m pip install 'pointblank[pl]' in a virtual environment and run each script with python instead.
Pointblank ships a small example table named small_table with thirteen rows and eight columns.
Its columns carry deliberately generic names, a through f alongside two date columns, which keeps the focus on the validation mechanics rather than any one domain.
The first script validates three of them, one for each common kind of check.
Since small_table has only thirteen rows, you can print the whole thing from the Python REPL and see every value before you run anything.
The scripts in this tutorial declare their own dependencies, but a bare REPL doesn’t, so launch one with Pointblank and Polars already available:
$ uv run --with 'pointblank[pl]' python
Then load the dataset and print the three columns you’ll validate:
>>> import pointblank as pb
>>> import polars as pl
>>> small_table = pb.load_dataset("small_table", tbl_type="polars")
>>> with pl.Config(tbl_rows=13):
... print(small_table.select(["c", "d", "f"]))
shape: (13, 3)
┌──────┬─────────┬──────┐
│ c ┆ d ┆ f │
│ --- ┆ --- ┆ --- │
│ i64 ┆ f64 ┆ str │
╞══════╪═════════╪══════╡
│ 3 ┆ 3423.29 ┆ high │
│ 8 ┆ 9999.99 ┆ low │
│ 3 ┆ 2343.23 ┆ high │
│ null ┆ 3892.4 ┆ mid │
│ 7 ┆ 283.94 ┆ low │
│ 4 ┆ 3291.03 ┆ mid │
│ 3 ┆ 843.34 ┆ high │
│ 2 ┆ 1035.64 ┆ low │
│ 9 ┆ 837.93 ┆ high │
│ 9 ┆ 837.93 ┆ high │
│ 7 ┆ 833.98 ┆ low │
│ 8 ┆ 108.34 ┆ low │
│ null ┆ 2230.09 ┆ high │
└──────┴─────────┴──────┘
You’ll write one rule for each of these three columns, listed in the same order as the table, with bounds chosen to illustrate the check rather than to describe real measurements:
cis an integer column that the plan requires to be non-null, and two of its rows are null.dis a numeric column that the plan requires to fall between0and5000, and one row,9999.99, falls outside that range.fis a categorical column that the plan limits to the labelslow,mid, andhigh, and every row already matches.
With the whole table in view, the failure counts you’ll see in a moment are no surprise: two null c values, one out-of-range d value, and a clean f column.
When you validate your own data, you’d swap these for your real columns and the thresholds your pipeline expects.
The script imports pointblank as pb, the convention you’ll see throughout the tutorial.
Each .col_vals_*() call returns the validation object, so the chained calls build up the rule list one method at a time:



