Metadata-Version: 2.5
Name: stalegate
Version: 0.1.0
Summary: Refuse to read stale data. Track which outputs went stale when code changed, and which docs quoted them.
Project-URL: Homepage, https://github.com/Taisui9/stalegate
Project-URL: Issues, https://github.com/Taisui9/stalegate/issues
Author: Taisui9
License: MIT
License-File: LICENSE
Keywords: cache-invalidation,data-pipeline,documentation,drift,lineage,provenance,reproducibility,staleness
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.9
Requires-Dist: tomli>=2.0; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# stalegate

[![tests](https://github.com/Taisui9/stalegate/actions/workflows/test.yml/badge.svg)](https://github.com/Taisui9/stalegate/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/stalegate.svg)](https://pypi.org/project/stalegate/)
[![Python versions](https://img.shields.io/pypi/pyversions/stalegate.svg)](https://pypi.org/project/stalegate/)

**Refuse to read stale data.**

You change a function. Three CSVs in `_output/` were produced by code that
included that function. Nothing rebuilds them, nothing warns you, and the
analysis script you run tomorrow reads them as if they were current. The
paragraph in your report that quotes a number from one of them is now wrong,
and nothing on earth is going to tell you.

`stalegate` closes that loop with two calls:

```python
import stalegate
import pandas as pd

df = pd.read_csv(stalegate.path("table.csv"))   # read: only through the gate
...
result.to_csv(out, index=False)
stalegate.stamp("table.csv")                    # write: stamp immediately
```

`path()` is the only way to get at an artifact. If the code that produces
`table.csv` has changed since it was last stamped, `path()` raises instead of
handing you a path:

```
stalegate.errors.StaleArtifact: refusing to read 'table.csv' - it is code-drift
    helpers.py: clean, normalise
    pkg/inner.py: raw

  Regenerate it:
    python build.py
```

It tells you *which symbols* changed, not just which files.

## Why not just check timestamps

Because timestamps answer the wrong question. `stalegate` parses your code and
hashes each top-level definition on its own, so:

- editing a docstring or a comment invalidates nothing;
- changing one function names *that function* in the report;
- a module you declare as `non_data` (plotting, logging, CLI glue) never
  invalidates anything, no matter how much it churns;
- imports are followed transitively, through packages and relative imports, so
  a change three modules deep still counts.

Fingerprints default to content hashes rather than size+mtime, because mtime is
not stable across machines, CI runners, or a fresh `git clone` — and a gate
that cries wolf after every checkout is a gate people learn to ignore.

## The part almost nothing else does: prose

Data tools track lineage between tables. What they do not track is the sentence
in `report.md` that says *"the effect was +2.32 per month"*, written from a
table that has since been regenerated. Nothing rebuilds prose, nothing tests
it, and nothing tells you it went stale.

Register which sections quote which artifacts:

```bash
stalegate docs suggest      # scans your docs for artifact filenames
stalegate docs register notes/report.md "Results" table.csv
```

From then on, stamping speaks up at the moment it matters:

```
  stalegate: table.csv moved - 2 documented section(s) quote it:
      notes/report.md / Results
      notes/report.md / Appendix
      Re-read them, then: stalegate docs ack <doc> <section> --why ...
```

And `stalegate docs status` exits non-zero while anything is unconfirmed, so
CI can hold the line.

## Install

```bash
pip install stalegate
```

Python 3.9+. No runtime dependencies beyond `tomli` on Python < 3.11.

## Configure

```bash
stalegate init
```

There is a complete runnable walkthrough in
[`examples/demo/`](examples/demo/) — build, break the rule, watch the gate
refuse, rebuild, and hear which paragraph went stale. Four minutes.

Then describe your project in `stalegate.toml`:

```toml
[stalegate]
output_dir  = "_output"   # where generated artifacts land
source_dirs = ["."]       # where your own modules live ("src" also works)
non_data    = ["plots.py"]
fingerprint = "content"   # or "mtime" for very large artifacts

[artifacts."table.csv"]
by      = "build_table.py"
parents = []

[artifacts."summary.csv"]
by      = "summarize.py"
parents = ["table.csv"]      # a child can never be fresher than its parent

[artifacts."reference.csv"]
by     = "build_reference.py"
frozen = "Snapshot kept on purpose as a comparison baseline. Do not rebuild."

[docs]
dirs = ["notes"]
glob = "**/*.md"
```

State lives in `.stalegate/` in your project as readable JSON, so it diffs in
review alongside the code it describes.

## Commands

| Command | What it does |
|---|---|
| `stalegate status` | one line per artifact; exit 1 if anything is blocked |
| `stalegate explain NAME` | why one artifact is blocked, and how to fix it |
| `stalegate adopt NAME --why W` | "I checked; it matches the current code" |
| `stalegate ack NAME f.py:sym --why W` | forgive one symbol, keep gating the rest |
| `stalegate docs status` | registered sections that need re-reading |
| `stalegate docs suggest` | scan docs, print the `register` commands |

Every escape hatch demands a reason in writing. `--stale-ok` (or
`STALEGATE_OK=1`) bypasses the gate for one run and prints a loud warning; use
it when you know what you are doing and do not quote the results.

## What it is not

- Not a build system. It refuses to give you stale data; it will not rebuild
  it for you. Use `make`, `dbt`, or `snakemake` for that — they compose fine.
- Not a data-quality checker. It says nothing about whether the numbers are
  *good*, only about whether they came from the code you are running now.
- Not a lineage graph for a warehouse. It is for the pile of scripts and CSVs
  that a real analysis actually lives in.

## Where it came from

It was extracted from a private research project, where the first day it ran it
caught two genuine regressions: an output whose generating rule had changed
under it, and a set of written-up conclusions quoting numbers from a table that
had since been rebuilt. It is not a toy built to be published.

## License

MIT
