Pre-commit Hooks and Bootstrap Scripts
Pre-commit hooks and bootstrap scripts turn your setup and quality checks into repeatable commands instead of tribal knowledge. Together, they make it easy for every developer and every clone of the repository to use the same environment, tools, and automated checks with minimal effort.
Two Sides of the Same Coin
- Bootstrap scripts answer "how do I set this project up?" with a single command
- Pre-commit hooks answer "is this code ready to commit?" by running the same checks on every change before it reaches the remote repository
Why Use a scripts/ Folder?¶
A scripts/ (or tools/) directory in the project root gives you a dedicated home for small utilities and onboarding helpers such as bootstrap.sh and bootstrap.ps1.
Single Source of Truth
Instead of a long list of instructions in chat or documentation, you keep the authoritative setup steps in version-controlled scripts that can be evolved with the codebase.
What a Bootstrap Script Should Do¶
A typical bootstrap script for a Python project takes care of the repetitive tasks you want every developer to run:
- Create or refresh the virtual environment if it does not exist yet
- Install main and development dependencies in the right order
- Install or update developer tooling, including
pre-commit, test runners, and any project CLIs
This reduces onboarding to a single command:
One Command Setup
The script becomes the single source of truth for environment setup. Update it once when you change how dependencies or tools are installed, and every developer benefits immediately.
Example Bootstrap Scripts¶
Below are minimal examples you can adapt. They assume a .venv directory for the environment and requirements.txt plus requirements-dev.txt for dependencies.
#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE}")/.." && pwd)"
cd "$PROJECT_ROOT"
if [ ! -d ".venv" ]; then
python -m venv .venv
fi
# shellcheck disable=SC1091
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
pre-commit install
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $ProjectRoot\..
if (-not (Test-Path ".venv")) {
python -m venv .venv
}
& .\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
pre-commit install
This script creates the environment if needed, activates it, installs dependencies, and wires up pre-commit in one run.
Platform Consistency
PowerShell performs the same steps on Windows, giving every platform a one-command setup story. Include both scripts in your repo for maximum usability.
What Are Pre-commit Hooks?¶
Git hooks are scripts that run at specific points in the Git workflow, such as before a commit is created. The pre-commit hook runs just before Git finalizes the commit, allowing checks to block bad changes.
The pre-commit Framework
The pre-commit framework is a tool that manages these hooks for you: you declare a list of hooks in .pre-commit-config.yaml, and it installs and runs them automatically on the staged files whenever you commit.
Why Pre-commit Is Useful
Pre-commit hooks are valuable because they:
- Catch issues early – Formatting errors, lint violations, and security problems are caught before code leaves your machine
- Apply consistent rules – Remove most style nitpicks from code review with team-wide enforcement
- Automate checks – Integrate naturally into the Git workflow instead of relying on people to remember separate commands
- Self-healing – Many hooks can fix problems in place, so a failed hook often comes with an automatic patch to re-stage and commit
Choosing Hooks for a Python Project¶
For Python projects, you typically combine several focused tools:
Core Hooks (Fast & Essential)¶
- Formatting:
blackorruff formatto enforce a consistent style - Linting:
rufforflake8to catch common mistakes and style issues - Import ordering:
isort(with--profile=black) to keep imports tidy - Housekeeping: trailing whitespace, end-of-file fixers, YAML/JSON checkers
Code Quality (Add as Projects Mature)¶
- Type checking:
mypyto statically check type hints and catch type-related bugs early - Docstrings:
pydocstyleto enforce PEP 257 conventions;docformatterto auto-normalize - Security:
banditto detect insecure patterns in Python code - Dead code:
vultureto find unused functions, classes, variables, and imports
Advanced Checks (Use When Needed)¶
- Dependency security:
pip-auditorsafetyto scan for known vulnerable packages - Code complexity:
pylintfor deeper semantic checks and code smell detection - Testing:
pytestas a pre-commit or pre-push hook to ensure tests pass before changes land - Coverage:
pytest-covto block commits if coverage drops below a threshold - Branch protection:
no-commit-to-branchto prevent accidental commits tomainorrelease/*
!!! tip Start Small, Grow Smart Begin with a small, fast set of hooks and expand only when you see consistent value from additional checks. A fast feedback loop keeps developers happy.
Installing and Wiring Pre-commit¶
Info
To use the pre-commit framework in your project:
-
Install the tool inside your project environment:
-
Create a
.pre-commit-config.yamlfile in the repository root -
Register hooks with Git (run once per clone):
After this, every git commit will run the configured hooks on the staged files.
Running Hooks Manually¶
You can run all hooks on the whole codebase at any time:
When to Use This
This is useful when adding pre-commit to an existing project or after changing the configuration to validate the entire codebase.
Example .pre-commit-config.yaml¶
Here is a minimal configuration that combines common Python tools:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
args: ["check"]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile=black"]
- repo: https://github.com/PyCQA/bandit
rev: 1.7.9
hooks:
- id: bandit
args: ["-ll", "-r", "."]
This configuration runs simple housekeeping hooks, formats code with Black, lints with Ruff, orders imports with isort using the Black profile, and runs Bandit for basic security checks.
Making Scripts and Hooks Work Together¶
Your bootstrap scripts and pre-commit configuration should complement each other:
Bootstrap installs dependencies – The script installs pre-commit and any tools referenced in .pre-commit-config.yaml, so hooks are guaranteed to have what they need
README stays simple – Describe onboarding in just a few lines, delegating details to scripts and configuration:
CI mirrors local workflow – Reuse these same scripts and pre-commit commands in CI to ensure local and remote behavior stay aligned
The Complete Picture
With this in place, quality checks become part of the default workflow instead of an optional extra step, and setting up the project on a new machine becomes repeatable, fast, and foolproof.