Sessions
The session mixin, the session object, the avatar, and the session stores.
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.
- 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.- Parameters:
kwargs (Any)
- property session_store: SessionStore
The store backing this server’s sessions.
Server-managed session: id, meta, Bag data, and an optional Avatar.
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"]. Each session carries an
Avatar | None — None is an anonymous session; capturing an identity is
an explicit avatar= at creation — and 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 an optional Avatar.
- __init__(session_id, avatar, ttl)[source]
Initialize the session with its token, an identity avatar, and a TTL.
- property data: Bag
Application data as a Bag.
- attach_avatar(avatar)[source]
Attach the identity 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 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
parametrized over implementations (§5.9), so the core 1b file/db backends plug
into the SAME tests. MemorySessionStore is the dict-backed default:
secrets tokens, a default_ttl for new sessions, lazy expiry on get,
opportunistic purge_expired at create time (no background task — those
arrive in core 1e), and a dump/restore that persists meta and the
avatar’s identity/tags only — never the data Bag. create() is anonymous by
default (avatar is None); capturing an identity into a session is an
explicit create(avatar=...).
- class genro_asgi.session.store.SessionStore(*args, **kwargs)[source]
Bases:
ProtocolProtocol for session storage backends.
- 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), purging expired ones opportunistically first.
- purge_expired()[source]
Drop every expired session from the store; return the count purged.
- Return type:
FileSessionStore — one JSON file per session over a storage mount.
Like MemorySessionStore this store keeps live Session objects in memory
(so get returns the same instance and touch is honored) but mirrors each
to <mount>:<prefix>/<id>.json, so a session survives a process restart or a
fresh store built on the SAME mount — the D22 survival line. The file carries
meta (created_at/last_access/ttl) and the avatar’s identity/tags ONLY, keyed on
disk by the session id (the filename); the data Bag is VOLATILE — never
persisted — the SAME contract as dump/restore (full-data persistence is
a future decision). All I/O is synchronous through the Phase 1 storage nodes
(core 1b ratified: async callers wrap in server.run_sync()).
purge_expired reaps the sessions this process tracks in memory (removing
their files too) and is invoked opportunistically at create time — no
background task, those arrive in core 1e. An expired file the store has not
loaded is reaped lazily the next time get touches it (no plain-text
fallback, no silent skip on a corrupt file: a non-JSON payload raises).
- class genro_asgi.session.file_store.FileSessionStore(storage, mount='site', prefix='sessions', default_ttl=3600)[source]
Bases:
objectSession store persisting one JSON file per session on a storage mount.
- Parameters:
storage (LocalStorage)
mount (str)
prefix (str)
default_ttl (int)
- __init__(storage, mount='site', prefix='sessions', default_ttl=3600)[source]
Bind the store to a storage
mount/prefixwith a default TTL.- Parameters:
storage (LocalStorage)
mount (str)
prefix (str)
default_ttl (int)
- Return type:
None
- create(avatar=None)[source]
Create and persist a session, purging expired ones opportunistically first.
- purge_expired()[source]
Drop every expired tracked session (and its file); return the count purged.
- Return type: