pmprov
Analytic provenance tracking middleware for exploratory process mining notebooks.
pmprov automatically records every meaningful decision an analyst makes during a Jupyter or Marimo session — which functions were called, with what arguments, on which data, and how the data changed — without requiring any modification to analyst code.
How it works
Your cell code → AST rewriter → trace_step() → DuckDB/SQLite + Parquet
(transparent) (non-blocking)
Every top-level function call is intercepted at the AST level, wrapped with a lightweight tracer, and recorded as a node in a provenance tree. The analyst's variables contain exactly the same values they would without the middleware.
Getting started
Prerequisites: Python ≥ 3.13, uv
git clone https://github.com/alvarobernal2412/pmprov.git
cd pmprov
uv pip install -e ".[full]"
In a Jupyter notebook:
from tracker import init_jupyter, operation_type
import pandas as pd
import pm4py
# Register semantic labels for third-party functions (optional but recommended)
operation_type("data_loading", pd.read_csv)
operation_type("process_discovery", pm4py.discover_petri_net_inductive)
# Start tracking — one call, no other changes to your code
rt = init_jupyter(history_name="My analysis")
# Your normal analysis code below — provenance is captured automatically
event_log = pd.read_csv("data/log.csv", parse_dates=["time:timestamp"])
net, im, fm = pm4py.discover_petri_net_inductive(event_log)
Resuming a history: calling init_jupyter/init_marimo again with the same history_name (and the same db_path) resumes the prior history by default — your kernel restarted, but the analysis lineage picks up where it left off. Pass fresh=True to force a brand-new history instead. Calling with no history_name always starts fresh, unaffected by this. Note this isn't safe across two kernels running concurrently against the same db_path + history_name — they'd race on whose writes "win".
In a Marimo notebook:
Marimo support requires a one-time setup step. The middleware patches Marimo's AST compiler at Python startup via sitecustomize.py, which must be placed in your virtual environment (already in place if installed via uv).
Structural requirement: Marimo's reactive DAG is built from the original cell code before the AST rewriter runs. This means the rewriter cannot inject dependency edges automatically. To guarantee that init_marimo() runs before any tracked cell, combine your path setup and init_marimo() call in one cell and export all imports (pd, constants, etc.) from it. Downstream cells then depend on those names naturally, which implicitly orders them after init — no need to add rt to every cell's parameters.
# ── Setup + init (one cell) ───────────────────────────────────────────────────
@app.cell
def _():
import sys, pandas as pd
from pathlib import Path
from tracker import init_marimo, operation_type
# ... path setup ...
rt = init_marimo(history_name="My analysis")
operation_type("data_loading", pd.read_csv)
def settle():
rt.storage._executor.submit(lambda: None).result()
return pd, rt, settle, DATA_FILE # export everything downstream cells need
# ── Analysis cells — no `rt` needed in params ─────────────────────────────────
@app.cell
def _(DATA_FILE, pd): # depends on pd + DATA_FILE → init already ran
event_log = pd.read_csv(DATA_FILE, parse_dates=["time:timestamp"])
return (event_log,)
Run with:
uv run marimo run examples/marimo/provenance_demo.py
# or for interactive editing:
uv run marimo edit examples/marimo/provenance_demo.py
Note: Provenance capture works in Marimo. Some introspection features (interactive widget, manual checkout) are not yet validated in the Marimo environment.
Inspect the provenance graph:
rt.list_states() # table of all recorded steps
rt.show_graph() # static matplotlib tree
rt.show_graph_widget() # interactive Jupyter widget (Jupyter only)
Branch from a past state:
branch = rt.checkout("paste-state-id-here", branch_name="experiment-v2")
See examples/jupyter/provenance_demo.ipynb for a full Jupyter walkthrough and examples/marimo/provenance_demo.py for the Marimo equivalent, both using the RTFM dataset.
Optional extras
| Extra | What it adds |
|---|---|
duckdb |
DuckDB storage backend (preferred) |
parquet |
DataFrame artifact persistence via pyarrow |
graph |
show_graph() via networkx + matplotlib |
widgets |
show_graph_widget() interactive panel |
marimo |
Marimo notebook support |
full |
Everything above |
uv pip install -e ".[full]"
Project structure
models/ Pydantic v2 domain models (provenance entities)
tracker/ Runtime middleware (AST rewriter, tracer, storage)
examples/ Demo notebook + RTFM dataset helpers
Running tests
uv sync --all-extras --dev
uv run pytest
Git hooks (optional)
Enforces the test suite and conventional commit format (≤ 45 chars) on every commit:
uv run pre-commit install && uv run pre-commit install --hook-type commit-msg
Skip this step if you prefer to run checks manually.
Contributing
See CONTRIBUTING.md.
License
Apache 2.0 — see LICENSE.
Metadata
Release files for pmprov 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pmprov-0.5.0.tar.gz | 70.4 kB | Without provenance | Details for pmprov-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pmprov-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for pmprov-0.5.0-py3-none-any.whl |
Total release size: 149.4 kB
Release files / pmprov-0.5.0.tar.gz
| Download URL | pmprov-0.5.0.tar.gz |
|---|---|
| Size | 70.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8ab13234c6a0aef415b6bcccc10fee1ef625b28f5f9e1a6e43a13f48b4ab25af
|
|
BLAKE2b-256 checksum How to use checksums |
0dc36737e3901c8731b48a745cae5701cfb10e6381077a348fdb102559111f8f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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":true}
|
Release files / pmprov-0.5.0-py3-none-any.whl
| Download URL | pmprov-0.5.0-py3-none-any.whl |
|---|---|
| Size | 79.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
360e3299e82cd29bed522cea37909e98ec1c0309dd1acef20c877f0132d274f2
|
|
BLAKE2b-256 checksum How to use checksums |
46bcc86cd93c4f2de0526902281103e5d4b9fb4b07e44b20ff6df6df9919f76a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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":true}
|
Security for pmprov 0.5.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.
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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.