mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-26 03:23:58 +03:00
Merge branch 'release/4.4.0' into master
This commit is contained in:
commit
262c035bc1
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -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/
|
||||||
|
|
|
@ -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]):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
89
docs/examples/flask-blueprints.rst
Normal file
89
docs/examples/flask-blueprints.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
.. _flask-blueprints-example:
|
||||||
|
|
||||||
|
Flask blueprints example
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,Flask,Blueprints,Example
|
||||||
|
:description: This example demonstrates a usage of the Flask Blueprints and Dependency Injector.
|
||||||
|
|
||||||
|
|
||||||
|
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_
|
||||||
|
blueprints.
|
||||||
|
|
||||||
|
The example application helps to search for repositories on the Github.
|
||||||
|
|
||||||
|
.. image:: images/flask.png
|
||||||
|
:width: 100%
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
|
||||||
|
|
||||||
|
Application structure
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Application has next structure:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
./
|
||||||
|
├── githubnavigator/
|
||||||
|
│ ├── blueprints
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── example.py
|
||||||
|
│ ├── templates
|
||||||
|
│ │ ├── base.html
|
||||||
|
│ │ └── index.py
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ ├── services.py
|
||||||
|
│ └── tests.py
|
||||||
|
├── config.yml
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
Container
|
||||||
|
---------
|
||||||
|
|
||||||
|
Declarative container is defined in ``githubnavigator/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Blueprints
|
||||||
|
----------
|
||||||
|
|
||||||
|
Blueprint's view has dependencies on search service and some config options. The dependencies are injected
|
||||||
|
using :ref:`wiring` feature.
|
||||||
|
|
||||||
|
Listing of ``githubnavigator/blueprints/example.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/blueprints/example.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Application factory
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Application factory creates container, wires it with the blueprints, creates
|
||||||
|
``Flask`` app, and setup routes.
|
||||||
|
|
||||||
|
Listing of ``githubnavigator/application.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/application.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/tests.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 44,67
|
||||||
|
|
||||||
|
Sources
|
||||||
|
-------
|
||||||
|
|
||||||
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -15,6 +15,7 @@ Explore the examples to see the ``Dependency Injector`` in action.
|
||||||
decoupled-packages
|
decoupled-packages
|
||||||
django
|
django
|
||||||
flask
|
flask
|
||||||
|
flask-blueprints
|
||||||
aiohttp
|
aiohttp
|
||||||
sanic
|
sanic
|
||||||
fastapi
|
fastapi
|
||||||
|
|
|
@ -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]):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
@ -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]):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@ -282,6 +283,7 @@ Choose one of the following as a next step:
|
||||||
- :ref:`decoupled-packages`
|
- :ref:`decoupled-packages`
|
||||||
- :ref:`django-example`
|
- :ref:`django-example`
|
||||||
- :ref:`flask-example`
|
- :ref:`flask-example`
|
||||||
|
- :ref:`flask-blueprints-example`
|
||||||
- :ref:`aiohttp-example`
|
- :ref:`aiohttp-example`
|
||||||
- :ref:`sanic-example`
|
- :ref:`sanic-example`
|
||||||
- :ref:`fastapi-example`
|
- :ref:`fastapi-example`
|
||||||
|
|
|
@ -7,6 +7,17 @@ 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.4.0
|
||||||
|
-----
|
||||||
|
- Add ``@inject`` decorator. It helps to fix a number of wiring bugs and make wiring be more resilient.
|
||||||
|
- Refactor ``wiring`` module.
|
||||||
|
- Update documentation and examples to use ``@inject`` decorator.
|
||||||
|
- Add ``Flask`` blueprints example.
|
||||||
|
- Fix wiring bug when wiring doesn't work with the class-based decorators.
|
||||||
|
- Fix wiring bug when wiring doesn't work with the decorators that doesn't use ``functools.wraps(...)``.
|
||||||
|
- Fix wiring bug with ``@app.route(...)`` -style decorators (Flask, Sanic, FastAPI, etc.).
|
||||||
|
- Fix wiring bug when wiring doesn't work with Flask blueprints.
|
||||||
|
|
||||||
4.3.9
|
4.3.9
|
||||||
-----
|
-----
|
||||||
- Add ``FastAPI`` example.
|
- Add ``FastAPI`` example.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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::
|
||||||
|
|
||||||
|
|
|
@ -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::
|
||||||
|
|
||||||
|
|
|
@ -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::
|
||||||
|
|
||||||
|
|
|
@ -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::
|
||||||
|
|
||||||
|
|
|
@ -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]):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@ -199,6 +207,7 @@ Take a look at other application examples:
|
||||||
- :ref:`decoupled-packages`
|
- :ref:`decoupled-packages`
|
||||||
- :ref:`django-example`
|
- :ref:`django-example`
|
||||||
- :ref:`flask-example`
|
- :ref:`flask-example`
|
||||||
|
- :ref:`flask-blueprints-example`
|
||||||
- :ref:`aiohttp-example`
|
- :ref:`aiohttp-example`
|
||||||
- :ref:`sanic-example`
|
- :ref:`sanic-example`
|
||||||
- :ref:`fastapi-example`
|
- :ref:`fastapi-example`
|
||||||
|
|
|
@ -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]):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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()],
|
||||||
|
|
100
examples/miniapps/flask-blueprints/README.rst
Normal file
100
examples/miniapps/flask-blueprints/README.rst
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
Flask Blueprints + Dependency Injector Example
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
This is a `Flask <https://flask.palletsprojects.com/>`_ Blueprints +
|
||||||
|
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application.
|
||||||
|
|
||||||
|
The example application helps to search for repositories on the Github.
|
||||||
|
|
||||||
|
.. image:: screenshot.png
|
||||||
|
|
||||||
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
Create virtual environment:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
virtualenv venv
|
||||||
|
. venv/bin/activate
|
||||||
|
|
||||||
|
Install requirements:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
To run the application do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export FLASK_APP=githubnavigator.application
|
||||||
|
export FLASK_ENV=development
|
||||||
|
flask run
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
* Serving Flask app "githubnavigator.application" (lazy loading)
|
||||||
|
* Environment: development
|
||||||
|
* Debug mode: on
|
||||||
|
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||||
|
* Restarting with fsevents reloader
|
||||||
|
* Debugger is active!
|
||||||
|
* Debugger PIN: 473-587-859
|
||||||
|
|
||||||
|
After that visit http://127.0.0.1:5000/ in your browser.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Github has a rate limit. When the rate limit is exceed you will see an exception
|
||||||
|
``github.GithubException.RateLimitExceededException``. For unauthenticated requests, the rate
|
||||||
|
limit allows for up to 60 requests per hour. To extend the limit to 5000 requests per hour you
|
||||||
|
need to set personal access token.
|
||||||
|
|
||||||
|
It's easy:
|
||||||
|
|
||||||
|
- Follow this `guide <https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token>`_ to create a token.
|
||||||
|
- Set a token to the environment variable:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export GITHUB_TOKEN=<your token>
|
||||||
|
|
||||||
|
- Restart the app with ``flask run``
|
||||||
|
|
||||||
|
`Read more on Github rate limit <https://developer.github.com/v3/#rate-limiting>`_
|
||||||
|
|
||||||
|
Test
|
||||||
|
----
|
||||||
|
|
||||||
|
This application comes with the unit tests.
|
||||||
|
|
||||||
|
To run the tests do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
py.test githubnavigator/tests.py --cov=githubnavigator
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||||
|
plugins: flask-1.0.0, cov-2.10.0
|
||||||
|
collected 2 items
|
||||||
|
|
||||||
|
githubnavigator/tests.py .. [100%]
|
||||||
|
|
||||||
|
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
----------------------------------------------------
|
||||||
|
githubnavigator/__init__.py 0 0 100%
|
||||||
|
githubnavigator/application.py 15 0 100%
|
||||||
|
githubnavigator/blueprints/example.py 12 0 100%
|
||||||
|
githubnavigator/containers.py 7 0 100%
|
||||||
|
githubnavigator/services.py 14 0 100%
|
||||||
|
githubnavigator/tests.py 34 0 100%
|
||||||
|
-----------------------------------------------------------
|
||||||
|
TOTAL 82 0 100%
|
5
examples/miniapps/flask-blueprints/config.yml
Normal file
5
examples/miniapps/flask-blueprints/config.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
github:
|
||||||
|
request_timeout: 10
|
||||||
|
default:
|
||||||
|
query: "Dependency Injector"
|
||||||
|
limit: 10
|
|
@ -0,0 +1 @@
|
||||||
|
"""Top-level package."""
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""Application module."""
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from flask_bootstrap import Bootstrap
|
||||||
|
|
||||||
|
from .containers import Container
|
||||||
|
from .blueprints import example
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> Flask:
|
||||||
|
container = Container()
|
||||||
|
container.config.from_yaml('config.yml')
|
||||||
|
container.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||||
|
container.wire(modules=[example])
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.container = container
|
||||||
|
app.register_blueprint(example.blueprint)
|
||||||
|
|
||||||
|
bootstrap = Bootstrap()
|
||||||
|
bootstrap.init_app(app)
|
||||||
|
|
||||||
|
return app
|
|
@ -0,0 +1 @@
|
||||||
|
"""Blueprints package."""
|
|
@ -0,0 +1,30 @@
|
||||||
|
"""Example blueprint."""
|
||||||
|
|
||||||
|
from flask import Blueprint, request, render_template
|
||||||
|
from dependency_injector.wiring import inject, Provide
|
||||||
|
|
||||||
|
from githubnavigator.services import SearchService
|
||||||
|
from githubnavigator.containers import Container
|
||||||
|
|
||||||
|
|
||||||
|
blueprint = Blueprint('example', __name__, template_folder='templates/')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/')
|
||||||
|
@inject
|
||||||
|
def index(
|
||||||
|
search_service: SearchService = Provide[Container.search_service],
|
||||||
|
default_query: str = Provide[Container.config.default.query],
|
||||||
|
default_limit: int = Provide[Container.config.default.limit.as_int()],
|
||||||
|
):
|
||||||
|
query = request.args.get('query', default_query)
|
||||||
|
limit = request.args.get('limit', default_limit, int)
|
||||||
|
|
||||||
|
repositories = search_service.search_repositories(query, limit)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'index.html',
|
||||||
|
query=query,
|
||||||
|
limit=limit,
|
||||||
|
repositories=repositories,
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from github import Github
|
||||||
|
|
||||||
|
from . import services
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
github_client = providers.Factory(
|
||||||
|
Github,
|
||||||
|
login_or_token=config.github.auth_token,
|
||||||
|
timeout=config.github.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
search_service = providers.Factory(
|
||||||
|
services.SearchService,
|
||||||
|
github_client=github_client,
|
||||||
|
)
|
|
@ -0,0 +1,44 @@
|
||||||
|
"""Services module."""
|
||||||
|
|
||||||
|
from github import Github
|
||||||
|
from github.Repository import Repository
|
||||||
|
from github.Commit import Commit
|
||||||
|
|
||||||
|
|
||||||
|
class SearchService:
|
||||||
|
"""Search service performs search on Github."""
|
||||||
|
|
||||||
|
def __init__(self, github_client: Github):
|
||||||
|
self._github_client = github_client
|
||||||
|
|
||||||
|
def search_repositories(self, query, limit):
|
||||||
|
"""Search for repositories and return formatted data."""
|
||||||
|
repositories = self._github_client.search_repositories(
|
||||||
|
query=query,
|
||||||
|
**{'in': 'name'},
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
self._format_repo(repository)
|
||||||
|
for repository in repositories[:limit]
|
||||||
|
]
|
||||||
|
|
||||||
|
def _format_repo(self, repository: Repository):
|
||||||
|
commits = repository.get_commits()
|
||||||
|
return {
|
||||||
|
'url': repository.html_url,
|
||||||
|
'name': repository.name,
|
||||||
|
'owner': {
|
||||||
|
'login': repository.owner.login,
|
||||||
|
'url': repository.owner.html_url,
|
||||||
|
'avatar_url': repository.owner.avatar_url,
|
||||||
|
},
|
||||||
|
'latest_commit': self._format_commit(commits[0]) if commits else {},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _format_commit(self, commit: Commit):
|
||||||
|
return {
|
||||||
|
'sha': commit.sha,
|
||||||
|
'url': commit.html_url,
|
||||||
|
'message': commit.commit.message,
|
||||||
|
'author_name': commit.commit.author.name,
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
{{ bootstrap.load_css() }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Your page content -->
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
{{ bootstrap.load_js() }}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,70 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Github Navigator{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="mb-4">Github Navigator</h1>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<div class="form-group form-row">
|
||||||
|
<div class="col-10">
|
||||||
|
<label for="search_query" class="col-form-label">
|
||||||
|
Search for:
|
||||||
|
</label>
|
||||||
|
<input class="form-control" type="text" id="search_query"
|
||||||
|
placeholder="Type something to search on the GitHub"
|
||||||
|
name="query"
|
||||||
|
value="{{ query if query }}">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="search_limit" class="col-form-label">
|
||||||
|
Limit:
|
||||||
|
</label>
|
||||||
|
<select class="form-control" id="search_limit" name="limit">
|
||||||
|
{% for value in [5, 10, 20] %}
|
||||||
|
<option {% if value == limit %}selected{% endif %}>
|
||||||
|
{{ value }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p><small>Results found: {{ repositories|length }}</small></p>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Repository</th>
|
||||||
|
<th class="text-nowrap">Repository owner</th>
|
||||||
|
<th class="text-nowrap">Last commit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for repository in repositories %} {{n}}
|
||||||
|
<tr>
|
||||||
|
<th>{{ loop.index }}</th>
|
||||||
|
<td><a href="{{ repository.url }}">
|
||||||
|
{{ repository.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td><a href="{{ repository.owner.url }}">
|
||||||
|
<img src="{{ repository.owner.avatar_url }}"
|
||||||
|
alt="avatar" height="24" width="24"/></a>
|
||||||
|
<a href="{{ repository.owner.url }}">
|
||||||
|
{{ repository.owner.login }}</a>
|
||||||
|
</td>
|
||||||
|
<td><a href="{{ repository.latest_commit.url }}">
|
||||||
|
{{ repository.latest_commit.sha }}</a>
|
||||||
|
{{ repository.latest_commit.message }}
|
||||||
|
{{ repository.latest_commit.author_name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
71
examples/miniapps/flask-blueprints/githubnavigator/tests.py
Normal file
71
examples/miniapps/flask-blueprints/githubnavigator/tests.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
"""Tests module."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from github import Github
|
||||||
|
from flask import url_for
|
||||||
|
|
||||||
|
from .application import create_app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app():
|
||||||
|
app = create_app()
|
||||||
|
yield app
|
||||||
|
app.container.unwire()
|
||||||
|
|
||||||
|
|
||||||
|
def test_index(client, app):
|
||||||
|
github_client_mock = mock.Mock(spec=Github)
|
||||||
|
github_client_mock.search_repositories.return_value = [
|
||||||
|
mock.Mock(
|
||||||
|
html_url='repo1-url',
|
||||||
|
name='repo1-name',
|
||||||
|
owner=mock.Mock(
|
||||||
|
login='owner1-login',
|
||||||
|
html_url='owner1-url',
|
||||||
|
avatar_url='owner1-avatar-url',
|
||||||
|
),
|
||||||
|
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||||
|
),
|
||||||
|
mock.Mock(
|
||||||
|
html_url='repo2-url',
|
||||||
|
name='repo2-name',
|
||||||
|
owner=mock.Mock(
|
||||||
|
login='owner2-login',
|
||||||
|
html_url='owner2-url',
|
||||||
|
avatar_url='owner2-avatar-url',
|
||||||
|
),
|
||||||
|
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
with app.container.github_client.override(github_client_mock):
|
||||||
|
response = client.get(url_for('example.index'))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Results found: 2' in response.data
|
||||||
|
|
||||||
|
assert b'repo1-url' in response.data
|
||||||
|
assert b'repo1-name' in response.data
|
||||||
|
assert b'owner1-login' in response.data
|
||||||
|
assert b'owner1-url' in response.data
|
||||||
|
assert b'owner1-avatar-url' in response.data
|
||||||
|
|
||||||
|
assert b'repo2-url' in response.data
|
||||||
|
assert b'repo2-name' in response.data
|
||||||
|
assert b'owner2-login' in response.data
|
||||||
|
assert b'owner2-url' in response.data
|
||||||
|
assert b'owner2-avatar-url' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_index_no_results(client, app):
|
||||||
|
github_client_mock = mock.Mock(spec=Github)
|
||||||
|
github_client_mock.search_repositories.return_value = []
|
||||||
|
|
||||||
|
with app.container.github_client.override(github_client_mock):
|
||||||
|
response = client.get(url_for('example.index'))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Results found: 0' in response.data
|
7
examples/miniapps/flask-blueprints/requirements.txt
Normal file
7
examples/miniapps/flask-blueprints/requirements.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
dependency-injector
|
||||||
|
flask
|
||||||
|
bootstrap-flask
|
||||||
|
pygithub
|
||||||
|
pyyaml
|
||||||
|
pytest-flask
|
||||||
|
pytest-cov
|
BIN
examples/miniapps/flask-blueprints/screenshot.png
Normal file
BIN
examples/miniapps/flask-blueprints/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 647 KiB |
|
@ -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%
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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'):
|
||||||
|
|
|
@ -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%
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
@ -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)})
|
||||||
|
|
||||||
|
|
|
@ -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!'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Top-level package."""
|
"""Top-level package."""
|
||||||
|
|
||||||
__version__ = '4.3.9'
|
__version__ = '4.4.0'
|
||||||
"""Version number.
|
"""Version number.
|
||||||
|
|
||||||
:type: str
|
:type: str
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user