Python development guidelines for modern Python projects. Use when creating Python modules, classes, functions, scripts, or working with type hints, pytest, packaging (pip/poetry), virtual environments, async/await, data classes, or Python best practices. Covers project structure, typing, testing patterns, error handling, logging, and Pythonic idioms.
Establish consistency and best practices for Python development, covering modern Python 3.12+ patterns, type safety, testing, and project organization.
| Category | Standard |
|---|---|
| Python | 3.12+, FastAPI, async/await preferred |
| Formatting | ruff (96-char lines, double quotes, sorted imports) |
| Typing | Strict (Pydantic v2 models preferred); from __future__ import annotations |
| Naming | snake_case (functions/variables), PascalCase (classes), SCREAMING_SNAKE (constants) |
| Error Handling | Typed exceptions; context managers for resources |
| Documentation | Google-style docstrings for public functions/classes |
| Testing | Separate test files matching source file patterns |
Automatically activates when working on:
.py).python-versionfrom __future__ import annotationsexceptions.pyfrom __future__ import annotations at top__all__ export list (if applicable)tests/ mirror structureexceptions.pyproject/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── exceptions.py # Hierarchical typed exceptions
│ ├── core/
│ │ ├── __init__.py
│ │ └── module.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── helpers.py
│ └── py.typed # PEP 561 marker
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_core/
│ │ └── test_module.py # Mirrors src/mypackage/core/module.py
│ └── test_utils/
│ └── test_helpers.py # Mirrors src/mypackage/utils/helpers.py
├── pyproject.toml
├── ruff.toml # ruff config (96-char, double quotes)
├── README.md
└── .python-version # 3.12+
project/
├── mypackage/
│ ├── __init__.py
│ └── module.py
├── tests/
│ └── test_module.py
├── pyproject.toml
└── README.md
from __future__ import annotations # Always at top of file
# ❌ NEVER: Untyped public functions
def process_data(data):
return data.upper()
# ✅ ALWAYS: Full type annotations
def process_data(data: str) -> str:
"""Process input data.
Args:
data: The input string to process.
Returns:
The processed uppercase string.
"""
return data.upper()
from __future__ import annotations
from pydantic import BaseModel, EmailStr, Field
# ✅ Pydantic v2 for validation (preferred)
class UserCreate(BaseModel):
"""User creation model with validation."""
name: str = Field(..., min_length=1, max_length=100)
email: EmailStr
class UserResponse(BaseModel):
"""User response model."""
id: int
name: str
email: str
# For simple internal data without validation, dataclasses are acceptable
from dataclasses import dataclass
@dataclass
class InternalConfig:
timeout: int = 30
retries: int = 3
# exceptions.py - Define hierarchical typed exceptions
from __future__ import annotations
class AppError(Exception):
"""Base exception for application errors."""
def __init__(self, message: str) -> None:
self.message = message
super().__init__(message)
class ValidationError(AppError):
"""Raised when validation fails."""
pass
class NotFoundError(AppError):
"""Raised when resource not found."""
pass
class DatabaseError(AppError):
"""Raised when database operation fails."""
pass
# Usage - catch specific exceptions, not general Exception
from mypackage.exceptions import ValidationError, NotFoundError
async def get_user(user_id: int) -> User:
"""Get user by ID.
Args:
user_id: The user's unique identifier.
Returns:
The user object.
Raises:
NotFoundError: If user does not exist.
"""
user = await db.find(user_id)
if not user:
raise NotFoundError(f"User {user_id} not found")
return user
from __future__ import annotations
from contextlib import asynccontextmanager, contextmanager
# ❌ NEVER: Manual resource management
f = open("file.txt")
data = f.read()
f.close()
# ✅ ALWAYS: Context managers
with open("file.txt") as f:
data = f.read()
# ✅ Sync context manager
@contextmanager
def database_transaction():
"""Manage database transaction with automatic cleanup."""
conn = get_connection()
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
# ✅ Async - use try/finally to ensure cleanup
@asynccontextmanager
async def async_db_session():
"""Manage async database session with cleanup."""
session = await create_session()
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
# ❌ Avoid deep inheritance
class Animal: ...
class Mammal(Animal): ...
class Dog(Mammal): ...
# ✅ Prefer composition and protocols
from typing import Protocol
class Walker(Protocol):
def walk(self) -> None: ...
class Dog:
def __init__(self, legs: int = 4):
self.legs = legs
def walk(self) -> None:
print(f"Walking on {self.legs} legs")
import logging
logger = logging.getLogger(__name__)
# ❌ NEVER
print(f"Processing {item}")
# ✅ ALWAYS
logger.info("Processing %s", item)
logger.error("Failed to process", exc_info=True)
# ❌ Hard to test: hidden dependencies
def send_email(user_id: int) -> None:
user = database.get_user(user_id) # Hidden dependency
smtp.send(user.email, "Hello") # Hidden dependency
# ✅ Easy to test: explicit dependencies
def send_email(
user: User,
email_sender: EmailSender
) -> None:
email_sender.send(user.email, "Hello")
from __future__ import annotations
from typing import TypeVar, Generic
from collections.abc import Callable
# Basic types (Python 3.12+)