feat(redis): add native JSON session storage for 4.1.0
Build & Publish Package / publish (push) Successful in 49s
Build & Publish Package / publish (push) Successful in 49s
- Add opt-in SESSION_REDIS_JSON storage for asyncio Redis backends. - Check JSON command support at startup and reject JSON mode on Trio. - Read legacy strings and migrate sessions on their next save. - Validate JSON objects before transactional replacement and expiry. - Preserve tagged serialization for datetime, tuple, and other types. - Cover command contracts, error handling, and session round-trips. - Document server requirements, permissions, and migration limits. - Normalize indentation and whitespace across code, tests, and examples. - Bump the package and lockfile to 4.1.0 and update the changelog. - Refresh the locked pycparser dependency to 3.12.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""Native JSON storage for the asyncio Redis session backend."""
|
||||
|
||||
import json
|
||||
|
||||
async def check_support(backend):
|
||||
"""Fail at startup rather than silently falling back to string storage."""
|
||||
commands = await backend.execute_command("COMMAND", "INFO", "JSON.GET", "JSON.SET")
|
||||
entries = list(commands.values()) if isinstance(commands, dict) else commands
|
||||
if len(entries) != 2 or not all(entries):
|
||||
raise RuntimeError("SESSION_REDIS_JSON requires JSON.GET and JSON.SET support")
|
||||
|
||||
async def get(backend, key):
|
||||
"""Read legacy strings as well as native JSON documents."""
|
||||
from redis.exceptions import ResponseError
|
||||
|
||||
try:
|
||||
return await backend.get(key)
|
||||
except ResponseError as error:
|
||||
if not str(error).startswith("WRONGTYPE"):
|
||||
raise
|
||||
# A single root path returns the object, not a JSONPath result array.
|
||||
return await backend.execute_command("JSON.GET", key, ".")
|
||||
|
||||
async def set(backend, key, value, expiry):
|
||||
"""Replace a string or JSON key and apply expiry in one transaction."""
|
||||
# Validate before deleting an existing key. Preserve TaggedJSONSerializer's
|
||||
# representation so tuples, datetimes, etc. still round-trip normally.
|
||||
document = json.loads(value)
|
||||
if not isinstance(document, dict):
|
||||
raise ValueError("Native Redis JSON sessions require a JSON object")
|
||||
async with backend.pipeline(transaction=True) as pipeline:
|
||||
pipeline.delete(key)
|
||||
pipeline.execute_command("JSON.SET", key, "$", value)
|
||||
if expiry is not None:
|
||||
pipeline.expire(key, expiry)
|
||||
results = await pipeline.execute()
|
||||
return results[1]
|
||||
Reference in New Issue
Block a user