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.
- {wireup-2.4.0 → wireup-2.6.0}/.vscode/settings.json +1 -0
- {wireup-2.4.0 → wireup-2.6.0}/PKG-INFO +4 -22
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/configuration.md +38 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/index.md +2 -1
- {wireup-2.4.0 → wireup-2.6.0}/pyproject.toml +1 -1
- {wireup-2.4.0 → wireup-2.6.0}/readme.md +3 -21
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/test_flask_integration.py +33 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_as_type.py +77 -0
- wireup-2.6.0/test/unit/test_concurrency.py +85 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_override.py +23 -0
- wireup-2.6.0/test/unit/test_container_override_extended.py +118 -0
- wireup-2.6.0/test/unit/test_parameter.py +182 -0
- {wireup-2.4.0 → wireup-2.6.0}/uv.lock +1 -1
- wireup-2.4.0/z_test_codegen.py → wireup-2.6.0/wireup/codegen.py +2 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/errors.py +11 -2
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/aiohttp.py +4 -4
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/fastapi.py +1 -2
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/flask.py +7 -6
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/configuration.py +32 -4
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/__init__.py +1 -1
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/base_container.py +9 -0
- wireup-2.6.0/wireup/ioc/container/lock_registry.py +20 -0
- wireup-2.6.0/wireup/ioc/factory_compiler.py +256 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/override_manager.py +14 -3
- wireup-2.6.0/z_test_bench.py +103 -0
- wireup-2.6.0/z_test_repro.py +39 -0
- wireup-2.6.0/z_test_repro_fastapi_race.py +123 -0
- wireup-2.6.0/z_test_repro_wireup_race.py +120 -0
- wireup-2.4.0/test/unit/test_parameter.py +0 -52
- wireup-2.4.0/wireup/ioc/factory_compiler.py +0 -175
- wireup-2.4.0/z_test_CHEATSHEET.md +0 -476
- wireup-2.4.0/z_test_abstract.py +0 -19
- wireup-2.4.0/z_test_benchmark.py +0 -126
- wireup-2.4.0/z_test_binds.py +0 -92
- wireup-2.4.0/z_test_blog_post.md +0 -169
- wireup-2.4.0/z_test_circular.py +0 -26
- wireup-2.4.0/z_test_err.md +0 -56
- wireup-2.4.0/z_test_fastapi-zero-overhead.md +0 -181
- wireup-2.4.0/z_test_get.py +0 -39
- wireup-2.4.0/z_test_inheritance.py +0 -42
- wireup-2.4.0/z_test_instance.py +0 -17
- wireup-2.4.0/z_test_interfaces.py +0 -52
- wireup-2.4.0/z_test_isolated.py +0 -31
- wireup-2.4.0/z_test_mapping.py +0 -13
- wireup-2.4.0/z_test_mermaid.py +0 -103
- wireup-2.4.0/z_test_new.py +0 -20
- wireup-2.4.0/z_test_optional.py +0 -57
- wireup-2.4.0/z_test_override.py +0 -27
- wireup-2.4.0/z_test_param.py +0 -7
- wireup-2.4.0/z_test_param_output.txt +0 -4
- wireup-2.4.0/z_test_param_output_2.txt +0 -20
- wireup-2.4.0/z_test_param_output_3.txt +0 -4
- wireup-2.4.0/z_test_protocol_mypy.py +0 -33
- wireup-2.4.0/z_test_py_ver.py +0 -3
- wireup-2.4.0/z_test_pydantic.py +0 -31
- wireup-2.4.0/z_test_repo_summary.md +0 -162
- wireup-2.4.0/z_test_repro.py +0 -51
- wireup-2.4.0/z_test_repro_results_2.txt +0 -5
- wireup-2.4.0/z_test_roadmap.md +0 -2
- wireup-2.4.0/z_test_runtime_checkable.py +0 -14
- wireup-2.4.0/z_test_thing.py +0 -12
- {wireup-2.4.0 → wireup-2.6.0}/.agent/rules.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/.agent/workflows/context.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/.github/contributing.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/.github/workflows/run_all.yml +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/.github/workflows/run_integration_tests.yml +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/.gitignore +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/.vscode/launch.json +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/Makefile +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/mkdocs.yml +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/overrides/main.html +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/aiohttp_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/click_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/config_store.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/django_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/fastapi_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/flask_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/override_manager.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/starlette_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/class/wireup.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/container.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/factories.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/function_injection.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/getting_started.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/injectables.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/aiohttp/class_based_handlers.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/aiohttp/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/click/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/django/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/fastapi/class_based_handlers.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/fastapi/direct_container_access.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/fastapi/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/flask/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/integrations/starlette/index.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/interfaces.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/lifetimes_and_scopes.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/resources.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/stylesheets/extra.css +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/testing.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/upgrading.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/docs/pages/versioning.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/license.md +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/profile_tests.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/conftest.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/handler.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/routes.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/services.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/aiohttp/test_aiohttp_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/click/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/click/test_click_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/app.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_1/views.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/app.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/app_2/views.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/app.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/drf_app/views.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/app.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/apps/ninja_app/views.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/factory/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/factory/factories.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/injectable/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/injectable/current_request_service.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/injectable/random_service.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/templates/bar.html +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/templates/foo.html +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/test_django_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/django/view.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/cbr.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/router.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/services.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/test_fastapi_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/fastapi/wireup_route.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/bp.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/services/factories.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/flask/services/is_test_service.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/starlette/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/integration/starlette/test_starlette_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/shared/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/greeter.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/rand.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/shared/shared_services/scoped.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/service_refs/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/abstract_multiple_bases.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/inheritance_test/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/inheritance_test/base.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/random/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/random/random_service.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/no_annotations/random/truly_random_service.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/with_annotations/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/with_annotations/env/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/services/with_annotations/services.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_core.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_creation.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_generator_factory.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_container_scope.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_eval_type.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_exit_stack.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_inject_from_container.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_inject_from_container_optional_types.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_inject_from_container_stringified_types.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_module_loading.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_optional_type.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_service_deprecation.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_service_registry.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_signature_hiding.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_type_analysis.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/test_util.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/test/unit/util.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/tox.ini +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/_annotations.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/_decorators.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/_discovery.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/click.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/django/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/django/apps.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/django/decorators.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/integration/starlette.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/_exit_stack.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/async_container.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/container/sync_container.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/registry.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/type_analysis.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/types.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/ioc/util.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/py.typed +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/wireup/util.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/js_stack.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_cit_code1.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_cit_code2.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_cit_palindrome.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_citadel_oa1.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_js_code1.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_prep/z_test_js_code2.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_services/bar.py +0 -0
- {wireup-2.4.0 → wireup-2.6.0}/z_test_services/foo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wireup
|
|
3
|
-
Version: 2.
|
|
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
|
[](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
|
|
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
|
[](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
|
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
[](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
|
|
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)
|