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
37 changes: 37 additions & 0 deletions docs/migration/to-4.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,43 @@ parameters from the `__new__` annotations.
A `Factory` whose creator returns a union of several types (`-> A | B`) still gets no bound type,
and now emits a `UserWarning` saying so. Pass `bound_type=` explicitly to silence it.

### `Container` registries are private

`providers_registry`, `cache_registry`, `context_registry` and `overrides_registry` are no longer
public attributes of `Container`. To look up the provider registered for a type, call
`container.find_provider(SomeType)`, which returns the provider or `None`:

```python
# 3.x
provider = container.providers_registry.find_provider(SomeType)

# 4.0
provider = container.find_provider(SomeType)
```

Register providers with `groups=` on the root or with `add_providers()`, and manage overrides with
`override()` and `reset_override()`. See
[Container: looking up a provider](../providers/container.md#looking-up-a-provider).

### `Container.closed` is read-only

`container.closed` still reports whether the container is closed, but assigning to it raises
`AttributeError`. Close a container with `close_sync()`, `close_async()` or by leaving `with` /
`async with`, and reopen it with `open()`.

### A child container rejects `groups=`

`Container(scope=..., parent_container=parent, groups=[...])` raises
`ChildContainerRegistrationError`, the same error `add_providers()` raises on a child. In 3.x the
groups were registered into the registry the whole tree shares. Pass the groups to the root
container instead. The error message now covers both cases, so update any test that matched the
old `Container.add_providers can only be called on a root container` text.

### A closed container raises before the provider lookup

`resolve()` on a closed container raises `ContainerClosedError` even when the type is not
registered. In 3.x that call raised `ProviderNotRegisteredError`.

### The 3.x deprecations are removed

- `Container(validate=...)` raises `TypeError`, and `ValidateArgumentWarning` is gone with it. Drop
Expand Down
18 changes: 15 additions & 3 deletions docs/providers/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,23 @@ context/cache), while app-scoped code reaches the app container.
## Registering providers after construction

`container.add_providers(*providers)` registers additional providers on a **root** container after
it's built. It is the blessed seam framework integrations use instead of reaching into
`providers_registry` directly. Raises `ChildContainerRegistrationError` if called on a child
container. See [Writing an integration](../integrations/writing-integrations.md#the-contract) for
it's built. Framework integrations use it to register their connection providers. Raises
`ChildContainerRegistrationError` if called on a child container, and so does passing `groups=`
together with `parent_container=`. See [Writing an integration](../integrations/writing-integrations.md#the-contract) for
the full contract.

## Looking up a provider

`container.find_provider(SomeType)` returns the provider registered for `SomeType`, or `None` when
nothing is. Every container in a tree sees the same providers, so a child answers the same as its
root. The lookup ignores overrides and the closed state, and it never resolves anything:

```python
provider = request_container.find_provider(UserRepository)
if provider is not None:
repository = request_container.resolve_provider(provider)
```

## Resolving a provider or type

`container.resolve_dependency(dep)` accepts either a provider reference or a type and dispatches to
Expand Down
3 changes: 2 additions & 1 deletion docs/providers/errors-and-exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ declared or registered, or by `validate()`, which reports `InvalidScopeDependenc
(within one group, across groups passed together, or against an already-registered type). See
[Troubleshooting: Duplicate type](../troubleshooting/duplicate-type-error.md).
- `ChildContainerRegistrationError` is raised by `Container.add_providers()` when called on a child
container; registration is root-only because the providers registry is shared tree-wide, so
container, and by `Container(...)` when `groups=` comes with `parent_container=`. Registration is
root-only because the providers registry is shared tree-wide, so
registering from a child would mutate every container in the tree. Call `add_providers` on the root
container instead. Inspect `.scope` for the offending child container's scope. See
[Container: registering after construction](container.md#registering-providers-after-construction) and
Expand Down
4 changes: 2 additions & 2 deletions docs/providers/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ finalizer; the sync path is only a safety net.

## Closing and reopening

A constructed container is **open from construction**: `closed = False` the moment `Container(...)`
returns, with no `open()` step required before the first `resolve()` / `resolve_provider()` call.
A constructed container is **open from construction**: `container.closed` is `False` the moment
`Container(...)` returns, with no `open()` step required before the first `resolve()` / `resolve_provider()` call.
`build_child_container()` never checks or touches any container's open/closed state (it only reads
the parent's shared registries and scope map), and the returned child starts open too, same as any
fresh container. `close_sync()` / `close_async()` run the finalizers (in reverse-creation order, as
Expand Down
2 changes: 1 addition & 1 deletion docs/providers/scopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ with app_container.build_child_container(scope=Scope.REQUEST) as request_contain

`Dependencies` here is a `Group` subclass holding the provider definitions. See the [Quick Start](../index.md) or [Resolving dependencies](../introduction/resolving.md) for how it's declared.

Children share their parent's `providers_registry` (provider definitions) and `overrides_registry` (test overrides) but have their own `cache_registry` (resolved instances) and `context_registry` (runtime context values). That's why a REQUEST-scoped factory produces one instance per request: the cache lives on the request container, not the app container.
Children share their parent's provider definitions and test overrides, and each one keeps its own resolved instances and runtime context values. That's why a REQUEST-scoped factory produces one instance per request: the cache lives on the request container, not the app container.

## The scope dependency rule

Expand Down
15 changes: 12 additions & 3 deletions docs/troubleshooting/child-container-registration-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

## Symptom

Raised from `Container.add_providers()`, naming the scope of the child container it was called on.
Raised from `Container.add_providers()` on a child container, or from `Container(...)` when
`groups=` is passed together with `parent_container=`. It names the child container's scope.

## Cause

`add_providers()` was called on a child container rather than the root. The providers registry is
Providers were registered on a child container rather than the root. The providers registry is
shared tree-wide (every container in the chain points at the same registry), so registering from a
child would silently mutate every container in the tree, so the call is disallowed.

## Fix

Call `add_providers()` on the root container instead:
Register on the root container instead, either with `groups=` when you build it or with
`add_providers()` later:

```python
app_container = Container(scope=Scope.APP, groups=[MyGroup])
Expand All @@ -23,6 +25,13 @@ request_container.add_providers(late_provider) # raises ChildContainerRegistrat

# Works
app_container.add_providers(late_provider)

# Wrong
Container(scope=Scope.REQUEST, parent_container=app_container, groups=[RequestGroup])

# Right
app_container = Container(scope=Scope.APP, groups=[MyGroup, RequestGroup])
request_container = app_container.build_child_container(scope=Scope.REQUEST)
```

If you only have a reference to the child container at the call site, keep a reference to the root
Expand Down
Loading
Loading