Skip to content

docs: rework the wiring, alias, group and kwargs troubleshooting pages - #663

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

lesnik512 merged 1 commit into
mainfrom
docs/rework-errors-b

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Reworks the 13 troubleshooting pages for wiring, alias, group and kwargs errors, the same per-page pass already done for the Quickstart, Introduction and Providers pages. I checked every behavioural claim by running a script against this checkout. The scripts live outside the repo.

What was wrong

  • Examples that raise the error they were meant to fix. The Fix blocks on duplicate-type-error.md and argument-resolution-error.md declared two Factory(Repository) / Factory(Service) providers in one group. Building a container from either group raises DuplicateProviderTypeError. Sybil never caught it because neither block built a container. unsupported-creator-parameter-error.md had the same latent duplicate (thing and thing2).
  • Broken/Works pairs inside one block. On creator-call-error.md, unknown-factory-kwarg-error.md and group-instantiation-error.md, the Works half either replaced the Broken provider under the same attribute name or never ran, because the block stopped at the first raise.
  • missing-provider.md said the error fires when a creator parameter's type has no provider. That case raises ArgumentResolutionError. ProviderNotRegisteredError comes only from a direct resolve(SomeType). The page also said validate() catches a missing group. It catches a missing type only when another registered provider depends on it, so a type you only resolve directly still passes validate() (verified: Container().validate() passes, then resolve(X) raises). Its union cause can only produce ArgumentResolutionError, so it moved to that page.
  • circular-dependency.md suggested passing "a factory or value via kwargs" to break a cycle. A provider in kwargs is still a graph edge, and validate() still reports the cycle. Only a static value breaks it. Likewise, an interface aliased back to a member of the cycle keeps the cycle (both verified).
  • Quoted messages. alias-bound-to-source-error.md and duplicate-type-error.md showed <class 'Implementation'> and <class 'SomeType'>, but the library prints the module-qualified repr. Eight pages had only a paraphrase where the message should be.
  • group-instantiation-error.md guessed that the error comes from writing Dependencies() in a type annotation. Nothing supports that, so I cut it.

What was added

  • Every page now quotes the real message, byte for byte apart from the illustrative myapp.<module> path and line numbers, including the variants: the Did you mean: block (subclass, base class, similar name), the union and unannotated forms of ArgumentResolutionError, both UnsupportedCreatorParameterError reasons, and the plain resolve-time CircularDependencyError next to the validate() form.
  • Each page says when the error is raised (at declaration, by the class statement, by Container(...) / add_providers, by validate() inside ValidationFailedError, or at resolve) and lists the attributes you can inspect.
  • creator-call-error.md: validate() cannot catch it under skip_creator_parsing=True, and it also covers a positional-only parameter passed by keyword.
  • unknown-factory-kwarg-error.md: the check is skipped for **kwargs creators, for unreadable signatures and under skip_creator_parsing=True. In that last case a bad key surfaces as CreatorCallError at resolve.
  • duplicate-type-error.md: the distinct-subclass fix the recipes already recommend, plus a bound_type=None example that builds a container and resolves both pairs.
  • circular-dependency.md: a runnable example of breaking the cycle with an interface whose provider sits outside it.

Structure

  • Every page follows Symptom, Cause, Fix, See also. Broken examples get their own <!-- raises: X --> block that really raises X. Works examples build a Container and resolve or validate, so Sybil catches a hidden duplicate from now on.
  • missing-provider.md H1 is now # ProviderNotRegisteredError, per docs-style.md (the class name describes the symptom well). context-not-set.md keeps its sanctioned symptom H1.
  • circular-dependency.md: the top-level "How to detect" section is gone. The runtime-guard text is now an H3 under Cause, and the validate() block opens Fix.
  • context-not-set.md: causes 1 and 2 (the same scope-mismatch mechanism, from both directions) are merged. The optional-value cause now links to providers/context.md and no longer repeats it.
  • unsupported-creator-parameter-error.md: the "Escape hatches" section is folded into the skip_creator_parsing option. The default example uses tuple[Item, ...] = (), which matches its type.
  • See also bullets use the [Title](link): gloss. format of the reworked Providers pages.

Anchors kept

  • circular-dependency.md#the-runtime-cycle-guard-without-validate keeps its heading text verbatim. providers/errors-and-exceptions.md and recipes/good-and-bad-practices.md link to it.
  • No page was renamed, so the docs_slug census and every See: URL still match.
  • Every outbound anchor resolves (mkdocs --strict with anchors: warn is clean).

