Keep your code clean using Black & Pylint & Git Hooks & Pre-commit

Ismail Mebsout
October 23, 2024
5 min

Coding can be a very hard task especially when working on a project with different developers. Each member of the team has his/her own way of coding leading to very heterogeneous scripts.
This is why it is important to have a similar code formatter and code linter in order to make your git commits cleaner. This can be carried out either between the staging and committing phase or during the CI/CD chain.

In this article, we will see how to do so as a pre-commit step using git hooks.

The summary is as follows:

  1. Black
  2. Pylint
  3. Git Hooks

Black

Black is a python code formatter which style configurations are deliberately limited. It makes your code cleaner so you can focus more on the content. The code review is also more efficient since the diffs are as minor as possible.

It can be installed using the following command line:

pip install black

You can run black on any python file by typing:

Black pathtofile.py

Black formatter

Black can be slightly configured using the file pyproject.toml which should be placed in the root of your project. Below an example of this file:

[tool.black]
line-length = 88
target-version = [‘py36’, ‘py37’, ‘py38’]
include = ‘\.pyi?$’
exclude = '''
/(
  \.toml
  |\.sh
  |\.git
  |\.ini
  |Dockerfile
  |Jenkinfile
)/
'''

We can choose the length of the lines of codes for example and also set the extensions which files should not be formatted.

Pylint

Pylint is a “Python static code analysis tool” which evaluates the quality of the developed scripts. It guides the team into adopting the same standards.

Pylint can be installed using the following command line:

pip install pylint

To evaluate your coding quality on a given script you can run:

pylint pathtofile.py

NB: In order to run pylint on your entire project, your repository should include an __init__.py file.

Like Black, Pylint can also be configured using the file .pylintrc which is also placed on the root of your project

[MASTER]
jobs=4 #number of processes to use
[BASIC]
good-names=nameOfYourProject #names to be considered ok
[pre-commit-hook]
command=custom_pylint
disable=E0401, C0301

You can check this page for more details on the configuration.

Git Hooks

Git hooks are defined scripts that are launched when a git action is run.
There are two types of hooks:

  • Client-side hooks: are run after committing and merging
  • Server-side hooks: are run on network operations, after pushing commits for example

In this article we will focus on client-side ones which workflow can be described by the following graph:

Git commit's pipeline

In a git repository, hooks are placed in .git/hooks/ . They can be visualized in VSCode for example by adding the following lines to your Json settings:

"files.exclude": {
     "**/.git": false
}

Git hooks

Setup of pre-commits

To add a pre-commit action to your repository:

1.Create the file pre-commit in the folder .git/hooks/. You should not include any extension in its name.

2.Add bash commands in your file. In our case:

#!/bin/sh
black .
python lint.py -p ../projectName/

The first line applies black formatter and the second line applies the linting on every python file of your project. You can download the file lint.py from this link:

import argparse
import logging
from pylint.lint import Run


logging.getLogger().setLevel(logging.INFO)

parser = argparse.ArgumentParser(prog="LINT")

parser.add_argument('-p',
                    '--path',
                    help='path to directory you want to run pylint | '
                         'Default: %(default)s | '
                         'Type: %(type)s ',
                    default='./src',
                    type=str)

parser.add_argument('-t',
                    '--threshold',
                    help='score threshold to fail pylint runner | '
                         'Default: %(default)s | '
                         'Type: %(type)s ',
                    default=7,
                    type=float)

args = parser.parse_args()
path = str(args.path)
threshold = float(args.threshold)

logging.info('PyLint Starting | '
             'Path: {} | '
             'Threshold: {} '.format(path, threshold))

results = Run([path], do_exit=False)

final_score = results.linter.stats['global_note']

if final_score < threshold:

    message = ('PyLint Failed | '
               'Score: {} | '
               'Threshold: {} '.format(final_score, threshold))

    logging.error(message)
    raise Exception(message)

else:
    message = ('PyLint Passed | '
               'Score: {} | '
               'Threshold: {} '.format(final_score, threshold))

    logging.info(message)

    exit(0)

NB: A threshold of 7 is set by default. If the pylint score is below this number, the commit will fail and you will have to clean your script before commit again.

3. Make it executable by running the following bash command:

chmod +x .git/hooks/pre-commit

With that being done, each commit of your project will be preceded by the formatting of black and linting of pylint.
We consider the following app for illustration:

Code example

When committing a new change, we get the following results:

Git pre-commits

Sharing the linter & formatter

Now that you have set up black as your formatter and pylint as your linter, you definitely want your team to have the same configuration.
To do so, you can use the following bash file which will be placed at the root of your project and run by each member of your team after cloning the repository:

#!/bin/bash
echo $"#/bin/sh\nblack .\npython lint.py -p ../projectName/'> .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Conclusion

Pre-commits are very powerful tools to keep your code well-formatted and to standardize the developing methods across the member of your team. Similarly, they can include other checks besides linting and formatting.
In this article, we have used a classical method that calls installed Python libraries, you can also use the python module pre-commit which includes many checks and calls on their GitHub repositories. It is very efficient but can be difficult to set up especially when facing proxy filters.

Get In Touch

Have any questions? We'd love to hear from you.

