Pre-commit Hooks Setup for Python Static Analysis

Establishing a deterministic pre-commit workflow enforces type hints and static analysis before code enters version control. This approach aligns local developer environments with continuous integration pipelines.

Isolating hook execution environments prevents dependency conflicts across projects. Deterministic caching enables sub-second type validation during iterative development. Hook strictness must scale with project maturity and team velocity. Local execution should mirror broader Static Analysis Tools & CI Integration standards to eliminate environment drift.

Environment Isolation & Hook Architecture

Pre-commit creates isolated virtual environments for each hook repository. This guarantees reproducible execution regardless of the host system. You must declare language: python alongside explicit additional_dependencies.

Global site-packages are automatically excluded. This prevents silent failures caused by mismatched package versions. Full-project type scanners require pass_filenames: false to maintain cross-module inference accuracy.

Execution order directly impacts latency. Place fast formatters and linters before heavy type checkers. This prevents redundant AST parsing on already-modified files.

repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
 rev: v0.4.5
 hooks:
 - id: ruff
 args: [--fix, --exit-non-zero-on-fix]
 types_or: [python, pyi]
 - repo: https://github.com/pre-commit/mirrors-mypy
 rev: v1.10.0
 hooks:
 - id: mypy
 args: [--strict, --show-error-codes, --cache-dir=.mypy_cache]
 additional_dependencies: [types-requests, pydantic]
 pass_filenames: false

The configuration above demonstrates isolated dependency injection. Cache directory pinning prevents stale state accumulation. Disabling filename passing forces mypy to analyze the entire project graph.

Type Checker Integration & Strictness Alignment

Type checkers diverge significantly in performance and configuration syntax. Ruff handles linting and import sorting in milliseconds. Mypy prioritizes exhaustive type narrowing but requires full context. Pyright offers incremental analysis with different configuration semantics.

Aligning strictness tiers prevents developer friction during onboarding. Apply args: [--strict] alongside targeted exclude patterns for legacy modules. Reference Mypy Configuration & Strictness baselines when defining tiered validation rules.

Evaluate Pyright vs Mypy Comparison benchmarks before selecting a primary checker. Large-scale migrations benefit from incremental strictness. Use --follow-imports=skip to isolate new modules from untyped dependencies.

[tool.mypy]
strict = true
warn_return_any = true
ignore_missing_imports = false
exclude = ["^tests/fixtures/", "^legacy/"]

[tool.ruff]
target-version = "py310"
select = ["E", "F", "I", "UP", "N", "TCH"]

Decouple hook configuration from project defaults using targeted exclusions. Modern Python targeting ensures compatibility with structural pattern matching. The following example demonstrates a Python 3.10+ module that triggers strict validation:

from typing import Protocol, runtime_checkable

@runtime_checkable
class DataProcessor(Protocol):
 def transform(self, payload: bytes) -> dict[str, list[int]]: ...

def execute(processor: DataProcessor, raw: bytes) -> dict[str, list[int]]:
 # Type checker enforces return type alignment
 return processor.transform(raw)

CI/CD Pipeline Synchronization & Cache Optimization

Local hooks must execute identically in continuous integration environments. Share .mypy_cache and .ruff_cache across runners via artifact storage. This eliminates redundant computation during parallel job execution.

Restrict always_run: true to baseline validation hooks only. Overusing this flag degrades pipeline throughput. Configure fail_fast: false to surface all violations in a single execution pass.

Pin hook repository revisions to guarantee reproducible builds across branches. Floating tags introduce unexpected dependency updates. Cache directories should be archived using runner-specific path mappings.

Debugging Hook Failures & Incremental Adoption

Environment drift causes false positives during rollout. Run pre-commit run --show-diff-on-failure to map violations to exact code changes. This output clarifies whether failures stem from local state or actual type errors.

Use SKIP=hook_id for temporary bypass during emergency merges. Document all bypasses in pull request descriptions. Profile execution latency with time pre-commit run --all-files to identify bottlenecks.

Transition from advisory to blocking mode using Automating pre-commit type validation workflows. Gradual enforcement reduces merge conflicts. Monitor violation trends before raising strictness thresholds.

Common Mistakes & Mitigation

  • Running type checkers with pass_filenames: true: Causes fragmented analysis and false negatives. Type checkers require full project context for accurate cross-module inference.
  • Relying on unpinned hook repository revisions: Leads to non-deterministic CI failures. Upstream hooks frequently introduce breaking changes or unexpected dependency updates.
  • Sharing local cache directories directly with CI runners: Creates cross-platform cache corruption. Differing OS filesystem semantics and Python patch versions invalidate cached state.

Frequently Asked Questions

Should I run pre-commit hooks on all files or only staged changes? Run linters on staged files for speed. Execute type checkers with pass_filenames: false on the full repository to maintain inference accuracy.

How do I reduce pre-commit hook execution time in large monorepos? Enable persistent caching. Exclude generated and legacy directories. Parallelize independent hooks using stages: [pre-commit].

Can pre-commit hooks replace CI type checking? No. Pre-commit provides immediate feedback. CI must re-validate to catch environment drift and enforce branch protection policies.

How do I handle third-party library type stubs in pre-commit? Install required stubs via additional_dependencies in the hook configuration. This ensures consistent resolution across all developer machines.