wireup 2.4.0__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. {wireup-2.4.0 → wireup-2.6.0}/.vscode/settings.json +1 -0
  2. {wireup-2.4.0 → wireup-2.6.0}/PKG-INFO +4 -22
  3. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/configuration.md +38 -0
  4. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/index.md +2 -1
  5. {wireup-2.4.0 → wireup-2.6.0}/pyproject.toml +1 -1
  6. {wireup-2.4.0 → wireup-2.6.0}/readme.md +3 -21
  7. {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/test_flask_integration.py +33 -0
  8. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_as_type.py +77 -0
  9. wireup-2.6.0/test/unit/test_concurrency.py +85 -0
  10. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_override.py +23 -0
  11. wireup-2.6.0/test/unit/test_container_override_extended.py +118 -0
  12. wireup-2.6.0/test/unit/test_parameter.py +182 -0
  13. {wireup-2.4.0 → wireup-2.6.0}/uv.lock +1 -1
  14. wireup-2.4.0/z_test_codegen.py → wireup-2.6.0/wireup/codegen.py +2 -0
  15. {wireup-2.4.0 → wireup-2.6.0}/wireup/errors.py +11 -2
  16. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/aiohttp.py +4 -4
  17. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/fastapi.py +1 -2
  18. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/flask.py +7 -6
  19. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/configuration.py +32 -4
  20. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/__init__.py +1 -1
  21. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/base_container.py +9 -0
  22. wireup-2.6.0/wireup/ioc/container/lock_registry.py +20 -0
  23. wireup-2.6.0/wireup/ioc/factory_compiler.py +256 -0
  24. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/override_manager.py +14 -3
  25. wireup-2.6.0/z_test_bench.py +103 -0
  26. wireup-2.6.0/z_test_repro.py +39 -0
  27. wireup-2.6.0/z_test_repro_fastapi_race.py +123 -0
  28. wireup-2.6.0/z_test_repro_wireup_race.py +120 -0
  29. wireup-2.4.0/test/unit/test_parameter.py +0 -52
  30. wireup-2.4.0/wireup/ioc/factory_compiler.py +0 -175
  31. wireup-2.4.0/z_test_CHEATSHEET.md +0 -476
  32. wireup-2.4.0/z_test_abstract.py +0 -19
  33. wireup-2.4.0/z_test_benchmark.py +0 -126
  34. wireup-2.4.0/z_test_binds.py +0 -92
  35. wireup-2.4.0/z_test_blog_post.md +0 -169
  36. wireup-2.4.0/z_test_circular.py +0 -26
  37. wireup-2.4.0/z_test_err.md +0 -56
  38. wireup-2.4.0/z_test_fastapi-zero-overhead.md +0 -181
  39. wireup-2.4.0/z_test_get.py +0 -39
  40. wireup-2.4.0/z_test_inheritance.py +0 -42
  41. wireup-2.4.0/z_test_instance.py +0 -17
  42. wireup-2.4.0/z_test_interfaces.py +0 -52
  43. wireup-2.4.0/z_test_isolated.py +0 -31
  44. wireup-2.4.0/z_test_mapping.py +0 -13
  45. wireup-2.4.0/z_test_mermaid.py +0 -103
  46. wireup-2.4.0/z_test_new.py +0 -20
  47. wireup-2.4.0/z_test_optional.py +0 -57
  48. wireup-2.4.0/z_test_override.py +0 -27
  49. wireup-2.4.0/z_test_param.py +0 -7
  50. wireup-2.4.0/z_test_param_output.txt +0 -4
  51. wireup-2.4.0/z_test_param_output_2.txt +0 -20
  52. wireup-2.4.0/z_test_param_output_3.txt +0 -4
  53. wireup-2.4.0/z_test_protocol_mypy.py +0 -33
  54. wireup-2.4.0/z_test_py_ver.py +0 -3
  55. wireup-2.4.0/z_test_pydantic.py +0 -31
  56. wireup-2.4.0/z_test_repo_summary.md +0 -162
  57. wireup-2.4.0/z_test_repro.py +0 -51
  58. wireup-2.4.0/z_test_repro_results_2.txt +0 -5
  59. wireup-2.4.0/z_test_roadmap.md +0 -2
  60. wireup-2.4.0/z_test_runtime_checkable.py +0 -14
  61. wireup-2.4.0/z_test_thing.py +0 -12
  62. {wireup-2.4.0 → wireup-2.6.0}/.agent/rules.md +0 -0
  63. {wireup-2.4.0 → wireup-2.6.0}/.agent/workflows/context.md +0 -0
  64. {wireup-2.4.0 → wireup-2.6.0}/.github/contributing.md +0 -0
  65. {wireup-2.4.0 → wireup-2.6.0}/.github/workflows/run_all.yml +0 -0
  66. {wireup-2.4.0 → wireup-2.6.0}/.github/workflows/run_integration_tests.yml +0 -0
  67. {wireup-2.4.0 → wireup-2.6.0}/.gitignore +0 -0
  68. {wireup-2.4.0 → wireup-2.6.0}/.vscode/launch.json +0 -0
  69. {wireup-2.4.0 → wireup-2.6.0}/Makefile +0 -0
  70. {wireup-2.4.0 → wireup-2.6.0}/docs/mkdocs.yml +0 -0
  71. {wireup-2.4.0 → wireup-2.6.0}/docs/overrides/main.html +0 -0
  72. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/aiohttp_integration.md +0 -0
  73. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/click_integration.md +0 -0
  74. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/config_store.md +0 -0
  75. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/django_integration.md +0 -0
  76. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/fastapi_integration.md +0 -0
  77. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/flask_integration.md +0 -0
  78. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/override_manager.md +0 -0
  79. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/starlette_integration.md +0 -0
  80. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/wireup.md +0 -0
  81. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/container.md +0 -0
  82. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/factories.md +0 -0
  83. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/function_injection.md +0 -0
  84. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/getting_started.md +0 -0
  85. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/injectables.md +0 -0
  86. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/aiohttp/class_based_handlers.md +0 -0
  87. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/aiohttp/index.md +0 -0
  88. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/click/index.md +0 -0
  89. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/django/index.md +0 -0
  90. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/fastapi/class_based_handlers.md +0 -0
  91. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/fastapi/direct_container_access.md +0 -0
  92. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/fastapi/index.md +0 -0
  93. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/flask/index.md +0 -0
  94. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/index.md +0 -0
  95. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/starlette/index.md +0 -0
  96. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/interfaces.md +0 -0
  97. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/lifetimes_and_scopes.md +0 -0
  98. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/resources.md +0 -0
  99. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/stylesheets/extra.css +0 -0
  100. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/testing.md +0 -0
  101. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/upgrading.md +0 -0
  102. {wireup-2.4.0 → wireup-2.6.0}/docs/pages/versioning.md +0 -0
  103. {wireup-2.4.0 → wireup-2.6.0}/license.md +0 -0
  104. {wireup-2.4.0 → wireup-2.6.0}/profile_tests.py +0 -0
  105. {wireup-2.4.0 → wireup-2.6.0}/test/__init__.py +0 -0
  106. {wireup-2.4.0 → wireup-2.6.0}/test/conftest.py +0 -0
  107. {wireup-2.4.0 → wireup-2.6.0}/test/integration/__init__.py +0 -0
  108. {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/__init__.py +0 -0
  109. {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/handler.py +0 -0
  110. {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/routes.py +0 -0
  111. {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/services.py +0 -0
  112. {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/test_aiohttp_integration.py +0 -0
  113. {wireup-2.4.0 → wireup-2.6.0}/test/integration/click/__init__.py +0 -0
  114. {wireup-2.4.0 → wireup-2.6.0}/test/integration/click/test_click_integration.py +0 -0
  115. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/__init__.py +0 -0
  116. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/__init__.py +0 -0
  117. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/__init__.py +0 -0
  118. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/app.py +0 -0
  119. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/urls.py +0 -0
  120. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/views.py +0 -0
  121. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/__init__.py +0 -0
  122. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/app.py +0 -0
  123. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/urls.py +0 -0
  124. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/views.py +0 -0
  125. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/__init__.py +0 -0
  126. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/app.py +0 -0
  127. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/urls.py +0 -0
  128. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/views.py +0 -0
  129. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/__init__.py +0 -0
  130. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/app.py +0 -0
  131. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/urls.py +0 -0
  132. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/views.py +0 -0
  133. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/factory/__init__.py +0 -0
  134. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/factory/factories.py +0 -0
  135. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/injectable/__init__.py +0 -0
  136. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/injectable/current_request_service.py +0 -0
  137. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/injectable/random_service.py +0 -0
  138. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/templates/bar.html +0 -0
  139. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/templates/foo.html +0 -0
  140. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/test_django_integration.py +0 -0
  141. {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/view.py +0 -0
  142. {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/__init__.py +0 -0
  143. {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/cbr.py +0 -0
  144. {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/router.py +0 -0
  145. {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/services.py +0 -0
  146. {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/test_fastapi_integration.py +0 -0
  147. {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/wireup_route.py +0 -0
  148. {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/__init__.py +0 -0
  149. {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/bp.py +0 -0
  150. {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/services/__init__.py +0 -0
  151. {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/services/factories.py +0 -0
  152. {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/services/is_test_service.py +0 -0
  153. {wireup-2.4.0 → wireup-2.6.0}/test/integration/starlette/__init__.py +0 -0
  154. {wireup-2.4.0 → wireup-2.6.0}/test/integration/starlette/test_starlette_integration.py +0 -0
  155. {wireup-2.4.0 → wireup-2.6.0}/test/shared/__init__.py +0 -0
  156. {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/__init__.py +0 -0
  157. {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/greeter.py +0 -0
  158. {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/rand.py +0 -0
  159. {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/scoped.py +0 -0
  160. {wireup-2.4.0 → wireup-2.6.0}/test/unit/__init__.py +0 -0
  161. {wireup-2.4.0 → wireup-2.6.0}/test/unit/service_refs/__init__.py +0 -0
  162. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/__init__.py +0 -0
  163. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/abstract_multiple_bases.py +0 -0
  164. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/inheritance_test/__init__.py +0 -0
  165. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/inheritance_test/base.py +0 -0
  166. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/__init__.py +0 -0
  167. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/random/__init__.py +0 -0
  168. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/random/random_service.py +0 -0
  169. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/random/truly_random_service.py +0 -0
  170. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/with_annotations/__init__.py +0 -0
  171. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/with_annotations/env/__init__.py +0 -0
  172. {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/with_annotations/services.py +0 -0
  173. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_core.py +0 -0
  174. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_creation.py +0 -0
  175. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_generator_factory.py +0 -0
  176. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_scope.py +0 -0
  177. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_eval_type.py +0 -0
  178. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_exit_stack.py +0 -0
  179. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_inject_from_container.py +0 -0
  180. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_inject_from_container_optional_types.py +0 -0
  181. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_inject_from_container_stringified_types.py +0 -0
  182. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_module_loading.py +0 -0
  183. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_optional_type.py +0 -0
  184. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_service_deprecation.py +0 -0
  185. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_service_registry.py +0 -0
  186. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_signature_hiding.py +0 -0
  187. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_type_analysis.py +0 -0
  188. {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_util.py +0 -0
  189. {wireup-2.4.0 → wireup-2.6.0}/test/unit/util.py +0 -0
  190. {wireup-2.4.0 → wireup-2.6.0}/tox.ini +0 -0
  191. {wireup-2.4.0 → wireup-2.6.0}/wireup/__init__.py +0 -0
  192. {wireup-2.4.0 → wireup-2.6.0}/wireup/_annotations.py +0 -0
  193. {wireup-2.4.0 → wireup-2.6.0}/wireup/_decorators.py +0 -0
  194. {wireup-2.4.0 → wireup-2.6.0}/wireup/_discovery.py +0 -0
  195. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/__init__.py +0 -0
  196. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/click.py +0 -0
  197. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/django/__init__.py +0 -0
  198. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/django/apps.py +0 -0
  199. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/django/decorators.py +0 -0
  200. {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/starlette.py +0 -0
  201. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/__init__.py +0 -0
  202. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/_exit_stack.py +0 -0
  203. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/async_container.py +0 -0
  204. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/sync_container.py +0 -0
  205. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/registry.py +0 -0
  206. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/type_analysis.py +0 -0
  207. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/types.py +0 -0
  208. {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/util.py +0 -0
  209. {wireup-2.4.0 → wireup-2.6.0}/wireup/py.typed +0 -0
  210. {wireup-2.4.0 → wireup-2.6.0}/wireup/util.py +0 -0
  211. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/__init__.py +0 -0
  212. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/js_stack.py +0 -0
  213. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_cit_code1.py +0 -0
  214. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_cit_code2.py +0 -0
  215. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_cit_palindrome.py +0 -0
  216. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_citadel_oa1.py +0 -0
  217. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_js_code1.py +0 -0
  218. {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_js_code2.py +0 -0
  219. {wireup-2.4.0 → wireup-2.6.0}/z_test_services/__init__.py +0 -0
  220. {wireup-2.4.0 → wireup-2.6.0}/z_test_services/bar.py +0 -0
  221. {wireup-2.4.0 → wireup-2.6.0}/z_test_services/foo.py +0 -0
@@ -12,6 +12,7 @@
12
12
  "cursorpyright.analysis.autoImportCompletions": true,
13
13
  "cSpell.words": [
14
14
  "asyncio",
15
+ "Codegen",
15
16
  "pyrefly"
16
17
  ],
17
18
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wireup
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Python Dependency Injection Library
5
5
  Project-URL: Homepage, https://github.com/maldoinc/wireup
6
6
  Project-URL: Repository, https://github.com/maldoinc/wireup
@@ -47,7 +47,9 @@ Description-Content-Type: text/markdown
47
47
  [![Documentation](https://img.shields.io/badge/%F0%9F%93%9A%20Documentation-3D9970)](https://maldoinc.github.io/wireup)
48
48
  </div>
49
49
 
50
- Automate dependency management using Python's type system. Build complex applications with native support for async and generators, plus integrations for popular frameworks out of the box.
50
+ Automate dependency management using Python's type system. Build complex applications with native support for async and
51
+ generators, plus integrations for popular frameworks out of the box. Wireup is thread-safe and ready for no-GIL
52
+ Python (PEP 703).
51
53
 
52
54
  > [!TIP]
53
55
  > **New**: Inject Dependencies in FastAPI with zero runtime overhead using [Class-Based Handlers](https://maldoinc.github.io/wireup/latest/integrations/fastapi/class_based_handlers/).
@@ -339,26 +341,6 @@ def command(service: Injected[UserService]): ...
339
341
  def task(service: Injected[UserService]): ...
340
342
  ```
341
343
 
342
- ```mermaid
343
- graph LR
344
- subgraph "Core Business Logic"
345
- Services[Injectables & Services]
346
- Domain[Domain Models]
347
- end
348
-
349
- subgraph "Entry Points"
350
- API[FastAPI / Web]
351
- CLI[Click / CLI]
352
- Worker[Celery / Worker]
353
- end
354
-
355
- API -->|Uses| Services
356
- CLI -->|Uses| Services
357
- Worker -->|Uses| Services
358
-
359
- Services --> Domain
360
- ```
361
-
362
344
  ### 🔌 Native Integration with popular frameworks
363
345
 
364
346
  Integrate with popular frameworks for a smoother developer experience.
@@ -39,6 +39,44 @@ class DatabaseService:
39
39
  self.url = url
40
40
  ```
41
41
 
42
+ ### Dot Notation (Nested Configuration)
43
+
44
+ Access nested configuration values using dot notation. Wireup allows you to inject configuration from any point in the
45
+ tree, not just leaf values.
46
+
47
+ ```python
48
+ import wireup
49
+ from typing import Annotated, Any
50
+ from wireup import injectable, Inject
51
+
52
+ container = wireup.create_sync_container(
53
+ config={
54
+ "db": {
55
+ "host": "localhost",
56
+ "port": 5432,
57
+ "options": {"timeout": 30},
58
+ },
59
+ }
60
+ )
61
+
62
+
63
+ @injectable
64
+ class DatabaseService:
65
+ def __init__(
66
+ self,
67
+ # Injects "localhost"
68
+ host: Annotated[str, Inject(config="db.host")],
69
+ # Injects 5432
70
+ port: Annotated[int, Inject(config="db.port")],
71
+ # Injects the entire dictionary: {"timeout": 30}
72
+ # You can inject any value from the config, not just primitives.
73
+ options: Annotated[dict[str, Any], Inject(config="db.options")],
74
+ # Injects the whole 'db' config: {"host": "...", "port": ..., "options": ...}
75
+ db_config: Annotated[dict[str, Any], Inject(config="db")],
76
+ ) -> None:
77
+ self.host = host
78
+ ```
79
+
42
80
  ### Structured Objects
43
81
 
44
82
  You are not limited to primitives. You can inject entire configuration objects, such as Dataclasses or Pydantic models.
@@ -5,7 +5,8 @@ Performant, concise and type-safe Dependency Injection for Python.
5
5
  [![PyPI - Version](https://img.shields.io/pypi/v/wireup)](https://pypi.org/project/wireup/)
6
6
 
7
7
  Automate dependency management using Python's type system. Build complex applications with native support for async and
8
- generators, plus integrations for popular frameworks out of the box.
8
+ generators, plus integrations for popular frameworks out of the box. Wireup is thread-safe and ready for no-GIL Python
9
+ (PEP 703).
9
10
 
10
11
  !!! tip "Zero Runtime Overhead"
11
12
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wireup"
3
- version = "2.4.0"
3
+ version = "2.6.0"
4
4
  description = "Python Dependency Injection Library"
5
5
  authors = [{ name = "Aldo Mateli", email = "aldo.mateli@gmail.com" }]
6
6
  license = { text = "MIT" }
@@ -9,7 +9,9 @@
9
9
  [![Documentation](https://img.shields.io/badge/%F0%9F%93%9A%20Documentation-3D9970)](https://maldoinc.github.io/wireup)
10
10
  </div>
11
11
 
12
- Automate dependency management using Python's type system. Build complex applications with native support for async and generators, plus integrations for popular frameworks out of the box.
12
+ Automate dependency management using Python's type system. Build complex applications with native support for async and
13
+ generators, plus integrations for popular frameworks out of the box. Wireup is thread-safe and ready for no-GIL
14
+ Python (PEP 703).
13
15
 
14
16
  > [!TIP]
15
17
  > **New**: Inject Dependencies in FastAPI with zero runtime overhead using [Class-Based Handlers](https://maldoinc.github.io/wireup/latest/integrations/fastapi/class_based_handlers/).
@@ -301,26 +303,6 @@ def command(service: Injected[UserService]): ...
301
303
  def task(service: Injected[UserService]): ...
302
304
  ```
303
305
 
304
- ```mermaid
305
- graph LR
306
- subgraph "Core Business Logic"
307
- Services[Injectables & Services]
308
- Domain[Domain Models]
309
- end
310
-
311
- subgraph "Entry Points"
312
- API[FastAPI / Web]
313
- CLI[Click / CLI]
314
- Worker[Celery / Worker]
315
- end
316
-
317
- API -->|Uses| Services
318
- CLI -->|Uses| Services
319
- Worker -->|Uses| Services
320
-
321
- Services --> Domain
322
- ```
323
-
324
306
  ### 🔌 Native Integration with popular frameworks
325
307
 
326
308
  Integrate with popular frameworks for a smoother developer experience.
@@ -1,9 +1,11 @@
1
+ from typing import Iterator, NewType
1
2
  from unittest.mock import MagicMock
2
3
 
3
4
  import pytest
4
5
  import wireup.integration.flask
5
6
  from flask import Flask
6
7
  from flask.testing import FlaskClient
8
+ from wireup._annotations import Injected, injectable
7
9
  from wireup.integration.flask import get_app_container
8
10
 
9
11
  from test.integration.flask import services as flask_integration_services
@@ -73,3 +75,34 @@ def test_service_override(client: FlaskClient, app: Flask):
73
75
  res = client.get("/foo")
74
76
  assert res.status_code == 200
75
77
  assert res.json == {"test": "mocked"}
78
+
79
+
80
+ def test_flask_err_cleanup() -> None:
81
+ Something = NewType("Something", str)
82
+ dep = {"created": False, "cleanup": False}
83
+
84
+ @injectable(lifetime="scoped")
85
+ def make_with_cleanup() -> Iterator[Something]:
86
+ dep["created"] = True
87
+ try:
88
+ yield Something("hello")
89
+ finally:
90
+ dep["cleanup"] = True
91
+
92
+ app = Flask(__name__)
93
+ app.config["PROPAGATE_EXCEPTIONS"] = True
94
+
95
+ @app.get("/err")
96
+ def _err_endpoint(with_cleanup: Injected[Something]) -> str:
97
+ assert with_cleanup == "hello"
98
+ raise Exception("err in endpoint")
99
+
100
+ container = wireup.create_sync_container(injectables=[make_with_cleanup])
101
+ wireup.integration.flask.setup(container, app)
102
+
103
+ client = app.test_client()
104
+ with pytest.raises(Exception): # noqa: B017
105
+ client.get("/err")
106
+
107
+ assert dep["created"] is True
108
+ assert dep["cleanup"] is True
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import dataclass
4
6
  from typing import Optional, Protocol
5
7
 
6
8
  import pytest
@@ -171,3 +173,78 @@ def test_as_type_on_optional_factory_new_union_optional():
171
173
  instance = container.get(OptionalProto | None)
172
174
  assert isinstance(instance, OptionalImpl)
173
175
  assert instance.opt() == "opt"
176
+
177
+
178
+ class AbstractFoo(ABC):
179
+ @abstractmethod
180
+ def bar(self) -> str: ...
181
+
182
+
183
+ @injectable(as_type=AbstractFoo)
184
+ class AbstractFooImpl(AbstractFoo):
185
+ def bar(self) -> str:
186
+ return "bar"
187
+
188
+
189
+ def test_register_with_as_type_abc():
190
+ container = create_sync_container(injectables=[AbstractFooImpl])
191
+
192
+ # Should be resolvable via the abstraction
193
+ instance = container.get(AbstractFoo)
194
+ assert isinstance(instance, AbstractFooImpl)
195
+ assert instance.bar() == "bar"
196
+
197
+ # Should NOT be resolvable via the implementation class directly
198
+ with pytest.raises(UnknownServiceRequestedError):
199
+ container.get(AbstractFooImpl)
200
+
201
+
202
+ class FunctionProto(Protocol):
203
+ def call(self) -> int: ...
204
+
205
+
206
+ class RealService:
207
+ def call(self) -> int:
208
+ return 42
209
+
210
+
211
+ def test_as_type_on_simple_function():
212
+ @injectable(as_type=FunctionProto)
213
+ def factory() -> RealService:
214
+ return RealService()
215
+
216
+ container = create_sync_container(injectables=[factory])
217
+
218
+ svc = container.get(FunctionProto)
219
+ assert svc.call() == 42
220
+ assert isinstance(svc, RealService)
221
+
222
+
223
+ class DependencyProto(Protocol):
224
+ def do_something(self) -> str: ...
225
+
226
+
227
+ class DependencyImpl:
228
+ def do_something(self) -> str:
229
+ return "dependency_impl"
230
+
231
+
232
+ @injectable
233
+ @dataclass
234
+ class ServiceWithDependency:
235
+ dependency: DependencyProto
236
+
237
+
238
+ def test_as_type_factory_dependency_injection():
239
+ # Test that a dependency produced by a factory with as_type
240
+ # is correctly injected into another service.
241
+
242
+ @injectable(as_type=DependencyProto)
243
+ def make_dependency() -> DependencyImpl:
244
+ return DependencyImpl()
245
+
246
+ container = create_sync_container(injectables=[make_dependency, ServiceWithDependency])
247
+
248
+ service = container.get(ServiceWithDependency)
249
+ assert isinstance(service.dependency, DependencyImpl)
250
+ assert service.dependency.do_something() == "dependency_impl"
@@ -0,0 +1,85 @@
1
+ import asyncio
2
+
3
+ import pytest
4
+ from wireup import create_async_container, injectable
5
+
6
+
7
+ class Counter:
8
+ pass
9
+
10
+
11
+ class ScopedCounter:
12
+ pass
13
+
14
+
15
+ @injectable
16
+ async def counter_factory() -> Counter:
17
+ await asyncio.sleep(0.01)
18
+ return Counter()
19
+
20
+
21
+ @injectable(lifetime="scoped")
22
+ async def scoped_counter_factory() -> ScopedCounter:
23
+ await asyncio.sleep(0.01)
24
+ return ScopedCounter()
25
+
26
+
27
+ async def test_singleton_concurrency() -> None:
28
+ container = create_async_container(services=[counter_factory])
29
+
30
+ results = await asyncio.gather(
31
+ container.get(Counter),
32
+ container.get(Counter),
33
+ container.get(Counter),
34
+ container.get(Counter),
35
+ container.get(Counter),
36
+ )
37
+
38
+ unique_instances = set(results)
39
+ assert len(unique_instances) == 1, f"Expected 1 singleton instance, got {len(unique_instances)}"
40
+
41
+
42
+ async def test_scoped_concurrency_same_scope() -> None:
43
+ container = create_async_container(services=[scoped_counter_factory])
44
+
45
+ async with container.enter_scope() as scope:
46
+ results = await asyncio.gather(
47
+ scope.get(ScopedCounter),
48
+ scope.get(ScopedCounter),
49
+ scope.get(ScopedCounter),
50
+ scope.get(ScopedCounter),
51
+ scope.get(ScopedCounter),
52
+ )
53
+
54
+ unique_instances = set(results)
55
+ assert len(unique_instances) == 1, f"Expected 1 scoped instance within same scope, got {len(unique_instances)}"
56
+
57
+
58
+ async def test_scoped_concurrency_different_scopes() -> None:
59
+ container = create_async_container(services=[scoped_counter_factory])
60
+
61
+ async with container.enter_scope() as scope1:
62
+ async with container.enter_scope() as scope2:
63
+ res1, res2 = await asyncio.gather(scope1.get(ScopedCounter), scope2.get(ScopedCounter))
64
+
65
+ assert res1 is not res2, "Different scopes should produce different instances"
66
+
67
+
68
+ async def test_singleton_concurrency_cross_scope() -> None:
69
+ container = create_async_container(services=[counter_factory])
70
+
71
+ async with container.enter_scope() as scope1, container.enter_scope() as scope2:
72
+ # Concurrently request the SINGLETON from two different scopes
73
+ results = await asyncio.gather(
74
+ scope1.get(Counter),
75
+ scope1.get(Counter),
76
+ scope1.get(Counter),
77
+ scope2.get(Counter),
78
+ scope2.get(Counter),
79
+ scope2.get(Counter),
80
+ scope2.get(Counter),
81
+ container.get(Counter),
82
+ )
83
+
84
+ unique_instances = set(results)
85
+ assert len(unique_instances) == 1, f"Expected 1 singleton instance, got {len(unique_instances)}"
@@ -92,3 +92,26 @@ async def test_overrides_async_dependency() -> None:
92
92
 
93
93
  res = await container.get(FooBar)
94
94
  assert res.foo == "bar"
95
+
96
+
97
+ async def test_override_async_transitive_dependency_with_sync_instance():
98
+ class FooDep:
99
+ pass
100
+
101
+ @wireup.injectable
102
+ async def async_foo_dep() -> FooDep:
103
+ return FooDep()
104
+
105
+ @wireup.injectable
106
+ class BarDep:
107
+ def __init__(self, foo: FooDep):
108
+ self.foo = foo
109
+
110
+ class FooOverride:
111
+ pass
112
+
113
+ container = wireup.create_async_container(injectables=[async_foo_dep, BarDep])
114
+
115
+ with container.override.injectable(FooDep, FooOverride()):
116
+ bar = await container.get(BarDep)
117
+ assert isinstance(bar.foo, FooOverride)
@@ -0,0 +1,118 @@
1
+ from typing import Protocol
2
+ from unittest.mock import MagicMock
3
+
4
+ from wireup import abstract, create_async_container, create_sync_container, injectable
5
+
6
+
7
+ @abstract
8
+ class AbstractBase:
9
+ pass
10
+
11
+
12
+ @injectable
13
+ class ConcreteImpl(AbstractBase):
14
+ pass
15
+
16
+
17
+ @injectable(lifetime="transient")
18
+ class ServiceDependsOnAbstract:
19
+ def __init__(self, dep: AbstractBase):
20
+ self.dep = dep
21
+
22
+
23
+ class Proto(Protocol):
24
+ def method(self): ...
25
+
26
+
27
+ @injectable(as_type=Proto)
28
+ class ProtoImpl:
29
+ def method(self):
30
+ return "impl"
31
+
32
+
33
+ @injectable(lifetime="transient")
34
+ class ServiceDependsOnProto:
35
+ def __init__(self, dep: Proto):
36
+ self.dep = dep
37
+
38
+
39
+ def test_override_abstract_direct():
40
+ container = create_sync_container(injectables=[AbstractBase, ConcreteImpl])
41
+
42
+ mock_obj = MagicMock(spec=AbstractBase)
43
+
44
+ with container.override.injectable(target=AbstractBase, new=mock_obj):
45
+ assert container.get(AbstractBase) is mock_obj
46
+
47
+ assert isinstance(container.get(AbstractBase), ConcreteImpl)
48
+
49
+
50
+ def test_override_abstract_indirect():
51
+ container = create_sync_container(injectables=[AbstractBase, ConcreteImpl, ServiceDependsOnAbstract])
52
+
53
+ mock_obj = MagicMock(spec=AbstractBase)
54
+
55
+ with container.override.injectable(target=AbstractBase, new=mock_obj):
56
+ with container.enter_scope() as scope:
57
+ svc = scope.get(ServiceDependsOnAbstract)
58
+ assert svc.dep is mock_obj
59
+
60
+ with container.enter_scope() as scope:
61
+ svc = scope.get(ServiceDependsOnAbstract)
62
+ assert isinstance(svc.dep, ConcreteImpl)
63
+
64
+
65
+ async def test_override_abstract_indirect_async():
66
+ container = create_async_container(injectables=[AbstractBase, ConcreteImpl, ServiceDependsOnAbstract])
67
+
68
+ mock_obj = MagicMock(spec=AbstractBase)
69
+
70
+ with container.override.injectable(target=AbstractBase, new=mock_obj):
71
+ async with container.enter_scope() as scope:
72
+ svc = await scope.get(ServiceDependsOnAbstract)
73
+ assert svc.dep is mock_obj
74
+
75
+ async with container.enter_scope() as scope:
76
+ svc = await scope.get(ServiceDependsOnAbstract)
77
+ assert isinstance(svc.dep, ConcreteImpl)
78
+
79
+
80
+ def test_override_as_type_direct():
81
+ container = create_sync_container(injectables=[ProtoImpl])
82
+
83
+ mock_obj = MagicMock(spec=Proto)
84
+
85
+ with container.override.injectable(target=Proto, new=mock_obj):
86
+ assert container.get(Proto) is mock_obj
87
+
88
+ assert isinstance(container.get(Proto), ProtoImpl)
89
+
90
+
91
+ def test_override_as_type_indirect():
92
+ container = create_sync_container(injectables=[ProtoImpl, ServiceDependsOnProto])
93
+
94
+ mock_obj = MagicMock(spec=Proto)
95
+
96
+ with container.override.injectable(target=Proto, new=mock_obj):
97
+ with container.enter_scope() as scope:
98
+ svc = scope.get(ServiceDependsOnProto)
99
+ assert svc.dep is mock_obj
100
+
101
+ with container.enter_scope() as scope:
102
+ svc = scope.get(ServiceDependsOnProto)
103
+ assert isinstance(svc.dep, ProtoImpl)
104
+
105
+
106
+ async def test_override_as_type_indirect_async():
107
+ container = create_async_container(injectables=[ProtoImpl, ServiceDependsOnProto])
108
+
109
+ mock_obj = MagicMock(spec=Proto)
110
+
111
+ with container.override.injectable(target=Proto, new=mock_obj):
112
+ async with container.enter_scope() as scope:
113
+ svc = await scope.get(ServiceDependsOnProto)
114
+ assert svc.dep is mock_obj
115
+
116
+ async with container.enter_scope() as scope:
117
+ svc = await scope.get(ServiceDependsOnProto)
118
+ assert isinstance(svc.dep, ProtoImpl)