mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-25 11:04:01 +03:00
Aiohttp integration (#270)
* Add aiohttp extension module * Add giphynav-aiohttp app * Add missing docstrings * Remove print() call * Remove not needed import from flask extension tests * Improve coroutine provider tests * Add aiohttp extension tests * Update tox.ini * Add aiohttp extras * Try fix Python 3.4 tests * Try fix 3.6 tests * Stop running coroutine tests for Python 3.4 * Rename tests * Remove type hints * Fix pypy and change python version for coverage job to 3.8 * Fix coveralls job * Try fix Python 3.4, 3.5 tests * Make coverage job to run 3.5+ tests * Add tests * Add readme * Update the readmes * Add API docs * Add API docs page * Update changelog
This commit is contained in:
parent
bed547cc91
commit
e0d81c2d28
|
@ -3,7 +3,7 @@ dist: xenial
|
||||||
language: python
|
language: python
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- python: 3.6
|
- python: 3.8
|
||||||
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
|
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
|
|
9
docs/api/aiohttpext.rst
Normal file
9
docs/api/aiohttpext.rst
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
dependency_injector.ext.aiohttp
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.ext.aiohttp
|
||||||
|
:members:
|
||||||
|
:special-members:
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -8,4 +8,5 @@ API Documentation
|
||||||
providers
|
providers
|
||||||
containers
|
containers
|
||||||
errors
|
errors
|
||||||
|
aiohttpext
|
||||||
flaskext
|
flaskext
|
||||||
|
|
|
@ -7,6 +7,11 @@ that were made in every particular version.
|
||||||
From version 0.7.6 *Dependency Injector* framework strictly
|
From version 0.7.6 *Dependency Injector* framework strictly
|
||||||
follows `Semantic versioning`_
|
follows `Semantic versioning`_
|
||||||
|
|
||||||
|
Development version
|
||||||
|
-------------------
|
||||||
|
- Add ``Aiohttp`` integration module ``dependency_injector.ext.aiohttp``.
|
||||||
|
- Add ``Aiohttp`` + ``Dependency Injector`` example ``giphynav-aiohttp``.
|
||||||
|
|
||||||
3.23.2
|
3.23.2
|
||||||
------
|
------
|
||||||
- Fix ``Flask`` tutorial code issues, typos and change some wording.
|
- Fix ``Flask`` tutorial code issues, typos and change some wording.
|
||||||
|
@ -48,7 +53,7 @@ follows `Semantic versioning`_
|
||||||
|
|
||||||
3.20.0
|
3.20.0
|
||||||
------
|
------
|
||||||
- Add ``Flask`` integration module ``dependency_injector.flask.ext``.
|
- Add ``Flask`` integration module ``dependency_injector.ext.flask``.
|
||||||
- Add ``Flask`` + ``Dependency Injector`` example ``ghnav-flask``.
|
- Add ``Flask`` + ``Dependency Injector`` example ``ghnav-flask``.
|
||||||
- Add ``Factory.provides`` attribute. It is an alias to the ``Factory.cls``.
|
- Add ``Factory.provides`` attribute. It is an alias to the ``Factory.cls``.
|
||||||
- New README.
|
- New README.
|
||||||
|
|
|
@ -68,7 +68,7 @@ After that visit http://127.0.0.1:5000/ in your browser.
|
||||||
Test
|
Test
|
||||||
----
|
----
|
||||||
|
|
||||||
This application comes with unit tests.
|
This application comes with the unit tests.
|
||||||
|
|
||||||
To run the tests do:
|
To run the tests do:
|
||||||
|
|
||||||
|
|
117
examples/miniapps/giphynav-aiohttp/README.rst
Normal file
117
examples/miniapps/giphynav-aiohttp/README.rst
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
Aiohttp Dependency Injection Example
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Application ``giphynavigator`` is a `Aiohttp <https://docs.aiohttp.org/>`_ +
|
||||||
|
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ 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
|
||||||
|
adev runserver giphynavigator/application.py --livereload
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
[18:52:59] Starting aux server at http://localhost:8001 ◆
|
||||||
|
[18:52:59] Starting dev server at http://localhost:8000 ●
|
||||||
|
|
||||||
|
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, aiohttp-0.3.0, asyncio-0.14.0
|
||||||
|
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 5 5 0%
|
||||||
|
giphynavigator/application.py 10 0 100%
|
||||||
|
giphynavigator/containers.py 10 0 100%
|
||||||
|
giphynavigator/giphy.py 16 11 31%
|
||||||
|
giphynavigator/services.py 9 1 89%
|
||||||
|
giphynavigator/tests.py 35 0 100%
|
||||||
|
giphynavigator/views.py 7 0 100%
|
||||||
|
---------------------------------------------------
|
||||||
|
TOTAL 92 17 82%
|
5
examples/miniapps/giphynav-aiohttp/config.yml
Normal file
5
examples/miniapps/giphynav-aiohttp/config.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
giphy:
|
||||||
|
request_timeout: 10
|
||||||
|
search:
|
||||||
|
default_query: "Dependency Injector"
|
||||||
|
default_limit: 10
|
|
@ -0,0 +1 @@
|
||||||
|
"""Top-level package."""
|
|
@ -0,0 +1,10 @@
|
||||||
|
"""Main module."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .application import create_app
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = create_app()
|
||||||
|
web.run_app(app)
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""Application module."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .containers import ApplicationContainer
|
||||||
|
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
"""Create and return Flask application."""
|
||||||
|
container = ApplicationContainer()
|
||||||
|
container.config.from_yaml('config.yml')
|
||||||
|
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||||
|
|
||||||
|
app: web.Application = container.app()
|
||||||
|
app.container = container
|
||||||
|
|
||||||
|
app.add_routes([
|
||||||
|
web.get('/', container.index_view.as_view()),
|
||||||
|
])
|
||||||
|
|
||||||
|
return app
|
|
@ -0,0 +1,33 @@
|
||||||
|
"""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,
|
||||||
|
)
|
29
examples/miniapps/giphynav-aiohttp/giphynavigator/giphy.py
Normal file
29
examples/miniapps/giphynav-aiohttp/giphynavigator/giphy.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
"""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."""
|
||||||
|
if not query:
|
||||||
|
return []
|
||||||
|
|
||||||
|
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()
|
|
@ -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']]
|
77
examples/miniapps/giphynav-aiohttp/giphynavigator/tests.py
Normal file
77
examples/miniapps/giphynav-aiohttp/giphynavigator/tests.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
"""Tests module."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from giphynavigator.application import create_app
|
||||||
|
from giphynavigator.giphy import GiphyClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app():
|
||||||
|
return create_app()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(app, aiohttp_client, loop):
|
||||||
|
return loop.run_until_complete(aiohttp_client(app))
|
||||||
|
|
||||||
|
|
||||||
|
async def test_index(client, app):
|
||||||
|
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||||
|
giphy_client_mock.search.return_value = {
|
||||||
|
'data': [
|
||||||
|
{'url': 'https://giphy/gif1.gif'},
|
||||||
|
{'url': 'https://giphy/gif2.gif'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
with app.container.giphy_client.override(giphy_client_mock):
|
||||||
|
response = await client.get(
|
||||||
|
'/',
|
||||||
|
params={
|
||||||
|
'query': 'test',
|
||||||
|
'limit': 10,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
data = await response.json()
|
||||||
|
assert data == {
|
||||||
|
'query': 'test',
|
||||||
|
'limit': 10,
|
||||||
|
'gifs': [
|
||||||
|
{'url': 'https://giphy/gif1.gif'},
|
||||||
|
{'url': 'https://giphy/gif2.gif'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_index_no_data(client, 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 client.get('/')
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
data = await response.json()
|
||||||
|
assert data['gifs'] == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_index_default_params(client, 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 client.get('/')
|
||||||
|
|
||||||
|
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()
|
25
examples/miniapps/giphynav-aiohttp/giphynavigator/views.py
Normal file
25
examples/miniapps/giphynav-aiohttp/giphynavigator/views.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
"""Views module."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .services import SearchService
|
||||||
|
|
||||||
|
|
||||||
|
async def index(
|
||||||
|
request: web.Request,
|
||||||
|
search_service: SearchService,
|
||||||
|
default_query: str,
|
||||||
|
default_limit: int,
|
||||||
|
) -> web.Response:
|
||||||
|
query = request.query.get('query', default_query)
|
||||||
|
limit = int(request.query.get('limit', default_limit))
|
||||||
|
|
||||||
|
gifs = await search_service.search(query, limit)
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
'query': query,
|
||||||
|
'limit': limit,
|
||||||
|
'gifs': gifs,
|
||||||
|
},
|
||||||
|
)
|
6
examples/miniapps/giphynav-aiohttp/requirements.txt
Normal file
6
examples/miniapps/giphynav-aiohttp/requirements.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
dependency-injector
|
||||||
|
aiohttp
|
||||||
|
aiohttp-devtools
|
||||||
|
pyyaml
|
||||||
|
pytest-aiohttp
|
||||||
|
pytest-cov
|
|
@ -1 +1,2 @@
|
||||||
flask
|
flask
|
||||||
|
aiohttp
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -67,6 +67,9 @@ setup(name='dependency-injector',
|
||||||
'flask': [
|
'flask': [
|
||||||
'flask',
|
'flask',
|
||||||
],
|
],
|
||||||
|
'aiohttp': [
|
||||||
|
'aiohttp',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
zip_safe=True,
|
zip_safe=True,
|
||||||
license='BSD New',
|
license='BSD New',
|
||||||
|
|
46
src/dependency_injector/ext/aiohttp.py
Normal file
46
src/dependency_injector/ext/aiohttp.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
"""Aiohttp extension module."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from dependency_injector import providers
|
||||||
|
|
||||||
|
|
||||||
|
class Application(providers.Singleton):
|
||||||
|
"""Aiohttp application provider."""
|
||||||
|
|
||||||
|
|
||||||
|
class Extension(providers.Singleton):
|
||||||
|
"""Aiohttp extension provider."""
|
||||||
|
|
||||||
|
|
||||||
|
class Middleware(providers.DelegatedCallable):
|
||||||
|
"""Aiohttp middleware provider."""
|
||||||
|
|
||||||
|
__middleware_version__ = 1
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareFactory(providers.Factory):
|
||||||
|
"""Aiohttp middleware factory provider."""
|
||||||
|
|
||||||
|
|
||||||
|
class View(providers.Callable):
|
||||||
|
"""Aiohttp view provider."""
|
||||||
|
|
||||||
|
def as_view(self):
|
||||||
|
"""Return aiohttp view function."""
|
||||||
|
@functools.wraps(self.provides)
|
||||||
|
async def _view(request, *args, **kwargs):
|
||||||
|
return await self.__call__(request, *args, **kwargs)
|
||||||
|
return _view
|
||||||
|
|
||||||
|
|
||||||
|
class ClassBasedView(providers.Factory):
|
||||||
|
"""Aiohttp class-based view provider."""
|
||||||
|
|
||||||
|
def as_view(self):
|
||||||
|
"""Return aiohttp view function."""
|
||||||
|
async def _view(request, *args, **kwargs):
|
||||||
|
return await self.__call__(request, *args, **kwargs)
|
||||||
|
return _view
|
93
tests/unit/ext/test_aiohttp_py35.py
Normal file
93
tests/unit/ext/test_aiohttp_py35.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
"""Dependency injector Aiohttp extension unit tests."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.ext import aiohttp
|
||||||
|
|
||||||
|
|
||||||
|
async def index(_):
|
||||||
|
return web.Response(text='Hello World!')
|
||||||
|
|
||||||
|
|
||||||
|
async def test(_):
|
||||||
|
return web.Response(text='Test!')
|
||||||
|
|
||||||
|
|
||||||
|
class Test(web.View):
|
||||||
|
async def get(self):
|
||||||
|
return web.Response(text='Test class-based!')
|
||||||
|
|
||||||
|
|
||||||
|
@web.middleware
|
||||||
|
async def middleware(request, handler):
|
||||||
|
resp = await handler(request)
|
||||||
|
resp.text = resp.text + ' wink1'
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
def middleware_factory(text):
|
||||||
|
@web.middleware
|
||||||
|
async def sample_middleware(request, handler):
|
||||||
|
resp = await handler(request)
|
||||||
|
resp.text = resp.text + text
|
||||||
|
return resp
|
||||||
|
return sample_middleware
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationContainer(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
app = aiohttp.Application(
|
||||||
|
web.Application,
|
||||||
|
middlewares=providers.List(
|
||||||
|
aiohttp.Middleware(middleware),
|
||||||
|
aiohttp.MiddlewareFactory(middleware_factory, text=' wink2'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
index_view = aiohttp.View(index)
|
||||||
|
test_view = aiohttp.View(test)
|
||||||
|
test_class_view = aiohttp.ClassBasedView(Test)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationTests(AioHTTPTestCase):
|
||||||
|
|
||||||
|
async def get_application(self):
|
||||||
|
"""
|
||||||
|
Override the get_app method to return your application.
|
||||||
|
"""
|
||||||
|
container = ApplicationContainer()
|
||||||
|
app = container.app()
|
||||||
|
app.container = container
|
||||||
|
app.add_routes([
|
||||||
|
web.get('/', container.index_view.as_view()),
|
||||||
|
web.get('/test', container.test_view.as_view(), name='test'),
|
||||||
|
web.get('/test-class', container.test_class_view.as_view()),
|
||||||
|
])
|
||||||
|
return app
|
||||||
|
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_index(self):
|
||||||
|
response = await self.client.get('/')
|
||||||
|
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(await response.text(), 'Hello World! wink2 wink1')
|
||||||
|
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_test(self):
|
||||||
|
response = await self.client.get('/test')
|
||||||
|
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(await response.text(), 'Test! wink2 wink1')
|
||||||
|
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_test_class_based(self):
|
||||||
|
response = await self.client.get('/test-class')
|
||||||
|
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(await response.text(), 'Test class-based! wink2 wink1')
|
||||||
|
|
||||||
|
@unittest_run_loop
|
||||||
|
async def test_endpoints(self):
|
||||||
|
self.assertEqual(str(self.app.router['test'].url_for()), '/test')
|
|
@ -4,7 +4,7 @@ import unittest2 as unittest
|
||||||
from flask import Flask, url_for
|
from flask import Flask, url_for
|
||||||
from flask.views import MethodView
|
from flask.views import MethodView
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers
|
||||||
from dependency_injector.ext import flask
|
from dependency_injector.ext import flask
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
"""Dependency injector coroutine providers unit tests."""
|
"""Dependency injector coroutine providers unit tests."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
||||||
|
@ -10,20 +13,65 @@ from dependency_injector import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _example(arg1, arg2, arg3, arg4):
|
||||||
def _example(arg1, arg2, arg3, arg4):
|
|
||||||
future = asyncio.Future()
|
future = asyncio.Future()
|
||||||
future.set_result(None)
|
future.set_result(None)
|
||||||
yield from future
|
await future
|
||||||
return arg1, arg2, arg3, arg4
|
return arg1, arg2, arg3, arg4
|
||||||
|
|
||||||
|
|
||||||
def _run(coro):
|
def run(main):
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
return loop.run_until_complete(coro)
|
return loop.run_until_complete(main)
|
||||||
|
|
||||||
|
|
||||||
class CoroutineTests(unittest.TestCase):
|
def setup_test_loop(
|
||||||
|
loop_factory=asyncio.new_event_loop
|
||||||
|
) -> asyncio.AbstractEventLoop:
|
||||||
|
loop = loop_factory()
|
||||||
|
try:
|
||||||
|
module = loop.__class__.__module__
|
||||||
|
skip_watcher = 'uvloop' in module
|
||||||
|
except AttributeError: # pragma: no cover
|
||||||
|
# Just in case
|
||||||
|
skip_watcher = True
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
if sys.platform != "win32" and not skip_watcher:
|
||||||
|
policy = asyncio.get_event_loop_policy()
|
||||||
|
watcher = asyncio.SafeChildWatcher() # type: ignore
|
||||||
|
watcher.attach_loop(loop)
|
||||||
|
with contextlib.suppress(NotImplementedError):
|
||||||
|
policy.set_child_watcher(watcher)
|
||||||
|
return loop
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_test_loop(loop: asyncio.AbstractEventLoop,
|
||||||
|
fast: bool=False) -> None:
|
||||||
|
closed = loop.is_closed()
|
||||||
|
if not closed:
|
||||||
|
loop.call_soon(loop.stop)
|
||||||
|
loop.run_forever()
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
if not fast:
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
asyncio.set_event_loop(None)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.loop = setup_test_loop()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
teardown_test_loop(self.loop)
|
||||||
|
|
||||||
|
def _run(self, f):
|
||||||
|
return self.loop.run_until_complete(f)
|
||||||
|
|
||||||
|
|
||||||
|
class CoroutineTests(AsyncTestCase):
|
||||||
|
|
||||||
def test_init_with_coroutine(self):
|
def test_init_with_coroutine(self):
|
||||||
self.assertTrue(providers.Coroutine(_example))
|
self.assertTrue(providers.Coroutine(_example))
|
||||||
|
@ -33,34 +81,34 @@ class CoroutineTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_call_with_positional_args(self):
|
def test_call_with_positional_args(self):
|
||||||
provider = providers.Coroutine(_example, 1, 2, 3, 4)
|
provider = providers.Coroutine(_example, 1, 2, 3, 4)
|
||||||
self.assertTupleEqual(_run(provider()), (1, 2, 3, 4))
|
self.assertTupleEqual(self._run(provider()), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_call_with_keyword_args(self):
|
def test_call_with_keyword_args(self):
|
||||||
provider = providers.Coroutine(_example,
|
provider = providers.Coroutine(_example,
|
||||||
arg1=1, arg2=2, arg3=3, arg4=4)
|
arg1=1, arg2=2, arg3=3, arg4=4)
|
||||||
self.assertTupleEqual(_run(provider()), (1, 2, 3, 4))
|
self.assertTupleEqual(self._run(provider()), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_call_with_positional_and_keyword_args(self):
|
def test_call_with_positional_and_keyword_args(self):
|
||||||
provider = providers.Coroutine(_example,
|
provider = providers.Coroutine(_example,
|
||||||
1, 2,
|
1, 2,
|
||||||
arg3=3, arg4=4)
|
arg3=3, arg4=4)
|
||||||
self.assertTupleEqual(_run(provider()), (1, 2, 3, 4))
|
self.assertTupleEqual(run(provider()), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_call_with_context_args(self):
|
def test_call_with_context_args(self):
|
||||||
provider = providers.Coroutine(_example, 1, 2)
|
provider = providers.Coroutine(_example, 1, 2)
|
||||||
self.assertTupleEqual(_run(provider(3, 4)), (1, 2, 3, 4))
|
self.assertTupleEqual(self._run(provider(3, 4)), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_call_with_context_kwargs(self):
|
def test_call_with_context_kwargs(self):
|
||||||
provider = providers.Coroutine(_example, arg1=1)
|
provider = providers.Coroutine(_example, arg1=1)
|
||||||
self.assertTupleEqual(
|
self.assertTupleEqual(
|
||||||
_run(provider(arg2=2, arg3=3, arg4=4)),
|
self._run(provider(arg2=2, arg3=3, arg4=4)),
|
||||||
(1, 2, 3, 4),
|
(1, 2, 3, 4),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_call_with_context_args_and_kwargs(self):
|
def test_call_with_context_args_and_kwargs(self):
|
||||||
provider = providers.Coroutine(_example, 1)
|
provider = providers.Coroutine(_example, 1)
|
||||||
self.assertTupleEqual(
|
self.assertTupleEqual(
|
||||||
_run(provider(2, arg3=3, arg4=4)),
|
self._run(provider(2, arg3=3, arg4=4)),
|
||||||
(1, 2, 3, 4),
|
(1, 2, 3, 4),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,7 +117,7 @@ class CoroutineTests(unittest.TestCase):
|
||||||
.add_args(1, 2) \
|
.add_args(1, 2) \
|
||||||
.add_kwargs(arg3=3, arg4=4)
|
.add_kwargs(arg3=3, arg4=4)
|
||||||
|
|
||||||
self.assertTupleEqual(_run(provider()), (1, 2, 3, 4))
|
self.assertTupleEqual(self._run(provider()), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_set_args(self):
|
def test_set_args(self):
|
||||||
provider = providers.Coroutine(_example) \
|
provider = providers.Coroutine(_example) \
|
||||||
|
@ -213,7 +261,7 @@ class DelegatedCoroutineTests(unittest.TestCase):
|
||||||
hex(id(provider))))
|
hex(id(provider))))
|
||||||
|
|
||||||
|
|
||||||
class AbstractCoroutineTests(unittest.TestCase):
|
class AbstractCoroutineTests(AsyncTestCase):
|
||||||
|
|
||||||
def test_inheritance(self):
|
def test_inheritance(self):
|
||||||
self.assertIsInstance(providers.AbstractCoroutine(_example),
|
self.assertIsInstance(providers.AbstractCoroutine(_example),
|
||||||
|
@ -227,7 +275,7 @@ class AbstractCoroutineTests(unittest.TestCase):
|
||||||
provider = providers.AbstractCoroutine(_abstract_example)
|
provider = providers.AbstractCoroutine(_abstract_example)
|
||||||
provider.override(providers.Coroutine(_example))
|
provider.override(providers.Coroutine(_example))
|
||||||
|
|
||||||
self.assertTrue(_run(provider(1, 2, 3, 4)), (1, 2, 3, 4))
|
self.assertTrue(self._run(provider(1, 2, 3, 4)), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_call_overridden_by_delegated_coroutine(self):
|
def test_call_overridden_by_delegated_coroutine(self):
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
|
@ -237,7 +285,7 @@ class AbstractCoroutineTests(unittest.TestCase):
|
||||||
provider = providers.AbstractCoroutine(_abstract_example)
|
provider = providers.AbstractCoroutine(_abstract_example)
|
||||||
provider.override(providers.DelegatedCoroutine(_example))
|
provider.override(providers.DelegatedCoroutine(_example))
|
||||||
|
|
||||||
self.assertTrue(_run(provider(1, 2, 3, 4)), (1, 2, 3, 4))
|
self.assertTrue(self._run(provider(1, 2, 3, 4)), (1, 2, 3, 4))
|
||||||
|
|
||||||
def test_call_not_overridden(self):
|
def test_call_not_overridden(self):
|
||||||
provider = providers.AbstractCoroutine(_example)
|
provider = providers.AbstractCoroutine(_example)
|
15
tox.ini
15
tox.ini
|
@ -8,12 +8,13 @@ deps=
|
||||||
extras=
|
extras=
|
||||||
yaml
|
yaml
|
||||||
flask
|
flask
|
||||||
|
aiohttp
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py3.py
|
unit2 discover -s tests/unit -p test_*_py3*.py
|
||||||
|
|
||||||
[testenv:coveralls]
|
[testenv:coveralls]
|
||||||
passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH DEPENDENCY_INJECTOR_DEBUG_MODE
|
passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH DEPENDENCY_INJECTOR_DEBUG_MODE
|
||||||
basepython=python3.6
|
basepython=python3.8
|
||||||
usedevelop=True
|
usedevelop=True
|
||||||
deps=
|
deps=
|
||||||
{[testenv]deps}
|
{[testenv]deps}
|
||||||
|
@ -22,19 +23,27 @@ deps=
|
||||||
coveralls
|
coveralls
|
||||||
commands=
|
commands=
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*_py3.py
|
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*_py35.py
|
||||||
coverage report --rcfile=./.coveragerc
|
coverage report --rcfile=./.coveragerc
|
||||||
coveralls
|
coveralls
|
||||||
|
|
||||||
[testenv:py27]
|
[testenv:py27]
|
||||||
|
extras=
|
||||||
|
yaml
|
||||||
|
flask
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
||||||
|
|
||||||
[testenv:py34]
|
[testenv:py34]
|
||||||
|
commands=
|
||||||
|
unit2 discover -s tests/unit -p test_*_py3.py
|
||||||
extras=
|
extras=
|
||||||
flask
|
flask
|
||||||
|
|
||||||
[testenv:pypy]
|
[testenv:pypy]
|
||||||
|
extras=
|
||||||
|
yaml
|
||||||
|
flask
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
unit2 discover -s tests/unit -p test_*_py2_py3.py
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user