Skip to content

docs: rework the migration guides and dev pages - #666

Merged
lesnik512 merged 1 commit into
mainfrom
docs/rework-migration
Oct 10, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
docs/rework-migration

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

This reworks the two "from another library" migration guides, the 4.x upgrade guide and the two dev pages, using the same approach as the Quickstart, Introduction and Providers pages. I checked each behavioural claim with a script against this checkout. For 4.0 claims I ran an extracted git archive 4.0.0 copy of modern_di. that-depends claims were read from its source, and dependency-injector claims were run against 4.49.1, the version in benchmarks/comparative.

What was wrong

to-4.x.md

from-that-depends.md

  • The override example called container.override_providers_sync({...}) as a statement. In that-depends it is a @contextmanager, so that call overrides nothing. It now uses with Container.override_providers_sync(...).
  • ContextResource was mapped to an uncached REQUEST Factory, which builds a new instance on every resolve and runs no teardown. It now maps to REQUEST plus cache=CacheSettings(finalizer=...).
  • .bind(T) was mapped to Alias. bind replaces the bindings, so the direct equivalent is Factory(..., bound_type=T). I checked that it resolves by Repository and no longer by PostgresRepository. Alias is now described as the way to answer for extra types.
  • The marker notation Provide[T]() does not exist in that-depends, because Provide[...] takes a provider or a string. That row now uses Provide[Container.x], Provide(), Depends(Container.x) and litestar.di.Provide(Container.x).
  • The List/Dict example registered under bare list. resolve(list[object]) raises ProviderNotRegisteredError, a list[X] consumer parameter raises UnsupportedCreatorParameterError, and a second list factory raises DuplicateProviderTypeError. The example now passes the list provider to its consumer through kwargs and explains all three behaviours. The dependency-injector page had the same problem and gets the same fix.
  • The worked example passed an undefined settings on the that-depends side and dropped it on the modern-di side. Both sides now declare Settings, and create_sa_engine(settings: Settings) wires by type.
  • The scopes bullet implied that that-depends has no scopes. It has ContextScopes and default_scope.
  • Both from-* pages said the integration setup call creates per-request containers and closes the APP container. That is false for Flask, gRPC and Typer (see providers/lifecycle.md). Typer's per-command container also comes from @modern_di_typer.inject, not from its setup call.

from-dependency-injector.md

  • The page said a missing dependency fails "at declaration time (UnsupportedCreatorParameterError)". That error only covers unsupported parameter shapes. Factory(Needs) with an unregistered parameter type declares fine and fails on resolve with ArgumentResolutionError, or in validate().
  • "did you mean" suggestions only appear when a type with a similar name is registered.
  • The cycle cell blamed a "Cython-level deepcopy". dependency-injector 4.49.1 raises RecursionError on the first plain call into the cycle. In modern-di without validate(), the CircularDependencyError is chained to a RecursionError, and the traceback ran to 2,341 lines in my run.
  • Factory.creator is not a public attribute. The cache is always lock-guarded, so "by default" was wrong. The async Resource and Coroutine rows pointed at a section with no async content, and now link to the async-lifespan recipe.
  • I removed the star count and "largest user base" because they are time-sensitive and unverified. I also removed "the in-house template for this page" and the call-time-argument point that appeared three times.

dev/contributing.md

  • The page described a PR template with "why, design, non-goals, verification". The repo has no template, and the org default has Summary, Changes and Checklist.
  • The CI description listed only test-ci and lint-ci. It now lists everything _checks.yml runs.

dev/performance-history.md

What was added

  • to-4.x: new sections "Subclassing a provider raises TypeError", "FinalizerError summarizes its errors" and "DuplicateProviderTypeError names both providers".
  • that-depends: new rows for connect_containers, ContextScopes/default_scope, Provide() and string markers, LazyProvider, BaseContainer.resolve/resolver and override_context_sync. Also a group-level default scope in step 2, a warning about async def creators (see below), a concrete per-request async example, and a fuller description of what validate() checks and what it misses (context values).
  • dependency-injector: new rows for ContextLocalSingleton, Container/DependenciesContainer, Self, Delegate and the Delegated* providers, AbstractSingleton/AbstractCallable/AbstractCoroutine and ExternalDependency. A script lists the Provider subclasses in 4.49.1. Every class it reports is mapped or is a base or helper class covered by its parent row. There is also a note that validate() does not report a missing ContextProvider value.
  • Both from-* pages link every integration page.
  • contributing: a docs section (test-docs, test-docs-integrations, docs-build, and docs/agents/docs-style.md), plus test-race, bench and bench-compare.

