Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions docs/troubleshooting/alias-bound-to-source-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,59 @@

## Symptom

Raised when the `Alias(...)` is declared, before any container exists:

```
modern_di.exceptions.registration.AliasBoundToSourceError: Alias of <class 'Implementation'> is bound to its own source type, so it would resolve to itself. Pass bound_type= with the type the alias should answer for, such as a base class or Protocol, or bound_type=None to resolve it by reference only.
modern_di.exceptions.registration.AliasBoundToSourceError: Alias of <class 'myapp.ioc.Implementation'> is bound to its own source type, so it would resolve to itself. Pass bound_type= with the type the alias should answer for, such as a base class or Protocol, or bound_type=None to resolve it by reference only.
See: https://modern-di.modern-python.org/troubleshooting/alias-bound-to-source-error/
```

`.source_type` holds the type the alias points at. It descends from `RegistrationError`, see
It is raised by the `Alias(...)` call itself, before any container exists. `.source_type` holds the
type the alias points at. The class descends from `RegistrationError`; see
[Errors and exceptions](../providers/errors-and-exceptions.md).

## Cause

An alias is registered under its `bound_type`. With `bound_type` equal to `source_type`, the alias
registers itself as the provider for its own source. If nothing else provides
that type, the alias resolves to itself, a one-node cycle. If something does, the two collide as
duplicates. Neither does anything useful, so the declaration is rejected.

## Fix

Pass the type the alias should answer for:
registers itself as the provider for its own source. If nothing else provides that type, the alias
resolves to itself, a one-node cycle. If something does, the two collide as duplicates. Neither does
anything useful, so the declaration is rejected.

```python
<!-- invisible-code-block: python
from typing import Protocol

from modern_di import Group, providers
from modern_di import Container, Group, providers


class Interface(Protocol): ...


class Implementation: ...
-->

<!-- raises: AliasBoundToSourceError -->

```python
# Broken:
providers.Alias(Implementation, bound_type=Implementation)
```

## Fix

Pass the type the alias should answer for, such as a base class or a `Protocol`:

```python
# Works:
class Dependencies(Group):
impl = providers.Factory(Implementation)

# Broken: providers.Alias(Implementation, bound_type=Implementation)
# Works:
interface_alias = providers.Alias(Implementation, bound_type=Interface)


assert isinstance(Container(groups=[Dependencies]).resolve(Interface), Implementation)
```

To use the alias only by reference, through `container.resolve_provider(...)` or as a
`kwargs=` value, pass `bound_type=None`.
To use the alias only by reference, through `container.resolve_provider(...)` or as a `kwargs=`
value, pass `bound_type=None`.

## See also

- [Alias](../providers/alias.md) covers binding one type to an already-registered provider.
- [AliasSourceNotRegisteredError](alias-source-not-registered-error.md) covers an alias whose source has no provider.
- [Alias](../providers/alias.md): binding one type to an already registered provider.
- [AliasSourceNotRegisteredError](alias-source-not-registered-error.md): an alias whose source has no provider.
59 changes: 33 additions & 26 deletions docs/troubleshooting/alias-source-not-registered-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,60 @@

## Symptom

Raised naming the `source_type` an `Alias` points at, saying no provider is registered for it.

## Cause
```
modern_di.exceptions.resolution.AliasSourceNotRegisteredError: Cannot resolve dependency chain:
APP Interface
caused by: Alias source type <class 'myapp.alias.Implementation'> is not registered in providers registry. Register a provider for <class 'myapp.alias.Implementation'> before the alias is resolved.
See: https://modern-di.modern-python.org/troubleshooting/alias-source-not-registered-error/
```

`Alias(X, bound_type=...)` was declared, but no provider's `bound_type` resolves to `X`: either the
provider for `X` was never defined, its group wasn't passed to `Container(groups=[...])`, or it was
declared with `bound_type=None` (making it unresolvable by type, which an alias also can't reach).
This is checked eagerly during `validate()`, and again at resolve time if validation was skipped.
`resolve()` and `resolve_provider()` raise it with the chain shown above. `container.validate()`
reports it inside [`ValidationFailedError`](validation-failed-error.md), without the chain. `.source_type`
holds the type the alias points at.

## Fix
## Cause

Register (and include) a provider for the source type. It only has to be registered by the time the alias is first resolved:
`Alias(X, bound_type=...)` delegates to whatever provider is registered under `X`, and none is. One of
these applies:

<!-- invisible-code-block: python
class Interface: ...
- No provider for `X` was ever declared.
- The group that declares it was not passed to `Container(groups=[...])`.
- The provider for `X` was declared with `bound_type=None`. The alias looks its source up by type, so a
provider that is reachable only by reference does not count, and the message still says
`not registered`.

## Fix

class Implementation(Interface): ...
-->
Register a provider for the source type under that type, and pass its group to the container:

```python
from typing import Protocol

