PROJECTS NOTES HOME

What is pre-commit

1 Intro

1.2 What is pre-commit

Pre-commit is exactly how it sounds. It is some action that happens before a git commit is formed. So for example you can write some python code. Then you might have a pre-commit setup that checks the linting and formatting in your python code. If it finds that it's not compliant, it will "break" the commit and ask you to do that needed changes so that those pre-commits would pass. When you fix them, when they pass, you can then form the commit again and this time the commit passes, you can push it to remote.

You set it up, then everyone that is working on your repo will be forced to run these pre-commit checks. This way you can ensure that the code that is being pushed to your repo is formatted/linted/checked/tested the way you want, no matter who pushes it

The pre-commit runs only on the changed files. So for example isort will only run on files changed in current commit, not ALL of the files in the project.

It actually sometimes catch errors in my scripts that I write. I might not see the syntax error when building a github action in .yml file, but pre-commit does, so it warns me about it(because of check-yaml pre-commit hook) and prevents me from committing it.

2 Set up

Install a needed python package:

pip install pre-commit

Create .pre-commit-config.yaml file in your root directory. Add such content for a test:

exclude: .*migrations\/.*

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.4.0
    hooks:
      - id: check-docstring-first
      - id: check-merge-conflict
      - id: trailing-whitespace

We basically ask to look at a particular repo that contains lots of hooks and tell to use a few of them.

Now run pre-commit install. This command installs the pre-commit hook into your .git/hooks/pre-commit so that it will be automatically run before each commit

!!!! DON'T FORGET TO RUN pre-commit install COMMAND IN TERMINAL AFTER MAKING CHANGES TO THIS FILE !!!

3 Use

3.1 Pre-commit commands

Run pre-commit manually on all files

pre-commit run --all-files

3.2 Pre-commit examples:

3.2.1 pre-made pre-commit hooks

Simply choose from possible pre-commit hooks and add them like so:

- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v3.4.0
  hooks:
    - id: check-docstring-first
    - id: check-merge-conflict
    - id: trailing-whitespace
    - id: end-of-file-fixer
    - id: check-yaml
    - id: check-ast
    - id: check-added-large-files
    - id: check-symlinks
    - id: debug-statements
    - id: detect-private-key

3.2.2 black - The uncompromising Python code formatter

All the files that you want to commit will be checked for any inconsistencies and bad styling (based on PEP 8 standard).

This hook will automatically fix those issues according to the standards

Have some settings in settings.json of vscode related to black also.

- repo: https://github.com/ambv/black
  rev: 24.4.0
  hooks:
    - id: black
      args: ["--line-length", "79"] # "black-formatter.args": ["--line-length", "79"], in vscode

Can use pyproject.toml file to store your black configs.

3.2.3 isort - sort imports alphabetically, and automatically separated into sections and by type

- repo: https://github.com/pycqa/isort
  rev: 5.12.0
  hooks:
    - id: isort
      name: isort (python)
      args: ["--profile", "black"]

Can use pyproject.toml file to store your isort configs.

3.2.4 pyupgrade - A tool to automatically upgrade syntax for newer versions

- repo: https://github.com/asottile/pyupgrade
  rev: v3.15.2
  hooks:
  - id: pyupgrade

3.2.5 flake8 - Tool For Style Guide Enforcement

This plugin doesn't update the file for us you have to manually fix the code to the standard OR add an exception like "# noqa: E501" for example to the piece of code can create .flake8 file inside the repo to override some style requirements

- repo: https://github.com/pycqa/flake8
  rev: 6.0.0
  hooks:
    - id: flake8

3.2.6 pylint - linter and a static code analyzer

Pylint is just like flake8. It will check your code for any formatting issues as well as any performance issues.

The difference is that pylint is a little more thorough and more customizable.

