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."""
fromdependency_injectorimport containers, providers
fromgithubimport Github
from.import services
classContainer(containers.DeclarativeContainer):
 wiring_config = containers.WiringConfiguration(modules=[".views"])
 config = providers.Configuration(yaml_files=["config.yml"])
 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."""
fromflaskimport request, render_template
fromdependency_injector.wiringimport inject, Provide
from.servicesimport SearchService
from.containersimport Container
@inject
defindex(
 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."""
fromflaskimport Flask
fromflask_bootstrapimport Bootstrap4
from.containersimport Container
from.import views
defcreate_app() -> Flask:
 container = Container()
 container.config.github.auth_token.from_env("GITHUB_TOKEN")
 app = Flask(__name__)
 app.container = container
 app.add_url_rule("/", "index", views.index)
 bootstrap = Bootstrap4()
 bootstrap.init_app(app)
 return app

Tests

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

"""Tests module."""
fromunittestimport mock
importpytest
fromgithubimport Github
fromflaskimport url_for
from.applicationimport create_app
@pytest.fixture
defapp():
 app = create_app()
 yield app
 app.container.unwire()
deftest_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
deftest_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.

Sponsor the project on GitHub:

[フレーム]