From bfae6bb6ea017eea778d1434fa6a31446df3a5f6 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Wed, 8 Jul 2026 18:10:30 +0300 Subject: [PATCH] =?UTF-8?q?docs(release):=202.27.0=20notes=20=E2=80=94=20p?= =?UTF-8?q?ositional=20subjects,=20group=20scope,=20CM=20override,=20ancho?= =?UTF-8?q?rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/releases/2.27.0.md | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 planning/releases/2.27.0.md diff --git a/planning/releases/2.27.0.md b/planning/releases/2.27.0.md new file mode 100644 index 0000000..f53b616 --- /dev/null +++ b/planning/releases/2.27.0.md @@ -0,0 +1,75 @@ +# modern-di 2.27.0 — shorter registrations, self-resetting overrides, anchored errors + +Back-compatible: no behavior flips, no new warnings. Four features from the +3.0 UX research land together — the registration line gets shorter twice, +test overrides clean up after themselves, and error messages now point at +the source line that declared the failing provider. + +## Feature + +- **Positional subject arguments (API-5).** `Factory`, `ContextProvider`, + and `Alias` accept their subject as the first positional argument — + `providers.Factory(UserRepository)`, + `providers.ContextProvider(HttpRequest)`, `providers.Alias(Concrete)`. + The keyword spellings (`creator=`, `context_type=`, `source_type=`) + remain first-class; nothing is deprecated. Docs teach the positional + form throughout. +- **Group-level default scope (API-4).** A `Group` subclass may declare its + members' default scope: + + ```python + class RequestGroup(Group, scope=Scope.REQUEST): + repo = providers.Factory(UserRepository) # takes REQUEST + audit = providers.Factory(AuditLog, scope=Scope.APP) # explicit wins + ``` + + Priority: explicit `scope=` > the group's kwarg (inherited via MRO; + a subclass's kwarg applies to providers declared in its own body) > + `Scope.APP`. `Alias` never participates (its scope derives from its + source). A scope-defaulted provider shared by two groups with different + defaults raises the new `GroupScopeConflictError` at class creation — + import order never decides a provider's scope. +- **Context-manager override (INT-4).** `container.override(provider, obj)` + still applies immediately and now returns an `OverrideHandle`: + + ```python + with container.override(MyGroup.api_client, mock_client) as client: + ... # resolution returns mock_client + # prior override state restored here — even on exception + ``` + + Exit restores the snapshot taken at the call (a previously stacked + override, or none); nested overrides of the same provider unwind in + order. Imperative `override()`/`reset_override()` callers are unaffected. +- **Definition-site anchors on error paths (ERR-6).** Breadcrumb lines and + cycle hops now end with the creator's declaration site: + + ``` + Cannot resolve dependency chain: + REQUEST UserService (app.services:17) + APP └─> SessionFactory (app.db:42) + ``` + + Captured lazily on the error path only (zero import/resolution cost), + memoized, with silent fallback for callables without source. Cycle + errors expose the parallel `.cycle_locations`; `.cycle_path` stays bare + type names. + +## Downstream + +- **Tests asserting exact error messages** will break on the new trailing + `(module:line)` anchors in breadcrumb and cycle lines. As with 2.24.0 + and 2.26.0: prefer `pytest.raises(..., match=...)` with a substring — + anchors are trailing additions, so substring matches survive. +- `Container.override` now returns an `OverrideHandle` instead of `None` — + invisible to callers that ignore the return value. +- New exception: `GroupScopeConflictError` (a `RegistrationError`), with + its [troubleshooting page](https://modern-di.modern-python.org/troubleshooting/group-scope-conflict-error/). +- No floor bumps required. + +## Internals + +- The eager warm-up capability (API-8/INT-3, `init_cache()`-style) is + explicitly deferred with a revisit trigger recorded in planning. +- 100% line coverage maintained across Python 3.10–3.14; `ruff` and `ty` + clean; docs built under `mkdocs --strict` in CI.