mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 17:47:02 +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/providers/*.h
|
||||
src/dependency_injector/providers/*.so
|
||||
|
||||
# Workspace for samples
|
||||
.workspace/
|
||||
|
|
|
@ -78,7 +78,7 @@ Key features of the ``Dependency Injector``:
|
|||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -97,6 +97,7 @@ Key features of the ``Dependency Injector``:
|
|||
)
|
||||
|
||||
|
||||
@inject
|
||||
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
|
||||
django
|
||||
flask
|
||||
flask-blueprints
|
||||
aiohttp
|
||||
sanic
|
||||
fastapi
|
||||
|
|
|
@ -85,7 +85,7 @@ Key features of the ``Dependency Injector``:
|
|||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -104,6 +104,7 @@ Key features of the ``Dependency Injector``:
|
|||
)
|
||||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
...
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ the dependency.
|
|||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -181,6 +181,7 @@ the dependency.
|
|||
)
|
||||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
...
|
||||
|
||||
|
@ -282,6 +283,7 @@ Choose one of the following as a next step:
|
|||
- :ref:`decoupled-packages`
|
||||
- :ref:`django-example`
|
||||
- :ref:`flask-example`
|
||||
- :ref:`flask-blueprints-example`
|
||||
- :ref:`aiohttp-example`
|
||||
- :ref:`sanic-example`
|
||||
- :ref:`fastapi-example`
|
||||
|
|
|
@ -7,6 +7,17 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
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
|
||||
-----
|
||||
- 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
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 23
|
||||
:emphasize-lines: 24
|
||||
|
||||
Framework initializes and injects the resource into the function. With the ``Closing`` marker
|
||||
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``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 4-7,10-13,17
|
||||
:emphasize-lines: 4-7,10-14,18
|
||||
|
||||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
request: web.Request,
|
||||
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``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 13-14,16-17
|
||||
:emphasize-lines: 14-15,17-18
|
||||
|
||||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
request: web.Request,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
|
@ -821,11 +823,11 @@ You should see:
|
|||
giphynavigator/application.py 12 0 100%
|
||||
giphynavigator/containers.py 6 0 100%
|
||||
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/tests.py 37 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 87 10 89%
|
||||
TOTAL 88 10 89%
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -442,18 +442,19 @@ and call the ``run()`` method. We will use :ref:`wiring` feature.
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3-7,11-12,19
|
||||
:emphasize-lines: 3-7,11-13,20
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
|
||||
dispatcher.run()
|
||||
|
||||
|
@ -992,14 +993,14 @@ You should see:
|
|||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
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/dispatcher.py 44 5 89%
|
||||
monitoringdaemon/http.py 6 3 50%
|
||||
monitoringdaemon/monitors.py 23 1 96%
|
||||
monitoringdaemon/tests.py 37 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 133 21 84%
|
||||
TOTAL 134 22 84%
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -575,18 +575,19 @@ Let's inject the ``lister`` into the ``main()`` function.
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3-7,11,18
|
||||
:emphasize-lines: 3-7,11-12,19
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
...
|
||||
|
||||
|
@ -606,18 +607,19 @@ Francis Lawrence and movies released in 2016.
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 12-18
|
||||
:emphasize-lines: 13-19
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print('Francis Lawrence movies:')
|
||||
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``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 24
|
||||
:emphasize-lines: 25
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print('Francis Lawrence movies:')
|
||||
for movie in lister.movies_directed_by('Francis Lawrence'):
|
||||
|
@ -1023,14 +1026,14 @@ You should see:
|
|||
Name Stmts Miss Cover
|
||||
------------------------------------------
|
||||
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/entities.py 7 1 86%
|
||||
movies/finders.py 26 13 50%
|
||||
movies/listers.py 8 0 100%
|
||||
movies/tests.py 24 0 100%
|
||||
------------------------------------------
|
||||
TOTAL 91 31 66%
|
||||
TOTAL 92 32 65%
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -707,17 +707,19 @@ Let's inject ``SearchService`` into the ``index`` view. We will use :ref:`Wiring
|
|||
Edit ``views.py``:
|
||||
|
||||
.. 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."""
|
||||
|
||||
from flask import request, render_template
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def index(search_service: SearchService = Provide[Container.search_service]):
|
||||
query = request.args.get('query', 'Dependency Injector')
|
||||
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``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10-16
|
||||
:emphasize-lines: 11-17
|
||||
|
||||
"""Views module."""
|
||||
|
||||
from flask import request, render_template
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def index(
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
default_query: str = Provide[Container.config.default.query],
|
||||
|
@ -972,9 +975,9 @@ You should see:
|
|||
githubnavigator/containers.py 7 0 100%
|
||||
githubnavigator/services.py 14 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::
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ Wiring feature provides a way to inject container providers into the functions a
|
|||
|
||||
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.
|
||||
- **Wire the container with the markers in the code**. Call ``container.wire()``
|
||||
specifying modules and packages you would like to wire it with.
|
||||
|
@ -25,9 +26,10 @@ a function or method argument:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
|
@ -40,9 +42,10 @@ There are two types of markers:
|
|||
|
||||
.. 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]):
|
||||
bar = bar_provider()
|
||||
...
|
||||
|
@ -51,18 +54,22 @@ You can use configuration, provided instance and sub-container providers as you
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(token: str = Provide[Container.config.api_token]):
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(timeout: int = Provide[Container.config.timeout.as_(int)]):
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(baz: Baz = Provide[Container.bar.provided.baz]):
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.subcontainer.bar]):
|
||||
...
|
||||
|
||||
|
@ -100,6 +107,7 @@ When wiring is done functions and methods with the markers are patched to provid
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
|
@ -199,6 +207,7 @@ Take a look at other application examples:
|
|||
- :ref:`decoupled-packages`
|
||||
- :ref:`django-example`
|
||||
- :ref:`flask-example`
|
||||
- :ref:`flask-blueprints-example`
|
||||
- :ref:`aiohttp-example`
|
||||
- :ref:`sanic-example`
|
||||
- :ref:`fastapi-example`
|
||||
|
|
|
@ -2,7 +2,7 @@ import sys
|
|||
from unittest import mock
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from after import ApiClient, Service
|
||||
|
||||
|
@ -23,6 +23,7 @@ class Container(containers.DeclarativeContainer):
|
|||
)
|
||||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
...
|
||||
|
||||
|
|
|
@ -111,8 +111,8 @@ The output should be something like:
|
|||
giphynavigator/application.py 12 0 100%
|
||||
giphynavigator/containers.py 6 0 100%
|
||||
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/tests.py 37 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 87 10 89%
|
||||
TOTAL 88 10 89%
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
request: web.Request,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import UserService, AuthService, PhotoService
|
||||
from .containers import Application
|
||||
|
||||
|
||||
@inject
|
||||
def main(
|
||||
email: str,
|
||||
password: str,
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import UserService, AuthService, PhotoService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(
|
||||
email: str,
|
||||
password: str,
|
||||
|
|
|
@ -76,11 +76,11 @@ The output should be something like:
|
|||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
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/dispatcher.py 44 5 89%
|
||||
monitoringdaemon/http.py 6 3 50%
|
||||
monitoringdaemon/monitors.py 23 1 96%
|
||||
monitoringdaemon/tests.py 37 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 133 21 84%
|
||||
TOTAL 134 22 84%
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
|
||||
dispatcher.run()
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .user.repositories import UserRepository
|
||||
from .photo.repositories import PhotoRepository
|
||||
|
@ -10,6 +10,7 @@ from .analytics.services import AggregationService
|
|||
from .containers import ApplicationContainer
|
||||
|
||||
|
||||
@inject
|
||||
def main(
|
||||
user_repository: UserRepository = Provide[
|
||||
ApplicationContainer.user_package.user_repository
|
||||
|
|
|
@ -108,6 +108,6 @@ The output should be something like:
|
|||
web/apps.py 7 0 100%
|
||||
web/tests.py 28 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.shortcuts import render
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from githubnavigator.containers import Container
|
||||
from githubnavigator.services import SearchService
|
||||
|
||||
|
||||
@inject
|
||||
def index(
|
||||
request: HttpRequest,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
|
|
|
@ -113,9 +113,9 @@ The output should be something like:
|
|||
giphynavigator/__init__.py 0 0 100%
|
||||
giphynavigator/application.py 13 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/services.py 9 1 89%
|
||||
giphynavigator/tests.py 38 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 85 10 88%
|
||||
TOTAL 86 10 88%
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
"""Endpoints module."""
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
query: str = Provide[Container.config.default.query],
|
||||
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/services.py 14 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."""
|
||||
|
||||
from flask import request, render_template
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def index(
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
default_query: str = Provide[Container.config.default.query],
|
||||
|
|
|
@ -68,11 +68,11 @@ The output should be something like:
|
|||
Name Stmts Miss Cover
|
||||
------------------------------------------
|
||||
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/entities.py 7 1 86%
|
||||
movies/finders.py 26 13 50%
|
||||
movies/listers.py 8 0 100%
|
||||
movies/tests.py 24 0 100%
|
||||
------------------------------------------
|
||||
TOTAL 91 31 66%
|
||||
TOTAL 92 32 65%
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print('Francis Lawrence movies:')
|
||||
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/containers.py 6 0 100%
|
||||
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/tests.py 34 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 89 14 84%
|
||||
TOTAL 90 14 84%
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
from sanic.request import Request
|
||||
from sanic.response import HTTPResponse, json
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
@inject
|
||||
async def index(
|
||||
request: Request,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import sys
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
class Service:
|
||||
|
@ -15,6 +15,7 @@ class Container(containers.DeclarativeContainer):
|
|||
service = providers.Factory(Service)
|
||||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import sys
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from flask import Flask, json
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ class Container(containers.DeclarativeContainer):
|
|||
service = providers.Factory(Service)
|
||||
|
||||
|
||||
@inject
|
||||
def index_view(service: Service = Provide[Container.service]) -> str:
|
||||
return json.dumps({'service_id': id(service)})
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import sys
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ class Container(containers.DeclarativeContainer):
|
|||
service = providers.Resource(init_service)
|
||||
|
||||
|
||||
@inject
|
||||
def index_view(service: Service = Closing[Provide[Container.service]]):
|
||||
assert service is current_app.container.service()
|
||||
return 'Hello World!'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Top-level package."""
|
||||
|
||||
__version__ = '4.3.9'
|
||||
__version__ = '4.4.0'
|
||||
"""Version number.
|
||||
|
||||
:type: str
|
||||
|
|
|
@ -6,7 +6,19 @@ import importlib
|
|||
import pkgutil
|
||||
import sys
|
||||
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):
|
||||
from typing import GenericMeta
|
||||
|
@ -21,15 +33,35 @@ from . import providers
|
|||
__all__ = (
|
||||
'wire',
|
||||
'unwire',
|
||||
'inject',
|
||||
'Provide',
|
||||
'Provider',
|
||||
'Closing',
|
||||
)
|
||||
|
||||
T = TypeVar('T')
|
||||
F = TypeVar('F', bound=Callable[..., 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:
|
||||
|
||||
def __init__(self, container):
|
||||
|
@ -152,7 +184,7 @@ class ProvidersMap:
|
|||
return providers_map
|
||||
|
||||
|
||||
def wire(
|
||||
def wire( # noqa: C901
|
||||
container: Container,
|
||||
*,
|
||||
modules: Optional[Iterable[ModuleType]] = None,
|
||||
|
@ -179,6 +211,9 @@ def wire(
|
|||
for method_name, method in inspect.getmembers(member, _is_method):
|
||||
_patch_method(member, method_name, method, providers_map)
|
||||
|
||||
for patched in _patched_registry.get_from_module(module):
|
||||
_bind_injections(patched, providers_map)
|
||||
|
||||
|
||||
def unwire(
|
||||
*,
|
||||
|
@ -201,6 +236,17 @@ def unwire(
|
|||
for method_name, method in inspect.getmembers(member, inspect.isfunction):
|
||||
_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(
|
||||
module: ModuleType,
|
||||
|
@ -208,11 +254,16 @@ def _patch_fn(
|
|||
fn: Callable[..., Any],
|
||||
providers_map: ProvidersMap,
|
||||
) -> None:
|
||||
injections, closing = _resolve_injections(fn, providers_map)
|
||||
if not injections:
|
||||
return
|
||||
patched = _patch_with_injections(fn, injections, closing)
|
||||
setattr(module, name, _wrap_patched(patched, fn, 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)
|
||||
|
||||
setattr(module, name, fn)
|
||||
|
||||
|
||||
def _patch_method(
|
||||
|
@ -221,28 +272,27 @@ def _patch_method(
|
|||
method: Callable[..., Any],
|
||||
providers_map: ProvidersMap,
|
||||
) -> None:
|
||||
injections, closing = _resolve_injections(method, providers_map)
|
||||
if not injections:
|
||||
return
|
||||
|
||||
if hasattr(cls, '__dict__') \
|
||||
and name in cls.__dict__ \
|
||||
and isinstance(cls.__dict__[name], (classmethod, staticmethod)):
|
||||
method = cls.__dict__[name]
|
||||
patched = _patch_with_injections(method.__func__, injections, closing)
|
||||
patched = type(method)(patched)
|
||||
fn = method.__func__
|
||||
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):
|
||||
patched.__wired__ = True
|
||||
patched.__original__ = original
|
||||
patched.__injections__ = injections
|
||||
patched.__closing__ = closing
|
||||
return patched
|
||||
if isinstance(method, (classmethod, staticmethod)):
|
||||
fn = type(method)(fn)
|
||||
|
||||
setattr(cls, name, fn)
|
||||
|
||||
|
||||
def _unpatch(
|
||||
|
@ -250,14 +300,20 @@ def _unpatch(
|
|||
name: str,
|
||||
fn: Callable[..., Any],
|
||||
) -> 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):
|
||||
return
|
||||
setattr(module, name, _get_original_from_patched(fn))
|
||||
|
||||
_unbind_injections(fn)
|
||||
|
||||
|
||||
def _resolve_injections(
|
||||
def _fetch_reference_injections(
|
||||
fn: Callable[..., Any],
|
||||
providers_map: ProvidersMap,
|
||||
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||
signature = inspect.signature(fn)
|
||||
|
||||
|
@ -268,24 +324,33 @@ def _resolve_injections(
|
|||
continue
|
||||
marker = parameter.default
|
||||
|
||||
closing_modifier = False
|
||||
if isinstance(marker, Closing):
|
||||
closing_modifier = True
|
||||
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)
|
||||
|
||||
if provider is None:
|
||||
continue
|
||||
|
||||
if closing_modifier:
|
||||
closing[parameter_name] = provider
|
||||
|
||||
if isinstance(marker, Provide):
|
||||
injections[parameter_name] = provider
|
||||
fn.__injections__[injection] = 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):
|
||||
|
@ -303,26 +368,34 @@ def _is_method(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):
|
||||
_patched = _get_async_patched(fn, injections, closing)
|
||||
patched = _get_async_patched(fn)
|
||||
else:
|
||||
_patched = _get_patched(fn, injections, closing)
|
||||
return _patched
|
||||
patched = _get_sync_patched(fn)
|
||||
|
||||
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)
|
||||
def _patched(*args, **kwargs):
|
||||
to_inject = kwargs.copy()
|
||||
for injection, provider in injections.items():
|
||||
for injection, provider in _patched.__injections__.items():
|
||||
if injection not in kwargs \
|
||||
or _is_fastapi_default_arg_injection(injection, kwargs):
|
||||
to_inject[injection] = provider()
|
||||
|
||||
result = fn(*args, **to_inject)
|
||||
|
||||
for injection, provider in closing.items():
|
||||
for injection, provider in _patched.__closing__.items():
|
||||
if injection in kwargs \
|
||||
and not _is_fastapi_default_arg_injection(injection, kwargs):
|
||||
continue
|
||||
|
@ -334,18 +407,18 @@ def _get_patched(fn, injections, closing):
|
|||
return _patched
|
||||
|
||||
|
||||
def _get_async_patched(fn, injections, closing):
|
||||
def _get_async_patched(fn):
|
||||
@functools.wraps(fn)
|
||||
async def _patched(*args, **kwargs):
|
||||
to_inject = kwargs.copy()
|
||||
for injection, provider in injections.items():
|
||||
for injection, provider in _patched.__injections__.items():
|
||||
if injection not in kwargs \
|
||||
or _is_fastapi_default_arg_injection(injection, kwargs):
|
||||
to_inject[injection] = provider()
|
||||
|
||||
result = await fn(*args, **to_inject)
|
||||
|
||||
for injection, provider in closing.items():
|
||||
for injection, provider in _patched.__closing__.items():
|
||||
if injection in kwargs \
|
||||
and not _is_fastapi_default_arg_injection(injection, kwargs):
|
||||
continue
|
||||
|
@ -366,10 +439,6 @@ def _is_patched(fn):
|
|||
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:
|
||||
return (not isinstance(instance, type)
|
||||
and getattr(instance, '__IS_CONTAINER__', False) is True
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from decimal import Decimal
|
||||
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 .service import Service
|
||||
|
@ -11,30 +11,37 @@ from .service import Service
|
|||
|
||||
class TestClass:
|
||||
|
||||
@inject
|
||||
def __init__(self, service: Service = Provide[Container.service]):
|
||||
self.service = service
|
||||
|
||||
@inject
|
||||
def method(self, service: Service = Provide[Container.service]):
|
||||
return service
|
||||
|
||||
@classmethod
|
||||
@inject
|
||||
def class_method(cls, service: Service = Provide[Container.service]):
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
@inject
|
||||
def static_method(service: Service = Provide[Container.service]):
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_function(service: Service = Provide[Container.service]):
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_function_provider(service_provider: Callable[..., Service] = Provider[Container.service]):
|
||||
service = service_provider()
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_config_value(
|
||||
some_value_int: int = Provide[Container.config.a.b.c.as_int()],
|
||||
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
|
||||
|
||||
|
||||
@inject
|
||||
def test_provide_provider(service_provider: Callable[..., Service] = Provider[Container.service.provider]):
|
||||
service = service_provider()
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_provided_instance(some_value: int = Provide[Container.service.provided.foo['bar'].call()]):
|
||||
return some_value
|
||||
|
||||
|
||||
@inject
|
||||
def test_subcontainer_provider(some_value: int = Provide[Container.sub.int_object]):
|
||||
return some_value
|
||||
|
||||
|
||||
@inject
|
||||
def test_config_invariant(some_value: int = Provide[Container.config.option[Container.config.switch]]):
|
||||
return some_value
|
||||
|
||||
|
||||
@inject
|
||||
def test_provide_from_different_containers(
|
||||
service: Service = Provide[Container.service],
|
||||
some_value: int = Provide[SubContainer.int_object],
|
||||
):
|
||||
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 ...service import Service
|
||||
|
||||
|
||||
@inject
|
||||
def test_function(service: Service = Provide[Container.service]):
|
||||
return service
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, Closing
|
||||
from dependency_injector.wiring import inject, Provide, Closing
|
||||
|
||||
|
||||
class Service:
|
||||
|
@ -32,5 +32,6 @@ class Container(containers.DeclarativeContainer):
|
|||
service = providers.Resource(init_service)
|
||||
|
||||
|
||||
@inject
|
||||
def test_function(service: Service = Closing[Provide[Container.service]]):
|
||||
return service
|
||||
|
|
|
@ -226,6 +226,10 @@ class WiringTest(unittest.TestCase):
|
|||
self.assertEqual(result_2.init_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):
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user