mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-11-01 00:17:55 +03:00 
			
		
		
		
	Merge branch 'release/4.3.0' into master
This commit is contained in:
		
						commit
						84b0029494
					
				|  | @ -65,7 +65,7 @@ Key features of the ``Dependency Injector``: | ||||||
| - **Containers**. Provides declarative and dynamic containers. | - **Containers**. Provides declarative and dynamic containers. | ||||||
|   See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_. |   See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_. | ||||||
| - **Resources**. Helps with initialization and configuring of logging, event loop, thread | - **Resources**. Helps with initialization and configuring of logging, event loop, thread | ||||||
|   or process pool, etc. |   or process pool, etc. Can be used for per-function execution scope in tandem with wiring. | ||||||
|   See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_. |   See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_. | ||||||
| - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | ||||||
|   other frameworks: Django, Flask, Aiohttp, etc. |   other frameworks: Django, Flask, Aiohttp, etc. | ||||||
|  |  | ||||||
|  | @ -73,7 +73,8 @@ Key features of the ``Dependency Injector``: | ||||||
| - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables | - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables | ||||||
|   and dictionaries. See :ref:`configuration-provider`. |   and dictionaries. See :ref:`configuration-provider`. | ||||||
| - **Resources**. Helps with initialization and configuring of logging, event loop, thread | - **Resources**. Helps with initialization and configuring of logging, event loop, thread | ||||||
|   or process pool, etc. See :ref:`resource-provider`. |   or process pool, etc. Can be used for per-function execution scope in tandem with wiring. | ||||||
|  |   See :ref:`resource-provider`. | ||||||
| - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. | - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. | ||||||
| - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | ||||||
|   other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`. |   other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`. | ||||||
|  |  | ||||||
|  | @ -19,7 +19,8 @@ Key features of the ``Dependency Injector``: | ||||||
| - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables | - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables | ||||||
|   and dictionaries. See :ref:`configuration-provider`. |   and dictionaries. See :ref:`configuration-provider`. | ||||||
| - **Resources**. Helps with initialization and configuring of logging, event loop, thread | - **Resources**. Helps with initialization and configuring of logging, event loop, thread | ||||||
|   or process pool, etc. See :ref:`resource-provider`. |   or process pool, etc. Can be used for per-function execution scope in tandem with wiring. | ||||||
|  |   See :ref:`resource-provider`. | ||||||
| - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. | - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. | ||||||
| - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | ||||||
|   other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`. |   other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`. | ||||||
|  |  | ||||||
|  | @ -7,6 +7,11 @@ that were made in every particular version. | ||||||
| From version 0.7.6 *Dependency Injector* framework strictly  | From version 0.7.6 *Dependency Injector* framework strictly  | ||||||
| follows `Semantic versioning`_ | follows `Semantic versioning`_ | ||||||
| 
 | 
 | ||||||
|  | 4.3.0 | ||||||
|  | ----- | ||||||
|  | - Implement per-function execution scope for ``Resource`` provider in tandem | ||||||
|  |   with ``wiring.Closing``. | ||||||
|  | 
 | ||||||
| 4.2.0 | 4.2.0 | ||||||
| ----- | ----- | ||||||
| - Add support of Python 3.9. | - Add support of Python 3.9. | ||||||
|  |  | ||||||
|  | @ -203,4 +203,36 @@ first argument. | ||||||
|            # shutdown |            # shutdown | ||||||
|            ... |            ... | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | .. _resource-provider-wiring-closing: | ||||||
|  | 
 | ||||||
|  | Resources, wiring and per-function execution scope | ||||||
|  | -------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | You can compound ``Resource`` provider with :ref:`wiring` to implement per-function | ||||||
|  | execution scope. For doing this you need to use additional ``Closing`` marker from | ||||||
|  | ``wiring`` module. | ||||||
|  | 
 | ||||||
