Skip to main content

runprov

Records what a Python script actually read, wrote and ran — and writes that record into the result file itself.

A hand-maintained log describes what an author believed they did. It cannot describe what the program did. runprov records the reads and writes as they happen, and writes the record in two forms that need no software to read: a YAML log whose digests are in sha256sum's own format, and a comment header inside the artifact.

One command, runprov verify, re-derives every recorded digest and reports whether a result still follows from the inputs it was made from — or has itself been edited — reading only the file. No database, no history, no network.

  • Zero runtime dependencies. Python 3.10+.
  • Observed, not declared. A CPython audit hook sees every open, including those made by a library nobody thought to instrument; unregistered reads are recorded rather than ignored.
  • The record does not depend on the tool that wrote it. With the package uninstalled, cat reads the history, grep finds every run that touched a file, and sha256sum -c verifies the digests.
pip install runprov

Adoption is one call

run.input(p) returns the path it was given, so it is written on the way to the open that was already there:

from runprov import Run, configure

configure(root=".")

with Run("summarise", {"threshold": 5}) as run:
    with open(run.input("data/measurements.tsv"), encoding="utf-8") as fh:
        rows = fh.read().splitlines()
    with run.open_output("results/summary.tsv") as out:
        out.write(f"n\t{len(rows)}\n")
    run.note("rows_read", len(rows))

The artifact then carries its own provenance, as a comment block above its first line:

# provenance — this artifact and what produced it
#   script     : summarise
#   commit     : b02d309
#   body       : d8c46f515971fb75
#   inputs (1), content digest:
#     17ffe7054ecf0cc8  data/measurements.tsv

Inside the script, and watching it run

@run.step  # digests what a function received and returned
def normalise(rows, factor=1.0):
    return [r * factor for r in rows]

A digest says a file changed; this says an argument changed. Values that cannot be canonically serialised are recorded as UNDIGESTIBLE:<type> — never a repr, never a pickle.

On Python 3.12+ it also records which of your own functions ran, and how often, with no decorator: sys.monitoring, scoped to code under the project root, and bounded — the observer stops itself after 50 000 calls and the record says when. configure( auto_steps="arguments") additionally digests the distinct argument sets each function saw.

Every record carries an observation block naming what the run was able to observe, so a record with no steps is distinguishable from one made where steps could not be observed.

[00:00] read  data/m1.tsv  1541e29a8301ba21
[00:02] still running — last: read data/m1.tsv
[00:03] wrote out.tsv      c533232884b32c60

Progress is on when stderr is a terminal and off otherwise; the heartbeat fires only after silence, so a run producing events steadily never beats. Both are configured once, never per script. run.open_output(p, record_header=True) records the column names it just wrote.

Checking it later

$ runprov verify results/summary.tsv
OK      results/summary.tsv

# after one line is appended to the input:
$ runprov verify results/summary.tsv
STALE   results/summary.tsv
        STALE  data/measurements.tsv
        (pinned 17ffe7054ecf0cc8, now a3d2c39b016f6a7e)

OK, STALE, GONE and ALTERED are the four verdicts. Also available: runprov log (the run history as YAML or a table), runprov show (what one run did), runprov capture (record a script that has no runprov calls in it at all), and runprov export (RO-Crate and W3C PROV-JSON).

What it is not

It records; it does not audit — it cannot tell you that a registered read was the read that mattered. @run.step digests what crossed a function's boundary — arguments in, result out — and not the dataflow between statements, which is what noWorkflow reports and what requires rewriting the AST, at the cost of changing the program it observes.

It identifies versions; it does not store them. A digest says an input changed, and says which version a result was built from — it cannot give those bytes back. That is DVC's job, and the two compose: DVC stores content, runprov records which content a run actually read.

Provenance written into a scientific artifact is not new either: SAM/BAM @PG has carried program, version and command line for over a decade. What is narrower and true here is a format-agnostic pin carrying the content digest of every input, plus a checker that re-derives them from the artifact alone.

Documentation

The full documentation is one long README in the repository — the complete API, every supported format, the failure each design decision came from, and what has actually been run:

https://github.com/Taylor-Nicole/runprov

Why it exists, and how it compares WHY.md
Design decisions docs/adr/
Changes CHANGELOG.md
Security policy SECURITY.md

Licence

BSD 3-Clause. Copyright (c) 2026 Assistance Publique – Hôpitaux de Paris (AP-HP), Hôpital Henri-Mondor, and Taylor Thompson.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runprov-0.2.0.tar.gz (696.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runprov-0.2.0-py3-none-any.whl (238.0 kB view details)

Uploaded Python 3

File details

Details for the file runprov-0.2.0.tar.gz.

File metadata

  • Download URL: runprov-0.2.0.tar.gz
  • Upload date:
  • Size: 696.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for runprov-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2d92e09da07f06bcd528bed761e0612ea7c4ca432418b266ee255334e067cbb1
MD5 72de24e6f219ee9d6ef92b29703b6c43
BLAKE2b-256 2b9750dea25ea54f443387dd423430f092a7387d82844f6171c8acf48421d9d5

See more details on using hashes here.

Provenance

The following attestation bundles were made for runprov-0.2.0.tar.gz:

Publisher: publish.yml on Taylor-Nicole/runprov

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file runprov-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: runprov-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 238.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for runprov-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5694737d11a10e4722b976f81944d64f43ad3c5da4d5057e08e0a107b288b80c
MD5 8aab7a34d434ccce846d358ba9253f40
BLAKE2b-256 94da35a85a5db4060271f29075556711816673cfaab0d6fef86536ec36de0972

See more details on using hashes here.

Provenance

The following attestation bundles were made for runprov-0.2.0-py3-none-any.whl:

Publisher: publish.yml on Taylor-Nicole/runprov

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

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