Add wiring docs

This commit is contained in:
Roman Mogylatov 2020-09-29 22:29:24 -04:00
parent b4890dcf80
commit c4b5494b6b
5 changed files with 245 additions and 12 deletions

View File

@ -77,8 +77,10 @@ Key features of the ``Dependency Injector``:
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries. See :ref:`configuration-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Performance**. Fast. Written in ``Cython``.
- **Wiring**. Injects container providers into functions and methods. Helps integrating with
other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python
@ -134,15 +136,16 @@ Explore the documentation to know more about the ``Dependency Injector``.
Contents
--------
.. toctree::
:maxdepth: 2
.. toctree::
:maxdepth: 2
introduction/index
examples/index
tutorials/index
providers/index
containers/index
examples-other/index
api/index
main/feedback
main/changelog
introduction/index
examples/index
tutorials/index
providers/index
containers/index
wiring
examples-other/index
api/index
main/feedback
main/changelog

174
docs/wiring.rst Normal file
View File

@ -0,0 +1,174 @@
.. _wiring:
Wiring
======
Wiring feature provides a way to inject container providers into the functions and methods.
To use wiring you need:
- **Place markers in the code**. Wiring marker specifies what provider 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 Provide
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 Provider
def foo(bar_provider: Callable[..., Bar] = Provider[Container.bar]):
...
You can use configuration, provided instance and sub-container providers as you normally do.
.. code-block:: python
def foo(token: str = Provide[Container.config.api_token]):
...
def foo(timeout: int = Provide[Container.config.timeout.as_(int)]):
...
def foo(baz: Baz = Provide[Container.bar.provided.baz]):
...
def foo(bar: Bar = Provide[Container.subcontainer.bar]):
...
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
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.
Integration with other frameworks
---------------------------------
Wiring feature helps integrate the container providers with other frameworks:
Django, Flask, Aiohttp, Sanic, your custom framework, 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.
4. Run the application.
.. literalinclude:: ../examples/wiring/flask_example.py
:language: python
:lines: 3-
.. disqus::

View File

@ -0,0 +1,26 @@
"""Wiring example."""
import sys
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
def main(service: Service = Provide[Container.service]) -> None:
...
if __name__ == '__main__':
container = Container()
container.wire(modules=[sys.modules[__name__]])
main()

View File

@ -0,0 +1,29 @@
"""Flask wiring example."""
import sys
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide
from flask import Flask, json
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
def index_view(service: Service = Provide[Container.service]) -> str:
return json.dumps({'service_id': id(service)})
if __name__ == '__main__':
container = Container()
container.wire(modules=[sys.modules[__name__]])
app = Flask(__name__)
app.add_url_rule('/', 'index', index_view)
app.run()

View File

@ -4,6 +4,7 @@ max_complexity = 10
exclude = types.py
per-file-ignores =
examples/demo/*: F841
examples/wiring/*: F841
[pydocstyle]
ignore = D100,D101,D102,D105,D106,D107,D203,D213