Python Quality - Quick Reference

SkillAI & models

Python code quality with Ruff, Black, mypy, and Pylint. Covers linting, formatting, type checking, and best practices.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Python Quality - Quick Reference skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/quality/python-quality/SKILL.md and read by ahel’s review.

When NOT to Use This Skill

  • SonarQube setup - Use sonarqube skill
  • pytest configuration - Use pytest skills
  • Security scanning - Use python-security skill

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: fastapi or django for framework-specific patterns.

Tool Overview

ToolFocusSpeedUse Case
RuffLinting + formattingFastestAll-in-one replacement
BlackFormatting onlyFastOpinionated formatting
mypyType checkingMediumStatic type analysis
PylintDeep analysisSlowComprehensive linting
isortImport sortingFastImport organization

Recommendation: Use Ruff (replaces Black, isort, Flake8, and many Pylint rules).

Ruff Setup (Recommended)

Installation

pip install ruff

# Or with project
pip install "ruff>=0.3.0"

pyproject.toml

[tool.ruff]
target-version = "py312"
line-length = 100
exclude = [".venv", "migrations", "__pycache__"]

[tool.ruff.lint]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # Pyflakes
    "I",      # isort
    "B",      # flake8-bugbear
    "C4",     # flake8-comprehensions
    "UP",     # pyupgrade
    "ARG",    # flake8-unused-arguments
    "SIM",    # flake8-simplify
    "TCH",    # flake8-type-checking
    "PTH",    # flake8-use-pathlib
    "ERA",    # eradicate (commented code)
    "PL",     # Pylint
    "RUF",    # Ruff-specific
]
ignore = [
    "PLR0913",  # Too many arguments (configure separately)
    "PLR2004",  # Magic value comparison
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101"]  # Allow assert in tests

[tool.ruff.lint.pylint]
max-args = 5
max-branches = 10
max-returns = 3

[tool.ruff.lint.isort]
known-first-party = ["myapp"]
force-single-line = true

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

Commands

# Lint
ruff check .

# Fix auto-fixable
ruff check --fix .

# Format
ruff format .

# Check format without changing
ruff format --check .

# Watch mode
ruff check --watch .

mypy Setup

Installation

pip install mypy

pyproject.toml

[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_unreachable = true

# Per-module overrides
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false

[[tool.mypy.overrides]]
module = "migrations.*"
ignore_errors = true

Commands

# Type check
mypy src/

# Show error codes
mypy src/ --show-error-codes

# Generate stubs for library
stubgen -p some_library

Common mypy Errors

ErrorDescriptionFix
[arg-type]Wrong argument typeFix type or add annotation
[return-value]Wrong return typeFix return or annotation
[assignment]Incompatible assignmentFix type or use cast
[no-untyped-def]Missing type annotationsAdd annotations
[union-attr]Optional access without checkAdd None check

Pylint Setup (Optional - Deep Analysis)

Installation

pip install pylint

pyproject.toml

[tool.pylint.main]
py-version = "3.12"
jobs = 0  # Auto-detect CPU count
ignore-patterns = ["migrations", "__pycache__"]

[tool.pylint.messages_control]
disable = [
    "missing-module-docstring",
    "missing-class-docstring",
    "missing-function-docstring",
    "too-few-public-methods",
]

[tool.pylint.design]
max-args = 5
max-locals = 15
max-branches = 10
max-statements = 50
max-attributes = 10
max-public-methods = 20

[tool.pylint.format]
max-line-length = 100

[tool.pylint.similarities]
min-similarity-lines = 5
ignore-imports = true

Commands

# Full check
pylint src/

# Specific file
pylint src/main.py

# Generate config
pylint --generate-rcfile > .pylintrc

Type Hints Best Practices

Basic Types

from typing import Any

# Primitives
name: str = "John"
age: int = 30
active: bool = True
score: float = 95.5

# Collections
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95, "Bob": 87}
unique_ids: set[int] = {1, 2, 3}
coordinates: tuple[float, float] = (1.0, 2.0)

# Optional (can be None)
middle_name: str | None = None

# Union types
identifier: str | int = "abc123"

# Any (avoid when possible)
data: Any = get_dynamic_data()

Function Signatures

from collections.abc import Callable, Iterable

def greet(name: str) -> str:
    return f"Hello, {name}"

def process_items(items: Iterable[int]) -> list[int]:
    return [item * 2 for item in items]

def apply_func(func: Callable[[int], int], value: int) -> int:
    return func(value)

# Generic functions
from typing import TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T | None:
    return items[0] if items else None

Classes

from dataclasses import dataclass
from typing import Self

@dataclass
class User:
    id: int
    email: str
    name: str | None = None

    def with_name(self, name: str) -> Self:
        return User(id=self.id, email=self.email, name=name)

Protocols (Structural Typing)

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

def render(item: Drawable) -> None:
    item.draw()

# Any class with draw() method works
class Circle:
    def draw(self) -> None:
        print("Drawing circle")

render(Circle())  # OK

Common Code Smells & Fixes

1. Mutable Default Arguments

# BAD - Mutable default
def append_to(item, target=[]):
    target.append(item)
    return target

# GOOD - Use None
def append_to(item: str, target: list[str] | None = None) -> list[str]:
    if target is None:
        target = []
    target.append(item)
    return target

2. Bare Except

# BAD
try:
    do_something()
except:
    pass

# GOOD - Specific exceptions
try:
    do_something()
except ValueError as e:
    logger.warning(f"Invalid value: {e}")
except IOError as e:
    logger.error(f"IO error: {e}")
    raise

3. God Class

# BAD
class OrderProcessor:
    def create_order(self): ...
    def send_email(self): ...
    def generate_pdf(self): ...
    def calculate_tax(self): ...
    def update_inventory(self): ...

# GOOD - Single responsibility
class OrderService:
    def __init__(
        self,
        email_service: EmailService,
        pdf_generator: PdfGenerator,
        tax_calculator: TaxCalculator,
    ):
        self._email = email_service
        self._pdf = pdf_generator
        self._tax = tax_calculator

    def create_order(self, request: OrderRequest) -> Order:
        order = self._build_order(request)
        order.tax = self._tax.calculate(order)
        return order

4. Long Functions

# BAD - 100+ line function
def process_order(order):
    # validation
    # calculation
    # database
    # notifications
    pass

# GOOD - Extracted functions
def process_order(order: Order) -> ProcessedOrder:
    validate_order(order)
    total = calculate_total(order)
    saved = save_order(order, total)
    notify_user(saved)
    return saved

5. Magic Numbers

# BAD
if user.age >= 18:
    if len(password) >= 8:
        pass

# GOOD - Named constants
MINIMUM_AGE = 18
MIN_PASSWORD_LENGTH = 8

if user.age >= MINIMUM_AGE:
    if len(password) >= MIN_PASSWORD_LENGTH:
        pass

Pre-commit Setup

.pre-commit-config.yaml

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

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        additional_dependencies:
          - types-requests
          - pydantic

Commands

# Install hooks
pre-commit install

# Run on all files
pre-commit run --all-files

# Update hooks
pre-commit autoupdate

VS Code Settings

// .vscode/settings.json
{
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
      "source.organizeImports.ruff": "explicit"
    }
  },
  "python.analysis.typeCheckingMode": "strict",
  "mypy-type-checker.importStrategy": "fromEnvironment"
}

