Skip to content
Back to skills

Python Quality

ASecurity

Python code quality with Ruff, Black, mypy, and Pylint. Covers linting, formatting, type checking, and best practices. USE WHEN: user works with "Python", "Django", "FastAPI", "Flask", asks about "Ruff", "Black", "mypy", "Pylint", "Python linting", "Python type hints" DO NOT USE FOR: SonarQube - use `sonarqube` skill, testing - use pytest skills, security - use `python-security` skill

  • 31 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 8, 2026
ai-agentspythongobashfastapidjangoflasktestinggitapidatabase

Works with

  • vscode
  • api
  • mcp

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 8, 2026

npx -y skills add claude-dev-suite/claude-dev-suite --skill python-quality --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Quality?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Python Quality
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/claude-dev-suite-python-quality-claude-dev-suite/badge)](https://www.skillsdirectory.com/skills/claude-dev-suite-python-quality-claude-dev-suite)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: python-quality
description: |
  Python code quality with Ruff, Black, mypy, and Pylint.
  Covers linting, formatting, type checking, and best practices.

  USE WHEN: user works with "Python", "Django", "FastAPI", "Flask", asks about "Ruff", "Black", "mypy", "Pylint", "Python linting", "Python type hints"

  DO NOT USE FOR: SonarQube - use `sonarqube` skill, testing - use pytest skills, security - use `python-security` skill
allowed-tools: Read, Grep, Glob, Bash
---
# Python Quality - Quick Reference

## 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

| 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

```bash
pip install ruff

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

### pyproject.toml

```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

```bash
# 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

```bash
pip install mypy
```

### pyproject.toml

```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

```bash
# 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

```bash
pip install pylint
```

### pyproject.toml

```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

```bash
# Full check
pylint src/

# Specific file
pylint src/main.py

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

## Type Hints Best Practices

### Basic Types

```python
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

```python
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

```python
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)

```python
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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```python
# 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

```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

```bash
# Install hooks
pre-commit install

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

# Update hooks
pre-commit autoupdate
```

## VS Code Settings

```json
// .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

```yaml
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
- [SonarQube](../sonarqube/SKILL.md)
- [Clean Code](../../best-practices/clean-code/SKILL.md)
- [Python Security](../../security/python-security/SKILL.md)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.