Repository navigation
feat: add provider.derive(fn) to inject a value computed from another provider - #733
Merged
Merged
Conversation
… provider settings.derive(lambda s: s.db_dsn) is a provider of fn(value). Type checkers infer s from the source. It is never cached, has no scope of its own and resolves at its source's effective scope, so it works inline in kwargs in any group and an override of the source reaches it. An exception raised by fn propagates unchanged, with a note naming the derived provider. The class is private (_Derived) and lives in providers/abstract.py, because the derive method on AbstractProvider needs it, and a separate module would create an import cycle. Closes #730
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: 16b7f7e | Previous: 5644031 | Ratio |
|---|---|---|---|
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type |
5688748.111334327 iter/sec (stddev: 1.835784278806345e-8) |
4904470.719322522 iter/sec (stddev: 8.90402796485949e-9) |
0.86 |
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry |
5859863.817923041 iter/sec (stddev: 1.6118092909924765e-8) |
4840463.4085030025 iter/sec (stddev: 1.2665696484187963e-8) |
0.83 |
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve |
19816.274017584732 iter/sec (stddev: 0.00004034859102604059) |
23628.313618776076 iter/sec (stddev: 0.00003977866103226871) |
1.19 |
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached |
15553.078006793326 iter/sec (stddev: 0.00013864966221817962) |
18648.76958371226 iter/sec (stddev: 0.00013151010005662552) |
1.20 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] |
777.851822658794 iter/sec (stddev: 0.000040883666344383664) |
635.49521612325 iter/sec (stddev: 0.00010830725124873891) |
0.82 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] |
732.7812404570436 iter/sec (stddev: 0.000056866581075568786) |
622.2577111713392 iter/sec (stddev: 0.000022994150438437634) |
0.85 |
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] |
688.3813254253176 iter/sec (stddev: 0.000055503270910848876) |
600.8444213149718 iter/sec (stddev: 0.00003111836380343128) |
0.87 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] |
16173.409136434311 iter/sec (stddev: 0.000011900032408579106) |
19253.65454423782 iter/sec (stddev: 0.000006325155207578131) |
1.19 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] |
6346.400837090051 iter/sec (stddev: 0.00003463308509007541) |
9804.191504426777 iter/sec (stddev: 0.000018586115607355884) |
1.54 |
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] |
3415.8624682695195 iter/sec (stddev: 0.00004535892669506383) |
5039.582349677059 iter/sec (stddev: 0.00002463455424331871) |
1.48 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] |
12530.022455842092 iter/sec (stddev: 0.000010984717487878114) |
14602.474998002726 iter/sec (stddev: 0.000027412516970430302) |
1.17 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] |
4786.674281731906 iter/sec (stddev: 0.00002930509145646027) |
6404.044965525755 iter/sec (stddev: 0.000015517061243459845) |
1.34 |
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] |
2248.961668454162 iter/sec (stddev: 0.00005240594445521762) |
3011.8917014085687 iter/sec (stddev: 0.000026324174080671828) |
1.34 |
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[1] |
31252.70205603029 iter/sec (stddev: 0.000006610706983284418) |
53793.88067732621 iter/sec (stddev: 0.0000022875613335364985) |
1.72 |
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[2] |
11985.558600412329 iter/sec (stddev: 0.000024845369830575134) |
23331.437654305966 iter/sec (stddev: 0.000006265710218518168) |
1.95 |
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[4] |
5438.430642952551 iter/sec (stddev: 0.00004095086582800911) |
8590.035201249366 iter/sec (stddev: 0.000022493863850767943) |
1.58 |
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container |
2250176.9201494516 iter/sec (stddev: 3.615768058648405e-8) |
2113855.7590402174 iter/sec (stddev: 2.031861697710027e-8) |
0.94 |
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope |
2461037.924291189 iter/sec (stddev: 3.886570985644965e-8) |
2349543.3221972664 iter/sec (stddev: 1.6650926537950493e-8) |
0.95 |
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch |
4180.551436665803 iter/sec (stddev: 0.000007008447618006861) |
4451.999269637383 iter/sec (stddev: 0.000019825297086034126) |
1.06 |
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control |
81831.96167710234 iter/sec (stddev: 0.0000011438983832709063) |
75792.7049367867 iter/sec (stddev: 0.0000010181345487109347) |
0.93 |
benchmarks/test_guard_lifecycle.py::test_g7b_request_cycle_sync |
578864.4323811524 iter/sec (stddev: 6.561305324599335e-8) |
579072.6614580763 iter/sec (stddev: 7.801953521917385e-8) |
1.00 |
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale |
84016.68440863906 iter/sec (stddev: 0.0000013029683975836587) |
89586.42894679612 iter/sec (stddev: 9.419924223584152e-7) |
1.07 |
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers |
767.6633772670398 iter/sec (stddev: 0.0012014510800948679) |
808.4589055286162 iter/sec (stddev: 0.0010166585713580374) |
1.05 |
benchmarks/test_guard_resolve.py::test_g1_transient_resolve |
3274826.376899151 iter/sec (stddev: 2.751959581092676e-8) |
2993114.7137404513 iter/sec (stddev: 2.3521816194443065e-8) |
0.91 |
benchmarks/test_guard_resolve.py::test_g2_cached_resolve |
5476376.281836439 iter/sec (stddev: 1.363982460503147e-8) |
4816101.962687137 iter/sec (stddev: 5.295105482873398e-9) |
0.88 |
benchmarks/test_guard_resolve.py::test_g3_deep_chain |
1207090.2793101487 iter/sec (stddev: 4.1405091565502474e-8) |
1113569.6873886315 iter/sec (stddev: 3.487097985959811e-8) |
0.92 |
benchmarks/test_guard_resolve.py::test_g4_wide_resolve |
681966.4242686173 iter/sec (stddev: 3.612952682357208e-7) |
612604.315410136 iter/sec (stddev: 3.4851084483792727e-7) |
0.90 |
benchmarks/test_guard_resolve.py::test_g5_cross_scope |
2825883.139694212 iter/sec (stddev: 2.3994413132611186e-8) |
2674624.3874501064 iter/sec (stddev: 2.4053681698733978e-8) |
0.95 |
benchmarks/test_guard_resolve.py::test_g9_context_resolve |
2142767.452735959 iter/sec (stddev: 1.269114249373723e-7) |
1960294.8165720731 iter/sec (stddev: 1.311245293630255e-7) |
0.91 |
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve |
1223976.2754155502 iter/sec (stddev: 3.7142038160007894e-8) |
1180518.9348746464 iter/sec (stddev: 5.260935842068453e-8) |
0.96 |
benchmarks/test_guard_resolve.py::test_g18_alias_hop |
5697638.516862223 iter/sec (stddev: 1.396202929205016e-8) |
4813390.158237099 iter/sec (stddev: 4.760819374662499e-9) |
0.84 |
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain |
42333.4150131789 iter/sec (stddev: 0.000015519171916960328) |
53266.10819056237 iter/sec (stddev: 0.000014901713787393878) |
1.26 |
benchmarks/test_guard_validate.py::test_g11_validate_wide |
25905.449874576603 iter/sec (stddev: 0.00002038012677720931) |
31125.67788213068 iter/sec (stddev: 0.000019062727395208582) |
1.20 |
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.
Closes #730.
Adds
provider.derive(fn), a provider offn(value), wherevalueis whatproviderresolves to. It covers the settings case from discussion #136:mypy, pyright and ty all infer
sfromsettingsand flags.db_dns. A derived provider is never cached, has no scope of its own and is not registered under any type. Overriding the source reaches it, and so does overriding the derived provider directly.validate()checks the consuming factory against the source's effective scope and never callsfn. An exception fromfnis re-raised unchanged with a note:raised by the function of Settings.derive (module:line).Design notes:
_Derivedis private and lives inproviders/abstract.py, notproviders/derived.pyas the issue said.AbstractProvider.derivebuilds it, so a separate module would need a circular import.dependency_graph.scope_chainextendsterminal_chainthrough derived sources.effective_scopeand the scope checks incollect_errorsuse it.redirect_targetis unchanged, because a derived node is a step of its own in the chain, not a hop.fn(source_resolver(container)). That costs two Python calls per node, the same as aFactory, andtest_derived_node_costs_its_resolver_frame_and_fnpins it.AbstractProvider.__init__takescall_depthso the call site recorded for a derived provider is the user'sderive(...)line.finalizer_notedoes.docs/providers/factories.mdcarries the settings example. TheAttrGetterand.providedrows in the migration guides now point there.#732, which gives inline providers the group's scope, is a separate branch. Whichever lands second needs a small rebase. #732's inline-scope walk will also need a branch for derived sources.