Checks

  • just test-docs -q -p no:cacheprovider --no-cov: 174 passed, 21 skipped
  • just docs-build: no WARNING lines
  • just lint-ci: clean
  • lychee --offline over docs and README.md: 0 errors
  • tests/test_docs_slug_census.py: passed

Possible library bugs (not fixed here)

  1. A provider instance shared by two groups can't go into one container. The pages document this as current behaviour.
    shared = providers.Factory(SomeService)
    class GroupA(Group): svc = shared
    class GroupB(Group): svc = shared
    Container(groups=[GroupA, GroupB])
    This raises DuplicateProviderTypeError with first_provider is second_provider. The message tells you to set bound_type=None "on one of them", but there is only one. providers/scopes.md says sharing an instance across groups is allowed, which holds at class-creation time only.
  2. The duplicate message can't tell factories with the same creator apart. Two Factory(SomeType) attributes in one group both print - Factory (myapp.ioc:4), the creator's line, so the message doesn't show which attributes collide.
  3. The alias message says "not registered" when the source does have a provider. Factory(Implementation, bound_type=None) plus Alias(Implementation, bound_type=Interface) reports Alias source type ... is not registered in providers registry, even though a provider exists by reference. The page lists this case as a cause.

Needed outside this batch

  • docs/introduction/resolving.md:71: change the link text "No provider registered for type" to "ProviderNotRegisteredError" to match the new H1.
  • docs/providers/errors-and-exceptions.md:130,156,167: the link texts "Troubleshooting: Missing provider", "Context not set" and "Duplicate type" no longer match their targets' H1s.
  • docs/recipes/multi-group.md:118 and docs/recipes/request-scoped-engine.md:88: change "Duplicate provider type" to "DuplicateProviderTypeError".
  • docs/providers/scopes.md (group-level default scope): "Sharing the same provider instance ... is allowed" should mention bug 1, or bug 1 should be fixed.
  • Optional: the mkdocs.yml nav label "Missing provider for type" could become "Provider not registered".

Quote the real error text on every page, fix examples that hid a
DuplicateProviderTypeError, correct the missing-provider and
circular-dependency fix advice, and make every Works example build a
container so the docs tests catch regressions.

