Сбор покрытия Flask (Python) в Runtime
Всем привет, меня зовут Осипов Станислав. Я занимаюсь AppSec/DevOps с 2021 года. В этой статье я хочу рассказать как можно собрать покрытие Python приложения в runtime незавершая работу приложения.
Соображения Midjourney по поводу статьи
Что было использовано в статье:
https://github.com/pallets/flask — Flask 3.03
https://github.com/nedbat/coveragepy — coverage 7.5.1
Подготовка Flask
Выполним установку Flask согласно инструкции:
https://github.com/pallets/flask/blob/main/docs/installation.rst
В директории приложения создадим app.py, взяв за основу пример из репозитория Flask, и добавим пару роутов:
import coverage, hashlib
from flask import Flask
#Регистрация rout'ов
def main():
@app.route("/")
def hello():
return "Main route"
@app.route("/1")
def hello1():
return "First route"
@app.route("/2")
def hello2():
return hashlib.sha256(b"Nobody inspects the spammish repetition").hexdigest()
#Запуск веб-сервера
app.run()
#Инициализация
app = Flask(__name__)
main()
Подготовка coverage
Установим модуль для сборки покрытия:
pip install coverage
Для отображения покрытия third-party модулей в отчёте закомментим следующие строки:
.venv/lib/python3.11/site-packages/coverage/inorout.py-16478:
if self.third_match.match(filename) and not self.source_in_third_match.match(filename):
.venv/lib/python3.11/site-packages/coverage/inorout.py:16579:
return "inside --source, but is third-party"
Инструментация кода
Согласно https://coverage.readthedocs.io/en/7.5.1/api.html сбор покрытия осуществляется следующим образом:
import coverage
cov = coverage.Coverage() #Инициализация модуля
cov.start() # Запуск сбора покрытия
# .. call your code ..
cov.stop() # Остановить сбор покрытия - в нашем случае является избыточным
cov.save() # Сохранить покрытие
cov.html_report() # Сгенерировать отчет в формате .html
Так как стоит задача обновлять покрытие при каждом запросе к приложению Flask, то необходимо внедрить функции по сборке покрытия в исходный код Flask.
Для этого в файле выполним следующие изменения:
.venv/lib/python3.11/site-packages/flask/app.py
Подключим модуль coverage указав флаги:
cover_pylib — отображать покрытие стандартых модулей Python
auto_data — продолжать запись покрытия в один файл
source — список директорий исходного кода, которые будут учитываться в отчете
Подключения модуля coverage
13a14
> import coverage
Встраивание модуля покрытия
Добавим инициализацию модуля coverage в аттрибуте класса Flask и метод запуска сбора покрытия:
253c253,254 #Добавляем инициализацию сбора покрытия в объект класса Flask
и запуск сбора покрытия в метод "run" класса Flask
<
---
> self.cov = coverage.Coverage(cover_pylib=True, auto_data=True, source=["./", ".venv"])
> self.cov.start()
Поиск метода для сохранения покрытия
Найдем метод, который будет вызываться при обработке запроса.
В случае Flask я выбрал метод make_response, который отвечает за отправку ответа на запорс.
В методе этом будем сохранять/обновлять покрытие
1230c1229,1230
<
---
> self.cov.save()
> self.cov.html_report()
Проверяем сбор покрытия в Runtime
Запустим приложение: python3 app.py
В браузере перейдем по адресу, содержащий main route — 127.0.0.1:5000
Откроем страницу, содержащую покрытие — firefox htmlcov/index.html
Результат сбора покрытия
Покрытие: Составило 11% вместе исходным кодом сторонних зависимостей.
В браузере перейдем по адресу, содержащий второй route — 127.0.0.1:5000/2
Обновим страницу, содержащую покрытие
Результат сбора покрытия
Покрытие: Увеличилось на 5 процентов после обращения к 2 rout’у.
Итоги
Благодаря встраиванию модуля сбора покрытия в исходный код Flask, удалось добиться обновления покрытия при каждом запросе к приложеню.
Благодарю за внимание!
Предложения, вопросы, замечания, конструктивная критика приветствуется в комментариях.