written by Eric J. Ma on 2024-04-09 | tags: pre-commit webp optimization python
In this blog post, I share my journey of creating my first distributable pre-commit hook, convert-to-webp, using the pre-commit framework. This hook automatically converts images to the .webp format before they're committed to a repository, ensuring optimized image storage. I detail the essential configuration files, the creation of a Typer CLI for the hook, and how to make the hook available for others by tagging versions and adding it to a project's .pre-commit-config.yaml file. Curious about how to streamline your codebase with automated checks? How might this improve your project's efficiency?
Following my previous blog post on how to make pre-commit hooks, I finally made my first distributable pre-commit hook that is installable by the pre-commit framework! The pre-commit hook is called convert-to-webp, and is housed in the repo ericmjl/webp-pre-commit.
Pre-commit hooks are fantastic! They enable us to run automatic checks on files to be committed into a repository before they are committed. This allows us to automatically ensure that, for example, large files are not committed into the repository or, in the case of convert-to-webp, images are converted to the highly optimized .webp format before being committed.
Having just learned how to make a distributable pre-commit hook, I am documenting how it works here for future reference.
The most important configuration file we need within the repository is called .pre-commit-hooks.yaml. This defines what hooks are available from within the repository. In my case, I have only one defined, convert-to-webp, and it is configured as follows:
- id: convert-to-webp name: Convert images to WebP description: This hook converts image files to WebP format. entry: webp-hook language: python files: \.(png|jpg|jpeg|gif|bmp|tiff)$ additional_dependencies: - Pillow - typer
Let's explain what each line is:
id: This is the name identifier of the pre-commit hook to run.name: This is the display name of the hook when it is run at the terminal.description: Something human-readable.entry: This is the command that gets run. All relevant files are added as arguments automatically, so in this case, it'll be $ webp-hook file1.png file2.tiff file3.bmp etc.language: This is the programming language that it's written in.files: A regex pattern for the kind of file extensions that will be subject to checks by this hook.additional_dependencies: An array of dependencies necessary to run the hook. In this case, I have a list of package dependencies.A few questions may be lingering here: what is this webp-hook command that I wrote?
It turns out to be a Typer CLI! This is implemented within the webp_hook directory:
drwxr-xr-x - ericmjl 7 Apr 23:56 -- ├── webp_hook .rw-r--r-- 922 ericmjl 8 Apr 08:38 -- │ └── cli.py
To ensure that the CLI gets run as a command entry point at the command line, we configure this in pyproject.toml:
[project.scripts] webp-hook = "webp_hook.cli:app"
Because there's only one command in cli.py (defined under the convert function) I can get away with using webp-hook as the command to run rather than webp-hook convert as the command.
Another crucial thing is to have tagged versions of the repository. To ensure this is done efficiently, I use bumpversion (but should upgrade to bump-my-version soon) when cutting a new tagged release. This tagged version is referenced in my individual project repo's configuration files.
To obtain the pre-commit hook, all we need to do is add the following line into the .pre-commit-config.yaml (not to be confused with the .pre-commit-hooks.yaml) file:
- repo: https://github.com/ericmjl/webp-pre-commit # <-- URL to repository rev: v0.0.8 # <-- this is the tagged version hooks: - id: convert-to-webp # <-- this is the `id` defined above
Now, the pre-commit framework will automatically manage the installation of the pre-commit hook within its own virtual environment, install dependencies, and run the command webp-hook upon committing new files. The output should look like this:
❯ git commit check yaml...........................................(no files to check)Skipped fix end of files.....................................(no files to check)Skipped trim trailing whitespace.............................(no files to check)Skipped Convert images to WebP...............................(no files to check)Skipped # notice the name of the pre-commit hook above!
Let's recap how do we create a pre-commit hook that can be distributed to others. The key steps, in order, are:
.pre-commit-hooks.yaml configuration file that declares that the pre-commit hook is.bump-my-version (or manual git tagging) to cut new tagged releases of your pre-commit hook..pre-commit-config.yaml file.As you develop the hook, continue cutting new releases through git tags. When you cut new releases, go to your downstream project code and ask pre-commit to update the hooks by running pre-commit autoupdate.
Now that you know how to build your own pre-commit hooks, you can be empowered to create custom checks for your code that hold it to your company's own internal standards! I can also imagine many creative uses, including the use of LLM APIs within the pre-commit hooks that get run that parse code for issues that are difficult to detect using programmatic rules, not unlike writing commit messages based on git diffs!
Happy code checking!
@article{
ericmjl-2024-how-to-make-distributable-pre-commit-hooks,
author = {Eric J. Ma},
title = {How to make distributable pre-commit hooks},
year = {2024},
month = {04},
day = {09},
howpublished = {\url{https://ericmjl.github.io}},
journal = {Eric J. Ma's Blog},
url = {https://ericmjl.github.io/blog/2024/4/9/how-to-make-distributable-pre-commit-hooks},
}
I send out a newsletter with tips and tools for data scientists. Come check it out at Substack.
# TODO(eric): learn how to learn anything. feb 2027.
If you would like to sponsor the coffee that goes into making my posts, please consider GitHub Sponsors!