Future work - #462
Draft
silviogutierrez wants to merge 1 commit into
Draft
Conversation
Placeholder PR tracking future cleanups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Placeholder PR tracking future cleanups. Not for merge as-is.
TODO
Remove
ReactivatedMiddleware. It's now redundant:Template.render()already sets the JSONcontent-typeoff_is_reactivated_response, and the JSX template backend is gone. Only keep if a JSON-props response can bypassTemplate.render().Implement a
KeyOfAST builder, e.g.FooPresets = KeyOf[Foo, int]builds a type keyed byFoo's members withintvalues, sopresets: FooPresetstype-checks precisely. Likepick(), 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 takeexport=True), or whether callingexport()on the result is enough. Context: raw enum-memberTypedDictindexing isn't runtime-safe (TypedDict accepts an Enum member as a key and reveals the value type, but KeyErrors at runtime python/mypy#21722), soKeyOfshould 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.superusergates, mirroringrouter.authenticated— cached root scopes gated onrequest.user.is_staff/.is_superuser, injecting the concreteUser(via django-stubs_User). Order the checkis_authenticated and is_staffso_Usernarrows. They inject a plainUser; a project wanting aStaffUserproof-type (aNewType) 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]sorouter.staff -> Scope[StaffT, StaffT]defaults to_Userbut 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 viasync_to_async(_ScopeAdapter.run), and anasync defscope body is bounced throughasync_to_syncinside that syncrun(a double bounce, loop → thread → loop). Handlers follow a four-cell matrix (sync|async × atomic): underatomic_requeststhe handler runs inside one synctransaction.atomic(), async handlers bounced viaasync_to_syncso their ORM re-joins the transaction thread. Two steps to go native:_ScopeAdapter.arun(await async scopes,sync_to_asyncthe sync ones) and routeasync handler + atomic_requests=Falsethrough 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 viaawait request.auser()rather than the syncrequest.user— theiscoroutinefunctionsignal to choose is already present. Public API is unchanged; this is a pure engine optimization.transaction.atomic()is sync-only (Django 5.2Atomicdefines__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.userandrequest.auser()cache separately (_cached_uservs_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 thereactivatelauncher), 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 explicitDjangorange (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.[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 sayreactivated[standard]. The alternative that works today, a meta-package split (the ansible/ansible-core pattern: bare name = heavy meta-package depending on a-coredistribution), buys the literal default at the cost of two PyPI distributions in lockstep — not worth it at this scale.uvicorn[standard]only belongs in[standard]together with a production serve entrypoint (areactivate servecounterpart 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.