mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-10-31 07:57:43 +03:00 
			
		
		
		
	Aiohttp tutorial (#271)
* Make a code style change to the giphynav-aiohttp app * Make minimal punctuation changes for the flask tutorial * Add parts of http tutorial * Fix few issues in the flask tutorial * Make some cosmetic changes to test data * Fix typo in flask tutorial
This commit is contained in:
		
							parent
							
								
									577c7854da
								
							
						
					
					
						commit
						adcea61657
					
				|  | @ -1,6 +1,692 @@ | ||||||
| Aiohttp tutorial | Aiohttp tutorial | ||||||
| ================ | ================ | ||||||
| 
 | 
 | ||||||
| Coming soon... | .. _aiohttp-tutorial: | ||||||
|  | 
 | ||||||
|  | This tutorials shows how to build ``Aiohttp`` REST API application following dependency injection | ||||||
|  | principle. | ||||||
|  | 
 | ||||||
|  | Start from the scratch or jump to the section: | ||||||
|  | 
 | ||||||
|  | .. contents:: | ||||||
|  |    :local: | ||||||
|  |    :backlinks: none | ||||||
|  | 
 | ||||||
|  | You can find complete project on the | ||||||
|  | `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/giphynav-aiohttp>`_. | ||||||
|  | 
 | ||||||
|  | What are we going to build? | ||||||
|  | --------------------------- | ||||||
|  | 
 | ||||||
|  | .. image:: https://media.giphy.com/media/apvx5lPCPsjN6/source.gif | ||||||
|  | 
 | ||||||
|  | We will build a REST API application that searches for funny GIFs on the `Giphy <https://giphy.com/>`_. | ||||||
|  | Let's call it Giphy Navigator. | ||||||
|  | 
 | ||||||
|  | How does Giphy Navigator work? | ||||||
|  | 
 | ||||||
|  | - Client sends a request specifying the search query and the number of results. | ||||||
|  | - Giphy Navigator returns a response in json format. | ||||||
|  | - The response contains: | ||||||
|  |     - the search query | ||||||
|  |     - the limit number | ||||||
|  |     - the list of gif urls | ||||||
|  | 
 | ||||||
|  | Example response: | ||||||
|  | 
 | ||||||
|  | .. 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" | ||||||
|  |            } | ||||||
|  |        ] | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | The task is naive and that's exactly what we need for the tutorial. | ||||||
|  | 
 | ||||||
|  | Prepare the environment | ||||||
|  | ----------------------- | ||||||
|  | 
 | ||||||
|  | Let's create the environment for the project. | ||||||
|  | 
 | ||||||
|  | First we need to create a project folder and the virtual environment: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    mkdir giphynav-aiohttp-tutorial | ||||||
|  |    cd giphynav-aiohttp-tutorial | ||||||
|  |    python3 -m venv venv | ||||||
|  | 
 | ||||||
|  | Now let's activate the virtual environment: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    . venv/bin/activate | ||||||
|  | 
 | ||||||
|  | Environment is ready and now we're going to create the layout of the project. | ||||||
|  | 
 | ||||||
|  | Project layout | ||||||
|  | -------------- | ||||||
|  | 
 | ||||||
|  | Create next structure in the current directory. All files should be empty. That's ok for now. | ||||||
|  | 
 | ||||||
|  | Initial project layout:: | ||||||
|  | 
 | ||||||
|  |    ./ | ||||||
|  |    ├── giphynavigator/ | ||||||
|  |    │   ├── __init__.py | ||||||
|  |    │   ├── application.py | ||||||
|  |    │   ├── containers.py | ||||||
|  |    │   └── views.py | ||||||
|  |    ├── venv/ | ||||||
|  |    └── requirements.txt | ||||||
|  | 
 | ||||||
|  | Install the requirements | ||||||
|  | ------------------------ | ||||||
|  | 
 | ||||||
|  | Now it's time to install the project requirements. We will use next packages: | ||||||
|  | 
 | ||||||
