# Agent Coding Best Practices Guide

## Overview
This guide outlines essential coding practices for building maintainable, scalable, and type-safe applications. Follow these principles to ensure code quality and consistency across projects.

## 1. Single Responsibility Principle (SRP)

### Core Principle
Each class, function, and module should have one reason to change and one well-defined responsibility.

### Implementation Guidelines

**✅ Good Example:**
```python
class UserValidator:
    """Responsible only for user data validation"""
    def validate_email(self, email: str) -> bool:
        return "@" in email and "." in email
    
    def validate_age(self, age: int) -> bool:
        return 0 < age < 150

class UserRepository:
    """Responsible only for user data persistence"""
    def save_user(self, user: User) -> User:
        # Database save logic
        pass
    
    def find_user(self, user_id: str) -> Optional[User]:
        # Database query logic
        pass
```

**❌ Bad Example:**
```python
class UserManager:
    """Violates SRP - handles validation, persistence, and business logic"""
    def create_user(self, email: str, age: int):
        # Validation logic
        if "@" not in email:
            raise ValueError("Invalid email")
        
        # Business logic
        user = User(email=email, age=age)
        
        # Persistence logic
        self.database.save(user)
        
        # Email logic
        self.send_welcome_email(user)
```

## 2. API Architecture Pattern

### Type-Driven Development
Follow the pattern: **Types (T) → Controller\<T\> → Service\<T\>**

```python
# Types/Models
from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class UserCreateRequest(BaseModel):
    email: str
    name: str
    age: int

class UserResponse(BaseModel):
    id: str
    email: str
    name: str
    age: int
    created_at: datetime

class UserUpdateRequest(BaseModel):
    name: Optional[str] = None
    age: Optional[int] = None
```

### Service Layer
```python
from abc import ABC, abstractmethod
from typing import List, Optional

class UserService(ABC):
    @abstractmethod
    async def create_user(self, request: UserCreateRequest) -> UserResponse:
        pass
    
    @abstractmethod
    async def get_user(self, user_id: str) -> Optional[UserResponse]:
        pass
    
    @abstractmethod
    async def update_user(self, user_id: str, request: UserUpdateRequest) -> UserResponse:
        pass

class UserServiceImpl(UserService):
    def __init__(self, repository: UserRepository):
        self.repository = repository
    
    async def create_user(self, request: UserCreateRequest) -> UserResponse:
        user = await self.repository.create(request)
        return UserResponse.from_orm(user)
```

### Controller Layer
```python
from fastapi import APIRouter, Depends, HTTPException
from typing import List

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

class UserController:
    def __init__(self, service: UserService):
        self.service = service
    
    @router.post("/", response_model=UserResponse)
    async def create_user(self, request: UserCreateRequest) -> UserResponse:
        return await self.service.create_user(request)
    
    @router.get("/{user_id}", response_model=UserResponse)
    async def get_user(self, user_id: str) -> UserResponse:
        user = await self.service.get_user(user_id)
        if not user:
            raise HTTPException(status_code=404, detail="User not found")
        return user
```

## 3. Schema Management

### Pydantic Models with FastAPI
```python
from pydantic import BaseModel, Field, validator
from typing import Optional, List
from enum import Enum

class UserRole(str, Enum):
    ADMIN = "admin"
    USER = "user"
    MODERATOR = "moderator"

class BaseUser(BaseModel):
    """Base user model with common fields"""
    email: str = Field(..., description="User email address")
    name: str = Field(..., min_length=1, max_length=100)
    
    @validator('email')
    def validate_email(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email format')
        return v

class UserCreate(BaseUser):
    """Model for user creation"""
    password: str = Field(..., min_length=8)
    role: UserRole = UserRole.USER

class UserResponse(BaseUser):
    """Model for user responses (excludes sensitive data)"""
    id: str
    role: UserRole
    created_at: datetime
    
    class Config:
        orm_mode = True
```

### OpenAPI Configuration
```python
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

app = FastAPI()

def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    
    openapi_schema = get_openapi(
        title="API Service",
        version="1.0.0",
        description="Production-ready API with consistent models",
        routes=app.routes,
    )
    
    # Add custom schema configurations
    openapi_schema["components"]["schemas"]["Error"] = {
        "type": "object",
        "properties": {
            "detail": {"type": "string"},
            "code": {"type": "string"}
        }
    }
    
    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi
```

