Python Development Guidelines
Purpose
Establish consistency and best practices for Python development, covering modern Python 3.12+ patterns, type safety, testing, and project organization.
Standards Overview
| 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 |
When to Use This Skill
Automatically activates when working on:
- Creating or modifying Python files (
.py)
- Writing classes, functions, or modules
- Setting up Python projects (pyproject.toml, setup.py)
- Writing tests with pytest
- Working with type hints and mypy
- Async/await patterns with FastAPI
- Package management (pip, poetry, conda)
Quick Start
New Python Project Checklist
New Module Checklist
Project Structure
Recommended Layout (src-layout)
project/
├── 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+
Alternative: Flat Layout (smaller projects)
project/
├── mypackage/
│ ├── __init__.py
│ └── module.py
├── tests/
│ └── test_module.py
├── pyproject.toml
└── README.md
Core Principles (7 Key Rules)
1. Type Everything Public (Strict Typing)
python
1from __future__ import annotations # Always at top of file
2
3# ❌ NEVER: Untyped public functions
4def process_data(data):
5 return data.upper()
6
7# ✅ ALWAYS: Full type annotations
8def process_data(data: str) -> str:
9 """Process input data.
10
11 Args:
12 data: The input string to process.
13
14 Returns:
15 The processed uppercase string.
16 """
17 return data.upper()
2. Use Pydantic v2 for Data Models (Preferred)
python
1from __future__ import annotations
2from pydantic import BaseModel, EmailStr, Field
3
4# ✅ Pydantic v2 for validation (preferred)
5class UserCreate(BaseModel):
6 """User creation model with validation."""
7
8 name: str = Field(..., min_length=1, max_length=100)
9 email: EmailStr
10
11class UserResponse(BaseModel):
12 """User response model."""
13
14 id: int
15 name: str
16 email: str
17
18# For simple internal data without validation, dataclasses are acceptable
19from dataclasses import dataclass
20
21@dataclass
22class InternalConfig:
23 timeout: int = 30
24 retries: int = 3
3. Handle Errors with Typed Hierarchical Exceptions
python
1# exceptions.py - Define hierarchical typed exceptions
2from __future__ import annotations
3
4
5class AppError(Exception):
6 """Base exception for application errors."""
7
8 def __init__(self, message: str) -> None:
9 self.message = message
10 super().__init__(message)
11
12
13class ValidationError(AppError):
14 """Raised when validation fails."""
15
16 pass
17
18
19class NotFoundError(AppError):
20 """Raised when resource not found."""
21
22 pass
23
24
25class DatabaseError(AppError):
26 """Raised when database operation fails."""
27
28 pass
29
30
31# Usage - catch specific exceptions, not general Exception
32from mypackage.exceptions import ValidationError, NotFoundError
33
34async def get_user(user_id: int) -> User:
35 """Get user by ID.
36
37 Args:
38 user_id: The user's unique identifier.
39
40 Returns:
41 The user object.
42
43 Raises:
44 NotFoundError: If user does not exist.
45 """
46 user = await db.find(user_id)
47 if not user:
48 raise NotFoundError(f"User {user_id} not found")
49 return user
4. Use Context Managers for Resources
python
1from __future__ import annotations
2from contextlib import asynccontextmanager, contextmanager
3
4# ❌ NEVER: Manual resource management
5f = open("file.txt")
6data = f.read()
7f.close()
8
9# ✅ ALWAYS: Context managers
10with open("file.txt") as f:
11 data = f.read()
12
13# ✅ Sync context manager
14@contextmanager
15def database_transaction():
16 """Manage database transaction with automatic cleanup."""
17 conn = get_connection()
18 try:
19 yield conn
20 conn.commit()
21 except Exception:
22 conn.rollback()
23 raise
24 finally:
25 conn.close()
26
27# ✅ Async - use try/finally to ensure cleanup
28@asynccontextmanager
29async def async_db_session():
30 """Manage async database session with cleanup."""
31 session = await create_session()
32 try:
33 yield session
34 await session.commit()
35 except Exception:
36 await session.rollback()
37 raise
38 finally:
39 await session.close()
5. Prefer Composition Over Inheritance
python
1# ❌ Avoid deep inheritance
2class Animal: ...
3class Mammal(Animal): ...
4class Dog(Mammal): ...
5
6# ✅ Prefer composition and protocols
7from typing import Protocol
8
9class Walker(Protocol):
10 def walk(self) -> None: ...
11
12class Dog:
13 def __init__(self, legs: int = 4):
14 self.legs = legs
15
16 def walk(self) -> None:
17 print(f"Walking on {self.legs} legs")
6. Use Logging, Not Print
python
1import logging
2
3logger = logging.getLogger(__name__)
4
5# ❌ NEVER
6print(f"Processing {item}")
7
8# ✅ ALWAYS
9logger.info("Processing %s", item)
10logger.error("Failed to process", exc_info=True)
7. Write Testable Code
python
1# ❌ Hard to test: hidden dependencies
2def send_email(user_id: int) -> None:
3 user = database.get_user(user_id) # Hidden dependency
4 smtp.send(user.email, "Hello") # Hidden dependency
5
6# ✅ Easy to test: explicit dependencies
7def send_email(
8 user: User,
9 email_sender: EmailSender
10) -> None:
11 email_sender.send(user.email, "Hello")
Type Hints Quick Reference
python
1from __future__ import annotations
2from typing import TypeVar, Generic
3from collections.abc import Callable
4
5# Basic types (Python 3.12+)
6values: list[int] = [1, 2, 3]
7mapping: dict[str, int] = {"a": 1}
8value: str | None = None # Union syntax
9
10# Callable and Generics
11handler: Callable[[int, str], bool]
12T = TypeVar("T")
13
14class Container(Generic[T]):
15 def __init__(self, value: T) -> None:
16 self.value = value
Testing Patterns
python
1# tests/test_user_service.py - mirrors src/mypackage/services/user_service.py
2from __future__ import annotations
3import pytest
4from mypackage.services import UserService
5from mypackage.exceptions import ValidationError
6
7class TestUserService:
8 """Tests for UserService."""
9
10 def test_create_user_success(self, mock_database):
11 """Should create user with valid data."""
12 service = UserService(mock_database)
13 user = service.create(name="Test", email="test@example.com")
14 assert user.name == "Test"
15
16 def test_create_user_invalid_email_raises(self, mock_database):
17 """Should raise ValidationError for invalid email."""
18 service = UserService(mock_database)
19 with pytest.raises(ValidationError, match="invalid email"):
20 service.create(name="Test", email="not-an-email")
Anti-Patterns to Avoid
❌ Mutable default arguments (def foo(items=[]))
❌ Bare except: clauses - catch specific exceptions
❌ Catching general Exception - use typed exceptions
❌ from module import *
❌ Global mutable state
❌ Ignoring type checker errors
❌ print() instead of logging
❌ String concatenation in loops (use join)
❌ Not using if __name__ == "__main__":
❌ Missing from __future__ import annotations
Async Patterns (FastAPI Preferred)
python
1from __future__ import annotations
2from fastapi import FastAPI, HTTPException, status
3from mypackage.exceptions import ValidationError
4
5app = FastAPI()
6
7# ✅ FastAPI endpoint with proper error handling
8@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
9async def create_user(user: UserCreate) -> UserResponse:
10 """Create a new user."""
11 try:
12 return await user_service.create(user)
13 except ValidationError as e:
14 raise HTTPException(status_code=400, detail=str(e)) from e
15
16# ✅ Async error handling with exception chaining (from e)
17async def process_data(data: dict) -> Result:
18 """Process data with proper exception handling."""
19 try:
20 return await do_processing(data)
21 except KeyError as e:
22 raise ValidationError(f"Missing required field: {e}") from e
23
24# ✅ Concurrent operations
25async def process_items(items: list[str]) -> list[dict]:
26 return await asyncio.gather(*[fetch_data(item) for item in items])
Resource Files
Google Python Style Guide + PEP 8 practices, naming, docstrings, imports
<!-- ### [project-setup.md](resources/project-setup.md)
pyproject.toml, poetry, pip, virtual environments
### [typing-guide.md](resources/typing-guide.md)
Advanced type hints, generics, protocols, mypy configuration
### [testing-patterns.md](resources/testing-patterns.md)
pytest fixtures, mocking, parameterization, coverage
### [async-patterns.md](resources/async-patterns.md)
asyncio, aiohttp, async context managers
### [packaging.md](resources/packaging.md)
Building packages, publishing to PyPI, versioning -->
- cpp-dev-guidelines - C++ development patterns
- error-tracking - Sentry integration for Python
- skill-developer - Creating and managing skills
Skill Status: COMPLETE ✅
Line Count: < 450 ✅
Progressive Disclosure: Resource files for details ✅