| name | python-standards |
| description | Python coding standards, conventions, and best practices. Use when writing, reviewing, or testing Python code. |
Python Coding Standards
New Project Preferences
When starting new Python projects, prefer:
- uv for package/environment management (fast, modern alternative to pip/venv)
- FastAPI for web APIs
- Pydantic for data validation and serialization
Style Guide
Follow PEP 8 with these project-specific additions:
Formatting
- Line length: 88 characters (Black default)
- Use Black for formatting, isort for imports
- Use double quotes for strings (Black default)
Naming Conventions
# Modules and packages: lowercase_with_underscores
user_service.py
# Classes: PascalCase
class UserService:
pass
# Functions and variables: snake_case
def get_user_by_id(user_id: int) -> User:
active_users = []
# Constants: SCREAMING_SNAKE_CASE
MAX_RETRY_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
# Private: single leading underscore
def _internal_helper():
pass
# "Private" (name mangling): double leading underscore (rare)
class Base:
def __private_method(self):
pass
Imports
# Order: stdlib, third-party, local (isort handles this)
import os
import sys
from pathlib import Path
import requests
from pydantic import BaseModel
from app.models import User
from app.services import UserService
Type Hints
Always use type hints for function signatures:
from typing import Optional, List, Dict, Any, Union
from collections.abc import Sequence, Mapping
def process_users(
users: List[User],
filter_active: bool = True,
metadata: Optional[Dict[str, Any]] = None,
) -> List[ProcessedUser]:
"""Process a list of users with optional filtering."""
...
# Use | for unions (Python 3.10+)
def get_value(key: str) -> str | None:
...
Docstrings
Use Google-style docstrings:
def fetch_user(user_id: int, include_deleted: bool = False) -> User:
"""Fetch a user by their ID.
Args:
user_id: The unique identifier of the user.
include_deleted: Whether to include soft-deleted users.
Returns:
The User object if found.
Raises:
UserNotFoundError: If no user exists with the given ID.
DatabaseConnectionError: If the database is unavailable.
"""
...
Error Handling
# Be specific with exceptions
try:
user = await fetch_user(user_id)
except UserNotFoundError:
logger.warning(f"User {user_id} not found")
raise HTTPException(status_code=404, detail="User not found")
except DatabaseConnectionError as e:
logger.error(f"Database error: {e}")
raise HTTPException(status_code=503, detail="Service unavailable")
# Create custom exceptions for domain errors
class UserNotFoundError(Exception):
"""Raised when a user cannot be found."""
def __init__(self, user_id: int):
self.user_id = user_id
super().__init__(f"User with ID {user_id} not found")
Classes and Data
# Prefer dataclasses for simple data containers
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
id: int
email: str
name: str
created_at: datetime = field(default_factory=datetime.utcnow)
is_active: bool = True
# Use Pydantic for validation and serialization
from pydantic import BaseModel, EmailStr, validator
class UserCreate(BaseModel):
email: EmailStr
name: str
@validator('name')
def name_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('Name cannot be empty')
return v.strip()
Async Code
# Use async/await consistently
async def get_user_with_posts(user_id: int) -> UserWithPosts:
async with get_db_session() as session:
user = await session.get(User, user_id)
posts = await session.execute(
select(Post).where(Post.user_id == user_id)
)
return UserWithPosts(user=user, posts=posts.scalars().all())
# Use asyncio.gather for concurrent operations
async def fetch_all_data(user_id: int) -> tuple[User, list[Post], Settings]:
user, posts, settings = await asyncio.gather(
fetch_user(user_id),
fetch_posts(user_id),
fetch_settings(user_id),
)
return user, posts, settings
Testing
# Use pytest with fixtures
import pytest
from unittest.mock import Mock, patch, AsyncMock
@pytest.fixture
def mock_user():
return User(id=1, email="test@example.com", name="Test User")
@pytest.fixture
def mock_db_session():
with patch('app.database.get_session') as mock:
yield mock
# Async tests
@pytest.mark.asyncio
async def test_fetch_user(mock_db_session, mock_user):
mock_db_session.return_value.get = AsyncMock(return_value=mock_user)
result = await fetch_user(1)
assert result.id == 1
assert result.email == "test@example.com"
# Parameterized tests
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("World", "WORLD"),
("", ""),
])
def test_uppercase(input, expected):
assert uppercase(input) == expected
Project Commands
Check .claude/commands.md for project-specific commands. Common Python commands:
# Virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Formatting
black .
isort .
# Linting
ruff check .
mypy .
# Testing
pytest
pytest --cov=app --cov-report=html
pytest -x -v # stop on first failure, verbose
Common Patterns
Context Managers
from contextlib import contextmanager, asynccontextmanager
@contextmanager
def temporary_file(suffix: str = ".tmp"):
path = Path(tempfile.mktemp(suffix=suffix))
try:
yield path
finally:
path.unlink(missing_ok=True)
@asynccontextmanager
async def get_db_connection():
conn = await create_connection()
try:
yield conn
finally:
await conn.close()
Logging
import logging
logger = logging.getLogger(__name__)
def process_order(order_id: int) -> None:
logger.info(f"Processing order {order_id}")
try:
# ... processing
logger.debug(f"Order {order_id} validated")
except ValidationError as e:
logger.warning(f"Order {order_id} validation failed: {e}")
raise
except Exception as e:
logger.exception(f"Unexpected error processing order {order_id}")
raise