Привет,
Я создатель Dependency Injector. Это dependency injection фреймворк для Python.
В этом руководстве хочу показать как применять Dependency Injector для разработки Flask приложений.
Руководство состоит из таких частей:
Завершенный проект можно найти на Github.
Для старта необходимо иметь:
И желательно иметь:
Мы будем строить приложение, которое помогает искать репозитории на Github. Назовем его Github Navigator.
Как работает Github Navigator?
В первую очередь нам нужно создать папку проекта и virtual environment:
Теперь давайте активируем virtual environment:
Окружение готово, теперь займемся структурой проекта.
Создадим в текущей папке следующую структуру. Все файлы пока оставляем пустыми. Это пока не критично.
Начальная структура:
Пришло время установить Flask и Dependency Injector.
Добавим следующие строки в файл
Теперь давайте их установим:
И проверим что установка прошла успешно:
Вы увидите что-то вроде:
Давайте создадим минимальное hello world приложение.
Добавим следующие строки в файл
Теперь добавим контейнер зависимостей (дальше просто контейнер). Контейнер будет содержать все компоненты приложения. Добавим первые два компонента. Это Flask приложение и представление
Добавим следующее в файл
Теперь нам нужно создать фабрику Flask приложения. Ее обычно называют
Отредактируем
Теперь наше приложение готово сказать «Hello, World!».
Выполните в терминале:
Вывод должен выглядеть приблизительно так:
Откройте браузер и зайдите на http://127.0.0.1:5000/.
Вы увидите «Hello, World!».
Отлично. Наше минимальное приложение успешно стартует и работает.
Давайте сделаем его немного красивее.
Мы будем использовать Bootstrap 4. Используем для этого расширение Bootstrap-Flask. Оно поможет нам добавить все нужные файлы в несколько кликов.
Добавим
и выполним в терминале:
Теперь добавим расширение
Отредактируйте
Давайте инициализируем расширение
Отредактируйте
Теперь нужно добавить шаблоны. Для этого нам понадобится добавить папку
Создаем папку
Теперь давайте наполним базовый шаблон.
Добавим следующие строки в файл
Теперь наполним шаблон основной страницы.
Добавим следующие строки в файл
Отлично, почти готово. Последним шагом изменим представление
Отредактируем
Готово.
Убедитесь что приложение работает или выполните
Вы должны увидите:
В этом разделе интегрируем наше приложение с Github API.
Мы будем использовать библиотеку PyGithub.
Добавим её в
и выполним в терминале:
Теперь нам нужно добавить Github API клиент в контейнер. Для этого нам нужно будет воспользоваться двумя новыми провайдерами из модуля
Сделаем это.
Отредактируем
Теперь давайте добавим файл конфигурации.
Будем использовать YAML.
Создайте пустой файл
И заполните его следующими строками:
Для работы с конфигурационным файлом мы будем использовать библиотеку PyYAML. Добавим ее в файл с зависимостями.
Отредактируйте
и установите зависимость:
Для передачи API токена мы будем использовать переменную окружения
Теперь нам нужно отредактировать
Отредактируйте
Теперь нам нужно создать API токен.
Для это нужно:
Готово.
Установка Github API клиента завершена.
Пришло время добавить сервис поиска
Создайте пустой файл
и добавьте в него следующие строки:
Теперь добавим
Отредактируйте
Теперь мы готовы чтобы поиск заработал. Давайте используем
Отредактируйте
Теперь изменим контейнер чтобы передавать зависимость
Отредактируйте
Убедитесь что приложение работает или выполните
Вы увидите:
Наше представление
Давайте сделаем небольшой рефакторинг. Мы перенесем эти значения в конфигурацию.
Отредактируйте
Теперь нам нужно чтобы эти значения передавались при вызове. Давайте обновим контейнер.
Отредактируйте
Теперь давайте обновим конфигурационный файл.
Отредактируйте
Готово.
Рефакторинг закончен. Му сделали код чище.
Было бы хорошо добавить немного тестов. Давайте это сделаем.
Мы будем использовать pytest и coverage.
Отредактируйте
и установите новые пакеты:
Создайте пустой файл
и добавьте в него следующие строки:
Теперь давайте запустим тестирование и проверим покрытие:
Вы увидите:
Мы построили Flask приложения применяя принцип dependency injection. Мы использовали Dependency Injector в качестве dependency injection фреймворка.
Основная часть нашего приложения это контейнер. Он содержит все компоненты приложения и их зависимости в одном месте. Это предоставляет контроль над структурой приложения. Её легко понимать и изменять:
Контейнер как карта вашего приложения. Вы всегда знайте что от чего зависит.
Я создатель Dependency Injector. Это dependency injection фреймворк для Python.
В этом руководстве хочу показать как применять Dependency Injector для разработки Flask приложений.
Руководство состоит из таких частей:
- Что мы будем строить?
- Подготовим окружение
- Структура проекта
- Hello world!
- Подключаем стили
- Подключаем Github
- Сервис поиска
- Подключаем поиск
- Немного рефакторинга
- Добавляем тесты
- Заключение
Завершенный проект можно найти на Github.
Для старта необходимо иметь:
- Python 3.5+
- Virtual environment
И желательно иметь:
- Начальные навыки разработки с помощью Flask
- Общее представление о принципе dependency injection
Что мы будем строить?
Мы будем строить приложение, которое помогает искать репозитории на Github. Назовем его Github Navigator.
Как работает Github Navigator?
- Пользователь открывает веб-страницу где ему предлагают ввести поисковый запрос.
- Пользователь вводит запрос и нажимает Enter.
- Github Navigator ищет подходящие репозитории на Github.
- По окончанию поиска Github Navigator показывает пользователю веб-страницу с результатами.
- Страница результатов показывает все найденные репозитории и поисковый запрос.
- Для каждого репозитория пользователь видит:
- имя репозитория
- владельца репозитория
- последний коммит в репозиторий
- Пользователь может нажать на любой из элементов чтобы открыть его страницу на Github.
Подготовим окружение
В первую очередь нам нужно создать папку проекта и virtual environment:
mkdir ghnav-flask-tutorial
cd ghnav-flask-tutorial
python3 -m venv venv
Теперь давайте активируем virtual environment:
. venv/bin/activate
Окружение готово, теперь займемся структурой проекта.
Структура проекта
Создадим в текущей папке следующую структуру. Все файлы пока оставляем пустыми. Это пока не критично.
Начальная структура:
./
+-- githubnavigator/
¦ +-- __init__.py
¦ +-- application.py
¦ +-- containers.py
¦ L-- views.py
+-- venv/
L-- requirements.txt
Пришло время установить Flask и Dependency Injector.
Добавим следующие строки в файл
requirements.txt
:dependency-injector
flask
Теперь давайте их установим:
pip install -r requirements.txt
И проверим что установка прошла успешно:
python -c "import dependency_injector; print(dependency_injector.__version__)"
python -c "import flask; print(flask.__version__)"
Вы увидите что-то вроде:
(venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)"
3.22.0
(venv) $ python -c "import flask; print(flask.__version__)"
1.1.2
Hello world!
Давайте создадим минимальное hello world приложение.
Добавим следующие строки в файл
views.py
:"""Views module."""
def index():
return 'Hello, World!'
Теперь добавим контейнер зависимостей (дальше просто контейнер). Контейнер будет содержать все компоненты приложения. Добавим первые два компонента. Это Flask приложение и представление
index
.Добавим следующее в файл
containers.py
:"""Application containers module."""
from dependency_injector import containers
from dependency_injector.ext import flask
from flask import Flask
from . import views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
index_view = flask.View(views.index)
Теперь нам нужно создать фабрику Flask приложения. Ее обычно называют
create_app()
. Она будет создавать контейнер. Контейнер будет использован для создания Flask приложения. Последним шагом настроим маршрутизацию — мы назначим представление index_view
из контейнера обрабатывать запросы к корню "/" нашего приложения.Отредактируем
application.py
:"""Application module."""
from .containers import ApplicationContainer
def create_app():
"""Create and return Flask application."""
container = ApplicationContainer()
app = container.app()
app.container = container
app.add_url_rule('/', view_func=container.index_view.as_view())
return app
Контейнер — первый объект в приложении. Он используется для получения всех остальных объектов.
Теперь наше приложение готово сказать «Hello, World!».
Выполните в терминале:
export FLASK_APP=githubnavigator.application
export FLASK_ENV=development
flask run
Вывод должен выглядеть приблизительно так:
* Serving Flask app "githubnavigator.application" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with fsevents reloader
* Debugger is active!
* Debugger PIN: 473-587-859
Откройте браузер и зайдите на http://127.0.0.1:5000/.
Вы увидите «Hello, World!».
Отлично. Наше минимальное приложение успешно стартует и работает.
Давайте сделаем его немного красивее.
Подключаем стили
Мы будем использовать Bootstrap 4. Используем для этого расширение Bootstrap-Flask. Оно поможет нам добавить все нужные файлы в несколько кликов.
Добавим
bootstrap-flask
в requirements.txt
:dependency-injector
flask
bootstrap-flask
и выполним в терминале:
pip install --upgrade -r requirements.txt
Теперь добавим расширение
bootstrap-flask
в контейнер.Отредактируйте
containers.py
:"""Application containers module."""
from dependency_injector import containers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from . import views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
index_view = flask.View(views.index)
Давайте инициализируем расширение
bootstrap-flask
. Нам нужно будет изменить create_app()
.Отредактируйте
application.py
:"""Application module."""
from .containers import ApplicationContainer
def create_app():
"""Create and return Flask application."""
container = ApplicationContainer()
app = container.app()
app.container = container
bootstrap = container.bootstrap()
bootstrap.init_app(app)
app.add_url_rule('/', view_func=container.index_view.as_view())
return app
Теперь нужно добавить шаблоны. Для этого нам понадобится добавить папку
templates/
в пакет githubnavigator
. Внутри папки с шаблонами добавим два файла:base.html
— базовый шаблонindex.html
— шаблон основной страницы
Создаем папку
templates
и два пустых файла внутри base.html
и index.html
:./
+-- githubnavigator/
¦ +-- templates/
¦ ¦ +-- base.html
¦ ¦ L-- index.html
¦ +-- __init__.py
¦ +-- application.py
¦ +-- containers.py
¦ L-- views.py
+-- venv/
L-- requirements.txt
Теперь давайте наполним базовый шаблон.
Добавим следующие строки в файл
base.html
:<!doctype html>
<html lang="en">
<head>
{% block head %}
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block styles %}
<!-- Bootstrap CSS -->
{{ bootstrap.load_css() }}
{% endblock %}
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
<!-- Your page content -->
{% block content %}{% endblock %}
{% block scripts %}
<!-- Optional JavaScript -->
{{ bootstrap.load_js() }}
{% endblock %}
</body>
</html>
Теперь наполним шаблон основной страницы.
Добавим следующие строки в файл
index.html
:{% extends "base.html" %}
{% block title %}Github Navigator{% endblock %}
{% block content %}
<div class="container">
<h1 class="mb-4">Github Navigator</h1>
<form>
<div class="form-group form-row">
<div class="col-10">
<label for="search_query" class="col-form-label">
Search for:
</label>
<input class="form-control" type="text" id="search_query"
placeholder="Type something to search on the GitHub"
name="query"
value="{{ query if query }}">
</div>
<div class="col">
<label for="search_limit" class="col-form-label">
Limit:
</label>
<select class="form-control" id="search_limit" name="limit">
{% for value in [5, 10, 20] %}
<option {% if value == limit %}selected{% endif %}>
{{ value }}
</option>
{% endfor %}
</select>
</div>
</div>
</form>
<p><small>Results found: {{ repositories|length }}</small></p>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Repository</th>
<th class="text-nowrap">Repository owner</th>
<th class="text-nowrap">Last commit</th>
</tr>
</thead>
<tbody>
{% for repository in repositories %} {{n}}
<tr>
<th>{{ loop.index }}</th>
<td><a href="{{ repository.url }}">
{{ repository.name }}</a>
</td>
<td><a href="{{ repository.owner.url }}">
<img src="{{ repository.owner.avatar_url }}"
alt="avatar" height="24" width="24"/></a>
<a href="{{ repository.owner.url }}">
{{ repository.owner.login }}</a>
</td>
<td><a href="{{ repository.latest_commit.url }}">
{{ repository.latest_commit.sha }}</a>
{{ repository.latest_commit.message }}
{{ repository.latest_commit.author_name }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
Отлично, почти готово. Последним шагом изменим представление
index
чтобы оно использовало шаблон index.html
.Отредактируем
views.py
:"""Views module."""
from flask import request, render_template
def index():
query = request.args.get('query', 'Dependency Injector')
limit = request.args.get('limit', 10, int)
repositories = []
return render_template(
'index.html',
query=query,
limit=limit,
repositories=repositories,
)
Готово.
Убедитесь что приложение работает или выполните
flask run
и откройте http://127.0.0.1:5000/.Вы должны увидите:
Подключаем Github
В этом разделе интегрируем наше приложение с Github API.
Мы будем использовать библиотеку PyGithub.
Добавим её в
requirements.txt
:dependency-injector
flask
bootstrap-flask
pygithub
и выполним в терминале:
pip install --upgrade -r requirements.txt
Теперь нам нужно добавить Github API клиент в контейнер. Для этого нам нужно будет воспользоваться двумя новыми провайдерами из модуля
dependency_injector.providers
:- Провайдер
Factory
будет создавать Github клиент. - Провайдер
Configuration
будет передавать API токен и таймаут Github клиенту.
Сделаем это.
Отредактируем
containers.py
:"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
config = providers.Configuration()
github_client = providers.Factory(
Github,
login_or_token=config.github.auth_token,
timeout=config.github.request_timeout,
)
index_view = flask.View(views.index)
Мы использовали параметры конфигурации перед тем как задали их значения. Это принцип, по которому работает провайдерConfiguration
.
Сначала используем, потом задаем значения.
Теперь давайте добавим файл конфигурации.
Будем использовать YAML.
Создайте пустой файл
config.yml
в корне проекта:./
+-- githubnavigator/
¦ +-- templates/
¦ ¦ +-- base.html
¦ ¦ L-- index.html
¦ +-- __init__.py
¦ +-- application.py
¦ +-- containers.py
¦ L-- views.py
+-- venv/
+-- config.yml
L-- requirements.txt
И заполните его следующими строками:
github:
request_timeout: 10
Для работы с конфигурационным файлом мы будем использовать библиотеку PyYAML. Добавим ее в файл с зависимостями.
Отредактируйте
requirements.txt
:dependency-injector
flask
bootstrap-flask
pygithub
pyyaml
и установите зависимость:
pip install --upgrade -r requirements.txt
Для передачи API токена мы будем использовать переменную окружения
GITHUB_TOKEN
.Теперь нам нужно отредактировать
create_app()
чтобы сделать 2 действие при старте приложения:- Загрузить конфигурацию из
config.yml
- Загрузить API токен из переменной окружения
GITHUB_TOKEN
Отредактируйте
application.py
:"""Application module."""
from .containers import ApplicationContainer
def create_app():
"""Create and return Flask application."""
container = ApplicationContainer()
container.config.from_yaml('config.yml')
container.config.github.auth_token.from_env('GITHUB_TOKEN')
app = container.app()
app.container = container
bootstrap = container.bootstrap()
bootstrap.init_app(app)
app.add_url_rule('/', view_func=container.index_view.as_view())
return app
Теперь нам нужно создать API токен.
Для это нужно:
- Следовать этому руководству на Github
- Установить токен в переменную окружения:
export GITHUB_TOKEN=<your token>
Этот пункт можно временно пропустить.
Приложение будет работать без токена, но с ограниченной пропускной способностью. Ограничение для неаутентифицированных клиентов: 60 запросов в час. Токен нужен чтобы увеличить эту квоту до 5000 в час.
Готово.
Установка Github API клиента завершена.
Сервис поиска
Пришло время добавить сервис поиска
SearchService
. Он будет:- Выполнять поиск на Github
- Получать дополнительные данные о коммитах
- Преобразовывать формат результат
SearchService
будет использовать Github API клиент.Создайте пустой файл
services.py
в пакете githubnavigator
:./
+-- githubnavigator/
¦ +-- templates/
¦ ¦ +-- base.html
¦ ¦ L-- index.html
¦ +-- __init__.py
¦ +-- application.py
¦ +-- containers.py
¦ +-- services.py
¦ L-- views.py
+-- venv/
+-- config.yml
L-- requirements.txt
и добавьте в него следующие строки:
"""Services module."""
from github import Github
from github.Repository import Repository
from github.Commit import Commit
class SearchService:
"""Search service performs search on Github."""
def __init__(self, github_client: Github):
self._github_client = github_client
def search_repositories(self, query, limit):
"""Search for repositories and return formatted data."""
repositories = self._github_client.search_repositories(
query=query,
**{'in': 'name'},
)
return [
self._format_repo(repository)
for repository in repositories[:limit]
]
def _format_repo(self, repository: Repository):
commits = repository.get_commits()
return {
'url': repository.html_url,
'name': repository.name,
'owner': {
'login': repository.owner.login,
'url': repository.owner.html_url,
'avatar_url': repository.owner.avatar_url,
},
'latest_commit': self._format_commit(commits[0]) if commits else {},
}
def _format_commit(self, commit: Commit):
return {
'sha': commit.sha,
'url': commit.html_url,
'message': commit.commit.message,
'author_name': commit.commit.author.name,
}
Теперь добавим
SearchService
в контейнер.Отредактируйте
containers.py
:"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
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,
)
index_view = flask.View(views.index)
Подключаем поиск
Теперь мы готовы чтобы поиск заработал. Давайте используем
SearchService
в index
представлении.Отредактируйте
views.py
:"""Views module."""
from flask import request, render_template
from .services import SearchService
def index(search_service: SearchService):
query = request.args.get('query', 'Dependency Injector')
limit = request.args.get('limit', 10, int)
repositories = search_service.search_repositories(query, limit)
return render_template(
'index.html',
query=query,
limit=limit,
repositories=repositories,
)
Теперь изменим контейнер чтобы передавать зависимость
SearchService
в представление index
при его вызове.Отредактируйте
containers.py
:"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
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,
)
index_view = flask.View(
views.index,
search_service=search_service,
)
Убедитесь что приложение работает или выполните
flask run
и откройте http://127.0.0.1:5000/.Вы увидите:
Немного рефакторинга
Наше представление
index
содержит два hardcoded значения:- Поисковый запрос по умолчанию
- Лимит количества результатов
Давайте сделаем небольшой рефакторинг. Мы перенесем эти значения в конфигурацию.
Отредактируйте
views.py
:"""Views module."""
from flask import request, render_template
from .services import SearchService
def index(
search_service: SearchService,
default_query: str,
default_limit: 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,
)
Теперь нам нужно чтобы эти значения передавались при вызове. Давайте обновим контейнер.
Отредактируйте
containers.py
:"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
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,
)
index_view = flask.View(
views.index,
search_service=search_service,
default_query=config.search.default_query,
default_limit=config.search.default_limit,
)
Теперь давайте обновим конфигурационный файл.
Отредактируйте
config.yml
:github:
request_timeout: 10
search:
default_query: "Dependency Injector"
default_limit: 10
Готово.
Рефакторинг закончен. Му сделали код чище.
Добавляем тесты
Было бы хорошо добавить немного тестов. Давайте это сделаем.
Мы будем использовать pytest и coverage.
Отредактируйте
requirements.txt
:dependency-injector
flask
bootstrap-flask
pygithub
pyyaml
pytest-flask
pytest-cov
и установите новые пакеты:
pip install -r requirements.txt
Создайте пустой файл
tests.py
в пакете githubnavigator
:./
+-- githubnavigator/
¦ +-- templates/
¦ ¦ +-- base.html
¦ ¦ L-- index.html
¦ +-- __init__.py
¦ +-- application.py
¦ +-- containers.py
¦ +-- services.py
¦ +-- tests.py
¦ L-- views.py
+-- venv/
+-- config.yml
L-- requirements.txt
и добавьте в него следующие строки:
"""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():
return create_app()
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
Теперь давайте запустим тестирование и проверим покрытие:
py.test githubnavigator/tests.py --cov=githubnavigator
Вы увидите:
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: flask-1.0.0, cov-2.10.0
collected 2 items
githubnavigator/tests.py .. [100%]
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name Stmts Miss Cover
----------------------------------------------------
githubnavigator/__init__.py 0 0 100%
githubnavigator/application.py 11 0 100%
githubnavigator/containers.py 13 0 100%
githubnavigator/services.py 14 0 100%
githubnavigator/tests.py 32 0 100%
githubnavigator/views.py 7 0 100%
----------------------------------------------------
TOTAL 77 0 100%
Обратите внимание как мы заменяемgithub_client
моком с помощью метода.override()
. Таким образом можно переопределить возвращаемое значения любого провайдера.
Заключение
Мы построили Flask приложения применяя принцип dependency injection. Мы использовали Dependency Injector в качестве dependency injection фреймворка.
Основная часть нашего приложения это контейнер. Он содержит все компоненты приложения и их зависимости в одном месте. Это предоставляет контроль над структурой приложения. Её легко понимать и изменять:
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
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,
)
index_view = flask.View(
views.index,
search_service=search_service,
default_query=config.search.default_query,
default_limit=config.search.default_limit,
)
Контейнер как карта вашего приложения. Вы всегда знайте что от чего зависит.
Что дальше?
- Узнайте больше о Dependency Injector на GitHub
- Ознакомтесь с документацией на Read the Docs
- Есть вопрос или нашли баг? Откройте issue на Github
Tanner
Мне показалось, или вы вместо исходного текста
containers.py
сохранили в своём репо файлcontainers.c
, сгенерированный автоматически?anonymous Автор
Все верно.
Модуль
containers
— Python код, который транслирован в C c помощью Cython. Есть несколько особенностей: несколько mapping'ов на модульproviders
, несколько cdef приведений типов и несколькоcpdef
функций.В планах перевести модуль
containers
в более глубокую Cython типизацию после прекращения поддержки Python 2. Пока не получается из-за применения метакласса.