Skip to content

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"},
)

Logging in Exception Handlers

# Good - includes stack trace
except Exception as e:
    logger.exception(f"Failed to process: {e}")
    raise

# Good - when you don't want full traceback
except ValidationError as e:
    logger.warning(f"Validation failed: {e}")
    return error_response(str(e))