37. diwire is the session registry¶
Date: 2026-11-03
Status¶
Accepted
Context¶
A session builds its components out of a table of values it fills as the
build goes on: the framework values it provides itself, such as
SessionConfig, every component it builds, and every value a component
shares with provides. That table was an in-n-out Store, although only
app-model, which fills the commands of a Qt application, needs in-n-out.
Every session depended on it, Qt or not.
Two directions redsun wants to take need a table that belongs to one session
and nothing else. A web frontend would fill its request handlers from the same
values the components were built from, and a worker process running a heavy
presenter would build a table of its own from values sent to it. in-n-out
keeps a global store next to the named ones, and it answers a request for a
subclass with a value registered under its base class, so what a component
receives depends on more than what it asked for.
Two dependency injection libraries were weighed for the new table:
diwirebuilds a container that takes registrations at any time, and refuses a key nobody registered when it is created withMissingPolicy.ERROR. It has no dependencies of its own.wireupchecks its container once, when it is created from a complete list, and takes nothing afterwards.
A session still registers after its first lookup: a provider is built from
the registry and then adds the values it shares. wireup takes nothing once
its container exists, so diwire was chosen.
Decision¶
redsun.injection.Registrywraps onediwireContainer, one per session, throughdiwire's public API only. A value is registered as a factory called on each request, asin-n-outcalled a provider each time.- A key holds one value. A second registration under a key raises
RegistrationErrornaming both owners, and the build stops. A component sharing a framework key such asSessionConfig, or the class of a component declared once, is refused the same way. - Every key is registered in the registry step, before any component is
built: each component's name, its class when the class is declared once,
and the return type of each of its
providesmethods, all read from the declarations. Each factory returns the value the build fills in later, orNoneuntil then, and aNonecounts as not answered.diwirethrows away its compiled graph on each registration, so registering components as they were built made it compile once per component: 40 presenters took 1.8 s. Registered first, they take 0.12 s and one compile, and each key adds about 1 ms; plain methods and properties add nothing. - A lookup matches its key exactly.
X | Noneis answered by the value registered underX, because the build turns every parameter with a default into that form; nothing else falls back to another key. A union of several types that containsNone, such asA | B | None, is optional: the parameter keeps its default. app-modelreads the registry throughRegistryStore, anin-n-outStorewhose providers come from the registry, so nothing is registered onin-n-outitself.- Once the build has finished, a protocol question from outside the build, such
as a command parameter typed with a protocol, is answered by matching built
components by shape each time it is asked, as
setupis answered (ADR 17). - A hook in
diwirethat would let the container askredsunabout a key nobody registered was left for later. It only saves a future web frontend one step when it starts.
A frontend that shares the session's table with an application of its own
overrides the method returning it, as QtSession does for its app-model
application. RegistryStore is internal to redsun.qt.
Before, in QtSession:
After, in QtSession:
def make_registry(self) -> Registry:
registry = super().make_registry()
cast("RegistryStore", self.model.injection_store).registry = registry
return registry
A component asking for a subclass of what another component shares now gets nothing.
class MyStage(Stage): ...
class MyController:
@provides
def stage(self) -> Stage: ...
class MyView:
def setup(self, stage: MyStage | None = None) -> None: ...
Under in-n-out, MyView.setup received the Stage shared by
MyController, although it asked for MyStage. Under the registry it
receives None, because nothing is registered under MyStage.
Consequences¶
in-n-outmoves to theqt-commonextra, and the core no longer imports it. Code that importsin_n_outwithout the Qt extra installs it itself.BuildableSession.make_storebecomesmake_registry, which returns aRegistry.- A session that registered one key twice, which
in-n-outallowed, stops building in the registry step, before any component is built, and names both owners. A component that would have failed to build still clashes. - A component asking for a subclass or for a union
A | Bno longer receives a value registered under a base class or under one member of the union. With a default,A | B | Nonekeeps it. - A session builds about three times slower than under
in-n-out, which compiles nothing: 0.12 s against 0.04 s for 40 presenters. - A command can ask a protocol question once the build has finished.