Skip to content

feat(cache): add vary to key cached responses on request headers - #309

Merged
allen0099 merged 1 commit into
feat/build-cache-keyfrom
feat/cache-vary
Sep 27, 2026
Merged

allen0099 merged 1 commit into
feat/build-cache-keyfrom
feat/cache-vary

Conversation

@allen0099

@allen0099 allen0099 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #307 (build_cache_key, #264): the base is feat/build-cache-key, and GitHub retargets this PR to master once #307 merges. Review only the top commit.

Summary

@cache(vary=["Accept-Language"]) gives each value of the listed request headers its own entry and sends the names in Vary.

  • Key: each listed header adds a name=value component (name lower-cased, value trimmed, repeated header lines joined with ,, missing = empty), escaped with the @cache: public build_cache_key() helper so custom key builders don't hand-roll the format #264 helper (build_cache_key and vary share _append_key_components). E.g. GET|||example.com|||/greeting|||||||accept-language=de.
  • Composition with key_builder: vary components are appended after whatever the builder returns, so a custom builder and vary compose (...|||tenant-1|||accept-language=de).
  • Vary header: added to every response to a GET on the route: 200 and 304, cache hit/miss, and the paths that skip the backend (private, no_store, no_cache, the @cache stores responses marked private/no-store, with Set-Cookie, or for Authorization requests #296 Authorization bypass, unstored Set-Cookie/private responses). It runs after the response is built, so the @cache stores responses marked private/no-store, with Set-Cookie, or for Authorization requests #296 private Cache-Control variants are unchanged (tested). Names already listed (any case) are not repeated, and Vary: * is left alone. Non-GET responses are untouched. Routes without vary have no extra wrapper at all.
  • _add_vary: moved from session/middleware.py to a new fastapi_cachex/headers.py (add_vary), used by both the middleware and @cache.
  • Validation at decoration time (CacheXError): vary must be a list/tuple; a bare str (vary="Accept"), bytes, sets, empty names, non-token names (spaces, commas) and * are rejected. Duplicate names (case-insensitive) collapse to the first spelling.
  • invalidate(request, key_builder=None, vary=None): new optional vary so it can rebuild a vary route's key (deletes the variant the request selects). clear_path(path) clears every variant thanks to the @cache: public build_cache_key() helper so custom key builders don't hand-roll the format #264 change.
  • Additive: routes without vary keep their keys and get no Vary (tested).
  • Docs: new "Varying on request headers" section in HTTP_CACHING.md with a warning that each header multiplies entries (values are client-controlled) and an example that normalises Accept-Language to a supported locale with a custom key_builder + build_cache_key; invalidate docs and CACHE_FLOW.md updated; zh-TW mirrors. README does not list decorator parameters, so it is unchanged.

Follow-up tracked in #310: a normaliser for vary= headers (e.g. vary={"Accept-Language": normalise}), so the common "reduce to supported values" case doesn't need a custom key builder.

Changelog

changelog.d/268.added.md

Test plan

  • uv run ruff check fastapi_cachex tests && uv run ruff format --check fastapi_cachex tests && uv run mypy fastapi_cachex --strict
  • Full suite including the live Redis and Memcached tests: 1175 passed, 1 skipped, coverage 99%
  • uv run pytest (no live servers): 984 passed, 192 skipped, coverage 94%

Closes #268

@cache(vary=["Accept-Language"]) appends a name=value component per listed
header to the key, after whatever the key builder returns, and adds the
names to the Vary header of every GET response, including 304s and
responses that bypass or are not stored in the backend. invalidate() takes
the same list. The Vary helper moves from the session middleware to
fastapi_cachex/headers.py so both use it.
@allen0099 allen0099 added this to the 0.3.9 milestone Sep 27, 2026
@allen0099
allen0099 added this pull request to stack #311 September 27, 2026 13:44
@allen0099
allen0099 merged commit 5f383b4 into master Sep 27, 2026
12 checks passed
@allen0099
allen0099 deleted the feat/cache-vary branch September 27, 2026 13:47
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.

@cache: vary= to key responses on selected request headers

1 participant