Skip to content

Future work - #462

Draft
silviogutierrez wants to merge 1 commit into
mainfrom
future-work
Draft

Future work#462
silviogutierrez wants to merge 1 commit into
mainfrom
future-work

Conversation

@silviogutierrez

@silviogutierrez silviogutierrez commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Placeholder PR tracking future cleanups. Not for merge as-is.

TODO

  • Remove ReactivatedMiddleware. It's now redundant: Template.render() already sets the JSON content-type off _is_reactivated_response, and the JSX template backend is gone. Only keep if a JSON-props response can bypass Template.render().

  • Implement a KeyOf AST builder, e.g. FooPresets = KeyOf[Foo, int] builds a type keyed by Foo's members with int values, so presets: FooPresets type-checks precisely. Like pick(), it constructs the type at runtime and the reactivated mypy plugin substitutes the real generated type at check time. Decide whether it also needs to be a function (to take export=True), or whether calling export() on the result is enough. Context: raw enum-member TypedDict indexing isn't runtime-safe (TypedDict accepts an Enum member as a key and reveals the value type, but KeyErrors at runtime python/mypy#21722), so KeyOf should generate keys that are actually safe to index.

  • Audit PEP 696 (type variable defaults) under the newer mypy's complete support: how it affects the codebase, what we can support, and schema generation.

  • Built-in router.staff / router.superuser gates, mirroring router.authenticated — cached root scopes gated on request.user.is_staff / .is_superuser, injecting the concrete User (via django-stubs _User). Order the check is_authenticated and is_staff so _User narrows. They inject a plain User; a project wanting a StaffUser proof-type (a NewType) still writes its own gate — the gate is the runtime proof, the NewType only adds static propagation and is worth it only when the principal is threaded into privilege-requiring helpers. Tie-in with the PEP 696 item above: generic defaults could let the built-ins be optionally typed to a project NewType — e.g. Router[Request, StaffT = _User] so router.staff -> Scope[StaffT, StaffT] defaults to _User but a project can substitute its own principal type with no per-module boilerplate. Depends on the PEP 696 audit (plugin/default interaction, schema gen).

  • Native async execution. Scopes and rpc handlers can be authored async def, but execution is currently sync-driven: the scope chain runs via sync_to_async(_ScopeAdapter.run), and an async def scope body is bounced through async_to_sync inside that sync run (a double bounce, loop → thread → loop). Handlers follow a four-cell matrix (sync|async × atomic): under atomic_requests the handler runs inside one sync transaction.atomic(), async handlers bounced via async_to_sync so their ORM re-joins the transaction thread. Two steps to go native:

    • Non-atomic (no external dependency): add an async _ScopeAdapter.arun (await async scopes, sync_to_async the sync ones) and route async handler + atomic_requests=False through it, so the chain runs in the request's event loop with no thread hop. On this path the built-in gates should resolve the user via await request.auser() rather than the sync request.user — the iscoroutinefunction signal to choose is already present. Public API is unchanged; this is a pure engine optimization.
    • Atomic (blocked upstream): transaction.atomic() is sync-only (Django 5.2 Atomic defines __enter__/__exit__, no __aenter__/__aexit__), so an atomic request must drop to a sync thread until Django ships an async transaction; then the atomic path can go native too.

    Note the accessor caveat: request.user and request.auser() cache separately (_cached_user vs _acached_user, no cross-population), so mixing accessors within a request double-resolves — match the accessor to the execution path.

  • Packaging tiers: reactivated[core] / reactivated[standard]. Two problems today: the package hard-depends on heavy tooling (mypy, django-stubs, now uvicorn/watchfiles for the reactivate launcher), and Django itself is only a transitive dependency via django-stubs — always installed, declared by nobody. Proposal:

    • [core] — the lean tier: just the glue (pick/rpc/templates) and its direct needs (pydantic, requests, asgiref). For installs that bring their own stack (slim production images).
    • [standard] — the batteries tier: core plus the boot stack every downstream project currently re-declares by hand: an explicit Django range (fixing the via-stubs transitive), psycopg[binary] (standardize on psycopg 3; downstream projects still on psycopg2 migrate), dj-database-url, uvicorn[standard], and mypy + django-stubs. The framework knows which versions it works against; downstream projects should pin via their lockfiles, not re-solve compatibility.
    • The bare name should mean [standard] for a typical PyPI install. Extras are strictly opt-in under current packaging standards, so that's not expressible yet: PEP 771 (default extras) is the exact mechanism — plain install gets the default extra, reactivated[core] deselects — but it's still in draft with no pip/uv support to rely on. Until it lands, the default lives in the paved path: docs, README, and the scaffold all say reactivated[standard]. The alternative that works today, a meta-package split (the ansible/ansible-core pattern: bare name = heavy meta-package depending on a -core distribution), buys the literal default at the cost of two PyPI distributions in lockstep — not worth it at this scale.
    • Dependency follows invocation: uvicorn[standard] only belongs in [standard] together with a production serve entrypoint (a reactivate serve counterpart to the dev launcher), so downstream deployment units invoke the framework rather than the uvicorn binary directly. Until that exists, downstream projects correctly keep declaring their own production server — a project execing a binary it doesn't declare, provided transitively by a framework that might later slim down, is the silent-breakage trap this whole item exists to avoid.

Placeholder PR tracking future cleanups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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