python-dependency-injector/docs/wiring.rst
Roman Mogylatov feed916f46
Async resources and injections (#352)
* Add support of async injections into wiring

* Add support of async functions and async generators for resources

* Update resource provider typing stub for stutdown

* Add resource base class for async resources

* Fix tests

* Add tests for async injections in wiring @inject

* Refactor provider tests

* Add tests for async resources

* Rework async resources callbacks to .add_done_callback() style (fixes pypy3 issue)

* Add awaits into async resource class test

* Refactor FastAPI tests

* Implement async resources initialization in container

* Move container async resource tests to a separate module for Python 3.6+

* Fix init async resources in container on Python 2

* Add first dirty async injections implementation

* Fix isawaitable error

* Turm asyncio import to conditional for safer Py2 usage

* Refactor kwargs injections

* Implement positional injections, add tests and make refactoring

* Implement attribute injections and add tests

* Add singleton implementation + tests for all singleton types

* Implement injections in thread-local and thread-safe singleton providers

* Update .provided + fix resource concurent initialization issue

* Implement async mode for Dependency provider

* Add async mode for the provider

* Add overload for Factory typing

* Add typing stubs for async resource

* Refactor abstract* providers __call__()

* Add async mode API + tests

* Add typing stubs & tests for async mode API

* Add tests for async mode auto configuration

* Refactor Provider.__call__() to use async mode api

* Refactor Dependency provider to use async mode api

* Add tests for Dependency provider async mode

* Add support of async mode for FactoryAggregate provider + tests

* Refactor Singleton provider to use async mode api

* Refactor ThreadSafeSingleton provider to use async mode api

* Refactor ThreadLocalSingleton provider to use async mode api

* Finish Singleton refactoring to use async mode api

* Refactor Resource provider to use async mode api

* Add Provider.async_() method + tests

* Add typing stubs for async_() method + tests

* Refactor Singleton typing stubs to return singleton from argument methods

* Refactor provider typing stubs

* Improve resource typing stub

* Add tests for async context kwargs injections

* Fix typo in resource provider tests

* Cover shutdown of not initialized resource

* Add test to cover resource initialization with an error

* Fix Singleton and ThreadLocalSingleton to handle initialization errors

* Add FastAPI + Redis example

* Make cosmetic fixes to FastAPI + Redis example

* Add missing development requirements

* Update module docblock in fastapi + redis example

* Add FastAPI + Redis example docs

* Add references to FastAPI + Redis example

* Refactor resource docs

* Add asynchronous resources docs

* Refactor wiring docs

* Add async injections docs for wiring

* Add async injections page and update docs index, readme, and key features pages

* Add providers async injections example

* Add docs on provider async mode enabling

* Reword async provider docs

* Add provider async mode docs

* Add cross links to async docs

* Mute flake8 errors in async provider examples

* Update changelog

* Make cosmetic fix to containers.pyx
2021-01-10 19:26:15 -05:00

301 lines
7.3 KiB
ReStructuredText

.. _wiring:
Wiring
======
Wiring feature provides a way to inject container providers into the functions and methods.
To use wiring you need:
- **Place @inject decorator**. Decorator ``@inject`` injects the dependencies.
- **Place markers**. Wiring marker specifies what dependency to inject,
e.g. ``Provide[Container.bar]``. This helps container to find the injections.
- **Wire the container with the markers in the code**. Call ``container.wire()``
specifying modules and packages you would like to wire it with.
- **Use functions and classes as you normally do**. Framework will provide specified injections.
.. literalinclude:: ../examples/wiring/example.py
:language: python
:lines: 3-
Markers
-------
Wiring feature uses markers to make injections. Injection marker is specified as a default value of
a function or method argument:
.. code-block:: python
from dependency_injector.wiring import inject, Provide
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
Specifying an annotation is optional.
There are two types of markers:
- ``Provide[foo]`` - call the provider ``foo`` and injects the result
- ``Provider[foo]`` - injects the provider ``foo`` itself
.. code-block:: python
from dependency_injector.wiring import inject, Provider
@inject
def foo(bar_provider: Callable[..., Bar] = Provider[Container.bar]):
bar = bar_provider()
...
You can use configuration, provided instance and sub-container providers as you normally do.
.. code-block:: python
@inject
def foo(token: str = Provide[Container.config.api_token]):
...
@inject
def foo(timeout: int = Provide[Container.config.timeout.as_(int)]):
...
@inject
def foo(baz: Baz = Provide[Container.bar.provided.baz]):
...
@inject
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
--------------------------------
To wire a container with a module you need to call ``container.wire(modules=[...])`` method. Argument
``modules`` is an iterable of the module objects.
.. code-block:: python
from yourapp import module1, module2
container = Container()
container.wire(modules=[module1, module2])
You can wire container with a package. Container walks recursively over package modules.
.. code-block:: python
from yourapp import package1, package2
container = Container()
container.wire(packages=[package1, package2])
Arguments ``modules`` and ``packages`` can be used together.
When wiring is done functions and methods with the markers are patched to provide injections when called.
.. code-block:: python
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
container = Container()
container.wire(modules=[sys.modules[__name__]])
foo() # <--- Argument "bar" is injected
Injections are done as keyword arguments.
.. code-block:: python
foo() # Equivalent to:
foo(bar=container.bar())
Context keyword arguments have a priority over injections.
.. code-block:: python
foo(bar=Bar()) # Bar() is injected
To unpatch previously patched functions and methods call ``container.unwire()`` method.
.. code-block:: python
container.unwire()
You can use that in testing to re-create and re-wire a container before each test.
.. code-block:: python
import unittest
class SomeTest(unittest.TestCase):
def setUp(self):
self.container = Container()
self.container.wire(modules=[module1, module2])
self.addCleanup(self.container.unwire)
.. code-block:: python
import pytest
@pytest.fixture
def container():
container = Container()
container.wire(modules=[module1, module2])
yield container
container.unwire()
.. note::
Wiring can take time if you have a large codebase. Consider to persist a container instance and
avoid re-wiring between tests.
.. note::
Python has a limitation on patching individually imported functions. To protect from errors
prefer importing modules to importing individual functions or make sure imports happen
after the wiring:
.. code-block:: python
# Potential error:
from .module import fn
fn()
Instead use next:
.. code-block:: python
# Always works:
from . import module
module.fn()
.. _async-injections-wiring:
Asynchronous injections
-----------------------
Wiring feature supports asynchronous injections:
.. code-block:: python
class Container(containers.DeclarativeContainer):
db = providers.Resource(init_async_db_client)
cache = providers.Resource(init_async_cache_client)
@inject
async def main(
db: Database = Provide[Container.db],
cache: Cache = Provide[Container.cache],
):
...
When you call asynchronous function wiring prepares injections asynchronously.
Here is what it does for previous example:
.. code-block:: python
db, cache = await asyncio.gather(
container.db(),
container.cache(),
)
await main(db=db, cache=cache)
You can also use ``Closing`` marker with the asynchronous ``Resource`` providers:
.. code-block:: python
@inject
async def main(
db: Database = Closing[Provide[Container.db]],
cache: Cache = Closing[Provide[Container.cache]],
):
...
Wiring does closing asynchronously:
.. code-block:: python
db, cache = await asyncio.gather(
container.db(),
container.cache(),
)
await main(db=db, cache=cache)
await asyncio.gather(
container.db.shutdown(),
container.cache.shutdown(),
)
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for
details on ``Closing`` marker.
.. note::
Wiring does not not convert asynchronous injections to synchronous.
It handles asynchronous injections only for ``async def`` functions. Asynchronous injections into
synchronous ``def`` function still work, but you need to take care of awaitables by your own.
See also:
- Provider :ref:`async-injections`
- Resource provider :ref:`resource-async-initializers`
- :ref:`fastapi-redis-example`
Integration with other frameworks
---------------------------------
Wiring feature helps to integrate with other frameworks like Django, Flask, etc.
With wiring you do not need to change the traditional application structure of your framework.
1. Create a container and put framework-independent components as providers.
2. Place wiring markers in the functions and methods where you want the providers
to be injected (Flask or Django views, Aiohttp or Sanic handlers, etc).
3. Wire the container with the application modules.
4. Run the application.
.. literalinclude:: ../examples/wiring/flask_example.py
:language: python
:lines: 3-
Take a look at other application examples:
- :ref:`application-single-container`
- :ref:`application-multiple-containers`
- :ref:`decoupled-packages`
- :ref:`django-example`
- :ref:`flask-example`
- :ref:`flask-blueprints-example`
- :ref:`aiohttp-example`
- :ref:`sanic-example`
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
.. disqus::