wireup 2.4.0__tar.gz → 2.5.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.5.0}/.vscode/settings.json +1 -0
- {wireup-2.4.0 → wireup-2.5.0}/PKG-INFO +4 -22
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/index.md +2 -1
- {wireup-2.4.0 → wireup-2.5.0}/pyproject.toml +1 -1
- {wireup-2.4.0 → wireup-2.5.0}/readme.md +3 -21
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_as_type.py +77 -0
- wireup-2.5.0/test/unit/test_concurrency.py +85 -0
- wireup-2.4.0/z_test_codegen.py → wireup-2.5.0/wireup/codegen.py +2 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/aiohttp.py +4 -4
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/fastapi.py +1 -2
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/container/__init__.py +1 -1
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/container/base_container.py +9 -0
- wireup-2.5.0/wireup/ioc/container/lock_registry.py +20 -0
- wireup-2.5.0/wireup/ioc/factory_compiler.py +245 -0
- wireup-2.5.0/z_test_bench.py +103 -0
- wireup-2.5.0/z_test_repro_fastapi_race.py +123 -0
- wireup-2.5.0/z_test_repro_wireup_race.py +120 -0
- 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.5.0}/.agent/rules.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/.agent/workflows/context.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/.github/contributing.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/.github/workflows/run_all.yml +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/.github/workflows/run_integration_tests.yml +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/.gitignore +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/.vscode/launch.json +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/Makefile +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/mkdocs.yml +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/overrides/main.html +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/aiohttp_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/click_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/config_store.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/django_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/fastapi_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/flask_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/override_manager.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/starlette_integration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/class/wireup.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/configuration.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/container.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/factories.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/function_injection.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/getting_started.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/injectables.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/aiohttp/class_based_handlers.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/aiohttp/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/click/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/django/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/fastapi/class_based_handlers.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/fastapi/direct_container_access.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/fastapi/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/flask/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/integrations/starlette/index.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/interfaces.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/lifetimes_and_scopes.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/resources.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/stylesheets/extra.css +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/testing.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/upgrading.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/docs/pages/versioning.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/license.md +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/profile_tests.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/conftest.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/aiohttp/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/aiohttp/handler.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/aiohttp/routes.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/aiohttp/services.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/aiohttp/test_aiohttp_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/click/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/click/test_click_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_1/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_1/app.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_1/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_1/views.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_2/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_2/app.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_2/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/app_2/views.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/drf_app/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/drf_app/app.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/drf_app/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/drf_app/views.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/ninja_app/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/ninja_app/app.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/ninja_app/urls.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/apps/ninja_app/views.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/factory/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/factory/factories.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/injectable/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/injectable/current_request_service.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/injectable/random_service.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/templates/bar.html +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/templates/foo.html +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/test_django_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/django/view.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/fastapi/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/fastapi/cbr.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/fastapi/router.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/fastapi/services.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/fastapi/test_fastapi_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/fastapi/wireup_route.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/flask/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/flask/bp.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/flask/services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/flask/services/factories.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/flask/services/is_test_service.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/flask/test_flask_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/starlette/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/integration/starlette/test_starlette_integration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/shared/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/shared/shared_services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/shared/shared_services/greeter.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/shared/shared_services/rand.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/shared/shared_services/scoped.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/service_refs/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/abstract_multiple_bases.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/inheritance_test/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/inheritance_test/base.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/no_annotations/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/no_annotations/random/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/no_annotations/random/random_service.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/no_annotations/random/truly_random_service.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/with_annotations/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/with_annotations/env/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/services/with_annotations/services.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_container_core.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_container_creation.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_container_generator_factory.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_container_override.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_container_scope.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_eval_type.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_exit_stack.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_inject_from_container.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_inject_from_container_optional_types.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_inject_from_container_stringified_types.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_module_loading.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_optional_type.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_parameter.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_service_deprecation.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_service_registry.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_signature_hiding.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_type_analysis.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/test_util.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/test/unit/util.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/tox.ini +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/uv.lock +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/_annotations.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/_decorators.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/_discovery.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/errors.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/click.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/django/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/django/apps.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/django/decorators.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/flask.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/integration/starlette.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/_exit_stack.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/configuration.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/container/async_container.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/container/sync_container.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/override_manager.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/registry.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/type_analysis.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/types.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/ioc/util.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/py.typed +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/wireup/util.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/js_stack.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/z_test_cit_code1.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/z_test_cit_code2.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/z_test_cit_palindrome.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/z_test_citadel_oa1.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/z_test_js_code1.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_prep/z_test_js_code2.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_services/__init__.py +0 -0
- {wireup-2.4.0 → wireup-2.5.0}/z_test_services/bar.py +0 -0
- {wireup-2.4.0 → wireup-2.5.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.5.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.
|
|
@@ -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,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)}"
|
|
@@ -76,10 +76,10 @@ def _get_startup_event(
|
|
|
76
76
|
container: wireup.AsyncContainer,
|
|
77
77
|
handlers: Optional[Iterable[Type[_WireupHandler]]],
|
|
78
78
|
) -> Callable[[web.Application], Awaitable[None]]:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
container.
|
|
79
|
+
if handlers:
|
|
80
|
+
for handler_type in handlers:
|
|
81
|
+
container._registry.extend(impls=[InjectableDeclaration(handler_type)])
|
|
82
|
+
container._recompile()
|
|
83
83
|
|
|
84
84
|
async def _inner(app: web.Application) -> None:
|
|
85
85
|
if handlers:
|
|
@@ -166,8 +166,7 @@ def _update_lifespan(
|
|
|
166
166
|
if class_based_routes:
|
|
167
167
|
for cbr in class_based_routes:
|
|
168
168
|
container._registry.extend(impls=[InjectableDeclaration(cbr)])
|
|
169
|
-
container.
|
|
170
|
-
container._scoped_compiler.compile()
|
|
169
|
+
container._recompile()
|
|
171
170
|
|
|
172
171
|
for cbr in class_based_routes:
|
|
173
172
|
await _instantiate_class_based_route(app, container, cbr)
|
|
@@ -93,7 +93,7 @@ def _create_container( # noqa: PLR0913
|
|
|
93
93
|
singleton_compiler = FactoryCompiler(registry, is_scoped_container=False)
|
|
94
94
|
scoped_compiler = FactoryCompiler(registry, is_scoped_container=True)
|
|
95
95
|
singleton_compiler.compile()
|
|
96
|
-
scoped_compiler.compile()
|
|
96
|
+
scoped_compiler.compile(copy_singletons_from=singleton_compiler)
|
|
97
97
|
|
|
98
98
|
override_manager = OverrideManager(registry.is_type_with_qualifier_known, singleton_compiler, scoped_compiler)
|
|
99
99
|
return klass(
|
|
@@ -17,6 +17,7 @@ from wireup.errors import (
|
|
|
17
17
|
UnknownServiceRequestedError,
|
|
18
18
|
WireupError,
|
|
19
19
|
)
|
|
20
|
+
from wireup.ioc.container.lock_registry import LockRegistry
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from wireup.ioc.configuration import ConfigStore
|
|
@@ -40,6 +41,7 @@ class BaseContainer:
|
|
|
40
41
|
"_factories",
|
|
41
42
|
"_global_scope_exit_stack",
|
|
42
43
|
"_global_scope_objects",
|
|
44
|
+
"_locks",
|
|
43
45
|
"_override_mgr",
|
|
44
46
|
"_registry",
|
|
45
47
|
"_scoped_compiler",
|
|
@@ -65,6 +67,7 @@ class BaseContainer:
|
|
|
65
67
|
self._compiler = factory_compiler
|
|
66
68
|
self._scoped_compiler = scoped_compiler
|
|
67
69
|
self._factories = self._compiler.factories
|
|
70
|
+
self._locks = LockRegistry({obj_id for obj_id, f in self._factories.items() if f.is_async})
|
|
68
71
|
|
|
69
72
|
@property
|
|
70
73
|
def params(self) -> ConfigStore:
|
|
@@ -111,3 +114,9 @@ class BaseContainer:
|
|
|
111
114
|
return compiled_factory.factory(self) # type:ignore[no-any-return]
|
|
112
115
|
|
|
113
116
|
raise UnknownServiceRequestedError(klass, qualifier)
|
|
117
|
+
|
|
118
|
+
def _recompile(self) -> None:
|
|
119
|
+
"""Update internal container state after registry changes"""
|
|
120
|
+
self._compiler.compile()
|
|
121
|
+
self._scoped_compiler.compile(copy_singletons_from=self._compiler)
|
|
122
|
+
self._locks._async_hashes.update({obj_id for obj_id, f in self._factories.items() if f.is_async})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LockRegistry(Dict[int, "asyncio.Lock | threading.Lock"]):
|
|
9
|
+
__slots__ = ("_async_hashes", "_lock")
|
|
10
|
+
|
|
11
|
+
def __init__(self, async_hashes: set[int]) -> None:
|
|
12
|
+
super().__init__()
|
|
13
|
+
self._async_hashes = async_hashes
|
|
14
|
+
self._lock = threading.Lock()
|
|
15
|
+
|
|
16
|
+
def __missing__(self, key: int) -> asyncio.Lock | threading.Lock:
|
|
17
|
+
with self._lock:
|
|
18
|
+
if key not in self:
|
|
19
|
+
self[key] = asyncio.Lock() if key in self._async_hashes else threading.Lock()
|
|
20
|
+
return self[key]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable, Hashable
|
|
8
|
+
|
|
9
|
+
from wireup.codegen import Codegen
|
|
10
|
+
from wireup.errors import WireupError
|
|
11
|
+
from wireup.ioc.registry import GENERATOR_FACTORY_TYPES, ContainerRegistry, FactoryType
|
|
12
|
+
from wireup.ioc.types import ConfigInjectionRequest, TemplatedString
|
|
13
|
+
from wireup.util import format_name
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from wireup.ioc.container.base_container import BaseContainer
|
|
17
|
+
from wireup.ioc.registry import InjectableFactory
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(**({"slots": True} if sys.version_info >= (3, 10) else {}))
|
|
21
|
+
class CompiledFactory:
|
|
22
|
+
factory: Callable[[BaseContainer], Any]
|
|
23
|
+
is_async: bool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_CONTAINER_SCOPE_ERROR_MSG = (
|
|
27
|
+
"Scope mismatch: Cannot resolve {lifetime} injectable {fmt_klass} "
|
|
28
|
+
"from the root container. "
|
|
29
|
+
"Only Singleton injectables can be resolved without a scope. "
|
|
30
|
+
"To resolve {lifetime} injectables, you must create a scope.\n"
|
|
31
|
+
"See: https://maldoinc.github.io/wireup/latest/lifetimes_and_scopes/"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
_WIREUP_GENERATED_FACTORY_NAME = "_wireup_factory"
|
|
35
|
+
_SENTINEL = object()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FactoryCompiler:
|
|
39
|
+
def __init__(self, registry: ContainerRegistry, *, is_scoped_container: bool) -> None:
|
|
40
|
+
self._registry = registry
|
|
41
|
+
self._is_scoped_container = is_scoped_container
|
|
42
|
+
self.factories: dict[int, CompiledFactory] = {}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def get_object_id(cls, impl: type, qualifier: Hashable) -> int:
|
|
46
|
+
return hash(impl if qualifier is None else (impl, qualifier))
|
|
47
|
+
|
|
48
|
+
def compile(self, copy_singletons_from: FactoryCompiler | None = None) -> None:
|
|
49
|
+
for impl, qualifiers in self._registry.impls.items():
|
|
50
|
+
for qualifier in qualifiers:
|
|
51
|
+
obj_id = FactoryCompiler.get_object_id(impl, qualifier)
|
|
52
|
+
|
|
53
|
+
if obj_id in self.factories:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
copy_singletons_from
|
|
58
|
+
and (compiled := copy_singletons_from.factories.get(obj_id))
|
|
59
|
+
and self._registry.lifetime[impl, qualifier] == "singleton"
|
|
60
|
+
):
|
|
61
|
+
self.factories[obj_id] = compiled
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
self.factories[obj_id] = self._compile_and_create_function(
|
|
65
|
+
self._registry.factories[impl, qualifier],
|
|
66
|
+
impl,
|
|
67
|
+
qualifier,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
for interface, impls in self._registry.interfaces.items():
|
|
71
|
+
for qualifier, impl in impls.items():
|
|
72
|
+
obj_id = FactoryCompiler.get_object_id(interface, qualifier)
|
|
73
|
+
|
|
74
|
+
if obj_id in self.factories:
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
copy_singletons_from
|
|
79
|
+
and (compiled := copy_singletons_from.factories.get(obj_id))
|
|
80
|
+
and self._registry.lifetime[impl, qualifier] == "singleton"
|
|
81
|
+
):
|
|
82
|
+
self.factories[obj_id] = compiled
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
self.factories[obj_id] = self._compile_and_create_function(
|
|
86
|
+
self._registry.factories[impl, qualifier],
|
|
87
|
+
interface,
|
|
88
|
+
qualifier,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _get_factory_code( # noqa: C901, PLR0915
|
|
92
|
+
self,
|
|
93
|
+
factory: InjectableFactory,
|
|
94
|
+
lifetime: str,
|
|
95
|
+
*,
|
|
96
|
+
is_interface: bool,
|
|
97
|
+
) -> tuple[str, bool, bool]:
|
|
98
|
+
cg = Codegen()
|
|
99
|
+
|
|
100
|
+
maybe_async = "async " if factory.is_async else ""
|
|
101
|
+
cg += f"{maybe_async}def {_WIREUP_GENERATED_FACTORY_NAME}(container):"
|
|
102
|
+
cache_created_instance = lifetime != "transient"
|
|
103
|
+
|
|
104
|
+
def _generate_factory_body(cg: Codegen) -> None:
|
|
105
|
+
kwargs = ""
|
|
106
|
+
for name, dep in self._registry.dependencies[factory.factory].items():
|
|
107
|
+
if isinstance(dep.annotation, ConfigInjectionRequest):
|
|
108
|
+
param_value = (
|
|
109
|
+
str(dep.annotation.config_key)
|
|
110
|
+
if isinstance(dep.annotation.config_key, TemplatedString)
|
|
111
|
+
else f'"{dep.annotation.config_key}"'
|
|
112
|
+
)
|
|
113
|
+
cg += f"_obj_dep_{name} = parameters.get({param_value})"
|
|
114
|
+
else:
|
|
115
|
+
if self._registry.is_interface_known(dep.klass):
|
|
116
|
+
dep_class = self._registry.interface_resolve_impl(dep.klass, dep.qualifier_value)
|
|
117
|
+
else:
|
|
118
|
+
dep_class = dep.klass
|
|
119
|
+
|
|
120
|
+
maybe_await = "await " if self._registry.factories[dep_class, dep.qualifier_value].is_async else ""
|
|
121
|
+
dep_hash = FactoryCompiler.get_object_id(dep_class, dep.qualifier_value)
|
|
122
|
+
cg += f"_obj_dep_{name} = {maybe_await}factories[{dep_hash}].factory(container)"
|
|
123
|
+
kwargs += f"{name}=_obj_dep_{name}, "
|
|
124
|
+
|
|
125
|
+
maybe_await = "await " if factory.factory_type == FactoryType.COROUTINE_FN else ""
|
|
126
|
+
|
|
127
|
+
cg += f"instance = {maybe_await}ORIGINAL_FACTORY({kwargs.strip()})"
|
|
128
|
+
|
|
129
|
+
if factory.factory_type in GENERATOR_FACTORY_TYPES:
|
|
130
|
+
if lifetime == "singleton":
|
|
131
|
+
cg += "container._global_scope_exit_stack.append(instance)"
|
|
132
|
+
else:
|
|
133
|
+
cg += "container._current_scope_exit_stack.append(instance)"
|
|
134
|
+
|
|
135
|
+
if factory.factory_type == FactoryType.GENERATOR:
|
|
136
|
+
cg += "instance = next(instance)"
|
|
137
|
+
else:
|
|
138
|
+
cg += "instance = await instance.__anext__()"
|
|
139
|
+
|
|
140
|
+
if cache_created_instance:
|
|
141
|
+
cg += "storage[OBJ_ID] = instance"
|
|
142
|
+
if is_interface:
|
|
143
|
+
cg += "storage[ORIGINAL_OBJ_ID] = instance"
|
|
144
|
+
|
|
145
|
+
cg += "return instance"
|
|
146
|
+
|
|
147
|
+
with cg.indent():
|
|
148
|
+
if cache_created_instance:
|
|
149
|
+
if lifetime == "singleton":
|
|
150
|
+
cg += "storage = container._global_scope_objects"
|
|
151
|
+
else:
|
|
152
|
+
cg += "storage = container._current_scope_objects"
|
|
153
|
+
|
|
154
|
+
cg += "if (res := storage.get(OBJ_ID, _SENTINEL)) is not _SENTINEL:"
|
|
155
|
+
with cg.indent():
|
|
156
|
+
cg += "return res"
|
|
157
|
+
|
|
158
|
+
lock = "_singleton_lock" if lifetime == "singleton" else "container._locks[OBJ_HASH]"
|
|
159
|
+
|
|
160
|
+
if factory.is_async:
|
|
161
|
+
cg += f"async with {lock}:"
|
|
162
|
+
else:
|
|
163
|
+
cg += f"with {lock}:"
|
|
164
|
+
|
|
165
|
+
with cg.indent():
|
|
166
|
+
# Use Double-Checked locking for factories
|
|
167
|
+
# See: https://en.wikipedia.org/wiki/Double-checked_locking
|
|
168
|
+
cg += "if (res := storage.get(OBJ_ID, _SENTINEL)) is not _SENTINEL:"
|
|
169
|
+
with cg.indent():
|
|
170
|
+
cg += "return res"
|
|
171
|
+
|
|
172
|
+
_generate_factory_body(cg)
|
|
173
|
+
else:
|
|
174
|
+
_generate_factory_body(cg)
|
|
175
|
+
|
|
176
|
+
return cg.get_source(), factory.is_async, lifetime == "singleton"
|
|
177
|
+
|
|
178
|
+
def _compile_and_create_function(
|
|
179
|
+
self, factory: InjectableFactory, impl: type, qualifier: Hashable
|
|
180
|
+
) -> CompiledFactory:
|
|
181
|
+
obj_id = impl, qualifier
|
|
182
|
+
resolved_obj_id = (
|
|
183
|
+
(self._registry.interface_resolve_impl(impl, qualifier), qualifier)
|
|
184
|
+
if self._registry.is_interface_known(impl)
|
|
185
|
+
else obj_id
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
is_interface = self._registry.is_interface_known(impl)
|
|
189
|
+
|
|
190
|
+
if is_interface:
|
|
191
|
+
lifetime = self._registry.lifetime[self._registry.interface_resolve_impl(impl, qualifier), qualifier]
|
|
192
|
+
else:
|
|
193
|
+
lifetime = self._registry.lifetime[impl, qualifier]
|
|
194
|
+
|
|
195
|
+
# Non-singleton types cannot be resolved from the root container. Return a factory that will simply error.
|
|
196
|
+
if lifetime != "singleton" and not self._is_scoped_container:
|
|
197
|
+
return CompiledFactory(
|
|
198
|
+
factory=FactoryCompiler._create_scope_mismatch_error_factory(impl, qualifier, lifetime),
|
|
199
|
+
is_async=False,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
obj_hash = FactoryCompiler.get_object_id(impl, qualifier)
|
|
203
|
+
source, is_async, needs_global_lock = self._get_factory_code(
|
|
204
|
+
factory,
|
|
205
|
+
lifetime,
|
|
206
|
+
is_interface=is_interface,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
namespace: dict[str, Any] = {
|
|
211
|
+
"factories": self.factories,
|
|
212
|
+
"ORIGINAL_OBJ_ID": obj_id,
|
|
213
|
+
"OBJ_ID": resolved_obj_id,
|
|
214
|
+
"OBJ_HASH": obj_hash,
|
|
215
|
+
"ORIGINAL_FACTORY": self._registry.factories[resolved_obj_id].factory,
|
|
216
|
+
"TemplatedString": TemplatedString,
|
|
217
|
+
"WireupError": WireupError,
|
|
218
|
+
"_CONTAINER_SCOPE_ERROR_MSG": _CONTAINER_SCOPE_ERROR_MSG,
|
|
219
|
+
"_SENTINEL": _SENTINEL,
|
|
220
|
+
"parameters": self._registry.parameters,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if needs_global_lock:
|
|
224
|
+
namespace["_singleton_lock"] = asyncio.Lock() if is_async else threading.Lock()
|
|
225
|
+
|
|
226
|
+
compiled_code = compile(source, f"<{_WIREUP_GENERATED_FACTORY_NAME}_{obj_id}>", "exec")
|
|
227
|
+
exec(compiled_code, namespace) # noqa: S102
|
|
228
|
+
|
|
229
|
+
return CompiledFactory(factory=namespace[_WIREUP_GENERATED_FACTORY_NAME], is_async=is_async)
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
msg = f"Failed to compile generated factory {obj_id}: {e}"
|
|
233
|
+
raise WireupError(msg) from e
|
|
234
|
+
|
|
235
|
+
@staticmethod
|
|
236
|
+
def _create_scope_mismatch_error_factory(
|
|
237
|
+
impl: type,
|
|
238
|
+
qualifier: Hashable,
|
|
239
|
+
lifetime: str,
|
|
240
|
+
) -> Callable[[BaseContainer], Any]:
|
|
241
|
+
def _factory(_: BaseContainer) -> Any:
|
|
242
|
+
msg = _CONTAINER_SCOPE_ERROR_MSG.format(fmt_klass=format_name(impl, qualifier), lifetime=lifetime)
|
|
243
|
+
raise WireupError(msg)
|
|
244
|
+
|
|
245
|
+
return _factory
|