Wiring reengineering (#324)

* Bump version to 4.3.9: FastAPI example

* Reengineer wiring

* Add @inject decorator

* Add .workspace dir to gitignore

* Add generic typing for @inject

* Add type cast for @inject

* Update movie lister example

* Update cli application tutorial

* Update demo example

* Update wiring docs and examples

* Update aiohttp example and tutorial

* Update multiple containers example

* Update single container example

* Update decoupled packages example

* Update django example

* Update asyncio daemon example and tutorial

* Update FastAPI example

* Update flask example and tutorial

* Update sanic example

* Add wiring registry

* Add new line to .gitignore

* Add @inject to the test samples

* Fix flake8 errors
This commit is contained in:
Roman Mogylatov 2020-11-15 16:06:42 -05:00 committed by GitHub
parent bece33fc21
commit ae3024588c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 336 additions and 112 deletions

3
.gitignore vendored
View File

@ -69,3 +69,6 @@ src/dependency_injector/containers/*.h
src/dependency_injector/containers/*.so src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h src/dependency_injector/providers/*.h
src/dependency_injector/providers/*.so src/dependency_injector/providers/*.so
# Workspace for samples
.workspace/

View File

@ -68,7 +68,7 @@ Key features of the ``Dependency Injector``:
or process pool, etc. Can be used for per-function execution scope in tandem with wiring. 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, Sanic, FastAPI, etc.
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_. See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
- **Typing**. Provides typing stubs, ``mypy``-friendly. - **Typing**. Provides typing stubs, ``mypy``-friendly.
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_. See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
@ -78,7 +78,7 @@ Key features of the ``Dependency Injector``:
.. code-block:: python .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -97,6 +97,7 @@ Key features of the ``Dependency Injector``:
) )
@inject
def main(service: Service = Provide[Container.service]): def main(service: Service = Provide[Container.service]):
... ...

79
docs/examples/fastapi.rst Normal file
View File

@ -0,0 +1,79 @@
.. _fastapi-example:
FastAPI example
===============
.. meta::
:keywords: Python,Dependency Injection,FastAPI,Example
:description: This example demonstrates a usage of the FastAPI and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── endpoints.py
│ ├── giphy.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/containers.py
:language: python
Endpoints
---------
Endpoint has a dependency on search service. There are also some config options that are used as default values.
The dependencies are injected using :ref:`wiring` feature.
Listing of ``giphynavigator/endpoints.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/endpoints.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``endpoints`` module, creates
``FastAPI`` app, and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/tests.py
:language: python
:emphasize-lines: 29,57,72
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
.. disqus::

View File

@ -17,5 +17,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
flask flask
aiohttp aiohttp
sanic sanic
fastapi
.. disqus:: .. disqus::

View File

@ -77,7 +77,7 @@ Key features of the ``Dependency Injector``:
See :ref:`resource-provider`. 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, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`. - **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``. - **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported. - **Maturity**. Mature and production-ready. Well-tested, documented and supported.
@ -85,7 +85,7 @@ Key features of the ``Dependency Injector``:
.. code-block:: python .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -104,6 +104,7 @@ Key features of the ``Dependency Injector``:
) )
@inject
def main(service: Service = Provide[Container.service]): def main(service: Service = Provide[Container.service]):
... ...

View File

@ -162,7 +162,7 @@ the dependency.
.. code-block:: python .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -181,6 +181,7 @@ the dependency.
) )
@inject
def main(service: Service = Provide[Container.service]): def main(service: Service = Provide[Container.service]):
... ...
@ -284,6 +285,7 @@ Choose one of the following as a next step:
- :ref:`flask-example` - :ref:`flask-example`
- :ref:`aiohttp-example` - :ref:`aiohttp-example`
- :ref:`sanic-example` - :ref:`sanic-example`
- :ref:`fastapi-example`
- Pass the tutorials: - Pass the tutorials:
- :ref:`flask-tutorial` - :ref:`flask-tutorial`
- :ref:`aiohttp-tutorial` - :ref:`aiohttp-tutorial`

View File

@ -23,7 +23,7 @@ Key features of the ``Dependency Injector``:
See :ref:`resource-provider`. 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, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`. - **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``. - **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported. - **Maturity**. Mature and production-ready. Well-tested, documented and supported.

View File

@ -7,6 +7,10 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
4.3.9
-----
- Add ``FastAPI`` example.
4.3.8 4.3.8
----- -----
- Add a hotfix to support wiring for ``FastAPI`` endpoints. - Add a hotfix to support wiring for ``FastAPI`` endpoints.

View File

@ -216,7 +216,7 @@ execution scope. For doing this you need to use additional ``Closing`` marker fr
.. literalinclude:: ../../examples/wiring/flask_resource_closing.py .. literalinclude:: ../../examples/wiring/flask_resource_closing.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 23 :emphasize-lines: 24
Framework initializes and injects the resource into the function. With the ``Closing`` marker Framework initializes and injects the resource into the function. With the ``Closing`` marker
framework calls resource ``shutdown()`` method when function execution is over. framework calls resource ``shutdown()`` method when function execution is over.

View File

@ -526,17 +526,18 @@ the ``index`` handler. We will use :ref:`wiring` feature.
Edit ``handlers.py``: Edit ``handlers.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 4-7,10-13,17 :emphasize-lines: 4-7,10-14,18
"""Handlers module.""" """Handlers module."""
from aiohttp import web from aiohttp import web
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
async def index( async def index(
request: web.Request, request: web.Request,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],
@ -645,17 +646,18 @@ Let's make some refactoring. We will move these values to the config.
Edit ``handlers.py``: Edit ``handlers.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 13-14,16-17 :emphasize-lines: 14-15,17-18
"""Handlers module.""" """Handlers module."""
from aiohttp import web from aiohttp import web
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
async def index( async def index(
request: web.Request, request: web.Request,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],
@ -821,11 +823,11 @@ You should see:
giphynavigator/application.py 12 0 100% giphynavigator/application.py 12 0 100%
giphynavigator/containers.py 6 0 100% giphynavigator/containers.py 6 0 100%
giphynavigator/giphy.py 14 9 36% giphynavigator/giphy.py 14 9 36%
giphynavigator/handlers.py 9 0 100% giphynavigator/handlers.py 10 0 100%
giphynavigator/services.py 9 1 89% giphynavigator/services.py 9 1 89%
giphynavigator/tests.py 37 0 100% giphynavigator/tests.py 37 0 100%
--------------------------------------------------- ---------------------------------------------------
TOTAL 87 10 89% TOTAL 88 10 89%
.. note:: .. note::

View File

@ -442,18 +442,19 @@ and call the ``run()`` method. We will use :ref:`wiring` feature.
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 3-7,11-12,19 :emphasize-lines: 3-7,11-13,20
"""Main module.""" """Main module."""
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
from .containers import Container from .containers import Container
@inject
def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None: def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
dispatcher.run() dispatcher.run()
@ -992,14 +993,14 @@ You should see:
Name Stmts Miss Cover Name Stmts Miss Cover
---------------------------------------------------- ----------------------------------------------------
monitoringdaemon/__init__.py 0 0 100% monitoringdaemon/__init__.py 0 0 100%
monitoringdaemon/__main__.py 12 12 0% monitoringdaemon/__main__.py 13 13 0%
monitoringdaemon/containers.py 11 0 100% monitoringdaemon/containers.py 11 0 100%
monitoringdaemon/dispatcher.py 44 5 89% monitoringdaemon/dispatcher.py 44 5 89%
monitoringdaemon/http.py 6 3 50% monitoringdaemon/http.py 6 3 50%
monitoringdaemon/monitors.py 23 1 96% monitoringdaemon/monitors.py 23 1 96%
monitoringdaemon/tests.py 37 0 100% monitoringdaemon/tests.py 37 0 100%
---------------------------------------------------- ----------------------------------------------------
TOTAL 133 21 84% TOTAL 134 22 84%
.. note:: .. note::

View File

@ -575,18 +575,19 @@ Let's inject the ``lister`` into the ``main()`` function.
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 3-7,11,18 :emphasize-lines: 3-7,11-12,19
"""Main module.""" """Main module."""
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
... ...
@ -606,18 +607,19 @@ Francis Lawrence and movies released in 2016.
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 12-18 :emphasize-lines: 13-19
"""Main module.""" """Main module."""
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:') print('Francis Lawrence movies:')
for movie in lister.movies_directed_by('Francis Lawrence'): for movie in lister.movies_directed_by('Francis Lawrence'):
@ -861,18 +863,19 @@ Now we need to read the value of the ``config.finder.type`` option from the envi
Edit ``__main__.py``: Edit ``__main__.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 24 :emphasize-lines: 25
"""Main module.""" """Main module."""
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:') print('Francis Lawrence movies:')
for movie in lister.movies_directed_by('Francis Lawrence'): for movie in lister.movies_directed_by('Francis Lawrence'):
@ -1023,14 +1026,14 @@ You should see:
Name Stmts Miss Cover Name Stmts Miss Cover
------------------------------------------ ------------------------------------------
movies/__init__.py 0 0 100% movies/__init__.py 0 0 100%
movies/__main__.py 17 17 0% movies/__main__.py 18 18 0%
movies/containers.py 9 0 100% movies/containers.py 9 0 100%
movies/entities.py 7 1 86% movies/entities.py 7 1 86%
movies/finders.py 26 13 50% movies/finders.py 26 13 50%
movies/listers.py 8 0 100% movies/listers.py 8 0 100%
movies/tests.py 24 0 100% movies/tests.py 24 0 100%
------------------------------------------ ------------------------------------------
TOTAL 91 31 66% TOTAL 92 32 65%
.. note:: .. note::

View File

@ -707,17 +707,19 @@ Let's inject ``SearchService`` into the ``index`` view. We will use :ref:`Wiring
Edit ``views.py``: Edit ``views.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 4,6-7,10,14 :emphasize-lines: 4,6-7,10-11,15
:emphasize-lines: 4,6-7,10-11,15
"""Views module.""" """Views module."""
from flask import request, render_template from flask import request, render_template
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
def index(search_service: SearchService = Provide[Container.search_service]): def index(search_service: SearchService = Provide[Container.search_service]):
query = request.args.get('query', 'Dependency Injector') query = request.args.get('query', 'Dependency Injector')
limit = request.args.get('limit', 10, int) limit = request.args.get('limit', 10, int)
@ -783,17 +785,18 @@ Let's make some refactoring. We will move these values to the config.
Edit ``views.py``: Edit ``views.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 10-16 :emphasize-lines: 11-17
"""Views module.""" """Views module."""
from flask import request, render_template from flask import request, render_template
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
def index( def index(
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],
default_query: str = Provide[Container.config.default.query], default_query: str = Provide[Container.config.default.query],
@ -972,9 +975,9 @@ You should see:
githubnavigator/containers.py 7 0 100% githubnavigator/containers.py 7 0 100%
githubnavigator/services.py 14 0 100% githubnavigator/services.py 14 0 100%
githubnavigator/tests.py 34 0 100% githubnavigator/tests.py 34 0 100%
githubnavigator/views.py 9 0 100% githubnavigator/views.py 10 0 100%
---------------------------------------------------- ----------------------------------------------------
TOTAL 79 0 100% TOTAL 80 0 100%
.. note:: .. note::

View File

@ -7,7 +7,8 @@ Wiring feature provides a way to inject container providers into the functions a
To use wiring you need: To use wiring you need:
- **Place markers in the code**. Wiring marker specifies what provider to inject, - **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. e.g. ``Provide[Container.bar]``. This helps container to find the injections.
- **Wire the container with the markers in the code**. Call ``container.wire()`` - **Wire the container with the markers in the code**. Call ``container.wire()``
specifying modules and packages you would like to wire it with. specifying modules and packages you would like to wire it with.
@ -25,9 +26,10 @@ a function or method argument:
.. code-block:: python .. code-block:: python
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
@inject
def foo(bar: Bar = Provide[Container.bar]): def foo(bar: Bar = Provide[Container.bar]):
... ...
@ -40,9 +42,10 @@ There are two types of markers:
.. code-block:: python .. code-block:: python
from dependency_injector.wiring import Provider from dependency_injector.wiring import inject, Provider
@inject
def foo(bar_provider: Callable[..., Bar] = Provider[Container.bar]): def foo(bar_provider: Callable[..., Bar] = Provider[Container.bar]):
bar = bar_provider() bar = bar_provider()
... ...
@ -51,18 +54,22 @@ You can use configuration, provided instance and sub-container providers as you
.. code-block:: python .. code-block:: python
@inject
def foo(token: str = Provide[Container.config.api_token]): def foo(token: str = Provide[Container.config.api_token]):
... ...
@inject
def foo(timeout: int = Provide[Container.config.timeout.as_(int)]): def foo(timeout: int = Provide[Container.config.timeout.as_(int)]):
... ...
@inject
def foo(baz: Baz = Provide[Container.bar.provided.baz]): def foo(baz: Baz = Provide[Container.bar.provided.baz]):
... ...
@inject
def foo(bar: Bar = Provide[Container.subcontainer.bar]): def foo(bar: Bar = Provide[Container.subcontainer.bar]):
... ...
@ -100,6 +107,7 @@ When wiring is done functions and methods with the markers are patched to provid
.. code-block:: python .. code-block:: python
@inject
def foo(bar: Bar = Provide[Container.bar]): def foo(bar: Bar = Provide[Container.bar]):
... ...
@ -201,5 +209,6 @@ Take a look at other application examples:
- :ref:`flask-example` - :ref:`flask-example`
- :ref:`aiohttp-example` - :ref:`aiohttp-example`
- :ref:`sanic-example` - :ref:`sanic-example`
- :ref:`fastapi-example`
.. disqus:: .. disqus::

View File

@ -2,7 +2,7 @@ import sys
from unittest import mock from unittest import mock
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from after import ApiClient, Service from after import ApiClient, Service
@ -23,6 +23,7 @@ class Container(containers.DeclarativeContainer):
) )
@inject
def main(service: Service = Provide[Container.service]): def main(service: Service = Provide[Container.service]):
... ...

View File

@ -111,8 +111,8 @@ The output should be something like:
giphynavigator/application.py 12 0 100% giphynavigator/application.py 12 0 100%
giphynavigator/containers.py 6 0 100% giphynavigator/containers.py 6 0 100%
giphynavigator/giphy.py 14 9 36% giphynavigator/giphy.py 14 9 36%
giphynavigator/handlers.py 9 0 100% giphynavigator/handlers.py 10 0 100%
giphynavigator/services.py 9 1 89% giphynavigator/services.py 9 1 89%
giphynavigator/tests.py 37 0 100% giphynavigator/tests.py 37 0 100%
--------------------------------------------------- ---------------------------------------------------
TOTAL 87 10 89% TOTAL 88 10 89%

View File

@ -1,12 +1,13 @@
"""Handlers module.""" """Handlers module."""
from aiohttp import web from aiohttp import web
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
async def index( async def index(
request: web.Request, request: web.Request,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],

View File

@ -2,12 +2,13 @@
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import UserService, AuthService, PhotoService from .services import UserService, AuthService, PhotoService
from .containers import Application from .containers import Application
@inject
def main( def main(
email: str, email: str,
password: str, password: str,

View File

@ -2,12 +2,13 @@
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import UserService, AuthService, PhotoService from .services import UserService, AuthService, PhotoService
from .containers import Container from .containers import Container
@inject
def main( def main(
email: str, email: str,
password: str, password: str,

View File

@ -76,11 +76,11 @@ The output should be something like:
Name Stmts Miss Cover Name Stmts Miss Cover
---------------------------------------------------- ----------------------------------------------------
monitoringdaemon/__init__.py 0 0 100% monitoringdaemon/__init__.py 0 0 100%
monitoringdaemon/__main__.py 12 12 0% monitoringdaemon/__main__.py 13 13 0%
monitoringdaemon/containers.py 11 0 100% monitoringdaemon/containers.py 11 0 100%
monitoringdaemon/dispatcher.py 44 5 89% monitoringdaemon/dispatcher.py 44 5 89%
monitoringdaemon/http.py 6 3 50% monitoringdaemon/http.py 6 3 50%
monitoringdaemon/monitors.py 23 1 96% monitoringdaemon/monitors.py 23 1 96%
monitoringdaemon/tests.py 37 0 100% monitoringdaemon/tests.py 37 0 100%
---------------------------------------------------- ----------------------------------------------------
TOTAL 133 21 84% TOTAL 134 22 84%

View File

@ -2,12 +2,13 @@
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
from .containers import Container from .containers import Container
@inject
def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None: def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
dispatcher.run() dispatcher.run()

View File

@ -2,7 +2,7 @@
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .user.repositories import UserRepository from .user.repositories import UserRepository
from .photo.repositories import PhotoRepository from .photo.repositories import PhotoRepository
@ -10,6 +10,7 @@ from .analytics.services import AggregationService
from .containers import ApplicationContainer from .containers import ApplicationContainer
@inject
def main( def main(
user_repository: UserRepository = Provide[ user_repository: UserRepository = Provide[
ApplicationContainer.user_package.user_repository ApplicationContainer.user_package.user_repository

View File

@ -108,6 +108,6 @@ The output should be something like:
web/apps.py 7 0 100% web/apps.py 7 0 100%
web/tests.py 28 0 100% web/tests.py 28 0 100%
web/urls.py 3 0 100% web/urls.py 3 0 100%
web/views.py 11 0 100% web/views.py 12 0 100%
--------------------------------------------------- ---------------------------------------------------
TOTAL 120 10 92% TOTAL 121 10 92%

View File

@ -4,12 +4,13 @@ from typing import List
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import render from django.shortcuts import render
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from githubnavigator.containers import Container from githubnavigator.containers import Container
from githubnavigator.services import SearchService from githubnavigator.services import SearchService
@inject
def index( def index(
request: HttpRequest, request: HttpRequest,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],

View File

@ -113,9 +113,9 @@ The output should be something like:
giphynavigator/__init__.py 0 0 100% giphynavigator/__init__.py 0 0 100%
giphynavigator/application.py 13 0 100% giphynavigator/application.py 13 0 100%
giphynavigator/containers.py 6 0 100% giphynavigator/containers.py 6 0 100%
giphynavigator/endpoints.py 5 0 100% giphynavigator/endpoints.py 6 0 100%
giphynavigator/giphy.py 14 9 36% giphynavigator/giphy.py 14 9 36%
giphynavigator/services.py 9 1 89% giphynavigator/services.py 9 1 89%
giphynavigator/tests.py 38 0 100% giphynavigator/tests.py 38 0 100%
--------------------------------------------------- ---------------------------------------------------
TOTAL 85 10 88% TOTAL 86 10 88%

View File

@ -1,10 +1,11 @@
"""Endpoints module.""" """Endpoints module."""
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .containers import Container from .containers import Container
@inject
async def index( async def index(
query: str = Provide[Container.config.default.query], query: str = Provide[Container.config.default.query],
limit: int = Provide[Container.config.default.limit.as_int()], limit: int = Provide[Container.config.default.limit.as_int()],

View File

@ -95,6 +95,6 @@ The output should be something like:
githubnavigator/containers.py 7 0 100% githubnavigator/containers.py 7 0 100%
githubnavigator/services.py 14 0 100% githubnavigator/services.py 14 0 100%
githubnavigator/tests.py 34 0 100% githubnavigator/tests.py 34 0 100%
githubnavigator/views.py 9 0 100% githubnavigator/views.py 10 0 100%
---------------------------------------------------- ----------------------------------------------------
TOTAL 79 0 100% TOTAL 80 0 100%

View File

@ -1,12 +1,13 @@
"""Views module.""" """Views module."""
from flask import request, render_template from flask import request, render_template
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
def index( def index(
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],
default_query: str = Provide[Container.config.default.query], default_query: str = Provide[Container.config.default.query],

View File

@ -68,11 +68,11 @@ The output should be something like:
Name Stmts Miss Cover Name Stmts Miss Cover
------------------------------------------ ------------------------------------------
movies/__init__.py 0 0 100% movies/__init__.py 0 0 100%
movies/__main__.py 17 17 0% movies/__main__.py 18 18 0%
movies/containers.py 9 0 100% movies/containers.py 9 0 100%
movies/entities.py 7 1 86% movies/entities.py 7 1 86%
movies/finders.py 26 13 50% movies/finders.py 26 13 50%
movies/listers.py 8 0 100% movies/listers.py 8 0 100%
movies/tests.py 24 0 100% movies/tests.py 24 0 100%
------------------------------------------ ------------------------------------------
TOTAL 91 31 66% TOTAL 92 32 65%

View File

@ -2,12 +2,13 @@
import sys import sys
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .listers import MovieLister from .listers import MovieLister
from .containers import Container from .containers import Container
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None: def main(lister: MovieLister = Provide[Container.lister]) -> None:
print('Francis Lawrence movies:') print('Francis Lawrence movies:')
for movie in lister.movies_directed_by('Francis Lawrence'): for movie in lister.movies_directed_by('Francis Lawrence'):

View File

@ -112,8 +112,8 @@ The output should be something like:
giphynavigator/application.py 12 0 100% giphynavigator/application.py 12 0 100%
giphynavigator/containers.py 6 0 100% giphynavigator/containers.py 6 0 100%
giphynavigator/giphy.py 14 9 36% giphynavigator/giphy.py 14 9 36%
giphynavigator/handlers.py 10 0 100% giphynavigator/handlers.py 11 0 100%
giphynavigator/services.py 9 1 89% giphynavigator/services.py 9 1 89%
giphynavigator/tests.py 34 0 100% giphynavigator/tests.py 34 0 100%
--------------------------------------------------- ---------------------------------------------------
TOTAL 89 14 84% TOTAL 90 14 84%

View File

@ -2,12 +2,13 @@
from sanic.request import Request from sanic.request import Request
from sanic.response import HTTPResponse, json from sanic.response import HTTPResponse, json
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from .services import SearchService from .services import SearchService
from .containers import Container from .containers import Container
@inject
async def index( async def index(
request: Request, request: Request,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService = Provide[Container.search_service],

View File

@ -3,7 +3,7 @@
import sys import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
class Service: class Service:
@ -15,6 +15,7 @@ class Container(containers.DeclarativeContainer):
service = providers.Factory(Service) service = providers.Factory(Service)
@inject
def main(service: Service = Provide[Container.service]) -> None: def main(service: Service = Provide[Container.service]) -> None:
... ...

View File

@ -3,7 +3,7 @@
import sys import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from flask import Flask, json from flask import Flask, json
@ -16,6 +16,7 @@ class Container(containers.DeclarativeContainer):
service = providers.Factory(Service) service = providers.Factory(Service)
@inject
def index_view(service: Service = Provide[Container.service]) -> str: def index_view(service: Service = Provide[Container.service]) -> str:
return json.dumps({'service_id': id(service)}) return json.dumps({'service_id': id(service)})

View File

@ -3,7 +3,7 @@
import sys import sys
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, Closing from dependency_injector.wiring import inject, Provide, Closing
from flask import Flask, current_app from flask import Flask, current_app
@ -22,6 +22,7 @@ class Container(containers.DeclarativeContainer):
service = providers.Resource(init_service) service = providers.Resource(init_service)
@inject
def index_view(service: Service = Closing[Provide[Container.service]]): def index_view(service: Service = Closing[Provide[Container.service]]):
assert service is current_app.container.service() assert service is current_app.container.service()
return 'Hello World!' return 'Hello World!'

View File

@ -1,6 +1,6 @@
"""Top-level package.""" """Top-level package."""
__version__ = '4.3.8' __version__ = '4.3.9'
"""Version number. """Version number.
:type: str :type: str

View File

@ -6,7 +6,19 @@ import importlib
import pkgutil import pkgutil
import sys import sys
from types import ModuleType from types import ModuleType
from typing import Optional, Iterable, Callable, Any, Tuple, Dict, Generic, TypeVar, Type, cast from typing import (
Optional,
Iterable,
Iterator,
Callable,
Any,
Tuple,
Dict,
Generic,
TypeVar,
Type,
cast,
)
if sys.version_info < (3, 7): if sys.version_info < (3, 7):
from typing import GenericMeta from typing import GenericMeta
@ -21,15 +33,35 @@ from . import providers
__all__ = ( __all__ = (
'wire', 'wire',
'unwire', 'unwire',
'inject',
'Provide', 'Provide',
'Provider', 'Provider',
'Closing', 'Closing',
) )
T = TypeVar('T') T = TypeVar('T')
F = TypeVar('F', bound=Callable[..., Any])
Container = Any Container = Any
class Registry:
def __init__(self):
self._storage = set()
def add(self, patched: Callable[..., Any]) -> None:
self._storage.add(patched)
def get_from_module(self, module: ModuleType) -> Iterator[Callable[..., Any]]:
for patched in self._storage:
if patched.__module__ != module.__name__:
continue
yield patched
_patched_registry = Registry()
class ProvidersMap: class ProvidersMap:
def __init__(self, container): def __init__(self, container):
@ -152,7 +184,7 @@ class ProvidersMap:
return providers_map return providers_map
def wire( def wire( # noqa: C901
container: Container, container: Container,
*, *,
modules: Optional[Iterable[ModuleType]] = None, modules: Optional[Iterable[ModuleType]] = None,
@ -179,6 +211,9 @@ def wire(
for method_name, method in inspect.getmembers(member, _is_method): for method_name, method in inspect.getmembers(member, _is_method):
_patch_method(member, method_name, method, providers_map) _patch_method(member, method_name, method, providers_map)
for patched in _patched_registry.get_from_module(module):
_bind_injections(patched, providers_map)
def unwire( def unwire(
*, *,
@ -201,6 +236,17 @@ def unwire(
for method_name, method in inspect.getmembers(member, inspect.isfunction): for method_name, method in inspect.getmembers(member, inspect.isfunction):
_unpatch(member, method_name, method) _unpatch(member, method_name, method)
for patched in _patched_registry.get_from_module(module):
_unbind_injections(patched)
def inject(fn: F) -> F:
"""Decorate callable with injecting decorator."""
reference_injections, reference_closing = _fetch_reference_injections(fn)
patched = _get_patched(fn, reference_injections, reference_closing)
_patched_registry.add(patched)
return cast(F, patched)
def _patch_fn( def _patch_fn(
module: ModuleType, module: ModuleType,
@ -208,11 +254,16 @@ def _patch_fn(
fn: Callable[..., Any], fn: Callable[..., Any],
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> None: ) -> None:
injections, closing = _resolve_injections(fn, providers_map) if not _is_patched(fn):
if not injections: reference_injections, reference_closing = _fetch_reference_injections(fn)
return if not reference_injections:
patched = _patch_with_injections(fn, injections, closing) return
setattr(module, name, _wrap_patched(patched, fn, injections, closing)) fn = _get_patched(fn, reference_injections, reference_closing)
_patched_registry.add(fn)
_bind_injections(fn, providers_map)
setattr(module, name, fn)
def _patch_method( def _patch_method(
@ -221,28 +272,27 @@ def _patch_method(
method: Callable[..., Any], method: Callable[..., Any],
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> None: ) -> None:
injections, closing = _resolve_injections(method, providers_map)
if not injections:
return
if hasattr(cls, '__dict__') \ if hasattr(cls, '__dict__') \
and name in cls.__dict__ \ and name in cls.__dict__ \
and isinstance(cls.__dict__[name], (classmethod, staticmethod)): and isinstance(cls.__dict__[name], (classmethod, staticmethod)):
method = cls.__dict__[name] method = cls.__dict__[name]
patched = _patch_with_injections(method.__func__, injections, closing) fn = method.__func__
patched = type(method)(patched)
else: else:
patched = _patch_with_injections(method, injections, closing) fn = method
setattr(cls, name, _wrap_patched(patched, method, injections, closing)) if not _is_patched(fn):
reference_injections, reference_closing = _fetch_reference_injections(fn)
if not reference_injections:
return
fn = _get_patched(fn, reference_injections, reference_closing)
_patched_registry.add(fn)
_bind_injections(fn, providers_map)
def _wrap_patched(patched: Callable[..., Any], original, injections, closing): if isinstance(method, (classmethod, staticmethod)):
patched.__wired__ = True fn = type(method)(fn)
patched.__original__ = original
patched.__injections__ = injections setattr(cls, name, fn)
patched.__closing__ = closing
return patched
def _unpatch( def _unpatch(
@ -250,14 +300,20 @@ def _unpatch(
name: str, name: str,
fn: Callable[..., Any], fn: Callable[..., Any],
) -> None: ) -> None:
if hasattr(module, '__dict__') \
and name in module.__dict__ \
and isinstance(module.__dict__[name], (classmethod, staticmethod)):
method = module.__dict__[name]
fn = method.__func__
if not _is_patched(fn): if not _is_patched(fn):
return return
setattr(module, name, _get_original_from_patched(fn))
_unbind_injections(fn)
def _resolve_injections( def _fetch_reference_injections(
fn: Callable[..., Any], fn: Callable[..., Any],
providers_map: ProvidersMap,
) -> Tuple[Dict[str, Any], Dict[str, Any]]: ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
signature = inspect.signature(fn) signature = inspect.signature(fn)
@ -268,24 +324,33 @@ def _resolve_injections(
continue continue
marker = parameter.default marker = parameter.default
closing_modifier = False
if isinstance(marker, Closing): if isinstance(marker, Closing):
closing_modifier = True
marker = marker.provider marker = marker.provider
closing[parameter_name] = marker
injections[parameter_name] = marker
return injections, closing
def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None:
for injection, marker in fn.__reference_injections__.items():
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[parameter_name] = provider
if isinstance(marker, Provide): if isinstance(marker, Provide):
injections[parameter_name] = provider fn.__injections__[injection] = provider
elif isinstance(marker, Provider): elif isinstance(marker, Provider):
injections[parameter_name] = provider.provider fn.__injections__[injection] = provider.provider
return injections, closing if injection in fn.__reference_closing__:
fn.__closing__[injection] = provider
def _unbind_injections(fn: Callable[..., Any]) -> None:
fn.__injections__ = {}
fn.__closing__ = {}
def _fetch_modules(package): def _fetch_modules(package):
@ -303,26 +368,34 @@ 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, closing): def _get_patched(fn, reference_injections, reference_closing):
if inspect.iscoroutinefunction(fn): if inspect.iscoroutinefunction(fn):
_patched = _get_async_patched(fn, injections, closing) patched = _get_async_patched(fn)
else: else:
_patched = _get_patched(fn, injections, closing) patched = _get_sync_patched(fn)
return _patched
patched.__wired__ = True
patched.__original__ = fn
patched.__injections__ = {}
patched.__reference_injections__ = reference_injections
patched.__closing__ = {}
patched.__reference_closing__ = reference_closing
return patched
def _get_patched(fn, injections, closing): def _get_sync_patched(fn):
@functools.wraps(fn) @functools.wraps(fn)
def _patched(*args, **kwargs): def _patched(*args, **kwargs):
to_inject = kwargs.copy() to_inject = kwargs.copy()
for injection, provider in injections.items(): for injection, provider in _patched.__injections__.items():
if injection not in kwargs \ if injection not in kwargs \
or _is_fastapi_default_arg_injection(injection, kwargs): or _is_fastapi_default_arg_injection(injection, kwargs):
to_inject[injection] = provider() to_inject[injection] = provider()
result = fn(*args, **to_inject) result = fn(*args, **to_inject)
for injection, provider in closing.items(): for injection, provider in _patched.__closing__.items():
if injection in kwargs \ if injection in kwargs \
and not _is_fastapi_default_arg_injection(injection, kwargs): and not _is_fastapi_default_arg_injection(injection, kwargs):
continue continue
@ -334,18 +407,18 @@ def _get_patched(fn, injections, closing):
return _patched return _patched
def _get_async_patched(fn, injections, closing): def _get_async_patched(fn):
@functools.wraps(fn) @functools.wraps(fn)
async def _patched(*args, **kwargs): async def _patched(*args, **kwargs):
to_inject = kwargs.copy() to_inject = kwargs.copy()
for injection, provider in injections.items(): for injection, provider in _patched.__injections__.items():
if injection not in kwargs \ if injection not in kwargs \
or _is_fastapi_default_arg_injection(injection, kwargs): or _is_fastapi_default_arg_injection(injection, kwargs):
to_inject[injection] = provider() to_inject[injection] = provider()
result = await fn(*args, **to_inject) result = await fn(*args, **to_inject)
for injection, provider in closing.items(): for injection, provider in _patched.__closing__.items():
if injection in kwargs \ if injection in kwargs \
and not _is_fastapi_default_arg_injection(injection, kwargs): and not _is_fastapi_default_arg_injection(injection, kwargs):
continue continue
@ -366,10 +439,6 @@ def _is_patched(fn):
return getattr(fn, '__wired__', False) is True return getattr(fn, '__wired__', False) is True
def _get_original_from_patched(fn):
return getattr(fn, '__original__')
def _is_declarative_container_instance(instance: Any) -> bool: def _is_declarative_container_instance(instance: Any) -> bool:
return (not isinstance(instance, type) return (not isinstance(instance, type)
and getattr(instance, '__IS_CONTAINER__', False) is True and getattr(instance, '__IS_CONTAINER__', False) is True

View File

@ -3,7 +3,7 @@
from decimal import Decimal from decimal import Decimal
from typing import Callable from typing import Callable
from dependency_injector.wiring import Provide, Provider from dependency_injector.wiring import inject, Provide, Provider
from .container import Container, SubContainer from .container import Container, SubContainer
from .service import Service from .service import Service
@ -11,30 +11,37 @@ from .service import Service
class TestClass: class TestClass:
@inject
def __init__(self, service: Service = Provide[Container.service]): def __init__(self, service: Service = Provide[Container.service]):
self.service = service self.service = service
@inject
def method(self, service: Service = Provide[Container.service]): def method(self, service: Service = Provide[Container.service]):
return service return service
@classmethod @classmethod
@inject
def class_method(cls, service: Service = Provide[Container.service]): def class_method(cls, service: Service = Provide[Container.service]):
return service return service
@staticmethod @staticmethod
@inject
def static_method(service: Service = Provide[Container.service]): def static_method(service: Service = Provide[Container.service]):
return service return service
@inject
def test_function(service: Service = Provide[Container.service]): def test_function(service: Service = Provide[Container.service]):
return service return service
@inject
def test_function_provider(service_provider: Callable[..., Service] = Provider[Container.service]): def test_function_provider(service_provider: Callable[..., Service] = Provider[Container.service]):
service = service_provider() service = service_provider()
return service return service
@inject
def test_config_value( def test_config_value(
some_value_int: int = Provide[Container.config.a.b.c.as_int()], some_value_int: int = Provide[Container.config.a.b.c.as_int()],
some_value_str: str = Provide[Container.config.a.b.c.as_(str)], some_value_str: str = Provide[Container.config.a.b.c.as_(str)],
@ -43,25 +50,44 @@ def test_config_value(
return some_value_int, some_value_str, some_value_decimal return some_value_int, some_value_str, some_value_decimal
@inject
def test_provide_provider(service_provider: Callable[..., Service] = Provider[Container.service.provider]): def test_provide_provider(service_provider: Callable[..., Service] = Provider[Container.service.provider]):
service = service_provider() service = service_provider()
return service return service
@inject
def test_provided_instance(some_value: int = Provide[Container.service.provided.foo['bar'].call()]): def test_provided_instance(some_value: int = Provide[Container.service.provided.foo['bar'].call()]):
return some_value return some_value
@inject
def test_subcontainer_provider(some_value: int = Provide[Container.sub.int_object]): def test_subcontainer_provider(some_value: int = Provide[Container.sub.int_object]):
return some_value return some_value
@inject
def test_config_invariant(some_value: int = Provide[Container.config.option[Container.config.switch]]): def test_config_invariant(some_value: int = Provide[Container.config.option[Container.config.switch]]):
return some_value return some_value
@inject
def test_provide_from_different_containers( def test_provide_from_different_containers(
service: Service = Provide[Container.service], service: Service = Provide[Container.service],
some_value: int = Provide[SubContainer.int_object], some_value: int = Provide[SubContainer.int_object],
): ):
return service, some_value return service, some_value
class ClassDecorator:
def __init__(self, fn):
self._fn = fn
def __call__(self, *args, **kwargs):
return self._fn(*args, **kwargs)
@ClassDecorator
@inject
def test_class_decorator(service: Service = Provide[Container.service]):
return service

View File

@ -1,8 +1,9 @@
from dependency_injector.wiring import Provide from dependency_injector.wiring import inject, Provide
from ...container import Container from ...container import Container
from ...service import Service from ...service import Service
@inject
def test_function(service: Service = Provide[Container.service]): def test_function(service: Service = Provide[Container.service]):
return service return service

View File

@ -1,5 +1,5 @@
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, Closing from dependency_injector.wiring import inject, Provide, Closing
class Service: class Service:
@ -32,5 +32,6 @@ class Container(containers.DeclarativeContainer):
service = providers.Resource(init_service) service = providers.Resource(init_service)
@inject
def test_function(service: Service = Closing[Provide[Container.service]]): def test_function(service: Service = Closing[Provide[Container.service]]):
return service return service

View File

@ -226,6 +226,10 @@ class WiringTest(unittest.TestCase):
self.assertEqual(result_2.init_counter, 0) self.assertEqual(result_2.init_counter, 0)
self.assertEqual(result_2.shutdown_counter, 0) self.assertEqual(result_2.shutdown_counter, 0)
def test_class_decorator(self):
service = module.test_class_decorator()
self.assertIsInstance(service, Service)
class WiringAndFastAPITest(unittest.TestCase): class WiringAndFastAPITest(unittest.TestCase):