Flask example

This example shows how to use Dependency Injector with Flask.

The example application helps to search for repositories on the Github.

../_images/flask.png

The source code is available on the Github.

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

Application structure

Application has next structure:

./
├── githubnavigator/
│   ├── templates
│   │   ├── base.html
│   │   └── index.py
│   ├── __init__.py
│   ├── application.py
│   ├── containers.py
│   ├── services.py
│   ├── tests.py
│   └── views.py
├── config.yml
└── requirements.txt

Container

Declarative container is defined in githubnavigator/containers.py:

"""Containers module."""

from dependency_injector import containers, providers
from github import Github

from . import services


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.github.auth_token,
        timeout=config.github.request_timeout,
    )

    search_service = providers.Factory(
        services.SearchService,
        github_client=github_client,
    )

Views

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

Listing of githubnavigator/views.py:

"""Views module."""

from flask import request, render_template
from dependency_injector.wiring import Provide

from .services import SearchService
from .containers import Container


def index(
        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()],
):
    query = request.args.get('query', default_query)
    limit = request.args.get('limit', default_limit, int)

    repositories = search_service.search_repositories(query, limit)

    return render_template(
        'index.html',
        query=query,
        limit=limit,
        repositories=repositories,
    )

Application factory

Application factory creates container, wires it with the views module, creates Flask app and setup routes.

Listing of githubnavigator/application.py:

"""Application module."""

from flask import Flask
from flask_bootstrap import Bootstrap

from .containers import Container
from . import views


def create_app() -> Flask:
    container = Container()
    container.config.from_yaml('config.yml')
    container.config.github.auth_token.from_env('GITHUB_TOKEN')
    container.wire(modules=[views])

    app = Flask(__name__)
    app.container = container
    app.add_url_rule('/', 'index', views.index)

    bootstrap = Bootstrap()
    bootstrap.init_app(app)

    return app

Tests

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

"""Tests module."""

from unittest import mock

import pytest
from github import Github
from flask import url_for

from .application import create_app


@pytest.fixture
def app():
    app = create_app()
    yield app
    app.container.unwire()


def test_index(client, app):
    github_client_mock = mock.Mock(spec=Github)
    github_client_mock.search_repositories.return_value = [
        mock.Mock(
            html_url='repo1-url',
            name='repo1-name',
            owner=mock.Mock(
                login='owner1-login',
                html_url='owner1-url',
                avatar_url='owner1-avatar-url',
            ),
            get_commits=mock.Mock(return_value=[mock.Mock()]),
        ),
        mock.Mock(
            html_url='repo2-url',
            name='repo2-name',
            owner=mock.Mock(
                login='owner2-login',
                html_url='owner2-url',
                avatar_url='owner2-avatar-url',
            ),
            get_commits=mock.Mock(return_value=[mock.Mock()]),
        ),
    ]

    with app.container.github_client.override(github_client_mock):
        response = client.get(url_for('index'))

    assert response.status_code == 200
    assert b'Results found: 2' in response.data

    assert b'repo1-url' in response.data
    assert b'repo1-name' in response.data
    assert b'owner1-login' in response.data
    assert b'owner1-url' in response.data
    assert b'owner1-avatar-url' in response.data

    assert b'repo2-url' in response.data
    assert b'repo2-name' in response.data
    assert b'owner2-login' in response.data
    assert b'owner2-url' in response.data
    assert b'owner2-avatar-url' in response.data


def test_index_no_results(client, app):
    github_client_mock = mock.Mock(spec=Github)
    github_client_mock.search_repositories.return_value = []

    with app.container.github_client.override(github_client_mock):
        response = client.get(url_for('index'))

    assert response.status_code == 200
    assert b'Results found: 0' in response.data

Sources

Explore the sources on the Github.