Metadata-Version: 2.4
Name: yokedcache
Version: 1.0.2
Summary: Async-first multi-backend cache (memory, Redis, Memcached, disk, SQLite); sync helpers for scripts; tag invalidation, optional HTTP middleware, metrics
Project-URL: Homepage, https://github.com/sirstig/yokedcache
Project-URL: Documentation, https://sirstig.github.io/yokedcache
Project-URL: Repository, https://github.com/sirstig/yokedcache
Project-URL: Bug Tracker, https://github.com/sirstig/yokedcache/issues
Author: Project Yoked LLC
License-Expression: MIT
License-File: LICENSE
Keywords: asgi,async cache,cache invalidation,fastapi,memcached,multi-backend cache,opentelemetry,prometheus,python caching,redis,sqlalchemy,starlette,sync helpers,tag invalidation
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: AsyncIO
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
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 :: Database
Classifier: Topic :: Database :: Front-Ends
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Distributed Computing
Requires-Python: >=3.10
Requires-Dist: click>=8.0.0
Requires-Dist: orjson>=3.11.6
Requires-Dist: pyyaml>=6.0
Requires-Dist: typing-extensions>=4.0.0
Provides-Extra: backends
Requires-Dist: aiomcache<1.0.0,>=0.8.0; extra == 'backends'
Requires-Dist: aiosqlite>=0.19.0; extra == 'backends'
Requires-Dist: diskcache>=5.0.0; extra == 'backends'
Provides-Extra: dev
Requires-Dist: aiomcache<1.0.0,>=0.8.0; extra == 'dev'
Requires-Dist: aiosqlite>=0.19.0; extra == 'dev'
Requires-Dist: black==26.3.1; extra == 'dev'
Requires-Dist: build>=1.0.0; extra == 'dev'
Requires-Dist: diskcache>=5.6.0; extra == 'dev'
Requires-Dist: fakeredis>=2.0.0; extra == 'dev'
Requires-Dist: fastapi>=0.68.0; extra == 'dev'
Requires-Dist: flake8>=4.0.0; extra == 'dev'
Requires-Dist: fuzzywuzzy>=0.18.0; extra == 'dev'
Requires-Dist: httpx>=0.24.0; extra == 'dev'
Requires-Dist: isort>=5.10.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: numpy>=1.21.0; extra == 'dev'
Requires-Dist: opentelemetry-api>=1.24.0; extra == 'dev'
Requires-Dist: opentelemetry-sdk>=1.24.0; extra == 'dev'
Requires-Dist: pre-commit>=2.20.0; extra == 'dev'
Requires-Dist: prometheus-client>=0.16.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=9.0.3; extra == 'dev'
Requires-Dist: python-levenshtein>=0.12.0; extra == 'dev'
Requires-Dist: redis>=4.0.0; extra == 'dev'
Requires-Dist: scikit-learn>=1.0.0; extra == 'dev'
Requires-Dist: scipy>=1.7.0; extra == 'dev'
Requires-Dist: statsd>=4.0.0; extra == 'dev'
Requires-Dist: twine>=4.0.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
Requires-Dist: types-redis>=4.0.0; extra == 'dev'
Provides-Extra: disk
Requires-Dist: diskcache>=5.0.0; extra == 'disk'
Provides-Extra: docs
Requires-Dist: jinja2>=3.1.0; extra == 'docs'
Requires-Dist: markdown>=3.5.0; extra == 'docs'
Requires-Dist: pdoc>=14.0.0; extra == 'docs'
Requires-Dist: pygments>=2.14.0; extra == 'docs'
Provides-Extra: full
Requires-Dist: aiomcache<1.0.0,>=0.8.0; extra == 'full'
Requires-Dist: aiosqlite>=0.19.0; extra == 'full'
Requires-Dist: diskcache>=5.0.0; extra == 'full'
Requires-Dist: fastapi>=0.68.0; extra == 'full'
Requires-Dist: fuzzywuzzy>=0.18.0; extra == 'full'
Requires-Dist: numpy>=1.21.0; extra == 'full'
Requires-Dist: opentelemetry-api>=1.15.0; extra == 'full'
Requires-Dist: opentelemetry-sdk>=1.15.0; extra == 'full'
Requires-Dist: prometheus-client>=0.16.0; extra == 'full'
Requires-Dist: python-levenshtein>=0.12.0; extra == 'full'
Requires-Dist: redis>=4.0.0; extra == 'full'
Requires-Dist: scikit-learn>=1.0.0; extra == 'full'
Requires-Dist: scipy>=1.7.0; extra == 'full'
Requires-Dist: sqlalchemy>=1.4.0; extra == 'full'
Requires-Dist: statsd>=4.0.0; extra == 'full'
Provides-Extra: fuzzy
Requires-Dist: fuzzywuzzy>=0.18.0; extra == 'fuzzy'
Requires-Dist: python-levenshtein>=0.12.0; extra == 'fuzzy'
Provides-Extra: memcached
Requires-Dist: aiomcache<1.0.0,>=0.8.0; extra == 'memcached'
Provides-Extra: monitoring
Requires-Dist: prometheus-client>=0.16.0; extra == 'monitoring'
Requires-Dist: statsd>=4.0.0; extra == 'monitoring'
Provides-Extra: observability
Requires-Dist: opentelemetry-api>=1.15.0; extra == 'observability'
Requires-Dist: opentelemetry-sdk>=1.15.0; extra == 'observability'
Requires-Dist: prometheus-client>=0.16.0; extra == 'observability'
Requires-Dist: statsd>=4.0.0; extra == 'observability'
Provides-Extra: redis
Requires-Dist: redis>=4.0.0; extra == 'redis'
Provides-Extra: sqlalchemy
Requires-Dist: sqlalchemy>=1.4.0; extra == 'sqlalchemy'
Provides-Extra: sqlite
Requires-Dist: aiosqlite>=0.19.0; extra == 'sqlite'
Provides-Extra: tracing
Requires-Dist: opentelemetry-api>=1.15.0; extra == 'tracing'
Requires-Dist: opentelemetry-sdk>=1.15.0; extra == 'tracing'
Provides-Extra: vector
Requires-Dist: numpy>=1.21.0; extra == 'vector'
Requires-Dist: scikit-learn>=1.0.0; extra == 'vector'
Requires-Dist: scipy>=1.7.0; extra == 'vector'
Provides-Extra: web
Requires-Dist: starlette>=0.27.0; extra == 'web'
Description-Content-Type: text/markdown

