litestar-autowire
litestar-autowire 0.2.0 discovers Litestar controllers and event listeners
from domain packages. It can also load optional integration modules without
turning application setup into a central list of every domain component.
Use Autowire when the project already groups features into stable Python
packages. Keep manual Router and plugin composition when the app is small,
route registration is intentionally explicit, or packages do not follow a
consistent convention.
Code Style Rules
- Configure dotted package roots with
domain_packages.
- Import public APIs from
litestar_autowire.
- Keep controllers and listeners in their owning domain package.
- Keep route handlers and listeners async when they perform I/O.
- Configure third-party loaders via
integrations (DishkaIntegration(), QueuesIntegration()).
- Configure Dishka and Litestar Queues separately; Autowire only discovers or
wraps their domain-owned components.
- Keep runtime-inspected controller imports (
FromDishka, NamedDependency,
Request, injected service types, and DTOs) at module top-level with
# noqa: TC001, TC002 (or "**/controllers.py" = ["TC001", "TC002", "TC003"]
in Ruff per-file-ignores) when from __future__ import annotations is active.
- Call
clear_autowire_cache() when tests create, replace, or remove modules.
Quick Reference
Install
pip install litestar-autowire==0.2.0
Install only the integrations already used by the project:
pip install "litestar-autowire[dishka]==0.2.0"
pip install "litestar-autowire[queues]==0.2.0"
Domain layout
my_app/
└── domains/
├── accounts/
│ ├── controllers.py
│ ├── events.py
│ └── jobs.py
└── billing/
└── routes.py
Regular packages and PEP 420 namespace packages are supported. Autowire checks
the configured root and each direct child package. Read
Discovery before changing module conventions or
debugging a missing component.
App setup
from litestar import Litestar
from litestar_autowire import AutowireConfig, AutowirePlugin
autowire = AutowirePlugin(
AutowireConfig(domain_packages=["my_app.domains"]),
)
app = Litestar(plugins=[autowire])
The default component modules are:
| Component | Module names | Enabled |
|---|
| Controllers | controllers, routes, controller, route | Yes |
| Event listeners | events, listeners | Yes |
| Litestar Queues tasks | jobs | Only with integrations=["queues"] |
AutowireConfig fields (all constructor arguments are keyword-only):
| Field | Default | Contract |
|---|
domain_packages | () | str | Iterable[str] normalized to tuple[str, ...] |
integrations | () | Built-in names ("dishka", "queues") or AutowireIntegration objects |
discover_controllers | True | Register discovered Controller subclasses |
discover_listeners | True | Register discovered EventListener instances |
controller_modules | ("controllers", "routes", "controller", "route") | str | Iterable[str] normalized to tuple[str, ...] |
listener_modules | ("events", "listeners") | str | Iterable[str] normalized to tuple[str, ...] |
task_modules | ("jobs",) | str | Iterable[str] normalized to tuple[str, ...] |
router_class | None | Optional router wrapper (litestar.Router or compatible type) |
before_request | None | Optional hook attached to the wrapper router |
after_response | None | Optional hook attached to the wrapper router |
force_reload_tasks | False | Passed to litestar_queues.discover_tasks(force_reload=...) |
log_discovered | True | Defer discovery summary logs to the Litestar startup hook |
Use config.integration_enabled(name) to check whether an integration with name is configured.
Customize module names without changing the domain layout:
config = AutowireConfig(
domain_packages=["my_app.domains"],
controller_modules=["http"],
listener_modules=["subscribers"],
discover_listeners=False,
)
Router selection
By default, discovered controller classes are appended directly to
AppConfig.route_handlers. Set router_class=Router when the project needs a
wrapper for router-level before_request or after_response hooks:
from litestar import Router
from litestar_autowire import AutowireConfig
config = AutowireConfig(
domain_packages=["my_app.domains"],
router_class=Router,
before_request=set_request_context,
after_response=record_response,
)
The wrapper is constructed with path="/", the discovered controller classes,
and the configured hooks. Use integrations=["dishka"] instead when the
project already uses Dishka and needs DishkaRouter. See
Integrations.
<workflow>
Workflow
- Inspect the project layout and confirm domains are importable package roots.
- Choose Autowire only when domain packages follow consistent controller or
listener module conventions.
- Register one
AutowirePlugin(AutowireConfig(...)) in the app plugin list.
- Keep the default module names or configure explicit module-name tuples.
- Enable only integrations already present in the project stack.
- Configure Dishka or Litestar Queues through their own application plugins.
- Test discovery, error propagation, and cache isolation with the patterns in
Testing.
</workflow>
<guardrails>
Guardrails
- Do not describe Autowire as recursive domain discovery. It discovers the
configured root and direct feature children; component modules may themselves
contain importable leaf modules.
- Do not claim arbitrary route-handler discovery. Autowire registers
Controller subclasses and EventListener objects.
- Do not invent registry or discovered-item wrapper types. Autowire 0.2.0
exports
AutowirePlugin, AutowireConfig, AutowireContext,
AutowireIntegration, AutowireLoader, DishkaIntegration,
QueuesIntegration, clear_autowire_cache, discover_controllers,
discover_feature_packages, discover_listeners, discover_queue_tasks,
find_controllers_in_module, and find_listeners_in_module. There is no
AutowireRegistry, DomainMetadata, DiscoveredCLI, DiscoveredController,
DiscoveredJob, DiscoveredProvider, DiscoveredSchema, DiscoveredSignal,
LitestarQueuesIntegration, or SAQIntegration (use AutowireLoader for
SAQ or custom module registries).
- Do not imply queue tasks are enabled by default. The
queues integration
must be selected and its optional dependency installed.
- Do not suppress dependency import failures. Only an absent configured
package or absent target component module is skipped.
- Do not use both a custom
router_class and expect Dishka to replace it.
DishkaIntegration preserves an already selected router class.
- Do not hide
FromDishka[...] types under if TYPE_CHECKING: when
from __future__ import annotations is enabled in controller modules.
DishkaRouter.register calls typing.get_type_hints() on each controller
handler at registration time, which raises NameError if injected types are
absent from runtime module globals or defined only in a function scope. Keep
those imports at module top-level with # noqa: TC001, TC002 (or configure
Ruff per-file-ignores for controller modules).
- Do not reuse a
Controller class across unit tests after DishkaRouter
registration without restoring get_route_handlers or providing a Dishka
container. DishkaRouter.register monkey-patches
ControllerSubclass.get_route_handlers in-place on the class, so mounting
that controller on an isolated Litestar test app without setup_dishka
fails at request time with AttributeError: 'State' object has no attribute 'dishka_container'.
- Do not give custom integrations the names
dishka or queues. Built-in
name collisions fail during configuration.
- Do not reuse stale discovery state in tests or reload tooling. Clear the
process-local caches before rediscovery.
</guardrails>
<validation>
Validation Checkpoint
</validation>
<example>
Example
from litestar import Controller, get
class AccountController(Controller):
path = "/accounts"
@get()
async def list_accounts(self) -> list[dict[str, str]]:
return [{"name": "primary"}]
from litestar import Litestar
from litestar_autowire import AutowireConfig, AutowirePlugin
app = Litestar(
plugins=[
AutowirePlugin(
AutowireConfig(
domain_packages=["my_app.domains"],
discover_controllers=True,
discover_listeners=True,
)
)
],
)
Autowire imports my_app.domains.accounts.controllers, registers
AccountController, and checks the configured listener module names. It does
not load jobs.py until the queues integration is selected.
</example>
References Index
- Discovery — package traversal, PEP 420 behavior,
component selection, caches, and import failures
- Integrations — custom integrations,
AutowireLoader, router selection, Dishka, and Litestar Queues
- Testing — deterministic cache isolation and
package-discovery tests
- Litestar Routing — controller and manual
router composition
- Litestar Plugins — application plugin wiring
- Litestar DI — built-in DI and Dishka selection
- Litestar Queues — queue configuration, tasks,
and workers
- Litestar Testing — Litestar test clients and
dependency overrides
Official References
Shared Styleguide Baseline