Thank you! We will get back in touch with you within 48 hours.
Oops! Something went wrong while submitting the form.

Keep your code clean using Black & Pylint & Git Hooks & Pre-commit

Indispensable tools for collaborative development
Ismail Mebsout
Author
October 23, 2024
-
5 min

Coding can be a very hard task especially when working on a project with different developers. Each member of the team has his/her own way of coding leading to very heterogeneous scripts.
This is why it is important to have a similar code formatter and code linter in order to make your git commits cleaner. This can be carried out either between the staging and committing phase or during the CI/CD chain.

In this article, we will see how to do so as a pre-commit step using git hooks.

The summary is as follows:

  1. Black
  2. Pylint
  3. Git Hooks

Black

Black is a python code formatter which style configurations are deliberately limited. It makes your code cleaner so you can focus more on the content. The code review is also more efficient since the diffs are as minor as possible.

It can be installed using the following command line:

pip install black

You can run black on any python file by typing:

Black pathtofile.py

Black formatter

Black can be slightly configured using the file pyproject.toml which should be placed in the root of your project. Below an example of this file:

[tool.black]
line-length = 88
target-version = [‘py36’, ‘py37’, ‘py38’]
include = ‘\.pyi?$’
exclude = '''
/(
  \.toml
  |\.sh
  |\.git
  |\.ini
  |Dockerfile
  |Jenkinfile
)/
'''

We can choose the length of the lines of codes for example and also set the extensions which files should not be formatted.

Pylint

Pylint is a “Python static code analysis tool” which evaluates the quality of the developed scripts. It guides the team into adopting the same standards.

Pylint can be installed using the following command line:

pip install pylint

To evaluate your coding quality on a given script you can run:

pylint pathtofile.py

NB: In order to run pylint on your entire project, your repository should include an __init__.py file.

Like Black, Pylint can also be configured using the file .pylintrc which is also placed on the root of your project

[MASTER]
jobs=4 #number of processes to use
[BASIC]
good-names=nameOfYourProject #names to be considered ok
[pre-commit-hook]
command=custom_pylint
disable=E0401, C0301

You can check this page for more details on the configuration.

Git Hooks

Git hooks are defined scripts that are launched when a git action is run.
There are two types of hooks:

  • Client-side hooks: are run after committing and merging
  • Server-side hooks: are run on network operations, after pushing commits for example

In this article we will focus on client-side ones which workflow can be described by the following graph:

Git commit's pipeline

In a git repository, hooks are placed in .git/hooks/ . They can be visualized in VSCode for example by adding the following lines to your Json settings:

"files.exclude": {
     "**/.git": false
}

Git hooks

Setup of pre-commits

To add a pre-commit action to your repository:

1.Create the file pre-commit in the folder .git/hooks/. You should not include any extension in its name.

2.Add bash commands in your file. In our case:

#!/bin/sh
black .
python lint.py -p ../projectName/

The first line applies black formatter and the second line applies the linting on every python file of your project. You can download the file lint.py from this link:

import argparse
import logging
from pylint.lint import Run


logging.getLogger().setLevel(logging.INFO)

parser = argparse.ArgumentParser(prog="LINT")

parser.add_argument('-p',
                    '--path',
                    help='path to directory you want to run pylint | '
                         'Default: %(default)s | '
                         'Type: %(type)s ',
                    default='./src',
                    type=str)

parser.add_argument('-t',
                    '--threshold',
                    help='score threshold to fail pylint runner | '
                         'Default: %(default)s | '
                         'Type: %(type)s ',
                    default=7,
                    type=float)

args = parser.parse_args()
path = str(args.path)
threshold = float(args.threshold)

logging.info('PyLint Starting | '
             'Path: {} | '
             'Threshold: {} '.format(path, threshold))

results = Run([path], do_exit=False)

final_score = results.linter.stats['global_note']

if final_score < threshold:

    message = ('PyLint Failed | '
               'Score: {} | '
               'Threshold: {} '.format(final_score, threshold))

    logging.error(message)
    raise Exception(message)

else:
    message = ('PyLint Passed | '
               'Score: {} | '
               'Threshold: {} '.format(final_score, threshold))

    logging.info(message)

    exit(0)

NB: A threshold of 7 is set by default. If the pylint score is below this number, the commit will fail and you will have to clean your script before commit again.

3. Make it executable by running the following bash command:

chmod +x .git/hooks/pre-commit

With that being done, each commit of your project will be preceded by the formatting of black and linting of pylint.
We consider the following app for illustration:

Code example

When committing a new change, we get the following results:

Git pre-commits

Sharing the linter & formatter

Now that you have set up black as your formatter and pylint as your linter, you definitely want your team to have the same configuration.
To do so, you can use the following bash file which will be placed at the root of your project and run by each member of your team after cloning the repository:

#!/bin/bash
echo $"#/bin/sh\nblack .\npython lint.py -p ../projectName/'> .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Conclusion

Pre-commits are very powerful tools to keep your code well-formatted and to standardize the developing methods across the member of your team. Similarly, they can include other checks besides linting and formatting.
In this article, we have used a classical method that calls installed Python libraries, you can also use the python module pre-commit which includes many checks and calls on their GitHub repositories. It is very efficient but can be difficult to set up especially when facing proxy filters.