Metadata-Version: 2.4
Name: kaya-session
Version: 0.0.1
Summary: Session management for the Kaya lightweight ASGI web framework
Author-email: Walter Oggioni <oggioni.walter@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/woggioni/kaya
Project-URL: Bug Tracker, https://github.com/woggioni/kaya/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Topic :: Utilities
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Developers
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: kaya-core
Requires-Dist: pwo
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: ipdb; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: httpx; extra == "dev"
Requires-Dist: httpx-ws; extra == "dev"

# kaya-session

Session management for the Kaya web framework.

Provides server-side, identity-agnostic HTTP sessions via a session cookie. The
session data is accessible from request handlers as `ctx.session`.

## Usage

```python
from kaya.core import KayaApp, HttpContext
from kaya.session import SessionMixin, InMemorySessionStore

session = SessionMixin(InMemorySessionStore())
app = KayaApp(mixins=[session])

@app.GET('/')
async def home(ctx: HttpContext):
    n = ctx.session.get('visits', 0) + 1
    ctx.session['visits'] = n
    await ctx.send_str(200, f'visits: {n}')
```

Sessions are created lazily: a cookie is only set when the handler modifies the
session.

`SessionMixin` is a `KayaMixin`, so the app stays a `KayaApp` and both ASGI and
RSGI keep working.

## WebSocket sessions

The same session is available in websocket handlers as `ws.session`:

```python
@app.websocket('/ws/visits')
async def ws_visits(ws: WebSocket):
    await ws.accept()
    visits = ws.session.get('visits', 0)
    await ws.send_text(f'visits: {visits}')
```

The session is loaded from the cookie when the connection is opened and
persisted when the connection closes, if it was modified.

**Important:** although the ASGI spec allows custom headers on the WebSocket
handshake response (`websocket.accept` headers, spec 2.1+), most ASGI servers
in practice — including **Granian** and **Daphne** — do not forward them into
the HTTP `101` response. RSGI websocket handshakes cannot carry response headers
at all. Therefore, in real deployments a session cookie can only be set or
refreshed by an HTTP response. Use an HTTP endpoint to establish or update the
session before opening the WebSocket, and read the existing session in the
WebSocket handler.

## Session expiry

The cookie sent to the browser has a `Max-Age` (default 14 days), but that is
only a client-side hint. The real boundary is the store's server-side TTL,
which the mixin keeps in sync with the cookie `Max-Age`.

For `InMemorySessionStore`, a session expires if it is idle for longer than
`max_age`. Active sessions have their expiry slid forward on every access, so
a user that keeps visiting stays logged in. If the client ignores the cookie's
`Max-Age` and replays an old cookie value, the store rejects the expired
session and creates a fresh empty one.

Set `max_age=None` to disable server-side expiry (and the `Max-Age` cookie
attribute) entirely.

## Features

- `Session`: dict-like session object with modification tracking
- `SessionStore`: abstract store interface
- `InMemorySessionStore`: simple in-memory store for development/single-process
- `SessionMixin`: composable Kaya mixin managing session cookies and persistence
- Session ID regeneration (`session.regenerate_id()`) and invalidation
  (`session.invalidate()`) for authentication layers
- WebSocket support: the session is exposed as `ws.session` in websocket
  handlers, loaded at connect time and persisted on close

## Notes

- `InMemorySessionStore` does not survive process restarts and is not shared
  across processes. Production deployments should use a store backed by a shared
  storage system (planned).
