Skip to main content

Cachefy

PyPI version Cachefy - Test Coverage License: MIT Python versions

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 None is 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
    • FastAPI — the lifespan, and caching a response
    • Django — the ORM from a producer, and a management command
    • Flask — a synchronous world, and where the loop lives
  • 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.version is 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.

Buy Me a Coffee at ko-fi.com

📄 License

MIT

Copyright (c) 2026, Paulo Coutinho

Metadata

Release files for cachefy 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cachefy 0.0.2
File Size Uploaded
cachefy-0.0.2.tar.gz 193.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cachefy 0.0.2
File Interpreter ABI Platform
cachefy-0.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 226.4 kB

Release files / cachefy-0.0.2.tar.gz

Download URL cachefy-0.0.2.tar.gz
Size 193.2 kB
Tags Source
SHA-256 checksum
How to use checksums
bb0d5ba019d2959427b865a8ad07ff3807d19dccae3208386cf06350694f3cd5
BLAKE2b-256 checksum
How to use checksums
797126e48acb9382012c16db0d6318b717b5cdfc11712d447f2ff4894e5c2474
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

Release files / cachefy-0.0.2-py3-none-any.whl

Download URL cachefy-0.0.2-py3-none-any.whl
Size 33.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
005148136d018ed696454af65b69a6f4ce318c2fa39cb37ba9a4922290d6843d
BLAKE2b-256 checksum
How to use checksums
3482e94cd1da9c04bbb39bc11fb5a6ce9b2ff05c62b32a839819693550ac1b59
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

Release history Release notifications | RSS feed

1.0.0

2 release files

0.0.3

2 release files

This release

0.0.2 This release

2 release 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