feat(redis): add native JSON session storage for 4.1.0
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:
2026-10-23 20:26:24 +02:00
parent 5dc4a8f6cb
commit 4939bbe987
17 changed files with 1024 additions and 843 deletions
+27 -27
View File
@@ -7,39 +7,39 @@ from quart_session import Session
@pytest.fixture
def app_factory():
"""Builds a Quart app backed by an in-memory session store."""
"""Builds a Quart app backed by an in-memory session store."""
def _factory(**config) -> Quart:
app = Quart(__name__)
app.secret_key = "test-secret-key"
app.config["SESSION_TYPE"] = "null"
app.config.update(config)
Session(app)
def _factory(**config) -> Quart:
app = Quart(__name__)
app.secret_key = "test-secret-key"
app.config["SESSION_TYPE"] = "null"
app.config.update(config)
Session(app)
# 'null' interface is a no-op store, give it a dict so that
# open_session() can actually find data again.
store: dict = {}
interface = app.session_interface
# 'null' interface is a no-op store, give it a dict so that
# open_session() can actually find data again.
store: dict = {}
interface = app.session_interface
async def _get(key: str, app: Optional[Quart] = None):
return store.get(key)
async def _get(key: str, app: Optional[Quart] = None):
return store.get(key)
async def _set(key: str, value, expiry: Optional[int] = None,
app: Optional[Quart] = None):
store[key] = value
async def _set(key: str, value, expiry: Optional[int] = None,
app: Optional[Quart] = None):
store[key] = value
async def _delete(key: str, app: Optional[Quart] = None):
store.pop(key, None)
async def _delete(key: str, app: Optional[Quart] = None):
store.pop(key, None)
interface.get = _get
interface.set = _set
interface.delete = _delete
interface.get = _get
interface.set = _set
interface.delete = _delete
@app.route("/")
async def index():
session["hits"] = session.get("hits", 0) + 1
return {"hits": session["hits"], "sid": session.sid}
@app.route("/")
async def index():
session["hits"] = session.get("hits", 0) + 1
return {"hits": session["hits"], "sid": session.sid}
return app
return app
return _factory
return _factory