## 4. UI/Backend Model Consistency

### Shared Schema Approach
```python
# shared_models.py - Used by both frontend and backend
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime

class ApiResponse(BaseModel):
    """Standard API response wrapper"""
    success: bool
    data: Optional[dict] = None
    error: Optional[str] = None
    timestamp: datetime

class PaginatedResponse(BaseModel):
    """Standard pagination response"""
    items: List[dict]
    total: int
    page: int
    page_size: int
    has_next: bool
    has_prev: bool

# Generate TypeScript types from Pydantic models
# Use: datamodel-codegen or pydantic-to-typescript
```

### Frontend Type Generation
```bash
# Generate TypeScript types from OpenAPI schema
npm install -g @openapitools/openapi-generator-cli
openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ./src/types/api
```

## 5. Dead Code Removal

### Using Vulture
Install and configure vulture for dead code detection:

```bash
pip install vulture
```

### Configuration (.vulture.py)
```python
# .vulture.py
import vulture

# Whitelist for false positives
whitelist = [
    "*.settings",  # Django settings
    "*.urls",      # URL patterns
    "*.migrations.*",  # Database migrations
    "*.tests.*",   # Test methods
]

# Custom vulture configuration
vulture_instance = vulture.Vulture()
vulture_instance.ignore_names = ["setUp", "tearDown", "test_*"]
```

### Integration in CI/CD
```yaml
# .github/workflows/code-quality.yml
name: Code Quality
on: [push, pull_request]

jobs:
  dead-code-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.9
      - name: Install vulture
        run: pip install vulture
      - name: Check for dead code
        run: vulture . --exclude=migrations/,tests/ --min-confidence 80
```

### Regular Cleanup Commands
```bash
# Find dead code
vulture . --min-confidence 80

# Generate report
vulture . --min-confidence 80 > dead_code_report.txt

# Clean up after review
vulture . --min-confidence 90 --make-whitelist > .vulture_whitelist.py
```

## 6. Testing Strategy

### Structured Testing (No Ad-hoc Scripts)

**✅ Proper Test Structure:**
```python
# tests/services/test_user_service.py
import pytest
from unittest.mock import Mock, AsyncMock
from src.services.user_service import UserServiceImpl
from src.models.user import UserCreateRequest, UserResponse

class TestUserService:
    @pytest.fixture
    def mock_repository(self):
        return Mock()
    
    @pytest.fixture
    def user_service(self, mock_repository):
        return UserServiceImpl(mock_repository)
    
    @pytest.mark.asyncio
    async def test_create_user_success(self, user_service, mock_repository):
        # Arrange
        request = UserCreateRequest(email="test@example.com", name="Test", age=25)
        mock_repository.create.return_value = Mock(id="123", email="test@example.com")
        
        # Act
        result = await user_service.create_user(request)
        
        # Assert
        assert isinstance(result, UserResponse)
        assert result.email == "test@example.com"
        mock_repository.create.assert_called_once()
```

**✅ Terminal Commands for Quick Testing:**
```bash
# Run specific test
pytest tests/services/test_user_service.py::TestUserService::test_create_user_success -v

# Test with coverage
pytest --cov=src --cov-report=html

# Integration tests only
pytest -m integration

# Performance tests
pytest -m performance --benchmark-only
```

**❌ Avoid Ad-hoc Scripts:**
```python
# DON'T DO THIS - test_script.py
import requests

# Quick and dirty test
response = requests.post("http://localhost:8000/users", json={"email": "test@test.com"})
print(response.json())
```

## 7. Python Type Hints Best Practices

### Rigorous Type Hints
```python
from typing import Protocol, TypeVar, Generic, Optional, Union, List, Dict, Any
from abc import ABC, abstractmethod

# Use Protocols for structural typing
class Serializable(Protocol):
    def serialize(self) -> Dict[str, Any]:
        ...

# Use generic types appropriately
T = TypeVar('T', bound=Serializable)

class Repository(Generic[T], ABC):
    @abstractmethod
    async def save(self, entity: T) -> T:
        ...
    
    @abstractmethod
    async def find_by_id(self, id: str) -> Optional[T]:
        ...
```

