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

759 lines
20 KiB
Markdown

# 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
```
<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
```sql
-- 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:
```python
# ✅ 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:
```python
# ✅ 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
```python
# ✅ 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](./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
```python
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
```python
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
```python
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
```bash
# 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
```python
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
- UV Documentation: https://github.com/astral-sh/uv
- Ruff: https://github.com/astral-sh/ruff
- Pytest: https://docs.pytest.org/
- Pydantic: https://docs.pydantic.dev/
- FastAPI: https://fastapi.tiangolo.com/
### Python Best Practices
- PEP 8: https://pep8.org/
- PEP 484 (Type Hints): https://www.python.org/dev/peps/pep-0484/
- The Hitchhiker's Guide to Python: https://docs.python-guide.org/
## ⚠️ 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:
```bash
# ❌ 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._