# CLAUDE.md This file provides comprehensive guidance to Claude Code when working with Python code in this repository. ## Core Development Philosophy ### KISS (Keep It Simple, Stupid) Simplicity should be a key goal in design. Choose straightforward solutions over complex ones whenever possible. Simple solutions are easier to understand, maintain, and debug. ### YAGNI (You Aren't Gonna Need It) Avoid building functionality on speculation. Implement features only when they are needed, not when you anticipate they might be useful in the future. ### Design Principles - **Dependency Inversion**: High-level modules should not depend on low-level modules. Both should depend on abstractions. - **Open/Closed Principle**: Software entities should be open for extension but closed for modification. - **Single Responsibility**: Each function, class, and module should have one clear purpose. - **Fail Fast**: Check for potential errors early and raise exceptions immediately when issues occur. ## ๐Ÿงฑ Code Structure & Modularity ### File and Function Limits - **Never create a file longer than 500 lines of code**. If approaching this limit, refactor by splitting into modules. - **Functions should be under 50 lines** with a single, clear responsibility. - **Classes should be under 100 lines** and represent a single concept or entity. - **Organize code into clearly separated modules**, grouped by feature or responsibility. - **Line lenght should be max 100 characters** ruff rule in pyproject.toml - **Use venv_linux** (the virtual environment) whenever executing Python commands, including for unit tests. ### Project Architecture Follow strict vertical slice architecture with tests living next to the code they test: ``` src/project/ __init__.py main.py tests/ test_main.py conftest.py # Core modules database/ __init__.py connection.py models.py tests/ test_connection.py test_models.py auth/ __init__.py authentication.py authorization.py tests/ test_authentication.py test_authorization.py # Feature slices features/ user_management/ __init__.py handlers.py validators.py tests/ test_handlers.py test_validators.py payment_processing/ __init__.py processor.py gateway.py tests/ test_processor.py test_gateway.py ``` ## ๐Ÿ› ๏ธ Development Environment ### UV Package Management This project uses UV for blazing-fast Python package and environment management. ```bash # Install UV (if not already installed) curl -LsSf https://astral.sh/uv/install.sh | sh # Create virtual environment uv venv # Sync dependencies uv sync # Add a package ***NEVER UPDATE A DEPENDENCY DIRECTLY IN PYPROJECT.toml*** # ALWAYS USE UV ADD uv add requests # Add development dependency uv add --dev pytest ruff mypy # Remove a package uv remove requests # Run commands in the environment uv run python script.py uv run pytest uv run ruff check . # Install specific Python version uv python install 3.12 ``` ### Development Commands ```bash # Run all tests uv run pytest # Run specific tests with verbose output uv run pytest tests/test_module.py -v # Run tests with coverage uv run pytest --cov=src --cov-report=html # Format code uv run ruff format . # Check linting uv run ruff check . # Fix linting issues automatically uv run ruff check --fix . # Type checking uv run mypy src/ # Run pre-commit hooks uv run pre-commit run --all-files ``` ## ๐Ÿ“‹ Style & Conventions ### Python Style Guide - **Follow PEP8** with these specific choices: - Line length: 100 characters (set by Ruff in pyproject.toml) - Use double quotes for strings - Use trailing commas in multi-line structures - **Always use type hints** for function signatures and class attributes - **Format with `ruff format`** (faster alternative to Black) - **Use `pydantic` v2** for data validation and settings management ### Docstring Standards Use Google-style docstrings for all public functions, classes, and modules: ```python def calculate_discount( price: Decimal, discount_percent: float, min_amount: Decimal = Decimal("0.01") ) -> Decimal: """ Calculate the discounted price for a product. Args: price: Original price of the product discount_percent: Discount percentage (0-100) min_amount: Minimum allowed final price Returns: Final price after applying discount Raises: ValueError: If discount_percent is not between 0 and 100 ValueError: If final price would be below min_amount Example: >>> calculate_discount(Decimal("100"), 20) Decimal('80.00') """ ``` ### Naming Conventions - **Variables and functions**: `snake_case` - **Classes**: `PascalCase` - **Constants**: `UPPER_SNAKE_CASE` - **Private attributes/methods**: `_leading_underscore` - **Type aliases**: `PascalCase` - **Enum values**: `UPPER_SNAKE_CASE` ## ๐Ÿงช Testing Strategy ### Test-Driven Development (TDD) 1. **Write the test first** - Define expected behavior before implementation 2. **Watch it fail** - Ensure the test actually tests something 3. **Write minimal code** - Just enough to make the test pass 4. **Refactor** - Improve code while keeping tests green 5. **Repeat** - One test at a time ### Testing Best Practices ```python # Always use pytest fixtures for setup import pytest from datetime import datetime @pytest.fixture def sample_user(): """Provide a sample user for testing.""" return User( id=123, name="Test User", email="test@example.com", created_at=datetime.now() ) # Use descriptive test names def test_user_can_update_email_when_valid(sample_user): """Test that users can update their email with valid input.""" new_email = "newemail@example.com" sample_user.update_email(new_email) assert sample_user.email == new_email # Test edge cases and error conditions def test_user_update_email_fails_with_invalid_format(sample_user): """Test that invalid email formats are rejected.""" with pytest.raises(ValidationError) as exc_info: sample_user.update_email("not-an-email") assert "Invalid email format" in str(exc_info.value) ``` ### Test Organization - Unit tests: Test individual functions/methods in isolation - Integration tests: Test component interactions - End-to-end tests: Test complete user workflows - Keep test files next to the code they test - Use `conftest.py` for shared fixtures - Aim for 80%+ code coverage, but focus on critical paths ## ๐Ÿšจ Error Handling ### Exception Best Practices ```python # Create custom exceptions for your domain class PaymentError(Exception): """Base exception for payment-related errors.""" pass class InsufficientFundsError(PaymentError): """Raised when account has insufficient funds.""" def __init__(self, required: Decimal, available: Decimal): self.required = required self.available = available super().__init__( f"Insufficient funds: required {required}, available {available}" ) # Use specific exception handling try: process_payment(amount) except InsufficientFundsError as e: logger.warning(f"Payment failed: {e}") return PaymentResult(success=False, reason="insufficient_funds") except PaymentError as e: logger.error(f"Payment error: {e}") return PaymentResult(success=False, reason="payment_error") # Use context managers for resource management from contextlib import contextmanager @contextmanager def database_transaction(): """Provide a transactional scope for database operations.""" conn = get_connection() trans = conn.begin_transaction() try: yield conn trans.commit() except Exception: trans.rollback() raise finally: conn.close() ``` ### Logging Strategy ```python import logging from functools import wraps # Configure structured logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Log function entry/exit for debugging def log_execution(func): @wraps(func) def wrapper(*args, **kwargs): logger.debug(f"Entering {func.__name__}") try: result = func(*args, **kwargs) logger.debug(f"Exiting {func.__name__} successfully") return result except Exception as e: logger.exception(f"Error in {func.__name__}: {e}") raise return wrapper ``` ## ๐Ÿ”ง Configuration Management ### Environment Variables and Settings ```python from pydantic_settings import BaseSettings from functools import lru_cache class Settings(BaseSettings): """Application settings with validation.""" app_name: str = "MyApp" debug: bool = False database_url: str redis_url: str = "redis://localhost:6379" api_key: str max_connections: int = 100 class Config: env_file = ".env" env_file_encoding = "utf-8" case_sensitive = False @lru_cache() def get_settings() -> Settings: """Get cached settings instance.""" return Settings() # Usage settings = get_settings() ``` ## ๐Ÿ—๏ธ Data Models and Validation ### Example Pydantic Models strict with pydantic v2 ```python from pydantic import BaseModel, Field, validator, EmailStr from datetime import datetime from typing import Optional, List from decimal import Decimal class ProductBase(BaseModel): """Base product model with common fields.""" name: str = Field(..., min_length=1, max_length=255) description: Optional[str] = None price: Decimal = Field(..., gt=0, decimal_places=2) category: str tags: List[str] = [] @validator('price') def validate_price(cls, v): if v > Decimal('1000000'): raise ValueError('Price cannot exceed 1,000,000') return v class Config: json_encoders = { Decimal: str, datetime: lambda v: v.isoformat() } class ProductCreate(ProductBase): """Model for creating new products.""" pass class ProductUpdate(BaseModel): """Model for updating products - all fields optional.""" name: Optional[str] = Field(None, min_length=1, max_length=255) description: Optional[str] = None price: Optional[Decimal] = Field(None, gt=0, decimal_places=2) category: Optional[str] = None tags: Optional[List[str]] = None class Product(ProductBase): """Complete product model with database fields.""" id: int created_at: datetime updated_at: datetime is_active: bool = True class Config: from_attributes = True # Enable ORM mode ``` ## ๐Ÿ”„ Git Workflow ### Branch Strategy - `main` - Production-ready code - `develop` - Integration branch for features - `feature/*` - New features - `fix/*` - Bug fixes - `docs/*` - Documentation updates - `refactor/*` - Code refactoring - `test/*` - Test additions or fixes ### Commit Message Format Never include claude code, or written by claude code in commit messages ``` ():