Metadata-Version: 2.4
Name: cash-lib
Version: 0.10.0
Summary: A Python cache that re-runs only what changed.
Project-URL: Homepage, https://github.com/galgtonold/cash
Project-URL: Documentation, https://cash-lib.readthedocs.io/
Project-URL: Bug Tracker, https://github.com/galgtonold/cash/issues
Project-URL: Changelog, https://github.com/galgtonold/cash/blob/main/CHANGELOG.md
Author: Cash Contributors
License: MIT License
        
        Copyright (c) 2025-2026 Cash Contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: cache,caching,data-science,decorator,dependency-tracking,incremental-computation,ipython,jupyter,machine-learning,memoization,notebook,pandas
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: IPython
Classifier: Framework :: Jupyter
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Provides-Extra: all
Requires-Dist: boto3<2,>=1.26; extra == 'all'
Requires-Dist: cloudpickle<4,>=2.0; extra == 'all'
Requires-Dist: ipynbname>=0.1; extra == 'all'
Requires-Dist: ipython<10,>=8.0; extra == 'all'
Requires-Dist: ipywidgets<9,>=7.0; extra == 'all'
Requires-Dist: matplotlib<4,>=3.5; extra == 'all'
Requires-Dist: pandas; extra == 'all'
Requires-Dist: polars<3,>=1.0; extra == 'all'
Requires-Dist: psutil<7,>=5.0; extra == 'all'
Requires-Dist: pyarrow>=13.0; extra == 'all'
Requires-Dist: redis<6,>=4.0; extra == 'all'
Provides-Extra: cloudpickle
Requires-Dist: cloudpickle<4,>=2.0; extra == 'cloudpickle'
Provides-Extra: dev
Requires-Dist: dask[dataframe]; extra == 'dev'
Requires-Dist: ipykernel; extra == 'dev'
Requires-Dist: jupyter-client; extra == 'dev'
Requires-Dist: jupyter-ui-poll; extra == 'dev'
Requires-Dist: matplotlib<4,>=3.5; extra == 'dev'
Requires-Dist: moto[server]>=5.0; extra == 'dev'
Requires-Dist: nbclient; extra == 'dev'
Requires-Dist: nest-asyncio; extra == 'dev'
Requires-Dist: pandas; extra == 'dev'
Requires-Dist: polars<3,>=1.0; extra == 'dev'
Requires-Dist: pyarrow>=13.0; extra == 'dev'
Requires-Dist: pydantic<3,>=2; extra == 'dev'
Requires-Dist: pyinstrument<6,>=4.6; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-rerunfailures; extra == 'dev'
Requires-Dist: pytest-subtests; extra == 'dev'
Requires-Dist: pytest-timeout; extra == 'dev'
Requires-Dist: pytest-xdist; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: s3fs; extra == 'dev'
Requires-Dist: scikit-learn; extra == 'dev'
Requires-Dist: scipy; extra == 'dev'
Requires-Dist: traitlets; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.0; extra == 'docs'
Requires-Dist: mkdocs-mermaid2-plugin>=1.2; extra == 'docs'
Requires-Dist: mkdocs>=1.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Provides-Extra: docs-test
Requires-Dist: aiohttp>=3.9; extra == 'docs-test'
Requires-Dist: boto3>=1.28; extra == 'docs-test'
Requires-Dist: fakeredis>=2.0; extra == 'docs-test'
Requires-Dist: redis<6,>=4.0; extra == 'docs-test'
Provides-Extra: ipynbname
Requires-Dist: ipynbname>=0.1; extra == 'ipynbname'
Provides-Extra: memory
Requires-Dist: psutil<7,>=5.0; extra == 'memory'
Provides-Extra: notebook
Requires-Dist: ipython<10,>=8.0; extra == 'notebook'
Provides-Extra: pandas
Requires-Dist: pandas; extra == 'pandas'
Requires-Dist: pyarrow>=13.0; extra == 'pandas'
Provides-Extra: polars
Requires-Dist: polars<3,>=1.0; extra == 'polars'
Provides-Extra: redis
Requires-Dist: redis<6,>=4.0; extra == 'redis'
Provides-Extra: s3
Requires-Dist: boto3<2,>=1.26; extra == 's3'
Provides-Extra: sqlite
Provides-Extra: viz
Requires-Dist: ipywidgets<9,>=7.0; extra == 'viz'
Requires-Dist: matplotlib<4,>=3.5; extra == 'viz'
Description-Content-Type: text/markdown

<!-- Built from docs/_brand/readme-header.html by scripts/build_brand_assets.py.
     Absolute raw URLs, not relative paths: PyPI renders this same README and
     does not resolve repo-relative image paths. PyPI's sanitizer also strips
     <picture>/<source>, which is why the light variant is the inner <img> --
     it survives as the fallback there, while GitHub honours the dark source. -->
