A worker kneels at a Python-branded validation machine labeled VALIDATING, sorting boxes from a conveyor belt into two stacks marked PASS and REVIEW alongside wooden crates.

Validating Data With Pointblank in Python

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 onto pb.Validate and runs when you call .interrogate().
  • Thresholds turn a pass/fail check into warning, error, and critical tiers based on the fraction of failing rows.
  • Actions trigger 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 its type argument.
  • A plan stored as YAML runs through the pb command-line tool, where --fail-on critical returns 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 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:

Language: Windows PowerShell
PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Language: Shell
$ curl -LsSf https://astral.sh/uv/install.sh | sh

If you don’t have curl installed on your system, then you can use wget as shown below:

Language: Shell
$ wget -qO- https://astral.sh/uv/install.sh | sh

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.

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:

Language: Shell
$ uv run --with 'pointblank[pl]' python

Then load the dataset and print the three columns you’ll validate:

Language: Python
>>> 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:

  • c is an integer column that the plan requires to be non-null, and two of its rows are null.
  • d is a numeric column that the plan requires to fall between 0 and 5000, and one row, 9999.99, falls outside that range.
  • f is a categorical column that the plan limits to the labels low, mid, and high, 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:

Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

Locked learning resources

The full article is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

About Rohit Goswami

Rohit Goswami is a researcher and open-source contributor passionate about scientific computing and high-performance Python. He works on F2PY and NumPy, helping bridge Python with Fortran and is an advocate for research software engineering.

» More about Rohit

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

What Do You Think?

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Become a Member to join the conversation.

Keep Learning