Python Linting Standard Operating Procedure (SOP)

12 min read

Note: these articles are auto generated from my Obsidian notebook by Claude

This SOP establishes consistent linting practices for Python projects to ensure code quality, maintainability, and team efficiency.

Overview

Python linting involves automated code quality checks including:

  • Code formatting (spacing, line breaks)
  • Import organization
  • Style guide compliance
  • Common error detection
  • Type checking (optional but recommended)

Tool Stack

Option 1: Modern Approach (Recommended for New Projects)

  • Ruff: All-in-one linter and formatter (replaces Black, isort, Flake8)
  • mypy or Pyright: Static type checking

Option 2: Traditional Approach (For Existing Projects)

  • Black: Code formatting
  • isort: Import sorting
  • Flake8: Style guide enforcement
  • mypy: Type checking (optional)

Configuration Standards

1. Line Length

  • Standard: 100 characters
  • Rationale: Balance between readability and modern displays
  • Configuration: Set consistently across all tools

2. File Length

  • Target: 300-500 lines per file
  • Maximum: 500 lines (configurable based on project needs)
  • Action: Refactor files exceeding limits into modules

3. Import Organization

  • Order: Standard library → Third-party → Local imports
  • Style: Alphabetical within groups
  • Tool: isort with profile = "black" or Ruff

Setup Instructions

For New Projects Using Ruff

1. Install Ruff:

pip install ruff

2. Create pyproject.toml:

[tool.ruff]
line-length = 100
target-version = "py310"

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W"]
ignore = ["E501"]  # Line length handled by formatter

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

3. Add to Makefile:

lint:
    ruff check src/ --fix
    ruff format src/

lint-check:
    ruff check src/
    ruff format --check src/

For Existing Projects Using Traditional Tools

1. Install tools:

pip install black isort flake8 flake8-length

2. Create .flake8:

[flake8]
max-line-length = 100
max-physical-line-length = 500
max-lines = 500
ignore = 
    # Black compatibility
    E203,  # Whitespace before ':'
    W503,  # Line break before binary operator
    E704,  # Multiple statements on one line
    E501,  # Line too long (handled by Black)
    LLW405  # File length warnings (if allowing >100 lines)
exclude = venv,build,dist,.git,__pycache__,*.egg-info

3. Update pyproject.toml:

[tool.black]
line-length = 100

[tool.isort]
line_length = 100
profile = "black"  # CRITICAL: Makes isort compatible with Black

4. Create/Update Makefile:

lint:
    black src/
    isort src/
    flake8 src/

lint-check:
    black --check src/
    isort --check-only src/
    flake8 src/

Pre-commit Setup

1. Install pre-commit:

pip install pre-commit

2. Create .pre-commit-config.yaml:

repos:
  # For Ruff
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  # For traditional tools
  - repo: https://github.com/psf/black
    rev: 23.0.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: ["--profile", "black"]

3. Install hooks:

pre-commit install

CI/CD Integration

GitHub Actions Example

name: Lint and Test

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"
      
      - name: Run linting
        run: make lint-check
      
      - name: Run tests
        run: make test

Publishing Workflow

Pre-publish Checklist

  1. ✅ Run make lint to fix formatting
  2. ✅ Run make lint-check to verify
  3. ✅ Run make test to ensure tests pass
  4. ✅ Update CHANGELOG.md
  5. ✅ Commit all changes
  6. ✅ Run publish script

Common Publishing Issues and Solutions

1. "Working directory not clean"

  • Solution: Commit or stash all changes
  • Check: git status

2. "Black wants to reformat files"

  • Solution: Run make lint before make lint-check
  • Root cause: Formatter wasn't run

3. "isort and Black fighting"

  • Solution: Add profile = "black" to isort config
  • Verify: Both tools should be happy after one run

4. "File too long warnings (LLW405)"

  • Solution: Either refactor large files OR add LLW405 to ignore list
  • Decision: Based on project standards

Troubleshooting Guide

Issue: Linting passes locally but fails in CI

Causes:

  • Different Python versions
  • Missing dependencies
  • Cache issues

Solution:

# Clear caches
rm -rf .pytest_cache __pycache__
# Reinstall in fresh environment
python -m venv fresh_env
source fresh_env/bin/activate
pip install -e ".[dev]"
make lint-check

Issue: Import order keeps changing

Cause: isort not configured for Black compatibility

Solution: Add profile = "black" to isort configuration

Issue: Line length conflicts

Cause: Different tools have different line length settings

Solution: Ensure all tools use same line-length (100 recommended)

Best Practices

  1. Run linters before committing
    • Use pre-commit hooks
    • Include in PR checks
  2. Fix linting incrementally
    • Don't ignore all warnings at once
    • Address root causes, not symptoms
  3. Document exceptions
    • If ignoring a rule, comment why
    • Keep ignore list minimal
  4. Regular updates
    • Update linting tools quarterly
    • Review and adjust rules annually
  5. Team alignment
    • Agree on standards as a team
    • Document project-specific decisions

Quick Reference

Essential Commands

# Fix all formatting issues
make lint

# Check without modifying
make lint-check

# Run before publishing
make lint-check && make test

# Setup pre-commit
pre-commit install
pre-commit run --all-files

Configuration Files Priority

  1. pyproject.toml - Primary configuration
  2. .flake8 - Flake8 specific (can't use pyproject.toml)
  3. setup.cfg - Legacy, avoid if possible
  4. .pre-commit-config.yaml - Git hooks

Migration Path: Traditional → Ruff

Phase 1: Ensure current tools work together

  • Fix Black/isort compatibility
  • Resolve all linting errors

Phase 2: Install Ruff alongside

  • Run both toolsets in parallel
  • Compare outputs

Phase 3: Switch to Ruff

  • Update configurations
  • Update CI/CD
  • Remove old tools

The Bottom Line

Consistent linting improves code quality and reduces review friction. Start strict and relax rules based on team needs. Automation is key - let tools handle formatting so developers can focus on logic.

Last Updated: 2024-12-25

Version: 1.0