Per-user live working state, keyed on the caller's identity.
The saved-config *library* (``/api/configs``, ``backend/userdata.py``) was already
per-user, but the app's *live* working state -- the config currently loaded, its
runner, an in-flight optimization, the controller instance -- used to be process
globals. Two people connected at once therefore shared one design and clobbered
each other: uploads overwrote each other, an optimization refused to start while
another user's ran, results landed in a single slot the wrong user could read,
and one user's "stop" cancelled another's run.
This module gives each user their own ``UserSession`` holding all of that live
state. Identity is ``X-Auth-Email`` (via ``userdata.current_user``); with no Caddy
in front (local dev) there is no header, so everything collapses onto a single
``local`` session and the app behaves exactly as it did as a single-user global.
Endpoints obtain their session with ``session = Depends(get_session)`` and read
and write through it (``session.app_state``, ``session.optimizer``,
``session.controller``) instead of module globals. Because the heavy optimizer
jobs run inside nested closures, capturing ``session`` there propagates the right
user into the thread pool for free -- no ContextVar plumbing.
| str backend.session.replace_config |
( |
UserSession | session, |
|
|
| config, |
|
|
Optional[str] | config_path = None ) |
Put a whole new config in the session: off the event loop, with a new ``load_token``.
Every route that swaps the design wholesale (an upload, a design opened, a YAML handed to a
Layer or the Time-Series tab) goes through here, so none of them forgets either half.
``set_config`` builds the runner -- a cold CEA cache is minutes of CPU, which inline on the
loop froze every request. The token is how a second tab of the same user (one session per
user) sees that this is no longer the design it loaded and stops autosaving into it; a
replacement that kept the old token let that tab write the newcomer over its own design.
Returns the new token, for the response.
Definition at line 197 of file session.py.
| T backend.session.run_compute |
( |
UserSession | session, |
|
|
Callable[..., T] | fn, |
|
|
* | args, |
|
|
** | kwargs ) |
Run a solve on the session's runner in the thread pool, one at a time per session.
Forward evaluate and the Time-Series runs are seconds of CPU (a Time-Series run is ~10 s
on a laptop, ~23 s on a CI runner). As ``async def`` endpoints calling the runner inline
they ran on the event loop and froze every other request for that long -- /api/health
included, so a page opened while another page's load-time Time-Series run was going sat on
"Connecting", and the design bar's autosave could not reach the server either (the
Playwright failures of 2026-10-17). Off the loop the server keeps answering; the lock keeps
a session's solves serial, as they were.
Definition at line 178 of file session.py.