Skip to content

Autowired Dependency Injection

Overview

Autowired provides declarative property injection for the Loryth DI container. The mechanism is entirely metadata-driven: annotate attributes with typing.Annotated and the DI runtime will inject resolved dependencies before the instance is used. The new form removes helper factories and legacy markers, keeping annotations concise and type-checker friendly.

from typing import Annotated

from loryth.common.di.injection import Autowired


class OrderService:
    repository: Annotated["OrderRepository", Autowired()]
    logger: Annotated["LoggerProtocol", Autowired("orders")]

Quick Start

from typing import Annotated

from loryth.common.di.bootstrap import BootstrapPlan
from loryth.common.di.bootstrap import BootstrapRuntime
from loryth.common.di.bootstrap import SingletonRegistrationStep
from loryth.common.di.container import Container
from loryth.common.di.injection import Autowired


class Database:
    def save(self, entity: object) -> None:
        ...


class Logger:
    def log(self, message: str) -> None:
        ...


class UserService:
    database: Annotated[Database, Autowired()]
    audit_logger: Annotated[Logger, Autowired("audit")]

    def create_user(self, username: str) -> None:
        self.audit_logger.log(f"creating {username}")
        self.database.save({"username": username})


container = Container()
plan = BootstrapPlan(
    registrations=(
        SingletonRegistrationStep(Database, Database),
        SingletonRegistrationStep(Logger, Logger, qualifier="audit"),
    )
)
BootstrapRuntime().bootstrap(container, plan)

service = container.resolve(UserService)
service.create_user("alice")

Metadata Reference

from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class Autowired:
    qualifier: str | None = None
  • qualifier (optional): named binding identifier to request a specific registration. Qualifiers are stripped of surrounding whitespace and validated for non-empty strings.

Autowired metadata is only used inside Annotated. No runtime aliasing or helper factories are required.

Usage Patterns

Default Injection

class EmailService:
    sender: Annotated["EmailSender", Autowired()]

When the container resolves EmailService, AutowiredInjector looks up the EmailSender registration and assigns it to sender.

Qualified Injection

class ReportingService:
    primary_logger: Annotated["Logger", Autowired("primary")]
    audit_logger: Annotated["Logger", Autowired("audit")]

Register two loggers with distinct qualifiers. The injector selects the correct binding based on the qualifier metadata.

Combined Constructor and Property Injection

class SessionManager:
    token_cache: Annotated["Cache", Autowired()]

    def __init__(self, clock: "Clock") -> None:
        self.clock = clock

Constructor parameters come from the constructor injector, while token_cache is populated by AutowiredInjector.

Best Practices

  • Annotate every dependency. Unannotated attributes are ignored by the injector.
  • Prefer explicit qualifiers when multiple implementations for the same type are registered. Omitting the qualifier relies on the container's primary registration.
  • Avoid runtime mutation of injected properties. Treat them as read-only.
  • Keep annotations importable. Use forward references ("TypeName") when the injected class is defined later in the module.

Troubleshooting

  • Missing dependency: PropertyInjectionError wraps the underlying resolution failure. Confirm the service is registered (and qualified, if applicable).
  • Qualifier mismatch: Ensure the registration uses the same qualifier string as the annotation metadata.
  • Type checking issues: Annotated preserves static typing. Confirm the imported type is the same one used for registration.
  • loryth.common.di.injection.AutowiredInjector
  • loryth.common.di.di_container_builder.ContainerBuilder
  • loryth.common.di.bootstrap.BootstrapRuntime

For constructor injection and advanced wiring strategies see: - Constructor Injection Guide (coming soon) - Dependency Qualifiers (coming soon)