@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: 5a9bb67 Previous: 395e8fb Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 4750198.688951317 iter/sec (stddev: 2.3234789889186473e-8) 6139935.138982263 iter/sec (stddev: 1.8605923146541302e-8) 1.29
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 5159815.214623165 iter/sec (stddev: 6.54861821200782e-9) 6205832.315667191 iter/sec (stddev: 1.3805592208773412e-8) 1.20
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 32959.5627619044 iter/sec (stddev: 0.000035615743014019225) 32024.886577255867 iter/sec (stddev: 0.000030238944431934) 0.97
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 26089.773081797834 iter/sec (stddev: 0.00009554265666040005) 24960.133332489408 iter/sec (stddev: 0.00011886986138363566) 0.96
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 658.0016032234211 iter/sec (stddev: 0.00006042023167463634) 775.0388204206938 iter/sec (stddev: 0.00004473754687217169) 1.18
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 647.3457622181978 iter/sec (stddev: 0.00002843147166467482) 764.2436132277375 iter/sec (stddev: 0.0001144571715558005) 1.18
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 618.8227778828133 iter/sec (stddev: 0.00004109459785604703) 680.0345534095648 iter/sec (stddev: 0.0000803400475442327) 1.10
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 19002.212807836837 iter/sec (stddev: 0.000005392733855465352) 10419.935559673875 iter/sec (stddev: 0.00001230746171334532) 0.55
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 9737.807899266536 iter/sec (stddev: 0.000015023667747879051) 5634.100392630293 iter/sec (stddev: 0.00003435893765767916) 0.58
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 4614.2346061544895 iter/sec (stddev: 0.00006300250814986734) 2968.9836953557647 iter/sec (stddev: 0.000061919224501018) 0.64
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] 12697.28942499804 iter/sec (stddev: 0.000007078833510845157) 12389.480670083722 iter/sec (stddev: 0.000019500905757513246) 0.98
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] 6074.662461382533 iter/sec (stddev: 0.000015415074775060534) 4843.535831204409 iter/sec (stddev: 0.00003981695650002605) 0.80
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] 2727.735264381467 iter/sec (stddev: 0.000036021411070600376) 1868.0290700812566 iter/sec (stddev: 0.00007765838735564648) 0.68
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[1] 51964.69864875924 iter/sec (stddev: 0.000002363034821420123) 24896.9783783031 iter/sec (stddev: 0.000017881784016521047) 0.48
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[2] 22854.64843561566 iter/sec (stddev: 0.000010269317953374485) 11645.85197075752 iter/sec (stddev: 0.000015482573556939863) 0.51
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[4] 8631.845323089257 iter/sec (stddev: 0.000022672424357912897) 5052.235696391264 iter/sec (stddev: 0.000048475453192008314) 0.59
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 2206987.3882843857 iter/sec (stddev: 2.1192510241899735e-8) 2335541.2567186337 iter/sec (stddev: 3.860763352243666e-8) 1.06
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 2377850.9838867686 iter/sec (stddev: 2.1162243024614208e-8) 2505907.6773532387 iter/sec (stddev: 3.905850495221895e-8) 1.05
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 4297.152892765896 iter/sec (stddev: 0.000006469049794878834) 3317.2697581338502 iter/sec (stddev: 0.000052891745120707064) 0.77
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 75334.51114415422 iter/sec (stddev: 0.0000012633309713723485) 83100.58991551855 iter/sec (stddev: 0.0000014527703096612033) 1.10
benchmarks/test_guard_lifecycle.py::test_g7b_request_cycle_sync 533013.965579637 iter/sec (stddev: 5.4035962764772836e-8) 468518.9908558271 iter/sec (stddev: 8.515899938405845e-8) 0.88
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 81661.86882821958 iter/sec (stddev: 0.0000010990419881578487) 78785.45603292529 iter/sec (stddev: 0.0000017671089125152717) 0.96
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers 753.6125139931671 iter/sec (stddev: 0.0009246902143746485) 586.9668417253587 iter/sec (stddev: 0.0013708543619692236) 0.78
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 2885809.665850824 iter/sec (stddev: 2.0350585542345523e-8) 3091773.339921797 iter/sec (stddev: 3.1905368666696455e-8) 1.07
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 4963779.302433907 iter/sec (stddev: 6.782386687134114e-9) 5585002.481125255 iter/sec (stddev: 1.7326975696337195e-8) 1.13
benchmarks/test_guard_resolve.py::test_g3_deep_chain 1008560.9680641184 iter/sec (stddev: 4.209705229620944e-8) 1053634.7223852123 iter/sec (stddev: 4.376268176758002e-8) 1.04
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 612874.4701308002 iter/sec (stddev: 3.3833713132520127e-7) 602475.4027370019 iter/sec (stddev: 3.669391702377282e-7) 0.98
benchmarks/test_guard_resolve.py::test_g5_cross_scope 2598865.049632129 iter/sec (stddev: 1.5610323403957834e-8) 2722743.1964451196 iter/sec (stddev: 2.2608711961763022e-8) 1.05
benchmarks/test_guard_resolve.py::test_g9_context_resolve 2004663.9509149287 iter/sec (stddev: 1.2111169439512558e-7) 2095884.4060996436 iter/sec (stddev: 1.4539795764318154e-7) 1.05
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 1024176.1910789233 iter/sec (stddev: 2.6727729786419252e-8) 1056924.0255533508 iter/sec (stddev: 4.539064934432262e-8) 1.03
benchmarks/test_guard_resolve.py::test_g18_alias_hop 4989875.542511868 iter/sec (stddev: 4.6312347992645395e-9) 5564689.374851157 iter/sec (stddev: 1.595292015837515e-8) 1.12
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 54629.205089857984 iter/sec (stddev: 0.000014293526857234689) 23951.926949647954 iter/sec (stddev: 0.000031960404140896095) 0.44
benchmarks/test_guard_validate.py::test_g11_validate_wide 30639.952169012224 iter/sec (stddev: 0.000021512660796096633) 21724.253571453744 iter/sec (stddev: 0.00003130808206721572) 0.71

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

@lesnik512
lesnik512 merged commit bc50735 into main Oct 10, 2026
12 checks passed
@lesnik512
lesnik512 deleted the docs/rework-errors-b 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