From 709c575a882cd53e2b9668fe0966764a13e90b69 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Sat, 26 Sep 2026 12:44:27 +0000 Subject: [PATCH] feat(memcached): add a memcached extra and deprecate memcache The extra that installs pymemcache was named memcache, which does not match MemcachedBackend or the docs' own wording. Add memcached as the canonical name and keep memcache as a deprecated alias until 0.4.0 (#202), so existing installs keep pulling in pymemcache. Switch tox, the release workflow and the docs to the new name, and point the ImportError message at fastapi-cachex[memcached]. Closes #201 --- .github/workflows/release.yml | 2 +- CHANGELOG.md | 10 ++++++++++ CLAUDE.md | 2 +- README.md | 2 +- docs/BACKENDS.md | 6 ++++-- docs/DEVELOPMENT.md | 2 +- fastapi_cachex/backends/memcached.py | 2 +- i18n/zh-TW/docs/BACKENDS.md | 2 +- i18n/zh-TW/docs/index.md | 2 +- pyproject.toml | 2 ++ tox.ini | 2 +- uv.lock | 6 +++++- 12 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48a8540..21b6247 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,7 +81,7 @@ jobs: # a missing backend package would turn the gate below into a formality, # so it does not rely on a dev-group entry staying put. - name: Sync dependencies - run: uv sync --group dev --extra jwt --extra redis --extra memcache + run: uv sync --group dev --extra jwt --extra redis --extra memcached # The gate. A release is the one run where a red test result arrives too # late to matter, so everything CI checks elsewhere is checked here too, diff --git a/CHANGELOG.md b/CHANGELOG.md index bff1d18..390e755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. - **`CacheLock`, a distributed lock built on backend primitives.** Usable as an async context manager or with direct `acquire`/`release`/`extend`/`locked` calls, raising `LockTimeoutError` on timeout. ([#64](https://github.com/allen0099/FastAPI-CacheX/issues/64)) - **`expire_if_equals()` backend primitive for owner-checked TTL renewal.** Added to `BaseCacheBackend`, `MemoryBackend`, `AsyncRedisCacheBackend`, and `MemcachedBackend`. ([#64](https://github.com/allen0099/FastAPI-CacheX/issues/64)) +- **`memcached` extra for `MemcachedBackend`.** Install with + `fastapi-cachex[memcached]`, matching the backend's name. It pulls in the + same `pymemcache` dependency as the old `memcache` extra. + ([#201](https://github.com/allen0099/FastAPI-CacheX/issues/201)) + ### Changed - **GitHub release notes list one line per change.** Each changelog entry now @@ -37,6 +42,11 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. `DeprecationWarning` if it clears anything. The retry will be removed in 0.4.0. ([#125](https://github.com/allen0099/FastAPI-CacheX/issues/125)) +- **The `memcache` extra.** Use `memcached` instead. The old name keeps working + until 0.4.0 removes it; after that, pip and uv only warn about the unknown + extra and install without `pymemcache`. + ([#202](https://github.com/allen0099/FastAPI-CacheX/issues/202)) + ### Fixed - **Redis `clear_pattern()` no longer strips a pattern that starts with the key diff --git a/CLAUDE.md b/CLAUDE.md index 61713b9..2c050af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,7 +80,7 @@ The library has four independent subsystems: All backends implement `BaseCacheBackend` (abstract base in `backends/base.py`): - `MemoryBackend`: In-process dict with background cleanup task. Not suitable for multi-process production use. - `AsyncRedisCacheBackend` (`backends/redis.py`): Fully async; uses `SCAN` (not `KEYS`) for pattern operations. Requires `redis[hiredis]` and `orjson` extras. -- `MemcachedBackend` (`backends/memcached.py`): `clear_pattern`/`get_all_keys` are no-ops (return `0`/`[]` with a `RuntimeWarning`) since the Memcached protocol has no key enumeration. Runs the sync pymemcache client in worker threads with connection pooling (`use_pooling=True`, `default_noreply=False`), so concurrent calls never share a socket and every write is acknowledged before the next call on another socket can observe it. Requires `pymemcache` extra. +- `MemcachedBackend` (`backends/memcached.py`): `clear_pattern`/`get_all_keys` are no-ops (return `0`/`[]` with a `RuntimeWarning`) since the Memcached protocol has no key enumeration. Runs the sync pymemcache client in worker threads with connection pooling (`use_pooling=True`, `default_noreply=False`), so concurrent calls never share a socket and every write is acknowledged before the next call on another socket can observe it. Requires the `memcached` extra (`memcache` is a deprecated alias until 0.4.0). Backend keys are namespaced automatically (default prefix: `fastapi_cachex:`). diff --git a/README.md b/README.md index 616e2b4..6b4b50f 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ backends and the optional session transports ship as extras: | Extra | Install | Pulls in | Needed for | |-------|---------|----------|------------| | `redis` | `uv add "fastapi-cachex[redis]"` | `redis[hiredis]`, `orjson` | `AsyncRedisCacheBackend` | -| `memcache` | `uv add "fastapi-cachex[memcache]"` | `pymemcache` | `MemcachedBackend` (note: `memcache`, not `memcached`) | +| `memcached` | `uv add "fastapi-cachex[memcached]"` | `pymemcache` | `MemcachedBackend` (the older `memcache` name still works until 0.4.0) | | `jwt` | `uv add "fastapi-cachex[jwt]"` | `PyJWT` | `SessionConfig(token_format="jwt")` | Extras combine: `uv add "fastapi-cachex[redis,jwt]"`. diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 9d096df..0da3990 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -94,8 +94,10 @@ hiredis will fail to negotiate it. ## Memcached -Install the extra with `uv add "fastapi-cachex[memcache]"` (note: `memcache`, not -`memcached`). +Install the extra with `uv add "fastapi-cachex[memcached]"`. Before 0.3.8 it was +called `memcache`; that name still works but is deprecated and will be removed in +0.4.0. An unknown extra only produces a warning at install time, so after 0.4.0 +`fastapi-cachex[memcache]` would install without `pymemcache`. ```python from fastapi_cachex.backends import MemcachedBackend diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 0d43f68..926257e 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -109,7 +109,7 @@ Worth doing whenever a test is written for something security-relevant. ## Using tox tox ensures the code works across different Python versions (3.10-3.14, the -`env_list` in `tox.ini`). Each environment installs the `redis` and `memcache` +`env_list` in `tox.ini`). Each environment installs the `redis` and `memcached` extras through `tox-uv` and passes the `CACHEX_TEST_*` and `CACHEX_REQUIRE_LIVE_SERVERS` variables through, so the opt-in rules above apply unchanged. diff --git a/fastapi_cachex/backends/memcached.py b/fastapi_cachex/backends/memcached.py index 89476b5..2dde101 100644 --- a/fastapi_cachex/backends/memcached.py +++ b/fastapi_cachex/backends/memcached.py @@ -77,7 +77,7 @@ def __init__( try: from pymemcache import HashClient except ImportError: - msg = "pymemcache is not installed. Please install it with 'pip install pymemcache'" + msg = "pymemcache is not installed. Install it with the extra: pip install 'fastapi-cachex[memcached]'" raise CacheXError(msg) # Pooled connections have no ordering guarantee between each other, so diff --git a/i18n/zh-TW/docs/BACKENDS.md b/i18n/zh-TW/docs/BACKENDS.md index d6fc9ac..67cdf2e 100644 --- a/i18n/zh-TW/docs/BACKENDS.md +++ b/i18n/zh-TW/docs/BACKENDS.md @@ -71,7 +71,7 @@ BackendProxy.set(backend) ## Memcached {#memcached} -以 `uv add "fastapi-cachex[memcache]"` 安裝此 extra(注意是 `memcache`,不是 `memcached`)。 +以 `uv add "fastapi-cachex[memcached]"` 安裝此 extra。0.3.8 以前這個 extra 名為 `memcache`;舊名稱仍可使用但已棄用,將於 0.4.0 移除。安裝時遇到不存在的 extra 只會顯示警告,因此 0.4.0 之後 `fastapi-cachex[memcache]` 會裝好套件但不含 `pymemcache`。 ```python from fastapi_cachex.backends import MemcachedBackend diff --git a/i18n/zh-TW/docs/index.md b/i18n/zh-TW/docs/index.md index 470fe88..351a3d0 100644 --- a/i18n/zh-TW/docs/index.md +++ b/i18n/zh-TW/docs/index.md @@ -37,7 +37,7 @@ uv add fastapi-cachex | Extra | 安裝 | 帶入套件 | 用途 | |-------|------|---------|------| | `redis` | `uv add "fastapi-cachex[redis]"` | `redis[hiredis]`、`orjson` | `AsyncRedisCacheBackend` | -| `memcache` | `uv add "fastapi-cachex[memcache]"` | `pymemcache` | `MemcachedBackend`(注意是 `memcache`,不是 `memcached`) | +| `memcached` | `uv add "fastapi-cachex[memcached]"` | `pymemcache` | `MemcachedBackend`(舊名稱 `memcache` 在 0.4.0 之前仍可使用) | | `jwt` | `uv add "fastapi-cachex[jwt]"` | `PyJWT` | `SessionConfig(token_format="jwt")` | Extra 可以組合:`uv add "fastapi-cachex[redis,jwt]"`。 diff --git a/pyproject.toml b/pyproject.toml index 6ff777a..0000617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,8 @@ docs = [ ] [project.optional-dependencies] +memcached = ["pymemcache"] +# Deprecated alias of `memcached`, removed in 0.4.0 (#202). memcache = ["pymemcache"] redis = ["redis[hiredis]>=5.3.0", "orjson"] jwt = ["PyJWT>=2.9.0"] diff --git a/tox.ini b/tox.ini index 5ebc3e6..895dfe1 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ env_list = runner = uv-venv-lock-runner extras = redis - memcache + memcached # The Redis/Memcached suites are opt-in because they wipe the server they # connect to; without these they skip. See tests/live_servers.py. passenv = diff --git a/uv.lock b/uv.lock index 4d8c6c9..f9272f5 100644 --- a/uv.lock +++ b/uv.lock @@ -520,6 +520,9 @@ jwt = [ memcache = [ { name = "pymemcache" }, ] +memcached = [ + { name = "pymemcache" }, +] redis = [ { name = "orjson" }, { name = "redis", extra = ["hiredis"] }, @@ -559,9 +562,10 @@ requires-dist = [ { name = "pydantic" }, { name = "pyjwt", marker = "extra == 'jwt'", specifier = ">=2.9.0" }, { name = "pymemcache", marker = "extra == 'memcache'" }, + { name = "pymemcache", marker = "extra == 'memcached'" }, { name = "redis", extras = ["hiredis"], marker = "extra == 'redis'", specifier = ">=5.3.0" }, ] -provides-extras = ["memcache", "redis", "jwt"] +provides-extras = ["memcached", "memcache", "redis", "jwt"] [package.metadata.requires-dev] dev = [