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
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ passes code that CI rejects.

## Architecture

- **Scope** — `IntEnum`, `APP=1 → SESSION=2 → REQUEST=3 → ACTION=4 → STEP=5`. A provider resolves only
- **Scope**: `IntEnum`, `APP=1 → SESSION=2 → REQUEST=3 → ACTION=4 → STEP=5`. A provider resolves only
from a container of the same or deeper (higher-int) scope; otherwise a clear error is raised.
- **Container** — the central object. A child (`build_child_container`) shares the parent's
- **Container**: the central object. A child (`build_child_container`) shares the parent's
providers/overrides registries; cache and context are per-container. `container.validate()` (cycle +
transitive-scope checks) is the only thing that validates.

Expand All @@ -45,15 +45,15 @@ not tell you:
formatting; the class renders its own f-string and sets a `docs_slug` (its page under
`docs/troubleshooting/`, enforced by `tests/test_docs_slug_census.py`). Add a message, a glyph, or a
class here, never at the raise site. Submodules split by family; `__init__` re-exports every public name.
- `registries/` — `providers_registry` (type → provider, plus the shared plan/resolver memos) and
- `registries/`: `providers_registry` (type → provider, plus the shared plan/resolver memos) and
`overrides_registry` are shared tree-wide.
- `cache.py` — `CacheItem` and the functions over a container's own cache items and creation order
- `cache.py`: `CacheItem` and the functions over a container's own cache items and creation order
(fetch, count, close). Cache and context are per-container and live in the container's slots; the
context is a plain dict.
- `dependency_graph.py` walks `WiringPlan.provider_kwargs`, so what `validate()` traverses is exactly what
`resolve()` follows. Explicit-stack, never recursive: a caller runs it inside a `RecursionError`
handler near CPython's stack limit.
- `types.py` — `UNSET` is load-bearing on the resolve path: the miss marker for both the override
- `types.py`: `UNSET` is load-bearing on the resolve path: the miss marker for both the override
lookup and the cache slot, separating "not passed" from "explicitly `None`".

### Testing patterns
Expand All @@ -73,7 +73,7 @@ description, and PyPI does not rewrite relative links, so a relative one 404s on
- Design principle: conservative feature set; resolution is sync-only (async resolution was removed
in 2.x), though finalizers may still be sync or async (`close_sync`/`close_async`); no global state
- Docstrings: public API documents the contract; internal helpers get a one-line contract, plus at most
1–2 lines for a genuinely non-obvious constraint. Never narrate implementation or justify code to a
one or two lines for a genuinely non-obvious constraint. Never narrate implementation or justify code to a
reviewer; cross-file rationale lives in an invariant test's docstring
- `ruff` (`select = ["ALL"]`) and `ty` are configured in `pyproject.toml` and run by `just lint`

Expand All @@ -96,7 +96,7 @@ Single-context: `CONTEXT.md` and `docs/adr/` at the repo root. See `docs/agents/
Docs pages, PR and issue bodies, and commit messages run through the
[humanizer](https://github.com/blader/humanizer) skill when it is installed
(`npx skills add blader/humanizer`). Repo conventions it does not cover (heading and nav
casing, the em-dash carve-outs, contrast markers in code samples) are in
casing, colons in place of dashes in gloss bullets, contrast markers in code samples) are in
`docs/agents/docs-style.md`.

### Cutting a release
Expand Down
13 changes: 7 additions & 6 deletions docs/agents/docs-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ Every drift found so far came from a pass that recased headings and stopped ther

## Em dashes

Mid-sentence em dashes go, per the humanizer skill's §8. Four shapes keep theirs, because the dash
separates a term from its gloss instead of joining two clauses:
No em or en dashes outside code: not mid-sentence (per the humanizer skill's §8), not in See-also
lists, not in tables. A bullet that pairs a term with its gloss uses a colon:

- `## See also` bullets — `- [Scopes](scopes.md) — the APP → REQUEST lifetime model.`
- Table cells.
- Term-gloss bullets, the same shape outside a See-also list.
- Comments inside fenced code blocks.
- `## See also` bullets: `- [Scopes](scopes.md): the APP → REQUEST lifetime model.`
- Term-gloss bullets: `- **Scope**: the lifetime level a provider belongs to.`

