|
| 1 | +# modern-di 2.27.0 — shorter registrations, self-resetting overrides, anchored errors |
| 2 | + |
| 3 | +Back-compatible: no behavior flips, no new warnings. Four features from the |
| 4 | +3.0 UX research land together — the registration line gets shorter twice, |
| 5 | +test overrides clean up after themselves, and error messages now point at |
| 6 | +the source line that declared the failing provider. |
| 7 | + |
| 8 | +## Feature |
| 9 | + |
| 10 | +- **Positional subject arguments (API-5).** `Factory`, `ContextProvider`, |
| 11 | + and `Alias` accept their subject as the first positional argument — |
| 12 | + `providers.Factory(UserRepository)`, |
| 13 | + `providers.ContextProvider(HttpRequest)`, `providers.Alias(Concrete)`. |
| 14 | + The keyword spellings (`creator=`, `context_type=`, `source_type=`) |
| 15 | + remain first-class; nothing is deprecated. Docs teach the positional |
| 16 | + form throughout. |
| 17 | +- **Group-level default scope (API-4).** A `Group` subclass may declare its |
| 18 | + members' default scope: |
| 19 | + |
| 20 | + ```python |
| 21 | + class RequestGroup(Group, scope=Scope.REQUEST): |
| 22 | + repo = providers.Factory(UserRepository) # takes REQUEST |
| 23 | + audit = providers.Factory(AuditLog, scope=Scope.APP) # explicit wins |
| 24 | + ``` |
| 25 | + |
| 26 | + Priority: explicit `scope=` > the group's kwarg (inherited via MRO; |
| 27 | + a subclass's kwarg applies to providers declared in its own body) > |
| 28 | + `Scope.APP`. `Alias` never participates (its scope derives from its |
| 29 | + source). A scope-defaulted provider shared by two groups with different |
| 30 | + defaults raises the new `GroupScopeConflictError` at class creation — |
| 31 | + import order never decides a provider's scope. |
| 32 | +- **Context-manager override (INT-4).** `container.override(provider, obj)` |
| 33 | + still applies immediately and now returns an `OverrideHandle`: |
| 34 | + |
| 35 | + ```python |
| 36 | + with container.override(MyGroup.api_client, mock_client) as client: |
| 37 | + ... # resolution returns mock_client |
| 38 | + # prior override state restored here — even on exception |
| 39 | + ``` |
| 40 | + |
| 41 | + Exit restores the snapshot taken at the call (a previously stacked |
| 42 | + override, or none); nested overrides of the same provider unwind in |
| 43 | + order. Imperative `override()`/`reset_override()` callers are unaffected. |
| 44 | +- **Definition-site anchors on error paths (ERR-6).** Breadcrumb lines and |
| 45 | + cycle hops now end with the creator's declaration site: |
| 46 | + |
| 47 | + ``` |
| 48 | + Cannot resolve dependency chain: |
| 49 | + REQUEST UserService (app.services:17) |
| 50 | + APP └─> SessionFactory (app.db:42) |
| 51 | + ``` |
| 52 | + |
| 53 | + Captured lazily on the error path only (zero import/resolution cost), |
| 54 | + memoized, with silent fallback for callables without source. Cycle |
| 55 | + errors expose the parallel `.cycle_locations`; `.cycle_path` stays bare |
| 56 | + type names. |
| 57 | + |
| 58 | +## Downstream |
| 59 | + |
| 60 | +- **Tests asserting exact error messages** will break on the new trailing |
| 61 | + `(module:line)` anchors in breadcrumb and cycle lines. As with 2.24.0 |
| 62 | + and 2.26.0: prefer `pytest.raises(..., match=...)` with a substring — |
| 63 | + anchors are trailing additions, so substring matches survive. |
| 64 | +- `Container.override` now returns an `OverrideHandle` instead of `None` — |
| 65 | + invisible to callers that ignore the return value. |
| 66 | +- New exception: `GroupScopeConflictError` (a `RegistrationError`), with |
| 67 | + its [troubleshooting page](https://modern-di.modern-python.org/troubleshooting/group-scope-conflict-error/). |
| 68 | +- No floor bumps required. |
| 69 | + |
| 70 | +## Internals |
| 71 | + |
| 72 | +- The eager warm-up capability (API-8/INT-3, `init_cache()`-style) is |
| 73 | + explicitly deferred with a revisit trigger recorded in planning. |
| 74 | +- 100% line coverage maintained across Python 3.10–3.14; `ruff` and `ty` |
| 75 | + clean; docs built under `mkdocs --strict` in CI. |
0 commit comments