Python Style Guide (Ruff)
All Python code must pass Ruff linting. The Ruff configuration is defined in the root pyproject.toml. This guide is the expanded reference for the summary in AGENTS.md.
Configuration summary
Section titled βConfiguration summaryβ- Line length: 88 characters (hard limit)
- Target Python version: 3.11+
- Enabled rule sets: E4, E7, E9, F (Pyflakes), B (flake8-bugbear), I (isort), UP (pyupgrade), C4 (comprehensions), SIM (simplify), RUF (Ruff-specific)
- Ignored rules: E501 (line length handled by formatter), F401 (unused imports for re-exports), F403 (star imports in
__init__.py), B008 (FastAPI Depends pattern), B904 (FastAPI HTTPException), SIM117 (nested with statements), RUF001/RUF003 (intentional unicode)
Rule-by-rule examples
Section titled βRule-by-rule examplesβ1. Import organization (isort β I rules)
Section titled β1. Import organization (isort β I rules)βGroup imports: standard library β third-party β local. Use absolute imports, not relative (unless in the same package). Sort alphabetically within each group.
# β
Correctimport osfrom typing import Optional
from fastapi import FastAPIfrom pydantic import BaseModel
from app.models import Orderfrom app.services import OrderService
# β Wrong β mixed groups, relative importsfrom .models import Orderimport osfrom fastapi import FastAPI2. Line length (88 characters)
Section titled β2. Line length (88 characters)βBreak long lines using parentheses for function calls; use trailing commas in multi-line structures.
# β
Correctresult = some_function( arg1, arg2, arg3,)
# β Wrong β line too longresult = some_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)3. Comprehensions (C4 rules)
Section titled β3. Comprehensions (C4 rules)βPrefer list/dict/set comprehensions over loops when appropriate. Donβt use comprehensions for side effects.
# β
Correctsquares = [x**2 for x in range(10)]result = {k: v for k, v in items.items() if v > 0}
# β Wrong β comprehension for side effects[print(x) for x in items] # Use a loop instead4. Simplifications (SIM rules)
Section titled β4. Simplifications (SIM rules)βUse if x: instead of if x is True:; combine multiple if statements when possible.
# β
Correctif x and y: do_something()
# β Wrongif x is True: if y is True: do_something()5. Pyflakes (F rules)
Section titled β5. Pyflakes (F rules)βNever use undefined variables. Remove unused imports (except in __init__.py for re-exports) and unused variables (except in tests where mocks are used).
6. Bugbear (B rules)
Section titled β6. Bugbear (B rules)βDonβt use mutable defaults β use None instead. Donβt call functions in default arguments (except FastAPI Depends).
# β
Correctdef process(items: list[str] | None = None) -> list[str]: if items is None: items = [] return items
# β Wrong β mutable defaultdef process(items: list[str] = []): return items7. Pyupgrade (UP rules)
Section titled β7. Pyupgrade (UP rules)βUse modern Python 3.11+ syntax: list[str] instead of List[str], dict[str, int] instead of Dict[str, int], f-strings instead of .format() or %.
# β
Correct (Python 3.11+)def process(items: list[str]) -> dict[str, int]: return {item: len(item) for item in items}
# β Wrong β old syntaxfrom typing import List, Dict
def process(items: List[str]) -> Dict[str, int]: ...8. Error handling (E7 rules β bare except: is E722)
Section titled β8. Error handling (E7 rules β bare except: is E722)βUse specific exception types, not bare except:.
# β
Correcttry: result = risky_operation()except ValueError as e: logger.error(f"Value error: {e}") raise
# β Wrongtry: result = risky_operation()except: # Bare except passPer-file ignore map
Section titled βPer-file ignore mapβ| File pattern | Ignored rules | Why |
|---|---|---|
**/main.py |
B008, B904, SIM117, E402 | FastAPI patterns (Depends(), lazy imports) |
**/tasks.py |
SIM102, SIM105, SIM117, E731 | Worker task patterns |
**/init_langfuse_prompts.py |
RUF001, UP035 | Intentional unicode, older-Python compatibility |
**/tests/** |
SIM117, F841, E402 | Test patterns (mocks, grouping) |
**/conftest.py |
E402 | Imports after test setup |
**/__init__.py |
F401 | Re-exports |
data-engineering/workbench/notebook_template.py |
B018 | Marimo cell-output idiom (data-engineering/workbench-repo is in extend-exclude) |
**/crud.py |
SIM105, B904, UP035 | Shared library patterns (explicit pass, custom error wrapping) |
**/config.py |
B904 | raise from within except (custom error wrapping) |
**/extractors.py |
E402 | Module imports after env var setup (GEMINI_API_KEY before mirascope) |
**/r2.py |
B904 | raise from within except (custom R2Error wrapping with metadata) |
**/utils/**/*.py |
UP035 | typing.Dict/List backward compatibility |
**/observability/**/*.py |
RUF013 | Implicit Optional in metrics function signatures |
**/pdf_performance_metrics.py |
B025 | Duplicate exception (layered import fallbacks) |
Note: Customer Marimo notebooks live in the data-engineering/workbench-repo submodule, which is excluded via extend-exclude in root pyproject.toml. See pyproject.toml ([tool.ruff.lint.per-file-ignores]) for the authoritative list and per-rule rationale comments.
FastAPI-specific patterns (allowed in main.py and routes)
Section titled βFastAPI-specific patterns (allowed in main.py and routes)β# β
Correct β B008 is ignored in main.pyfrom fastapi import FastAPI, Depends
def get_db(): return db_connection()
@app.get("/items")async def get_items(db=Depends(get_db)): # Function call in default OK return db.query(Item).all()Test-specific patterns (allowed in tests/)
Section titled βTest-specific patterns (allowed in tests/)β# β
Correct β F841 is ignored in testsdef test_something(): mock_db = Mock() # Unused variable OK in tests result = function_under_test() assert result == expectedBefore and after writing Python code
Section titled βBefore and after writing Python codeβ- Before: check if the file matches a per-file-ignore pattern; use modern type hints; keep lines under 88 chars; organize imports correctly.
- After: verify with
ruff check <file>orpnpm lint:python; auto-fix withruff check --fix <file>orpnpm lint:python:fix. For the Dagster app specifically:cd apps/dagster && ruff check .
Remember: when in doubt, run ruff check <file> to see what violations exist, then fix them before committing.