From 283355d45810dd5d3347a2de8223536038e921a7 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Fri, 17 Jul 2026 18:08:01 +0300 Subject: [PATCH] =?UTF-8?q?docs(release):=202.29.0=20notes=20=E2=80=94=20t?= =?UTF-8?q?he=20compiled=20resolver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover the 2.28.0->2.29.0 backlog: the single-path compiled resolver (#334) and its measured perf, the wiring-plan memoization (#326), the three correctness fixes (#340 ContextProvider-via-kwargs default, #321 NoneType default/nullability, #320 validate traverses kwargs=), and the closed provider set under an explicit breaking-change heading (per planning/decisions/2026-07-17-custom-providers-retracted.md, the release notes carry that warning, not the docs). Co-authored-by: Claude Opus 4.8 (1M context) --- planning/releases/2.29.0.md | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 planning/releases/2.29.0.md diff --git a/planning/releases/2.29.0.md b/planning/releases/2.29.0.md new file mode 100644 index 0000000..9974fb7 --- /dev/null +++ b/planning/releases/2.29.0.md @@ -0,0 +1,95 @@ +# modern-di 2.29.0 — the compiled resolver + +Resolution was rebuilt onto a single-path compiled-closure resolver: measurably +faster on every hot-path shape, with two correctness fixes to how creator +defaults and nullable parameters are wired. **One breaking change** — the +provider-type set is now closed; subclassing `AbstractProvider` (or `Factory`) +to add a provider type is no longer supported. See below. + +## Performance + +- **Single-path compiled resolver (#334).** The interpreted resolve recursion + is replaced by one per-provider compiled closure, memoized on + `ProvidersRegistry` alongside the wiring plan. Each closure front-guards its + own override, navigates to its target scope once (same-scope dependencies skip + via an int compare), and inlines the kwargs build and creator call. The shipped + tree has exactly one resolve path. + + Measured against the prior path (guard-tier medians): transient resolve + **-37%**, warm-cached singleton **-31%**, deep dependency chain **-61%**, wide + fan-out **-62%**; lifecycle -6.5%, `build_child_container` ~flat (no + regression). modern-di stays the only zero-dependency pure-Python framework in + its comparative tier holding its own against the codegen frameworks. + +- **Per-node hot-path tax trimmed (A1–A4).** Redundant per-node framework + overhead removed from the resolve path, feeding the numbers above. + +- **Wiring plans memoized on the registry (#326).** A creator's parameter + partition is computed once and shared tree-wide, so child containers reuse the + parent's plan instead of re-parsing signatures per resolve. + +## Fixes + +- **A `ContextProvider` passed via `kwargs={...}` now honors the creator's + default (#340).** When a context value was unset, a provider wired explicitly + through `kwargs=` bypassed the creator's default and fell through to the direct + `ContextProvider.resolve` path — which returned `None` (with a + `ContextValueNoneWarning`) instead of omitting the parameter so its default + applied. It now routes through the same dependent-parameter path as by-type + wiring: an unset context value with a creator default omits the parameter, a + nullable annotation injects `None`, and a present value is injected. This is + the warning users saw on optional-`Request` creators like + `def choose_engine(*, ..., request: fastapi.Request | None = None)`. + +- **`NoneType` parameters keep their default and nullability (#321).** A + parameter annotated `None`/`NoneType` no longer loses its creator default or + its nullable disposition during signature parsing. + +- **`validate()` traverses providers supplied via `kwargs=` (#320).** Providers + declared through `kwargs={...}` are now part of the graph `validate()` walks, + so cycle and transitive-scope checks cover them like type-wired dependencies. + +## Breaking change + +- **The provider-type set is closed; custom providers are no longer supported.** + `Factory`, `Alias`, `ContextProvider`, and the pre-built `container_provider` + are the only provider types. The compiled resolver (#334) dispatches by exact + type identity, so a subclass of `AbstractProvider` — **or of `Factory`** — + raises `TypeError` at its first resolve. Note the failure mode: `validate()` + walks the dependency graph but compilation is lazy, so a container built with + `validate=True` reports clean and then raises `TypeError` on the first resolve + under traffic. + + Custom provider support was never a designed capability — it was an emergent + property of the old polymorphic `provider.resolve(self)` dispatch, and a + now-removed docs section described that accident. If you relied on it, move the + behavior into a creator function, or use `Alias`, instead of introducing a + provider type. Rationale and the rejected alternatives (a 2.x deprecation ramp, + holding for 3.0) are recorded in + [`planning/decisions/2026-07-17-custom-providers-retracted.md`](https://github.com/modern-python/modern-di/blob/main/planning/decisions/2026-07-17-custom-providers-retracted.md). + +## Docs & internals + +- The "Subclassing `AbstractProvider`" extension point is retracted from the + advanced-API docs, replaced by a note stating the closed set and the failure + mode above (#341, #338). +- Curated core-terms glossary (#329); corrected `architecture/resolution.md` + after the edge-set unification (#322); `CLAUDE.md` message-ownership claim + corrected (#323). +- Internal refactors with no behavior change: exceptions own every rendering + glyph, raise sites carry only facts (#324); `Scope` gets its own algebra + (#325); `CacheItem` owns the singleton get-or-create invariant (#314); the + override short-circuit folds into `OverridesRegistry` (#315); the suggester + owns finding a suggestion, the registry is storage (#327); + `ProvidersRegistry` owns its validation-freshness check (#339). +- 100% line coverage maintained across Python 3.10–3.14; `ruff` and `ty` clean; + docs built under `mkdocs --strict` in CI. + +## Downstream + +- **Existing integrations:** no action needed — none of the sibling + `modern-di-*` adapters subclass a provider type; the breaking change does not + reach them. +- **If you subclass `AbstractProvider` or `Factory`** in your own code: this + release breaks that on first resolve. Replace the custom provider with a + creator function or an `Alias` before upgrading.