Those two complement each other (so if you need to ignore something, you will have to ignore it twice.

For flake ignore a line with (# noqa: E501), for pylint ignore a single line with (pylint: disable=C0301).

You don't have to set both of them up, but I sleep better when I know that two unrelated programs checked my code :)

created .pylintrc, added some of my own configs to it

if you don't like the score or something, can run pylint manually with "make lint" depends on a local pylint installation, not a github repo, like the rest

Have an extension pylint installed in VsCode to do the linting automatically, but this is for "just in case", to see those 10.00/10 ;)

My current .pylintrc (some rules that we want to ignore, we put them in .pylintrc file):

# my simple .pylintrc
# decided not to add all defaults from https://github.com/pylint-dev/pylint/blob/main/pylintrc, cuz basic rules like docstrings and length's are not warned about then

[MASTER]

# let's not pylint the migrations folder
ignore=migrations

[MESSAGES CONTROL]

# this makes sure that lines like such "quotes = Quote.objects.all()"
# don't throw errors like such "apps\quotes\views.py:82:20: E1101: Class 'Quote' has no 'objects' member (no-member)"
disable=no-member

[DESIGN]

# "class AuthorCreateView(CreateView):" such class declarations would throw R0901: Too many ancestors (10/7) (too-many-ancestors) warning from pylint.
# indicates that your class, AuthorCreateView, is inheriting (directly or indirectly) from more than the default allowed number of ancestor classes.
# By default, pylint sets this limit to 7, which is often exceeded in Django projects, especially when using class-based views (CBVs) that inherit from
# Django's generic views and mixins, which themselves have multiple layers of inheritance.

# This warning is a part of the pylint design checker, which aims to identify potential design issues in your code. However, in the context of Django,
# especially with CBVs, having more than 7 ancestors is not uncommon and is typically not a sign of bad design. Django's CBVs are designed to be extended
# and composed through inheritance.
max-parents=10

[FORMAT]

# Maximum number of characters on a single line.
max-line-length=79
- repo: local
  hooks:
    - id: pylint
      name: pylint
      entry: pylint
      language: system
      types: [python]
      args:
	[
	  "-rn", # Only display messages (warnings)
	  "-sn", # Don't display the score
	  "--rcfile=.pylintrc", # Link to config file. Tested. It is taken into consideration
	]

3.2.7 djlint-django - Looks for errors and inconsistencies in your HTML files.

Seems unmaintained. Will try to use it anyway for now.

- repo: https://github.com/Riverside-Healthcare/djLint
  rev: v1.34.1
  hooks:
    - id: djlint-django

Can use pyproject.toml file to store your djlint configs.

3.2.8 bandit - a tool designed to find common security issues in Python code.

Reminds that you should hide secret_keys and similar

- repo: https://github.com/PyCQA/bandit
  rev: 1.7.8
  hooks:
    - id: bandit

3.2.9 Run local Django tests

On windows:

For some reason, when running pre-commit over vscode's github window, it does not look into the virtual env where dotenv and psycopg2 is installed.

To fix it, I installed dotenv and psycopg2 and mysqlclient locally… not the best idea, but good enough for now

On devcontainers:

Above comments got fixed when using devcontainers. I can remove dotenv, psycopg2 and mysqlclient from global installation and it all works

- repo: local
  hooks:
    - id: local unit tests
      name: local unit tests
      entry: python manage.py test
      language: system
      pass_filenames: false # this hook will not receive the filenames of the files being committed as arguments. It will simply run the command python manage.py test without any file context. Just as we want.

3.2.10 Check for TODO keywords in your project with a custom pre-commit hook

TODO keyword indicates that there is something you did not do. Perhaps you want to be reminded about that before you commit/push. That is what this hook here is for.

Inspiration for such pre-commit hook

NOTE: did not manage to implement it. I'd say too low of a value to put more effort in

4 Extra

4.1 Why not to use pre-commits

You want to commit quick.. without disturbances.. If on each commit you are forced to run tests or some build command that takes ~1min or so, you will get mad. Make commits quick, inform the users about broken tests, etc (actions that take longer than a few seconds) with github actions. Also, a good on this topic here - why not to use pre-commits (valid points).

4.2 Tips for VSCode on Windows

By default in vscode you do not see the .git folder. It is useful to see it so you know if your pre-commit was installed or not.

To make the .git folder visible in vscode, go file -> preferences -> settings -> files.exclude and uncheck .git folder (then hide it again, so it does not show up in the searches).

Can make the commit from the terminal, OR can use source-control tab in VsCode and a "commit" button. Both will run the pre-commit. Can see the output in the terminal.