A pre-commit hook that lints SQL in Alembic migrations using squawk, a PostgreSQL migration linter.
Squawk operates on raw SQL files, but Alembic migrations are Python. This hook bridges the gap by generating DDL via alembic upgrade --sql (offline mode) and passing the complete SQL output to squawk for analysis. This captures all SQL statements a migration produces, including ORM operations like op.create_index(), op.create_table(), and op.alter_column().
Add the following to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/kintsugi-tax/squawk-pre-commit
rev: v0.3.3
hooks:
- id: squawk-alembicNo additional configuration is required. The hook auto-detects your migrations directory by reading script_location from alembic.ini. The consumer's alembic must be available on PATH (the hook calls it via subprocess).
The hook depends on squawk-cli >= 2.0. To pin a specific squawk version (matching your local dev dependency, for example), use additional_dependencies:
repos:
- repo: https://github.com/kintsugi-tax/squawk-pre-commit
rev: v0.3.3
hooks:
- id: squawk-alembic
additional_dependencies: ["squawk-cli==2.41.0"]This overrides the default version and ensures pre-commit uses the exact squawk release you specify.
To skip migrations that already exist on a branch (useful for repos with existing violations you can't fix immediately), pass --diff-branch:
repos:
- repo: https://github.com/kintsugi-tax/squawk-pre-commit
rev: v0.3.3
hooks:
- id: squawk-alembic
args: [--diff-branch, main]With this flag, the hook checks whether each migration file exists on the specified branch. Files that already exist are skipped. New files (not yet on the branch) are linted. This makes pre-commit run --all-files safe to run in repos where older migrations would fail linting.
The value must be a ref that resolves locally via git rev-parse --verify. In CI environments with shallow checkouts or no local tracking branch, use the remote form:
args: [--diff-branch, origin/main]When pre-commit runs, the hook:
- Parses
alembic.inito find the migrationsversions/directory - Filters staged files to only those under that directory
- Runs
alembic upgrade --sqlto generate the complete DDL for each migration - Pipes the generated SQL to squawk for linting
Merge migrations (where down_revision is a tuple) are skipped since they produce no DDL.
- The hook runs
alembic upgrade --sql, which executes your project'senv.pyin offline mode. No database connection is made, but the Python code inenv.pydoes run. - If
DATABASE_URLis not set, the hook provides a dummy fallback (postgresql://localhost/lint) so alembic's offline mode can generate SQL without a real connection string. - If
alembic upgrade --sqlfails for a migration (e.g. due to missing dependencies or env configuration), the hook prints the error to stderr and fails the run.
Squawk reads its configuration from .squawk.toml in the consumer repo root. See the squawk docs for available options.
Prerequisites:
- Python 3.10+ (3.12 recommended for development)
- Poetry
- squawk-cli (
pip install squawk-cli)
Steps:
- Install dependencies:
poetry install - Activate the virtual environment:
source .venv/bin/activate - Install the pre-commit hooks:
pre-commit install - Run tests:
poetry run pytest tests/ -v
Some integration tests in tests/test_squawk_config.py require squawk on PATH and are automatically skipped if it is not installed.
Versioning:
PRs that change package files (squawk_alembic/, pyproject.toml, .pre-commit-hooks.yaml) must include a version bump. CI enforces this. To bump:
make bump VERSION=0.3.3This updates both pyproject.toml and squawk_alembic/__init__.py. On merge to main, CI automatically creates a git tag and GitHub release from the new version. Changes to tests, docs, or CI config do not require a version bump.
To test the hook against a consumer repo locally:
cd /path/to/consumer-repo
pre-commit try-repo /path/to/squawk-pre-commit squawk-alembic --all-files