### Avoid Unnecessary Unions
**✅ Good - Common Interface:**
```python
from abc import ABC, abstractmethod

class Agent(ABC):
    @abstractmethod
    def process(self, data: str) -> str:
        pass

class ChatAgent(Agent):
    def process(self, data: str) -> str:
        return f"Chat: {data}"

class SearchAgent(Agent):
    def process(self, data: str) -> str:
        return f"Search: {data}"

# Function accepts the common interface
def handle_request(agent: Agent, data: str) -> str:
    return agent.process(data)
```

**❌ Bad - Unnecessary Union:**
```python
from typing import Union

def handle_request(agent: Union[ChatAgent, SearchAgent], data: str) -> str:
    if isinstance(agent, ChatAgent):
        return agent.process(data)
    elif isinstance(agent, SearchAgent):
        return agent.process(data)
    else:
        raise ValueError("Unknown agent type")
```

### Complex Type Annotations
```python
from typing import TypedDict, Literal, Callable, Awaitable
from datetime import datetime

# Use TypedDict for structured dictionaries
class UserDict(TypedDict):
    id: str
    email: str
    created_at: datetime
    is_active: bool

# Use Literal for specific string values
Status = Literal["pending", "approved", "rejected"]

# Use Callable for function types
ProcessorFunc = Callable[[str], Awaitable[str]]

class DocumentProcessor:
    def __init__(self, processor: ProcessorFunc):
        self.processor = processor
    
    async def process_document(self, content: str) -> str:
        return await self.processor(content)
```

## 8. Project Structure

### Recommended Directory Layout
```
project/
├── src/
│   ├── models/           # Data models and schemas
│   │   ├── __init__.py
│   │   ├── user.py
│   │   ├── product.py
│   │   └── base.py
│   ├── services/         # Business logic layer
│   │   ├── __init__.py
│   │   ├── user_service.py
│   │   ├── auth_service.py
│   │   └── base_service.py
│   ├── controllers/      # API endpoints and request handling
│   │   ├── __init__.py
│   │   ├── user_controller.py
│   │   ├── auth_controller.py
│   │   └── health_controller.py
│   ├── repositories/     # Data access layer
│   │   ├── __init__.py
│   │   ├── user_repository.py
│   │   └── base_repository.py
│   ├── schemas/          # Pydantic schemas for API
│   │   ├── __init__.py
│   │   ├── user_schemas.py
│   │   └── common_schemas.py
│   ├── core/            # Core configuration and utilities
│   │   ├── __init__.py
│   │   ├── config.py
│   │   ├── database.py
│   │   └── dependencies.py
│   └── main.py          # Application entry point
├── tests/
│   ├── unit/
│   ├── integration/
│   └── conftest.py
├── migrations/          # Database migrations
├── docs/               # Documentation
├── requirements.txt
├── pyproject.toml
└── README.md
```

### Model Usage Guidelines

**✅ Always Use Typed Models:**
```python
# models/user.py
from pydantic import BaseModel
from datetime import datetime
from typing import Optional

class User(BaseModel):
    id: str
    email: str
    name: str
    created_at: datetime
    last_login: Optional[datetime] = None

# services/user_service.py
class UserService:
    async def get_user_profile(self, user_id: str) -> User:
        # Return typed model, not dict or dynamic object
        user_data = await self.repository.find_by_id(user_id)
        return User(**user_data)  # Explicit type conversion
```

**❌ Avoid Duck Typing and Guessing:**
```python
# DON'T DO THIS
def process_user(user_data):  # No type hint
    # Assuming user_data has certain fields
    if hasattr(user_data, 'email'):
        return user_data.email
    return None
```

### Model Consistency Rules

1. **Always define explicit models** for data transfer between layers
2. **Use Pydantic BaseModel** for API schemas and validation
3. **Create separate models** for different contexts (Create, Update, Response)
4. **Avoid generic dictionaries** - use TypedDict or Pydantic models
5. **Handle type conversions explicitly** at layer boundaries

## Summary

Following these practices ensures:
- **Maintainable code** through SRP and clear separation of concerns
- **Type safety** with rigorous type hints and model usage
- **API consistency** between frontend and backend
- **Clean codebase** with automated dead code removal
- **Reliable testing** with structured test suites
- **Scalable architecture** with proper layering

Remember: **Type first, test always, structure deliberately.**