diff --git a/README.md b/README.md
index 3ce1747..6ed1a1c 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
[English](https://fastapi-cachex.readthedocs.io/en/latest/) | [繁體中文](https://fastapi-cachex.readthedocs.io/zh-tw/latest/)
-A high-performance caching extension for FastAPI: a server-side response cache with `Cache-Control` and `ETag` support, application-level caching, and optional session management.
+A high-performance caching extension for FastAPI: a server-side response cache with `Cache-Control` and `ETag` support, and application-level caching.
**Documentation:** — guides and the full API reference.
@@ -26,9 +26,9 @@ A high-performance caching extension for FastAPI: a server-side response cache w
your own code, with compute-on-miss `get_or_set()` and atomic store-if-absent `add()`.
- **Backends** — in-memory, Redis and Memcached, with atomic counters,
one-shot values and locks.
-- **Sessions (optional)** — HMAC-signed or JWT session tokens over headers,
- bearer tokens or cookies, with sliding expiration and IP/User-Agent binding.
-- **OAuth state** — one-time state tokens for CSRF protection in OAuth/OIDC flows.
+- **Sessions and OAuth state (deprecated)**: signed session tokens and one-time
+ OAuth state tokens. Both are deprecated in 0.4.0 and removed in 0.5.0; see
+ [where to move](https://fastapi-cachex.readthedocs.io/en/latest/MIGRATING_0_4/#session-state-deprecated).
## Installation
@@ -105,7 +105,7 @@ async def report(cache: AppCache):
- [Application cache](https://fastapi-cachex.readthedocs.io/en/latest/APP_CACHE/) — `CacheManager`
- [Backends](https://fastapi-cachex.readthedocs.io/en/latest/BACKENDS/) — choosing and configuring a backend, atomic primitives
- [Distributed lock](https://fastapi-cachex.readthedocs.io/en/latest/LOCK/) — `CacheLock` for multi-process mutual exclusion
-- [Session management](https://fastapi-cachex.readthedocs.io/en/latest/SESSION/), [OAuth state](https://fastapi-cachex.readthedocs.io/en/latest/STATE/) (one-shot OAuth/CSRF state tokens) and [JWT claims](https://fastapi-cachex.readthedocs.io/en/latest/JWT_CLAIMS/)
+- Deprecated, removed in 0.5.0: [Session management](https://fastapi-cachex.readthedocs.io/en/latest/SESSION/), [OAuth state](https://fastapi-cachex.readthedocs.io/en/latest/STATE/) (one-shot OAuth/CSRF state tokens) and [JWT claims](https://fastapi-cachex.readthedocs.io/en/latest/JWT_CLAIMS/)
- [Cache flow](https://fastapi-cachex.readthedocs.io/en/latest/CACHE_FLOW/) — what happens inside a cached request
- [Runnable examples](https://github.com/allen0099/FastAPI-CacheX/tree/master/examples) — one complete app per feature, each covered by the test suite
- [API reference](https://fastapi-cachex.readthedocs.io/en/latest/api/http-caching/)
diff --git a/SECURITY.md b/SECURITY.md
index 0c252fe..4c0fbdf 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -13,6 +13,9 @@ still there.
A fix ships as a new 0.3.x patch release; earlier releases are not patched.
+`fastapi_cachex.session` and `fastapi_cachex.state` are deprecated in 0.4.0.
+They get security fixes during 0.4.x and none after 0.5.0 removes them.
+
## Reporting a vulnerability
Please do **not** open a public issue, pull request or discussion for a
diff --git a/changelog.d/420.deprecated.md b/changelog.d/420.deprecated.md
new file mode 100644
index 0000000..85aa7cd
--- /dev/null
+++ b/changelog.d/420.deprecated.md
@@ -0,0 +1,8 @@
+**Sessions and OAuth state are deprecated and will be removed in 0.5.0.**
+FastAPI-CacheX is narrowing to HTTP and application caching. Importing
+`fastapi_cachex.session` or `fastapi_cachex.state`, or reading one of their
+names from the `fastapi_cachex` package, emits a `FutureWarning`; a plain
+`import fastapi_cachex` does not. The names are no longer in
+`fastapi_cachex.__all__`. Both packages get security fixes only until 0.5.0;
+"Sessions and OAuth state are deprecated" in the 0.4.0 migration guide says
+where to move.
diff --git a/docs/JWT_CLAIMS.md b/docs/JWT_CLAIMS.md
index e6a7f23..1d2d565 100644
--- a/docs/JWT_CLAIMS.md
+++ b/docs/JWT_CLAIMS.md
@@ -1,5 +1,8 @@
# JWT Claims: Implementation Notes and Extension Guide
+> [!WARNING]
+> **Deprecated.** `fastapi_cachex.session` is deprecated in 0.4.0 and removed in 0.5.0 ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)). Importing it emits a `FutureWarning`. [Migrating to 0.4.0](MIGRATING_0_4.md#session-state-deprecated) says where to move.
+
## Overview
FastAPI-CacheX's JWT token serializer implements a minimal set of JWT claims to carry session tokens securely. This document explains:
diff --git a/docs/MIGRATING_0_4.md b/docs/MIGRATING_0_4.md
index 86b0da9..dae2f97 100644
--- a/docs/MIGRATING_0_4.md
+++ b/docs/MIGRATING_0_4.md
@@ -26,12 +26,13 @@ Every warning below names the setting to change and links to its issue. `FutureW
| Change | Issue | Warned in 0.3.9 | Section |
|--------|-------|-----------------|---------|
+| Sessions and OAuth state deprecated, removed in 0.5.0 | [#420](https://github.com/allen0099/FastAPI-CacheX/issues/420) | No (0.4.0 warns: `FutureWarning`) | [Sessions and OAuth state are deprecated](#session-state-deprecated) |
| Session cookie defaults to `__Host-session` with `Secure` | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | `FutureWarning` | [Session cookie](#session-cookie) |
| Contradictory `__Host-` / `__Secure-` cookie settings are rejected | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | `UserWarning` (`FutureWarning` when `cookie_name` is left at its default, under the middleware only) | [Session cookie](#session-cookie) |
| Explicit `login()` / `logout()`, read-only `Session.user` | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | No | [Login and logout](#login-logout) |
| `get_or_set()` locks by default | [#280](https://github.com/allen0099/FastAPI-CacheX/issues/280) | `FutureWarning` | [get_or_set lock](#get-or-set-lock) |
-| `get_session_manager` resolves through `SessionManagerProxy` | [#131](https://github.com/allen0099/FastAPI-CacheX/issues/131) | `FutureWarning` | [get_session_manager](#get-session-manager) |
-| `token_source_priority` names every token source; a list without `"cookie"` disables the cookie | [#75](https://github.com/allen0099/FastAPI-CacheX/issues/75) | `FutureWarning` | [Token sources](#token-source-priority) |
+| Dropped: `get_session_manager` resolving through `SessionManagerProxy` | [#131](https://github.com/allen0099/FastAPI-CacheX/issues/131) | `FutureWarning` (0.4.0 no longer warns) | [get_session_manager](#get-session-manager) |
+| Dropped: `token_source_priority` naming every token source | [#75](https://github.com/allen0099/FastAPI-CacheX/issues/75) | `FutureWarning` (0.4.0 no longer warns) | [Token sources](#token-source-priority) |
| `add_routes()` requires `dependencies`, no content preview by default | [#298](https://github.com/allen0099/FastAPI-CacheX/issues/298) | `UserWarning` (only when `dependencies` is left out) | [Monitoring routes](#add-routes) |
| Redis `encoding` option removed | [#126](https://github.com/allen0099/FastAPI-CacheX/issues/126) | `DeprecationWarning` (`RuntimeWarning` for a value other than UTF-8) | [Redis encoding](#redis-encoding) |
| JWT HMAC secrets shorter than the hash output are rejected | [#129](https://github.com/allen0099/FastAPI-CacheX/issues/129) | `UserWarning` | [JWT secret length](#jwt-secret) |
@@ -39,7 +40,7 @@ Every warning below names the setting to change and links to its issue. `FutureW
| `BackendProxy.get_backend()` / `set_backend()` removed | [#70](https://github.com/allen0099/FastAPI-CacheX/issues/70) | `DeprecationWarning` | [BackendProxy](#backend-proxy) |
| `CacheError` removed | [#130](https://github.com/allen0099/FastAPI-CacheX/issues/130) | `DeprecationWarning` | [CacheError](#cache-error) |
| Redis `clear_pattern()` prefix-stripped retry removed | [#125](https://github.com/allen0099/FastAPI-CacheX/issues/125) | `DeprecationWarning` | [Redis clear_pattern](#redis-clear-pattern) |
-| `SessionConfig.use_bearer_token` removed | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [Token sources](#token-source-priority) |
+| `SessionConfig.use_bearer_token` stays deprecated, removed in 0.5.0 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [Token sources](#token-source-priority) |
| `UserSessionDep` requires a user | [#127](https://github.com/allen0099/FastAPI-CacheX/issues/127) | No | [UserSessionDep](#user-session-dep) |
| `memcache` extra removed | [#202](https://github.com/allen0099/FastAPI-CacheX/issues/202) | No | [memcache extra](#memcache-extra) |
| `BaseCacheBackend.delete()` returns `bool` | [#71](https://github.com/allen0099/FastAPI-CacheX/issues/71) | No | [delete() return value](#backend-delete) |
@@ -47,8 +48,40 @@ Every warning below names the setting to change and links to its issue. `FutureW
| HTTP cache key format | [#271](https://github.com/allen0099/FastAPI-CacheX/issues/271), [#270](https://github.com/allen0099/FastAPI-CacheX/issues/270), [#269](https://github.com/allen0099/FastAPI-CacheX/issues/269), [#266](https://github.com/allen0099/FastAPI-CacheX/issues/266), [#265](https://github.com/allen0099/FastAPI-CacheX/issues/265), [#72](https://github.com/allen0099/FastAPI-CacheX/issues/72) | No | [Cache keys](#cache-keys) |
| Conditional session writes | [#128](https://github.com/allen0099/FastAPI-CacheX/issues/128) | No | [Session writes](#session-writes) |
+## Sessions and OAuth state are deprecated {#session-state-deprecated}
+
+`fastapi_cachex.session` and `fastapi_cachex.state` are deprecated in 0.4.0 and removed in 0.5.0 ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420), [#421](https://github.com/allen0099/FastAPI-CacheX/issues/421)). FastAPI-CacheX is narrowing to HTTP and application caching. Session handling and OAuth state are security-sensitive, and libraries built for them maintain them better. 0.3.9 did not announce this, so both packages keep working throughout 0.4.x and get security fixes only.
+
+Importing either package, or reading one of their names from `fastapi_cachex` (such as `fastapi_cachex.SessionConfig`), emits a `FutureWarning` that points at the importing line. `import fastapi_cachex` on its own does not warn, and neither do `@cache`, `CacheManager`, `CacheLock` or the backends. The session and state names are no longer in `fastapi_cachex.__all__`, so `from fastapi_cachex import *` stops providing them. Until you migrate, import them by name.
+
+Where to move:
+
+| You use | Move to |
+|---------|---------|
+| `FastAPICacheXSessionMiddleware` with cookie sessions | Starlette's `SessionMiddleware` keeps small session data in a signed cookie. If sessions must live on the server (large data, server-side revocation), use a server-side session library, for example `starsessions`. |
+| Session tokens in a header or `Authorization: Bearer` for an API | The access tokens of your authentication stack, for example OAuth 2 bearer tokens verified with a JWT library. |
+| `StateManager` for the OAuth/OIDC `state` | Your OAuth client library. Authlib's Starlette integration, for example, creates and checks `state` and `nonce` for you; it keeps them in `request.session`, so it needs Starlette's `SessionMiddleware`. |
+| `CacheManager`, `CacheLock`, `@cache` | Nothing to do: they are not deprecated. |
+
+Sessions and states already in the backend need no cleanup; they expire on their own TTL.
+
+To silence the warning while you migrate, filter it before the first import:
+
+```python
+import warnings
+
+warnings.filterwarnings(
+ "ignore", message="fastapi_cachex.session is deprecated", category=FutureWarning
+)
+warnings.filterwarnings(
+ "ignore", message="fastapi_cachex.state is deprecated", category=FutureWarning
+)
+```
+
## Sessions {#sessions}
+These changes still apply if you keep using sessions during 0.4.x.
+
### Session cookie defaults {#session-cookie}
0.4.0 changes two `SessionConfig` defaults ([#256](https://github.com/allen0099/FastAPI-CacheX/issues/256)): `cookie_name` becomes `"__Host-session"` (from `"session"`) and `cookie_https_only` becomes `True` (from `False`). Browsers accept a `__Host-` cookie only when it is `Secure`, has `Path=/` and no `Domain`, and never from a subdomain, which removes the usual way to plant a session cookie. Two consequences:
@@ -116,23 +149,7 @@ await login(request, SessionUser(user_id=user_id))
### get_session_manager {#get-session-manager}
-`get_session_manager` (and `SessionManagerDep`, `ClientIPDep` and `rotate_session_id()`, which use it) currently returns the manager the middleware stored on `app.state`. 0.4.0 resolves it through `SessionManagerProxy` only, like `BackendProxy` and `CacheManagerProxy` ([#131](https://github.com/allen0099/FastAPI-CacheX/issues/131)). In 0.3.9 it emits a `FutureWarning`, once per app, when the proxy holds no manager or a different one.
-
-Before:
-
-```python
-session_manager = SessionManager(backend, config)
-app.add_middleware(FastAPICacheXSessionMiddleware, session_manager=session_manager)
-```
-
-After:
-
-```python
-session_manager = SessionManager(backend, config)
-SessionManagerProxy.set(session_manager)
-# The middleware picks the manager up from the proxy.
-app.add_middleware(FastAPICacheXSessionMiddleware)
-```
+0.3.9 announced that 0.4.0 would resolve `get_session_manager` (and `SessionManagerDep`, `ClientIPDep` and `rotate_session_id()`, which use it) through `SessionManagerProxy` only ([#131](https://github.com/allen0099/FastAPI-CacheX/issues/131)), and emitted a `FutureWarning`. Since sessions are deprecated ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)), 0.4.0 does not make this change. It keeps returning the manager the middleware stored on `app.state` and no longer warns. If you already call `SessionManagerProxy.set()`, keeping it is harmless.
### UserSessionDep {#user-session-dep}
@@ -196,19 +213,10 @@ SessionConfig(
### Token sources {#token-source-priority}
-In 0.4.0, `SessionConfig.token_source_priority` names every token source ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)). `FastAPICacheXSessionMiddleware` resolves the token by walking the list, `"cookie"` may appear anywhere in it, and the default becomes `["header", "bearer", "cookie"]`, which is the order used today. A list without `"cookie"` means no cookie: the middleware neither reads nor sets it, and a session created for a request without a token sends its token in the `header_name` response header. That is how an API-only app turns the cookie off.
-
-In 0.3.9, `"cookie"` is accepted as the last entry (where the cookie is read today, so it changes nothing yet), and `FastAPICacheXSessionMiddleware` emits a `FutureWarning` when the list was set explicitly without it. The default list does not warn.
-
-```python
-# Before: the cookie is read after these, although the list does not say so
-SessionConfig(secret_key=SECRET, token_source_priority=["bearer", "header"])
-
-# After, keeping the cookie (works on 0.3.9 and 0.4.0)
-SessionConfig(secret_key=SECRET, token_source_priority=["bearer", "header", "cookie"])
-```
+0.3.9 announced two changes here. Since sessions are deprecated ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)), 0.4.0 makes neither of them:
-`use_bearer_token` is removed ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)); the list alone decides whether bearer tokens are read. 0.3.9 emits a `DeprecationWarning` when it is passed, whatever its value:
+- **`token_source_priority` naming every token source ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)) is dropped.** The middleware still reads the session cookie after the header sources whether or not the list names it, and `"cookie"` is still accepted only as the last entry. 0.4.0 no longer emits the `FutureWarning` for a list without `"cookie"`. A list that already ends in `"cookie"` keeps working.
+- **`use_bearer_token` is not removed in 0.4.0 ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)).** It stays deprecated, still emits a `DeprecationWarning` when it is passed, and is removed in 0.5.0 together with `fastapi_cachex.session`. The list alone decides whether bearer tokens are read:
```python
# Before
diff --git a/docs/SESSION.md b/docs/SESSION.md
index 1c73a5b..72a2609 100644
--- a/docs/SESSION.md
+++ b/docs/SESSION.md
@@ -1,5 +1,8 @@
# Session Management Extension
+> [!WARNING]
+> **Deprecated.** `fastapi_cachex.session` is deprecated in 0.4.0 and removed in 0.5.0 ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)). Importing it emits a `FutureWarning`. [Migrating to 0.4.0](MIGRATING_0_4.md#session-state-deprecated) says where to move.
+
FastAPI-CacheX Session Management provides complete user session handling, including signed
tokens, sliding expiration, and optional IP/User-Agent binding. Session contents always live in
the cache backend; the client only holds a single signed token.
@@ -64,9 +67,7 @@ keeps the token it gets back and sends it on later requests.
```
-The example also registers the manager on `SessionManagerProxy`, where
-`get_session_manager` looks for it from 0.4.0 (see
-[Migrating to 0.4.0](MIGRATING_0_4.md#get-session-manager)). With the manager
+The example also registers the manager on `SessionManagerProxy`. With the manager
there, the middleware can pick it up instead of taking it as an argument. When
`config` is omitted, the middleware uses `session_manager.config`:
@@ -296,19 +297,13 @@ the expiry reaches that cap no further renewed tokens are issued.
and only falls back to the cookie when none of them yields a token. The response side follows the
token's source (header in, header out; cookie in, `Set-Cookie` out).
-Until 0.4.0 the cookie is read whether or not the list names it. `"cookie"` is accepted only as the
-last entry, which is where it is read anyway, so listing it changes nothing yet; any other position
-raises a `ValidationError`.
-
-In 0.4.0 the list names every token source, and its default becomes `["header", "bearer",
-"cookie"]`, the order used today. A list without `"cookie"` then means no cookie at all: the
-middleware neither reads nor sets it, and a session created for a request without a token sends
-its token in the `header_name` response header ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)).
-So `FastAPICacheXSessionMiddleware` emits a `FutureWarning` when the list was set explicitly
-without `"cookie"`. Add `"cookie"` as the last entry to keep the cookie; the default list does not
-warn. See [Migrating to 0.4.0](MIGRATING_0_4.md#token-source-priority).
+The cookie is read whether or not the list names it. `"cookie"` is accepted only as the last
+entry, which is where it is read anyway, so listing it changes nothing; any other position raises a
+`ValidationError`. (0.3.9 announced that 0.4.0 would make the list name every token source; that
+change was dropped when sessions were deprecated. See
+[Migrating to 0.4.0](MIGRATING_0_4.md#token-source-priority).)
-`use_bearer_token` is deprecated and removed in 0.4.0 ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)):
+`use_bearer_token` is deprecated and removed in 0.5.0 with this package ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)):
passing it emits a `DeprecationWarning`. Instead of `use_bearer_token=False`, leave `"bearer"` out
of the list (`token_source_priority=["header", "cookie"]`); `use_bearer_token=True` is the
default and can simply be dropped.
@@ -642,11 +637,7 @@ from fastapi_cachex.session.dependencies import (
`get_session_manager` returns the manager the middleware stored on `app.state` when it handled
its first request; it responds with `500` if no session middleware has run yet. Using it avoids
-importing the manager into your route modules. From 0.4.0 it resolves the manager through
-`SessionManagerProxy` instead, so register it there with `SessionManagerProxy.set(manager)`:
-until then, `get_session_manager` (and `SessionManagerDep`, `ClientIPDep` and
-`rotate_session_id()`, which use it) emits a `FutureWarning` once per app when the proxy holds no
-manager or a different one. See [Migrating to 0.4.0](MIGRATING_0_4.md#get-session-manager).
+importing the manager into your route modules.
```python
from fastapi_cachex.session import SessionUser
diff --git a/docs/STATE.md b/docs/STATE.md
index bccf1fa..f1f3fe5 100644
--- a/docs/STATE.md
+++ b/docs/STATE.md
@@ -1,5 +1,8 @@
# State Management Extension
+> [!WARNING]
+> **Deprecated.** `fastapi_cachex.state` is deprecated in 0.4.0 and removed in 0.5.0 ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)). Importing it emits a `FutureWarning`. [Migrating to 0.4.0](MIGRATING_0_4.md#session-state-deprecated) says where to move.
+
`fastapi_cachex.state` provides **one-time state tokens** for OAuth / OIDC authorization
flows. Before starting the authorization, generate a random state and store it in the cache
backend. When the callback comes back, **consume** it. A consumed state cannot be used a
diff --git a/docs/api/session.md b/docs/api/session.md
index 54a1825..7a7dd9e 100644
--- a/docs/api/session.md
+++ b/docs/api/session.md
@@ -1,5 +1,8 @@
# Session
+> [!WARNING]
+> **Deprecated.** `fastapi_cachex.session` is deprecated in 0.4.0 and removed in 0.5.0 ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)). See [Migrating to 0.4.0](../MIGRATING_0_4.md#session-state-deprecated).
+
See the [session management guide](../SESSION.md) for how the pieces fit together.
::: fastapi_cachex.session.middleware.FastAPICacheXSessionMiddleware
diff --git a/docs/api/state.md b/docs/api/state.md
index 842c961..82aa805 100644
--- a/docs/api/state.md
+++ b/docs/api/state.md
@@ -1,5 +1,8 @@
# State
+> [!WARNING]
+> **Deprecated.** `fastapi_cachex.state` is deprecated in 0.4.0 and removed in 0.5.0 ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)). See [Migrating to 0.4.0](../MIGRATING_0_4.md#session-state-deprecated).
+
See the [state management guide](../STATE.md) for the OAuth flow these support.
::: fastapi_cachex.state.manager.StateManager
diff --git a/examples/README.md b/examples/README.md
index e01e929..44a2de4 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -4,6 +4,10 @@ Each file here is a complete FastAPI app that shows one feature of
FastAPI-CacheX. They use only the public API and the in-memory backend (except
`redis_backend.py` and `session_redis.py`), so they run without any server.
+The `session_*.py` and `oauth_state.py` examples use `fastapi_cachex.session`
+and `fastapi_cachex.state`, which are deprecated in 0.4.0 and removed in 0.5.0
+([where to move](https://fastapi-cachex.readthedocs.io/en/latest/MIGRATING_0_4/#session-state-deprecated)).
+
| Example | What it shows | Needs |
|---------|---------------|-------|
| [`http_cache.py`](http_cache.py) | `@cache` with a TTL, ETag and `304 Not Modified`, `no_cache` and `private` routes, `clear_path()` after an update, the monitoring routes behind an admin check | — |
diff --git a/fastapi_cachex/__init__.py b/fastapi_cachex/__init__.py
index 17ddee2..f793606 100644
--- a/fastapi_cachex/__init__.py
+++ b/fastapi_cachex/__init__.py
@@ -1,8 +1,10 @@
"""FastAPI-CacheX: A powerful and flexible caching extension for FastAPI."""
import logging
+from importlib import import_module
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version
+from typing import TYPE_CHECKING
from .cache import build_cache_key as build_cache_key
from .cache import cache as cache
@@ -23,34 +25,39 @@
from .manager_proxy import CacheManagerProxy as CacheManagerProxy
from .proxy import BackendProxy as BackendProxy
from .routes import add_routes as add_routes
-from .session import FastAPICacheXSessionMiddleware as FastAPICacheXSessionMiddleware
-from .session import Session as Session
-from .session import SessionConfig as SessionConfig
-from .session import SessionManager as SessionManager
-from .session import SessionManagerProxy as SessionManagerProxy
-from .session import SessionUser as SessionUser
-from .session import get_optional_session as get_optional_session
-from .session import get_session as get_session
-from .session import get_session_manager as get_session_manager
-from .session import require_session as require_session
-from .session import require_user_session as require_user_session
-from .session.exceptions import SessionError as SessionError
-from .session.exceptions import SessionExpiredError as SessionExpiredError
-from .session.exceptions import SessionInvalidError as SessionInvalidError
-from .session.exceptions import SessionNotFoundError as SessionNotFoundError
-from .session.exceptions import SessionSecurityError as SessionSecurityError
-from .session.exceptions import SessionTokenError as SessionTokenError
-from .state import InvalidStateError as InvalidStateError
-from .state import StateData as StateData
-from .state import StateDataError as StateDataError
-from .state import StateError as StateError
-from .state import StateExpiredError as StateExpiredError
-from .state import StateManager as StateManager
-from .state import StateManagerDep as StateManagerDep
-from .state import StateManagerProxy as StateManagerProxy
-from .state import get_state_manager as get_state_manager
from .types import CacheKeyBuilder as CacheKeyBuilder
+if TYPE_CHECKING:
+ # Type checkers see the real types; at runtime `__getattr__` loads them.
+ from .session import (
+ FastAPICacheXSessionMiddleware as FastAPICacheXSessionMiddleware,
+ )
+ from .session import Session as Session
+ from .session import SessionConfig as SessionConfig
+ from .session import SessionManager as SessionManager
+ from .session import SessionManagerProxy as SessionManagerProxy
+ from .session import SessionUser as SessionUser
+ from .session import get_optional_session as get_optional_session
+ from .session import get_session as get_session
+ from .session import get_session_manager as get_session_manager
+ from .session import require_session as require_session
+ from .session import require_user_session as require_user_session
+ from .session.exceptions import SessionError as SessionError
+ from .session.exceptions import SessionExpiredError as SessionExpiredError
+ from .session.exceptions import SessionInvalidError as SessionInvalidError
+ from .session.exceptions import SessionNotFoundError as SessionNotFoundError
+ from .session.exceptions import SessionSecurityError as SessionSecurityError
+ from .session.exceptions import SessionTokenError as SessionTokenError
+ from .state import InvalidStateError as InvalidStateError
+ from .state import StateData as StateData
+ from .state import StateDataError as StateDataError
+ from .state import StateError as StateError
+ from .state import StateExpiredError as StateExpiredError
+ from .state import StateManager as StateManager
+ from .state import StateManagerDep as StateManagerDep
+ from .state import StateManagerProxy as StateManagerProxy
+ from .state import get_state_manager as get_state_manager
+
def _read_version() -> str:
"""Return the installed distribution's version.
@@ -72,6 +79,51 @@ def _read_version() -> str:
logging.NullHandler()
) # Attach a NullHandler to avoid "No handler found" warnings in user applications.
+# Session and OAuth state names, deprecated in 0.4.0 and removed in 0.5.0
+# (#420). They load on first use, so `import fastapi_cachex` does not import
+# either package; importing one emits its FutureWarning. They are left out of
+# `__all__`, so `from fastapi_cachex import *` does not load them either.
+_DEPRECATED_NAMES = {
+ "FastAPICacheXSessionMiddleware": "fastapi_cachex.session",
+ "InvalidStateError": "fastapi_cachex.state",
+ "Session": "fastapi_cachex.session",
+ "SessionConfig": "fastapi_cachex.session",
+ "SessionError": "fastapi_cachex.session.exceptions",
+ "SessionExpiredError": "fastapi_cachex.session.exceptions",
+ "SessionInvalidError": "fastapi_cachex.session.exceptions",
+ "SessionManager": "fastapi_cachex.session",
+ "SessionManagerProxy": "fastapi_cachex.session",
+ "SessionNotFoundError": "fastapi_cachex.session.exceptions",
+ "SessionSecurityError": "fastapi_cachex.session.exceptions",
+ "SessionTokenError": "fastapi_cachex.session.exceptions",
+ "SessionUser": "fastapi_cachex.session",
+ "StateData": "fastapi_cachex.state",
+ "StateDataError": "fastapi_cachex.state",
+ "StateError": "fastapi_cachex.state",
+ "StateExpiredError": "fastapi_cachex.state",
+ "StateManager": "fastapi_cachex.state",
+ "StateManagerDep": "fastapi_cachex.state",
+ "StateManagerProxy": "fastapi_cachex.state",
+ "get_optional_session": "fastapi_cachex.session",
+ "get_session": "fastapi_cachex.session",
+ "get_session_manager": "fastapi_cachex.session",
+ "get_state_manager": "fastapi_cachex.state",
+ "require_session": "fastapi_cachex.session",
+ "require_user_session": "fastapi_cachex.session",
+}
+
+
+def __getattr__(name: str) -> object:
+ """Resolve a deprecated session or state name on first use."""
+ module_name = _DEPRECATED_NAMES.get(name)
+ if module_name is None:
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg)
+ value = getattr(import_module(module_name), name)
+ globals()[name] = value
+ return value
+
+
__all__ = [
"AppCache",
"BackendNotFoundError",
@@ -83,29 +135,9 @@ def _read_version() -> str:
"CacheManager",
"CacheManagerProxy",
"CacheXError",
- "FastAPICacheXSessionMiddleware",
- "InvalidStateError",
"LockTimeoutError",
"ProxyNotSetError",
"RequestNotFoundError",
- "Session",
- "SessionConfig",
- "SessionError",
- "SessionExpiredError",
- "SessionInvalidError",
- "SessionManager",
- "SessionManagerProxy",
- "SessionNotFoundError",
- "SessionSecurityError",
- "SessionTokenError",
- "SessionUser",
- "StateData",
- "StateDataError",
- "StateError",
- "StateExpiredError",
- "StateManager",
- "StateManagerDep",
- "StateManagerProxy",
"__version__",
"add_routes",
"build_cache_key",
@@ -113,11 +145,5 @@ def _read_version() -> str:
"default_key_builder",
"get_app_cache",
"get_cache_backend",
- "get_optional_session",
- "get_session",
- "get_session_manager",
- "get_state_manager",
"invalidate",
- "require_session",
- "require_user_session",
]
diff --git a/fastapi_cachex/_deprecation.py b/fastapi_cachex/_deprecation.py
new file mode 100644
index 0000000..1245ead
--- /dev/null
+++ b/fastapi_cachex/_deprecation.py
@@ -0,0 +1,68 @@
+"""Deprecation of the session and OAuth state subsystems (#420).
+
+Both packages warn once, when they are first imported, and are removed in
+0.5.0 (#421).
+"""
+
+import inspect
+import warnings
+from types import FrameType
+
+_MIGRATION_URL = (
+ "https://fastapi-cachex.readthedocs.io/en/latest/"
+ "MIGRATING_0_4/#session-state-deprecated"
+)
+
+SESSION_DEPRECATION = (
+ "fastapi_cachex.session is deprecated and will be removed in "
+ "fastapi-cachex 0.5.0. Use Starlette's SessionMiddleware for signed-cookie "
+ f"sessions, or a dedicated session library for server-side sessions; see {_MIGRATION_URL}"
+)
+
+STATE_DEPRECATION = (
+ "fastapi_cachex.state is deprecated and will be removed in "
+ "fastapi-cachex 0.5.0. Use the state handling of your OAuth client library "
+ f"(Authlib, for example); see {_MIGRATION_URL}"
+)
+
+
+def _is_import_machinery(frame: FrameType) -> bool:
+ """Whether ``warnings.warn()`` skips ``frame`` when it counts stacklevel."""
+ filename = frame.f_code.co_filename
+ return "importlib" in filename and "_bootstrap" in filename
+
+
+def _importer_stacklevel() -> int:
+ """Return the ``stacklevel`` of the first frame outside this package.
+
+ Frames of fastapi_cachex and of the import machinery are skipped, so the
+ warning names the application's ``import`` line, or the line that read a
+ deprecated name from the ``fastapi_cachex`` package.
+ """
+ frame = inspect.currentframe()
+ if frame is None or frame.f_back is None: # no frame support
+ return 2
+ # Level 1 is warn_deprecated(); start at its caller.
+ level, frame = 2, frame.f_back.f_back
+ while frame is not None:
+ module = frame.f_globals.get("__name__", "")
+ if not module.startswith(("fastapi_cachex.", "importlib.")) and module not in {
+ "fastapi_cachex",
+ "importlib",
+ }:
+ break
+ # warnings.warn() does not count the frozen import machinery's frames
+ # towards stacklevel, so neither may this.
+ if not _is_import_machinery(frame):
+ level += 1
+ frame = frame.f_back
+ return level
+
+
+def warn_deprecated(message: str) -> None:
+ """Emit the ``FutureWarning`` for a deprecated subsystem.
+
+ ``FutureWarning`` rather than ``DeprecationWarning``: it is shown by
+ default, and the removal affects the application, not only its tests.
+ """
+ warnings.warn(message, FutureWarning, stacklevel=_importer_stacklevel())
diff --git a/fastapi_cachex/cache.py b/fastapi_cachex/cache.py
index c96017b..36356c3 100644
--- a/fastapi_cachex/cache.py
+++ b/fastapi_cachex/cache.py
@@ -45,7 +45,6 @@
from .headers import add_vary
from .proxy import BackendProxy
from .proxy import get_backend_or_fallback
-from .session.config import DEFAULT_SESSION_HEADER_NAME
from .types import CACHE_KEY_SEPARATOR
from .types import CacheEntry
from .types import CacheKeyBuilder
@@ -195,7 +194,9 @@ def _validate_vary(vary: Sequence[str] | None) -> list[str]:
"authorization",
"proxy-authorization",
"cookie",
- DEFAULT_SESSION_HEADER_NAME.lower(),
+ # The session token header (`SessionConfig.header_name`'s default).
+ # Inlined, so @cache does not import the deprecated session package.
+ "x-session-token",
}
)
_HASHED_VARY_MARKER = "sha256:"
diff --git a/fastapi_cachex/session/__init__.py b/fastapi_cachex/session/__init__.py
index 7d7f76e..e7fbc5f 100644
--- a/fastapi_cachex/session/__init__.py
+++ b/fastapi_cachex/session/__init__.py
@@ -1,4 +1,11 @@
-"""Session management extension for FastAPI-CacheX."""
+"""Session management extension for FastAPI-CacheX.
+
+Deprecated in 0.4.0 and removed in 0.5.0 (#420): importing this package
+emits a ``FutureWarning``.
+"""
+
+from fastapi_cachex._deprecation import SESSION_DEPRECATION as _SESSION_DEPRECATION
+from fastapi_cachex._deprecation import warn_deprecated as _warn_deprecated
from .config import SessionConfig
from .dependencies import get_optional_session
@@ -17,6 +24,8 @@
from .models import SessionUser
from .proxy import SessionManagerProxy
+_warn_deprecated(_SESSION_DEPRECATION)
+
__all__ = [
"FastAPICacheXSessionMiddleware",
"Session",
diff --git a/fastapi_cachex/session/config.py b/fastapi_cachex/session/config.py
index 1900908..a0399bb 100644
--- a/fastapi_cachex/session/config.py
+++ b/fastapi_cachex/session/config.py
@@ -108,13 +108,13 @@ class SessionConfig(BaseModel):
use_bearer_token: bool = Field(
default=True,
description="Whether to accept Authorization Bearer tokens (deprecated: "
- 'leave "bearer" out of token_source_priority instead; removed in 0.4.0)',
+ 'leave "bearer" out of token_source_priority instead)',
)
token_source_priority: list[Literal["header", "bearer", "cookie"]] = Field(
default=["header", "bearer"],
description="Priority order for token sources. The session cookie is "
- 'read after the header sources; "cookie" may only be the last entry '
- "until 0.4.0, which drops the cookie from a list without it",
+ "read after the header sources, whether or not the list names it; "
+ '"cookie" may only be the last entry',
)
# JWT settings (used when token_format == 'jwt')
@@ -302,24 +302,23 @@ def _check_cookie_prefix(self) -> "SessionConfig":
def _check_cookie_is_last(
cls, value: list[Literal["header", "bearer", "cookie"]]
) -> list[Literal["header", "bearer", "cookie"]]:
- """Only accept ``"cookie"`` where the cookie is read today: last.
+ """Only accept ``"cookie"`` where the cookie is read: last.
``FastAPICacheXSessionMiddleware`` reads the cookie after the header
- sources, so listing it last changes nothing before 0.4.0, when the
- list decides the order and a list without it disables the cookie (#75).
+ sources whether or not the list names it, so listing it anywhere else
+ would promise an order the middleware does not follow.
"""
if "cookie" in value and value.index("cookie") != len(value) - 1:
msg = (
'"cookie" must be the last entry of token_source_priority: the '
- "session cookie is read after the header sources until 0.4.0 "
- "(https://github.com/allen0099/FastAPI-CacheX/issues/75)"
+ "session cookie is read after the header sources"
)
raise ValueError(msg)
return value
@model_validator(mode="after")
def _warn_use_bearer_token(self) -> "SessionConfig":
- """Deprecate ``use_bearer_token``, which 0.4.0 removes (#377).
+ """Deprecate ``use_bearer_token``, removed with the session package in 0.5.0.
``token_source_priority`` already decides whether bearer tokens are
read, so passing the flag at all warns, whatever its value.
@@ -327,7 +326,8 @@ def _warn_use_bearer_token(self) -> "SessionConfig":
if "use_bearer_token" in self.model_fields_set:
warnings.warn(
"SessionConfig(use_bearer_token=...) is deprecated and will be "
- "removed in version 0.4.0. token_source_priority decides the "
+ "removed in version 0.5.0 with fastapi_cachex.session. "
+ "token_source_priority decides the "
"token sources: drop use_bearer_token=True (the default), and "
'replace use_bearer_token=False by leaving "bearer" out of '
'token_source_priority, keeping "cookie" last, e.g. '
diff --git a/fastapi_cachex/session/dependencies.py b/fastapi_cachex/session/dependencies.py
index 741e812..9722e01 100644
--- a/fastapi_cachex/session/dependencies.py
+++ b/fastapi_cachex/session/dependencies.py
@@ -1,6 +1,5 @@
"""FastAPI dependency injection utilities for session management."""
-import warnings
from collections.abc import Iterable
from typing import TYPE_CHECKING
from typing import Annotated
@@ -12,24 +11,18 @@
from fastapi.security import HTTPAuthorizationCredentials
from fastapi.security import HTTPBearer
-from fastapi_cachex.exceptions import ProxyNotSetError
-
from .exceptions import SessionError
from .middleware import _SESSION_READ_KEY
from .middleware import _log_in
from .middleware import _RequestSession
from .middleware import get_client_ip
from .models import Session
-from .proxy import SessionManagerProxy
if TYPE_CHECKING:
from .manager import SessionManager
from .models import SessionUser
-# ``app.state`` flag: get_session_manager() already warned about #131 there.
-_PROXY_WARNED = "__fastapi_cachex_session_manager_proxy_warned"
-
# HTTPBearer security scheme for OpenAPI UI
_http_bearer = HTTPBearer(
scheme_name="SessionBearer",
@@ -159,12 +152,6 @@ async def login(
Raises:
HTTPException: 500 if no session middleware has registered a
SessionManager yet
-
- Warns:
- FutureWarning: Once per app, if the manager the middleware registered
- is not the one set with ``SessionManagerProxy.set()`` (or none is
- set there). 0.4.0 resolves this dependency through
- ``SessionManagerProxy`` only.
"""
state = request.app.state
manager: SessionManager | None = getattr(
@@ -178,35 +165,9 @@ async def login(
"FastAPICacheXSessionMiddleware is added to the app."
),
)
- proxy_manager = _proxy_manager()
- if not getattr(state, _PROXY_WARNED, False) and manager is not proxy_manager:
- setattr(state, _PROXY_WARNED, True)
- registered = (
- "no SessionManager is set in SessionManagerProxy"
- if proxy_manager is None
- else "a different SessionManager is set in SessionManagerProxy"
- )
- warnings.warn(
- "get_session_manager() returned the SessionManager the session "
- f"middleware registered, but {registered}. Version 0.4.0 resolves "
- "get_session_manager() (and SessionManagerDep, ClientIPDep and "
- "rotate_session_id(), which use it) through SessionManagerProxy "
- "only. Call SessionManagerProxy.set(session_manager) at startup "
- "(https://github.com/allen0099/FastAPI-CacheX/issues/131).",
- FutureWarning,
- stacklevel=2,
- )
return manager
-def _proxy_manager() -> "SessionManager | None":
- """Return the manager set in ``SessionManagerProxy``, or None."""
- try:
- return SessionManagerProxy.get()
- except ProxyNotSetError:
- return None
-
-
async def rotate_session_id(request: Request) -> bool:
"""Give the request's session a new ID, as a defence against session fixation.
diff --git a/fastapi_cachex/session/middleware.py b/fastapi_cachex/session/middleware.py
index 050088d..664e203 100644
--- a/fastapi_cachex/session/middleware.py
+++ b/fastapi_cachex/session/middleware.py
@@ -1,7 +1,6 @@
"""Session middleware for FastAPI."""
import logging
-import warnings
from typing import TYPE_CHECKING
from typing import Any
@@ -165,36 +164,6 @@ def _stash_session_manager(app: Any, manager: SessionManager) -> None:
setattr(app.state, "__fastapi_cachex_session_manager", manager)
-def _warn_if_priority_without_cookie(config: SessionConfig) -> None:
- """Warn that 0.4.0 stops using the cookie for a list without ``"cookie"``.
-
- In 0.4.0 ``token_source_priority`` lists every token source, so a list
- set without ``"cookie"`` means the middleware neither reads nor sets the
- session cookie (#75). The default list becomes ``["header", "bearer",
- "cookie"]``, which is today's order, so it does not warn.
-
- Args:
- config: The configuration the middleware uses
- """
- if (
- "token_source_priority" not in config.model_fields_set
- or "cookie" in config.token_source_priority
- ):
- return
- warnings.warn(
- f"SessionConfig(token_source_priority={config.token_source_priority!r}) "
- 'does not list "cookie". In version 0.4.0 the list names every token '
- "source, so FastAPICacheXSessionMiddleware will neither read nor set the "
- "session cookie, and a session created for a request without a token "
- 'sends its token in the header_name response header. Add "cookie" as the '
- "last entry to keep the cookie; leaving it out opts into 0.4.0's "
- "cookie-less behaviour, which only takes effect then "
- "(https://github.com/allen0099/FastAPI-CacheX/issues/75).",
- FutureWarning,
- stacklevel=3,
- )
-
-
class _RequestSession(StarletteSession):
"""``request.session`` that remembers an explicit ``clear()``.
@@ -251,15 +220,10 @@ def __init__(
Raises:
ProxyNotSetError: If ``session_manager`` is omitted and
``SessionManagerProxy`` holds none.
-
- Warns:
- FutureWarning: If the configuration sets ``token_source_priority``
- without ``"cookie"``, which disables the cookie in 0.4.0.
"""
self.app = app
self.session_manager = session_manager or SessionManagerProxy.get()
self.config = config or self.session_manager.config
- _warn_if_priority_without_cookie(self.config)
security_flags = f"httponly; samesite={self.config.cookie_same_site}"
if self.config.cookie_https_only:
diff --git a/fastapi_cachex/state/__init__.py b/fastapi_cachex/state/__init__.py
index 560b07b..44cc25c 100644
--- a/fastapi_cachex/state/__init__.py
+++ b/fastapi_cachex/state/__init__.py
@@ -1,4 +1,11 @@
-"""State management extension for FastAPI-CacheX."""
+"""State management extension for FastAPI-CacheX.
+
+Deprecated in 0.4.0 and removed in 0.5.0 (#420): importing this package
+emits a ``FutureWarning``.
+"""
+
+from fastapi_cachex._deprecation import STATE_DEPRECATION as _STATE_DEPRECATION
+from fastapi_cachex._deprecation import warn_deprecated as _warn_deprecated
from .dependencies import StateManagerDep as StateManagerDep
from .dependencies import get_state_manager as get_state_manager
@@ -9,3 +16,5 @@
from .manager import StateManager as StateManager
from .models import StateData as StateData
from .proxy import StateManagerProxy as StateManagerProxy
+
+_warn_deprecated(_STATE_DEPRECATION)
diff --git a/i18n/zh-TW/docs/JWT_CLAIMS.md b/i18n/zh-TW/docs/JWT_CLAIMS.md
index b5ec0da..18311c5 100644
--- a/i18n/zh-TW/docs/JWT_CLAIMS.md
+++ b/i18n/zh-TW/docs/JWT_CLAIMS.md
@@ -1,5 +1,8 @@
# JWT claims:實作說明與擴充指南 {#jwt-claims-implementation-notes-and-extension-guide}
+> [!WARNING]
+> **已棄用。** `fastapi_cachex.session` 在 0.4.0 已棄用,並將在 0.5.0 移除([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420))。匯入時會發出 `FutureWarning`。遷移方向請見[遷移至 0.4.0](MIGRATING_0_4.md#session-state-deprecated)。
+
## 概觀 {#overview}
FastAPI-CacheX 的 JWT 權杖序列化器只實作了最小的一組 JWT claim,用來安全地承載 Session 權杖。本文件說明:
diff --git a/i18n/zh-TW/docs/MIGRATING_0_4.md b/i18n/zh-TW/docs/MIGRATING_0_4.md
index b799f4b..2589329 100644
--- a/i18n/zh-TW/docs/MIGRATING_0_4.md
+++ b/i18n/zh-TW/docs/MIGRATING_0_4.md
@@ -26,12 +26,13 @@ filterwarnings = [
| 變更 | Issue | 0.3.9 是否警告 | 章節 |
|------|-------|----------------|------|
+| Session 與 OAuth state 已棄用,0.5.0 移除 | [#420](https://github.com/allen0099/FastAPI-CacheX/issues/420) | 否(0.4.0 會發出 `FutureWarning`) | [Session 與 OAuth state 已棄用](#session-state-deprecated) |
| Session Cookie 預設為 `__Host-session` 並帶 `Secure` | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | `FutureWarning` | [Session Cookie](#session-cookie) |
| 拒絕互相矛盾的 `__Host-`/`__Secure-` Cookie 設定 | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | `UserWarning`(`cookie_name` 維持預設值時為 `FutureWarning`,僅限使用中介軟體時) | [Session Cookie](#session-cookie) |
| 明確的 `login()`/`logout()`、唯讀的 `Session.user` | [#256](https://github.com/allen0099/FastAPI-CacheX/issues/256) | 否 | [登入與登出](#login-logout) |
| `get_or_set()` 預設使用鎖 | [#280](https://github.com/allen0099/FastAPI-CacheX/issues/280) | `FutureWarning` | [get_or_set 的鎖](#get-or-set-lock) |
-| `get_session_manager` 透過 `SessionManagerProxy` 取得 | [#131](https://github.com/allen0099/FastAPI-CacheX/issues/131) | `FutureWarning` | [get_session_manager](#get-session-manager) |
-| `token_source_priority` 列出所有權杖來源;沒有 `"cookie"` 的清單會停用 Cookie | [#75](https://github.com/allen0099/FastAPI-CacheX/issues/75) | `FutureWarning` | [權杖來源](#token-source-priority) |
+| 已取消:`get_session_manager` 透過 `SessionManagerProxy` 取得 | [#131](https://github.com/allen0099/FastAPI-CacheX/issues/131) | `FutureWarning`(0.4.0 不再警告) | [get_session_manager](#get-session-manager) |
+| 已取消:`token_source_priority` 列出所有權杖來源 | [#75](https://github.com/allen0099/FastAPI-CacheX/issues/75) | `FutureWarning`(0.4.0 不再警告) | [權杖來源](#token-source-priority) |
| `add_routes()` 必須傳入 `dependencies`,預設不含內容預覽 | [#298](https://github.com/allen0099/FastAPI-CacheX/issues/298) | `UserWarning`(僅在省略 `dependencies` 時) | [監控路由](#add-routes) |
| 移除 Redis 的 `encoding` 選項 | [#126](https://github.com/allen0099/FastAPI-CacheX/issues/126) | `DeprecationWarning`(UTF-8 以外的值為 `RuntimeWarning`) | [Redis encoding](#redis-encoding) |
| 拒絕短於雜湊輸出的 JWT HMAC 密鑰 | [#129](https://github.com/allen0099/FastAPI-CacheX/issues/129) | `UserWarning` | [JWT 密鑰長度](#jwt-secret) |
@@ -39,7 +40,7 @@ filterwarnings = [
| 移除 `BackendProxy.get_backend()`/`set_backend()` | [#70](https://github.com/allen0099/FastAPI-CacheX/issues/70) | `DeprecationWarning` | [BackendProxy](#backend-proxy) |
| 移除 `CacheError` | [#130](https://github.com/allen0099/FastAPI-CacheX/issues/130) | `DeprecationWarning` | [CacheError](#cache-error) |
| 移除 Redis `clear_pattern()` 去除前綴後的重試 | [#125](https://github.com/allen0099/FastAPI-CacheX/issues/125) | `DeprecationWarning` | [Redis clear_pattern](#redis-clear-pattern) |
-| 移除 `SessionConfig.use_bearer_token` | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [權杖來源](#token-source-priority) |
+| `SessionConfig.use_bearer_token` 維持已棄用,0.5.0 移除 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [權杖來源](#token-source-priority) |
| `UserSessionDep` 需要使用者 | [#127](https://github.com/allen0099/FastAPI-CacheX/issues/127) | 否 | [UserSessionDep](#user-session-dep) |
| 移除 `memcache` extra | [#202](https://github.com/allen0099/FastAPI-CacheX/issues/202) | 否 | [memcache extra](#memcache-extra) |
| `BaseCacheBackend.delete()` 回傳 `bool` | [#71](https://github.com/allen0099/FastAPI-CacheX/issues/71) | 否 | [delete() 的回傳值](#backend-delete) |
@@ -47,8 +48,40 @@ filterwarnings = [
| HTTP 快取鍵格式 | [#271](https://github.com/allen0099/FastAPI-CacheX/issues/271)、[#270](https://github.com/allen0099/FastAPI-CacheX/issues/270)、[#269](https://github.com/allen0099/FastAPI-CacheX/issues/269)、[#266](https://github.com/allen0099/FastAPI-CacheX/issues/266)、[#265](https://github.com/allen0099/FastAPI-CacheX/issues/265)、[#72](https://github.com/allen0099/FastAPI-CacheX/issues/72) | 否 | [快取鍵](#cache-keys) |
| 有條件的 Session 寫入 | [#128](https://github.com/allen0099/FastAPI-CacheX/issues/128) | 否 | [Session 寫入](#session-writes) |
+## Session 與 OAuth state 已棄用 {#session-state-deprecated}
+
+`fastapi_cachex.session` 與 `fastapi_cachex.state` 在 0.4.0 已棄用,並將在 0.5.0 移除([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)、[#421](https://github.com/allen0099/FastAPI-CacheX/issues/421))。FastAPI-CacheX 的範圍將收斂到 HTTP 快取與應用層快取。Session 處理與 OAuth state 都牽涉安全性,由專門的函式庫維護會更好。由於 0.3.9 沒有預告這項變更,兩個套件在整個 0.4.x 期間都能繼續使用,但只會收到安全性修正。
+
+匯入其中任一個套件,或從 `fastapi_cachex` 讀取它們的名稱(例如 `fastapi_cachex.SessionConfig`),都會發出 `FutureWarning`,並指向匯入的那一行。單純 `import fastapi_cachex` 不會發出警告,`@cache`、`CacheManager`、`CacheLock` 與各後端也不會。Session 與 state 的名稱已不在 `fastapi_cachex.__all__` 中,因此 `from fastapi_cachex import *` 不再提供它們。遷移完成前,請以名稱個別匯入。
+
+遷移方向:
+
+| 目前使用 | 改用 |
+|----------|------|
+| 搭配 Cookie Session 的 `FastAPICacheXSessionMiddleware` | Starlette 的 `SessionMiddleware` 會把少量的 Session 資料存放在簽署過的 Cookie 中。若 Session 必須存放在伺服器端(資料量大,或需要在伺服器端撤銷),請改用伺服器端 Session 函式庫,例如 `starsessions`。 |
+| API 以標頭或 `Authorization: Bearer` 傳遞 Session 權杖 | 改用驗證機制本身的存取權杖,例如以 JWT 函式庫驗證的 OAuth 2 Bearer 權杖。 |
+| 以 `StateManager` 處理 OAuth/OIDC 的 `state` | 改用 OAuth 用戶端函式庫。例如 Authlib 的 Starlette 整合會替你產生並檢查 `state` 與 `nonce`;它把這些值存放在 `request.session`,因此需要 Starlette 的 `SessionMiddleware`。 |
+| `CacheManager`、`CacheLock`、`@cache` | 不需處理,這些並未棄用。 |
+
+後端中已存在的 Session 與 state 不需要清理,會依各自的 TTL 自行過期。
+
+遷移期間若要隱藏警告,請在第一次匯入之前加上篩選:
+
+```python
+import warnings
+
+warnings.filterwarnings(
+ "ignore", message="fastapi_cachex.session is deprecated", category=FutureWarning
+)
+warnings.filterwarnings(
+ "ignore", message="fastapi_cachex.state is deprecated", category=FutureWarning
+)
+```
+
## Session {#sessions}
+若在 0.4.x 期間繼續使用 Session,下列變更仍然適用。
+
### Session Cookie 預設值 {#session-cookie}
0.4.0 會改變兩個 `SessionConfig` 預設值([#256](https://github.com/allen0099/FastAPI-CacheX/issues/256)):`cookie_name` 從 `"session"` 改為 `"__Host-session"`,`cookie_https_only` 從 `False` 改為 `True`。瀏覽器只接受帶 `Secure`、`Path=/` 且沒有 `Domain` 的 `__Host-` Cookie,也不接受子網域設定的這種 Cookie,因此移除了植入 Session Cookie 最常見的途徑。這帶來兩個後果:
@@ -116,22 +149,7 @@ await login(request, SessionUser(user_id=user_id))
### get_session_manager {#get-session-manager}
-`get_session_manager`(以及使用它的 `SessionManagerDep`、`ClientIPDep` 與 `rotate_session_id()`)目前回傳中介軟體存放在 `app.state` 的管理器。0.4.0 改為只透過 `SessionManagerProxy` 取得,與 `BackendProxy` 和 `CacheManagerProxy` 一致([#131](https://github.com/allen0099/FastAPI-CacheX/issues/131))。在 0.3.9 中,當 proxy 沒有管理器或持有不同的管理器時,每個應用程式會發出一次 `FutureWarning`。
-
-修改前:
-
-```python
-session_manager = SessionManager(backend, config)
-app.add_middleware(FastAPICacheXSessionMiddleware, session_manager=session_manager)
-```
-
-修改後:
-
-```python
-session_manager = SessionManager(backend, config)
-SessionManagerProxy.set(session_manager)
-app.add_middleware(FastAPICacheXSessionMiddleware) # 從 proxy 取得管理器
-```
+0.3.9 曾預告 0.4.0 會改為只透過 `SessionManagerProxy` 取得 `get_session_manager`(以及使用它的 `SessionManagerDep`、`ClientIPDep` 與 `rotate_session_id()`)([#131](https://github.com/allen0099/FastAPI-CacheX/issues/131)),並發出 `FutureWarning`。由於 Session 已棄用([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)),0.4.0 不會做這項變更:它仍回傳中介軟體存放在 `app.state` 的管理器,也不再發出警告。若你已經呼叫 `SessionManagerProxy.set()`,保留它也沒有影響。
### UserSessionDep {#user-session-dep}
@@ -195,19 +213,10 @@ SessionConfig(
### 權杖來源 {#token-source-priority}
-0.4.0 起,`SessionConfig.token_source_priority` 列出所有權杖來源([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75))。`FastAPICacheXSessionMiddleware` 會依照清單順序尋找權杖,`"cookie"` 可以放在任何位置,預設值改為 `["header", "bearer", "cookie"]`,也就是目前使用的順序。沒有 `"cookie"` 的清單代表不使用 Cookie:中介軟體既不讀取也不設定它,而為沒有權杖的請求建立的 Session 會在 `header_name` 回應標頭中送出權杖。純 API 的應用就是用這種方式關閉 Cookie。
-
-0.3.9 接受把 `"cookie"` 放在最後一項(也就是目前讀取 Cookie 的位置,因此暫時不會改變任何行為);明確設定的清單沒有它時,`FastAPICacheXSessionMiddleware` 會發出 `FutureWarning`。預設清單不會發出警告。
-
-```python
-# 之前:雖然清單沒寫,Cookie 仍會在這兩者之後被讀取
-SessionConfig(secret_key=SECRET, token_source_priority=["bearer", "header"])
-
-# 之後,保留 Cookie(在 0.3.9 與 0.4.0 皆可運作)
-SessionConfig(secret_key=SECRET, token_source_priority=["bearer", "header", "cookie"])
-```
+0.3.9 在這裡預告了兩項變更。由於 Session 已棄用([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)),0.4.0 兩項都不會做:
-`use_bearer_token` 已移除([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)),是否讀取 Bearer 權杖只由清單決定。0.3.9 在傳入它時,不論值為何,都會發出 `DeprecationWarning`:
+- **取消 `token_source_priority` 列出所有權杖來源([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75))。** 不論清單是否列出 `"cookie"`,中介軟體仍會在標頭來源之後讀取 Session Cookie,且 `"cookie"` 仍只能放在最後一項。0.4.0 不再對沒有 `"cookie"` 的清單發出 `FutureWarning`。已經以 `"cookie"` 結尾的清單可以照常使用。
+- **0.4.0 不會移除 `use_bearer_token`([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377))。** 它維持已棄用,傳入時仍會發出 `DeprecationWarning`,並在 0.5.0 隨 `fastapi_cachex.session` 一起移除。是否讀取 Bearer 權杖只由清單決定:
```python
# 之前
diff --git a/i18n/zh-TW/docs/SESSION.md b/i18n/zh-TW/docs/SESSION.md
index f98c039..23c7722 100644
--- a/i18n/zh-TW/docs/SESSION.md
+++ b/i18n/zh-TW/docs/SESSION.md
@@ -1,5 +1,8 @@
# Session 管理擴充 {#session-management-extension}
+> [!WARNING]
+> **已棄用。** `fastapi_cachex.session` 在 0.4.0 已棄用,並將在 0.5.0 移除([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420))。匯入時會發出 `FutureWarning`。遷移方向請見[遷移至 0.4.0](MIGRATING_0_4.md#session-state-deprecated)。
+
FastAPI-CacheX 的 Session 管理提供完整的使用者 Session 處理,包括簽署過的權杖、滑動過期,以及可選的 IP / User-Agent 綁定。Session 內容一律存放在快取後端;用戶端只持有一個簽署過的權杖。
**`FastAPICacheXSessionMiddleware` 如何傳遞權杖:**
@@ -55,7 +58,7 @@ uv add "fastapi-cachex[jwt]"
```
-範例也把管理器註冊到 `SessionManagerProxy`,0.4.0 起 `get_session_manager` 只會從那裡取得它(見[遷移至 0.4.0](MIGRATING_0_4.md#get-session-manager))。管理器註冊在那裡之後,中介軟體也可以從 proxy 取得,而不必以參數傳入。省略 `config` 時,中介軟體會使用 `session_manager.config`:
+範例也把管理器註冊到 `SessionManagerProxy`。管理器註冊在那裡之後,中介軟體可以從 proxy 取得,而不必以參數傳入。省略 `config` 時,中介軟體會使用 `session_manager.config`:
```python
app.add_middleware(FastAPICacheXSessionMiddleware) # 從 proxy 取得
@@ -182,11 +185,9 @@ Session 會在 `session_ttl` 秒後過期。啟用 `sliding_expiration` 時,
`FastAPICacheXSessionMiddleware` 先依照 `token_source_priority` 的順序讀取標頭來源,只有它們都沒有產生權杖時才退回使用 Cookie。回應端依權杖的來源決定(標頭進、標頭出;Cookie 進、`Set-Cookie` 出)。
-在 0.4.0 以前,不論清單是否列出 Cookie,都會讀取 Cookie。`"cookie"` 只能放在清單的最後一項,也就是它現在本來就被讀取的位置,因此列出它目前不會改變任何行為;放在其他位置會引發 `ValidationError`。
-
-0.4.0 起,這個清單列出所有權杖來源,預設值改為 `["header", "bearer", "cookie"]`,也就是目前使用的順序。沒有 `"cookie"` 的清單代表完全不使用 Cookie:中介軟體既不讀取也不設定它,而為沒有權杖的請求建立的 Session 會在 `header_name` 回應標頭中送出權杖([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75))。因此,明確設定了不含 `"cookie"` 的清單時,`FastAPICacheXSessionMiddleware` 會發出 `FutureWarning`。要保留 Cookie,請把 `"cookie"` 加在最後一項;預設清單不會發出警告。見[遷移至 0.4.0](MIGRATING_0_4.md#token-source-priority)。
+不論清單是否列出 Cookie,都會讀取 Cookie。`"cookie"` 只能放在清單的最後一項,也就是它本來就被讀取的位置,因此列出它不會改變任何行為;放在其他位置會引發 `ValidationError`。(0.3.9 曾預告 0.4.0 會讓清單列出所有權杖來源;Session 棄用後,這項變更已取消。請參閱[遷移至 0.4.0](MIGRATING_0_4.md#token-source-priority)。)
-`use_bearer_token` 已棄用,並於 0.4.0 移除([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)):傳入它會發出 `DeprecationWarning`。請以不在清單中列出 `"bearer"`(`token_source_priority=["header", "cookie"]`)取代 `use_bearer_token=False`;`use_bearer_token=True` 是預設值,直接拿掉即可。
+`use_bearer_token` 已棄用,並將在 0.5.0 隨本套件一起移除([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)):傳入它會發出 `DeprecationWarning`。請以不在清單中列出 `"bearer"`(`token_source_priority=["header", "cookie"]`)取代 `use_bearer_token=False`;`use_bearer_token=True` 是預設值,直接拿掉即可。
**標頭/Bearer 用戶端**應將權杖存放在 `localStorage` 或 `sessionStorage`,並以 `Authorization: Bearer ` 或 `X-Session-Token: ` 送出。**Cookie 用戶端**(瀏覽器)不需要自行處理權杖,但要留意 CSRF:瀏覽器會自動附上 Cookie,因此請將 `cookie_same_site` 與你自己的 CSRF 防護搭配使用。
@@ -412,7 +413,7 @@ from fastapi_cachex.session.dependencies import (
)
```
-`get_session_manager` 回傳中介軟體在處理第一個請求時存放在 `app.state` 上的管理器;若尚未有任何 Session 中介軟體執行過,它會回應 `500`。使用它可以避免在路由模組中匯入管理器。0.4.0 起它改為透過 `SessionManagerProxy` 取得管理器,因此請以 `SessionManagerProxy.set(manager)` 註冊:在那之前,當 proxy 沒有管理器或持有不同的管理器時,`get_session_manager`(以及使用它的 `SessionManagerDep`、`ClientIPDep` 與 `rotate_session_id()`)每個應用程式會發出一次 `FutureWarning`。請參閱[遷移至 0.4.0](MIGRATING_0_4.md#get-session-manager)。
+`get_session_manager` 回傳中介軟體在處理第一個請求時存放在 `app.state` 上的管理器;若尚未有任何 Session 中介軟體執行過,它會回應 `500`。使用它可以避免在路由模組中匯入管理器。
```python
from fastapi_cachex.session import SessionUser
diff --git a/i18n/zh-TW/docs/STATE.md b/i18n/zh-TW/docs/STATE.md
index 3bb6edc..ba0b750 100644
--- a/i18n/zh-TW/docs/STATE.md
+++ b/i18n/zh-TW/docs/STATE.md
@@ -1,5 +1,8 @@
# State 管理擴充 {#state-management-extension}
+> [!WARNING]
+> **已棄用。** `fastapi_cachex.state` 在 0.4.0 已棄用,並將在 0.5.0 移除([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420))。匯入時會發出 `FutureWarning`。遷移方向請見[遷移至 0.4.0](MIGRATING_0_4.md#session-state-deprecated)。
+
`fastapi_cachex.state` 為 OAuth / OIDC 授權流程提供**一次性 state 權杖**。開始授權之前,先產生一個隨機 state 並存入快取後端;回呼(callback)回來時,再將它**消耗**掉。已消耗的 state 無法再使用第二次。
只有當 state **綁定到發起流程的瀏覽器**時,它才能保護流程免於 CSRF 攻擊(RFC 6749 §10.12)。光是儲存並不夠:攻擊者可以在自己的瀏覽器中發起流程,再把受害者導向帶有攻擊者 state 與 code 的回呼,使受害者登入攻擊者的帳號。請在建立 state 時傳入 `binding`(一個同時設為 Cookie 的隨機 nonce),並在消耗時傳入相同的值,如下方快速開始所示。
diff --git a/i18n/zh-TW/docs/index.md b/i18n/zh-TW/docs/index.md
index f4feb18..e9ba183 100644
--- a/i18n/zh-TW/docs/index.md
+++ b/i18n/zh-TW/docs/index.md
@@ -14,7 +14,7 @@
[English](https://fastapi-cachex.readthedocs.io/en/latest/) | [繁體中文](https://fastapi-cachex.readthedocs.io/zh-tw/latest/)
-FastAPI-CacheX 是 FastAPI 的高效能快取擴充套件:提供支援 `Cache-Control` 與 `ETag` 的伺服器端回應快取、應用層快取,以及可選的 Session 管理。
+FastAPI-CacheX 是 FastAPI 的高效能快取擴充套件:提供支援 `Cache-Control` 與 `ETag` 的伺服器端回應快取,以及應用層快取。
**文件:** (尚未翻譯的頁面與完整 API 參考請見[英文文件](https://fastapi-cachex.readthedocs.io/en/latest/))
@@ -23,8 +23,7 @@ FastAPI-CacheX 是 FastAPI 的高效能快取擴充套件:提供支援 `Cache-
- **HTTP 快取**:GET 路由專用的 `@cache` 裝飾器,支援 `Cache-Control`、`ETag` / `If-None-Match`(304)與單一路由的快取失效。
- **應用層快取**:`CacheManager` 可在自己的程式碼中快取任意 JSON 值,提供未命中時才計算的 `get_or_set()` 與原子性的「不存在才寫入」`add()`。
- **後端**:記憶體、Redis 與 Memcached,支援原子操作的計數器、一次性取值與鎖。
-- **Session(可選)**:以 HMAC 簽章或 JWT 發行的 Session 權杖,可經由標頭、Bearer 權杖或 Cookie 傳遞,支援滑動過期與 IP / User-Agent 綁定。
-- **OAuth state**:OAuth / OIDC 流程中用於防範 CSRF 的一次性 state 權杖。
+- **Session 與 OAuth state(已棄用)**:簽署過的 Session 權杖與一次性的 OAuth state 權杖。兩者在 0.4.0 已棄用,並將在 0.5.0 移除,遷移方向請見[這裡](MIGRATING_0_4.md#session-state-deprecated)。
## 安裝 {#installation}
@@ -94,7 +93,7 @@ async def report(cache: AppCache):
- [應用層快取](APP_CACHE.md):`CacheManager`
- [後端](BACKENDS.md):選擇與設定後端、原子操作的基本功能
- [分散式鎖](LOCK.md):以 `CacheLock` 在多個行程之間互斥
-- [Session 管理](SESSION.md)、[OAuth state](STATE.md)(一次性的 OAuth / CSRF state 權杖)與 [JWT claims](JWT_CLAIMS.md)
+- 已棄用、0.5.0 移除:[Session 管理](SESSION.md)、[OAuth state](STATE.md)(一次性的 OAuth / CSRF state 權杖)與 [JWT claims](JWT_CLAIMS.md)
- [快取流程](CACHE_FLOW.md):快取請求內部的處理流程
- [可執行範例](https://github.com/allen0099/FastAPI-CacheX/tree/master/examples)(英文):每個功能一個完整的應用程式,皆由測試套件涵蓋
- [API 參考](https://fastapi-cachex.readthedocs.io/en/latest/api/http-caching/)(英文)
diff --git a/pyproject.toml b/pyproject.toml
index 1e66825..f5a6e4b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -99,6 +99,11 @@ filterwarnings = [
# TestClient through an alias that newer anyio deprecates. Later starlette
# releases no longer use it; nothing in this package or its tests does.
"ignore:The anyio.abc.BlockingPortal alias is deprecated:DeprecationWarning",
+ # Session and OAuth state are deprecated (#420) and warn once, on first
+ # import; their own tests keep running until 0.5.0 removes them.
+ # tests/test_session_state_deprecation.py checks the warnings themselves.
+ "ignore:fastapi_cachex.session is deprecated:FutureWarning",
+ "ignore:fastapi_cachex.state is deprecated:FutureWarning",
]
[tool.ruff.lint]
diff --git a/tests/session/test_0_4_0_notices.py b/tests/session/test_dropped_0_4_0_changes.py
similarity index 58%
rename from tests/session/test_0_4_0_notices.py
rename to tests/session/test_dropped_0_4_0_changes.py
index 2150026..a4f59a8 100644
--- a/tests/session/test_0_4_0_notices.py
+++ b/tests/session/test_dropped_0_4_0_changes.py
@@ -1,8 +1,11 @@
-"""Advance notices for the session changes 0.4.0 makes (#352).
+"""Session changes 0.3.9 announced for 0.4.0 and dropped with #420.
-- #131: ``get_session_manager`` resolves through ``SessionManagerProxy`` only.
-- #75: a ``token_source_priority`` without ``"cookie"`` disables the cookie.
-- #377: ``SessionConfig.use_bearer_token`` is removed.
+Sessions are deprecated and leave in 0.5.0, so 0.4.0 keeps 0.3.9's behaviour
+and no longer warns about these changes:
+
+- #131: ``get_session_manager`` keeps returning the middleware's manager.
+- #75: the cookie is read whether or not ``token_source_priority`` lists it.
+- #377: ``SessionConfig.use_bearer_token`` stays deprecated, now until 0.5.0.
"""
import warnings
@@ -32,7 +35,7 @@ def _middleware(config: SessionConfig) -> FastAPICacheXSessionMiddleware:
return FastAPICacheXSessionMiddleware(FastAPI(), session_manager=manager)
-# --- #131: get_session_manager through SessionManagerProxy ---------------------
+# --- #131: get_session_manager keeps the middleware's manager -----------------
def _manager_app(manager: SessionManager, config: SessionConfig) -> FastAPI:
@@ -56,72 +59,24 @@ async def rotate(request: Request) -> dict[str, bool]:
return app
-@pytest.mark.parametrize("path", ["/manager", "/ip"])
-def test_get_session_manager_warns_without_the_proxy(
- manager: SessionManager, config: SessionConfig, path: str
-) -> None:
- client = TestClient(_manager_app(manager, config))
-
- with pytest.warns(FutureWarning, match=r"SessionManagerProxy\.set\(") as record:
- first = client.get(path)
- # Once per app: later requests stay quiet.
- second = client.get(path)
-
- assert first.status_code == second.status_code == 200
- assert "issues/131" in str(record[0].message)
-
-
-def test_rotate_session_id_warns_without_the_proxy(
- manager: SessionManager, config: SessionConfig
-) -> None:
- client = TestClient(_manager_app(manager, config))
-
- with pytest.warns(FutureWarning, match="rotate_session_id"):
- response = client.post("/rotate")
-
- assert response.json() == {"rotated": False}
-
-
-def test_get_session_manager_warns_when_the_proxy_holds_another_manager(
- manager: SessionManager, config: SessionConfig
-) -> None:
- SessionManagerProxy.set(SessionManager(MemoryBackend(), config))
- client = TestClient(_manager_app(manager, config))
-
- with pytest.warns(
- FutureWarning, match="a different SessionManager is set in SessionManagerProxy"
- ):
- response = client.get("/manager")
-
- # 0.3.x still answers with the middleware's manager.
- assert response.json() == {"is_same": True}
-
-
-def test_get_session_manager_warns_when_the_proxy_is_empty(
- manager: SessionManager, config: SessionConfig
+@pytest.mark.parametrize("proxy", ["empty", "other"])
+def test_get_session_manager_returns_the_middleware_manager_silently(
+ manager: SessionManager, config: SessionConfig, proxy: str
) -> None:
- client = TestClient(_manager_app(manager, config))
-
- with pytest.warns(
- FutureWarning, match="no SessionManager is set in SessionManagerProxy"
- ):
- response = client.get("/manager")
-
- assert response.json() == {"is_same": True}
-
-
-def test_get_session_manager_is_silent_when_the_proxy_agrees(
- manager: SessionManager, config: SessionConfig
-) -> None:
- SessionManagerProxy.set(manager)
+ """Whatever the proxy holds, 0.4.0 answers with the middleware's manager."""
+ if proxy == "other":
+ SessionManagerProxy.set(SessionManager(MemoryBackend(), config))
client = TestClient(_manager_app(manager, config))
with warnings.catch_warnings():
warnings.simplefilter("error")
response = client.get("/manager")
- client.post("/rotate")
+ ip = client.get("/ip")
+ rotated = client.post("/rotate")
assert response.json() == {"is_same": True}
+ assert ip.status_code == 200
+ assert rotated.json() == {"rotated": False}
def test_middleware_from_the_proxy_is_silent(config: SessionConfig) -> None:
@@ -142,35 +97,22 @@ async def read_manager(mgr: SessionManagerDep) -> dict[str, bool]:
assert response.json() == {"is_same": True}
-# --- #75: token_source_priority names every token source -----------------------
+# --- #75: the cookie is read whether or not the list names it ------------------
_COOKIE = {"cookie_name": "session", "cookie_https_only": False}
-@pytest.mark.parametrize(
- "priority", [["header", "bearer"], ["bearer", "header"], ["header"], []]
-)
-def test_middleware_warns_for_an_explicit_list_without_cookie(
- priority: list[str],
-) -> None:
- config = SessionConfig(secret_key=SECRET, token_source_priority=priority, **_COOKIE)
-
- with pytest.warns(FutureWarning, match='does not list "cookie"') as record:
- _middleware(config)
-
- assert "issues/75" in str(record[0].message)
-
-
@pytest.mark.parametrize(
"settings",
[
- {}, # the default list becomes ["header", "bearer", "cookie"]: same order
+ {},
+ {"token_source_priority": ["header", "bearer"]},
+ {"token_source_priority": ["header"]},
+ {"token_source_priority": []},
{"token_source_priority": ["header", "bearer", "cookie"]},
- {"token_source_priority": ["bearer", "cookie"]},
- {"token_source_priority": ["cookie"]},
],
)
-def test_middleware_is_silent_for_the_default_or_a_list_with_cookie(
+def test_middleware_is_silent_with_or_without_cookie(
settings: dict[str, object],
) -> None:
config = SessionConfig(secret_key=SECRET, **settings, **_COOKIE)
@@ -188,14 +130,12 @@ def test_cookie_is_accepted_only_as_the_last_source(priority: list[str]) -> None
SessionConfig(secret_key=SECRET, token_source_priority=priority)
-async def test_the_cookie_is_still_read_before_0_4_0_with_or_without_the_entry() -> (
- None
-):
- """Listing "cookie" last is today's order, and leaving it out changes nothing yet."""
+async def test_the_cookie_is_read_with_or_without_the_entry() -> None:
+ """Listing "cookie" last is the order it is read in; leaving it out changes nothing."""
backend = MemoryBackend()
for priority in (["header", "cookie"], ["header"]):
with warnings.catch_warnings():
- warnings.simplefilter("ignore", FutureWarning)
+ warnings.simplefilter("error")
config = SessionConfig(
secret_key=SECRET, token_source_priority=priority, **_COOKIE
)
@@ -220,7 +160,7 @@ async def read(session: OptionalSession) -> dict[str, object]:
assert client.get("/").json() == {"user": "u"}, priority
-# --- #377: use_bearer_token is removed ------------------------------------------
+# --- #377: use_bearer_token is deprecated until 0.5.0 ----------------------------
@pytest.mark.parametrize("value", [True, False])
@@ -229,7 +169,7 @@ def test_passing_use_bearer_token_warns(value: bool) -> None:
SessionConfig(secret_key=SECRET, use_bearer_token=value)
message = str(record[0].message)
- assert "0.4.0" in message
+ assert "removed in version 0.5.0" in message
assert "issues/377" in message
diff --git a/tests/test_session_state_deprecation.py b/tests/test_session_state_deprecation.py
new file mode 100644
index 0000000..9ee249c
--- /dev/null
+++ b/tests/test_session_state_deprecation.py
@@ -0,0 +1,109 @@
+"""Session and OAuth state are deprecated in 0.4.0 (#420).
+
+Each package warns once per process, on first import, so these tests run the
+import in a fresh interpreter.
+"""
+
+import ast
+import pkgutil
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+import fastapi_cachex
+
+
+def _run(script: str, tmp_path: Path) -> str:
+ """Run ``script`` as a file in a new interpreter; return its stderr."""
+ path = tmp_path / "app.py"
+ path.write_text(script)
+ result = subprocess.run( # noqa: S603 - fixed interpreter, script written by the test
+ [sys.executable, "-W", "always::FutureWarning", str(path)],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ return result.stderr
+
+
+def _core_modules() -> list[str]:
+ """Every fastapi_cachex module outside the session and state packages."""
+ return sorted(
+ info.name
+ for info in pkgutil.walk_packages(
+ fastapi_cachex.__path__, prefix="fastapi_cachex."
+ )
+ if not info.name.startswith(("fastapi_cachex.session", "fastapi_cachex.state"))
+ )
+
+
+def test_core_modules_do_not_warn(tmp_path: Path) -> None:
+ """Nothing outside session and state imports them, the backends included."""
+ modules = _core_modules()
+ assert "fastapi_cachex.backends.redis" in modules
+ stderr = _run(
+ "import fastapi_cachex\n"
+ "from fastapi_cachex import cache, CacheManager, CacheLock\n"
+ + "".join(f"import {name}\n" for name in modules),
+ tmp_path,
+ )
+ assert "FutureWarning" not in stderr
+
+
+@pytest.mark.parametrize(
+ ("script", "package"),
+ [
+ ("from fastapi_cachex.session import SessionConfig\n", "session"),
+ ("from fastapi_cachex.session.config import SessionConfig\n", "session"),
+ ("from fastapi_cachex import SessionConfig\n", "session"),
+ ("import fastapi_cachex\nfastapi_cachex.get_session\n", "session"),
+ ("from fastapi_cachex.state import StateManager\n", "state"),
+ ("from fastapi_cachex import StateManager\n", "state"),
+ ],
+)
+def test_import_warns_at_the_importing_line(
+ script: str, package: str, tmp_path: Path
+) -> None:
+ stderr = _run(script, tmp_path)
+ line = script.count("\n")
+ assert (
+ f"app.py:{line}: FutureWarning: fastapi_cachex.{package} is deprecated "
+ "and will be removed in fastapi-cachex 0.5.0"
+ ) in stderr
+ assert "MIGRATING_0_4/#session-state-deprecated" in stderr
+
+
+def test_state_does_not_import_session(tmp_path: Path) -> None:
+ stderr = _run("from fastapi_cachex.state import StateManager\n", tmp_path)
+ assert "fastapi_cachex.session is deprecated" not in stderr
+
+
+@pytest.mark.parametrize("name", sorted(fastapi_cachex._DEPRECATED_NAMES))
+def test_deprecated_name_resolves_but_is_not_exported(name: str) -> None:
+ module = __import__(fastapi_cachex._DEPRECATED_NAMES[name], fromlist=[name])
+ assert getattr(fastapi_cachex, name) is getattr(module, name)
+ assert name not in fastapi_cachex.__all__
+
+
+def test_unknown_name_raises_attribute_error() -> None:
+ with pytest.raises(AttributeError, match="has no attribute 'nope'"):
+ fastapi_cachex.nope # noqa: B018
+
+
+def test_type_checking_imports_match_the_lazy_names() -> None:
+ """The names type checkers see are the names ``__getattr__`` resolves."""
+ tree = ast.parse(Path(fastapi_cachex.__file__).read_text())
+ block = next(
+ node
+ for node in tree.body
+ if isinstance(node, ast.If) and ast.unparse(node.test) == "TYPE_CHECKING"
+ )
+ imported = {
+ alias.name: f"fastapi_cachex.{node.module}"
+ for node in block.body
+ if isinstance(node, ast.ImportFrom)
+ for alias in node.names
+ }
+ assert imported == fastapi_cachex._DEPRECATED_NAMES
diff --git a/zensical.toml b/zensical.toml
index f66a759..57a319d 100644
--- a/zensical.toml
+++ b/zensical.toml
@@ -6,7 +6,7 @@
[project]
site_name = "FastAPI-CacheX"
-site_description = "HTTP caching, application caching and session management for FastAPI."
+site_description = "HTTP caching and application caching for FastAPI."
# Zensical cannot read READTHEDOCS_CANONICAL_URL, so the canonical URL is fixed
# to the default version Read the Docs serves.
site_url = "https://fastapi-cachex.readthedocs.io/en/latest/"
@@ -25,7 +25,7 @@ nav = [
{ "Backends" = "BACKENDS.md" },
{ "Distributed lock" = "LOCK.md" },
] },
- { "Sessions and sign-in" = [
+ { "Sessions and sign-in (deprecated)" = [
{ "Session management" = "SESSION.md" },
{ "OAuth state" = "STATE.md" },
{ "JWT claims" = "JWT_CLAIMS.md" },
@@ -38,8 +38,8 @@ nav = [
{ "CacheManager" = "api/cache-manager.md" },
{ "Backends" = "api/backends.md" },
{ "CacheLock" = "api/lock.md" },
- { "Session" = "api/session.md" },
- { "State" = "api/state.md" },
+ { "Session (deprecated)" = "api/session.md" },
+ { "State (deprecated)" = "api/state.md" },
{ "Types and exceptions" = "api/types.md" },
] },
{ "Development" = [
diff --git a/zensical.zh-TW.toml b/zensical.zh-TW.toml
index 8f3dfd5..a69d9da 100644
--- a/zensical.zh-TW.toml
+++ b/zensical.zh-TW.toml
@@ -10,7 +10,7 @@
[project]
site_name = "FastAPI-CacheX"
-site_description = "FastAPI 的 HTTP 快取、應用層快取與 Session 管理。"
+site_description = "FastAPI 的 HTTP 快取與應用層快取。"
site_url = "https://fastapi-cachex.readthedocs.io/zh-tw/latest/"
repo_url = "https://github.com/allen0099/FastAPI-CacheX"
repo_name = "allen0099/FastAPI-CacheX"
@@ -29,7 +29,7 @@ nav = [
{ "後端" = "BACKENDS.md" },
{ "分散式鎖" = "LOCK.md" },
] },
- { "Session 與登入" = [
+ { "Session 與登入(已棄用)" = [
{ "Session 管理" = "SESSION.md" },
{ "OAuth state" = "STATE.md" },
{ "JWT claims" = "JWT_CLAIMS.md" },