Lesson 036: Linter Rules vs. Framework Idioms

Lesson 036: Linter Rules vs. Framework Idioms

The Lesson

When a linter rule flags code that follows a framework's official pattern, suppress the rule per-line with noqa rather than restructuring the code. Linter rules encode general best practices; framework idioms encode domain-specific patterns that intentionally violate those practices. Restructuring idiomatic framework code to satisfy a general lint rule produces code that is harder to read, harder to maintain, and unfamiliar to anyone who knows the framework.

Context

A FastAPI web application used Depends(get_db) as a default argument in route functions — the standard FastAPI dependency injection pattern documented in FastAPI's official tutorials. Ruff's B008 rule ("Do not perform function call in argument defaults") flagged every route function. B008 exists because calling a function in a default argument is evaluated once at definition time, which causes bugs when the function returns a mutable object (e.g., def f(items=list())). FastAPI's Depends() deliberately exploits this behavior: it returns a dependency marker at definition time, which FastAPI's framework evaluates per-request at runtime.

What Happened

  1. Wrote 6 FastAPI route functions following the official Depends() pattern: def list_images(..., conn = Depends(get_db)):.
  2. Ruff flagged all 6 with B008. The suggested fix was to move the Depends() call inside the function body. But FastAPI requires Depends() in the function signature — moving it to the body would break dependency injection entirely.
  3. Considered three options:
    • Suppress B008 globally in pyproject.toml — too broad; B008 catches real bugs in non-FastAPI code.
    • Restructure to use Annotated[Connection, Depends(get_db)] — valid FastAPI syntax, but adds verbosity without benefit, and is still flagged by B008.
    • Suppress per-line with # noqa: B008 — minimal, targeted, self-documenting.
  4. Chose per-line suppression. Added # noqa: B008 to each Depends() default. The comment signals to readers: "yes, this is intentional — it's a framework pattern."
  5. All other B008 violations in the codebase (if any) would still be caught. The rule remains active for non-FastAPI code.

Key Insights

Examples

# FastAPI's official pattern — triggers B008
@router.get("/api/images")
def list_images(
    page: int = Query(1, ge=1),
    conn: Connection = Depends(get_db),  # noqa: B008
):
    ...

# "Fix" that breaks dependency injection — DO NOT DO THIS
@router.get("/api/images")
def list_images(page: int = Query(1, ge=1)):
    conn = Depends(get_db)  # BUG: returns a Depends marker, not a connection
    ...

# Global suppression — too broad for mixed codebases
# pyproject.toml
# [tool.ruff.lint]
# ignore = ["B008"]  # suppresses ALL B008, including real bugs elsewhere

Applicability

This pattern applies whenever a linter rule conflicts with a framework's documented idiom. It does NOT apply when the flagged code is genuinely buggy — if Depends were a regular function returning a mutable default, B008 would be correct to flag it. The key question is: does the framework deliberately exploit the behavior the lint rule is guarding against? If yes, suppress. If no, fix.

Related Lessons