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.
38 lines
1.5 KiB
Python
38 lines
1.5 KiB
Python
"""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]
|