|  | .. literalinclude:: ../../examples/wiring/flask_resource_closing.py | ||||||
|  |    :language: python | ||||||
|  |    :lines: 3- | ||||||
|  |    :emphasize-lines: 23 | ||||||
|  | 
 | ||||||
|  | Framework initializes and injects the resource into the function. With the ``Closing`` marker | ||||||
|  | framework calls resource ``shutdown()`` method when function execution is over. | ||||||
|  | 
 | ||||||
|  | The example above produces next output: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    Init service | ||||||
|  |    Shutdown service | ||||||
|  |    127.0.0.1 - - [29/Oct/2020 22:39:40] "GET / HTTP/1.1" 200 - | ||||||
|  |    Init service | ||||||
|  |    Shutdown service | ||||||
|  |    127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 - | ||||||
|  |    Init service | ||||||
|  |    Shutdown service | ||||||
|  |    127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 - | ||||||
|  | 
 | ||||||
| .. disqus:: | .. disqus:: | ||||||
|  |  | ||||||
|  | @ -66,6 +66,10 @@ You can use configuration, provided instance and sub-container providers as you | ||||||
|    def foo(bar: Bar = Provide[Container.subcontainer.bar]): |    def foo(bar: Bar = Provide[Container.subcontainer.bar]): | ||||||
|        ... |        ... | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | You can compound wiring and ``Resource`` provider to implement per-function execution scope. | ||||||
|  | See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for details. | ||||||
|  | 
 | ||||||
| Wiring with modules and packages | Wiring with modules and packages | ||||||
| -------------------------------- | -------------------------------- | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										39
									
								
								examples/wiring/flask_resource_closing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								examples/wiring/flask_resource_closing.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | """`Resource` - Flask request scope example.""" | ||||||
|  | 
 | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | from dependency_injector import containers, providers | ||||||
|  | from dependency_injector.wiring import Provide, Closing | ||||||
|  | from flask import Flask, current_app | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Service: | ||||||
|  |     ... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def init_service() -> Service: | ||||||
|  |     print('Init service') | ||||||
|  |     yield Service() | ||||||
|  |     print('Shutdown service') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |     service = providers.Resource(init_service) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def index_view(service: Service = Closing[Provide[Container.service]]): | ||||||
|  |     assert service is current_app.container.service() | ||||||
|  |     return 'Hello  World!' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | container = Container() | ||||||
|  | container.wire(modules=[sys.modules[__name__]]) | ||||||
|  | 
 | ||||||
|  | app = Flask(__name__) | ||||||
|  | app.container = container | ||||||
|  | app.add_url_rule('/', 'index', view_func=index_view) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     app.run() | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| """Top-level package.""" | """Top-level package.""" | ||||||
| 
 | 
 | ||||||
| __version__ = '4.2.0' | __version__ = '4.3.0' | ||||||
| """Version number. | """Version number. | ||||||
| 
 | 
 | ||||||
| :type: str | :type: str | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import inspect | ||||||
| import pkgutil | import pkgutil | ||||||
| import sys | import sys | ||||||
| from types import ModuleType | from types import ModuleType | ||||||
| from typing import Optional, Iterable, Callable, Any, Dict, Generic, TypeVar, cast | from typing import Optional, Iterable, Callable, Any, Tuple, List, Dict, Generic, TypeVar, cast | ||||||
| 
 | 
 | ||||||
