Repository navigation
docs: rework the web integrations pages - #665
Merged
Merged
Conversation
Fix the examples that could not serve a request (FastAPI and Starlette websockets/endpoints without setup_di, the unregistered Litestar autowired route), add test-client checks to every page, document root-container lifecycle, websocket per-message scope, and the current Litestar di_container and Flask async-view limits.
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: 7a40ffc | Previous: 395e8fb | Ratio |
|---|---|---|---|
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type |
3743340.410268023 iter/sec (stddev: 1.945965266562093e-8) |
6139935.138982263 iter/sec (stddev: 1.8605923146541302e-8) |
1.64 |
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry |
3777787.0879038386 iter/sec (stddev: 2.129084541324819e-8) |
6205832.315667191 iter/sec (stddev: 1.3805592208773412e-8) |
1.64 |
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve |
21650.433509002833 iter/sec (stddev: 0.000050224903225197576) |
32024.886577255867 iter/sec (stddev: 0.000030238944431934) |
1.48 |
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached |
17213.872956368352 iter/sec (stddev: 0.00017365589749788804) |
24960.133332489408 iter/sec (stddev: 0.00011886986138363566) |
1.45 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] |
404.4407682868168 iter/sec (stddev: 0.00008288090366681325) |
775.0388204206938 iter/sec (stddev: 0.00004473754687217169) |
1.92 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] |
388.23115413237906 iter/sec (stddev: 0.00007418928348295908) |
764.2436132277375 iter/sec (stddev: 0.0001144571715558005) |
1.97 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] |
389.72389923006614 iter/sec (stddev: 0.00009724674879440644) |
680.0345534095648 iter/sec (stddev: 0.0000803400475442327) |
1.74 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] |
12807.588923195353 iter/sec (stddev: 0.00000944576406838672) |
10419.935559673875 iter/sec (stddev: 0.00001230746171334532) |
0.81 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] |
6256.8059057480905 iter/sec (stddev: 0.00002453665420051088) |
5634.100392630293 iter/sec (stddev: 0.00003435893765767916) |
0.90 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] |
3353.087957878677 iter/sec (stddev: 0.00004019848984021132) |
2968.9836953557647 iter/sec (stddev: 0.000061919224501018) |
0.89 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] |
9303.994530572103 iter/sec (stddev: 0.000010297066703511448) |
12389.480670083722 iter/sec (stddev: 0.000019500905757513246) |
1.33 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] |
3969.031499435977 iter/sec (stddev: 0.000024729970659738838) |
4843.535831204409 iter/sec (stddev: 0.00003981695650002605) |
1.22 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] |
1865.5908053284675 iter/sec (stddev: 0.000052658941734794635) |
1868.0290700812566 iter/sec (stddev: 0.00007765838735564648) |
1.00 |
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[1] |
36820.39899164147 iter/sec (stddev: 0.000007532203062333242) |
24896.9783783031 iter/sec (stddev: 0.000017881784016521047) |
0.68 |
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[2] |
14852.950222477191 iter/sec (stddev: 0.000014234321491713152) |
11645.85197075752 iter/sec (stddev: 0.000015482573556939863) |
0.78 |
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[4] |
5520.318544446068 iter/sec (stddev: 0.0000606538996928915) |
5052.235696391264 iter/sec (stddev: 0.000048475453192008314) |
0.92 |
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container |
1573171.758111508 iter/sec (stddev: 3.187681883859335e-8) |
2335541.2567186337 iter/sec (stddev: 3.860763352243666e-8) |
1.48 |
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope |
1677536.6850031943 iter/sec (stddev: 3.020559253939622e-8) |
2505907.6773532387 iter/sec (stddev: 3.905850495221895e-8) |
1.49 |
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch |
2999.3841663281455 iter/sec (stddev: 0.000010003406613499351) |
3317.2697581338502 iter/sec (stddev: 0.000052891745120707064) |
1.11 |
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control |
53276.610793483305 iter/sec (stddev: 0.0000024117576348446) |
83100.58991551855 iter/sec (stddev: 0.0000014527703096612033) |
1.56 |
benchmarks/test_guard_lifecycle.py::test_g7b_request_cycle_sync |
376293.43105884764 iter/sec (stddev: 8.883184373131304e-8) |
468518.9908558271 iter/sec (stddev: 8.515899938405845e-8) |
1.25 |
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale |
57026.50497532944 iter/sec (stddev: 0.0000017883777792193462) |
78785.45603292529 iter/sec (stddev: 0.0000017671089125152717) |
1.38 |
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers |
516.0632813972103 iter/sec (stddev: 0.001598336720073683) |
586.9668417253587 iter/sec (stddev: 0.0013708543619692236) |
1.14 |
benchmarks/test_guard_resolve.py::test_g1_transient_resolve |
2081404.240193595 iter/sec (stddev: 3.107292974359679e-8) |
3091773.339921797 iter/sec (stddev: 3.1905368666696455e-8) |
1.49 |
benchmarks/test_guard_resolve.py::test_g2_cached_resolve |
3675588.3119108104 iter/sec (stddev: 2.1239145246793365e-8) |
5585002.481125255 iter/sec (stddev: 1.7326975696337195e-8) |
1.52 |
benchmarks/test_guard_resolve.py::test_g3_deep_chain |
660777.1925859635 iter/sec (stddev: 5.367576369913599e-8) |
1053634.7223852123 iter/sec (stddev: 4.376268176758002e-8) |
1.59 |
benchmarks/test_guard_resolve.py::test_g4_wide_resolve |
419183.5670648758 iter/sec (stddev: 4.159435992373088e-7) |
602475.4027370019 iter/sec (stddev: 3.669391702377282e-7) |
1.44 |
benchmarks/test_guard_resolve.py::test_g5_cross_scope |
1990870.1680244368 iter/sec (stddev: 3.0860899831538185e-8) |
2722743.1964451196 iter/sec (stddev: 2.2608711961763022e-8) |
1.37 |
benchmarks/test_guard_resolve.py::test_g9_context_resolve |
1372041.1760618126 iter/sec (stddev: 1.740983688619453e-7) |
2095884.4060996436 iter/sec (stddev: 1.4539795764318154e-7) |
1.53 |
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve |
699092.7070003414 iter/sec (stddev: 6.297758575965574e-8) |
1056924.0255533508 iter/sec (stddev: 4.539064934432262e-8) |
1.51 |
benchmarks/test_guard_resolve.py::test_g18_alias_hop |
3699818.893865855 iter/sec (stddev: 2.4412835385069577e-8) |
5564689.374851157 iter/sec (stddev: 1.595292015837515e-8) |
1.50 |
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain |
29756.607318290702 iter/sec (stddev: 0.0002981952761153157) |
23951.926949647954 iter/sec (stddev: 0.000031960404140896095) |
0.80 |
benchmarks/test_guard_validate.py::test_g11_validate_wide |
20555.335270865344 iter/sec (stddev: 0.00003015358771617824) |
21724.253571453744 iter/sec (stddev: 0.00003130808206721572) |
1.06 |
This comment was automatically generated by workflow using github-action-benchmark.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reworks the five web framework pages (FastAPI, Starlette, Litestar, aiohttp, Flask) the same way the Quickstart, Introduction and Providers pages were done. Every behavioural claim was checked by running a script against modern-di at this branch and each integration's 4.0.0 source (fastapi 0.143.0, starlette 1.7.0, litestar 2.24.0, aiohttp 3.14.4, flask 3.1.3).
What was wrong
FastAPI()with nosetup_di, so the first connection raisedRuntimeError: No modern-di container found on the app.... Sybil passed it only because no request was ever sent.app = Starlette(...)withoutsetup_di, so every request raisedRuntimeError.list_userswas decorated after the app was built and never registered (GET /users returned 404), and its body returnedNonefor-> list[str].closedis False at construction, and a mounted sub-app or a no-lifespan run leaves it open with finalizers never run.lifespan=stays the outer context and its yielded state reachesrequest.state(checked).setup_diraisesValidationFailedErroron every framework (checked); the comment became one sentence of prose.connectionparameter that was the same object asrequest(conn is requestis True) and never usedrequest_container.What was added
Depends(scope=...)exists from 0.121.0.FromDIdependency caching anduse_cache=False(two identicalFromDIgive one instance,use_cache=Falsegives another), andFromDI(Container)/build_di_containerreturning the same child.FromDIof a REQUEST-scoped provider in a websocket handler raisingScopeNotInitializedError(checked on all four).Request/WebSocketis the middleware's own wrapper. It is a different object from the handler's but sharesrequest.state.di_containerdependency section and API row; the lifespan-only root close; a warning not to name an autowired providerdi_container(library bug below).web.Requestby type cannot resolve in a websocket handler (ScopeNotInitializedErrorfrom the SESSION container,ContextValueNotSetErrorfrom a REQUEST child). It also shows the SESSION-scopedkwargs={"request": aiohttp_websocket_provider}wiring. Also covered: the consequence of upgrade headers on an HTTP-only handler (SESSION child, REQUEST provider fails, client sees 500), andadd_subapppassing startup/cleanup to a sub-app (root closed, finalizer ran).auto_injectmisses routes added aftersetup_di(TypeError: get_late_report() missing 1 required positional argument: 'report'). Finalizers run underclose_sync(), so an async or failing finalizer raisesFinalizerErrorout of teardown, which gives a 500 under the Werkzeug server. The root teardown example usesatexit.register(fetch_di_container(app).close_sync).Structure
All five pages now share one layout: intro, Installation, Usage, Injecting into handlers, Scopes and lifecycle, Websockets, Framework context objects, See also, API. FastAPI keeps "Coming from
Depends" right after Usage, and Flask has no Websockets section. The old "How to use" H2 with numbered H3s is gone; nothing linked to those anchors. Class-based endpoints/views moved under "Injecting into handlers". The duplicated 12-line implicit/explicit context examples (with a faketimestamp) are merged into one group per page that shows both. Every group is namedAppGroup. The "Requires 3.2.0 or later" notes are dropped, because every release that supports modern-di 4 is 4.0.0 or later.Anchors kept
fastapi.md,litestar.mdandstarlette.md#framework-context-objects,aiohttp.md#api(all fromproviders/context.md), the page paths, the Litestarautowired_groupssection (recipes/multi-group.md), the FastAPI lifespan-merge fact (recipes/async-lifespan.md), and a root-lifecycle statement on every page (providers/lifecycle.md).Checks
just test-docs -q -p no:cacheprovider --no-cov: 152 passed, 21 skippedjust test-docs-integrations -q -p no:cacheprovider: 260 passed, 1 skipped (a NATS placeholder on another page)just docs-build: no WARNING linesjust lint-ci: passedlychee --offlineover docs and README: 0 errorsLibrary issues found (not fixed here)
modern-di-litestar: an autowired provider named
di_containerbreaks the app with RecursionError. It overwrites the plugin's container dependency, which everyFromDIdepends on.The plugin should reserve the name, by raising or skipping it.
modern-di-flask:
@injecton anasync defview hands Flask a coroutine. The wrapper is a sync function, so Flask'sensure_syncnever sees a coroutine function.auto_inject=Truewraps async views the same way.modern-di-aiohttp stores the child container under a
strrequest key. aiohttp 3.14 emitsNotAppKeyWarningon the first such write ("It is recommended to use web.RequestKey instances for keys"). Under this repo'sfilterwarnings = ["error"]that fails the first request, so the hidden aiohttp checks ignore that warning class. Switching toweb.RequestKeywould fix it.Out of this PR
docs/providers/context.md:253links[aiohttp](../integrations/aiohttp.md#api). It can now point at#framework-context-objectslike the other three.#apistill works.starlette.testclientwarns whenhttpx2is not installed (starlette 1.7). The hidden FastAPI/Starlette checks suppress it around the import. Addinghttpx2todocs_integration_extrasin thejustfilewould remove the need.