Code Quality on Autopilot: Demystifying pre-commit Hooks
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 thepre-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 theend-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://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✨
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.