Good day!My name is Andrey Sobolev and today I will tell you how we prepared .pre-commit hook on our project.Introduction
To begin with, a few words about what hooks are in general and why they may be needed. Git out of the box provides a tool that can run your scripts when an event occurs (for example, pushing to a server, etc.).Pre-commit is a convenient add-on to the default git pre-commit hook that runs the scripts described in .pre-commit-config.yaml before committing. In theory, it sounds simple, let's move on to practice.Installation
Set the necessary dependencies:pre-commit
autoflake
black
pyupgrade
reorder-python-imports
yesqa
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
I will express my opinion about flake-8 and linter in general. If you already have a large project with a bunch of legacy code, then you can safely delete linters. The costs that will be spent on "bringing to the ideal", the authorities will not appreciate. We put linters for new (and small) projects. I repeat, this is my personal opinion, I do not impose it on anyone.Environment Integration
We go to the root directory of the development environment and execute the following commands$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
$ pre-commit --version
pre-commit 2.4.0
If .pre-commit will swear on sqlite, then you will need to install it (for example $ yum install sqlite) and build python again
Setting up the .pre-commit-config.yaml file
In the root directory of the environment, create the file .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: 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
Tests
In addition to checking and formatting the code, we will perform tests at the stage of creating the commit. To do this, we will use pytest (https://docs.pytest.org/en/latest/) and configure it for our needs.In the root directory of the environment, create the tests folder and put the following filestest_example_without_db.py, test_example_with_db.py therefor convenience, configure the tests so that you can use the current database (for example, a copy of the database from the battle server), and not create a new one every time .Simple test test_example_without_db.pydef inc(x):
return x + 1
def test_answer():
assert inc(3) == 4
In simple tests, we can connect for example webbot and bypass the nodes of our system to automate the manual work of the tester.Test using test_example_with_db.py databaseimport 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
The example is quite artificial and created exclusively for this note, but nevertheless it allows us to access the current database and conduct complex tests that go beyond manual testing.We include tests in .pre-commit
To connect the tests, we need a shell script in the root directory of the environment, which we will call tests.sh:source ../../python38_env/bin/activate && python -m pytest -v tests
Its content is very obvious, but you may notice that the activation of the virtual environment is explicitly written in the code. This can be inconvenient if your team is developing on different workstations (for example, who deployed the environment on a local machine, and someone develops on a server).You can solve this problem through variables in .env Implementationexample:github.com/Sobolev5/starlette-vue-backend/blob/master/.env.example (note the variable ENV_ACTIVATE)github.com/Sobolev5/starlette-vue-backend /blob/master/tests.sh (parse ENV_ACTIVATE and activate the environment)Create a commit
Now it remains to create a commit and see how it works$ 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%]
A commit is now created in two steps. At the first stage, hooks format the code, so after they work, we just need to “repeat” the commands.It turns out the following sequence.$ git add .
$ git commit -m Sobolev:TestPreCommitHook
$ git add .
$ git commit -m Sobolev:TestPreCommitHook
That's all, thanks for your attention.Sitelinks
→ Full list of hooks