Интеграция .pre-commit hook в Django проект

от автора

Доброго дня!

Меня зовут Соболев Андрей и сегодня я вам расскажу как мы приготовили .pre-commit hook на нашем проекте.

Вступление

Для начала пару слов, о том что такое в целом хуки (hooks) и для чего они могут быть нужны. Git «из коробки» предоставляет инструмент, который умеет запускать ваши скрипты при наступлении какого либо события (к примеру пуш на сервер и т.п.)

.pre-commit это удобная надстройка над дефолтным git pre-commit hook, которая запускает скрипты описанные в .pre-commit-config.yaml перед созданием коммита. В теории звучит просто, перейдем к практике.

Установка

Установим необходимые зависимости

pre-commit # основной пакет https://pre-commit.com/  autoflake # для удаления неиспользуемых импортов (в нашем случае) black # форматируем код pyupgrade # приводим его к последней версии reorder-python-imports # делаем красивые импорты yesqa # удаляем неиспользуемые noqa комментарии (для линтеров)  # линтеры flake8 flake8-annotations flake8-annotations-coverage flake8-bandit flake8-broken-line flake8-bugbear flake8-builtins flake8-commas flake8-comprehensions flake8-debugger flake8-eradicate flake8-executable flake8-fixme flake8-future-import flake8-pyi flake8-pytest flake8-pytest-style flake8-mutable flake8-string-format flake8-todo flake8-unused-arguments  # тесты pytest 

Выскажу свое мнение по поводу flake-8 и линтеров в целом. Если у вас уже большой проект с кучей legacy кода, то можете линтеры смело удалять. Затраты которые будут потрачены на «приведение к идеалу», начальство не оценит. Линтеры ставим для новых (и небольших) проектов. Повторюсь, это лично мое мнение, никому его не навязываю.

Интеграция в среду

Заходим в корневой каталог среды разработки и выполняем следующие команды

$ pre-commit install pre-commit installed at .git/hooks/pre-commit $ pre-commit --version pre-commit 2.4.0 

Если .pre-commit будет ругаться на sqlite, то вам нужно будет его установить (к примеру $ yum install sqlite) и собрать python заново

Настройка файла .pre-commit-config.yaml

В корневом каталоге среды создаем файл .pre-commit-config.yaml

- repo: https://github.com/pre-commit/pre-commit-hooks   rev: "v2.5.0"   hooks:     - id: check-merge-conflict     - id: debug-statements  - repo: local    hooks:     - id: black       name: black       entry: black       language: system       types: [python]       args: [--line-length=200, --target-version=py37]      - id: autoflake       name: autoflake       entry: autoflake       language: system       types: [python]       args: [--in-place, --remove-all-unused-imports, --remove-duplicate-keys]      # -   id: flake8     #     name: flake8     #     entry: flake8     #     language: system     #     types: [python]     #     args: [     #         "--ignore=E203,W503,FI10,FI11,FI12,FI13,FI14,FI15,FI16,FI17,FI58,PT013",     #         # black     #             # E203 whitespace before ':'     #             # W503 line break before binary operator     #         # flake8-future-import     #             # FI10 __future__ import "division" missing     #             # FI11 __future__ import "absolute_import" missing     #             # FI12 __future__ import "with_statement" missing     #             # FI13 __future__ import "print_function" missing     #             # FI14 __future__ import "unicode_literals" missing     #             # FI15 __future__ import "generator_stop" missing     #             # FI16 __future__ import "nested_scopes" missing     #             # FI17 __future__ import "generators" missing     #             # FI58 __future__ import "annotations" present     #         # flake8-pytest-style     #             # PT013 found incorrect import of pytest, use simple 'import pytest' instead     #         "--max-line-length=110",     #         "--per-file-ignores=tests/*.py:S101"     #         # S101 Use of assert detected     #     ]      - id: pyupgrade       name: pyupgrade       entry: pyupgrade       language: system       types: [python]       args: [--py37-plus]      - id: reorder-python-imports       name: reorder-python-imports       entry: reorder-python-imports       language: system       types: [python]       args: [--py37-plus]      - id: yesqa       name: yesqa       entry: yesqa       language: system       types: [python]      - id: tests       name: Run tests       entry: "bash tests.sh"       language: system       verbose: true  

