fragmented-keys
Fragmented Key Management and Invalidation Library for use with Redis.
A Python port of noizu-labs/fragmented-keys (PHP), adapted to use Redis as the cache backend.
Overview
Fragmented Keys enable efficient cache invalidation by composing cache keys from multiple independently versioned tags. Instead of deleting cached entries individually, you increment a tag's version — all keys that depend on that tag automatically resolve to a new cache key, leaving the old entries to expire naturally.
Cache Key = md5( base_key + tag1:version1 + tag2:version2 + ... )
When any tag version changes, the resulting key changes, producing a cache miss and triggering a fresh data fetch.
Installation
uv add fragmented-keys
Or with pip:
pip install fragmented-keys
Quick Start
from redis import Redis
from fragmented_keys import Configuration, RedisHandler, StandardTag, StandardKey
# Setup
client = Redis(host="127.0.0.1", port=6379)
Configuration.set_default_cache_handler(RedisHandler(client))
Configuration.set_global_prefix("MyApp")
# Create tags
user_tag = StandardTag("User", "42")
city_tag = StandardTag("City", "chicago")
# Build a composite key
key = StandardKey("UserDashboard", [user_tag, city_tag])
cache_key = key.get_key_str() # MD5 hex digest
# Use it with Redis
data = client.get(cache_key)
if data is None:
data = fetch_from_database()
client.set(cache_key, data)
# Invalidate all keys depending on User:42
user_tag = StandardTag("User", "42")
user_tag.increment()
# Next call to get_key_str() with User:42 will produce a different key
Tag Types
StandardTag
Version is stored in the cache backend and can be incremented. Each increment changes the version by +0.1, invalidating all dependent keys.
tag = StandardTag("User", "42")
tag.get_tag_version() # Fetches or seeds version from cache
tag.increment() # Version += 0.1, persisted to cache
tag.reset_tag_version() # Resets to a new time-based value
ConstantTag
Version is fixed at construction time. Mutations are no-ops. Useful for incorporating static dimensions into composite keys.
tag = ConstantTag("ApiVersion", "v2", version=2.0)
tag.increment() # No-op
tag.get_tag_version() # Always 2.0
Cache Handlers
RedisHandler
Primary backend using redis-py. Supports mget for bulk version fetches and setex for TTL.
from redis import Redis
from fragmented_keys import RedisHandler
handler = RedisHandler(Redis(host="127.0.0.1", port=6379))
MemoryHandler
In-memory dict-based handler for testing.
from fragmented_keys import MemoryHandler
handler = MemoryHandler()
Custom Handlers
Implement the CacheHandler protocol:
from fragmented_keys.protocols import CacheHandler
class MyHandler:
def group_name(self) -> str:
return "MyHandler"
def get(self, key: str) -> str | None: ...
def set(self, key: str, value: str, ttl: int | None = None) -> None: ...
def get_multi(self, keys: list[str]) -> dict[str, str]: ...
KeyRing
FragmentedKeyRing is a factory for defining reusable key templates and producing configured StandardKey instances.
from redis import Redis
from fragmented_keys import FragmentedKeyRing, RedisHandler
redis_handler = RedisHandler(Redis())
memory_handler = MemoryHandler()
ring = FragmentedKeyRing(
global_options={"type": "standard"},
global_tag_options={
"universe": {"type": "constant", "version": 1.0},
},
default_cache_handler="redis",
cache_handlers={
"redis": redis_handler,
"memory": memory_handler,
},
default_prefix="MyApp",
)
# Define key templates
ring.define_key("Users", [
"universe",
{"tag": "planet", "cache_handler": "memory"},
"city",
])
# Get a configured key object
key_obj = ring.get_key_obj("Users", ["MilkyWay", "Earth", "Chicago"])
cache_key = key_obj.get_key_str()
# Or use dynamic accessor
key_obj = ring.get_users_key_obj("MilkyWay", "Earth", "Chicago")
Tag Options
Options are merged in order: global options → per-tag options → per-key overrides.
| Option | Type | Description |
|---|---|---|
type |
"standard" | "constant" |
Tag type (default: "standard") |
version |
float |
Initial version value |
cache_handler |
str |
Handler key from cache_handlers dict |
prefix |
str |
Override global prefix for this tag |
Configuration
Global defaults shared across all tags and keys:
from fragmented_keys import Configuration
Configuration.set_default_cache_handler(handler) # Fallback handler
Configuration.set_global_prefix("MyApp") # Key namespace prefix
How It Works
- Each
StandardTagstores its current version in the cache backend under the key{tag}_{instance}{prefix}. - When building a composite key,
StandardKeybulk-fetches all tag versions usingget_multi(grouped by handler) for efficiency.ConstantTaginstances are skipped in bulk fetches since their version is fixed. - The final key is
md5("{keyName}_{groupId}:t{tag1Name}:v{version1}:t{tag2Name}:v{version2}..."). - Calling
tag.increment()bumps the stored version by0.1, so subsequent key builds produce a different MD5 — effectively invalidating all cached data under the old key without deleting it.
Development
# Install with dev dependencies
uv sync --extra dev
# Run tests
uv run pytest tests/ -v
License
MIT
Metadata
Release files for fragmented-keys 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fragmented_keys-0.1.5.tar.gz | 8.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fragmented_keys-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.3 kB
Release files / fragmented_keys-0.1.5.tar.gz
| Download URL | fragmented_keys-0.1.5.tar.gz |
|---|---|
| Size | 8.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dd739cba6c4858048d3e3a56eed4fb404e868d2fc1bec9029621723954c88eb7
|
|
BLAKE2b-256 checksum How to use checksums |
c578e8a34075501efba9a62118e5d2ce0c244bdac3f01935208185a3de69ff8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / fragmented_keys-0.1.5-py3-none-any.whl
| Download URL | fragmented_keys-0.1.5-py3-none-any.whl |
|---|---|
| Size | 14.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
81ce3fe81e6830efdc6ca8be894d955058f4d08d43de02ca5cdd23c261aa1a4e
|
|
BLAKE2b-256 checksum How to use checksums |
aae30e6caf2252dfe4f53d43e693cd87377c56ec88646cb080580a19a8cbbec7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for fragmented-keys 0.1.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.