|  | - ``dependency-injector`` - the dependency injection framework | ||||||
|  | - ``aiohttp`` - the web framework | ||||||
|  | - ``aiohttp-devtools`` - the helper library that will provide a development server with live | ||||||
|  |   reloading | ||||||
|  | - ``pyyaml`` - the YAML files parsing library, used for the reading of the configuration files | ||||||
|  | - ``pytest-aiohttp``- the helper library for the testing of the ``aiohttp`` application | ||||||
|  | - ``pytest-cov`` - the helper library for measuring the test coverage | ||||||
|  | 
 | ||||||
|  | Put next lines into the ``requirements.txt`` file: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    dependency-injector | ||||||
|  |    aiohttp | ||||||
|  |    aiohttp-devtools | ||||||
|  |    pyyaml | ||||||
|  |    pytest-aiohttp | ||||||
|  |    pytest-cov | ||||||
|  | 
 | ||||||
|  | and run next in the terminal: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    pip install -r requirements.txt | ||||||
|  | 
 | ||||||
|  | Let's also install the ``httpie``. It is a user-friendly command-line HTTP client for the API era. | ||||||
|  | We will use it for the manual testing. | ||||||
|  | 
 | ||||||
|  | Run the command in the terminal: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    pip install httpie | ||||||
|  | 
 | ||||||
|  | The requirements are setup. Now we will build a minimal application. | ||||||
|  | 
 | ||||||
|  | Minimal application | ||||||
|  | ------------------- | ||||||
|  | 
 | ||||||
|  | In this section we will build a minimal application. It will have an endpoint that we can call. | ||||||
|  | The endpoint will answer in the right format and will have no data. | ||||||
|  | 
 | ||||||
|  | Edit ``views.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    """Views module.""" | ||||||
|  | 
 | ||||||
|  |    from aiohttp import web | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    async def index(request: web.Request) -> web.Response: | ||||||
|  |        query = request.query.get('query', 'Dependency Injector') | ||||||
|  |        limit = int(request.query.get('limit', 10)) | ||||||
|  | 
 | ||||||
|  |        gifs = [] | ||||||
|  | 
 | ||||||
