Skip to content

fix(cache-manager): match key_prefix literally in clear_pattern - #316

Merged
allen0099 merged 1 commit into
masterfrom
fix/clear-pattern-literal-prefix
Sep 27, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/clear-pattern-literal-prefix

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Summary

CacheManager.clear_pattern(pattern) passed key_prefix + pattern to backend.clear_pattern() as a single glob, so glob characters in the manager's own prefix were live:

  • CacheManager(key_prefix="cache[1]:").clear_pattern("*") missed its own keys.
  • key_prefix="a?:" also cleared the keys of a manager with prefix ab:.

This PR implements the hybrid option the maintainer chose:

  • Fast path, unchanged. A key_prefix without *, ?, [, ] or \ still calls backend.clear_pattern(key_prefix + pattern), e.g. Redis SCAN MATCH. The argument is the same as before.

  • Slow path. Any other prefix:

    • get_all_keys() lists every key;
    • it keeps keys that start with key_prefix literally and whose remainder matches pattern under fnmatch.fnmatchcase;
    • delete_many() deletes them;
    • the return value is the number deleted.

    On this path pattern is fnmatch syntax, not Redis glob: case-sensitive, no backslash escapes, [!a] for negation. This is documented in the docstring and the docs.

  • UserWarning at construction when key_prefix contains a glob metacharacter. The message says clear_pattern() will list and filter in Python, which is slower on Redis, and suggests a prefix without *?[]\. stacklevel=2 points it at the caller's line. The only internal constructor is get_app_cache(), which uses the default cache: prefix, so the warning never fires from library code.

  • Memcached. Both paths return 0 with exactly one RuntimeWarning. On the slow path it comes from get_all_keys(), and delete_many() of nothing is silent.

  • Scope. The metacharacter set mirrors _GLOB_SPECIAL in backends/redis.py as a private constant, so no backend API is added. The manager.py change is confined to __init__, clear_pattern and a small private helper.

Behaviour changes

  • A prefix with glob characters now clears exactly its own namespace. This can delete more keys (cache[1]:) or fewer keys (a?:) than before.
  • Such a prefix now warns at construction.
  • Plain prefixes behave exactly as before.

Tests

New file tests/test_cache_manager_clear_pattern.py, run on memory and live Redis where applicable:

  • the constructor warning appears for each metacharacter and points at the caller's file; a plain prefix produces no warning;
  • the fast path calls backend.clear_pattern("cache:user:*") and never enumerates keys;
  • cache[1]: clears its own keys but not cache1:a; a?: leaves the ab: manager's keys alone;
  • ?, * and [!u] in the pattern part still work as a glob on the slow path, case-sensitively;
  • the prefix is matched only at the start of the key;
  • Memcached returns 0 with a single RuntimeWarning on both paths.

Full suite incl. live Redis/Memcached tests: 1204 passed, 1 skipped, coverage 99% (manager.py 100%). ruff, ruff format and mypy --strict are clean.

Changelog

changelog.d/140.fixed.md

Closes #140

clear_pattern passed key_prefix + pattern to the backend as one glob, so
glob characters in the manager's own prefix were live: cache[1]: missed
its own keys and a?: also cleared ab:'s. A prefix free of glob
metacharacters keeps the backend's native clear_pattern; any other prefix
lists every key, matches the prefix literally and the remainder with
fnmatchcase, and deletes via delete_many. The constructor warns about
such a prefix.
@allen0099 allen0099 added this to the 0.3.9 milestone Sep 27, 2026
@allen0099
allen0099 merged commit 8ba498f into master Sep 27, 2026
12 checks passed
@allen0099
allen0099 deleted the fix/clear-pattern-literal-prefix branch September 27, 2026 14:17
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.

CacheManager.clear_pattern treats glob characters in key_prefix as live

1 participant