🚀 Executive Summary

TL;DR: Inconsistent Python code leads to time-consuming code reviews and increased cognitive load. This guide details how to automate formatting and linting on Git commit using `black`, `flake8`, and `pre-commit` hooks, ensuring consistent code quality before code is pushed and saving significant development time.

🎯 Key Takeaways

  • Implement `black` for uncompromising code formatting, `flake8` for robust linting (including PEP 8 and logical errors), and `pre-commit` as the framework to orchestrate these tools automatically on Git commit.
  • Centralize configuration for `black` and `flake8` within a single `pyproject.toml` file, ensuring critical settings like `line-length` are identical to prevent conflicts between the formatter and linter.
  • Define and pin specific versions for `pre-commit-hooks`, `black`, and `flake8` in `.pre-commit-config.yaml` to guarantee consistent behavior and avoid unexpected changes across different developer environments.

Format and Lint Python Code automatically on Git Commit

Format and Lint Python Code automatically on Git Commit

Hey everyone, Darian Vance here. Let’s talk about something that used to be a major time-sink for me: code reviews. I once calculated that I was spending nearly two hours a week just leaving comments like “please use single quotes” or “this line is too long.” It wasn’t about being picky; it was about the cognitive load of dealing with inconsistent code. It slows everyone down.

That all changed when I automated our team’s Python formatting and linting. By setting up a pre-commit hook, we ensure that every single commit is perfectly formatted and passes basic quality checks before it even gets pushed. It’s a one-time setup that saves countless hours down the line. Today, I’m going to walk you through my exact production setup.

Prerequisites

  • A Python project that is managed with Git.
  • Python and Pip installed on your machine.
  • Basic familiarity with your terminal.

The Guide: Step-by-Step

Step 1: Install the Core Tools

First, we need to get the necessary tools. I’ll skip the standard virtual environment setup since you likely have your own workflow for that. Just make sure you’ve activated your project’s `venv` before you proceed. Once you’re in, you’ll need to use pip to install three key packages:

  • black: The uncompromising code formatter. It takes your messy code and reformats it to a consistent style. No arguments, no debates.
  • flake8: A powerful linter that checks your code for logical errors (like unused imports) and style guide violations (PEP 8).
  • pre-commit: The framework that orchestrates everything. It manages and runs our hooks at the right time.

You can install them from your terminal by running the pip install command for `black`, `flake8`, and `pre-commit`.

Step 2: Create a Central Configuration

To keep things tidy, I recommend managing your tool configurations in a single `pyproject.toml` file at the root of your project. This is the modern standard and avoids cluttering your repository with multiple config files like `.flake8` or `.black.toml`.

Create a file named `pyproject.toml` and add the following configuration:


[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310']

[tool.flake8]
max-line-length = 88
extend-ignore = "E203"
exclude = [
    ".git",
    "__pycache__",
    "build",
    "dist",
    "venv"
]

Here’s what this does:

  • The `[tool.black]` section tells Black to format code with a max line length of 88 characters.
  • The `[tool.flake8]` section does the same for our linter, ensuring it doesn’t flag lines that Black just formatted. The `extend-ignore = “E203″` part resolves a known conflict between Black and Flake8 regarding whitespace before colons.
  • The `exclude` list tells Flake8 to ignore directories that don’t contain our source code.

Pro Tip: The most common source of friction is when your formatter and linter disagree. I always make sure that `black`’s `line-length` and `flake8`’s `max-line-length` are identical. This prevents them from fighting each other and causing commit failures.

Step 3: Configure the Pre-Commit Hooks

Now we tell the `pre-commit` framework which tools to run. Create a file named `.pre-commit-config.yaml` in your project’s root directory. This file defines the hooks.


repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
    -   id: check-yaml
    -   id: end-of-file-fixer
    -   id: trailing-whitespace

-   repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
    -   id: black

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

Let’s break this down:

  • `repos`: This is a list of repositories where our hooks are defined.
  • `rev`: This pins the hook to a specific version, which guarantees that the linter’s behavior doesn’t change unexpectedly for different developers on the team.
  • `hooks`: We’re defining three sets of hooks. The first set from `pre-commit-hooks` does general file cleanup. The next two add our main tools: `black` to format and `flake8` to lint.

Step 4: Install the Hooks into Git

This is the final, crucial step that ties it all together. With your configuration files in place, go to your terminal in the project root and run the command to install the hooks. You’ll use the command `pre-commit install`.

This command creates a script inside your local `.git/hooks/` directory named `pre-commit`. From now on, every time you run `git commit`, this script will execute and run the checks you defined in your YAML file.

Step 5: Test Your New Workflow

Time to see it in action! Open any Python file in your project and intentionally mess it up. Add some extra blank lines, use double quotes for a string, or make a line longer than 88 characters.

Now, stage the file (`git add .`) and try to commit it (`git commit -m “Test: bad formatting”`).

You should see `pre-commit` spring to life in your terminal. It will run `black`, which will automatically fix the formatting issues. Because a file was changed, the commit will be aborted. If you check the file again, you’ll see it’s now perfectly formatted! Now, just `git add` the newly formatted file and run the commit command again. This time, it will pass `black` (since it’s already clean) and then run `flake8`. If there are no other errors, your commit will succeed.

Common Pitfalls

I’ve set this up dozens of times, and here is where I usually mess up, so you don’t have to:

  • Forgetting to Run `pre-commit install`: This is the #1 mistake. You write the perfect configuration, but if you don’t install the hook, Git has no idea it exists. The command is idempotent, so it’s safe to run it again if you’re not sure.
  • Configuration Mismatches: I mentioned it in the pro-tip, but it’s worth repeating. If `black` and `flake8` have different line length rules, you will get stuck in a loop where one tool “fixes” what the other flags as an error. Keep them in sync in `pyproject.toml`.
  • Running on Generated Files: Make sure your `venv`, `build`, or data directories are in your `.gitignore` and in the `exclude` sections of your configs. You don’t want these tools trying to format a massive log file or a third-party library.

Conclusion

Taking 10 minutes to set this up pays massive dividends for the entire lifecycle of a project. It standardizes code quality, eliminates bikeshedding during code reviews, and ultimately lets your team focus on what really matters: building great software. This simple automation is one of the highest-leverage improvements you can make to your development workflow.

Give it a try. Your future self will thank you.

All the best,
Darian Vance

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How does automating Python formatting and linting on Git commit improve development workflow?

It standardizes code quality across the team, eliminates ‘bikeshedding’ during code reviews, reduces cognitive load by proactively enforcing style, and ultimately allows developers to focus on building features rather than fixing stylistic issues.

âť“ How does this pre-commit automation compare to manual code quality checks or post-commit CI/CD checks?

Pre-commit automation proactively enforces code standards and fixes issues *before* a commit is even created, preventing non-compliant code from entering the repository. This is more efficient than manual checks, which are reactive, or post-commit CI/CD checks, which provide feedback after the code has already been committed.

âť“ What is a common implementation pitfall when configuring `black` and `flake8` with `pre-commit`?

A common pitfall is configuration mismatches, specifically when `black`’s `line-length` and `flake8`’s `max-line-length` differ. This can lead to an endless loop where one tool ‘fixes’ what the other flags as an error. Ensure these values are identical in your `pyproject.toml`.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading