Sessions
The session mixin, the session object, the avatar, and the session store.
Session capability: HTTP sessions as a mixin over the base server (D16).
SessionMixin is composed BEFORE MiddlewareMixin and BaseServer
(class S(SessionMixin, MiddlewareMixin, BaseServer)). Its cooperative
__init__ peels session_store= (None → a fresh MemorySessionStore)
and session_ttl= (the default store’s TTL), then ARMS SessionMiddleware
by injecting {"session": True} into the middleware config it forwards
to MiddlewareMixin along the cooperative chain — composing the two mixins
arms sessions with no user action, while an explicit
middleware={"session": False} still wins (setdefault never overrides an
explicit switch). It overrides the §4 contract method session(request) to
return the session attached to the request scope; a composition WITHOUT the
mixin keeps the base answer (None). The login seam is not a server method:
a handler attaches the identity through the request facade
(request.session.attach_avatar(avatar)) — the session id never changes at
login, so the cookie already held by the client stays valid.
save_session= arms the pickle snapshot, the development survival line the
CLI wires from serve --name (~/.genroasgi/sessions/<name>.pickle):
__call__ intercepts the lifespan scope exactly like TaskMixin —
lifespan.py is NEVER touched (ratified) — loading the snapshot before the
protocol runs (an absent file starts empty) and saving EVERY live session,
data Bag included, when the protocol completes at shutdown. Unarmed, every
scope passes straight through.
- class genro_asgi.session.mixin.SessionMixin(**kwargs)[source]
Bases:
objectSession capability mixin, composed BEFORE the middleware/server classes.
Constructor kwargs peeled here:
session_store— an explicit store (Nonebuilds aMemorySessionStore);session_ttl— the default store’s TTL when no explicit store is given;save_session— the snapshot pickle path (None, the default, disarms the snapshot).- Parameters:
kwargs (Any)
- property session_store: SessionStore
The store backing this server’s sessions.
Server-managed session: id, meta, Bag data, and a keyed collection of Avatars.
A Session groups request-scoped state under a unique id with expiry
tracking. SessionMiddleware creates or reconnects sessions via the request
cookie and attaches them to scope["session"].
The dressing model. A session is not one identity but a wardrobe of them,
each stored under a key. The ROOT_AVATAR_KEY slot holds the identity of the
primary login — the one the auth chain resolves and the one avatar() returns
with no argument. Further keys are sub-logins: an identity a page acquired
inside the same session (a second-system credential, an impersonation, a
delegated account) that must coexist with the root one instead of replacing it.
Page trees will reference the slot they are dressed in by avatar_key, so the
identity of a page is a lookup in this collection, never a copy of it.
avatar(key) returns Avatar | None — None is an unclaimed slot, and an
absent root slot is an anonymous session; capturing an identity is an explicit
avatar= at creation (the root slot) or an attach_avatar call. avatars
is a read-only view for enumeration; attach_avatar is its only writer. There
is no detach: a slot claimed in a session stays claimed for its lifetime.
data is a Bag for arbitrary application data. touch() refreshes
last_access; is_expired() measures the TTL from it.
Write-back is explicit (D24): a session persists at request end ONLY when
dirty is set. attach_avatar marks it dirty (a login must survive), and a
handler mutating data marks it dirty with mark_dirty() — there is no
write-through. touch() is NOT a mutation for this purpose: the last_access
refresh happens on every get (including read-only requests), so making it
dirty would save on every request and defeat the zero-I/O read path. The
middleware clears the flag with clear_dirty() after a successful save.
- class genro_asgi.session.session.Session(session_id, avatar, ttl)[source]
Bases:
objectServer-managed session with meta, Bag data, and keyed identity avatars.
- __init__(session_id, avatar, ttl)[source]
Initialize the session with its token, its root avatar, and a TTL.
- property data: Bag
Application data as a Bag.
- avatar(key='root')[source]
The avatar dressed under
key;None= unclaimed slot.With no argument it returns the root avatar — the primary login — so an anonymous session answers
None.
- property avatars: Mapping[str, Avatar]
Read-only view of the keyed avatars (
attach_avataris the only writer).
- attach_avatar(avatar, key='root')[source]
Dress
keywith an avatar — the login event (marks the session dirty).The session stays the same object: id,
dataandmetaare untouched, so whatever an anonymous visitor accumulated survives the login. The default key is the root slot (the primary login); any other key is a sub-login coexisting with it. The change is marked dirty so the login persists.
- mark_dirty()[source]
Flag the session as changed — a handler mutating
datacalls this.- Return type:
- clear_dirty()[source]
Reset the dirty flag (the middleware calls this after a successful save).
- Return type:
Avatar — the ONE authenticated identity type across the package.
An Avatar is a plain slotted value object: an identity string, its
authorization tags, and an extensible Bag of per-user data. AuthCore
returns it and sessions carry it — “nobody” is None uniformly, never an
anonymous Avatar. The constructor normalizes tags=None to an empty list
(the identity boundary for e.g. a JWT null tags claim). No framework machinery.
- class genro_asgi.session.avatar.Avatar(identity, tags=None)[source]
Bases:
objectUser identity with authorization tags and extensible Bag data.
- property data: Bag
Extensible per-user data as a Bag.
Session store — the storage Protocol and the in-memory default.
SessionStore is a runtime-checkable Protocol (get/create/delete/
purge_expired/dump/restore). Its test suite is a shared CONTRACT suite driven
by a store factory (§5.9), so a custom backend plugs into the SAME tests.
MemorySessionStore is the dict-backed only shipped store: secrets
tokens, a default_ttl for new sessions, lazy expiry on get, and a
delta-checked purge_expired at create time — the mass reap runs only
when PURGE_INTERVAL has elapsed since the last one (no background task:
this REPLACES the former TaskManager purge loop, a ratified revision of core
1e/◆D22). dump/restore persist meta and the keyed avatars’
identity/tags only — never the data Bag. The serialized shape is
avatars: {key: {identity, tags}}, the whole wardrobe of the session.
save_snapshot/load_snapshot are the OTHER persistence pair — one
pickle file carrying every live session whole, data Bag included, the
development survival line SessionMixin drives around the lifespan.
create() is anonymous by default (avatar is None); capturing an identity
into a session is an explicit create(avatar=...), which dresses the root
slot.
- class genro_asgi.session.store.SessionStore(*args, **kwargs)[source]
Bases:
ProtocolProtocol for session storage backends.
- create(avatar=None)[source]
Create a new session with a unique token (anonymous by default).
avatardresses the session’s root slot.
- class genro_asgi.session.store.MemorySessionStore(default_ttl=3600)[source]
Bases:
objectIn-memory session store — the default implementation.
- Parameters:
default_ttl (int)
- __init__(default_ttl=3600)[source]
Initialize an empty store with a default TTL for new sessions.
- Parameters:
default_ttl (int)
- Return type:
None
- create(avatar=None)[source]
Create a session (default TTL); a delta-checked mass reap runs first.
The reap is opportunistic AND throttled: it runs only when
PURGE_INTERVALhas elapsed since the last one, so a burst of creates never pays a full-store scan each time.
- purge_expired()[source]
Drop every expired session from the store; return the count purged.
- Return type:
- save_snapshot(path)[source]
Pickle EVERY live session — data Bag INCLUDED — to path.
The development survival line (
genro-asgi serve --name): the whole store crosses a restart through one pickle file. This deliberately supersedes thedump/restorecontract (“the data Bag is never persisted”) for the snapshot path. Expired sessions are reaped first; parent directories are created. Returns how many sessions were saved.