|  |        return web.json_response( | ||||||
|  |            { | ||||||
|  |                'query': query, | ||||||
|  |                'limit': limit, | ||||||
|  |                'gifs': gifs, | ||||||
|  |            }, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  | Now let's create the main part of our application - the container. Container will keep all of the | ||||||
|  | application components and their dependencies. First two providers we need to add are | ||||||
|  | the ``aiohttp`` application provider and the view provider. | ||||||
|  | 
 | ||||||
|  | Put next into the ``containers.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    """Application containers module.""" | ||||||
|  | 
 | ||||||
|  |    from dependency_injector import containers | ||||||
|  |    from dependency_injector.ext import aiohttp | ||||||
|  |    from aiohttp import web | ||||||
|  | 
 | ||||||
|  |    from . import views | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    class ApplicationContainer(containers.DeclarativeContainer): | ||||||
|  |        """Application container.""" | ||||||
|  | 
 | ||||||
|  |        app = aiohttp.Application(web.Application) | ||||||
|  | 
 | ||||||
|  |        index_view = aiohttp.View(views.index) | ||||||
|  | 
 | ||||||
|  | At the last we need to create the ``aiohttp`` application factory. It is traditionally called | ||||||
|  | ``create_app()``. It will create the container. Then it will use the container to create | ||||||
|  | the ``aiohttp`` application. Last step is to configure the routing - we will assign | ||||||
|  | ``index_view`` from the container to handle the requests to the root ``/`` of our REST API server. | ||||||
|  | 
 | ||||||
|  | Put next into the ``application.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    """Application module.""" | ||||||
|  | 
 | ||||||
|  |    from aiohttp import web | ||||||
|  | 
 | ||||||
|  |    from .containers import ApplicationContainer | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    def create_app(): | ||||||
|  |        """Create and return Flask application.""" | ||||||
|  |        container = ApplicationContainer() | ||||||
|  | 
 | ||||||
|  |        app: web.Application = container.app() | ||||||
|  |        app.container = container | ||||||
|  | 
 | ||||||
|  |        app.add_routes([ | ||||||
|  |            web.get('/', container.index_view.as_view()), | ||||||
|  |        ]) | ||||||
|  | 
 | ||||||
|  |        return app | ||||||
|  | 
 | ||||||
|  | .. note:: | ||||||
|  | 
 | ||||||
|  |    Container is the first object in the application. | ||||||
|  | 
 | ||||||
|  |    The container is used to create all other objects. | ||||||
|  | 
 | ||||||
|  | Now we're ready to run our application | ||||||
|  | 
 | ||||||
|  | Do next in the terminal: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    adev runserver giphynavigator/application.py --livereload | ||||||
|  | 
 | ||||||
|  | The output should be something like: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    [18:52:59] Starting aux server at http://localhost:8001 ◆ | ||||||
|  |    [18:52:59] Starting dev server at http://localhost:8000 ● | ||||||
|  | 
 | ||||||
|  | Let's use ``httpie`` to check that it works: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    http http://127.0.0.1:8000/ | ||||||
|  | 
 | ||||||
|  | You should see: | ||||||
|  | 
 | ||||||
|  | .. code-block:: json | ||||||
|  | 
 | ||||||
|  |    HTTP/1.1 200 OK | ||||||
|  |    Content-Length: 844 | ||||||
|  |    Content-Type: application/json; charset=utf-8 | ||||||
|  |    Date: Wed, 29 Jul 2020 21:01:50 GMT | ||||||
|  |    Server: Python/3.8 aiohttp/3.6.2 | ||||||
|  | 
 | ||||||
|  |    { | ||||||
|  |        "gifs": [], | ||||||
|  |        "limit": 10, | ||||||
|  |        "query": "Dependency Injector" | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | Minimal application is ready. Let's connect our application with the Giphy API. | ||||||
|  | 
 | ||||||
|  | Giphy API client | ||||||
|  | ---------------- | ||||||
|  | 
 | ||||||
|  | In this section we will integrate our application with the Giphy API. | ||||||
|  | 
 | ||||||
|  | We will create our own API client using ``aiohttp`` client. | ||||||
|  | 
 | ||||||
|  | Create ``giphy.py`` module in the ``giphynavigator`` package: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  |    :emphasize-lines: 6 | ||||||
|  | 
 | ||||||
|  |    ./ | ||||||
|  |    ├── giphynavigator/ | ||||||
|  |    │   ├── __init__.py | ||||||
|  |    │   ├── application.py | ||||||
|  |    │   ├── containers.py | ||||||
|  |    │   ├── giphy.py | ||||||
|  |    │   └── views.py | ||||||
|  |    ├── venv/ | ||||||
|  |    └── requirements.txt | ||||||
|  | 
 | ||||||
|  | and put next into it: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    """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() | ||||||
|  | 
 | ||||||
|  | Now we need to add ``GiphyClient`` into the container. The ``GiphyClient`` has two dependencies | ||||||
|  | that have to be injected: the API key and the request timeout. We will need to use two more | ||||||
|  | providers from the ``dependency_injector.providers`` module: | ||||||
|  | 
 | ||||||
|  | - ``Factory`` provider that will create the ``GiphyClient`` client. | ||||||
|  | - ``Configuration`` provider that will provide the API key and the request timeout. | ||||||
|  | 
 | ||||||
|  | Edit ``containers.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 3,7,15,17-21 | ||||||
|  | 
 | ||||||
|  |    """Application containers module.""" | ||||||
|  | 
 | ||||||
|  |    from dependency_injector import containers, providers | ||||||
|  |    from dependency_injector.ext import aiohttp | ||||||
|  |    from aiohttp import web | ||||||
|  | 
 | ||||||
|  |    from . import giphy, 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, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  |        index_view = aiohttp.View(views.index) | ||||||
|  | 
 | ||||||
|  | .. note:: | ||||||
|  | 
 | ||||||
|  |    We have used the configuration value before it was defined. That's the principle how the | ||||||
|  |    ``Configuration`` provider works. | ||||||
|  | 
 | ||||||
|  |    Use first, define later. | ||||||
|  | 
 | ||||||
|  | Now let's add the configuration file. | ||||||
|  | 
 | ||||||
|  | We will use YAML. | ||||||
|  | 
 | ||||||
|  | Create an empty file ``config.yml`` in the root root of the project: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  |    :emphasize-lines: 9 | ||||||
|  | 
 | ||||||
|  |    ./ | ||||||
|  |    ├── giphynavigator/ | ||||||
|  |    │   ├── __init__.py | ||||||
|  |    │   ├── application.py | ||||||
|  |    │   ├── containers.py | ||||||
|  |    │   ├── giphy.py | ||||||
|  |    │   └── views.py | ||||||
|  |    ├── venv/ | ||||||
|  |    ├── config.yml | ||||||
|  |    └── requirements.txt | ||||||
|  | 
 | ||||||
|  | and put next into it: | ||||||
|  | 
 | ||||||
|  | .. code-block:: yaml | ||||||
|  | 
 | ||||||
|  |    giphy: | ||||||
|  |      request_timeout: 10 | ||||||
|  | 
 | ||||||
|  | We will use an environment variable ``GIPHY_API_KEY`` to provide the API key. | ||||||
|  | 
 | ||||||
|  | Now we need to edit ``create_app()`` to make two things when application starts: | ||||||
|  | 
 | ||||||
|  | - Load the configuration file the ``config.yml``. | ||||||
|  | - Load the API key from the ``GIPHY_API_KEY`` environment variable. | ||||||
|  | 
 | ||||||
|  | Edit ``application.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 11-12 | ||||||
|  | 
 | ||||||
|  |    """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 | ||||||
|  | 
 | ||||||
|  | Now we need to create an API key and set it to the environment variable. | ||||||
|  | 
 | ||||||
|  | As for now, don’t worry, just take this one: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0 | ||||||
|  | 
 | ||||||
|  | .. 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>`_. | ||||||
|  | 
 | ||||||
|  | The Giphy API client and the configuration setup is done. Let's proceed to the search service. | ||||||
|  | 
 | ||||||
|  | Search service | ||||||
|  | -------------- | ||||||
|  | 
 | ||||||
|  | Now it's time to add the ``SearchService``. It will: | ||||||
|  | 
 | ||||||
|  | - Perform the search. | ||||||
|  | - Format result data. | ||||||
|  | 
 | ||||||
|  | ``SearchService`` will use ``GiphyClient``. | ||||||
|  | 
 | ||||||
|  | Create ``services.py`` module in the ``giphynavigator`` package: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  |    :emphasize-lines: 7 | ||||||
|  | 
 | ||||||
|  |    ./ | ||||||
|  |    ├── giphynavigator/ | ||||||
|  |    │   ├── __init__.py | ||||||
|  |    │   ├── application.py | ||||||
|  |    │   ├── containers.py | ||||||
|  |    │   ├── giphy.py | ||||||
|  |    │   ├── services.py | ||||||
|  |    │   └── views.py | ||||||
|  |    ├── venv/ | ||||||
|  |    └── requirements.txt | ||||||
|  | 
 | ||||||
|  | and put next into it: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |    """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']] | ||||||
|  | 
 | ||||||
|  | The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be injected. | ||||||
|  | Let's add ``SearchService`` to the container. | ||||||
|  | 
 | ||||||
|  | Edit ``containers.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 7,23-26 | ||||||
|  | 
 | ||||||
|  |    """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) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | The search service is ready. In the next section we're going to make it work. | ||||||
|  | 
 | ||||||
|  | Make the search work | ||||||
|  | -------------------- | ||||||
|  | 
 | ||||||
|  | Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view. | ||||||
|  | 
 | ||||||
|  | Edit ``views.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 5,8-11,15 | ||||||
|  | 
 | ||||||
|  |    """Views module.""" | ||||||
|  | 
 | ||||||
|  |    from aiohttp import web | ||||||
|  | 
 | ||||||
|  |    from .services import SearchService | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |    async def index( | ||||||
|  |            request: web.Request, | ||||||
|  |            search_service: SearchService, | ||||||
|  |    ) -> web.Response: | ||||||
|  |        query = request.query.get('query', 'Dependency Injector') | ||||||
|  |        limit = int(request.query.get('limit', 10)) | ||||||
|  | 
 | ||||||
|  |        gifs = await search_service.search(query, limit) | ||||||
|  | 
 | ||||||
|  |        return web.json_response( | ||||||
|  |            { | ||||||
|  |                'query': query, | ||||||
|  |                'limit': limit, | ||||||
|  |                'gifs': gifs, | ||||||
|  |            }, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  | Now let's inject the ``SearchService`` dependency into the ``index`` view. | ||||||
|  | 
 | ||||||
|  | Edit ``containers.py``: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  |    :emphasize-lines: 28-31 | ||||||
|  | 
 | ||||||
|  |    """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, | ||||||
|  |        ) | ||||||
|  | 
 | ||||||
|  | Make sure the app is running or use: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    adev runserver giphynavigator/application.py --livereload | ||||||
|  | 
 | ||||||
|  | and make a request to the API in the terminal: | ||||||
|  | 
 | ||||||
|  | .. code-block:: bash | ||||||
|  | 
 | ||||||
|  |    http http://localhost:8000/ query=="wow,it works" | ||||||
|  | 
 | ||||||
|  | You should see: | ||||||
|  | 
 | ||||||
|  | .. code-block:: json | ||||||
|  | 
 | ||||||
|  |    HTTP/1.1 200 OK | ||||||
|  |    Content-Length: 850 | ||||||
|  |    Content-Type: application/json; charset=utf-8 | ||||||
|  |    Date: Wed, 29 Jul 2020 22:22:55 GMT | ||||||
|  |    Server: Python/3.8 aiohttp/3.6.2 | ||||||
|  | 
 | ||||||
|  |    { | ||||||
|  |        "gifs": [ | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/discoverychannel-nugget-gold-rush-rick-ness-KGGPIlnC4hr4u2s3pY" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/primevideoin-ll1hyBS2IrUPLE0E71" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/jackman-works-jackmanworks-l4pTgQoCrmXq8Txlu" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/cat-massage-at-work-l46CzMaOlJXAFuO3u" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/everwhatproductions-fun-christmas-3oxHQCI8tKXoeW4IBq" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/spacestationgaming-love-wow-team-YST1F1J5g2yyLLvMJc" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/dollyparton-3xIVVMnZfG3KQ9v4Ye" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/greatbigstory-wow-omg-BLGlU7OWvFAFMoNjsM" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/soulpancake-wow-work-xUe4HVXTPi0wQ2OAJC" | ||||||
|  |            }, | ||||||
|  |            { | ||||||
|  |                "url": "https://giphy.com/gifs/nickelodeon-nick-pull-ups-casagrandes-eK136cynbxuOVk0qzJ" | ||||||
|  |            } | ||||||
|  |        ], | ||||||
|  |        "limit": 10, | ||||||
|  |        "query": "wow,it works" | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  | .. image:: https://media.giphy.com/media/3oxHQCI8tKXoeW4IBq/source.gif | ||||||
|  | 
 | ||||||
|  | The search works! | ||||||
|  | 
 | ||||||
|  | Make some refactoring | ||||||
|  | --------------------- | ||||||
|  | 
 | ||||||
|  | Tests | ||||||
|  | ----- | ||||||
|  | 
 | ||||||
|  | Conclusion | ||||||
|  | ---------- | ||||||
| 
 | 
 | ||||||
| .. disqus:: | .. disqus:: | ||||||
|  |  | ||||||
|  | @ -3,7 +3,8 @@ | ||||||
| Flask tutorial | Flask tutorial | ||||||
| ============== | ============== | ||||||
| 
 | 
 | ||||||
| This tutorials shows how to build ``Flask`` application following dependency injection principle. | This tutorials shows how to build ``Flask`` application following the dependency injection | ||||||
|  | principle. | ||||||
| 
 | 
 | ||||||
| Start from the scratch or jump to the section: | Start from the scratch or jump to the section: | ||||||
| 
 | 
 | ||||||
|  | @ -627,11 +628,11 @@ Github API client setup is done. | ||||||
| Search service | Search service | ||||||
| -------------- | -------------- | ||||||
| 
 | 
 | ||||||
| Now it's time to add ``SearchService``. It will: | Now it's time to add  the ``SearchService``. It will: | ||||||
| 
 | 
 | ||||||
| - Perform the search. | - Perform the search. | ||||||
| - Fetch commit extra data for each result. | - Fetch commit extra data for each result. | ||||||
| - Format result data | - Format result data. | ||||||
| 
 | 
 | ||||||
| ``SearchService`` will use ``Github`` API client. | ``SearchService`` will use ``Github`` API client. | ||||||
| 
 | 
 | ||||||
|  | @ -827,8 +828,8 @@ Make some refactoring | ||||||
| 
 | 
 | ||||||
| Our ``index`` view has two hardcoded config values: | Our ``index`` view has two hardcoded config values: | ||||||
| 
 | 
 | ||||||
| - Default search term | - Default search query | ||||||
| - Limit of the results | - Default results limit | ||||||
| 
 | 
 | ||||||
| Let's make some refactoring. We will move these values to the config. | Let's make some refactoring. We will move these values to the config. | ||||||
| 
 | 
 | ||||||
|  | @ -1075,7 +1076,7 @@ You should see: | ||||||
| 
 | 
 | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|    Take a look on the highlights in the ``tests.py``. |    Take a look at the highlights in the ``tests.py``. | ||||||
| 
 | 
 | ||||||
|    It emphasizes the overriding of the ``Github`` API client. |    It emphasizes the overriding of the ``Github`` API client. | ||||||
| 
 | 
 | ||||||
|  | @ -1084,7 +1085,7 @@ Conclusion | ||||||
| 
 | 
 | ||||||
| We are done. | We are done. | ||||||
| 
 | 
 | ||||||
| It this tutorial we've build ``Flask`` application following dependency injection principle. | In this tutorial we've build ``Flask`` application following the dependency injection principle. | ||||||
| We've used ``Dependency Injector`` as a dependency injection framework. | We've used ``Dependency Injector`` as a dependency injection framework. | ||||||
| 
 | 
 | ||||||
| The main part of this application is the container. It keeps all the application components and | The main part of this application is the container. It keeps all the application components and | ||||||
|  |  | ||||||
|  | @ -2,8 +2,8 @@ | ||||||
| 
 | 
 | ||||||
| from dependency_injector import containers, providers | from dependency_injector import containers, providers | ||||||
| from dependency_injector.ext import aiohttp | from dependency_injector.ext import aiohttp | ||||||
| 
 |  | ||||||
| from aiohttp import web | from aiohttp import web | ||||||
|  | 
 | ||||||
| from . import giphy, services, views | from . import giphy, services, views | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,8 +22,8 @@ async def test_index(client, app): | ||||||
|     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) |     giphy_client_mock = mock.AsyncMock(spec=GiphyClient) | ||||||
|     giphy_client_mock.search.return_value = { |     giphy_client_mock.search.return_value = { | ||||||
|         'data': [ |         'data': [ | ||||||
|             {'url': 'https://giphy/gif1.gif'}, |             {'url': 'https://giphy.com/gif1.gif'}, | ||||||
|             {'url': 'https://giphy/gif2.gif'}, |             {'url': 'https://giphy.com/gif2.gif'}, | ||||||
|         ], |         ], | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -42,8 +42,8 @@ async def test_index(client, app): | ||||||
|         'query': 'test', |         'query': 'test', | ||||||
|         'limit': 10, |         'limit': 10, | ||||||
|         'gifs': [ |         'gifs': [ | ||||||
|             {'url': 'https://giphy/gif1.gif'}, |             {'url': 'https://giphy.com/gif1.gif'}, | ||||||
|             {'url': 'https://giphy/gif2.gif'}, |             {'url': 'https://giphy.com/gif2.gif'}, | ||||||
|         ], |         ], | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user