# yokedcache

[![PyPI version](https://img.shields.io/pypi/v/yokedcache.svg)](https://pypi.org/project/yokedcache/)
[![Python](https://img.shields.io/pypi/pyversions/yokedcache.svg)](https://pypi.org/project/yokedcache/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://github.com/sirstig/yokedcache/actions/workflows/test.yml/badge.svg)](https://github.com/sirstig/yokedcache/actions/workflows/test.yml)
[![Coverage](https://codecov.io/gh/sirstig/yokedcache/branch/main/graph/badge.svg)](https://codecov.io/gh/sirstig/yokedcache)

Async-first caching with the same API across backends: in-process memory (works out of the box), Redis, Memcached, disk, and SQLite. Tag and pattern invalidation, optional HTTP middleware, and production metrics built in.

Use `await` in FastAPI, Django async views, workers, or plain asyncio. Sync code is welcome too—`get_sync` / `set_sync` / `@cached` on a normal `def` run the same async implementation, no separate client needed.

**[Documentation](https://sirstig.github.io/yokedcache/)** · **[Changelog](https://sirstig.github.io/yokedcache/changelog.html)** · **[PyPI](https://pypi.org/project/yokedcache/)** · **[Issues](https://github.com/sirstig/yokedcache/issues)**

---

## What's included

- **Multiple backends** — Memory (zero deps), Redis, Memcached, disk, SQLite; per-prefix routing to mix them
- **Invalidation** — Tag-based, pattern-based, and auto-invalidation on DB writes
- **Sync + async** — Full async API; sync helpers for scripts and blocking code
- **HTTP middleware** — ETag / `Cache-Control` via Starlette (`yokedcache[web]`)
- **Resilience** — Circuit breaker, retries, stale-if-error
- **Observability** — Prometheus, StatsD, OpenTelemetry (optional extras)
- **CLI** — Inspect keys, stats, and run health checks from the shell

## Installation

```bash
pip install yokedcache
```

The base install ships with an in-process memory backend—no Redis required to get started. Add extras when you need them:

| Extra | What it adds |
|-------|-------------|
| `redis` | Redis backend via `redis-py` |
| `web` | Starlette HTTP cache middleware |
| `backends` | Disk, SQLite, and Memcached deps together |
| `observability` | Prometheus, StatsD, OpenTelemetry |
| `full` | Everything above plus fuzzy search, vector search, SQLAlchemy helpers |

To require a minimum patch line (for example after a security release): `pip install "yokedcache>=1.0.2"`.

Individual extras: `memcached`, `disk`, `sqlite`, `monitoring`, `tracing`, `vector`, `fuzzy`, `sqlalchemy`.

## Quick start

**Async (memory backend, no Redis needed):**

```python
import asyncio
from yokedcache import YokedCache
from yokedcache.config import CacheConfig

async def main():
    cache = YokedCache(CacheConfig())
    await cache.connect()
    await cache.set("user:1", {"name": "Ada"}, ttl=60)
    print(await cache.get("user:1"))
    await cache.disconnect()

asyncio.run(main())
```

**Sync (scripts and blocking code):**

```python
import asyncio
from yokedcache import YokedCache
from yokedcache.config import CacheConfig

cache = YokedCache(CacheConfig())
asyncio.run(cache.connect())
cache.set_sync("user:1", {"name": "Ada"}, ttl=60)
print(cache.get_sync("user:1"))
asyncio.run(cache.disconnect())
```

For Redis: `pip install "yokedcache[redis]"`, then set `redis_url="redis://..."` on `CacheConfig` (or the env var `YOKEDCACHE_REDIS_URL`).

## FastAPI example

```python
from fastapi import FastAPI, Depends
from yokedcache import cached_dependency

app = FastAPI()

cached_get_db = cached_dependency(get_db, ttl=300)

@app.get("/users/{user_id}")
async def get_user(user_id: int, db=Depends(cached_get_db)):
    return db.query(User).filter(User.id == user_id).first()
```

## Requirements

- **Python 3.10+** (tested on 3.10–3.14)
- **Redis** is optional; install `yokedcache[redis]` and point to a Redis 6+ server when you want a shared remote cache

Python 3.9 is not supported on 1.x. Pin `yokedcache==0.3.0` only as a temporary bridge—it does not receive security fixes. Upgrade when you can.

## Security

Treat Redis and Memcached as trusted stores—anyone who can write arbitrary keys can affect what your app deserializes. Set `allow_legacy_insecure_deserialization=False` on `CacheConfig` once you've migrated away from legacy entries.

The optional `disk` extra pulls in `diskcache`. **From 1.0.2**, the disk backend stores JSON-safe wrappers around the same **bytes envelope** as other backends (no pickle for cached payloads). **[CVE-2025-69872](https://github.com/advisories/GHSA-w8v5-vhqr-4h9v)** still applies to the upstream package metadata until a patched wheel ships—automated scanners may flag it. Skip the `disk` extra if you don't need it; keep the cache directory non-world-writable if you do. See [SECURITY.md](SECURITY.md).

## Development

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
```

Build the docs site locally:

```bash
pip install -e ".[docs]"
python scripts/build_docs_site.py
cp CHANGELOG.md site/changelog.md
python -m pdoc yokedcache -o site/api --template-directory site-src/pdoc-template
cd site && python -m http.server 8000
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.

## License

MIT. See [LICENSE](LICENSE).

Maintained by **Project Yoked LLC**; technical lead **Joshua Kac**.
