From c5f799a1ec00153b648a815bf8e34aee5f2e9244 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Fri, 30 Oct 2020 16:40:27 -0400 Subject: [PATCH 1/4] Fix issue with wiring and resource initialization --- src/dependency_injector/wiring.py | 42 ++++++++++--------- .../samples/wiringsamples/resourceclosing.py | 5 +++ tests/unit/wiring/test_wiring_py36.py | 22 ++++++++++ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/dependency_injector/wiring.py b/src/dependency_injector/wiring.py index 1b7c8f2d..c4dc1298 100644 --- a/src/dependency_injector/wiring.py +++ b/src/dependency_injector/wiring.py @@ -5,7 +5,7 @@ import inspect import pkgutil import sys from types import ModuleType -from typing import Optional, Iterable, Callable, Any, Tuple, List, Dict, Generic, TypeVar, cast +from typing import Optional, Iterable, Callable, Any, Tuple, Dict, Generic, TypeVar, cast if sys.version_info < (3, 7): from typing import GenericMeta @@ -226,11 +226,11 @@ def _unpatch_fn( def _resolve_injections( fn: Callable[..., Any], providers_map: ProvidersMap, -) -> Tuple[Dict[str, Any], List[Any]]: +) -> Tuple[Dict[str, Any], Dict[str, Any]]: signature = inspect.signature(fn) injections = {} - closing = [] + closing = {} for parameter_name, parameter in signature.parameters.items(): if not isinstance(parameter.default, _Marker): continue @@ -246,7 +246,7 @@ def _resolve_injections( continue if closing_modifier: - closing.append(provider) + closing[parameter_name] = provider if isinstance(marker, Provide): injections[parameter_name] = provider @@ -275,40 +275,44 @@ def _patch_with_injections(fn, injections, closing): if inspect.iscoroutinefunction(fn): @functools.wraps(fn) async def _patched(*args, **kwargs): - to_inject = {} + to_inject = kwargs.copy() for injection, provider in injections.items(): - to_inject[injection] = provider() - - to_inject.update(kwargs) + if injection not in kwargs: + to_inject[injection] = provider() result = await fn(*args, **to_inject) - for provider in closing: - if isinstance(provider, providers.Resource): - provider.shutdown() + for injection, provider in closing.items(): + if injection in kwargs: + continue + if not isinstance(provider, providers.Resource): + continue + provider.shutdown() return result else: @functools.wraps(fn) def _patched(*args, **kwargs): - to_inject = {} + to_inject = kwargs.copy() for injection, provider in injections.items(): - to_inject[injection] = provider() - - to_inject.update(kwargs) + if injection not in kwargs: + to_inject[injection] = provider() result = fn(*args, **to_inject) - for provider in closing: - if isinstance(provider, providers.Resource): - provider.shutdown() + for injection, provider in closing.items(): + if injection in kwargs: + continue + if not isinstance(provider, providers.Resource): + continue + provider.shutdown() return result _patched.__wired__ = True _patched.__original__ = fn _patched.__injections__ = injections - _patched.__closing__ = [] + _patched.__closing__ = closing return _patched diff --git a/tests/unit/samples/wiringsamples/resourceclosing.py b/tests/unit/samples/wiringsamples/resourceclosing.py index 33a160ca..f7f35bd1 100644 --- a/tests/unit/samples/wiringsamples/resourceclosing.py +++ b/tests/unit/samples/wiringsamples/resourceclosing.py @@ -6,6 +6,11 @@ class Service: init_counter: int = 0 shutdown_counter: int = 0 + @classmethod + def reset_counter(cls): + cls.init_counter = 0 + cls.shutdown_counter = 0 + @classmethod def init(cls): cls.init_counter += 1 diff --git a/tests/unit/wiring/test_wiring_py36.py b/tests/unit/wiring/test_wiring_py36.py index f5b1a509..d8e78c1d 100644 --- a/tests/unit/wiring/test_wiring_py36.py +++ b/tests/unit/wiring/test_wiring_py36.py @@ -178,6 +178,8 @@ class WiringTest(unittest.TestCase): def test_closing_resource(self): from wiringsamples import resourceclosing + resourceclosing.Service.reset_counter() + container = resourceclosing.Container() container.wire(modules=[resourceclosing]) self.addCleanup(container.unwire) @@ -193,3 +195,23 @@ class WiringTest(unittest.TestCase): self.assertEqual(result_2.shutdown_counter, 2) self.assertIsNot(result_1, result_2) + + def test_closing_resource_context(self): + from wiringsamples import resourceclosing + + resourceclosing.Service.reset_counter() + service = resourceclosing.Service() + + container = resourceclosing.Container() + container.wire(modules=[resourceclosing]) + self.addCleanup(container.unwire) + + result_1 = resourceclosing.test_function(service=service) + self.assertIs(result_1, service) + self.assertEqual(result_1.init_counter, 0) + self.assertEqual(result_1.shutdown_counter, 0) + + result_2 = resourceclosing.test_function(service=service) + self.assertIs(result_2, service) + self.assertEqual(result_2.init_counter, 0) + self.assertEqual(result_2.shutdown_counter, 0) From 63977a73b2f267756feff4af0dc87c59db3052e2 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Fri, 30 Oct 2020 16:47:26 -0400 Subject: [PATCH 2/4] Refactor wiring to reduce complexity --- src/dependency_injector/wiring.py | 77 +++++++++++++++++-------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/src/dependency_injector/wiring.py b/src/dependency_injector/wiring.py index c4dc1298..555ebb1d 100644 --- a/src/dependency_injector/wiring.py +++ b/src/dependency_injector/wiring.py @@ -273,41 +273,9 @@ def _is_method(member): def _patch_with_injections(fn, injections, closing): if inspect.iscoroutinefunction(fn): - @functools.wraps(fn) - async def _patched(*args, **kwargs): - to_inject = kwargs.copy() - for injection, provider in injections.items(): - if injection not in kwargs: - to_inject[injection] = provider() - - result = await fn(*args, **to_inject) - - for injection, provider in closing.items(): - if injection in kwargs: - continue - if not isinstance(provider, providers.Resource): - continue - provider.shutdown() - - return result + _patched = _get_async_patched(fn, injections, closing) else: - @functools.wraps(fn) - def _patched(*args, **kwargs): - to_inject = kwargs.copy() - for injection, provider in injections.items(): - if injection not in kwargs: - to_inject[injection] = provider() - - result = fn(*args, **to_inject) - - for injection, provider in closing.items(): - if injection in kwargs: - continue - if not isinstance(provider, providers.Resource): - continue - provider.shutdown() - - return result + _patched = _get_patched(fn, injections, closing) _patched.__wired__ = True _patched.__original__ = fn @@ -317,6 +285,47 @@ def _patch_with_injections(fn, injections, closing): return _patched +def _get_patched(fn, injections, closing): + def _patched(*args, **kwargs): + to_inject = kwargs.copy() + for injection, provider in injections.items(): + if injection not in kwargs: + to_inject[injection] = provider() + + result = fn(*args, **to_inject) + + for injection, provider in closing.items(): + if injection in kwargs: + continue + if not isinstance(provider, providers.Resource): + continue + provider.shutdown() + + return result + return _patched + + +def _get_async_patched(fn, injections, closing): + @functools.wraps(fn) + async def _patched(*args, **kwargs): + to_inject = kwargs.copy() + for injection, provider in injections.items(): + if injection not in kwargs: + to_inject[injection] = provider() + + result = await fn(*args, **to_inject) + + for injection, provider in closing.items(): + if injection in kwargs: + continue + if not isinstance(provider, providers.Resource): + continue + provider.shutdown() + + return result + return _patched + + def _is_patched(fn): return getattr(fn, '__wired__', False) is True From e4e77d0cdbf55394022862befa9a87949794b371 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Fri, 30 Oct 2020 16:55:37 -0400 Subject: [PATCH 3/4] Fix wiring decorator --- src/dependency_injector/wiring.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dependency_injector/wiring.py b/src/dependency_injector/wiring.py index 555ebb1d..f169a7e8 100644 --- a/src/dependency_injector/wiring.py +++ b/src/dependency_injector/wiring.py @@ -286,6 +286,7 @@ def _patch_with_injections(fn, injections, closing): def _get_patched(fn, injections, closing): + @functools.wraps(fn) def _patched(*args, **kwargs): to_inject = kwargs.copy() for injection, provider in injections.items(): From b0d3f08aa619ed62715bb799ec90d51756a39020 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Fri, 30 Oct 2020 16:56:13 -0400 Subject: [PATCH 4/4] Bump version to 4.3.2 --- docs/main/changelog.rst | 5 +++++ src/dependency_injector/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 14e3b652..e22993d1 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -7,6 +7,11 @@ that were made in every particular version. From version 0.7.6 *Dependency Injector* framework strictly follows `Semantic versioning`_ +4.3.2 +----- +- Fix a bug in ``wiring`` with mistakenly initialized and shutdown resource with ``Closing`` + marker on context argument providing. + 4.3.1 ----- - Fix README. diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py index dabe6454..af3d33b5 100644 --- a/src/dependency_injector/__init__.py +++ b/src/dependency_injector/__init__.py @@ -1,6 +1,6 @@ """Top-level package.""" -__version__ = '4.3.1' +__version__ = '4.3.2' """Version number. :type: str