from modern_di import Group, Scope, providers
from modern_di import Container, Group, Scope, providers


class Interface(Protocol): ...


class Implementation: ...


class Dependencies(Group):
# The alias's source must resolve by type — no bound_type=None here.
impl = providers.Factory(Implementation, scope=Scope.APP)

interface_alias = providers.Alias(Implementation, bound_type=Interface)
```

<!-- invisible-code-block: python
from modern_di import Container

Container(groups=[Dependencies]).validate()
-->
container = Container(groups=[Dependencies])
container.validate()
assert isinstance(container.resolve(Interface), Implementation)
```

If the source provider lives in a different `Group`, make sure that group is also passed to
`Container(groups=[...])`. Call `container.validate()` so this is caught at startup rather than on first
resolve.
The source only has to be registered by the time the alias is first resolved, so a provider added
later with `container.add_providers(...)` also works. Calling `container.validate()` at startup
reports a missing source before the first request.

## See also

- [Alias](../providers/alias.md) covers binding one type to an already-registered provider.
- [No provider registered for type](missing-provider.md) covers the same "unregistered type" problem, without an alias in the way.
- [Alias](../providers/alias.md): binding one type to an already registered provider.
- [ProviderNotRegisteredError](missing-provider.md): the same gap without an alias in the way.
- [ValidationFailedError](validation-failed-error.md): how `validate()` groups the errors it finds.
108 changes: 79 additions & 29 deletions docs/troubleshooting/argument-resolution-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,47 @@

## Symptom

Raised naming a creator's parameter and the type it's annotated with, saying the argument couldn't be
resolved while building a given dependency, often rendered as a dependency-chain trace with a
`caused by:` line naming the specific parameter.
```
modern_di.exceptions.resolution.ArgumentResolutionError: Cannot resolve dependency chain:
APP Service (myapp.clock:10)
caused by: Argument clock of type <class 'myapp.clock.Clock'> cannot be resolved. Trying to build dependency <class 'myapp.clock.Service'>.
Did you mean:
- SystemClock (registered subclass, scope=APP)
See: https://modern-di.modern-python.org/troubleshooting/argument-resolution-error/
```

The chain shows the provider being built, and the `caused by:` line names the parameter and its
annotation. The `Did you mean:` block appears only when a registered type looks like the one
requested: a registered subclass, a registered base class, or a type with a similar name. Two other
forms of the `caused by:` line exist:

- `Argument dep of type A | B cannot be resolved.` for a union parameter where no member has a provider.
- `Argument x has no usable type annotation, so it cannot be resolved by type. Pass it via the kwargs parameter or add a type annotation.` for an unannotated parameter.

`container.validate()` reports the same error inside
[`ValidationFailedError`](validation-failed-error.md). The exception carries `.parameter_name`,
`.parameter_type`, `.member_types` (the union members, for a union parameter), `.bound_type`,
`.creator` and `.suggestions`.

## Cause

A creator parameter has no registered provider for its annotated type, no default value, and no
matching `kwargs` entry, so `modern-di` has nothing to inject. This also covers an unannotated
parameter with none of those escape routes. A required `ContextProvider`-backed parameter whose
context value is unset raises [`ContextValueNotSetError`](context-not-set.md) instead.
A creator parameter has nothing to inject: no provider is registered for its annotated type, it has
no default, it is not nullable (`X | None`), and `kwargs` has no entry for it. The usual reasons:

