mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 09:36:48 +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.
|
- **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`.
|
||||||
|
|
|
@ -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()
|
|
@ -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