Code Quality on Autopilot: Demystifying pre-commit Hooks

Agatha BahatiAgatha Bahati
6 min read

Maintaining precise and consistent code formatting standards, commit message conventions and spell checking have all become part of my ritual when I work on projects; they are now practically woven into the fabric of my work.

Picture this: you're going through multiple files, one by one, just to spot any missing newline, throw it in there then finally hit save. It's a repetitive and time consuming process – really, who's got time for that? What if I told you that there's a quicker way?

Now, imagine if this process could be automated. Let me introduce you to pre-commit hooks, one of my fave Git features and the unassuming defenders of clean code.

What are pre-commit hooks?

Pre-commit hooks are scripts executed before committing changes to a repository i.e when you run git commit. They can be used to automate tasks, such as testing and enforcing code quality standards by way of formatting, linting, spell checking etc.

The concept is simple, elegant and intuitive yet yields a significant impact: before you commit any changes, automatically perform formatting and code checks to ensure a high-quality, consistent codebase.

They guarantee code validation, proper formatting and successful testing triggered before a Git commit event. Consider this one less manual task on your plate, while maintaining high code quality and consistency.

Setup and installation

To set up a pre-commit hook in your python project, follow the steps below:

Step 1: Install `pre-commit`

Let's start by installing the pre-commit package using pip:

pip install pre-commit

To verify the installation and find out the pre-commit package version, run:

pre-commit --version

Step 2: Create .pre-commit-config.yaml file

Next, create a .pre-commit-config.yaml in the root of your project.

Step 3: Define hooks

Now let's get our hands a little dirty, we will define a simple hook that checks for and corrects end-of-file issues in your project.

# pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.3.0
    hooks:
      - id: end-of-file-fixer
  • repos: this key presents the section to define the repositories and hooks to be utilized.

  • repo: represents the repository url where the hooks will be fetched (git cloned) by pre-commit.

  • rev: or revision, specifies the exact version or tag of the repository to use. In this case, it's set to version 4.0.1 of the pre-commit-hooks repository.

  • hooks: this key points to the section that lists the particular hooks from the specified repository that need to be used.

  • id: end-of-file-fixer here, we are specifying the hook to use from the repository. In this example, it's the end-of-file-fixer hook. This hook automatically adds a newline character to the end of each file in your project.

Step 4: Initializing `pre-commit`

To create the scripts that add the hooks to your project, run:

pre-commit install

And just like that, the pre-commit hook will run automatically on git commit , only on files staged for commit.

To run pre-commit on all files, inlcluding those not staged in the current commit, run the following command:

pre-commit run --all-files

When and how to use pre-commit hooks?

Let's look at practical ways you can use pre-commit hooks in your python project.

i. Formatting

Issue: Inconsistent code structure, indentation, line breaks and spacing can hinder collaboration and make the codebase difficult to read and understand.

Solution: A consistent code style can be automatically enforced throughout the entire codebase by using third party tools like black.

Make sure to install black in your project.

# pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black

It is worth noting that your changes will be committed successfully if and only if your committed code passes all checks. Otherwise you (or the hooks) have to change the code accordingly then commit again.

ii. Linting

Issue: Linting identifies problems in code like syntax mistakes, undefined variables, unused imports etc that could affect code readability.

Solution: To ensure that code style consistency is maintained in your project, we can integrate linters like flake8.

# pre-commit-config.yaml
repos:
  - repo: https://github.com/PyCQA/flake8
    rev: 7.0.0
    hooks:
      - id: flake8

iii. Spell checking

Issue: Typos, variable name inconsistencies or even overlooked documentation errors might bring about code readability issues.

Solution: Automating the process of spell checking - scanning code and identify spelling errors, providing suggestions for corrections - ensures that code remains free of spelling issues before being committed. Using tools like codespell come in handy.

Make sure to have codespell installed in your project.

# pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/mirrors-codespell
    rev: v2.2.6
    hooks:
      - id: codespell

iv. Consistent imports

Issue: Ensuring that imports follow a certain style guide and ordering might be time consuming especially if done manually.

Solution: Using a tool like isort, the process of organizing imports according to predefined style guides, such as PEP 8, is automated.

# pre-commit-config.yaml
repos:
  - repo: github.com/PyCQA/isort 
    rev: 5.13.2
    hooks:
      - id: isort

v. Dockerfile Linting

Issue: Any mistakes in Dockerfiles relating to conventions and standards could lead to errors in the resultant docker image impacting deployment and even security.

Solution: Automating the linting process for Dockerfiles by analyzing configuration files to catch issues, enforce coding standards and ensure consistency by using the dockerfilelint hook.

# pre-commit-config.yaml
repos:
  - repo: https://github.com/pryorda/dockerfilelint-precommit-hooks
    rev: 0.1.0
    hooks:
      - id: dockerfilelint

Customizing pre-commit hooks

You can enhance the usefulness of your hooks by adding extra configuration. These are a few examples of such ways:

Excluding files from formatting

To prevent black from formatting certain files:

#pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: stable
    hooks:
      - id: black
        args: ['--exclude', 'path/to/excluded/files/']

Specifying words to ignore

To make codespell ignore spelling errors in certain words and files/directories:

# pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/mirrors-codespell
    rev: 2.2.6
    hooks:
      - id: codespell
        args: ['--ignore-words', 'myword', '--skip', 'tests/']

In this example, the --ignore-words argument is used to specify custom words to ignore, and the --skip argument excludes the tests directory from spell checking.

Removing unused imports

To customize isort for additional functionality, such as removing unused imports:

# pre-commit-config.yaml
repos:
  - repo: https://github.com/PyCQA/isort
    rev: 5.13.2
    hooks:
      - id: isort
        args: ['--remove-unused-imports']

Conclusion

Pre-commit Git hooks can be used to automate tasks and ensure that code meets a certain level of quality and consistency. By using pre-commit hooks, you can catch potential issues before they are committed to the repository, saving time and improving productivity.

Resources

To learn more about Git hooks and see the many ways you can apply them in your project, check out the links below:

https://githooks.com/

https://pre-commit.com/#pre-commit-configyaml---hooks

https://pre-commit.com/hooks.html

Ready to turn those pre-commit theories into tangible coding wins? In the upcoming practical blog, we're going hands-on with pre-commit hooks, unlocking the secrets to cleaner, more efficient code. It's where theory meets action, and your coding journey takes a leap forward. Stay tuned for the practical guide – your code is about to get a serious upgrade! 🚀

Got thoughts on pre-commit hooks or have a cool hack? Share the wisdom, let's geek out together!🚀 #PreCommitMagic✨

36
Subscribe to my newsletter

Read articles from Agatha Bahati directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Agatha Bahati
Agatha Bahati

Agatha is a backend engineer fluent in Python (FastAPI, Django, Flask) and JavaScript (Node.js), with Docker always at hand. She writes to learn and to teach others, breaking down complex tech into simple, actionable insights—always aiming for clarity as sharp as her APIs. Her coding philosophy? Be like an empty cup: open to new ideas, ready to learn, and always willing to approach problems from a fresh angle. After all, the usefulness of a cup is in its emptiness. When she's not crafting backend magic, you’ll find her hiking, painting, or farming—because even coders need to debug in nature sometimes.