2025-08-05 07:10:34 -05:00

20 KiB

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.

# 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

# 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:

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

# 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

# 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

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

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

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

<type>(<scope>): <subject>

<body>

<footer>
``
Types: feat, fix, docs, style, refactor, test, chore

Example:

feat(auth): add two-factor authentication

  • Implement TOTP generation and validation
  • Add QR code generation for authenticator apps
  • Update user model with 2FA fields

Closes #123


## 🗄️ Database Naming Standards

### Entity-Specific Primary Keys
All database tables use entity-specific primary keys for clarity and consistency:

```sql
-- ✅ STANDARDIZED: Entity-specific primary keys
sessions.session_id UUID PRIMARY KEY
leads.lead_id UUID PRIMARY KEY
messages.message_id UUID PRIMARY KEY
daily_metrics.daily_metric_id UUID PRIMARY KEY
agencies.agency_id UUID PRIMARY KEY

Field Naming Conventions

-- Primary keys: {entity}_id
session_id, lead_id, message_id

-- Foreign keys: {referenced_entity}_id
session_id REFERENCES sessions(session_id)
agency_id REFERENCES agencies(agency_id)

-- Timestamps: {action}_at
created_at, updated_at, started_at, expires_at

-- Booleans: is_{state}
is_connected, is_active, is_qualified

-- Counts: {entity}_count
message_count, lead_count, notification_count

-- Durations: {property}_{unit}
duration_seconds, timeout_minutes

Repository Pattern Auto-Derivation

The enhanced BaseRepository automatically derives table names and primary keys:

# ✅ STANDARDIZED: Convention-based repositories
class LeadRepository(BaseRepository[Lead]):
    def __init__(self):
        super().__init__()  # Auto-derives "leads" and "lead_id"

class SessionRepository(BaseRepository[AvatarSession]):
    def __init__(self):
        super().__init__()  # Auto-derives "sessions" and "session_id"

Benefits:

  • Self-documenting schema
  • Clear foreign key relationships
  • Eliminates repository method overrides
  • Consistent with entity naming patterns

Model-Database Alignment

Models mirror database fields exactly to eliminate field mapping complexity:

# ✅ STANDARDIZED: Models mirror database exactly
class Lead(BaseModel):
    lead_id: UUID = Field(default_factory=uuid4)  # Matches database field
    session_id: UUID                               # Matches database field
    agency_id: str                                 # Matches database field
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(
        use_enum_values=True,
        populate_by_name=True,
        alias_generator=None  # Use exact field names
    )

API Route Standards

# ✅ STANDARDIZED: RESTful with consistent parameter naming
router = APIRouter(prefix="/api/v1/leads", tags=["leads"])

@router.get("/{lead_id}")           # GET /api/v1/leads/{lead_id}
@router.put("/{lead_id}")           # PUT /api/v1/leads/{lead_id}
@router.delete("/{lead_id}")        # DELETE /api/v1/leads/{lead_id}

# Sub-resources
@router.get("/{lead_id}/messages")  # GET /api/v1/leads/{lead_id}/messages
@router.get("/agency/{agency_id}")  # GET /api/v1/leads/agency/{agency_id}

For complete naming standards, see NAMING_CONVENTIONS.md.

📝 Documentation Standards

Code Documentation

  • Every module should have a docstring explaining its purpose
  • Public functions must have complete docstrings
  • Complex logic should have inline comments with # Reason: prefix
  • Keep README.md updated with setup instructions and examples
  • Maintain CHANGELOG.md for version history

API Documentation

from fastapi import APIRouter, HTTPException, status
from typing import List

router = APIRouter(prefix="/products", tags=["products"])

@router.get(
    "/",
    response_model=List[Product],
    summary="List all products",
    description="Retrieve a paginated list of all active products"
)
async def list_products(
    skip: int = 0,
    limit: int = 100,
    category: Optional[str] = None
) -> List[Product]:
    """
    Retrieve products with optional filtering.

    - **skip**: Number of products to skip (for pagination)
    - **limit**: Maximum number of products to return
    - **category**: Filter by product category
    """
    # Implementation here

🚀 Performance Considerations

Optimization Guidelines

  • Profile before optimizing - use cProfile or py-spy
  • Use lru_cache for expensive computations
  • Prefer generators for large datasets
  • Use asyncio for I/O-bound operations
  • Consider multiprocessing for CPU-bound tasks
  • Cache database queries appropriately

Example Optimization

from functools import lru_cache
import asyncio
from typing import AsyncIterator

@lru_cache(maxsize=1000)
def expensive_calculation(n: int) -> int:
    """Cache results of expensive calculations."""
    # Complex computation here
    return result

async def process_large_dataset() -> AsyncIterator[dict]:
    """Process large dataset without loading all into memory."""
    async with aiofiles.open('large_file.json', mode='r') as f:
        async for line in f:
            data = json.loads(line)
            # Process and yield each item
            yield process_item(data)

🛡️ Security Best Practices

Security Guidelines

  • Never commit secrets - use environment variables
  • Validate all user input with Pydantic
  • Use parameterized queries for database operations
  • Implement rate limiting for APIs
  • Keep dependencies updated with uv
  • Use HTTPS for all external communications
  • Implement proper authentication and authorization

Example Security Implementation

from passlib.context import CryptContext
import secrets

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    """Hash password using bcrypt."""
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Verify a password against its hash."""
    return pwd_context.verify(plain_password, hashed_password)

def generate_secure_token(length: int = 32) -> str:
    """Generate a cryptographically secure random token."""
    return secrets.token_urlsafe(length)

🔍 Debugging Tools

Debugging Commands

# Interactive debugging with ipdb
uv add --dev ipdb
# Add breakpoint: import ipdb; ipdb.set_trace()

# Memory profiling
uv add --dev memory-profiler
uv run python -m memory_profiler script.py

# Line profiling
uv add --dev line-profiler
# Add @profile decorator to functions

# Debug with rich traceback
uv add --dev rich
# In code: from rich.traceback import install; install()

📊 Monitoring and Observability

Structured Logging

import structlog

logger = structlog.get_logger()

# Log with context
logger.info(
    "payment_processed",
    user_id=user.id,
    amount=amount,
    currency="USD",
    processing_time=processing_time
)

📚 Useful Resources

Essential Tools

Python Best Practices

⚠️ Important Notes

  • NEVER ASSUME OR GUESS - When in doubt, ask for clarification
  • Always verify file paths and module names before use
  • Keep CLAUDE.md updated when adding new patterns or dependencies
  • Test your code - No feature is complete without tests
  • Document your decisions - Future developers (including yourself) will thank you

🔍 Search Command Requirements

CRITICAL: Always use rg (ripgrep) instead of traditional grep and find commands:

# ❌ Don't use grep
grep -r "pattern" .

# ✅ Use rg instead
rg "pattern"

# ❌ Don't use find with name
find . -name "*.py"

# ✅ Use rg with file filtering
rg --files | rg "\.py$"
# or
rg --files -g "*.py"

Enforcement Rules:

(
    r"^grep\b(?!.*\|)",
    "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
),
(
    r"^find\s+\S+\s+-name\b",
    "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
),

🚀 GitHub Flow Workflow Summary

main (protected) ←── PR ←── feature/your-feature ↓ ↑ deploy development

Daily Workflow:

  1. git checkout main && git pull origin main
  2. git checkout -b feature/new-feature
  3. Make changes + tests
  4. git push origin feature/new-feature
  5. Create PR → Review → Merge to main

This document is a living guide. Update it as the project evolves and new patterns emerge.