Тесты

Помимо проверки и форматирования кода мы будем выполнять тесты на этапе создания коммита. Для этого мы будем использовать pytest (https://docs.pytest.org/en/latest/) и настроим его для наших нужд.

В корневом каталоге среды создадим папку tests и поместим туда следующие файлы
test_example_without_db.py, test_example_with_db.py

Для удобства настроим тесты таким образом, чтобы можно было использовать текущую базу данных (к примеру копию базы данных с боевого сервера), а не создавать каждый раз новую.

Простой тест test_example_without_db.py

def inc(x):     return x + 1  def test_answer():     assert inc(3) == 4 

В простых тестах мы можем подключить например webbot (https://pypi.org/project/webbot/) и обходить узлы нашей системы, чтобы автоматизировать ручной труд тестировщика.

Тест с использованием базы данных test_example_with_db.py

import pytest from chat.models import ChatRoom from settings import POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, \                     POSTGRES_HOST, POSTGRES_PORT  @pytest.fixture(scope='session') def django_db_setup():     settings.DATABASES['default'] = {         'ENGINE': 'django.db.backends.postgresql_psycopg2',         'NAME': POSTGRES_DB,         'USER': POSTGRES_USER,         'PASSWORD': POSTGRES_PASSWORD,         'HOST': POSTGRES_HOST,         'PORT': POSTGRES_PORT,     }              @pytest.fixture def db_access_without_rollback_and_truncate(request, django_db_setup, django_db_blocker):     django_db_blocker.unblock()     request.addfinalizer(django_db_blocker.restore)  def chat():     return ChatRoom.objects.all().count()  @pytest.mark.django_db def test_chat():     assert chat() > 0 

Пример довольно искусственный и создан исключительно для данной заметки, но тем не менее он позволяет нам обратиться к текущей базе данных, и провести сложные тесты, которые выходят за рамки ручного тестирования.

Подключаем тесты в .pre-commit

Чтобы подключить тесты нам потребуется shell script в корневом каталоге среды, который мы назовем tests.sh

source ../../python38_env/bin/activate && python -m pytest -v tests 

Его содержание весьма очевидное, но вы можете заметить что активация виртуального окружения явным образом прописана в коде. Это может быть неудобно, если ваша команда ведет разработку на разных рабочих станциях (к примеру кто развернул среду на локальной машине, а кто-то разрабатывает на сервере).

Можно решить эту проблему через переменные в .env
Пример реализации:
github.com/Sobolev5/starlette-vue-backend/blob/master/.env.example (обратите внимание на переменную ENV_ACTIVATE)
github.com/Sobolev5/starlette-vue-backend/blob/master/tests.sh (парсим ENV_ACTIVATE и активируем среду)

Создаем коммит

Теперь осталось создать коммит и посмотреть как это работает

$ git add . $ git commit -m Sobolev:TestPreCommitHook Check for merge conflicts................................................Passed Debug Statements (Python)................................................Passed black....................................................................Failed - hook id: black - files were modified by this hook  reformatted /var/www/file.py All done!    1 file reformatted, 2 files left unchanged.  autoflake................................................................Passed pyupgrade................................................................Passed reorder-python-imports...................................................Failed - hook id: reorder-python-imports - exit code: 1 - files were modified by this hook  Reordering imports in file.py  yesqa....................................................................Passed Run tests................................................................Passed - hook id: tests - duration: 2.85s  tests/test_example_with_db.py::test_chat PASSED                          [ 66%] tests/test_example_without_db.py::test_answer PASSED                     [100%] 

Коммит теперь создается в «два этапа». На первом этапе хуки выполняют форматирование кода, поэтому после их работы нам нужно просто «повторить» команды.
Получается следующая последовательность.

$ git add . $ git commit -m Sobolev:TestPreCommitHook $ git add . $ git commit -m Sobolev:TestPreCommitHook 

На этом все, спасибо за внимание.

Дополнительные ссылки

pre-commit.com/hooks.html Полный список хуков

ссылка на оригинал статьи https://habr.com/ru/post/503270/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *