Intégration du hook .pre-commit dans un projet Django

Bonne journée!

Je m'appelle Andrey Sobolev et aujourd'hui je vais vous dire comment nous avons préparé le hook .pre-commit sur notre projet.

introduction


Pour commencer, quelques mots sur ce que sont les crochets en général et pourquoi ils peuvent être nécessaires. Git out of the box fournit un outil qui peut exécuter vos scripts lorsqu'un événement se produit (par exemple, pousser vers un serveur, etc.)

Pre-commit est un complément pratique au hook de pré-validation git par défaut qui exécute les scripts décrits dans .pre-commit-config.yaml avant de valider. En théorie, cela semble simple, passons à la pratique.

Installation


Définissez les dépendances nécessaires:

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

Je vais exprimer mon opinion sur flake-8 et linter en général. Si vous avez déjà un grand projet avec un tas de code hérité, vous pouvez supprimer en toute sécurité les linters. Les coûts qui seront dépensés pour "amener à l'idéal", les autorités ne l'apprécieront pas. Nous mettons des linters pour de nouveaux (et petits) projets. Je le répète, c'est mon opinion personnelle, je ne l'impose à personne.

Intégration de l'environnement


Nous allons dans le répertoire racine de l'environnement de développement et exécutons les commandes suivantes

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

Si .pre-commit jure sur sqlite, alors vous devrez l'installer (par exemple $ yum install sqlite) et reconstruire python

Configuration du fichier .pre-commit-config.yaml


Dans le répertoire racine de l'environnement, créez le fichier .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


Les tests


En plus de vérifier et de formater le code, nous effectuerons des tests au stade de la création du commit. Pour ce faire, nous utiliserons pytest (https://docs.pytest.org/en/latest/) et le configurerons selon nos besoins.

Dans le répertoire racine de l'environnement, créez le dossier de tests et placez les fichiers suivants
test_example_without_db.py, test_example_with_db.py

pour plus de commodité, configurez les tests afin que vous puissiez utiliser la base de données actuelle (par exemple, une copie de la base de données à partir du serveur de combat), et ne pas en créer de nouvelle à chaque fois .

Test simple test_example_without_db.py

def inc(x):
    return x + 1

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

Dans des tests simples, nous pouvons par exemple connecter le webbot et contourner les nœuds de notre système pour automatiser le travail manuel du testeur.

Test à l'aide de la base de données 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

L'exemple est assez artificiel et créé exclusivement pour cette note, mais il nous permet néanmoins d'accéder à la base de données actuelle et d'effectuer des tests complexes qui vont au-delà des tests manuels.

Nous incluons les tests dans .pre-commit


Pour connecter les tests, nous avons besoin d'un script shell dans le répertoire racine de l'environnement, que nous appellerons tests.sh:

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

Son contenu est très évident, mais vous remarquerez peut-être que l'activation de l'environnement virtuel est explicitement écrite dans le code. Cela peut être gênant si votre équipe se développe sur différents postes de travail (par exemple, qui a déployé l'environnement sur une machine locale et que quelqu'un se développe sur un serveur).

Vous pouvez résoudre ce problème via des variables dans l'

exemple d' implémentation

.env : github.com/Sobolev5/starlette-vue-backend/blob/master/.env.example (notez la variable

ENV_ACTIVATE ) github.com/Sobolev5/starlette-vue-backend /blob/master/tests.sh (analyser ENV_ACTIVATE et activer l'environnement)

Créer un commit


Il reste maintenant à créer un commit et à voir comment cela fonctionne

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

Un commit est maintenant créé en deux étapes. À la première étape, les hooks formatent le code, donc après qu'ils fonctionnent, nous avons juste besoin de «répéter» les commandes.

Il s'avère que la séquence suivante.

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

C'est tout, merci de votre attention.

Liens annexes


Liste complète des crochets

All Articles