Alias¶
Alias lets one type resolve to whatever provider already handles a different type. The most common use is binding an abstract base or Protocol to a concrete implementation that is already registered, without registering the implementation twice.
Resolving the alias delegates straight back through the container, so overrides and caching on the source provider apply transparently.
Parameters¶
Alias(source_type, *, bound_type=UNSET) — source_type may also be passed as a keyword
(source_type=).
source_type¶
The type whose registered provider should answer the call. At resolution time, the alias looks up source_type in the providers registry and delegates to that provider. If source_type is not registered, an AliasSourceNotRegisteredError is raised.
bound_type¶
The type the alias is registered under in the providers registry — i.e. the type you pass to container.resolve(...). Defaults to source_type (which makes the alias a no-op); set it to the abstract or Protocol type you want resolvable.
An alias holds no instance and applies no caching; its effective scope is derived from its source provider.
Basic Usage¶
import dataclasses
from typing import Protocol
from modern_di import Container, Group, Scope, providers
class Repository(Protocol):
def fetch(self) -> list[str]: ...
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class PostgresRepository:
dsn: str = "postgres://localhost"
def fetch(self) -> list[str]:
return ["row-1", "row-2"]
class Dependencies(Group):
repo = providers.Factory(
PostgresRepository,
cache=True,
)
abstract_repo = providers.Alias(
PostgresRepository,
bound_type=Repository,
)
container = Container(groups=[Dependencies], validate=True)
container.open()
concrete = container.resolve(PostgresRepository)
abstract = container.resolve(Repository)
# Both resolve to the same instance — the alias delegates to the
# cached source factory.
assert concrete is abstract
Sharing the source's cache¶
Because Alias does not cache anything itself, callers automatically share whatever instance the source provider returns. With a cached Factory, every resolution path — by the concrete type, by the abstract type, or via a downstream factory parameter typed as the abstract — returns the same singleton.
With an uncached source Factory, each resolution still goes through the source factory, so each call produces a new instance (matching the source factory's own behavior).
Overrides¶
Overrides are keyed by provider_id, so the alias and its source can be overridden independently. See Testing with overrides for the container.override / reset_override primitives.
mock_for_alias = PostgresRepository(dsn="alias-mock")
container.override(Dependencies.abstract_repo, mock_for_alias)
assert container.resolve(Repository) is mock_for_alias
# The source provider is untouched.
assert container.resolve(PostgresRepository) is not mock_for_alias
Note: an active override on the alias takes precedence over an override on its source for the aliased type, so reset the alias override first if you want the source override to win.
Override the source provider instead, and both resolution paths see the mock:
mock_for_source = PostgresRepository(dsn="source-mock")
container.override(Dependencies.repo, mock_for_source)
assert container.resolve(PostgresRepository) is mock_for_source
assert container.resolve(Repository) is mock_for_source
Validation and cycle detection¶
Alias participates in container.validate() (and Container(..., validate=True)):
- If
source_typeis not registered,AliasSourceNotRegisteredErroris raised eagerly. - The alias reports the source provider as a dependency, so cycles that pass through an alias are
detected and reported via
CircularDependencyError— see Troubleshooting: Circular dependency.
Scope is checked transitively through validate()
Container.validate() checks scope transitively through aliases. A shallow-scoped caller that
depends — via an alias — on a deeper-scoped source is flagged with InvalidScopeDependencyError
at validation time — the same scope dependency rule
enforced everywhere else, applied through the alias's source chain instead of letting it surface
as ScopeNotInitializedError at runtime.