Aiohttp example

This example shows how to use Dependency Injector with Aiohttp.

The example application is a REST API that searches for funny GIFs on the Giphy.

The source code is available on the Github.

Aiohttp tutorial demonstrates how to build this application step-by-step.

Application structure

Application has next structure:

./
├──giphynavigator/
│├──__init__.py
│├──application.py
│├──containers.py
│├──giphy.py
│├──handlers.py
│├──services.py
│└──tests.py
├──config.yml
└──requirements.txt

Container

Declarative container is defined in giphynavigator/containers.py:

"""Containers module."""
fromdependency_injectorimport containers, providers
from.import giphy, services
classContainer(containers.DeclarativeContainer):
 wiring_config = containers.WiringConfiguration(modules=[".handlers"])
 config = providers.Configuration(yaml_files=["config.yml"])
 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,
 )

Handlers

Handler has dependencies on search service and some config options. The dependencies are injected using Wiring feature.

Listing of giphynavigator/handlers.py:

"""Handlers module."""
fromaiohttpimport web
fromdependency_injector.wiringimport inject, Provide
from.servicesimport SearchService
from.containersimport Container
@inject
async defindex(
 request: web.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()],
) -> 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,
 },
 )

Application factory

Application factory creates container, wires it with the handlers module, creates Aiohttp app and setup routes.

Listing of giphynavigator/application.py:

"""Application module."""
fromaiohttpimport web
from.containersimport Container
from.import handlers
defcreate_app() -> web.Application:
 container = Container()
 container.config.giphy.api_key.from_env("GIPHY_API_KEY")
 app = web.Application()
 app.container = container
 app.add_routes([
 web.get("/", handlers.index),
 ])
 return app
if __name__ == "__main__":
 app = create_app()
 web.run_app(app)

Tests

Tests use Provider overriding feature to replace giphy client with a mock giphynavigator/tests.py:

"""Tests module."""
fromunittestimport mock
importpytest
importpytest_asyncio
fromgiphynavigator.applicationimport create_app
fromgiphynavigator.giphyimport GiphyClient
pytestmark = pytest.mark.asyncio
@pytest.fixture
defapp():
 app = create_app()
 yield app
 app.container.unwire()
@pytest_asyncio.fixture
async defclient(app, aiohttp_client):
 return await aiohttp_client(app)
async deftest_index(client, 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 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.com/gif1.gif"},
 {"url": "https://giphy.com/gif2.gif"},
 ],
 }
async deftest_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 deftest_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.default.query()
 assert data["limit"] == app.container.config.default.limit()

Sources

Explore the sources on the Github.

Sponsor the project on GitHub:

[フレーム]