When a long link pushes the gloss onto the next line, the colon ends the link line. Dashes inside
fenced code blocks and inline code are left alone.

## Contrast markers in code samples

Expand Down
8 changes: 4 additions & 4 deletions docs/introduction/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ a ratio would compare different work.

## See also

- [Comparison](comparison.md) — how modern-di compares on features.
- [Design decisions](design-decisions.md) — why resolution is sync-only.
- [Performance history](../dev/performance-history.md) — each optimization and what it measured.
- [`benchmarks/README.md`](https://github.com/modern-python/modern-di/blob/main/benchmarks/README.md) — method, per-framework code and the guard tier.
- [Comparison](comparison.md): how modern-di compares on features.
- [Design decisions](design-decisions.md): why resolution is sync-only.
- [Performance history](../dev/performance-history.md): each optimization and what it measured.
- [`benchmarks/README.md`](https://github.com/modern-python/modern-di/blob/main/benchmarks/README.md): method, per-framework code and the guard tier.
6 changes: 3 additions & 3 deletions docs/providers/advanced-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ this gives callers is in [Cached factories](factories.md#cached-factories).

## See also

- [Container](container.md) — building containers and a map of their methods.
- [Writing an integration](../integrations/writing-integrations.md) — the contract built on
- [Container](container.md): building containers and a map of their methods.
- [Writing an integration](../integrations/writing-integrations.md): the contract built on
`modern_di.integrations`.
- [Design decisions](../introduction/design-decisions.md) — why the public surface is small and
- [Design decisions](../introduction/design-decisions.md): why the public surface is small and
`Container` subclassing is limited.
8 changes: 4 additions & 4 deletions docs/providers/alias.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ cache_container.validate() # .exceptions holds an AliasSourceNotRegisteredError

## See also

- [Factories: `bound_type`](factories.md#bound_type) — how a provider's registered type is chosen.
- [AliasSourceNotRegisteredError](../troubleshooting/alias-source-not-registered-error.md) — the
- [Factories: `bound_type`](factories.md#bound_type): how a provider's registered type is chosen.
- [AliasSourceNotRegisteredError](../troubleshooting/alias-source-not-registered-error.md): the
source type has no provider.
- [AliasBoundToSourceError](../troubleshooting/alias-bound-to-source-error.md) — an alias bound to
- [AliasBoundToSourceError](../troubleshooting/alias-bound-to-source-error.md): an alias bound to
its own source type.
- [Testing with overrides](../recipes/testing-overrides.md) — overriding providers in tests.
- [Testing with overrides](../recipes/testing-overrides.md): overriding providers in tests.
6 changes: 3 additions & 3 deletions docs/providers/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ if provider is not None:

## See also

- [Context providers](context.md#context-propagation) — how context values reach (and don't
- [Context providers](context.md#context-propagation): how context values reach (and don't
reach) a `ContextProvider`.
- [Scopes](scopes.md) — the scope model and child containers.
- [Advanced / low-level API](advanced-api.md) — the public modules, `find_container`,
- [Scopes](scopes.md): the scope model and child containers.
- [Advanced / low-level API](advanced-api.md): the public modules, `find_container`,
`parent_container` and `Group.get_providers()`.
10 changes: 5 additions & 5 deletions docs/providers/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,11 @@ Each integration's page lists its provider names, scopes and types:

## See also

- [Factories](factories.md) — how factories receive injected context values.
- [Scopes](scopes.md) — choosing the scope a `ContextProvider` is bound to.
- [Container](container.md) — building containers with `context=`, and where `set_context` and
- [Factories](factories.md): how factories receive injected context values.
- [Scopes](scopes.md): choosing the scope a `ContextProvider` is bound to.
- [Container](container.md): building containers with `context=`, and where `set_context` and
`build_child_container` are covered.
- [ContextProvider has no value](../troubleshooting/context-not-set.md) — fixing
- [ContextProvider has no value](../troubleshooting/context-not-set.md): fixing
`ContextValueNotSetError`.
- [Writing an integration](../integrations/writing-integrations.md) — how an integration registers
- [Writing an integration](../integrations/writing-integrations.md): how an integration registers
its providers and seeds the context.
6 changes: 3 additions & 3 deletions docs/troubleshooting/async-finalizer-in-sync-close-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ Use `close_sync()` only for containers you know hold no async finalizer.

## See also

- [Lifecycle: async finalizers and `close_sync()`](../providers/lifecycle.md#async-finalizers-and-close_sync)
— how `close_sync()` treats an async finalizer.
- [FinalizerError](finalizer-error.md) — the group this error arrives in.
- [Lifecycle: async finalizers and `close_sync()`](../providers/lifecycle.md#async-finalizers-and-close_sync):
how `close_sync()` treats an async finalizer.
- [FinalizerError](finalizer-error.md): the group this error arrives in.
4 changes: 2 additions & 2 deletions docs/troubleshooting/container-closed-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ the container from a finalizer. Give the cached instance what its finalizer need

## See also

- [Lifecycle: closing and reopening](../providers/lifecycle.md#closing-and-reopening) — what close
- [Lifecycle: closing and reopening](../providers/lifecycle.md#closing-and-reopening): what close
runs and what survives a reopen.
- [Migration: To 4.x](../migration/to-4.x.md#resolving-on-a-closed-container-raises) — the 3.x
- [Migration: To 4.x](../migration/to-4.x.md#resolving-on-a-closed-container-raises): the 3.x
warn-and-reopen behavior this replaced.
4 changes: 2 additions & 2 deletions docs/troubleshooting/finalizer-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ instance cached, and a later `await container.close_async()` runs its finalizer.

## See also

- [Lifecycle: close-failure semantics](../providers/lifecycle.md#close-failure-semantics) — what
- [Lifecycle: close-failure semantics](../providers/lifecycle.md#close-failure-semantics): what
closing does when a finalizer fails or is cancelled.
- [AsyncFinalizerInSyncCloseError](async-finalizer-in-sync-close-error.md) — an async finalizer
- [AsyncFinalizerInSyncCloseError](async-finalizer-in-sync-close-error.md): an async finalizer
reached by `close_sync()`.
4 changes: 2 additions & 2 deletions docs/troubleshooting/invalid-child-scope-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ substep_container = step_container.build_child_container(scope=MyScope.SUBSTEP)

## See also

- [Scopes: custom scopes](../providers/scopes.md#custom-scopes) — how members of different enums mix
- [Scopes: custom scopes](../providers/scopes.md#custom-scopes): how members of different enums mix
in one container tree.
- [MaxScopeReachedError](max-scope-reached-error.md) — the error from `build_child_container()`
- [MaxScopeReachedError](max-scope-reached-error.md): the error from `build_child_container()`
without `scope=` at the last member of an enum.
2 changes: 1 addition & 1 deletion docs/troubleshooting/invalid-scope-type-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ way the hierarchy should nest.

## See also

- [Scopes: custom scopes](../providers/scopes.md#custom-scopes) — using your own `IntEnum` as a
- [Scopes: custom scopes](../providers/scopes.md#custom-scopes): using your own `IntEnum` as a
scope.
4 changes: 2 additions & 2 deletions docs/troubleshooting/max-scope-reached-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ their values match, and resolving raises [`ScopeSkippedError`](scope-skipped-err

## See also

- [Scopes: custom scopes](../providers/scopes.md#custom-scopes) — extending the hierarchy with your
- [Scopes: custom scopes](../providers/scopes.md#custom-scopes): extending the hierarchy with your
own `IntEnum`.
- [InvalidChildScopeError](invalid-child-scope-error.md) — the error for an explicit `scope=` that is
- [InvalidChildScopeError](invalid-child-scope-error.md): the error for an explicit `scope=` that is
not deeper than the parent's.
8 changes: 4 additions & 4 deletions docs/troubleshooting/scope-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ scope or a shallower one.

## See also

- [Scopes: how to choose a scope](../providers/scopes.md#how-to-choose-a-scope) — the shallowest
- [Scopes: how to choose a scope](../providers/scopes.md#how-to-choose-a-scope): the shallowest
scope a provider can have, given its dependencies.
- [Captive dependency](../recipes/good-and-bad-practices.md#1-captive-dependency-a-wide-scoped-provider-holding-a-narrow-scoped-one)
— the mistake this error reports, and its fix.
- [Lifecycle: validation](../providers/lifecycle.md#validation) — when to call `validate()` and what
- [Captive dependency](../recipes/good-and-bad-practices.md#1-captive-dependency-a-wide-scoped-provider-holding-a-narrow-scoped-one):
the mistake this error reports, and its fix.
- [Lifecycle: validation](../providers/lifecycle.md#validation): when to call `validate()` and what
it checks.
4 changes: 2 additions & 2 deletions docs/troubleshooting/scope-enum-mismatch-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ Container(groups=[Dependencies]).validate()

## See also

- [Scopes: custom scopes](../providers/scopes.md#custom-scopes) — how scopes from different enums
- [Scopes: custom scopes](../providers/scopes.md#custom-scopes): how scopes from different enums
mix in one tree.
- [InvalidScopeDependencyError](scope-chain.md) — a dependency on a deeper scope.
- [InvalidScopeDependencyError](scope-chain.md): a dependency on a deeper scope.
8 changes: 4 additions & 4 deletions docs/troubleshooting/scope-not-initialized-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ every captive dependency at startup and reports it as

## See also

- [InvalidScopeDependencyError](scope-chain.md) — the same problem reported by `validate()`.
- [Scopes: the scope dependency rule](../providers/scopes.md#the-scope-dependency-rule) — why a
- [InvalidScopeDependencyError](scope-chain.md): the same problem reported by `validate()`.
- [Scopes: the scope dependency rule](../providers/scopes.md#the-scope-dependency-rule): why a
provider cannot depend on a deeper scope.
- [Captive dependency](../recipes/good-and-bad-practices.md#1-captive-dependency-a-wide-scoped-provider-holding-a-narrow-scoped-one)
— the bug this error usually points at.
- [Captive dependency](../recipes/good-and-bad-practices.md#1-captive-dependency-a-wide-scoped-provider-holding-a-narrow-scoped-one):
the bug this error usually points at.
4 changes: 2 additions & 2 deletions docs/troubleshooting/scope-skipped-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ message, and give your providers those scopes.

## See also

- [InvalidScopeDependencyError](scope-chain.md) — a provider that depends on a deeper scope, found by
- [InvalidScopeDependencyError](scope-chain.md): a provider that depends on a deeper scope, found by
`validate()`.
- [Scopes: building child containers](../providers/scopes.md#building-child-containers) — how a
- [Scopes: building child containers](../providers/scopes.md#building-child-containers): how a
container chain maps to the scope hierarchy.
12 changes: 6 additions & 6 deletions docs/troubleshooting/validation-failed-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ line and the `See:` line.
The provider graph has one or more problems. `validate()` walks the whole graph in one pass and
collects every issue, so `.exceptions` may hold several kinds at once. Each entry is one of these:

- [`CircularDependencyError`](circular-dependency.md) — a cycle in the graph.
- [`InvalidScopeDependencyError`](scope-chain.md) — a provider that depends on a deeper-scoped one.
- [`ScopeEnumMismatchError`](scope-enum-mismatch-error.md) — a dependency at a same-valued scope of
- [`CircularDependencyError`](circular-dependency.md): a cycle in the graph.
- [`InvalidScopeDependencyError`](scope-chain.md): a provider that depends on a deeper-scoped one.
- [`ScopeEnumMismatchError`](scope-enum-mismatch-error.md): a dependency at a same-valued scope of
another enum.
- [`ArgumentResolutionError`](argument-resolution-error.md) — a creator parameter that nothing can
- [`ArgumentResolutionError`](argument-resolution-error.md): a creator parameter that nothing can
resolve.
- [`AliasSourceNotRegisteredError`](alias-source-not-registered-error.md) — an alias whose source
- [`AliasSourceNotRegisteredError`](alias-source-not-registered-error.md): an alias whose source
type has no provider.

## Fix
Expand Down Expand Up @@ -91,5 +91,5 @@ Nothing validates the graph for you, not even construction, `open()` or `resolve

## See also

- [Lifecycle: validation](../providers/lifecycle.md#validation) — when to call `validate()` and what
- [Lifecycle: validation](../providers/lifecycle.md#validation): when to call `validate()` and what
it checks.
Loading