mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-10-31 07:57:43 +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. | ||||
|   See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_. | ||||
| - **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>`_. | ||||
| - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | ||||
|   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 | ||||
|   and dictionaries. See :ref:`configuration-provider`. | ||||
| - **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`. | ||||
| - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | ||||
|   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 | ||||
|   and dictionaries. See :ref:`configuration-provider`. | ||||
| - **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`. | ||||
| - **Wiring**. Injects dependencies into functions and methods. Helps integrating with | ||||
|   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  | ||||
| follows `Semantic versioning`_ | ||||
| 
 | ||||
| 4.3.0 | ||||
| ----- | ||||
| - Implement per-function execution scope for ``Resource`` provider in tandem | ||||
|   with ``wiring.Closing``. | ||||
| 
 | ||||
| 4.2.0 | ||||
| ----- | ||||
| - Add support of Python 3.9. | ||||
|  |  | |||
|  | @ -203,4 +203,36 @@ first argument. | |||
|            # 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:: | ||||
|  |  | |||
|  | @ -66,6 +66,10 @@ You can use configuration, provided instance and sub-container providers as you | |||
|    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 | ||||
| -------------------------------- | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										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.""" | ||||
| 
 | ||||
| __version__ = '4.2.0' | ||||
| __version__ = '4.3.0' | ||||
| """Version number. | ||||
| 
 | ||||
| :type: str | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import inspect | |||
| import pkgutil | ||||
| import sys | ||||
| 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): | ||||
|     from typing import GenericMeta | ||||
|  | @ -22,6 +22,7 @@ __all__ = ( | |||
|     'unwire', | ||||
|     'Provide', | ||||
|     'Provider', | ||||
|     'Closing', | ||||
| ) | ||||
| 
 | ||||
| T = TypeVar('T') | ||||
|  | @ -206,10 +207,10 @@ def _patch_fn( | |||
|         fn: Callable[..., Any], | ||||
|         providers_map: ProvidersMap, | ||||
| ) -> None: | ||||
|     injections = _resolve_injections(fn, providers_map) | ||||
|     injections, closing = _resolve_injections(fn, providers_map) | ||||
|     if not injections: | ||||
|         return | ||||
|     setattr(module, name, _patch_with_injections(fn, injections)) | ||||
|     setattr(module, name, _patch_with_injections(fn, injections, closing)) | ||||
| 
 | ||||
| 
 | ||||
| def _unpatch_fn( | ||||
|  | @ -222,25 +223,37 @@ def _unpatch_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) | ||||
| 
 | ||||
|     injections = {} | ||||
|     closing = [] | ||||
|     for parameter_name, parameter in signature.parameters.items(): | ||||
|         if not isinstance(parameter.default, _Marker): | ||||
|             continue | ||||
|         marker = parameter.default | ||||
| 
 | ||||
|         closing_modifier = False | ||||
|         if isinstance(marker, Closing): | ||||
|             closing_modifier = True | ||||
|             marker = marker.provider | ||||
| 
 | ||||
|         provider = providers_map.resolve_provider(marker.provider) | ||||
|         if provider is None: | ||||
|             continue | ||||
| 
 | ||||
|         if closing_modifier: | ||||
|             closing.append(provider) | ||||
| 
 | ||||
|         if isinstance(marker, Provide): | ||||
|             injections[parameter_name] = provider | ||||
|         elif isinstance(marker, Provider): | ||||
|             injections[parameter_name] = provider.provider | ||||
| 
 | ||||
|     return injections | ||||
|     return injections, closing | ||||
| 
 | ||||
| 
 | ||||
| def _fetch_modules(package): | ||||
|  | @ -258,7 +271,7 @@ def _is_method(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): | ||||
|         @functools.wraps(fn) | ||||
|         async def _patched(*args, **kwargs): | ||||
|  | @ -268,7 +281,13 @@ def _patch_with_injections(fn, injections): | |||
| 
 | ||||
|             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: | ||||
|         @functools.wraps(fn) | ||||
|         def _patched(*args, **kwargs): | ||||
|  | @ -278,11 +297,18 @@ def _patch_with_injections(fn, injections): | |||
| 
 | ||||
|             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.__original__ = fn | ||||
|     _patched.__injections__ = injections | ||||
|     _patched.__closing__ = [] | ||||
| 
 | ||||
|     return _patched | ||||
| 
 | ||||
|  | @ -322,3 +348,7 @@ class Provide(_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.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