mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-02-07 07:00:49 +03:00
Add sanic example
This commit is contained in:
parent
e37b5181e4
commit
9653dfc263
|
@ -10,6 +10,7 @@ follows `Semantic versioning`_
|
||||||
4.0.0
|
4.0.0
|
||||||
-----
|
-----
|
||||||
- Add ``wiring`` feature.
|
- Add ``wiring`` feature.
|
||||||
|
- Add ``sanic`` example.
|
||||||
- Update ``aiohttp`` example.
|
- Update ``aiohttp`` example.
|
||||||
- Update ``flask`` example.
|
- Update ``flask`` example.
|
||||||
|
|
||||||
|
|
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
|
Loading…
Reference in New Issue
Block a user