Python coding standards for VerifyWise services, following PEP 8 and Python best practices.
Use 4 spaces per indentation level. Never use tabs.
# Good
def calculate_total(items: list[Item]) -> float:
total = 0.0
for item in items:
if item.is_active:
total += item.price * item.quantity
return total
# Bad - 2 spaces
def calculate_total(items: list[Item]) -> float:
total = 0.0
for item in items:
total += item.price
return total
# Bad - tabs
def calculate_total(items: list[Item]) -> float:
total = 0.0 # This is a tabMaximum line length is 88 characters (Black formatter default).
# Good - line within limit
user = await user_repository.find_by_email(email)
# Good - break long lines
user = await user_repository.find_by_email_with_preferences(
email=email,
include_inactive=False,
load_preferences=True,
)
# Good - break long strings
error_message = (
f"Failed to process user {user_id}: "
f"Invalid email format '{email}'"
)
# Good - break long conditions
if (
user.is_active
and user.has_permission("edit")
and project.status == "active"
):
allow_edit()- Two blank lines around top-level function and class definitions
- One blank line between method definitions in a class
- Use blank lines sparingly inside functions to indicate logical sections
import os
from typing import Optional
class UserService:
"""Service for user operations."""
def __init__(self, repository: UserRepository) -> None:
self.repository = repository
def get_user(self, user_id: str) -> Optional[User]:
"""Get user by ID."""
return self.repository.find_by_id(user_id)
def create_user(self, data: CreateUserInput) -> User:
"""Create a new user."""
# Validate email uniqueness
existing = self.repository.find_by_email(data.email)
if existing:
raise UserAlreadyExistsError(data.email)
# Create and return user
return self.repository.create(data)
def helper_function() -> None:
"""A helper function outside the class."""
passOrganize imports in three groups, separated by blank lines:
- Standard library imports
- Third-party imports
- Local imports
# Standard library
import os
import sys
from datetime import datetime
from typing import Optional, List
# Third-party
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
# Local imports
from app.config import settings
from app.models.user import User
from app.services.user_service import UserService
# Avoid wildcard imports
# Bad
from app.models import *
# Good
from app.models import User, Project, TaskUse isort or similar tool. Order alphabetically within groups.
# Sorted alphabetically
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, Field
from sqlalchemy import Column, String
from sqlalchemy.orm import Session
from app.config import settings
from app.database import get_db
from app.models import UserUse snake_case for variables, functions, and methods.
# Good
user_name = "John"
total_count = 0
is_active = True
def get_user_by_email(email: str) -> User:
pass
def calculate_total_price(items: list[Item]) -> float:
pass
# Bad
userName = "John" # camelCase
TotalCount = 0 # PascalCase
IsActive = True # PascalCase
def getUserByEmail(email: str) -> User: # camelCase
passUse PascalCase for class names.
# Good
class UserService:
pass
class HTTPClientError(Exception):
pass
class DatabaseConnectionPool:
pass
# Bad
class user_service: # snake_case
pass
class httpClientError: # camelCase
passUse UPPER_SNAKE_CASE for constants.
# Good
MAX_RETRY_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
API_BASE_URL = "https://api.example.com"
DATABASE_URL = os.getenv("DATABASE_URL")
# Bad
maxRetryAttempts = 3
default_timeout = 30
ApiBaseUrl = "https://api.example.com"Use a single leading underscore for internal/private members.
class UserService:
def __init__(self) -> None:
self._cache: dict[str, User] = {} # Internal cache
self._repository = UserRepository()
def _validate_email(self, email: str) -> bool:
"""Internal validation method."""
return "@" in email
def create_user(self, data: CreateUserInput) -> User:
"""Public method."""
if not self._validate_email(data.email):
raise ValidationError("Invalid email")
return self._repository.create(data)
# Double underscore for name mangling (rarely needed)
class Base:
def __init__(self) -> None:
self.__private = "Cannot be easily overridden"Use short, lowercase names. Avoid underscores if possible.
# Good
app/
├── models/
│ ├── __init__.py
│ ├── user.py
│ └── project.py
├── services/
│ ├── __init__.py
│ └── user_service.py # Underscore OK when needed for clarity
├── routers/
│ └── users.py
└── utils/
└── helpers.py
# Bad
app/
├── Models/ # PascalCase
├── UserServices/ # PascalCase
└── helper_utils/ # Redundant
# Standard module structure
"""
Module docstring explaining the purpose.
This module provides user-related functionality.
"""
# Imports (grouped as specified above)
from typing import Optional
from fastapi import HTTPException
from app.models import User
# Constants
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
# Type definitions
UserDict = dict[str, Any]
# Classes
class UserService:
"""Service for user operations."""
pass
# Functions
def format_user(user: User) -> UserDict:
"""Format user for API response."""
pass
# Private functions
def _validate_input(data: dict) -> bool:
"""Internal validation helper."""
passclass UserService:
"""
Service for user operations.
Provides methods for creating, reading, updating,
and deleting users.
"""
# Class constants
MAX_LOGIN_ATTEMPTS = 5
# Class initialization
def __init__(self, repository: UserRepository) -> None:
self._repository = repository
self._cache: dict[str, User] = {}
# Properties
@property
def cache_size(self) -> int:
"""Return current cache size."""
return len(self._cache)
# Public methods
def get_user(self, user_id: str) -> Optional[User]:
"""Get user by ID."""
pass
def create_user(self, data: CreateUserInput) -> User:
"""Create a new user."""
pass
# Private methods
def _validate_email(self, email: str) -> bool:
"""Validate email format."""
pass
# Special methods
def __repr__(self) -> str:
return f"UserService(cache_size={self.cache_size})"Use Google-style docstrings.
def calculate_discount(
price: float,
discount_percent: float,
max_discount: Optional[float] = None,
) -> float:
"""
Calculate the discounted price.
Applies a percentage discount to the given price, optionally
capping the maximum discount amount.
Args:
price: Original price in dollars.
discount_percent: Discount percentage (0-100).
max_discount: Maximum discount amount. If None, no cap is applied.
Returns:
The discounted price.
Raises:
ValueError: If price is negative or discount_percent is out of range.
Example:
>>> calculate_discount(100.0, 20.0)
80.0
>>> calculate_discount(100.0, 50.0, max_discount=30.0)
70.0
"""
if price < 0:
raise ValueError("Price cannot be negative")
if not 0 <= discount_percent <= 100:
raise ValueError("Discount percent must be between 0 and 100")
discount = price * (discount_percent / 100)
if max_discount is not None:
discount = min(discount, max_discount)
return price - discountclass UserRepository:
"""
Repository for user database operations.
Provides methods for CRUD operations on users in the database.
Uses SQLAlchemy for database interactions.
Attributes:
session: SQLAlchemy database session.
Example:
>>> repo = UserRepository(session)
>>> user = repo.find_by_id("user-123")
>>> print(user.name)
"""
def __init__(self, session: Session) -> None:
"""
Initialize the repository.
Args:
session: SQLAlchemy database session.
"""
self.session = session# exceptions.py
class AppError(Exception):
"""Base exception for application errors."""
def __init__(self, message: str, code: str = "APP_ERROR") -> None:
self.message = message
self.code = code
super().__init__(message)
class NotFoundError(AppError):
"""Raised when a resource is not found."""
def __init__(self, resource: str, identifier: str) -> None:
super().__init__(
message=f"{resource} not found: {identifier}",
code="NOT_FOUND",
)
class ValidationError(AppError):
"""Raised when validation fails."""
def __init__(self, message: str, field: Optional[str] = None) -> None:
super().__init__(message=message, code="VALIDATION_ERROR")
self.field = field
class AuthorizationError(AppError):
"""Raised when authorization fails."""
def __init__(self, message: str = "Access denied") -> None:
super().__init__(message=message, code="UNAUTHORIZED")# Good - specific exception handling
async def get_user(user_id: str) -> User:
try:
user = await repository.find_by_id(user_id)
if not user:
raise NotFoundError("User", user_id)
return user
except DatabaseError as e:
logger.error(f"Database error: {e}")
raise AppError("Failed to retrieve user") from e
# Good - context manager for resources
async def process_file(path: str) -> dict:
async with aiofiles.open(path) as f:
content = await f.read()
return parse_content(content)
# Bad - bare except
try:
result = process_data(data)
except: # Never do this
pass
# Bad - catching Exception without re-raising
try:
result = process_data(data)
except Exception:
return None # Silently swallowing errors# File handling
async with aiofiles.open("data.json") as f:
data = await f.read()
# Database sessions
async with async_session() as session:
user = await session.get(User, user_id)
# Custom context manager
from contextlib import asynccontextmanager
@asynccontextmanager
async def database_transaction():
session = async_session()
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()# Good - generator for large data
def read_large_file(path: str):
"""Read large file line by line."""
with open(path) as f:
for line in f:
yield line.strip()
# Good - async generator
async def fetch_all_users():
"""Fetch users in batches."""
offset = 0
batch_size = 100
while True:
users = await repository.find_all(offset=offset, limit=batch_size)
if not users:
break
for user in users:
yield user
offset += batch_size
# Usage
async for user in fetch_all_users():
process_user(user)from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
@dataclass
class User:
"""User data structure."""
id: str
email: str
name: str
created_at: datetime = field(default_factory=datetime.utcnow)
is_active: bool = True
role: str = "user"
@dataclass(frozen=True)
class Point:
"""Immutable point."""
x: float
y: float
# Usage
user = User(id="1", email="john@example.com", name="John")
point = Point(x=1.0, y=2.0)# Bad - mutable default
def add_item(item: str, items: list = []) -> list: # Bug!
items.append(item)
return items
# Good - use None and create inside
def add_item(item: str, items: Optional[list] = None) -> list:
if items is None:
items = []
items.append(item)
return items
# Good - use default_factory with dataclass
@dataclass
class Config:
items: list = field(default_factory=list)| Category | Convention |
|---|---|
| Indentation | 4 spaces |
| Line Length | 88 characters |
| Variables/Functions | snake_case |
| Classes | PascalCase |
| Constants | UPPER_SNAKE_CASE |
| Private | _single_leading_underscore |
| Imports | Grouped and sorted |
| Docstrings | Google style |