Conventions & Style Guide
Agreed Python idioms for the team - formatting, naming, typing, errors, and commits - enforced by ruff in CI and documented here so reviews focus on design, not taste.
Recipe
from __future__ import annotations
from pathlib import Path
DATA_DIR = Path(__file__).resolve().parent / "data"
def load_config(path: Path) -> dict[str, str]:
try:
text = path.read_text(encoding="utf-8")
except OSError as exc:
raise ConfigError(f"cannot read {path}") from exc
return parse_env(text)When to reach for this:
- Writing new modules in shared services
- Reviewing PRs for consistency
- Onboarding developers from other languages
- Resolving "which pattern do we use?" debates
Working Example
# modules/billing/services/tax.py
from decimal import Decimal
from billing.domain.models import LineItem
from billing.settings import Settings
class TaxService:
def __init__(self, settings: Settings) -> None:
self._rate = Decimal(settings.vat_rate)
def compute(self, items: list[LineItem]) -> Decimal:
subtotal = sum((i.unit_price * i.quantity for i in items), start=Decimal("0"))
return (subtotal * self._rate).quantize(Decimal("0.01"))Commit message:
feat(billing): apply VAT to EU line items [BILL-91]
Uses Decimal for money math. Adds regression test for zero-quantity lines.What this demonstrates:
from __future__ import annotationsfor forward refsDecimalfor currency - not float- Private
_rateattribute; public typed methods - Ticket id in commit subject
Deep Dive
How It Works
- ruff - Replaces flake8, isort, pyupgrade; config in
pyproject.toml. - Line length - Typically 88 or 100 - team picks one.
- Naming - PEP 8:
snake_casefunctions,PascalCaseclasses,SCREAMING_SNAKEconstants. - Imports - isort rules via ruff; no wildcard
from module import *.
Team Defaults
| Topic | Convention |
|---|---|
| Strings | f-strings preferred |
| Paths | pathlib.Path not os.path.join |
| Exceptions | Specific types; raise ... from exc |
| Logging | logging.getLogger(__name__) not print |
| Async | No blocking IO in async def routes |
| Money | Decimal or integer cents |
Python Notes
# pyproject.toml excerpt
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"] # if formatter handles line lengthGotchas
- Per-developer black/ruff versions - CI fails locally passed PR. Fix:
uv syncpins tool versions. - Type ignores without ticket - Debt accumulates. Fix:
# type: ignore[arg-type] # BILL-99. - Float literals for money -
0.1 + 0.2bugs. Fix: Decimal or int cents. - Bare
except:- Masks KeyboardInterrupt. Fix: catchExceptionminimum, preferably specific types. - Mutable class attributes - Shared state bugs. Fix: dataclass instances or instance attrs.
- Inconsistent framework patterns - Flask globals in FastAPI app. Fix: follow service ADR.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Org-wide python-rules doc | Cross-team standards | Service-specific overrides needed |
| Automated ruff only | Small team | Onboarding needs prose examples |
| Google Python style guide | External reference | Conflicts with ruff config |
| No style guide | Never | Reviews bikeshed forever |
FAQs
Black or ruff format?
ruff format when ruff adopted - one tool; do not run both.
Single or double quotes?
ruff formatter picks consistently - do not hand-fight in review.
Type hint every local variable?
Public API and non-obvious locals - not name = "ada" in small functions.
Docstrings required?
Public modules/classes/functions yes - Google or numpy style per README.
max line length 88 vs 100?
Pick one in pyproject.toml - match formatter and reviewer expectations.
Relative vs absolute imports?
Absolute from billing.domain import models in apps - relative only within tight package per ADR.
Enums vs literals?
StrEnum or Literal for small closed sets - document in type-hints section.
Test naming?
test_<behavior>_when_<condition> - readable failure output.
Conventional commit types?
feat, fix, chore, docs, refactor, test - match changelog automation if any.
Override convention?
ADR or inline comment with reason - not silent one-off in single PR.
Related
- PEP 8 and Style Reference - reference
- 50 Python Rules - rules list
- Ruff and Formatters - tooling
- Code Review Guidelines - review focus
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.