Asynchronous cache for Python, where a store that blinked is never a failure the caller sees.
🚀 Project
Cachefy is a cache built around one row: a name that holds a value until an instant.
A read is that row before that instant. A write replaces it. A miss is that row absent or past its instant. Memoizing a function, holding a name so exactly one caller computes, and counting a rate limit are all the same row under different values.
Two promises run through the whole of it:
- A store that cannot be reached is a miss and never an exception. A cache is what makes an application faster, not what makes it work.
- A value that could never be written is refused where it is written. That is a bug in the calling code, and it is told at once.
✨ Features
- Read, write and drop a value under a name, with a lifetime per space or per call
- Memoize a function on the arguments it was called with, and invalidate one call or all of them
- One caller computes while the rest wait, so a cold key never becomes a stampede
- Stale-while-revalidate: one caller refreshes while everybody else is served what is there
- Compare-and-set, so a slow writer never overwrites a newer value
- Atomic counters for rate limits and quotas
- Named spaces, cleared whole in one step
- Hooks for hits, misses and every call a store could not answer
- Pluggable stores: PostgreSQL, MySQL, Redis, SQLite and memory
- A value of
Noneis a value, told apart from a name holding nothing - No dependencies in the core
- 100% branch coverage: one contract against every store, plus differential, concurrency, interrupted-call, contention, resilience and multi-process suites
📦 Install
pip install "cachefy[redis]"
Or with SQLAlchemy:
pip install "cachefy[sqlalchemy]"
🧭 The five things it does
| What you want | How you ask for it |
|---|---|
| read a value | await users.get("42") |
| write a value | await users.set("42", profile) |
| compute it once, however many callers ask | await users.fetch("42", load) |
| remember what a function answered | @app.cached("profile", ttl=timedelta(minutes=5)) |
| count something under a name | await limits.incr(address) |
💡 How to use
import asyncio
from datetime import timedelta
from redis.asyncio import Redis
from cachefy.app import Cachefy
from cachefy.store.redis import RedisStore
app = Cachefy(RedisStore(Redis.from_url("redis://127.0.0.1:6379/0")))
users = app.space("users", ttl=timedelta(minutes=5), stale=timedelta(minutes=1))
@app.cached("profile", ttl=timedelta(minutes=5))
async def profile(user_id: int) -> dict:
return {"id": user_id, "name": "Paulo"}
async def main():
await app.setup()
await users.set("42", {"name": "Paulo"})
print(await users.get("42"))
print(await profile(42))
await profile.invalidate(42)
asyncio.run(main())
🧱 The words it uses
Four of them, and each means one thing:
| Word | What it is |
|---|---|
| Entry | one name and the value it holds until an instant |
| Space | a named family of entries, and the policy they are all written under |
| Store | where the entries live: Redis, a database, or this process |
| Memo | a function whose answers live in a space of their own |
📚 Documentation
- Getting started
- Spaces — lifetimes, freshness and every call a space answers
- Memoizing — caching what a function answered
- Stampede — one caller computes, and what everybody else is served
- Resilience — what happens when the store is gone
- Stores — Redis, SQLAlchemy, memory, and writing your own
- Janitor — sweeping what has died
- Hooks — hits, misses and failures
- Frameworks — the rule that covers all of them
- Contribution — how to help
🏷️ Releasing a new version
A release is a tag and nothing else. Bump project.version in pyproject.toml, commit it, then push
the matching tag:
git tag v1.0.1
git push origin v1.0.1
That tag is the only trigger. The workflow then runs the suite on every supported Python against real
Redis, MySQL and PostgreSQL, runs the stress suite against them, checks that
the tag agrees with pyproject.toml, builds the wheel and the sdist, publishes them to PyPI through
Trusted Publishing, and creates the GitHub release with its notes and the built files attached.
Do not publish the release from the GitHub interface. Creating one there writes the tag, the tag starts the workflow, and the workflow then finds a release already sitting where it was going to write its own. Push the tag and let the workflow create the release.
A version on PyPI is permanent. A tag that disagrees with
project.versionis refused before anything is built, because a number published by mistake is one PyPI never lets anybody use again.
☕ Buy me a coffee
Support the continuous development of this project.
📄 License
Copyright (c) 2026, Paulo Coutinho
Metadata
Release files for cachefy 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cachefy-0.0.3.tar.gz | 193.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cachefy-0.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 226.4 kB
Release files / cachefy-0.0.3.tar.gz
| Download URL | cachefy-0.0.3.tar.gz |
|---|---|
| Size | 193.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bc3413396cf722114e8b09dd53f8255f189cd8dec0f6a9b967bebe0f34cf61c2
|
|
BLAKE2b-256 checksum How to use checksums |
5c136aff6b0ed681a705d68a1658bddad23693549fa1694a99883c1dd32fcccd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / cachefy-0.0.3-py3-none-any.whl
| Download URL | cachefy-0.0.3-py3-none-any.whl |
|---|---|
| Size | 33.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
98feb58a62b028c7839ababef138220eab4425e0b94cdaec8f152bdbd3ec142d
|
|
BLAKE2b-256 checksum How to use checksums |
2afd805437c717af0ac7bd3216cd9ed769ddc2e9a295f6412a05491b7599a573
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log