Доброго дня!
Меня зовут Соболев Андрей и сегодня я вам расскажу как мы приготовили .pre-commit hook на нашем проекте.
Для начала пару слов, о том что такое в целом хуки (hooks) и для чего они могут быть нужны. Git «из коробки» предоставляет инструмент, который умеет запускать ваши скрипты при наступлении какого-либо события (к примеру пуш на сервер и т.п.)
.pre-commit это удобная надстройка над дефолтным git pre-commit hook, которая запускает скрипты описанные в .pre-commit-config.yaml перед созданием коммита. В теории звучит просто, перейдем к практике.
Установим необходимые зависимости:
Выскажу свое мнение по поводу flake-8 и линтеров в целом. Если у вас уже большой проект с кучей legacy кода, то можете линтеры смело удалять. Затраты которые будут потрачены на «приведение к идеалу», начальство не оценит. Линтеры ставим для новых (и небольших) проектов. Повторюсь, это лично мое мнение, никому его не навязываю.
Заходим в корневой каталог среды разработки и выполняем следующие команды
В корневом каталоге среды создаем файл .pre-commit-config.yaml
Помимо проверки и форматирования кода мы будем выполнять тесты на этапе создания коммита. Для этого мы будем использовать pytest (https://docs.pytest.org/en/latest/) и настроим его для наших нужд.
В корневом каталоге нашей среды создадим файл pytest.ini
Далее создадим папку tests и поместим туда следующие файлы
test_example_without_db.py, test_example_with_db.py
Для удобства настроим тесты таким образом, чтобы можно было использовать текущую базу данных (к примеру копию базы данных с боевого сервера), а не создавать каждый раз новую.
Простой тест test_example_without_db.py
В простых тестах мы можем подключить например webbot и обходить узлы нашей системы, чтобы автоматизировать ручной труд тестировщика.
Тест с использованием базы данных test_example_with_db.py
Пример довольно искусственный и создан исключительно для данной заметки, но тем не менее он позволяет нам обратиться к текущей базе данных, и провести сложные тесты, которые выходят за рамки ручного тестирования.
Чтобы подключить тесты нам потребуется shell script в корневом каталоге среды, который мы назовем tests.sh:
Его содержание весьма очевидное, но вы можете заметить что активация виртуального окружения явным образом прописана в коде. Это может быть неудобно, если ваша команда ведет разработку на разных рабочих станциях (к примеру кто развернул среду на локальной машине, а кто-то разрабатывает на сервере).
Можно решить эту проблему через переменные в .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 и активируем среду)
Теперь осталось создать коммит и посмотреть как это работает
Коммит теперь создается в «два этапа». На первом этапе хуки выполняют форматирование кода, поэтому после их работы нам нужно просто «повторить» команды.
Получается следующая последовательность.
На этом все, спасибо за внимание.
→ Полный список хуков
Меня зовут Соболев Андрей и сегодня я вам расскажу как мы приготовили .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 pytest-django
Выскажу свое мнение по поводу 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/) и настроим его для наших нужд.
В корневом каталоге нашей среды создадим файл pytest.ini
[pytest] DJANGO_SETTINGS_MODULE = settings python_files = tests.py test_*.py *_tests.py
Далее создадим папку 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 и обходить узлы нашей системы, чтобы автоматизировать ручной труд тестировщика.
Тест с использованием базы данных test_example_with_db.py
import pytest import settings 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
На этом все, спасибо за внимание.
Дополнительные ссылки
→ Полный список хуков