<picture>
  <source media="(prefers-color-scheme: dark)"
          srcset="https://raw.githubusercontent.com/galgtonold/cash/main/docs/_brand/readme-header-dark.png">
  <img alt="Cash — only recompute what actually changed. Statement-level caching in Jupyter, @cash.cache anywhere else."
       src="https://raw.githubusercontent.com/galgtonold/cash/main/docs/_brand/readme-header-light.png">
</picture>

[![PyPI version](https://img.shields.io/pypi/v/cash-lib.svg)](https://pypi.org/project/cash-lib/)
[![Downloads](https://img.shields.io/pepy/dt/cash-lib.svg)](https://pepy.tech/project/cash-lib)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://pypi.org/project/cash-lib/)
[![CI](https://github.com/galgtonold/cash/actions/workflows/ci.yml/badge.svg)](https://github.com/galgtonold/cash/actions/workflows/ci.yml)
[![Docs](https://readthedocs.org/projects/cash-lib/badge/?version=latest)](https://cash-lib.readthedocs.io/en/latest/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![Status: Beta](https://img.shields.io/badge/status-beta-orange.svg)](#status)

> **Two lines. Zero config. Restart-and-run-all in seconds instead of minutes.**

```python
import cash
%cash_on
```

That's it. The next time you re-run the notebook, every statement that hasn't changed is **restored from cache** instead of recomputed. Outside notebooks, the same engine wraps any function as `@cash.cache`.

**The number that matters:** a 100 MB DataFrame comes back from disk in **166 ms**. Whatever it cost you to compute, that is what every re-run hands back. Cash publishes what a restore costs rather than a speedup multiplier — [here's why](https://cash-lib.readthedocs.io/en/latest/benchmarks/).

📺 **Watch the 90-second demo** — Cash caching a real notebook, end to end:

https://github.com/user-attachments/assets/3f376660-aeb5-4794-89cc-532a04f82f32

**Or try it live in your browser** — no install:

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/galgtonold/cash/blob/main/examples/try_cash_colab.ipynb) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/galgtonold/cash/main?labpath=examples/try_cash_binder.ipynb)

**[Read the docs →](https://cash-lib.readthedocs.io/en/latest/)**

---

## Two ways to use cash

Pick the path that matches how you write code — both ride the same engine:

- **In a notebook** — add `%cash_on` and every cell caches itself, statement-level, with a badge above each cell's output. For interactive work in Jupyter, Colab, or VS Code.
- **In a script** — wrap a function with `@cash.cache` and it caches by its arguments and its own source. For modules, pipelines, and batch jobs.

---

## Why Cash is different

Most caching tools key on the **arguments** you pass. Cash also keys on **the code that runs** — a function's own source, the helpers it calls, and the files it reads — so editing any of them recomputes instead of handing back a stale answer.

**Both paths get:**

- **Change-awareness that follows callees.** It is not only the decorated function's own source that is keyed — edit a plain, undecorated helper it calls, several levels down, and the next call recomputes.
- **File-awareness.** `pd.read_csv`, `np.load`, `open`, … are intercepted. Replace `data.csv` and whatever read it recomputes.
- **Dependency-awareness.** Cash builds a lineage graph, so touching an upstream value re-runs only what transitively depends on it.
- **Persistence across processes.** The cache lives on disk by default — a restart, a fresh process, or a shared backend across machines.
- **Native pandas / numpy / polars / PyArrow hashing.** A DataFrame or an array can be an argument or a tracked input, content-hashed rather than keyed by identity.

**In a notebook, additionally:**

- **Statement-level, not cell-level.** Change one line in a 20-line cell → that line and its dependents recompute. The other 19 stay cached.
- **Mutation-aware.** `df.append(...)` and `+=` are detected, so cells *below* the mutation see it. Re-running a cell *above* one answers as a clean top-to-bottom run would — without it ([why](https://cash-lib.readthedocs.io/en/latest/known-limitations/)).
- **Zero-config.** `%cash_on` and you're done. No decorators, no config file.

Cash saves time on **re-runs** — restoring an unchanged result instead of recomputing it, not speeding the first execution up. The more a statement costs to compute relative to the size of its result, the more a restore saves; `%cash_stats` reports your actual numbers, and says so plainly when caching cost you time. See the [benchmarks](https://cash-lib.readthedocs.io/en/latest/benchmarks/) for how that plays out on real workloads.

Weighing cash against a tool you already use? The [comparison matrix](https://cash-lib.readthedocs.io/en/latest/why-cash/#cash-vs-the-alternatives-youve-tried) does that properly, tool by tool.

---

## Install

```bash
pip install cash-lib
```

Optional extras: `pip install "cash-lib[all]"` (pandas, polars, redis, s3, …). See [installation](https://cash-lib.readthedocs.io/en/latest/getting-started/installation/).

## Quick start

### In a notebook

**Cell 1** — turn it on:

```python
import cash
%cash_on
```

**Cell 2** — your normal code:

```python
import pandas as pd
df = pd.read_csv("large_dataset.csv")   # tracked: file change → recompute
summary = df.describe()
```

Re-run it, and Cash puts a badge above the cell's output saying exactly what
it did.
Here is one from a cell of the same shape — a load, then a summary:

<a href="https://cash-lib.readthedocs.io/en/latest/badges/">
  <img width="480" alt="A Cash badge below a notebook cell: the pd.read_csv row restored from cache, the df.describe row recomputed"
       src="https://raw.githubusercontent.com/galgtonold/cash/main/docs/_badges/status_mixed.png">
</a>

Two statements, one cell, two different decisions. The file hadn't changed, so
the load was **restored**; `describe()` had been edited, so only that line
**ran** — the two rails and the `CACHED 1` / `EXEC 1` chips say which is which.
That is what statement-level means in practice: a cell-level cache would have
had to redo both.

- ✅ Nothing changed → every row reads `CACHED`.
- 🔄 The CSV changed → the rows that read it flip to `EXECUTED`.
- ⚡ Only the analysis changed → the load stays `CACHED`, the analysis re-runs.

In the notebook the badge is expandable — click any row for its cache key,
storage tier and timing. Full anatomy in [Reading the Cash badge](https://cash-lib.readthedocs.io/en/latest/badges/).

### In a script

`@cash.cache` caches any Python function across processes — keyed by its
arguments *and* by the code that produced the result.

```python
import cash
import pandas as pd

def clean(frame):                 # a plain function, not decorated
    return frame.dropna()

@cash.cache
def features(path):
    return clean(pd.read_csv(path))["feature_a"].mean()

features("large_dataset.csv")     # runs
features("large_dataset.csv")     # restored — and again in the next process
```

Now edit `clean`: the next call **recomputes**, even though `features`'s own
source never changed. Same if `large_dataset.csv` changes on disk.

That reach into a plain, undecorated helper — not just the decorated
function's own source — is what keeps a cached result honest while you
refactor around it.

Impure functions (LLM calls, HTTP, file writes) are flagged by default, since
their side effects only run on the first call.

Full walkthrough in [the decorator guide](https://cash-lib.readthedocs.io/en/latest/decorator/).

---

## Learn more

- [Documentation home](https://cash-lib.readthedocs.io/en/latest/) · [Why Cash?](https://cash-lib.readthedocs.io/en/latest/why-cash/) · [Quick start](https://cash-lib.readthedocs.io/en/latest/getting-started/quickstart/)
- [Reading the badge](https://cash-lib.readthedocs.io/en/latest/badges/) · [Magic commands](https://cash-lib.readthedocs.io/en/latest/magics/) · [Annotations](https://cash-lib.readthedocs.io/en/latest/annotations/)
- [The decorator](https://cash-lib.readthedocs.io/en/latest/decorator/) · [Backends](https://cash-lib.readthedocs.io/en/latest/api/backends/) · [Command-line interface](https://cash-lib.readthedocs.io/en/latest/cli/)
- [Known limitations](https://cash-lib.readthedocs.io/en/latest/known-limitations/) · [Benchmarks](https://cash-lib.readthedocs.io/en/latest/benchmarks/) · [Versioning & compatibility](https://cash-lib.readthedocs.io/en/latest/versioning/)

## Status

**Beta.** The public API is stabilizing, but this is a `0.x` release — the cache format may still change between minor versions, so run `%cash_repair --full` after upgrading (see [versioning & compatibility](https://cash-lib.readthedocs.io/en/latest/versioning/)). The [known limitations](https://cash-lib.readthedocs.io/en/latest/known-limitations/) are documented honestly.

A cache is only worth as much as your trust in it, so [**how cash is tested**](https://cash-lib.readthedocs.io/en/latest/how-it-works/testing/) is written down: <!-- docnum:tests_total -->~9,200<!-- /docnum --> tests across <!-- docnum:platforms -->15<!-- /docnum --> platform combinations, documentation whose code is executed and whose prose is pinned to the source that decides it, and a section on what all of that still fails to catch.

Bug reports welcome: the badge has a "Report a bug" button, `%cash_feedback` prints how, or open an [issue](https://github.com/galgtonold/cash/issues).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). Good first issues are [tagged on the tracker](https://github.com/galgtonold/cash/issues?q=label%3A%22good+first+issue%22).

## License

MIT — see [LICENSE](LICENSE).
