mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-25 11:04:01 +03:00
Closing wiring marker (#315)
* Add closing marker * Add example * Fix flake8 errors * Add test * Update docs and README
This commit is contained in:
parent
b18385a867
commit
707446a70f
|
@ -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`.
|
||||
|
|
|
@ -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()
|
|
@ -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