Quality Metrics Targets

MetricTargetTool
Cyclomatic Complexity< 10Ruff (PLR0912)
Cognitive Complexity< 15Ruff (C901)
Function Length< 50 linesRuff (PLR0915)
Arguments< 5Ruff (PLR0913)
Returns< 3Ruff (PLR0911)
Type Coverage100%mypy

CI/CD Integration

GitHub Actions

name: Quality
on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          pip install ruff mypy
          pip install -e ".[dev]"

      - name: Ruff check
        run: ruff check .

      - name: Ruff format check
        run: ruff format --check .

      - name: mypy
        run: mypy src/

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
# type: ignore everywhereDefeats type checkingFix types or be specific
Any overuseNo type safetyUse proper types or TypeVar
Mutable default argsShared state bugsUse None with check
Bare except:Catches everythingCatch specific exceptions
noqa without codeIgnores all rulesUse noqa: E501 specifically
No type hintsHard to maintainAdd progressive typing

Quick Troubleshooting

IssueLikely CauseSolution
mypy can't find moduleMissing stubsInstall types-* package
Ruff conflicts with BlackBoth formattingUse only Ruff format
Type error in library codeLibrary not typedAdd to mypy ignore list
Pre-commit too slowRunning all checksUse --files for changed only
Import order conflictsMultiple toolsUse only Ruff isort

Related Skills

Signals

GitHub stars
33
Forks
8
Last commit
Sep 2026

ahel review

  • K1binfo
    installs-packages

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Catalog kind
skill
Gateway key
python-quality
Source
github.com/claude-dev-suite/claude-dev-suite