mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-11-04 09:57:37 +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