Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2.

## [Unreleased]

0.3.9 is the last 0.3.x release. 0.4.0 contains breaking changes; see [Migrating to 0.4.0](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/).

## [0.3.8] - 2026-09-27

### Added
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ def build_report() -> dict:

@app.get("/report")
async def report(cache: AppCache):
# Cache any JSON value in your own code.
return await cache.get_or_set("report", build_report, ttl=300)
# Cache any JSON value in your own code. lock=True runs build_report once
# for concurrent misses (the default from 0.4.0).
return await cache.get_or_set("report", build_report, ttl=300, lock=True)
```

> [!IMPORTANT]
Expand Down
5 changes: 5 additions & 0 deletions changelog.d/126.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**The `encoding` option of `AsyncRedisCacheBackend` and `RedisConfig`.** 0.4.0
removes it and reads raw bytes; entries were always UTF-8. Passing `encoding`
to `AsyncRedisCacheBackend`, or setting it on a `RedisConfig` given to
`load_from_config()`, now emits a `DeprecationWarning`. Leave it out: UTF-8 is
what you get without it. A value other than UTF-8 keeps its `RuntimeWarning`.
3 changes: 3 additions & 0 deletions changelog.d/129.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**JWT HMAC secrets shorter than the hash output.** The `UserWarning` that
`JWTTokenSerializer` emits for an `HS384` key under 48 bytes or an `HS512` key
under 64 bytes now says that 0.4.0 will reject such a key at startup.
7 changes: 7 additions & 0 deletions changelog.d/131.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
**`get_session_manager` finding the manager only on `app.state`.** 0.4.0
resolves `get_session_manager` (and `SessionManagerDep`, `ClientIPDep` and
`rotate_session_id()`, which use it) through `SessionManagerProxy` only. It now
emits a `FutureWarning`, once per app, when the proxy holds no manager or a
different one than the session middleware. Call
`SessionManagerProxy.set(session_manager)` at startup; the middleware can then
pick the manager up from the proxy.
5 changes: 5 additions & 0 deletions changelog.d/256.deprecated.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**`SessionConfig` with a `__Host-` or `__Secure-` cookie name that browsers
refuse.** A `__Host-` name without `cookie_https_only=True`, with a
`cookie_path` other than `"/"` or with a `cookie_domain`, and a `__Secure-` name
without `cookie_https_only=True`, now emit a `UserWarning`: browsers drop such a
cookie, so the session never sticks. 0.4.0 will reject these settings.
9 changes: 9 additions & 0 deletions changelog.d/256.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Relying on the session cookie defaults of `FastAPICacheXSessionMiddleware`.**
0.4.0 names the session cookie `__Host-session` and sets the `Secure` flag by
default, so every cookie session is logged out once on upgrade and a plain-HTTP
setup stops receiving the cookie. The middleware now emits a `FutureWarning`
when its config leaves `cookie_name` or `cookie_https_only` at the default. Set
both: `cookie_name="session", cookie_https_only=False` keeps the current cookie,
`cookie_name="__Host-session", cookie_https_only=True` switches now. Header-only
setups never send the cookie and do not warn. See "Migrating to 0.4.0" in the
docs.
8 changes: 8 additions & 0 deletions changelog.d/280.deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**Relying on the `lock=False` default of `CacheManager.get_or_set()`.** 0.4.0
turns stampede protection on by default. A `get_or_set()` call that passes no
`lock=`, on a manager created without `lock=` (including the one `AppCache`
creates), now emits a `FutureWarning` once per manager. Pass `lock=False` to
keep the current behaviour or `lock=True` to opt in now, per call or to
`CacheManager(...)`; for `AppCache`, register a manager with
`CacheManagerProxy.set()`. `CacheManager(lock=None)` is now accepted and means
"not chosen".
9 changes: 7 additions & 2 deletions docs/APP_CACHE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ async def expensive_operation(cache: AppCache):
return result


# Or instantiate directly, e.g. outside of a request:
manager = CacheManager(key_prefix="myapp:", default_ttl=60)
# Or instantiate directly, e.g. outside of a request. Pass `lock` explicitly:
# its default turns from False to True in 0.4.0 (see "Stampede protection").
manager = CacheManager(key_prefix="myapp:", default_ttl=60, lock=False)
await manager.set("user:42", {"name": "Alice"})
user = await manager.get("user:42") # {"name": "Alice"}
await manager.delete("user:42")
Expand Down Expand Up @@ -121,6 +122,10 @@ manager = CacheManager(lock=True, lock_ttl=60)

Ensure `lock_ttl` exceeds the expected execution time of `factory`. If `factory` outlives `lock_ttl`, the lock expires mid-run and a waiting caller may start a second computation.

### The default changes in 0.4.0

Stampede protection is off by default in 0.3.x and **on by default from 0.4.0**. A `get_or_set()` call that passes no `lock=`, on a manager created without `lock=` (including the one `AppCache` creates for you), emits a `FutureWarning` once per manager. Pass `lock=False` to keep the current behaviour or `lock=True` to opt in now, either per call or to `CacheManager(...)`; for `AppCache`, register your own manager with `CacheManagerProxy.set(CacheManager(lock=...))`. See [Migrating to 0.4.0](MIGRATING_0_4.md#get-or-set-lock).

## JSON round-trip

Values are stored as JSON (`json.dumps` with its defaults) and read back with
Expand Down
12 changes: 6 additions & 6 deletions docs/BACKENDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ config = RedisConfig(
port=6379,
password=None, # SecretStr | None
db=0,
encoding="utf-8", # keep UTF-8; see below
socket_timeout=1.0, # seconds; applies to reads/writes
socket_connect_timeout=1.0,
key_prefix="fastapi_cachex:",
Expand All @@ -102,11 +101,12 @@ backend = AsyncRedisCacheBackend.load_from_config(config)
BackendProxy.set(backend)
```

Keep `encoding="utf-8"`. Entries are always written as UTF-8 JSON, and the client
decodes replies with `encoding`, so any other value corrupts non-ASCII content on the way
back (with `"latin-1"`, a stored `b"\xe9"` reads back as `b"\xc3\xa9"`). The backend
emits a `RuntimeWarning` for a non-UTF-8 encoding, and the parameter will be removed in
0.4.0.
Leave `encoding` out, of both `RedisConfig` and `AsyncRedisCacheBackend`: it is deprecated
and removed in 0.4.0, and setting it at all emits a `DeprecationWarning`. Entries are always
written as UTF-8 JSON, and the client decodes replies with `encoding`, so any value other than
UTF-8 also corrupts non-ASCII content on the way back (with `"latin-1"`, a stored `b"\xe9"`
reads back as `b"\xc3\xa9"`), and the backend emits a `RuntimeWarning` for it. See
[Migrating to 0.4.0](MIGRATING_0_4.md#redis-encoding).

Keep `protocol=2` unless you need RESP3 features *and* your `hiredis` build
supports it (RESP3 needs hiredis >= 3.0). Redis 8.0 speaks RESP3, but an older
Expand Down
Loading
Loading