diff --git a/.travis.yml b/.travis.yml
index b21de242..bf64b222 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ dist: xenial
language: python
jobs:
include:
- - python: 3.6
+ - python: 3.8
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
install:
- pip install tox
diff --git a/docs/api/aiohttpext.rst b/docs/api/aiohttpext.rst
new file mode 100644
index 00000000..fa51411f
--- /dev/null
+++ b/docs/api/aiohttpext.rst
@@ -0,0 +1,9 @@
+dependency_injector.ext.aiohttp
+===============================
+
+.. automodule:: dependency_injector.ext.aiohttp
+ :members:
+ :special-members:
+
+
+.. disqus::
diff --git a/docs/api/index.rst b/docs/api/index.rst
index 738ec7a0..8784d94b 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -8,4 +8,5 @@ API Documentation
providers
containers
errors
+ aiohttpext
flaskext
diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst
index dd6f7fe8..79771859 100644
--- a/docs/main/changelog.rst
+++ b/docs/main/changelog.rst
@@ -7,6 +7,11 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
+3.24.0
+------
+- Add ``Aiohttp`` integration module ``dependency_injector.ext.aiohttp``.
+- Add ``Aiohttp`` + ``Dependency Injector`` example ``giphynav-aiohttp``.
+
3.23.2
------
- Fix ``Flask`` tutorial code issues, typos and change some wording.
@@ -48,7 +53,7 @@ follows `Semantic versioning`_
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 ``Factory.provides`` attribute. It is an alias to the ``Factory.cls``.
- New README.
diff --git a/examples/miniapps/ghnav-flask/README.rst b/examples/miniapps/ghnav-flask/README.rst
index ca6b3a2c..70d75185 100644
--- a/examples/miniapps/ghnav-flask/README.rst
+++ b/examples/miniapps/ghnav-flask/README.rst
@@ -68,7 +68,7 @@ After that visit http://127.0.0.1:5000/ in your browser.
Test
----
-This application comes with unit tests.
+This application comes with the unit tests.
To run the tests do:
diff --git a/examples/miniapps/giphynav-aiohttp/README.rst b/examples/miniapps/giphynav-aiohttp/README.rst
new file mode 100644
index 00000000..d9dddaf6
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/README.rst
@@ -0,0 +1,117 @@
+Aiohttp Dependency Injection Example
+====================================
+
+Application ``giphynavigator`` is a `Aiohttp `_ +
+`Dependency Injector `_ 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 `_.
+
+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%
diff --git a/examples/miniapps/giphynav-aiohttp/config.yml b/examples/miniapps/giphynav-aiohttp/config.yml
new file mode 100644
index 00000000..e1a0a14b
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/config.yml
@@ -0,0 +1,5 @@
+giphy:
+ request_timeout: 10
+search:
+ default_query: "Dependency Injector"
+ default_limit: 10
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/__init__.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/__init__.py
new file mode 100644
index 00000000..1c744ca5
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/__init__.py
@@ -0,0 +1 @@
+"""Top-level package."""
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/__main__.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/__main__.py
new file mode 100644
index 00000000..3ef0e377
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/__main__.py
@@ -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)
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/application.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/application.py
new file mode 100644
index 00000000..0a9b0e70
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/application.py
@@ -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
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/containers.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/containers.py
new file mode 100644
index 00000000..c7ee8af3
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/containers.py
@@ -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,
+ )
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/giphy.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/giphy.py
new file mode 100644
index 00000000..6fdd8766
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/giphy.py
@@ -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()
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/services.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/services.py
new file mode 100644
index 00000000..1c86e0d7
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/services.py
@@ -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']]
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/tests.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/tests.py
new file mode 100644
index 00000000..c34dfc10
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/tests.py
@@ -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()
diff --git a/examples/miniapps/giphynav-aiohttp/giphynavigator/views.py b/examples/miniapps/giphynav-aiohttp/giphynavigator/views.py
new file mode 100644
index 00000000..7af5a0f5
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/giphynavigator/views.py
@@ -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,
+ },
+ )
diff --git a/examples/miniapps/giphynav-aiohttp/requirements.txt b/examples/miniapps/giphynav-aiohttp/requirements.txt
new file mode 100644
index 00000000..e849f0dc
--- /dev/null
+++ b/examples/miniapps/giphynav-aiohttp/requirements.txt
@@ -0,0 +1,6 @@
+dependency-injector
+aiohttp
+aiohttp-devtools
+pyyaml
+pytest-aiohttp
+pytest-cov
diff --git a/requirements-ext.txt b/requirements-ext.txt
index 7e106024..ec1b3858 100644
--- a/requirements-ext.txt
+++ b/requirements-ext.txt
@@ -1 +1,2 @@
flask
+aiohttp
diff --git a/setup.py b/setup.py
index 2aa43a52..10e2653d 100644
--- a/setup.py
+++ b/setup.py
@@ -67,6 +67,9 @@ setup(name='dependency-injector',
'flask': [
'flask',
],
+ 'aiohttp': [
+ 'aiohttp',
+ ],
},
zip_safe=True,
license='BSD New',
diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py
index 13e92e6e..4573132c 100644
--- a/src/dependency_injector/__init__.py
+++ b/src/dependency_injector/__init__.py
@@ -1,6 +1,6 @@
"""Dependency injector top-level package."""
-__version__ = '3.23.2'
+__version__ = '3.24.0'
"""Version number that follows semantic versioning.
:type: str
diff --git a/src/dependency_injector/ext/aiohttp.py b/src/dependency_injector/ext/aiohttp.py
new file mode 100644
index 00000000..f42d95e4
--- /dev/null
+++ b/src/dependency_injector/ext/aiohttp.py
@@ -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
diff --git a/tests/unit/ext/test_aiohttp_py35.py b/tests/unit/ext/test_aiohttp_py35.py
new file mode 100644
index 00000000..6f7b0205
--- /dev/null
+++ b/tests/unit/ext/test_aiohttp_py35.py
@@ -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')
diff --git a/tests/unit/ext/test_flask_py2_py3.py b/tests/unit/ext/test_flask_py2_py3.py
index 7b9695a4..49cf3d5f 100644
--- a/tests/unit/ext/test_flask_py2_py3.py
+++ b/tests/unit/ext/test_flask_py2_py3.py
@@ -4,7 +4,7 @@ import unittest2 as unittest
from flask import Flask, url_for
from flask.views import MethodView
-from dependency_injector import containers, providers
+from dependency_injector import containers
from dependency_injector.ext import flask
diff --git a/tests/unit/providers/test_coroutines_py3.py b/tests/unit/providers/test_coroutines_py35.py
similarity index 81%
rename from tests/unit/providers/test_coroutines_py3.py
rename to tests/unit/providers/test_coroutines_py35.py
index e970b2ee..c9109f26 100644
--- a/tests/unit/providers/test_coroutines_py3.py
+++ b/tests/unit/providers/test_coroutines_py35.py
@@ -1,6 +1,9 @@
"""Dependency injector coroutine providers unit tests."""
import asyncio
+import contextlib
+import sys
+import gc
import unittest2 as unittest
@@ -10,20 +13,65 @@ from dependency_injector import (
)
-@asyncio.coroutine
-def _example(arg1, arg2, arg3, arg4):
+async def _example(arg1, arg2, arg3, arg4):
future = asyncio.Future()
future.set_result(None)
- yield from future
+ await future
return arg1, arg2, arg3, arg4
-def _run(coro):
+def run(main):
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):
self.assertTrue(providers.Coroutine(_example))
@@ -33,34 +81,34 @@ class CoroutineTests(unittest.TestCase):
def test_call_with_positional_args(self):
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):
provider = providers.Coroutine(_example,
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):
provider = providers.Coroutine(_example,
1, 2,
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):
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):
provider = providers.Coroutine(_example, arg1=1)
self.assertTupleEqual(
- _run(provider(arg2=2, arg3=3, arg4=4)),
+ self._run(provider(arg2=2, arg3=3, arg4=4)),
(1, 2, 3, 4),
)
def test_call_with_context_args_and_kwargs(self):
provider = providers.Coroutine(_example, 1)
self.assertTupleEqual(
- _run(provider(2, arg3=3, arg4=4)),
+ self._run(provider(2, arg3=3, arg4=4)),
(1, 2, 3, 4),
)
@@ -69,7 +117,7 @@ class CoroutineTests(unittest.TestCase):
.add_args(1, 2) \
.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):
provider = providers.Coroutine(_example) \
@@ -213,7 +261,7 @@ class DelegatedCoroutineTests(unittest.TestCase):
hex(id(provider))))
-class AbstractCoroutineTests(unittest.TestCase):
+class AbstractCoroutineTests(AsyncTestCase):
def test_inheritance(self):
self.assertIsInstance(providers.AbstractCoroutine(_example),
@@ -227,7 +275,7 @@ class AbstractCoroutineTests(unittest.TestCase):
provider = providers.AbstractCoroutine(_abstract_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):
@asyncio.coroutine
@@ -237,7 +285,7 @@ class AbstractCoroutineTests(unittest.TestCase):
provider = providers.AbstractCoroutine(_abstract_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):
provider = providers.AbstractCoroutine(_example)
diff --git a/tox.ini b/tox.ini
index 61d6a098..e494554a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,12 +8,13 @@ deps=
extras=
yaml
flask
+ aiohttp
commands=
- unit2 discover -s tests/unit -p test_*_py3.py
+ unit2 discover -s tests/unit -p test_*_py3*.py
[testenv:coveralls]
passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH DEPENDENCY_INJECTOR_DEBUG_MODE
-basepython=python3.6
+basepython=python3.8
usedevelop=True
deps=
{[testenv]deps}
@@ -22,19 +23,27 @@ deps=
coveralls
commands=
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_*_py3*.py
coverage report --rcfile=./.coveragerc
coveralls
[testenv:py27]
+extras=
+ yaml
+ flask
commands=
unit2 discover -s tests/unit -p test_*_py2_py3.py
[testenv:py34]
+commands=
+ unit2 discover -s tests/unit -p test_*_py3.py
extras=
flask
[testenv:pypy]
+extras=
+ yaml
+ flask
commands=
unit2 discover -s tests/unit -p test_*_py2_py3.py