client-query-cache
Client-side caching for PyMongo, kept coherent using MongoDB change streams. For Python applications that already talk to MongoDB through PyMongo directly, it adds a coherent read cache without introducing a separate cache server or changing how you connect. The library supports synchronous and asyncio clients.
It caches reads whose results the manager can invalidate correctly when the underlying data changes, and leaves everything else — including all writes — to go straight to MongoDB. Invalidation is asynchronous: a read running concurrently with a write can still return the previous cached value until the manager processes that write's change-stream event.
Built for production: 100% covered, extensively tested from cache-core invariants through real MongoDB deployments, continuously benchmarked, and protected by an automated pull-request performance regression guard.
Read latency across two different deployments, so you can see the range: the local-server row is the median from one of the retained local benchmark reports; the M0-deployment row is the mean of one batch from the real-server benchmark against a free-tier Atlas (M0) cluster — plotted on a logarithmic axis given the size of the gap. Cached-read latency barely moves between the two, since a cache hit never touches the network. Neither number is a universal performance guarantee for your own workload or deployment — see Stream cost benchmarks for the full local workload matrix and how to reproduce it.
Requirements
client-query-cache requires Python 3.14.6 or newer.
Reads and writes work against any MongoDB deployment PyMongo supports. Effective caching needs two separate things: a replica set or sharded cluster, since MongoDB only provides change streams on one of those topologies, not a standalone server; and MongoDB 8.0 or newer, a floor this library enforces itself at startup rather than a limit of change streams themselves. Against a deployment that doesn't meet both, the manager doesn't raise: it logs a warning and bypasses the cache for that database, executing every read as a normal, uncached PyMongo call.
Install
uv add client-query-cache
Or with pip: pip install client-query-cache.
Usage
CacheManager wraps a pymongo.MongoClient (or pymongo.AsyncMongoClient) that you construct and own. Keep using your PyMongo collection for writes and everything else, and ask the manager for a cached view of that collection for six reads — find_one, find, aggregate, count_documents, estimated_document_count, and distinct. The cache keeps those results coherent as the underlying data changes:
from pymongo import MongoClient
from client_query_cache import CacheManager
with (
MongoClient("mongodb://localhost:27017") as client,
CacheManager(client) as cache_manager,
):
collection = client["my_database"]["my_collection"]
cached_collection = cache_manager.cached(collection)
collection.insert_one({"_id": "example", "value": 42})
# Cache miss: reads from MongoDB.
cached_collection.find_one({"_id": "example"})
# Cache hit: served from the cache.
cached_collection.find_one({"_id": "example"})
# Prints 1. Bridge stats like this into OpenTelemetry:
# docs/architecture.md#opentelemetry-metrics
print(cache_manager.snapshot().hits)
collection.create_index("email", unique=True)
collection.insert_one({"_id": "user-1", "email": "a@example.com"})
# Also cached, like an `_id` lookup.
cached_collection.find_one({"email": "a@example.com"})
collection is PyMongo's own object, so your editor and type checker see PyMongo's real methods and signatures. cached_collection has only the six cached reads; find and aggregate on it return a list instead of a cursor. Use cached_collection.raw (the same collection) whenever you need PyMongo's own cursor behavior.
find_one caches deterministic single-document queries, including compound filters, match-all reads and missing results. Use sort to choose the first matching document and collation to control string matching:
cached_collection.find_one(
{"status": "active"},
sort=[("updated_at", -1)],
collation={"locale": "en", "strength": 2},
)
Exact _id lookups and qualifying unique indexes allow cached reads to survive writes to other documents. Other queries are refreshed after any write to the collection. Updates become visible after the manager processes their change-stream events; use the PyMongo collection for reads that must immediately observe a preceding write.
CacheManager starts a background change-stream task the first time a read touches a database, so close it (or use it as a context manager, as above) alongside the client — closing only the client leaves that background task running against a closed connection.
The same API is available for pymongo.AsyncMongoClient under client_query_cache.asynchronous, with the cached reads as coroutines:
import asyncio
from pymongo import AsyncMongoClient
from client_query_cache.asynchronous import CacheManager
async def main() -> None:
async with (
AsyncMongoClient("mongodb://localhost:27017") as client,
CacheManager(client) as cache_manager,
):
collection = client["my_database"]["my_collection"]
cached_collection = cache_manager.cached(collection)
await collection.insert_one({"_id": "example", "value": 42})
# Cache miss.
await cached_collection.find_one({"_id": "example"})
# Cache hit.
await cached_collection.find_one({"_id": "example"})
# A list, not a cursor.
await cached_collection.find({"value": 42})
asyncio.run(main())
A read bypasses the cache instead of using it whenever caching it safely isn't possible — for example, a caller-selected session, read preference, or read concern; a view; a nondeterministic or cross-collection aggregation pipeline; a time-series collection; or a database whose change stream isn't healthy. See Bypass conditions in the API reference for the complete list.
Scope and constraints
- Writes aren't cached — only the six read methods listed above are. See why only reads are cached for the rationale.
- Each
CacheManageris independent — it owns its own in-process cache and its own change-stream cursors; nothing is shared between managers or processes. See capacity planning before creating one per request or one per worker process.
Documentation
Browse the documentation site for searchable guides to the current main branch.
- API reference — the complete public surface: construction, configuration, limits, ownership, and raw fallback.
- Architecture and operations — system requirements, capacity planning, retry/error handling, observability, security, and recovery behavior.
- Examples — runnable programs that add the cache to real libraries that store data in MongoDB, such as requests-cache.
- Stream cost benchmarks — whether caching fits your workload, and the controlled benchmark reports backing that guidance.
MongoDB is a registered trademark of MongoDB, Inc. This project is independent and is not affiliated with, sponsored by, or endorsed by MongoDB, Inc.
Metadata
Release files for client-query-cache 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| client_query_cache-0.2.0.tar.gz | 39.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| client_query_cache-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 94.6 kB
Release files / client_query_cache-0.2.0.tar.gz
| Download URL | client_query_cache-0.2.0.tar.gz |
|---|---|
| Size | 39.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
39f2f8f1a0d0dc2462b7f4be02ebb28954780b86012c3534c0fda128344e39e9
|
|
BLAKE2b-256 checksum How to use checksums |
ebfe238f081ea2e0b3ce98bd03f2f7c3fedd0acdfa3a1ac59c024197035fc462
|
| 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 Oct 2, 2026.
Transparency logRelease files / client_query_cache-0.2.0-py3-none-any.whl
| Download URL | client_query_cache-0.2.0-py3-none-any.whl |
|---|---|
| Size | 55.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
03c46bd8d99124cb9d73a12bd761dc810261c2e60a4d23a1c6a9a90bd6367987
|
|
BLAKE2b-256 checksum How to use checksums |
fb85e7a8ee941e2c53489596a5a14acab9323d4fc7f81cd40b167ded024d54ab
|
| 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 Oct 2, 2026.
Transparency log