Skip to content

feat: @cached decorator for plain functions - #445

Merged
allen0099 merged 1 commit into
masterfrom
claude/lucid-archimedes-4gzb2o
Oct 7, 2026
Merged

allen0099 merged 1 commit into
masterfrom
claude/lucid-archimedes-4gzb2o

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Fixes #248

Stack 4/4 (roadmap #425): #242 (max_entries) → #334 (timedelta TTLs) → #335 (X-Cache) → this. Based on the #335 branch; merge last. Only the last commit is this PR's.

Summary

  • New fastapi_cachex/cached.py, exported as fastapi_cachex.cached and CachedFunction. @cached(ttl=..., key=..., manager=None, lock=None) on an async or sync function stores its result through CacheManager.get_or_set(), so it gets the manager's prefix, JSON round-trip and stampede protection. The decorated function is always awaited; a sync function runs on the loop like a get_or_set factory.
  • Default key: module.qualname: + SHA-256 of the arguments bound to the signature with defaults applied and serialized as canonical JSON, so load(1), load(user_id=1) and load(1, locale="en") share one entry. A non-JSON argument raises CacheXError naming key= rather than silently missing every time.
  • key= is a str.format template over the bound arguments ("user:{user_id}"; a plain string is a fixed key) or a callable given the call's arguments. manager=None resolves the application's manager (get_app_cache()) on each call, so one registered at startup is picked up.
  • fn.cache_key(*args, **kwargs) and await fn.invalidate(*args, **kwargs). CachedFunction.__get__ binds self, so methods, obj.load.cache_key(1) and obj.load.invalidate(1) work (with key=, since self is not JSON).
  • Typed with ParamSpec and overloads: await load(1) is inferred as the function's return type, and a wrong argument type is a mypy error (checked with a probe file).
  • Docs: "Caching a function" section in APP_CACHE.md (en + zh-TW) driven by a new --8<-- snippet in examples/app_cache.py, API reference, README feature line; changelog.d/248.added.md.

Tests: tests/test_cached.py (25 tests: keys, templates, callables, invalidate, TTL and manager defaults, JSON round-trip, app-manager resolution, concurrent misses run once, lock=False, method binding, bad arguments) and the extended test_examples.py::test_app_cache.

Checklist

  • The issue above is assigned to me
  • Tests under tests/ cover the change
  • A changelog fragment changelog.d/<issue>.<section>.md (required for any change under fastapi_cachex/)
  • uv run pre-commit run --all-files and uv run pytest pass

🤖 Generated with Claude Code

https://claude.ai/code/session_01UTXhK1BtwXTBDaouEcKtpW


Generated by Claude Code

`@cached(ttl=..., key=..., manager=None)` caches what an async or sync
function returns through `CacheManager.get_or_set()`, so it gets the
manager's prefix, JSON round-trip and stampede protection. The default
key is `module.qualname:` plus a SHA-256 of the JSON-serialized
arguments bound to the signature; `key=` takes a format template or a
callable for methods and non-JSON arguments. The decorated function has
`cache_key()` and `invalidate()`, and binds as a method.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTXhK1BtwXTBDaouEcKtpW
Base automatically changed from claude/lucid-archimedes-4gzb2o-335-x-cache to master October 7, 2026 11:00
@allen0099
allen0099 merged commit 148493e into master Oct 7, 2026
4 checks passed
@allen0099
allen0099 deleted the claude/lucid-archimedes-4gzb2o branch October 7, 2026 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@cached decorator for plain (non-route) functions

2 participants