Skip to main content

banzai

Lightweight package for functional data pipelines.

A piece of work is done when its output file exists. Everything else is bookkeeping around that.

  • A Unit is one piece of work's name: hive keys plus, for raw files, the file name. Unit(symbol="AAPL", day="2026-09-01", file="prices.csv").
  • A Dataset is a name in a store. It owns the path recipe: <name>/<keys>/<file>, relative to the store.
  • A Feed is your function that decides which units need doing right now. It returns a list of units.
  • A Job is your function that does one unit. It returns bytes or a polars frame. job.run(unit) writes that at the unit's path, overwriting. It never checks first. That's the feed's job.
  • tick() calls each job's feed and runs the job on every unit that comes back.

Nothing tracks state. Status is a listing of the store.

Install

uv add banzai-pipe            # local stores, polars frames
uv add banzai-pipe[s3]        # plus S3 via obstore
uv add banzai-pipe[joblib]    # plus a multi-core runner

Getting started

1. Pull daily price files from an API

One CSV per symbol per day. The source is immutable, so a unit is done once its file exists.

import datetime as dt
import os
import urllib.request

import banzai
from banzai import stores

SYMBOLS = ["AAPL", "MSFT", "NVDA"]
raw = banzai.Dataset("prices", stores.LocalStore("data/raw"))


@banzai.feed
def recent_days():
    # the last 7 days for every symbol, minus the ones already in the store
    today = dt.date.today()
    units = [
        banzai.Unit(symbol=s, day=today - dt.timedelta(days=i), file="prices.csv")
        for i in range(7)
        for s in SYMBOLS
    ]
    return [u for u in units if u not in raw.have(units)]


@banzai.job(raw, feed=recent_days)
def pull(unit):
    url = f"https://example.com/v1/{unit.symbol}/{unit.day}.csv"
    return urllib.request.urlopen(url).read()


if __name__ == "__main__":
    banzai.tick([pull])

Run it once and the store looks like this:

data/raw/prices/symbol=AAPL/day=2026-09-01/prices.csv
data/raw/prices/symbol=AAPL/day=2026-08-31/prices.csv
...

Run it again and nothing happens, because the feed sees every file is there. Delete one and the next run redoes exactly that one. Put it on a cron and it keeps the last week filled in. raw.have(units) is one listing, not one request per file, so it stays cheap at ten thousand units.

The path is the whole record. pull(unit) on its own just returns bytes, so you can call it in a notebook with no framework in the way.

2. Parse them into parquet

A second job reads the raw files and writes frames. Its feed is "raw units that have no parsed counterpart". Returning a polars frame makes the leaf <name>.parquet, written by polars' own writer.

import io

import polars as pl

parsed = banzai.Dataset("prices", stores.LocalStore("data/parsed"))


@banzai.feed
def unparsed():
    # every raw unit, re-keyed without the file, minus what parsed already has
    units = [
        banzai.Unit(symbol=k.split("symbol=")[1].split("/")[0], day=k.split("day=")[1].split("/")[0])
        for k in raw.keys()
    ]
    return [u for u in units if u not in parsed.have(units)]


@banzai.job(parsed, feed=unparsed)
def parse(unit):
    csv = raw.store.get(raw.path(banzai.Unit(**unit.fields, file="prices.csv")))
    return pl.read_csv(io.BytesIO(csv)).with_columns(pl.col("ts").str.to_datetime())


if __name__ == "__main__":
    banzai.tick([pull, parse])

Now the whole parsed dataset reads back as one lazy frame, with the keys as columns:

parsed.scan().filter(pl.col("symbol") == "AAPL").collect()

3. A source that revises files in place

Some sources rewrite a file and only tell you through a modified time. Put that time on the unit. A revision is then a new path, so "is it current" is the same existence check as "did I get it". Old versions stay.

@banzai.feed
def changed():
    listing = api.list_files()                       # [(name, modified_at), ...]
    units = [banzai.Unit(updated=ts, file=name) for name, ts in listing]
    return [u for u in units if u not in raw.have(units)]
raw/reports/updated=20260901T1204/A44.xml
raw/reports/updated=20260902T1031/A44.xml        # the revision; the first one is still there

Running on S3

Swap the store. Everything else is the same. prefix nests the store inside the bucket. The other kwargs go to obstore for bytes and to polars as storage_options for frames.

raw = banzai.Dataset("prices", stores.S3Store("my-bucket", prefix="raw", region="us-east-1"))

Running in parallel

tick takes a runner. A runner is runner(banzai_job, units) -> [path | Exception], one result per unit. A unit that fails doesn't stop the others: its exception comes back in its slot, tick logs it, and the tick carries on.

from banzai import runners

banzai.tick([pull], runner=runners.parallel)    # joblib, all local cores

tick returns a core.Outcome with written paths and failed as (job name, unit, exception). A failed unit is just a missing path, so the next tick's feed picks it up again.

For Modal, write a runner in your own code. banzai never imports Modal. try_run turns a job failure into a return value, and return_exceptions=True does the same for anything Modal itself raises, so both land in the unit's slot.

@app.function(image=image)
def run_unit(banzai_job: banzai.Job, unit: banzai.Unit) -> str | Exception:
    return banzai_job.try_run(unit)

def on_modal(banzai_job, units):
    return list(run_unit.map([banzai_job] * len(units), units, return_exceptions=True))

banzai.tick([pull], runner=on_modal)

Metadata

Release files for banzai-pipe 0.1.1

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

Source distribution (sdist)

Source distribution for banzai-pipe 0.1.1
File Size Uploaded Provenance
banzai_pipe-0.1.1.tar.gz 8.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for banzai-pipe 0.1.1
File Interpreter ABI Platform
banzai_pipe-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 19.3 kB

Release files / banzai_pipe-0.1.1.tar.gz

Download URL banzai_pipe-0.1.1.tar.gz
Size 8.7 kB
Tags Source
SHA-256 checksum
How to use checksums
bcbd822c92140d57126b9f6c8f98069a7a934e895aef3662299e9035f446280a
BLAKE2b-256 checksum
How to use checksums
e7b9c3b0b04fe67d0ed620832c976f6c39a78118077e3b3f6b77c03e23441121
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / banzai_pipe-0.1.1-py3-none-any.whl

Download URL banzai_pipe-0.1.1-py3-none-any.whl
Size 10.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eabb85058518d5c84415a5c746d2565c5f05ecdb24ff27cf5989bcf334677a1f
BLAKE2b-256 checksum
How to use checksums
0d2d97bd7b92f1b55cdecbbc07e2281929024de749bcee1afdf4f82a17a1aa63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for banzai-pipe 0.1.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.1.1 This release

2 release files No 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