From c4b5494b6b96fde2f94831b832ec4e150cf6aeb8 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Tue, 29 Sep 2020 22:29:24 -0400 Subject: [PATCH] Add wiring docs --- docs/index.rst | 27 ++--- docs/wiring.rst | 174 +++++++++++++++++++++++++++++++ examples/wiring/example.py | 26 +++++ examples/wiring/flask_example.py | 29 ++++++ setup.cfg | 1 + 5 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 docs/wiring.rst create mode 100644 examples/wiring/example.py create mode 100644 examples/wiring/flask_example.py diff --git a/docs/index.rst b/docs/index.rst index f6b474a3..9d80a51d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 diff --git a/docs/wiring.rst b/docs/wiring.rst new file mode 100644 index 00000000..671f5153 --- /dev/null +++ b/docs/wiring.rst @@ -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:: diff --git a/examples/wiring/example.py b/examples/wiring/example.py new file mode 100644 index 00000000..fe8f27ee --- /dev/null +++ b/examples/wiring/example.py @@ -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() diff --git a/examples/wiring/flask_example.py b/examples/wiring/flask_example.py new file mode 100644 index 00000000..3ec0963f --- /dev/null +++ b/examples/wiring/flask_example.py @@ -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() diff --git a/setup.cfg b/setup.cfg index 876b66ee..da9fdf7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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