Integrando .pre-commit hook em um projeto Django

Dia bom!

Meu nome é Andrey Sobolev e hoje vou lhe contar como preparamos o gancho .prometer em nosso projeto.

Introdução


Para começar, algumas palavras sobre o que são os ganchos em geral e por que eles podem ser necessários. O Git pronto para uso fornece uma ferramenta que pode executar seus scripts quando um evento ocorre (por exemplo, enviar para um servidor etc.) A

pré-confirmação é um complemento conveniente para o gancho de pré-confirmação padrão do git que executa os scripts descritos em .pre-commit-config.yaml antes de confirmar. Em teoria, parece simples, vamos à prática.

Instalação


Defina as dependências necessárias:

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

Vou expressar minha opinião sobre o flake-8 e o linter em geral. Se você já possui um projeto grande com um monte de código legado, pode excluir com segurança linters. Os custos que serão gastos em "levar ao ideal", as autoridades não apreciarão. Colocamos linters para novos (e pequenos) projetos. Repito, esta é a minha opinião pessoal, não a imponho a ninguém.

Integração com o ambiente


Vamos para o diretório raiz do ambiente de desenvolvimento e executamos os seguintes comandos

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

Se .pre-commit jurar no sqlite, será necessário instalá-lo (por exemplo, $ yum install sqlite) e compilar o python novamente

Configurando o arquivo .pre-commit-config.yaml


No diretório raiz do ambiente, crie o arquivo .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


Testes


Além de verificar e formatar o código, executaremos testes no estágio de criação do commit. Para fazer isso, usaremos pytest (https://docs.pytest.org/en/latest/) e configurá-lo para nossas necessidades.

No diretório raiz do ambiente, crie a pasta tests e coloque os seguintes arquivos
test_example_without_db.py, test_example_with_db.py lá

por conveniência, configure os testes para poder usar o banco de dados atual (por exemplo, uma cópia do banco de dados do servidor de batalha) e não criar um novo sempre .

Teste simples test_example_without_db.py

def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 4

Em testes simples, podemos conectar, por exemplo, webbot e ignorar os nós do nosso sistema para automatizar o trabalho manual do testador.

Teste usando o banco de dados 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

O exemplo é bastante artificial e criado exclusivamente para esta nota, mas, no entanto, permite acessar o banco de dados atual e realizar testes complexos que vão além do teste manual.

Incluímos testes em .pre-commit


Para conectar os testes, precisamos de um script de shell no diretório raiz do ambiente, que chamaremos de tests.sh:

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

Seu conteúdo é muito óbvio, mas você pode perceber que a ativação do ambiente virtual está explicitamente escrita no código. Isso pode ser inconveniente se sua equipe estiver desenvolvendo em diferentes estações de trabalho (por exemplo, quem implantou o ambiente em uma máquina local e alguém desenvolveu em um servidor).

Você pode resolver esse problema por meio de variáveis ​​no

exemplo de implementação

.env : github.com/Sobolev5/starlette-vue-backend/blob/master/.env.example (observe a variável ENV_ACTIVATE)

github.com/Sobolev5/starlette-vue-backend /blob/master/tests.sh (analise ENV_ACTIVATE e ative o ambiente)

Crie uma confirmação


Agora resta criar um commit e ver como ele funciona

$ 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%]

Uma confirmação agora é criada em duas etapas. No primeiro estágio, os ganchos formatam o código; portanto, depois que eles funcionam, precisamos apenas "repetir" os comandos.

Acontece a seguinte sequência.

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

Só isso, obrigado pela atenção.

Sitelinks


Lista completa de ganchos

All Articles