Dependency Injection 
Open Ticket AI uses dependency injection (DI) to manage services, pipes, and shared infrastructure. The container is responsible for building core singletons, loading plugins, and providing factories that resolve dependencies at runtime.
Component registry fundamentals 
ComponentRegistry tracks every injectable that the runtime can construct. Pipes and services are stored in separate dictionaries so the registry can enforce clearer error messages and type expectations during lookup.【F: src/open_ticket_ai/core/dependency_injection/component_registry.py†L12-L41】
- register()inspects the class that is being registered. Pipes (subclasses of- Pipe) are stored in- _pipes, while other subclasses of- Injectableare stored in- _services.
- When a lookup fails, InjectableNotFoundErrorincludes the identifiers that are currently loaded to make configuration issues easier to diagnose.【F: src/open_ticket_ai/core/dependency_injection/component_registry.py†L23-L40】【F: src/open_ticket_ai/core/config/errors.py†L26-L34】
- find_by_type()performs a filtered search across both collections. The TemplateRenderer bootstrap (described below) relies on this to find services that implement a specific interface.【F:src/open_ticket_ai/core/dependency_injection/component_registry.py†L42-L48】
How plugins populate the registry 
Each plugin implements Plugin._get_all_injectables() and returns every service and pipe class that should be discoverable. During application startup the PluginLoader locates entry points in the open_ticket_ai.plugins group, instantiates the plugin, and calls on_load() with the shared registry.【F:src/open_ticket_ai/core/plugins/plugin_loader.py†L19-L51】
Plugin.on_load() builds a registry identifier for each injectable. The prefix is derived from the plugin module name (with the global otai- plugin prefix removed) and combined with the injectable’s own get_registry_name() using : as the separator. This ensures registry IDs remain globally unique while still being readable (for example base:MyService).【F: src/open_ticket_ai/core/plugins/plugin.py†L13-L44】【F: src/open_ticket_ai/core/config/app_config.py†L13-L23】
Container bootstrap sequence 
AppModule is the Injector module that wires together the runtime. Its constructor eagerly creates several singletons:
- AppConfigloads environment,- .env, and- config.ymlsettings and exposes the workspace configuration model.【F:src/open_ticket_ai/core/dependency_injection/container.py†L22-L25】【F: src/open_ticket_ai/core/config/app_config.py†L5-L37】
- ComponentRegistryis instantiated and injected into the module, plugin loader, pipe factory, and tests.【F:src/open_ticket_ai/core/dependency_injection/container.py†L25-L28】
- LoggerFactoryis produced by- create_logger_factory()so that every injectable can obtain structured loggers.【F:src/open_ticket_ai/core/dependency_injection/container.py†L26-L28】【F: src/open_ticket_ai/core/logging/stdlib_logging_adapter.py†L1-L45】
- PluginLoaderreceives the registry, logger factory, and configuration.- load_plugins()runs immediately so that pipes and services from plugins are available before the Injector resolves other bindings.【F:src/open_ticket_ai/core/dependency_injection/container.py†L27-L34】
During configure(), AppModule binds these instances as singletons and registers the PipeFactory type itself as a singleton so other components can request it later.【F: src/open_ticket_ai/core/dependency_injection/container.py†L36-L42】
TemplateRenderer selection and safeguards 
Template rendering is a required service. The provider method create_renderer_from_service() inspects the configured services, filters the entries that implement TemplateRenderer, and enforces that exactly one configuration exists.【F: src/open_ticket_ai/core/dependency_injection/container.py†L44-L73】【F: src/open_ticket_ai/core/dependency_injection/service_registry_util.py†L1-L17】
- If more than one TemplateRenderer service is present, MultipleConfigurationsForSingletonServiceErrorstops the bootstrap. This prevents ambiguity about which renderer should be used for templating.【F: src/open_ticket_ai/core/dependency_injection/container.py†L63-L66】【F: src/open_ticket_ai/core/config/errors.py†L52-L64】
- If none are found, MissingConfigurationForRequiredServiceErroris raised, signalling that the configuration is incomplete and template rendering cannot proceed.【F: src/open_ticket_ai/core/dependency_injection/container.py†L68-L71】【F: src/open_ticket_ai/core/config/errors.py†L41-L50】
After validation, the registry is asked for the concrete renderer class, which is instantiated with its rendered parameters and returned as the singleton TemplateRenderer for the application.【F: src/open_ticket_ai/core/dependency_injection/container.py†L72-L78】【F: src/open_ticket_ai/core/template_rendering/template_renderer.py†L1-L52】
Injecting dependencies into pipes and services 
Runtime dependency resolution is orchestrated by PipeFactory:
- When a pipe is created, the factory resolves every injectsentry in the pipe configuration. Each mapping associates a constructor argument (such asticket_client) with the identifier of a configured service. The factory fetches the service configuration, renders its parameters, instantiates the service, and passes it to the pipe constructor.【F: src/open_ticket_ai/core/pipes/pipe_factory.py†L19-L74】
- Services and pipes must accept logger_factoryin their constructors (supplied byInjectable.__init__) so they can emit namespaced logs without reconfiguring logging backends.【F: src/open_ticket_ai/core/injectables/injectable.py†L11-L24】【F: src/open_ticket_ai/core/logging/logging_iface.py†L7-L23】
- Use the registry naming scheme produced by Plugin._get_registry_name()when declaringusetargets in configuration. This ensures the factory can look up the correct class (e.g.base:HttpTicketPipe).【F:src/open_ticket_ai/core/plugins/plugin.py†L25-L44】
Pipes receive their parameters through templating before instantiation. PipeFactory renders the params block using the active TemplateRenderer, then constructs the pipe with the rendered PipeConfig, the PipeContext, references to itself (for creating nested pipes), and any injected services.【F:src/open_ticket_ai/core/pipes/pipe_factory.py†L31-L61】
Services follow the same pattern: once selected, the factory renders the service configuration against an empty scope and builds the injectable. Because every service derives from Injectable, they automatically parse typed parameters via their ParamsModel and get a logger named after their configuration ID.【F:src/open_ticket_ai/core/pipes/pipe_factory.py†L62-L74】【F: src/open_ticket_ai/core/injectables/injectable.py†L11-L24】
Practical tips 
- Define a descriptive ParamsModelon your pipe or service subclass so configuration gets validated automatically during construction.【F: src/open_ticket_ai/core/injectables/injectable.py†L9-L24】
- Keep inject IDs consistent across configuration and runtime by reusing the registry prefix described above.
- When creating new plugins, return all injectable classes from _get_all_injectables()so they register automatically during bootstrap.【F:src/open_ticket_ai/core/plugins/plugin.py†L37-L44】
- If a pipe requires another pipe, inject PipeFactoryand callawait pipe_factory.create_pipe(...)rather than instantiating it directly. The factory will handle templating, logging, and dependency resolution for nested pipes.【F: src/open_ticket_ai/core/pipes/pipe_factory.py†L19-L61】