Skip to content

pyxle-db + pyxle-auth 0.3.0 — middleware + pure-ASGI streaming compatibility#1

Merged
shivamsn97 merged 5 commits into
mainfrom
fix/pure-asgi-middleware
Jun 18, 2026
Merged

pyxle-db + pyxle-auth 0.3.0 — middleware + pure-ASGI streaming compatibility#1
shivamsn97 merged 5 commits into
mainfrom
fix/pure-asgi-middleware

Conversation

@shivamsn97

Copy link
Copy Markdown
Member

pyxle-db + pyxle-auth 0.3.0

Brings both official plugins to 0.3.0 (PyPI is still at 0.2.0) and makes their middleware streaming-SSR-compatible.

Features (0.3.0)

  • pyxle-db — request-scoped DB injection (request.state.db), automatic transactions (commit-on-2xx / rollback-otherwise keyed on the response, not an escaping exception), an optional SQLAlchemy ORM path (request.state.session), and an Alembic-backed migration CLI.
  • pyxle-authAuthSessionMiddleware (populates request.user, serves /auth/{me,login,signup,logout,token,token/refresh}), sliding sessions, OAuth 2.0/OIDC (Google/GitHub/Discord, PKCE), JWT issue/refresh, and the useAuth client endpoints.

Fix: pure-ASGI middleware (streaming-SSR compatible)

All three middlewares (PyxleDbMiddleware, AuthSessionMiddleware, OAuthMiddleware) were Starlette BaseHTTPMiddleware, which buffers the whole response — that defeats streaming SSR (a <Suspense> page arrives all at once) and surfaces a mid-stream client disconnect as RuntimeError: No response returned. Rewritten as pure-ASGI: forward every body chunk straight through, capture status from http.response.start, and decide commit/rollback (db) or pass-through (auth) after the response completes. Endpoints, cookies, request.user seeding, and the auto-transaction contract are unchanged.

pyxle-db additionally:

  • a mid-stream disconnect rolls back the partial write (it was committing — anyio swallows the disconnect so the 200 was already captured; commit now requires the terminal body frame);
  • a commit failure after the response is sent is logged, not swallowed as an unhandled ASGI error, with the ORM session finalized in a finally.

Tests: pyxle-db 313 passed, pyxle-auth 389 passed.

…ic CLI

- PyxleDbMiddleware injects a request-scoped `request.state.db` and runs each
  unsafe-method request in one auto-transaction, committing or rolling back on
  the response status (the dispatcher swallows action errors, so commit cannot
  key off an escaping exception). Opt out per-action with @no_auto_transaction.
- Optional SQLAlchemy async ORM path behind the [sqlalchemy] extra: an async
  engine + session factory wired to the same database, with the middleware
  managing request.state.session.
- A `pyxle-db` CLI (argparse, base-dep-light): migrate/status plus
  alembic-init/revision/upgrade/downgrade/current/history for the ORM path.
- Migrator.status() + portable migration helpers. Ships an examples/crud-notes
  app. Bumped to 0.3.0.
… endpoints

- AuthSessionMiddleware populates request.user (zero-DB when no cookie) and
  serves the endpoints the client useAuth hook talks to: GET /me, POST /login,
  /signup, /logout. Publishes the user + endpoint map on scope['pyxle.auth']
  for the SSR seed. Guards reuse the cached request.user so a guarded loader
  never re-resolves.
- OAuth (Google/GitHub/Discord, [oauth] extra): PKCE S256, a signed single-use
  HttpOnly state cookie, verified-email-only account linking, same-origin-only
  next, env-only secrets.
- JWT (pyjwt, [jwt] extra): HS256 access tokens + opaque rotating refresh tokens
  with reuse -> family revoke; bearer_user/authenticate guards (session->JWT->PAT).
- login_required/permission_required guard aliases. AuthService.verify_credentials
  (extracted) + start_session + create_external_user. Migrations 0002/0003.
  Ships an examples/auth-app. Bumped to 0.3.0.
PyxleDbMiddleware (pyxle-db) and AuthSessionMiddleware + OAuthMiddleware (pyxle-auth) were Starlette BaseHTTPMiddleware, which buffers the whole response — defeating streaming SSR (a <Suspense> page arrives all at once) and surfacing a mid-stream disconnect as 'RuntimeError: No response returned'. Rewritten as pure-ASGI: forward every body chunk through, capture status from http.response.start, and decide commit/rollback (db) or pass-through (auth) after the response completes. Endpoints, cookies, request.user seeding, and the auto-transaction contract are unchanged.

pyxle-db additionally: a mid-stream disconnect now ROLLS BACK the partial write (it was committing — anyio swallows the disconnect so the 200 was already captured; commit now requires the terminal body frame); a commit failure after the response is sent is logged rather than escaping as an unhandled ASGI error, with the ORM session finalized in a finally; ruff per-file-ignore for the pytest.importorskip E402 in the dialect tests.

Part of the unreleased 0.3.0; CHANGELOGs note the streaming-safe property. Tests: pyxle-db 313 passed, pyxle-auth 389 passed.
The 0.3.0 ORM feature added tests/orm, whose conftest imports pyxle_db.orm
and fails collection (ConfigurationError) unless SQLAlchemy is installed.
The CI install line and the README dev-setup command only pulled in
[postgres,mysql,dev], so every test-matrix job errored at collection.
Add the sqlalchemy extra in both places.
The live-backend conformance suite's teardown dropped a fixed table list
that predated the 0.3.0 migrations, so oauth_identities (0002) and
jwt_refresh_tokens (0003) leaked between tests. On PostgreSQL the leaked
FK back to users blocked the users drop (DependentObjectsStillExist); on
MySQL the surviving index made the next run's bare CREATE INDEX fail
(Duplicate key name). Add both tables to the drop list and update the
idempotency assertion to expect all three applied migrations.

These tests run only against the live PG/MySQL service containers, so the
regression was invisible locally (the live params skip without the engine
URLs) and only surfaced once the sqlalchemy-extra fix let the job reach
the pyxle-auth step.
@shivamsn97 shivamsn97 merged commit db29473 into main Jun 18, 2026
7 checks passed
@shivamsn97 shivamsn97 deleted the fix/pure-asgi-middleware branch June 18, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant