Skip to content

refactor: tidy registry, wiring, factory and container internals (#575) - #594

Merged
lesnik512 merged 2 commits into
mainfrom
md-575
Oct 5, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
md-575

Conversation

@lesnik512

@lesnik512 lesnik512 commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Closes #575.

Summary

Internal cleanup. Behaviour and public API stay the same, and the generated resolver templates in resolver_compiler.py are untouched.

  • Providers registry. The three generation-checked stores now go through one _publish helper, generic over the memo's key and value types. drop_resolvers and _invalidate share _drop_resolvers.
  • Wiring data clump. plan_for(provider) and WiringPlan.build(owner, registry=...) take the factory and read its parameters and kwargs themselves. The factory no longer hands its own private fields back alongside itself.
  • WiringPlan. Dropped pure_provider (it was not static_kwargs) and the edges alias of provider_kwargs. Factory._can_call_positionally checks static_kwargs directly, and the graph walk reads provider_kwargs. AGENTS.md and two test docstrings now name provider_kwargs.
  • CacheItem.settings is required. Only cached factories reach fetch_cache_item, so the settings and ... guards were dead outside tests. The duplicated finalize condition is now _pending_finalizer(), and fetch_cache_item takes Factory[typing.Any]. The cache registry tests now build items with CacheSettings(), and the "bare" item with no settings is gone from the close test.
  • Factory.__init__. Parsing and checks moved into _parse_creator, _reject_unknown_kwargs and _reject_unresolvable_generics. ParsedCreator gained accepts_any_kwargs, so the unknown-kwarg check no longer calls inspect.signature a second time. The warnings use stacklevel=3 and still point at the Factory(...) line (checked by hand).
  • Alias. _find_source is gone. _redirect_target is a plain lookup that returns None for a missing source, so no exception is used for control flow. _get_dependencies raises AliasSourceNotRegisteredError on None.
  • Sentinels. is UNSET everywhere ty does not need to narrow (OverrideHandle.__exit__, both skip_creator_parsing/union-warning checks). isinstance(x, UnsetType) stays only where ty narrows the type afterwards, since is UNSET does not narrow.
  • dependency_graph. The stateless DependencyGraph class is now the module functions walk and find_cycle_from. container.py calls them through the module, so tests patch dependency_graph.find_cycle_from and dependency_graph.walk.
  • Group. typing.Any became object in __new__ and __init_subclass__, which removes both noqa: ANN401.
  • Naming. SignatureItem.args is now member_types, matching ArgumentResolutionError.member_types. Factory._parsed_kwargs is now _params, matching ParsedCreator.params.
  • Comments. Removed the two SLF001 rationale comments, the "inlined rather than a helper" comment and the coverage-tracer paragraph in _handle_recursion_error's docstring. The comments that stay are one line each.

Design decisions

  • WiringPlan.build reads owner._params and owner._kwargs with an inline noqa: SLF001. A per-file ignore for wiring.py was the other option, but two reads did not seem worth one. Two precedence tests used to pass made-up parameter maps straight to build. They now set owner._params on a throwaway factory first.
  • ParsedCreator.accepts_any_kwargs is also True when inspect.signature fails. The old check returned early in that case too, so no kwargs= key gets rejected for an uninspectable creator.

Bullets already done or not applicable

  • ParsedCreator.return_type as a whole SignatureItem. Kept. Factory reads .member_types from it for the union-return warning, not only .arg_type.
  • Exception __slots__. The repeated ("_base_message", "dependency_path") pair now appears only on ResolutionError. Moving it onto DependencyPathMixin fails with "multiple bases have instance lay-out conflict" next to RuntimeError. Dropping __slots__ makes instances weak-referenceable, which is a small behaviour change, so I left them as they are.
  • _compile_alias duplicating the source lookup. It is in resolver_compiler.py, works on the registry rather than a container, and that file belongs to Refactor: resolver compiler templates and resolution-step building #574. Left alone.
  • Line numbers, the registries becoming private container attributes and the container close rewrite were already handled by feat!: FinalizerError is an ExceptionGroup (#564) #583 to feat!: tighten Container's public surface (#565) #592.

Resolved questions

Test plan

  • just lint and just lint-ci clean
  • just test-ci: 617 passed, 100% line coverage
  • New test_factory_warning_points_at_the_factory_call checks that both Factory warnings report the caller's file and line. It fails with stacklevel=2.
  • Guard-tier benchmarks (pytest benchmarks/ --ignore=benchmarks/comparative --benchmark-only): 29 passed
  • mkdocs build --strict clean
  • CI green

@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: 139bc34 Previous: d989384 Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 2800759.9582160907 iter/sec (stddev: 1.306901301847443e-8) 3011454.7003564946 iter/sec (stddev: 1.1323228114990187e-8) 1.08
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 2755316.058588567 iter/sec (stddev: 1.3273165229357577e-8) 3019957.023593222 iter/sec (stddev: 1.093424802027123e-8) 1.10
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 20530.035817689233 iter/sec (stddev: 0.00005018214516050216) 20349.205785269238 iter/sec (stddev: 0.0000523667240716527) 0.99
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 16291.624553202475 iter/sec (stddev: 0.0001188397023851702) 16003.317127720646 iter/sec (stddev: 0.00012320275507795883) 0.98
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 363.8081338379358 iter/sec (stddev: 0.00006149524436734405) 394.762689858709 iter/sec (stddev: 0.00007995055136708627) 1.09
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 345.31715073637986 iter/sec (stddev: 0.000058203081407507076) 351.6911874614162 iter/sec (stddev: 0.000454984947973727) 1.02
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 322.6305153999816 iter/sec (stddev: 0.00005313844459959927) 345.1788240812406 iter/sec (stddev: 0.00006543668197917592) 1.07
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 1928.3533180369154 iter/sec (stddev: 0.00016705275736381535) 1934.6799490405135 iter/sec (stddev: 0.000182087980571908) 1.00
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 1490.3280688987932 iter/sec (stddev: 0.0001717478495164807) 1492.4575857536497 iter/sec (stddev: 0.0001797118465704635) 1.00
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 1044.8563628122886 iter/sec (stddev: 0.00017920148930629566) 1050.1470415889385 iter/sec (stddev: 0.0001817990035889884) 1.01
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] 1873.0512072831907 iter/sec (stddev: 0.00019811424111559198) 1910.6620291062904 iter/sec (stddev: 0.00017286707722268446) 1.02
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] 1326.9494385600083 iter/sec (stddev: 0.0001763031471026032) 1141.5328994635493 iter/sec (stddev: 0.0013432424266359635) 0.86
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] 766.3139621507365 iter/sec (stddev: 0.0011320296416828276) 847.901559872413 iter/sec (stddev: 0.00020861333395206277) 1.11
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 945413.2566797478 iter/sec (stddev: 4.411633039500867e-8) 973681.6763908018 iter/sec (stddev: 4.56938148133369e-8) 1.03
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 880626.3631138019 iter/sec (stddev: 2.7197862219509484e-8) 885870.3741764443 iter/sec (stddev: 8.512144883629969e-8) 1.01
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 2365.0806679514244 iter/sec (stddev: 0.000009746185481414945) 2418.3615689175267 iter/sec (stddev: 0.00003596881927590391) 1.02
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 57681.47281610466 iter/sec (stddev: 0.0000025851155368536627) 53782.15896686228 iter/sec (stddev: 0.000006961250967947257) 0.93
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 40337.80819895327 iter/sec (stddev: 0.000002111820985926339) 41835.43269737027 iter/sec (stddev: 0.0000033666901669700696) 1.04
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers 510.44935077391244 iter/sec (stddev: 0.000022310343707135817) 535.8588913626991 iter/sec (stddev: 0.00003599390005431521) 1.05
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 2084065.1441326537 iter/sec (stddev: 2.542606520528666e-8) 1970315.5219002054 iter/sec (stddev: 3.602626171593177e-8) 0.95
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 3107700.016694698 iter/sec (stddev: 1.4661207313646741e-8) 2988615.0205070125 iter/sec (stddev: 1.4195983575456663e-8) 0.96
benchmarks/test_guard_resolve.py::test_g3_deep_chain 694530.7423016601 iter/sec (stddev: 4.880839202154771e-8) 703914.1055850635 iter/sec (stddev: 4.0172620649808416e-8) 1.01
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 439587.9355442461 iter/sec (stddev: 3.7525631912197367e-7) 456542.412562416 iter/sec (stddev: 3.6789550844346597e-7) 1.04
benchmarks/test_guard_resolve.py::test_g5_cross_scope 1889775.0884144164 iter/sec (stddev: 2.0078037678759696e-8) 1849364.1146450916 iter/sec (stddev: 2.60036094632861e-8) 0.98
benchmarks/test_guard_resolve.py::test_g9_context_resolve 1332733.425589795 iter/sec (stddev: 1.2704744037183457e-7) 1329519.1202467596 iter/sec (stddev: 1.532936950112306e-7) 1.00
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 661291.0755985831 iter/sec (stddev: 4.6476599453976785e-8) 699879.2743247835 iter/sec (stddev: 5.822605001118232e-8) 1.06
benchmarks/test_guard_resolve.py::test_g18_alias_hop 2884471.743470469 iter/sec (stddev: 9.953240555059758e-9) 3107097.9695588322 iter/sec (stddev: 1.0543047063911014e-8) 1.08
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 27188.67915592998 iter/sec (stddev: 0.000027909728576002846) 26119.65196646598 iter/sec (stddev: 0.000025633985370715704) 0.96
benchmarks/test_guard_validate.py::test_g11_validate_wide 15989.422421410625 iter/sec (stddev: 0.000025913601680724643) 15718.498294278044 iter/sec (stddev: 0.000026702657245432177) 0.98

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

@lesnik512
lesnik512 merged commit d653e35 into main Oct 5, 2026
9 checks passed
@lesnik512
lesnik512 deleted the md-575 branch October 5, 2026 09:51
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.

Refactor: registries, wiring, container and exception internals

1 participant