Structure and anchors

  • to-4.x now has two H2s, ## Changes in 4.0 (replacing ## Key changes, which had no inbound links) and ## Changes in 4.1. All H3 slugs are unchanged, including the two linked from outside the page (#resolving-on-a-closed-container-raises, #a-missing-context-value-for-a-required-parameter-raises-contextvaluenotseterror).
  • The numbered H2s on both from-* pages are unchanged.
  • No file moved.

Checks

  • just test-docs -q -p no:cacheprovider --no-cov: 152 passed, 21 skipped
  • just docs-build: strict build, no WARNING lines
  • just lint-ci: passes
  • lychee --offline over docs and README.md: 0 errors
  • Sybil does not run the migration pages, so I ran every new or changed modern-di example from a scratch script: the worked example with Settings, bound_type and Alias binding, list via kwargs, the per-request set_context example, multi-group containers, ContextLocalSingleton, Container injection, and the 4.0.0 and current behaviour in the to-4.x 4.1 section.

Library behaviour found (not fixed here)

Factory accepts an async def creator, and resolving it returns an un-awaited coroutine:

from modern_di import Container, Group, providers
class Engine: ...
async def acreate() -> Engine: return Engine()
class G(Group):
    e = providers.Factory(acreate)
print(Container(groups=[G]).resolve_provider(G.e))  # <coroutine object acreate ...>

The only signal is RuntimeWarning: coroutine ... was never awaited at garbage collection. The that-depends page now documents this as current behaviour. Whether to reject or warn at Factory(...) time is a library decision.

Out-of-batch follow-ups

  • docs/introduction/comparison.md:137 says the that-depends guide "maps every provider and concept", and :103 says the dependency-injector guide maps it "provider by provider". Both hold after this PR. They would need softening if the new rows are ever removed.
  • There is no CHANGELOG, and 4.1.0 shipped breaking changes (Alias requires bound_type, provider subclassing raises). The new "Changes in 4.1" section is the only place that records them.

