# Models Structure ## Overview This project follows a **standardized models structure** at the root level, separating database models from Pydantic schemas. ## Directory Structure ``` models/ ├── database/ # SQLAlchemy database models (ORM) │ ├── __init__.py │ ├── user.py │ ├── vendor.py │ ├── product.py │ ├── order.py │ ├── admin.py │ ├── architecture_scan.py │ └── ... │ └── schema/ # Pydantic schemas (API validation) ├── __init__.py ├── auth.py ├── admin.py ├── product.py ├── order.py └── ... ``` ## Important Rules ### ✅ DO: Use Root-Level Models **ALL models must be in the root `models/` directory:** - Database models → `models/database/` - Pydantic schemas → `models/schema/` ### ❌ DON'T: Create `app/models/` **NEVER create or use `app/models/` directory.** The application structure is: ``` app/ # Application code (routes, services, core) models/ # Models (database & schemas) ``` NOT: ``` app/ models/ # ❌ WRONG - Don't create this! models/ # ✓ Correct location ``` --- ## Database Models (`models/database/`) ### Purpose SQLAlchemy ORM models that represent database tables. ### Naming Convention - Singular class names: `User`, `Product`, `Order` - File names match class: `user.py`, `product.py`, `order.py` ### Example Structure **File:** `models/database/product.py` ```python """Product database model""" from sqlalchemy import Column, Integer, String, Float, ForeignKey from sqlalchemy.orm import relationship from .base import Base class Product(Base): """Product database model""" __tablename__ = "products" id = Column(Integer, primary_key=True, index=True) name = Column(String(255), nullable=False) price = Column(Float, nullable=False) vendor_id = Column(Integer, ForeignKey("vendors.id")) # Relationships vendor = relationship("Vendor", back_populates="products") ``` ### Exporting Models All database models must be exported in `models/database/__init__.py`: ```python # models/database/__init__.py from .user import User from .vendor import Vendor from .product import Product from .order import Order, OrderItem __all__ = [ "User", "Vendor", "Product", "Order", "OrderItem", ] ``` ### Importing Database Models ```python # ✅ CORRECT - Import from models.database from models.database import User, Product from models.database.vendor import Vendor # ❌ WRONG - Don't import from app.models from app.models.user import User # This path doesn't exist! ``` --- ## Pydantic Schemas (`models/schema/`) ### Purpose Pydantic models for API request/response validation and serialization. ### Naming Convention - Use descriptive suffixes: `Create`, `Update`, `Response`, `InDB` - Group related schemas in same file - File names match domain: `auth.py`, `product.py`, `order.py` ### Example Structure **File:** `models/schema/product.py` ```python """Product Pydantic schemas""" from typing import Optional from pydantic import BaseModel, Field class ProductBase(BaseModel): """Base product schema""" name: str = Field(..., min_length=1, max_length=255) description: Optional[str] = None price: float = Field(..., gt=0) class ProductCreate(ProductBase): """Schema for creating a product""" vendor_id: int class ProductUpdate(BaseModel): """Schema for updating a product""" name: Optional[str] = Field(None, min_length=1, max_length=255) description: Optional[str] = None price: Optional[float] = Field(None, gt=0) class ProductResponse(ProductBase): """Schema for product API response""" id: int vendor_id: int class Config: from_attributes = True # Pydantic v2 # orm_mode = True # Pydantic v1 ``` ### Exporting Schemas Export schemas in `models/schema/__init__.py`: ```python # models/schema/__init__.py from .auth import LoginRequest, TokenResponse from .product import ProductCreate, ProductUpdate, ProductResponse __all__ = [ "LoginRequest", "TokenResponse", "ProductCreate", "ProductUpdate", "ProductResponse", ] ``` ### Importing Schemas ```python # ✅ CORRECT from models.schema import ProductCreate, ProductResponse from models.schema.auth import LoginRequest # ❌ WRONG from app.models.schema.product import ProductCreate ``` --- ## Common Patterns ### Pattern 1: Database Model with Schema **Database Model:** `models/database/vendor.py` ```python from sqlalchemy import Column, Integer, String, Boolean from .base import Base class Vendor(Base): __tablename__ = "vendors" id = Column(Integer, primary_key=True) name = Column(String(255), nullable=False) code = Column(String(50), unique=True, nullable=False) is_active = Column(Boolean, default=True) ``` **Pydantic Schema:** `models/schema/vendor.py` ```python from pydantic import BaseModel class VendorBase(BaseModel): name: str code: str class VendorCreate(VendorBase): pass class VendorResponse(VendorBase): id: int is_active: bool class Config: from_attributes = True ``` **Usage in API:** ```python from fastapi import APIRouter from sqlalchemy.orm import Session from models.database import Vendor from models.schema import VendorCreate, VendorResponse router = APIRouter() @router.post("/vendors", response_model=VendorResponse) def create_vendor(vendor_data: VendorCreate, db: Session): # VendorCreate validates input db_vendor = Vendor(**vendor_data.dict()) db.add(db_vendor) db.commit() db.refresh(db_vendor) # VendorResponse serializes output return db_vendor ``` --- ### Pattern 2: Complex Schemas For complex domains, organize schemas by purpose: ```python # models/schema/order.py class OrderBase(BaseModel): """Base order fields""" pass class OrderCreate(OrderBase): """Create order from customer""" items: List[OrderItemCreate] class OrderUpdate(BaseModel): """Admin order update""" status: Optional[OrderStatus] class OrderResponse(OrderBase): """Order API response""" id: int items: List[OrderItemResponse] class OrderAdminResponse(OrderResponse): """Extended response for admin""" internal_notes: Optional[str] ``` --- ## Migration Guide If you accidentally created models in the wrong location: ### Moving Database Models ```bash # If you created app/models/my_model.py (WRONG) # Move to correct location: mv app/models/my_model.py models/database/my_model.py # Update imports in all files # FROM: from app.models.my_model import MyModel # TO: from models.database.my_model import MyModel # Add to models/database/__init__.py # Remove app/models/ directory rm -rf app/models/ ``` ### Moving Pydantic Schemas ```bash # If you created app/schemas/my_schema.py (WRONG) # Move to correct location: mv app/schemas/my_schema.py models/schema/my_schema.py # Update imports # FROM: from app.schemas.my_schema import MySchema # TO: from models.schema.my_schema import MySchema # Add to models/schema/__init__.py # Remove app/schemas/ directory rm -rf app/schemas/ ``` --- ## Why This Structure? ### ✅ Benefits 1. **Clear Separation** - Database layer separate from application layer - Easy to understand where models live 2. **Import Consistency** - `from models.database import ...` - `from models.schema import ...` - No confusion about import paths 3. **Testing** - Easy to mock database models - Easy to test schema validation 4. **Scalability** - Models can be used by multiple apps - Clean separation of concerns 5. **Tool Compatibility** - Alembic migrations find models easily - IDE autocomplete works better - Linters understand structure ### ❌ Problems with `app/models/` 1. **Confusion**: Is it database or schema? 2. **Import Issues**: Circular dependencies 3. **Migration Problems**: Alembic can't find models 4. **Inconsistency**: Different parts of codebase use different paths --- ## Verification Checklist Use this checklist when adding new models: ### Database Model Checklist - [ ] File in `models/database/{name}.py` - [ ] Inherits from `Base` - [ ] Has `__tablename__` defined - [ ] Exported in `models/database/__init__.py` - [ ] Imported using `from models.database import ...` - [ ] NO file in `app/models/` ### Pydantic Schema Checklist - [ ] File in `models/schema/{name}.py` - [ ] Inherits from `BaseModel` - [ ] Has descriptive suffix (`Create`, `Update`, `Response`) - [ ] Exported in `models/schema/__init__.py` - [ ] Imported using `from models.schema import ...` - [ ] NO file in `app/schemas/` --- ## Project Structure ``` project/ ├── app/ │ ├── api/ # API routes │ ├── core/ # Core functionality (config, database, auth) │ ├── services/ # Business logic │ ├── templates/ # Jinja2 templates │ └── routes/ # Page routes │ ├── models/ # ✓ Models live here! │ ├── database/ # ✓ SQLAlchemy models │ └── schema/ # ✓ Pydantic schemas │ ├── static/ # Frontend assets ├── docs/ # Documentation ├── tests/ # Tests └── scripts/ # Utility scripts ``` **NOT:** ``` app/ models/ # ❌ Don't create this schemas/ # ❌ Don't create this ``` --- ## Examples from the Codebase ### ✅ Correct Examples **Database Model:** ```python # models/database/architecture_scan.py from sqlalchemy import Column, Integer, String from .base import Base class ArchitectureScan(Base): __tablename__ = "architecture_scans" id = Column(Integer, primary_key=True) ``` **Import in Service:** ```python # app/services/code_quality_service.py from models.database.architecture_scan import ArchitectureScan ``` **Pydantic Schema:** ```python # models/schema/admin.py from pydantic import BaseModel class AdminDashboardStats(BaseModel): total_vendors: int total_users: int ``` **Import in API:** ```python # app/api/v1/admin/dashboard.py from models.schema.admin import AdminDashboardStats ``` --- ## Summary **Golden Rule:** All models in `models/`, never in `app/models/` or `app/schemas/`. **Quick Reference:** - Database models → `models/database/` - Pydantic schemas → `models/schema/` - Import pattern → `from models.{type} import ...` - No models in `app/` directory This standard ensures consistency, clarity, and maintainability across the entire project.