| if sys.version_info < (3, 7): | if sys.version_info < (3, 7): | ||||||
|     from typing import GenericMeta |     from typing import GenericMeta | ||||||
|  | @ -22,6 +22,7 @@ __all__ = ( | ||||||
|     'unwire', |     'unwire', | ||||||
|     'Provide', |     'Provide', | ||||||
|     'Provider', |     'Provider', | ||||||
|  |     'Closing', | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| T = TypeVar('T') | T = TypeVar('T') | ||||||
|  | @ -206,10 +207,10 @@ def _patch_fn( | ||||||
|         fn: Callable[..., Any], |         fn: Callable[..., Any], | ||||||
|         providers_map: ProvidersMap, |         providers_map: ProvidersMap, | ||||||
| ) -> None: | ) -> None: | ||||||
|     injections = _resolve_injections(fn, providers_map) |     injections, closing = _resolve_injections(fn, providers_map) | ||||||
|     if not injections: |     if not injections: | ||||||
|         return |         return | ||||||
|     setattr(module, name, _patch_with_injections(fn, injections)) |     setattr(module, name, _patch_with_injections(fn, injections, closing)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _unpatch_fn( | def _unpatch_fn( | ||||||
|  | @ -222,25 +223,37 @@ def _unpatch_fn( | ||||||
|     setattr(module, name, _get_original_from_patched(fn)) |     setattr(module, name, _get_original_from_patched(fn)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _resolve_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> Dict[str, Any]: | def _resolve_injections( | ||||||
|  |         fn: Callable[..., Any], | ||||||
|  |         providers_map: ProvidersMap, | ||||||
|  | ) -> Tuple[Dict[str, Any], List[Any]]: | ||||||
|     signature = inspect.signature(fn) |     signature = inspect.signature(fn) | ||||||
| 
 | 
 | ||||||
|     injections = {} |     injections = {} | ||||||
|  |     closing = [] | ||||||
|     for parameter_name, parameter in signature.parameters.items(): |     for parameter_name, parameter in signature.parameters.items(): | ||||||
|         if not isinstance(parameter.default, _Marker): |         if not isinstance(parameter.default, _Marker): | ||||||
|             continue |             continue | ||||||
|         marker = parameter.default |         marker = parameter.default | ||||||
| 
 | 
 | ||||||
|  |         closing_modifier = False | ||||||
|  |         if isinstance(marker, Closing): | ||||||
|  |             closing_modifier = True | ||||||
|  |             marker = marker.provider | ||||||
|  | 
 | ||||||
|         provider = providers_map.resolve_provider(marker.provider) |         provider = providers_map.resolve_provider(marker.provider) | ||||||
|         if provider is None: |         if provider is None: | ||||||
|             continue |             continue | ||||||
| 
 | 
 | ||||||
|  |         if closing_modifier: | ||||||
|  |             closing.append(provider) | ||||||
|  | 
 | ||||||
|         if isinstance(marker, Provide): |         if isinstance(marker, Provide): | ||||||
|             injections[parameter_name] = provider |             injections[parameter_name] = provider | ||||||
|         elif isinstance(marker, Provider): |         elif isinstance(marker, Provider): | ||||||
|             injections[parameter_name] = provider.provider |             injections[parameter_name] = provider.provider | ||||||
| 
 | 
 | ||||||
|     return injections |     return injections, closing | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _fetch_modules(package): | def _fetch_modules(package): | ||||||
|  | @ -258,7 +271,7 @@ def _is_method(member): | ||||||
|     return inspect.ismethod(member) or inspect.isfunction(member) |     return inspect.ismethod(member) or inspect.isfunction(member) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _patch_with_injections(fn, injections): | def _patch_with_injections(fn, injections, closing): | ||||||
|     if inspect.iscoroutinefunction(fn): |     if inspect.iscoroutinefunction(fn): | ||||||
|         @functools.wraps(fn) |         @functools.wraps(fn) | ||||||
|         async def _patched(*args, **kwargs): |         async def _patched(*args, **kwargs): | ||||||
|  | @ -268,7 +281,13 @@ def _patch_with_injections(fn, injections): | ||||||
| 
 | 
 | ||||||
|             to_inject.update(kwargs) |             to_inject.update(kwargs) | ||||||
| 
 | 
 | ||||||
|             return await fn(*args, **to_inject) |             result = await fn(*args, **to_inject) | ||||||
|  | 
 | ||||||
|  |             for provider in closing: | ||||||
|  |                 if isinstance(provider, providers.Resource): | ||||||
|  |                     provider.shutdown() | ||||||
|  | 
 | ||||||
|  |             return result | ||||||
|     else: |     else: | ||||||
|         @functools.wraps(fn) |         @functools.wraps(fn) | ||||||
|         def _patched(*args, **kwargs): |         def _patched(*args, **kwargs): | ||||||
|  | @ -278,11 +297,18 @@ def _patch_with_injections(fn, injections): | ||||||
| 
 | 
 | ||||||
|             to_inject.update(kwargs) |             to_inject.update(kwargs) | ||||||
| 
 | 
 | ||||||
|             return fn(*args, **to_inject) |             result = fn(*args, **to_inject) | ||||||
|  | 
 | ||||||
|  |             for provider in closing: | ||||||
|  |                 if isinstance(provider, providers.Resource): | ||||||
|  |                     provider.shutdown() | ||||||
|  | 
 | ||||||
|  |             return result | ||||||
| 
 | 
 | ||||||
|     _patched.__wired__ = True |     _patched.__wired__ = True | ||||||
|     _patched.__original__ = fn |     _patched.__original__ = fn | ||||||
|     _patched.__injections__ = injections |     _patched.__injections__ = injections | ||||||
|  |     _patched.__closing__ = [] | ||||||
| 
 | 
 | ||||||
|     return _patched |     return _patched | ||||||
| 
 | 
 | ||||||
|  | @ -322,3 +348,7 @@ class Provide(_Marker): | ||||||
| 
 | 
 | ||||||
| class Provider(_Marker): | class Provider(_Marker): | ||||||
|     ... |     ... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Closing(_Marker): | ||||||
|  |     ... | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								tests/unit/samples/wiringsamples/resourceclosing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tests/unit/samples/wiringsamples/resourceclosing.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | from dependency_injector import containers, providers | ||||||
|  | from dependency_injector.wiring import Provide, Closing | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Service: | ||||||
|  |     init_counter: int = 0 | ||||||
|  |     shutdown_counter: int = 0 | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def init(cls): | ||||||
|  |         cls.init_counter += 1 | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def shutdown(cls): | ||||||
|  |         cls.shutdown_counter += 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def init_service(): | ||||||
|  |     service = Service() | ||||||
|  |     service.init() | ||||||
|  |     yield service | ||||||
|  |     service.shutdown() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Container(containers.DeclarativeContainer): | ||||||
|  | 
 | ||||||
|  |     service = providers.Resource(init_service) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_function(service: Service = Closing[Provide[Container.service]]): | ||||||
|  |     return service | ||||||
|  | @ -174,3 +174,22 @@ class WiringTest(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|         self.assertIsInstance(service, Service) |         self.assertIsInstance(service, Service) | ||||||
|         self.assertEqual(some_value, 1) |         self.assertEqual(some_value, 1) | ||||||
|  | 
 | ||||||
|  |     def test_closing_resource(self): | ||||||
|  |         from wiringsamples import resourceclosing | ||||||
|  | 
 | ||||||
|  |         container = resourceclosing.Container() | ||||||
|  |         container.wire(modules=[resourceclosing]) | ||||||
|  |         self.addCleanup(container.unwire) | ||||||
|  | 
 | ||||||
|  |         result_1 = resourceclosing.test_function() | ||||||
|  |         self.assertIsInstance(result_1, resourceclosing.Service) | ||||||
|  |         self.assertEqual(result_1.init_counter, 1) | ||||||
|  |         self.assertEqual(result_1.shutdown_counter, 1) | ||||||
|  | 
 | ||||||
|  |         result_2 = resourceclosing.test_function() | ||||||
|  |         self.assertIsInstance(result_2, resourceclosing.Service) | ||||||
|  |         self.assertEqual(result_2.init_counter, 2) | ||||||
|  |         self.assertEqual(result_2.shutdown_counter, 2) | ||||||
|  | 
 | ||||||
|  |         self.assertIsNot(result_1, result_2) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user