Split to-4.x into 4.0 and 4.1 changes and add the 4.1 breaking changes. Fix the
that-depends and dependency-injector mappings that were wrong (ContextResource,
bind, override_providers_sync, List/Dict, unwired-dependency errors, which
integrations close the APP container), add missing rows, and bring the
contributing page in line with the justfile and CI.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: f13575e Previous: 395e8fb Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 5169839.3022681 iter/sec (stddev: 4.425254602258383e-9) 6139935.138982263 iter/sec (stddev: 1.8605923146541302e-8) 1.19
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 5154900.64031136 iter/sec (stddev: 5.702025651807938e-9) 6205832.315667191 iter/sec (stddev: 1.3805592208773412e-8) 1.20
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 32893.51072521343 iter/sec (stddev: 0.0000361364057830406) 32024.886577255867 iter/sec (stddev: 0.000030238944431934) 0.97
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 26230.058956643566 iter/sec (stddev: 0.00012132664932644568) 24960.133332489408 iter/sec (stddev: 0.00011886986138363566) 0.95
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 665.39170411924 iter/sec (stddev: 0.00004861146509406188) 775.0388204206938 iter/sec (stddev: 0.00004473754687217169) 1.16
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 655.2699858865042 iter/sec (stddev: 0.000026732336225453076) 764.2436132277375 iter/sec (stddev: 0.0001144571715558005) 1.17
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 621.4330792500076 iter/sec (stddev: 0.00006664653470079753) 680.0345534095648 iter/sec (stddev: 0.0000803400475442327) 1.09
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 18234.962044817225 iter/sec (stddev: 0.000006875067808448918) 10419.935559673875 iter/sec (stddev: 0.00001230746171334532) 0.57
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 9673.657550270613 iter/sec (stddev: 0.000014824374514855715) 5634.100392630293 iter/sec (stddev: 0.00003435893765767916) 0.58
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 4895.516221173688 iter/sec (stddev: 0.000027825837771945206) 2968.9836953557647 iter/sec (stddev: 0.000061919224501018) 0.61
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] 13567.69567841955 iter/sec (stddev: 0.000008300568735721143) 12389.480670083722 iter/sec (stddev: 0.000019500905757513246) 0.91
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] 6033.6308550285985 iter/sec (stddev: 0.000013859482592709325) 4843.535831204409 iter/sec (stddev: 0.00003981695650002605) 0.80
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] 2745.805918751064 iter/sec (stddev: 0.00003145825320348441) 1868.0290700812566 iter/sec (stddev: 0.00007765838735564648) 0.68
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[1] 42809.86853034172 iter/sec (stddev: 0.000006065038202597864) 24896.9783783031 iter/sec (stddev: 0.000017881784016521047) 0.58
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[2] 22660.11210345477 iter/sec (stddev: 0.00001162303295389256) 11645.85197075752 iter/sec (stddev: 0.000015482573556939863) 0.51
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[4] 8276.138320722099 iter/sec (stddev: 0.000023757237062621398) 5052.235696391264 iter/sec (stddev: 0.000048475453192008314) 0.61
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 2266176.605977312 iter/sec (stddev: 2.6020883951608016e-8) 2335541.2567186337 iter/sec (stddev: 3.860763352243666e-8) 1.03
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 2414926.0785190356 iter/sec (stddev: 1.1403947233581225e-8) 2505907.6773532387 iter/sec (stddev: 3.905850495221895e-8) 1.04
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 4217.685640241885 iter/sec (stddev: 0.00002724028431485555) 3317.2697581338502 iter/sec (stddev: 0.000052891745120707064) 0.79
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 75136.86162271425 iter/sec (stddev: 0.0000011893100322501417) 83100.58991551855 iter/sec (stddev: 0.0000014527703096612033) 1.11
benchmarks/test_guard_lifecycle.py::test_g7b_request_cycle_sync 535100.2056729948 iter/sec (stddev: 8.408794150494762e-8) 468518.9908558271 iter/sec (stddev: 8.515899938405845e-8) 0.88
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 81535.10581732563 iter/sec (stddev: 0.0000010080793015740608) 78785.45603292529 iter/sec (stddev: 0.0000017671089125152717) 0.97
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers 740.1784440005631 iter/sec (stddev: 0.0011776885015094584) 586.9668417253587 iter/sec (stddev: 0.0013708543619692236) 0.79
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 2921995.930676916 iter/sec (stddev: 2.1795041229018354e-8) 3091773.339921797 iter/sec (stddev: 3.1905368666696455e-8) 1.06
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 4933662.224392176 iter/sec (stddev: 7.3950286904590865e-9) 5585002.481125255 iter/sec (stddev: 1.7326975696337195e-8) 1.13
benchmarks/test_guard_resolve.py::test_g3_deep_chain 1059482.555607536 iter/sec (stddev: 3.4479317273665386e-8) 1053634.7223852123 iter/sec (stddev: 4.376268176758002e-8) 0.99
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 561142.8077300378 iter/sec (stddev: 5.018835021512661e-7) 602475.4027370019 iter/sec (stddev: 3.669391702377282e-7) 1.07
benchmarks/test_guard_resolve.py::test_g5_cross_scope 2721174.883579432 iter/sec (stddev: 1.7480110450337723e-8) 2722743.1964451196 iter/sec (stddev: 2.2608711961763022e-8) 1.00
benchmarks/test_guard_resolve.py::test_g9_context_resolve 2078673.3158664652 iter/sec (stddev: 1.364837651904162e-7) 2095884.4060996436 iter/sec (stddev: 1.4539795764318154e-7) 1.01
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 1069069.7073013578 iter/sec (stddev: 3.366384516111828e-8) 1056924.0255533508 iter/sec (stddev: 4.539064934432262e-8) 0.99
benchmarks/test_guard_resolve.py::test_g18_alias_hop 5092566.563938978 iter/sec (stddev: 5.475000374878718e-9) 5564689.374851157 iter/sec (stddev: 1.595292015837515e-8) 1.09
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 55160.02751374086 iter/sec (stddev: 0.000014435754251702033) 23951.926949647954 iter/sec (stddev: 0.000031960404140896095) 0.43
benchmarks/test_guard_validate.py::test_g11_validate_wide 30499.222869642017 iter/sec (stddev: 0.00002256969011531934) 21724.253571453744 iter/sec (stddev: 0.00003130808206721572) 0.71

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit 12e9f00 into main Oct 10, 2026
12 checks passed
@lesnik512
lesnik512 deleted the docs/rework-migration branch October 10, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant