mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-06-26 00:13:29 +03:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b4890dcf80 | ||
|
d9334ef1fe | ||
|
cc1b6ba3e6 | ||
|
42b0bde8d2 | ||
|
5d4eeb648a | ||
|
26e490bf0a | ||
|
6182b8448a | ||
|
7f854548d6 | ||
|
95db0eddc9 | ||
|
6d92df32aa | ||
|
c20c57ae7c | ||
|
7b2baeeb6f | ||
|
9653dfc263 | ||
|
e37b5181e4 | ||
|
a9970b63b9 | ||
|
170819c6ed | ||
|
4fab71c35b | ||
|
170263de4d | ||
|
b7efb1e3e2 | ||
|
dd8778bf20 | ||
|
edd8979bf6 | ||
|
af7364e062 |
42
README.rst
42
README.rst
|
@ -74,31 +74,39 @@ Key features of the ``Dependency Injector``:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration()
|
||||
|
||||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
)
|
||||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
Service,
|
||||
api_client=api_client,
|
||||
)
|
||||
service = providers.Factory(
|
||||
Service,
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
...
|
||||
|
||||
service = container.service()
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
main()
|
||||
|
||||
With the ``Dependency Injector`` you keep **application structure in one place**.
|
||||
This place is called **the container**. You use the container to manage all the components of the
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -83,31 +83,39 @@ Key features of the ``Dependency Injector``:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration()
|
||||
|
||||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
)
|
||||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
Service,
|
||||
api_client=api_client,
|
||||
)
|
||||
service = providers.Factory(
|
||||
Service,
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
...
|
||||
|
||||
service = container.service()
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
main()
|
||||
|
||||
With the ``Dependency Injector`` you keep **application structure in one place**.
|
||||
This place is called **the container**. You use the container to manage all the components of the
|
||||
|
|
|
@ -7,6 +7,17 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
follows `Semantic versioning`_
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
- Add ``wiring`` feature.
|
||||
- Deprecate ``ext.aiohttp`` module.
|
||||
- Deprecate ``ext.flask`` module.
|
||||
- Remove deprecated ``types`` module.
|
||||
- Deprecate ``.delegate()`` provider method in favor of ``.provider`` attribute.
|
||||
- Add ``sanic`` example.
|
||||
- Update ``aiohttp`` example.
|
||||
- Update ``flask`` example.
|
||||
|
||||
3.44.0
|
||||
------
|
||||
- Add native support of the generics to the providers: ``some_provider = providers.Provider[SomeClass]``.
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
|
||||
class ApiClient:
|
||||
|
||||
def __init__(self, api_key: str, timeout: int):
|
||||
self.api_key = api_key
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
def __init__(self, api_client: ApiClient):
|
||||
self.api_client = api_client
|
||||
from di import ApiClient, Service
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -30,9 +23,19 @@ class Container(containers.DeclarativeContainer):
|
|||
)
|
||||
|
||||
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
|
||||
service = container.service()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
main()
|
||||
|
||||
with container.api_client.override(mock.Mock()):
|
||||
main()
|
|
@ -14,5 +14,15 @@ class Service:
|
|||
self.api_client = api_client
|
||||
|
||||
|
||||
def main() -> None:
|
||||
service = Service(
|
||||
api_client=ApiClient(
|
||||
api_key=os.getenv('API_KEY'),
|
||||
timeout=os.getenv('TIMEOUT'),
|
||||
),
|
||||
)
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
|
||||
main()
|
|
@ -14,5 +14,10 @@ class Service:
|
|||
self.api_client = ApiClient()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main() -> None:
|
||||
service = Service()
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,11 +0,0 @@
|
|||
from unittest import mock
|
||||
|
||||
from demo import Container
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
|
||||
with container.api_client.override(mock.Mock()):
|
||||
service = container.service()
|
||||
assert isinstance(service.api_client, mock.Mock)
|
|
@ -1,8 +1,8 @@
|
|||
Aiohttp Dependency Injection Example
|
||||
====================================
|
||||
Dependency Injector + Aiohttp Example
|
||||
=====================================
|
||||
|
||||
Application ``giphynavigator`` is an `Aiohttp <https://docs.aiohttp.org/>`_ +
|
||||
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ application.
|
||||
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ example application.
|
||||
|
||||
Run
|
||||
---
|
||||
|
@ -107,11 +107,11 @@ The output should be something like:
|
|||
---------------------------------------------------
|
||||
giphynavigator/__init__.py 0 0 100%
|
||||
giphynavigator/__main__.py 5 5 0%
|
||||
giphynavigator/application.py 10 0 100%
|
||||
giphynavigator/containers.py 10 0 100%
|
||||
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/services.py 9 1 89%
|
||||
giphynavigator/tests.py 35 0 100%
|
||||
giphynavigator/views.py 7 0 100%
|
||||
giphynavigator/tests.py 37 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 90 15 83%
|
||||
TOTAL 92 15 84%
|
5
examples/miniapps/aiohttp/config.yml
Normal file
5
examples/miniapps/aiohttp/config.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
giphy:
|
||||
request_timeout: 10
|
||||
default:
|
||||
query: "Dependency Injector"
|
||||
limit: 10
|
|
@ -2,20 +2,23 @@
|
|||
|
||||
from aiohttp import web
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
from .containers import Container
|
||||
from . import handlers
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return aiohttp application."""
|
||||
container = ApplicationContainer()
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||
|
||||
app: web.Application = container.app()
|
||||
container.wire(modules=[handlers])
|
||||
|
||||
app = web.Application()
|
||||
app.container = container
|
||||
|
||||
app.add_routes([
|
||||
web.get('/', container.index_view.as_view()),
|
||||
web.get('/', handlers.index),
|
||||
])
|
||||
|
||||
return app
|
21
examples/miniapps/aiohttp/giphynavigator/containers.py
Normal file
21
examples/miniapps/aiohttp/giphynavigator/containers.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
from . import giphy, services
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
api_key=config.giphy.api_key,
|
||||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
giphy_client=giphy_client,
|
||||
)
|
|
@ -1,15 +1,17 @@
|
|||
"""Views module."""
|
||||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
async def index(
|
||||
request: web.Request,
|
||||
search_service: SearchService,
|
||||
default_query: str,
|
||||
default_limit: int,
|
||||
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()],
|
||||
) -> web.Response:
|
||||
query = request.query.get('query', default_query)
|
||||
limit = int(request.query.get('limit', default_limit))
|
|
@ -10,7 +10,9 @@ from giphynavigator.giphy import GiphyClient
|
|||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return create_app()
|
||||
app = create_app()
|
||||
yield app
|
||||
app.container.unwire()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -73,5 +75,5 @@ async def test_index_default_params(client, app):
|
|||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['query'] == app.container.config.search.default_query()
|
||||
assert data['limit'] == app.container.config.search.default_limit()
|
||||
assert data['query'] == app.container.config.default.query()
|
||||
assert data['limit'] == app.container.config.default.limit()
|
|
@ -1,8 +1,8 @@
|
|||
Flask Dependency Injection Example
|
||||
==================================
|
||||
Dependency Injector + Flask Example
|
||||
===================================
|
||||
|
||||
Application ``githubnavigator`` is a `Flask <https://flask.palletsprojects.com/>`_ +
|
||||
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ application.
|
||||
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ example application.
|
||||
|
||||
.. image:: screenshot.png
|
||||
|
||||
|
@ -90,10 +90,10 @@ The output should be something like:
|
|||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
githubnavigator/__init__.py 0 0 100%
|
||||
githubnavigator/application.py 11 0 100%
|
||||
githubnavigator/containers.py 13 0 100%
|
||||
githubnavigator/application.py 15 0 100%
|
||||
githubnavigator/containers.py 7 0 100%
|
||||
githubnavigator/services.py 14 0 100%
|
||||
githubnavigator/tests.py 32 0 100%
|
||||
githubnavigator/views.py 7 0 100%
|
||||
githubnavigator/tests.py 34 0 100%
|
||||
githubnavigator/views.py 9 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 77 0 100%
|
||||
TOTAL 79 0 100%
|
5
examples/miniapps/flask/config.yml
Normal file
5
examples/miniapps/flask/config.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
github:
|
||||
request_timeout: 10
|
||||
default:
|
||||
query: "Dependency Injector"
|
||||
limit: 10
|
25
examples/miniapps/flask/githubnavigator/application.py
Normal file
25
examples/miniapps/flask/githubnavigator/application.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""Application module."""
|
||||
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
from .containers import Container
|
||||
from . import views
|
||||
|
||||
|
||||
def create_app():
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||
|
||||
container.wire(modules=[views])
|
||||
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
||||
app.add_url_rule('/', 'index', views.index)
|
||||
|
||||
return app
|
22
examples/miniapps/flask/githubnavigator/containers.py
Normal file
22
examples/miniapps/flask/githubnavigator/containers.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""Application 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,
|
||||
)
|
|
@ -11,7 +11,9 @@ from .application import create_app
|
|||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return create_app()
|
||||
app = create_app()
|
||||
yield app
|
||||
app.container.unwire()
|
||||
|
||||
|
||||
def test_index(client, app):
|
|
@ -1,14 +1,16 @@
|
|||
"""Views module."""
|
||||
|
||||
from flask import request, render_template
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def index(
|
||||
search_service: SearchService,
|
||||
default_query: str,
|
||||
default_limit: int,
|
||||
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)
|
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 647 KiB |
|
@ -1,5 +0,0 @@
|
|||
github:
|
||||
request_timeout: 10
|
||||
search:
|
||||
default_query: "Dependency Injector"
|
||||
default_limit: 10
|
|
@ -1,20 +0,0 @@
|
|||
"""Application module."""
|
||||
|
||||
from .containers import ApplicationContainer
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return Flask application."""
|
||||
container = ApplicationContainer()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||
|
||||
app = container.app()
|
||||
app.container = container
|
||||
|
||||
bootstrap = container.bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
||||
app.add_url_rule('/', view_func=container.index_view.as_view())
|
||||
|
||||
return app
|
|
@ -1,37 +0,0 @@
|
|||
"""Application containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import flask
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from github import Github
|
||||
|
||||
from . import views, services
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = flask.Application(Flask, __name__)
|
||||
|
||||
bootstrap = flask.Extension(Bootstrap)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
index_view = flask.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
default_query=config.search.default_query,
|
||||
default_limit=config.search.default_limit,
|
||||
)
|
|
@ -1,5 +0,0 @@
|
|||
giphy:
|
||||
request_timeout: 10
|
||||
search:
|
||||
default_query: "Dependency Injector"
|
||||
default_limit: 10
|
|
@ -1,33 +0,0 @@
|
|||
"""Application containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.ext import aiohttp
|
||||
from aiohttp import web
|
||||
|
||||
from . import giphy, services, views
|
||||
|
||||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
"""Application container."""
|
||||
|
||||
app = aiohttp.Application(web.Application)
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
api_key=config.giphy.api_key,
|
||||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
giphy_client=giphy_client,
|
||||
)
|
||||
|
||||
index_view = aiohttp.View(
|
||||
views.index,
|
||||
search_service=search_service,
|
||||
default_query=config.search.default_query,
|
||||
default_limit=config.search.default_limit,
|
||||
)
|
117
examples/miniapps/sanic/README.rst
Normal file
117
examples/miniapps/sanic/README.rst
Normal file
|
@ -0,0 +1,117 @@
|
|||
Dependency Injector + Sanic Example
|
||||
=====================================
|
||||
|
||||
Application ``giphynavigator`` is a `Sanic <https://sanic.readthedocs.io/en/latest/index.html>`_ +
|
||||
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ example application.
|
||||
|
||||
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 GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
|
||||
python -m giphynavigator
|
||||
|
||||
The output should be something like:
|
||||
|
||||
.. code-block::
|
||||
|
||||
[2020-09-23 18:16:31 -0400] [48258] [INFO] Goin' Fast @ http://0.0.0.0:8000
|
||||
[2020-09-23 18:16:31 -0400] [48258] [INFO] Starting worker [48258]
|
||||
|
||||
After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
|
||||
etc). You should see something like:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"query": "Dependency Injector",
|
||||
"limit": 10,
|
||||
"gifs": [
|
||||
{
|
||||
"url": "https://giphy.com/gifs/boxes-dependent-swbf2-6Eo7KzABxgJMY"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/depends-J56qCcOhk6hKE"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/web-series-ccstudios-bro-dependent-1lhU8KAVwmVVu"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/TheBoysTV-friends-friend-weneedeachother-XxR9qcIwcf5Jq404Sx"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/netflix-a-series-of-unfortunate-events-asoue-9rgeQXbwoK53pcxn7f"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/black-and-white-sad-skins-Hs4YzLs2zJuLu"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/always-there-for-you-i-am-here-PlayjhCco9jHBYrd9w"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/stream-famous-dollar-YT2dvOByEwXCdoYiA1"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/i-love-you-there-for-am-1BhGzgpZXYWwWMAGB1"
|
||||
},
|
||||
{
|
||||
"url": "https://giphy.com/gifs/life-like-twerk-9hlnWxjHqmH28"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
To create your own Giphy API key follow this
|
||||
`guide <https://support.giphy.com/hc/en-us/articles/360020283431-Request-A-GIPHY-API-Key>`_.
|
||||
|
||||
Test
|
||||
----
|
||||
|
||||
This application comes with the unit tests.
|
||||
|
||||
To run the tests do:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
py.test giphynavigator/tests.py --cov=giphynavigator
|
||||
|
||||
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: cov-2.10.0, sanic-1.6.1
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
Name Stmts Miss Cover
|
||||
---------------------------------------------------
|
||||
giphynavigator/__init__.py 0 0 100%
|
||||
giphynavigator/__main__.py 4 4 0%
|
||||
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/services.py 9 1 89%
|
||||
giphynavigator/tests.py 34 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 89 14 84%
|
5
examples/miniapps/sanic/config.yml
Normal file
5
examples/miniapps/sanic/config.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
giphy:
|
||||
request_timeout: 10
|
||||
default:
|
||||
query: "Dependency Injector"
|
||||
limit: 10
|
1
examples/miniapps/sanic/giphynavigator/__init__.py
Normal file
1
examples/miniapps/sanic/giphynavigator/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Top-level package."""
|
8
examples/miniapps/sanic/giphynavigator/__main__.py
Normal file
8
examples/miniapps/sanic/giphynavigator/__main__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
"""Main module."""
|
||||
|
||||
from .application import create_app
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
app.run(host='0.0.0.0', port=8000, debug=True)
|
22
examples/miniapps/sanic/giphynavigator/application.py
Normal file
22
examples/miniapps/sanic/giphynavigator/application.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""Application module."""
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
from .containers import Container
|
||||
from . import handlers
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create and return aiohttp application."""
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||
|
||||
container.wire(modules=[handlers])
|
||||
|
||||
app = Sanic('Giphy Navigator')
|
||||
app.container = container
|
||||
|
||||
app.add_route(handlers.index, '/')
|
||||
|
||||
return app
|
21
examples/miniapps/sanic/giphynavigator/containers.py
Normal file
21
examples/miniapps/sanic/giphynavigator/containers.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
"""Containers module."""
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
from . import giphy, services
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
api_key=config.giphy.api_key,
|
||||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
giphy_client=giphy_client,
|
||||
)
|
26
examples/miniapps/sanic/giphynavigator/giphy.py
Normal file
26
examples/miniapps/sanic/giphynavigator/giphy.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""Giphy client module."""
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
|
||||
|
||||
class GiphyClient:
|
||||
|
||||
API_URL = 'http://api.giphy.com/v1'
|
||||
|
||||
def __init__(self, api_key, timeout):
|
||||
self._api_key = api_key
|
||||
self._timeout = ClientTimeout(timeout)
|
||||
|
||||
async def search(self, query, limit):
|
||||
"""Make search API call and return result."""
|
||||
url = f'{self.API_URL}/gifs/search'
|
||||
params = {
|
||||
'q': query,
|
||||
'api_key': self._api_key,
|
||||
'limit': limit,
|
||||
}
|
||||
async with ClientSession(timeout=self._timeout) as session:
|
||||
async with session.get(url, params=params) as response:
|
||||
if response.status != 200:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
28
examples/miniapps/sanic/giphynavigator/handlers.py
Normal file
28
examples/miniapps/sanic/giphynavigator/handlers.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
"""Handlers module."""
|
||||
|
||||
from sanic.request import Request
|
||||
from sanic.response import HTTPResponse, json
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
async def index(
|
||||
request: Request,
|
||||
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()],
|
||||
) -> HTTPResponse:
|
||||
query = request.args.get('query', default_query)
|
||||
limit = int(request.args.get('limit', default_limit))
|
||||
|
||||
gifs = await search_service.search(query, limit)
|
||||
|
||||
return json(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
},
|
||||
)
|
18
examples/miniapps/sanic/giphynavigator/services.py
Normal file
18
examples/miniapps/sanic/giphynavigator/services.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""Services module."""
|
||||
|
||||
from .giphy import GiphyClient
|
||||
|
||||
|
||||
class SearchService:
|
||||
|
||||
def __init__(self, giphy_client: GiphyClient):
|
||||
self._giphy_client = giphy_client
|
||||
|
||||
async def search(self, query, limit):
|
||||
"""Search for gifs and return formatted data."""
|
||||
if not query:
|
||||
return []
|
||||
|
||||
result = await self._giphy_client.search(query, limit)
|
||||
|
||||
return [{'url': gif['url']} for gif in result['data']]
|
74
examples/miniapps/sanic/giphynavigator/tests.py
Normal file
74
examples/miniapps/sanic/giphynavigator/tests.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""Tests module."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from giphynavigator.application import create_app
|
||||
from giphynavigator.giphy import GiphyClient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = create_app()
|
||||
yield app
|
||||
app.container.unwire()
|
||||
|
||||
|
||||
async def test_index(app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
_, response = await app.asgi_client.get(
|
||||
'/',
|
||||
params={
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
data = response.json()
|
||||
assert data == {
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
'gifs': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_index_no_data(app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
_, response = await app.asgi_client.get('/')
|
||||
|
||||
assert response.status == 200
|
||||
data = response.json()
|
||||
assert data['gifs'] == []
|
||||
|
||||
|
||||
async def test_index_default_params(app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
_, response = await app.asgi_client.get('/')
|
||||
|
||||
assert response.status == 200
|
||||
data = response.json()
|
||||
assert data['query'] == app.container.config.default.query()
|
||||
assert data['limit'] == app.container.config.default.limit()
|
5
examples/miniapps/sanic/requirements.txt
Normal file
5
examples/miniapps/sanic/requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
dependency-injector
|
||||
sanic
|
||||
pyyaml
|
||||
pytest-sanic
|
||||
pytest-cov
|
|
@ -2,6 +2,8 @@
|
|||
max_line_length = 100
|
||||
max_complexity = 10
|
||||
exclude = types.py
|
||||
per-file-ignores =
|
||||
examples/demo/*: F841
|
||||
|
||||
[pydocstyle]
|
||||
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Top-level package."""
|
||||
|
||||
__version__ = '3.44.0'
|
||||
__version__ = '4.0.0a2'
|
||||
"""Version number.
|
||||
|
||||
:type: str
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
|||
from typing import Type, Dict, Tuple, Optional, Any, Union, ClassVar, Callable as _Callable
|
||||
from types import ModuleType
|
||||
from typing import Type, Dict, Tuple, Optional, Any, Union, ClassVar, Callable as _Callable, Iterable
|
||||
|
||||
from .providers import Provider
|
||||
|
||||
|
@ -16,6 +17,9 @@ class Container:
|
|||
def override_providers(self, **overriding_providers: Provider) -> None: ...
|
||||
def reset_last_overriding(self) -> None: ...
|
||||
def reset_override(self) -> None: ...
|
||||
def resolve_provider_name(self, provider_to_resolve: Provider) -> Optional[str]: ...
|
||||
def wire(self, modules: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None) -> None: ...
|
||||
def unwire(self) -> None: ...
|
||||
|
||||
|
||||
class DynamicContainer(Container): ...
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
"""Containers module."""
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from .errors import Error
|
||||
|
||||
from .providers cimport (
|
||||
Provider,
|
||||
deepcopy,
|
||||
)
|
||||
|
||||
|
||||
if sys.version_info[:2] >= (3, 6):
|
||||
from .wiring import wire, unwire
|
||||
else:
|
||||
def wire(*args, **kwargs):
|
||||
raise NotImplementedError('Wiring requires Python 3.6 or above')
|
||||
|
||||
def unwire(*args, **kwargs):
|
||||
raise NotImplementedError('Wiring requires Python 3.6 or above')
|
||||
|
||||
|
||||
class DynamicContainer(object):
|
||||
"""Dynamic inversion of control container.
|
||||
|
||||
|
@ -47,8 +58,11 @@ class DynamicContainer(object):
|
|||
:rtype: None
|
||||
"""
|
||||
self.provider_type = Provider
|
||||
self.providers = dict()
|
||||
self.providers = {}
|
||||
self.overridden = tuple()
|
||||
self.declarative_parent = None
|
||||
self.wired_to_modules = []
|
||||
self.wired_to_packages = []
|
||||
super(DynamicContainer, self).__init__()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
|
@ -60,6 +74,7 @@ class DynamicContainer(object):
|
|||
copied = self.__class__()
|
||||
copied.provider_type = Provider
|
||||
copied.overridden = deepcopy(self.overridden, memo)
|
||||
copied.declarative_parent = self.declarative_parent
|
||||
|
||||
for name, provider in deepcopy(self.providers, memo).items():
|
||||
setattr(copied, name, provider)
|
||||
|
@ -171,6 +186,34 @@ class DynamicContainer(object):
|
|||
for provider in six.itervalues(self.providers):
|
||||
provider.reset_override()
|
||||
|
||||
def wire(self, modules=None, packages=None):
|
||||
"""Wire container providers with provided packages and modules.
|
||||
|
||||
:rtype: None
|
||||
"""
|
||||
wire(
|
||||
container=self,
|
||||
modules=modules,
|
||||
packages=packages,
|
||||
)
|
||||
|
||||
if modules:
|
||||
self.wired_to_modules.extend(modules)
|
||||
|
||||
if packages:
|
||||
self.wired_to_packages.extend(packages)
|
||||
|
||||
def unwire(self):
|
||||
"""Unwire container providers from previously wired packages and modules."""
|
||||
unwire(
|
||||
modules=self.wired_to_modules,
|
||||
packages=self.wired_to_packages,
|
||||
)
|
||||
|
||||
self.wired_to_modules.clear()
|
||||
self.wired_to_packages.clear()
|
||||
|
||||
|
||||
|
||||
class DeclarativeContainerMetaClass(type):
|
||||
"""Declarative inversion of control container meta class."""
|
||||
|
@ -310,6 +353,7 @@ class DeclarativeContainer(object):
|
|||
"""
|
||||
container = cls.instance_type()
|
||||
container.provider_type = cls.provider_type
|
||||
container.declarative_parent = cls
|
||||
container.set_providers(**deepcopy(cls.providers))
|
||||
container.override_providers(**overriding_providers)
|
||||
return container
|
||||
|
@ -363,6 +407,15 @@ class DeclarativeContainer(object):
|
|||
for provider in six.itervalues(cls.providers):
|
||||
provider.reset_override()
|
||||
|
||||
@classmethod
|
||||
def resolve_provider_name(cls, provider_to_resolve):
|
||||
"""Try to resolve provider name by its instance."""
|
||||
for provider_name, container_provider in cls.providers.items():
|
||||
if container_provider is provider_to_resolve:
|
||||
return provider_name
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def override(object container):
|
||||
""":py:class:`DeclarativeContainer` overriding decorator.
|
||||
|
|
|
@ -3,10 +3,18 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
from dependency_injector import providers
|
||||
|
||||
|
||||
warnings.warn(
|
||||
'Module "dependency_injector.ext.aiohttp" is deprecated since '
|
||||
'version 4.0.0. Use "dependency_injector.wiring" module instead.',
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
class Application(providers.Singleton):
|
||||
"""Aiohttp application provider."""
|
||||
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
"""Flask extension module."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import warnings
|
||||
|
||||
from flask import request as flask_request
|
||||
|
||||
from dependency_injector import providers, errors
|
||||
|
||||
|
||||
warnings.warn(
|
||||
'Module "dependency_injector.ext.aiohttp" is deprecated since '
|
||||
'version 4.0.0. Use "dependency_injector.wiring" module instead.',
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
request = providers.Object(flask_request)
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -93,6 +93,10 @@ cdef class ConfigurationOption(Provider):
|
|||
cdef object __cache
|
||||
|
||||
|
||||
cdef class TypedConfigurationOption(Callable):
|
||||
pass
|
||||
|
||||
|
||||
cdef class Configuration(Object):
|
||||
cdef str __name
|
||||
cdef dict __children
|
||||
|
@ -181,9 +185,9 @@ cdef class List(Provider):
|
|||
|
||||
|
||||
cdef class Container(Provider):
|
||||
cdef object container_cls
|
||||
cdef dict overriding_providers
|
||||
cdef object container
|
||||
cdef object __container_cls
|
||||
cdef dict __overriding_providers
|
||||
cdef object __container
|
||||
|
||||
cpdef object _provide(self, tuple args, dict kwargs)
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ class Object(Provider, Generic[T]):
|
|||
class Delegate(Provider):
|
||||
def __init__(self, provides: Provider) -> None: ...
|
||||
def __call__(self, *args: Injection, **kwargs: Injection) -> Provider: ...
|
||||
@property
|
||||
def provides(self) -> Provider: ...
|
||||
|
||||
|
||||
class Dependency(Provider, Generic[T]):
|
||||
|
@ -122,14 +124,17 @@ class CoroutineDelegate(Delegate):
|
|||
|
||||
class ConfigurationOption(Provider):
|
||||
UNDEFINED: object
|
||||
def __init__(self, name: str, root: Configuration) -> None: ...
|
||||
def __init__(self, name: Tuple[str], root: Configuration) -> None: ...
|
||||
def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ...
|
||||
def __getattr__(self, item: str) -> ConfigurationOption: ...
|
||||
def __getitem__(self, item: str) -> ConfigurationOption: ...
|
||||
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
|
||||
@property
|
||||
def root(self) -> Configuration: ...
|
||||
def get_name(self) -> str: ...
|
||||
def as_int(self) -> Callable[int]: ...
|
||||
def as_float(self) -> Callable[float]: ...
|
||||
def as_(self, callback: _Callable[..., T], *args: Injection, **kwargs: Injection) -> Callable[T]: ...
|
||||
def get_name_segments(self) -> Tuple[Union[str, Provider]]: ...
|
||||
def as_int(self) -> TypedConfigurationOption[int]: ...
|
||||
def as_float(self) -> TypedConfigurationOption[float]: ...
|
||||
def as_(self, callback: _Callable[..., T], *args: Injection, **kwargs: Injection) -> TypedConfigurationOption[T]: ...
|
||||
def update(self, value: Any) -> None: ...
|
||||
def from_ini(self, filepath: str) -> None: ...
|
||||
def from_yaml(self, filepath: str) -> None: ...
|
||||
|
@ -137,11 +142,16 @@ class ConfigurationOption(Provider):
|
|||
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
||||
|
||||
|
||||
class TypedConfigurationOption(Callable[T]):
|
||||
@property
|
||||
def option(self) -> ConfigurationOption: ...
|
||||
|
||||
|
||||
class Configuration(Object):
|
||||
DEFAULT_NAME: str = 'config'
|
||||
def __init__(self, name: str = DEFAULT_NAME, default: Optional[Any] = None) -> None: ...
|
||||
def __getattr__(self, item: str) -> ConfigurationOption: ...
|
||||
def __getitem__(self, item: str) -> ConfigurationOption: ...
|
||||
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
|
||||
def get_name(self) -> str: ...
|
||||
def get(self, selector: str) -> Any: ...
|
||||
def set(self, selector: str, value: Any) -> OverridingContext: ...
|
||||
|
@ -268,6 +278,8 @@ class Container(Provider):
|
|||
def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Provider) -> None: ...
|
||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
||||
def __getattr__(self, name: str) -> Provider: ...
|
||||
@property
|
||||
def container(self) -> T: ...
|
||||
|
||||
|
||||
class Selector(Provider):
|
||||
|
|
|
@ -8,6 +8,7 @@ import re
|
|||
import sys
|
||||
import types
|
||||
import threading
|
||||
import warnings
|
||||
import weakref
|
||||
|
||||
try:
|
||||
|
@ -257,6 +258,11 @@ cdef class Provider(object):
|
|||
|
||||
:rtype: :py:class:`Delegate`
|
||||
"""
|
||||
warnings.warn(
|
||||
'Method ".delegate()" is deprecated since version 4.0.0. '
|
||||
'Use ".provider" attribute instead.',
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
return Delegate(self)
|
||||
|
||||
@property
|
||||
|
@ -391,6 +397,11 @@ cdef class Delegate(Provider):
|
|||
"""
|
||||
return self.__str__()
|
||||
|
||||
@property
|
||||
def provides(self):
|
||||
"""Return provider."""
|
||||
return self.__provides
|
||||
|
||||
cpdef object _provide(self, tuple args, dict kwargs):
|
||||
"""Return provided instance.
|
||||
|
||||
|
@ -1139,18 +1150,25 @@ cdef class ConfigurationOption(Provider):
|
|||
segment() if is_provider(segment) else segment for segment in self.__name
|
||||
)
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
return self.__root_ref()
|
||||
|
||||
def get_name(self):
|
||||
root = self.__root_ref()
|
||||
return '.'.join((root.get_name(), self._get_self_name()))
|
||||
|
||||
def get_name_segments(self):
|
||||
return self.__name
|
||||
|
||||
def as_int(self):
|
||||
return Callable(int, self)
|
||||
return TypedConfigurationOption(int, self)
|
||||
|
||||
def as_float(self):
|
||||
return Callable(float, self)
|
||||
return TypedConfigurationOption(float, self)
|
||||
|
||||
def as_(self, callback, *args, **kwargs):
|
||||
return Callable(callback, self, *args, **kwargs)
|
||||
return TypedConfigurationOption(callback, self, *args, **kwargs)
|
||||
|
||||
def override(self, value):
|
||||
if isinstance(value, Provider):
|
||||
|
@ -1262,6 +1280,13 @@ cdef class ConfigurationOption(Provider):
|
|||
self.override(value)
|
||||
|
||||
|
||||
cdef class TypedConfigurationOption(Callable):
|
||||
|
||||
@property
|
||||
def option(self):
|
||||
return self.args[0]
|
||||
|
||||
|
||||
cdef class Configuration(Object):
|
||||
"""Configuration provider provides configuration options to the other providers.
|
||||
|
||||
|
@ -2458,13 +2483,13 @@ cdef class Container(Provider):
|
|||
|
||||
def __init__(self, container_cls, container=None, **overriding_providers):
|
||||
"""Initialize provider."""
|
||||
self.container_cls = container_cls
|
||||
self.overriding_providers = overriding_providers
|
||||
self.__container_cls = container_cls
|
||||
self.__overriding_providers = overriding_providers
|
||||
|
||||
if container is None:
|
||||
container = container_cls()
|
||||
container.override_providers(**overriding_providers)
|
||||
self.container = container
|
||||
self.__container = container
|
||||
|
||||
super(Container, self).__init__()
|
||||
|
||||
|
@ -2475,9 +2500,9 @@ cdef class Container(Provider):
|
|||
return copied
|
||||
|
||||
copied = self.__class__(
|
||||
self.container_cls,
|
||||
deepcopy(self.container, memo),
|
||||
**deepcopy(self.overriding_providers, memo),
|
||||
self.__container_cls,
|
||||
deepcopy(self.__container, memo),
|
||||
**deepcopy(self.__overriding_providers, memo),
|
||||
)
|
||||
|
||||
return copied
|
||||
|
@ -2489,7 +2514,11 @@ cdef class Container(Provider):
|
|||
'\'{cls}\' object has no attribute '
|
||||
'\'{attribute_name}\''.format(cls=self.__class__.__name__,
|
||||
attribute_name=name))
|
||||
return getattr(self.container, name)
|
||||
return getattr(self.__container, name)
|
||||
|
||||
@property
|
||||
def container(self):
|
||||
return self.__container
|
||||
|
||||
def override(self, provider):
|
||||
"""Override provider with another provider."""
|
||||
|
@ -2497,7 +2526,7 @@ cdef class Container(Provider):
|
|||
|
||||
cpdef object _provide(self, tuple args, dict kwargs):
|
||||
"""Return single instance."""
|
||||
return self.container
|
||||
return self.__container
|
||||
|
||||
|
||||
cdef class Selector(Provider):
|
||||
|
@ -2657,6 +2686,11 @@ cdef class ProvidedInstance(Provider):
|
|||
def __getitem__(self, item):
|
||||
return ItemGetter(self, item)
|
||||
|
||||
@property
|
||||
def provides(self):
|
||||
"""Return provider."""
|
||||
return self.__provider
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
return MethodCaller(self, *args, **kwargs)
|
||||
|
||||
|
@ -2696,6 +2730,16 @@ cdef class AttributeGetter(Provider):
|
|||
def __getitem__(self, item):
|
||||
return ItemGetter(self, item)
|
||||
|
||||
@property
|
||||
def provides(self):
|
||||
"""Return provider."""
|
||||
return self.__provider
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return name of the attribute."""
|
||||
return self.__attribute
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
return MethodCaller(self, *args, **kwargs)
|
||||
|
||||
|
@ -2736,6 +2780,16 @@ cdef class ItemGetter(Provider):
|
|||
def __getitem__(self, item):
|
||||
return ItemGetter(self, item)
|
||||
|
||||
@property
|
||||
def provides(self):
|
||||
"""Return provider."""
|
||||
return self.__provider
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return name of the item."""
|
||||
return self.__item
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
return MethodCaller(self, *args, **kwargs)
|
||||
|
||||
|
@ -2787,6 +2841,37 @@ cdef class MethodCaller(Provider):
|
|||
def __getitem__(self, item):
|
||||
return ItemGetter(self, item)
|
||||
|
||||
@property
|
||||
def provides(self):
|
||||
"""Return provider."""
|
||||
return self.__provider
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
"""Return positional argument injections."""
|
||||
cdef int index
|
||||
cdef PositionalInjection arg
|
||||
cdef list args
|
||||
|
||||
args = list()
|
||||
for index in range(self.__args_len):
|
||||
arg = self.__args[index]
|
||||
args.append(arg.__value)
|
||||
return tuple(args)
|
||||
|
||||
@property
|
||||
def kwargs(self):
|
||||
"""Return keyword argument injections."""
|
||||
cdef int index
|
||||
cdef NamedInjection kwarg
|
||||
cdef dict kwargs
|
||||
|
||||
kwargs = dict()
|
||||
for index in range(self.__kwargs_len):
|
||||
kwarg = self.__kwargs[index]
|
||||
kwargs[kwarg.__name] = kwarg.__value
|
||||
return kwargs
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
return MethodCaller(self, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
from typing import TypeVar, Generic, Any
|
||||
import warnings
|
||||
|
||||
|
||||
warnings.warn(
|
||||
'Types module is deprecated since version 3.44.0. Use "providers" module instead: '
|
||||
'providers.Provider[SomeClass]',
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
Injection = Any
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class Provider(Generic[T]):
|
||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
321
src/dependency_injector/wiring.py
Normal file
321
src/dependency_injector/wiring.py
Normal file
|
@ -0,0 +1,321 @@
|
|||
"""Wiring module."""
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import pkgutil
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Optional, Iterable, Callable, Any, Type, Dict, Generic, TypeVar, cast
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
from typing import GenericMeta
|
||||
else:
|
||||
class GenericMeta(type):
|
||||
...
|
||||
|
||||
|
||||
from . import providers
|
||||
|
||||
|
||||
__all__ = (
|
||||
'wire',
|
||||
'unwire',
|
||||
'Provide',
|
||||
'Provider',
|
||||
)
|
||||
|
||||
T = TypeVar('T')
|
||||
Container = Any
|
||||
|
||||
|
||||
class ProvidersMap:
|
||||
|
||||
def __init__(self, container):
|
||||
self._container = container
|
||||
self._map = self._create_providers_map(
|
||||
current_providers=container.providers,
|
||||
original_providers=container.declarative_parent.providers,
|
||||
)
|
||||
|
||||
def resolve_provider(self, provider: providers.Provider) -> providers.Provider:
|
||||
if isinstance(provider, providers.Delegate):
|
||||
return self._resolve_delegate(provider)
|
||||
elif isinstance(provider, (
|
||||
providers.ProvidedInstance,
|
||||
providers.AttributeGetter,
|
||||
providers.ItemGetter,
|
||||
providers.MethodCaller,
|
||||
)):
|
||||
return self._resolve_provided_instance(provider)
|
||||
elif isinstance(provider, providers.ConfigurationOption):
|
||||
return self._resolve_config_option(provider)
|
||||
elif isinstance(provider, providers.TypedConfigurationOption):
|
||||
return self._resolve_config_option(provider.option, as_=provider.provides)
|
||||
else:
|
||||
return self._resolve_provider(provider)
|
||||
|
||||
def _resolve_delegate(self, original: providers.Delegate) -> providers.Provider:
|
||||
return self._resolve_provider(original.provides)
|
||||
|
||||
def _resolve_provided_instance(self, original: providers.Provider) -> providers.Provider:
|
||||
modifiers = []
|
||||
while isinstance(original, (
|
||||
providers.ProvidedInstance,
|
||||
providers.AttributeGetter,
|
||||
providers.ItemGetter,
|
||||
providers.MethodCaller,
|
||||
)):
|
||||
modifiers.insert(0, original)
|
||||
original = original.provides
|
||||
|
||||
new = self._resolve_provider(original)
|
||||
|
||||
for modifier in modifiers:
|
||||
if isinstance(modifier, providers.ProvidedInstance):
|
||||
new = new.provided
|
||||
elif isinstance(modifier, providers.AttributeGetter):
|
||||
new = getattr(new, modifier.name)
|
||||
elif isinstance(modifier, providers.ItemGetter):
|
||||
new = new[modifier.name]
|
||||
elif isinstance(modifier, providers.MethodCaller):
|
||||
new = new.call(
|
||||
*modifier.args,
|
||||
**modifier.kwargs,
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _resolve_config_option(
|
||||
self,
|
||||
original: providers.ConfigurationOption,
|
||||
as_: Any = None,
|
||||
) -> providers.Provider:
|
||||
original_root = original.root
|
||||
new = self._resolve_provider(original_root)
|
||||
new = cast(providers.Configuration, new)
|
||||
|
||||
for segment in original.get_name_segments():
|
||||
if providers.is_provider(segment):
|
||||
segment = self.resolve_provider(segment)
|
||||
new = new[segment]
|
||||
else:
|
||||
new = getattr(new, segment)
|
||||
|
||||
if as_:
|
||||
new = new.as_(as_)
|
||||
|
||||
return new
|
||||
|
||||
def _resolve_provider(self, original: providers.Provider) -> providers.Provider:
|
||||
try:
|
||||
return self._map[original]
|
||||
except KeyError:
|
||||
raise Exception('Unable to resolve original provider')
|
||||
|
||||
@classmethod
|
||||
def _create_providers_map(
|
||||
cls,
|
||||
current_providers: Dict[str, providers.Provider],
|
||||
original_providers: Dict[str, providers.Provider],
|
||||
) -> Dict[providers.Provider, providers.Provider]:
|
||||
providers_map = {}
|
||||
for provider_name, current_provider in current_providers.items():
|
||||
original_provider = original_providers[provider_name]
|
||||
providers_map[original_provider] = current_provider
|
||||
|
||||
if isinstance(current_provider, providers.Container) \
|
||||
and isinstance(original_provider, providers.Container):
|
||||
subcontainer_map = cls._create_providers_map(
|
||||
current_providers=current_provider.container.providers,
|
||||
original_providers=original_provider.container.providers,
|
||||
)
|
||||
providers_map.update(subcontainer_map)
|
||||
|
||||
return providers_map
|
||||
|
||||
|
||||
def wire(
|
||||
container: Container,
|
||||
*,
|
||||
modules: Optional[Iterable[ModuleType]] = None,
|
||||
packages: Optional[Iterable[ModuleType]] = None,
|
||||
) -> None:
|
||||
"""Wire container providers with provided packages and modules."""
|
||||
if not _is_declarative_container_instance(container):
|
||||
raise Exception('Can wire only an instance of the declarative container')
|
||||
|
||||
if not modules:
|
||||
modules = []
|
||||
|
||||
if packages:
|
||||
for package in packages:
|
||||
modules.extend(_fetch_modules(package))
|
||||
|
||||
providers_map = ProvidersMap(container)
|
||||
|
||||
for module in modules:
|
||||
for name, member in inspect.getmembers(module):
|
||||
if inspect.isfunction(member):
|
||||
_patch_fn(module, name, member, providers_map)
|
||||
elif inspect.isclass(member):
|
||||
_patch_cls(member, providers_map)
|
||||
|
||||
|
||||
def unwire(
|
||||
*,
|
||||
modules: Optional[Iterable[ModuleType]] = None,
|
||||
packages: Optional[Iterable[ModuleType]] = None,
|
||||
) -> None:
|
||||
"""Wire provided packages and modules with previous wired providers."""
|
||||
if not modules:
|
||||
modules = []
|
||||
|
||||
if packages:
|
||||
for package in packages:
|
||||
modules.extend(_fetch_modules(package))
|
||||
|
||||
for module in modules:
|
||||
for name, member in inspect.getmembers(module):
|
||||
if inspect.isfunction(member):
|
||||
_unpatch_fn(module, name, member)
|
||||
elif inspect.isclass(member):
|
||||
_unpatch_cls(member,)
|
||||
|
||||
|
||||
def _patch_cls(
|
||||
cls: Type[Any],
|
||||
providers_map: ProvidersMap,
|
||||
) -> None:
|
||||
if not hasattr(cls, '__init__'):
|
||||
return
|
||||
init_method = getattr(cls, '__init__')
|
||||
injections = _resolve_injections(init_method, providers_map)
|
||||
if not injections:
|
||||
return
|
||||
setattr(cls, '__init__', _patch_with_injections(init_method, injections))
|
||||
|
||||
|
||||
def _unpatch_cls(cls: Type[Any]) -> None:
|
||||
if not hasattr(cls, '__init__'):
|
||||
return
|
||||
init_method = getattr(cls, '__init__')
|
||||
if not _is_patched(init_method):
|
||||
return
|
||||
setattr(cls, '__init__', _get_original_from_patched(init_method))
|
||||
|
||||
|
||||
def _patch_fn(
|
||||
module: ModuleType,
|
||||
name: str,
|
||||
fn: Callable[..., Any],
|
||||
providers_map: ProvidersMap,
|
||||
) -> None:
|
||||
injections = _resolve_injections(fn, providers_map)
|
||||
if not injections:
|
||||
return
|
||||
setattr(module, name, _patch_with_injections(fn, injections))
|
||||
|
||||
|
||||
def _unpatch_fn(
|
||||
module: ModuleType,
|
||||
name: str,
|
||||
fn: Callable[..., Any],
|
||||
) -> None:
|
||||
if not _is_patched(fn):
|
||||
return
|
||||
setattr(module, name, _get_original_from_patched(fn))
|
||||
|
||||
|
||||
def _resolve_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> Dict[str, Any]: # noqa
|
||||
signature = inspect.signature(fn)
|
||||
|
||||
injections = {}
|
||||
for parameter_name, parameter in signature.parameters.items():
|
||||
if not isinstance(parameter.default, _Marker):
|
||||
continue
|
||||
marker = parameter.default
|
||||
|
||||
provider = providers_map.resolve_provider(marker.provider)
|
||||
if isinstance(marker, Provide):
|
||||
injections[parameter_name] = provider
|
||||
elif isinstance(marker, Provider):
|
||||
injections[parameter_name] = provider.provider
|
||||
|
||||
return injections
|
||||
|
||||
|
||||
def _fetch_modules(package):
|
||||
modules = []
|
||||
for loader, module_name, is_pkg in pkgutil.walk_packages(
|
||||
path=package.__path__,
|
||||
prefix=package.__name__ + '.',
|
||||
):
|
||||
module = loader.find_module(module_name).load_module(module_name)
|
||||
modules.append(module)
|
||||
return modules
|
||||
|
||||
|
||||
def _patch_with_injections(fn, injections):
|
||||
if inspect.iscoroutinefunction(fn):
|
||||
@functools.wraps(fn)
|
||||
async def _patched(*args, **kwargs):
|
||||
to_inject = {}
|
||||
for injection, provider in injections.items():
|
||||
to_inject[injection] = provider()
|
||||
|
||||
to_inject.update(kwargs)
|
||||
|
||||
return await fn(*args, **to_inject)
|
||||
else:
|
||||
@functools.wraps(fn)
|
||||
def _patched(*args, **kwargs):
|
||||
to_inject = {}
|
||||
for injection, provider in injections.items():
|
||||
to_inject[injection] = provider()
|
||||
|
||||
to_inject.update(kwargs)
|
||||
|
||||
return fn(*args, **to_inject)
|
||||
|
||||
_patched.__wired__ = True
|
||||
_patched.__original__ = fn
|
||||
_patched.__injections__ = injections
|
||||
|
||||
return _patched
|
||||
|
||||
|
||||
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
|
||||
and getattr(instance, 'declarative_parent', None) is not None)
|
||||
|
||||
|
||||
class ClassGetItemMeta(GenericMeta):
|
||||
def __getitem__(cls, item):
|
||||
# Spike for Python 3.6
|
||||
return cls(item)
|
||||
|
||||
|
||||
class _Marker(Generic[T], metaclass=ClassGetItemMeta):
|
||||
|
||||
def __init__(self, provider: providers.Provider) -> None:
|
||||
self.provider = provider
|
||||
|
||||
def __class_getitem__(cls, item) -> T:
|
||||
return cls(item)
|
||||
|
||||
|
||||
class Provide(_Marker):
|
||||
...
|
||||
|
||||
|
||||
class Provider(_Marker):
|
||||
...
|
1
tests/unit/wiring/__init__.py
Normal file
1
tests/unit/wiring/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Wiring tests."""
|
17
tests/unit/wiring/container.py
Normal file
17
tests/unit/wiring/container.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
from .service import Service
|
||||
|
||||
|
||||
class SubContainer(containers.DeclarativeContainer):
|
||||
|
||||
int_object = providers.Object(1)
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
service = providers.Factory(Service)
|
||||
|
||||
sub = providers.Container(SubContainer)
|
49
tests/unit/wiring/module.py
Normal file
49
tests/unit/wiring/module.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
"""Test module for wiring."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Callable
|
||||
|
||||
from dependency_injector.wiring import Provide, Provider
|
||||
|
||||
from .container import Container
|
||||
from .service import Service
|
||||
|
||||
|
||||
class TestClass:
|
||||
|
||||
def __init__(self, service: Service = Provide[Container.service]):
|
||||
self.service = service
|
||||
|
||||
|
||||
def test_function(service: Service = Provide[Container.service]):
|
||||
return service
|
||||
|
||||
|
||||
def test_function_provider(service_provider: Callable[..., Service] = Provider[Container.service]):
|
||||
service = service_provider()
|
||||
return service
|
||||
|
||||
|
||||
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)],
|
||||
some_value_decimal: Decimal = Provide[Container.config.a.b.c.as_(Decimal)],
|
||||
):
|
||||
return some_value_int, some_value_str, some_value_decimal
|
||||
|
||||
|
||||
def test_provide_provider(service_provider: Callable[..., Service] = Provider[Container.service.provider]):
|
||||
service = service_provider()
|
||||
return service
|
||||
|
||||
|
||||
def test_provided_instance(some_value: int = Provide[Container.service.provided.foo['bar'].call()]):
|
||||
return some_value
|
||||
|
||||
|
||||
def test_subcontainer_provider(some_value: int = Provide[Container.sub.int_object]):
|
||||
return some_value
|
||||
|
||||
|
||||
def test_config_invariant(some_value: int = Provide[Container.config.option[Container.config.switch]]):
|
||||
return some_value
|
0
tests/unit/wiring/package/__init__.py
Normal file
0
tests/unit/wiring/package/__init__.py
Normal file
0
tests/unit/wiring/package/subpackage/__init__.py
Normal file
0
tests/unit/wiring/package/subpackage/__init__.py
Normal file
8
tests/unit/wiring/package/subpackage/submodule.py
Normal file
8
tests/unit/wiring/package/subpackage/submodule.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from dependency_injector.wiring import Provide
|
||||
|
||||
from ...container import Container
|
||||
from ...service import Service
|
||||
|
||||
|
||||
def test_function(service: Service = Provide[Container.service]):
|
||||
return service
|
2
tests/unit/wiring/service.py
Normal file
2
tests/unit/wiring/service.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class Service:
|
||||
service_attr: int
|
106
tests/unit/wiring/test_wiring_py36.py
Normal file
106
tests/unit/wiring/test_wiring_py36.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
from decimal import Decimal
|
||||
import unittest
|
||||
|
||||
from dependency_injector.wiring import wire
|
||||
|
||||
from . import module, package
|
||||
from .service import Service
|
||||
from .container import Container
|
||||
|
||||
|
||||
class WiringTest(unittest.TestCase):
|
||||
|
||||
container: Container
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.container = Container(config={'a': {'b': {'c': 10}}})
|
||||
self.container.wire(
|
||||
modules=[module],
|
||||
packages=[package],
|
||||
)
|
||||
self.addCleanup(self.container.unwire)
|
||||
|
||||
def test_package_lookup(self):
|
||||
from .package.subpackage.submodule import test_function
|
||||
service = test_function()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_class_wiring(self):
|
||||
test_class_object = module.TestClass()
|
||||
self.assertIsInstance(test_class_object.service, Service)
|
||||
|
||||
def test_class_wiring_context_arg(self):
|
||||
test_service = self.container.service()
|
||||
|
||||
test_class_object = module.TestClass(service=test_service)
|
||||
self.assertIs(test_class_object.service, test_service)
|
||||
|
||||
def test_function_wiring(self):
|
||||
service = module.test_function()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_function_wiring_context_arg(self):
|
||||
test_service = self.container.service()
|
||||
|
||||
service = module.test_function(service=test_service)
|
||||
self.assertIs(service, test_service)
|
||||
|
||||
def test_function_wiring_provider(self):
|
||||
service = module.test_function_provider()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_function_wiring_provider_context_arg(self):
|
||||
test_service = self.container.service()
|
||||
|
||||
service = module.test_function_provider(service_provider=lambda: test_service)
|
||||
self.assertIs(service, test_service)
|
||||
|
||||
def test_configuration_option(self):
|
||||
int_value, str_value, decimal_value = module.test_config_value()
|
||||
self.assertEqual(int_value, 10)
|
||||
self.assertEqual(str_value, '10')
|
||||
self.assertEqual(decimal_value, Decimal(10))
|
||||
|
||||
def test_provide_provider(self):
|
||||
service = module.test_provide_provider()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_provided_instance(self):
|
||||
class TestService:
|
||||
foo = {
|
||||
'bar': lambda: 10,
|
||||
}
|
||||
|
||||
with self.container.service.override(TestService()):
|
||||
some_value = module.test_provided_instance()
|
||||
self.assertEqual(some_value, 10)
|
||||
|
||||
def test_subcontainer(self):
|
||||
some_value = module.test_subcontainer_provider()
|
||||
self.assertEqual(some_value, 1)
|
||||
|
||||
def test_config_invariant(self):
|
||||
config = {
|
||||
'option': {
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
},
|
||||
'switch': 'a',
|
||||
}
|
||||
self.container.config.from_dict(config)
|
||||
|
||||
with self.container.config.switch.override('a'):
|
||||
value_a = module.test_config_invariant()
|
||||
self.assertEqual(value_a, 1)
|
||||
|
||||
with self.container.config.switch.override('b'):
|
||||
value_b = module.test_config_invariant()
|
||||
self.assertEqual(value_b, 2)
|
||||
|
||||
def test_wire_with_class_error(self):
|
||||
with self.assertRaises(Exception):
|
||||
wire(
|
||||
container=Container,
|
||||
modules=[module],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user