## Fix
- The type has no provider, or its group was not passed to `Container(groups=[...])`.
- The provider exists under another type. A `Factory(SystemClock)` registers under `SystemClock`, so a
parameter typed `Clock` does not find it. This is the case the `registered subclass` hint points at.
- The parameter has no type annotation.
- The parameter is a union. `modern-di` injects the first member, in the order the union lists them,
that has a provider, and raises this error when none has one.

Pick whichever applies: register a provider for the missing type, give the parameter a default, or
pass it explicitly via `kwargs`:
A required parameter backed by a `ContextProvider` whose value is unset raises
[`ContextValueNotSetError`](context-not-set.md) instead.

## Fix

<!-- invisible-code-block: python
from modern_di import Group, Scope, providers
from modern_di import Container, Group, Scope, providers


class Clock: ...
Expand All @@ -33,32 +56,59 @@ class Service:
self.clock = clock
-->

<!-- raises: ArgumentResolutionError -->

```python
# Broken:
class Dependencies(Group):
system_clock = providers.Factory(SystemClock, scope=Scope.APP)
service = providers.Factory(Service, scope=Scope.APP)


Container(groups=[Dependencies]).resolve(Service)
```

Register the provider under the type the parameter asks for:

```python
# Works:
class Dependencies(Group):
# missing: no provider for `Clock` anywhere
service = providers.Factory(Service, scope=Scope.APP) # Service(clock: Clock)
system_clock = providers.Factory(SystemClock, scope=Scope.APP, bound_type=Clock)
service = providers.Factory(Service, scope=Scope.APP)

# fix option 1: register a provider
clock = providers.Factory(SystemClock, scope=Scope.APP, bound_type=Clock)

# fix option 2: pass explicitly
service2 = providers.Factory(Service, scope=Scope.APP, kwargs={"clock": clock})
assert isinstance(Container(groups=[Dependencies]).resolve(Service).clock, SystemClock)
```

If the missing type is one a framework
integration provides at runtime (`fastapi.Request`, `taskiq.TaskiqMessage`, …), its
`ContextProvider` is registered by `setup_di()`, so a `container.validate()` call made
*before* `setup_di()` runs sees no provider for it yet and raises. Call `validate()` after
`setup_di()`, when the provider is registered. A factory that must also resolve outside a
request can take `request: fastapi.Request | None = None`: once `setup_di()` has registered the
provider, the parameter gets the request inside one and `None` outside. See
Or pass the provider explicitly through `kwargs`. With `bound_type=None` the clock is reachable only
by reference:

```python
# Works:
class Dependencies(Group):
system_clock = providers.Factory(SystemClock, scope=Scope.APP, bound_type=None)
service = providers.Factory(Service, scope=Scope.APP, kwargs={"clock": system_clock})


assert isinstance(Container(groups=[Dependencies]).resolve(Service).clock, SystemClock)
```

Giving the parameter a default, or annotating it as `Clock | None`, also stops the error; the creator
then gets the default or `None`.

### Framework types

A type such as `fastapi.Request` or `taskiq.TaskiqMessage` gets its `ContextProvider` from the
integration's `setup_di()`. A `container.validate()` call made before `setup_di()` sees no provider
for it and reports this error, so call `validate()` after `setup_di()`. A factory that must also
resolve outside a request can take `request: fastapi.Request | None = None`: it gets the request
inside one and `None` outside. See
[Framework context objects](../providers/context.md#framework-context-objects) and
[Optional parameters](../providers/context.md#optional-parameters).

Check `.suggestions` on the caught exception for a "did you mean" hint when a similarly-named type is
registered instead.

## See also

- [No provider registered for type](missing-provider.md) describes the direct-resolve form of this same gap.
- [Factories](../providers/factories.md#creator) explains how parameters are parsed and wired.
- [ProviderNotRegisteredError](missing-provider.md): the same gap, hit by a direct `resolve`.
- [Factories: creator](../providers/factories.md#creator): how creator parameters are parsed and wired.
- [Factories: union type parameters](../providers/factories.md#union-type-parameters): which union member is injected.
- [Framework context objects](../providers/context.md#framework-context-objects): the providers integrations register.
Loading
Loading