di package

Reviewed against commit 8a2cb42ad on 2026-10-23.

Reference: package page (2 modules, 918 lines) · key module: container. The container's one in-tree consumer is the gateway server; how the pieces are wired together overall is in the overview. Reviewed with no factual drift: the five public classes, the module count and the core-only imports all match the code.

The story

orchestrata_ultimate.di exists because orchestrata is a system of many long-lived, interdependent services — gateways, engines, stores — that need to be wired together without every subsystem hard-coding how its collaborators are constructed. Per its package docstring, it "provides a flexible, type-safe DI system supporting multiple scopes and configuration-driven component assembly via DIContainer." Rather than scattering construction logic across the codebase, a composition root describes what implements which interface, and the container takes over the mechanics of building, caching, and eventually tearing down the object graph. The whole implementation is deliberately small: one module, container.py, whose five public classes (ComponentRegistration, ContainerBuilder, ContainerRegistry, DIContainer, ResolutionContext) are re-exported by the package __init__.py.

The lifecycle of a component starts with registration. An application either calls DIContainer.register(...) / register_instance(...) directly, or — the more idiomatic, "configuration-driven assembly" path — uses the fluent ContainerBuilder (with_config, with_component, with_instance, with_context, then build()). Every registration is validated and stored as a ComponentRegistration dataclass: which interface it satisfies, the implementing class or an alternative factory callable, its ComponentScope (singleton by default, or transient / request), a per-component config dict, and an explicit dependencies list. Registration methods return the container (or the builder) itself, so calls chain fluently.

Resolution is where the container earns its keep. await container.resolve(Interface) walks _resolve_with_context → _create_instance → _resolve_dependencies, using inspect (in _extract_dependencies) to read constructor parameters so that dependencies are injected recursively without any manual plumbing. A ResolutionContext is threaded through the whole pass: its resolving stack detects circular dependencies before they become infinite recursion, its cache holds resolved instances, and its context_id isolates REQUEST-scoped components so that concurrent requests never share state. Instantiation is thread-aware — the container guards its scope caches with a threading.Lock — which matters in this free-threaded Python 3.14 codebase. When the process winds down, await shutdown() tears down the managed instances; ContainerRegistry.shutdown_all() does the same across every named container published to the process-wide registry.

The package sits at the very bottom of the dependency stack: it imports only orchestrata_ultimate.core — the IComponent interface and ComponentScope enum from core.protocols, plus the @traces requirement decorator from core.base.traces, which ties nearly every class and method to SPEC-024 requirement IDs. Its main in-tree consumer is the WebSocket gateway — gateway/server.py uses DIContainer as part of the composition root that ties the gateway's subsystems together — while ContainerRegistry gives any other package a named lookup point for containers built elsewhere.

Key responsibilities

Class diagram

Principal classes of orchestrata_ultimate.di with their real attributes, methods, and relationships (generated from the source, render-verified). The classes are deliberately flat — there is no inheritance; the structure is association and composition around DIContainer:

Diagram projects-orchestrata-ultimate-modules-di-1

Module dependency diagram

Internal module imports within orchestrata_ultimate.di (generated from the AST, render-verified):

Diagram projects-orchestrata-ultimate-modules-di-2

Module inventory

Module Purpose
di/__init__.py Package entry point — "Dependency Injection container subpackage"; re-exports the five public classes from container.
di/container.py "Dependency Injection Container for Orchestrata" — the full DI system: registration dataclasses, DIContainer, fluent ContainerBuilder, and the global ContainerRegistry.

container.py — DIContainer and the resolution engine

DIContainer is the heart of the package. It supports interface-based registration (register for classes and factories, register_instance for pre-built objects, both guarded by allow_override), and its async resolve(interface, context_id=None) builds the requested object and everything beneath it. Internally, resolution descends through _resolve_with_context, _create_instance, and _resolve_dependencies / _resolve_param, discovering constructor dependencies via inspect in _extract_dependencies; _cached_instance and _store_instance mediate the scope caches. Scopes govern reuse: singletons are cached, transients are rebuilt on every resolve, and request-scoped instances are keyed by context_id. Beyond construction, the container carries arbitrary context values (set_context / get_context) available during resolution and registers itself under di_container; it offers introspection through has, get_all_registrations, and get_registry_info, and cleans up managed instances via async shutdown(). Almost every public method carries @traces decorators tying it to SPEC-024 requirements.

container.py — the registration data model

Two small dataclasses give the container its shape. ComponentRegistration is the record the container stores per registration: the interface, an optional implementation class, the scope (defaulting to ComponentScope.SINGLETON), a per-component config dict, an explicit dependencies list, and an optional factory callable that can stand in for a class when construction needs custom logic. ResolutionContext is the transient companion created for each resolution pass — its resolving stack is what makes circular-dependency detection possible, its cache is where resolved instances live for the pass, and its context_id is the isolation key for request scope. Keeping these as plain dataclasses keeps the container's state model explicit and testable.

container.py — ContainerBuilder and ContainerRegistry

The remaining two classes handle composition and discovery. ContainerBuilder is the fluent front door for configuration-driven assembly: a caller chains with_config(dict), with_component(...) (which can name a config_key to slice from the accumulated config), with_instance(interface, instance), and with_context(key, value), then calls build() to receive a fully configured DIContainer. ContainerRegistry complements it with a class-level (process-global) registry of named containers — register(name, container), get(name="default") (raising KeyError if absent), has(name), and async shutdown_all() — so a container assembled in one composition root (such as the gateway server's) can be looked up by name anywhere else in the process and torn down uniformly at exit.

Notes

Class map (generated)

Generated from the live source AST by scripts/gen-module-class-diagrams.py — do not hand-edit. Abstract classes have a green border; members listed are those defined on the class itself (inherited members excluded). Solid empty-triangle arrows = in-package inheritance; dashed arrows into dashed boxes = external/stdlib/cross-package bases; filled diamond = composition (constructed in __init__); open diamond = aggregation (annotated/injected reference); diamond edge labels name the owning attribute.

di class map