Python Coding Style Guide
Tools Configuration
| Tool | Purpose | Configuration File |
|---|---|---|
| black | Code formatting | pyproject.toml |
| ruff | Linting + import sorting | pyproject.toml |
| mypy | Type checking | pyproject.toml |
Python Standards
Imports
Follow PEP 8 import ordering with 3 sections, separated by blank lines:
# 1. Standard library
import os
from datetime import datetime
from http import HTTPStatus
# 2. Third-party packages
from fastapi import APIRouter, Depends
from pydantic import BaseModel
# 3. Local imports
from src.config.settings import settings
from src.api.models.user import UserInfo
Rules:
- Alphabetical order within each section
- Import statements before from statements
- One import per line (no import os, sys)
- Use absolute imports, not relative (from src.config not from ..config)
- No comments between imports - they break automatic sorting
Type Hints
Use modern Python 3.10+ syntax:
# Good - Modern syntax
def process(name: str | None = None) -> dict[str, Any]:
items: list[str] = []
# Avoid - Old syntax
from typing import Optional, List, Dict
def process(name: Optional[str] = None) -> Dict[str, Any]:
items: List[str] = []
Type hint guidelines:
- All public functions must have type hints
- Use str | None instead of Optional[str]
- Use list[str] instead of List[str]
- Use dict[str, Any] instead of Dict[str, Any]
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Classes | PascalCase | UserService, ChatRequest |
| Functions | snake_case | get_user_info, process_file |
| Variables | snake_case | user_name, file_path |
| Constants | UPPER_SNAKE | MAX_RETRIES, API_VERSION |
| Private | _leading_underscore | _internal_method |
| Module | snake_case | user_service.py |
Naming tips:
- Be descriptive: get_user_by_id() not get_u()
- Use verbs for functions: create_, get_, update_, delete_, is_, has_
- Use nouns for classes: UserService, FileProcessor
- Avoid abbreviations unless widely known (url, id, api)
String Formatting
Use f-strings (preferred) or .format():
# Good - f-string
message = f"User {user_id} not found"
logger.info(f"Processing file: {file_path}")
# Good - for logging with lazy evaluation
logger.debug("Processing %s items", count)
# Avoid - % formatting for regular strings
message = "User %s not found" % user_id
# Avoid - concatenation
message = "User " + str(user_id) + " not found"
Docstrings
Use Google-style docstrings for public functions and classes:
def process_file(file_path: str, options: dict | None = None) -> ProcessResult:
"""
Process a file and return the result.
Args:
file_path: Path to the file to process.
options: Optional processing options.
Returns:
ProcessResult containing the processed data.
Raises:
FileNotFoundError: If the file does not exist.
ProcessingError: If processing fails.
"""
When to write docstrings:
- All public functions and methods
- All classes
- Complex private methods
- Not needed for simple/obvious methods like __init__ with typed parameters
Error Handling
Exception Guidelines
Catch specific exceptions, not bare Exception:
# Good
try:
data = json.loads(content)
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON: {e}")
raise ValidationError("Invalid JSON format") from e
# Avoid
try:
data = json.loads(content)
except Exception:
pass # Hides all errors!
Use exception chaining with from:
# Good - preserves original traceback
except DatabaseError as e:
raise ServiceError("Database operation failed") from e
# Avoid - loses original context
except DatabaseError as e:
raise ServiceError("Database operation failed")
Use contextlib.suppress() only for intentionally ignored exceptions:
from contextlib import suppress
# Good - clearly ignoring expected case
with suppress(FileNotFoundError):
os.remove(temp_file)
# Good - cleanup that shouldn't mask original error
except SomeError:
with suppress(Exception):
resource.close()
raise
# Bad - hiding potential bugs
with suppress(Exception):
do_something()
HTTP Exceptions in FastAPI
from http import HTTPStatus
from fastapi import HTTPException
# Use HTTPStatus enum for clarity
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail={"message": "User not found", "error": "user_not_found"},
)