Python Quality - Quick Reference
SkillAI & modelsPython 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.
No other account needed.
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
sonarqubeskill - pytest configuration - Use pytest skills
- Security scanning - Use
python-securityskill
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:fastapiordjangofor framework-specific patterns.
Tool Overview
| Tool | Focus | Speed | Use Case |
|---|---|---|---|
| Ruff | Linting + formatting | Fastest | All-in-one replacement |
| Black | Formatting only | Fast | Opinionated formatting |
| mypy | Type checking | Medium | Static type analysis |
| Pylint | Deep analysis | Slow | Comprehensive linting |
| isort | Import sorting | Fast | Import 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
| Error | Description | Fix |
|---|---|---|
[arg-type] | Wrong argument type | Fix type or add annotation |
[return-value] | Wrong return type | Fix return or annotation |
[assignment] | Incompatible assignment | Fix type or use cast |
[no-untyped-def] | Missing type annotations | Add annotations |
[union-attr] | Optional access without check | Add 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
| Metric | Target | Tool |
|---|---|---|
| Cyclomatic Complexity | < 10 | Ruff (PLR0912) |
| Cognitive Complexity | < 15 | Ruff (C901) |
| Function Length | < 50 lines | Ruff (PLR0915) |
| Arguments | < 5 | Ruff (PLR0913) |
| Returns | < 3 | Ruff (PLR0911) |
| Type Coverage | 100% | 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-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
# type: ignore everywhere | Defeats type checking | Fix types or be specific |
Any overuse | No type safety | Use proper types or TypeVar |
| Mutable default args | Shared state bugs | Use None with check |
Bare except: | Catches everything | Catch specific exceptions |
noqa without code | Ignores all rules | Use noqa: E501 specifically |
| No type hints | Hard to maintain | Add progressive typing |
Quick Troubleshooting
| Issue | Likely Cause | Solution |
|---|---|---|
| mypy can't find module | Missing stubs | Install types-* package |
| Ruff conflicts with Black | Both formatting | Use only Ruff format |
| Type error in library code | Library not typed | Add to mypy ignore list |
| Pre-commit too slow | Running all checks | Use --files for changed only |
| Import order conflicts | Multiple tools | Use 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