# Models & Schemas Architecture ## Overview This project uses a **hybrid architecture** for models and schemas: 1. **CORE models/schemas** in `models/database/` and `models/schema/` - Framework-level entities used across modules 2. **Module models/schemas** in `app/modules//models/` and `app/modules//schemas/` - Domain-specific entities 3. **Inline schemas** defined directly in API route files - Endpoint-specific request/response models ## Directory Structure ``` models/ # CORE framework models ├── database/ # SQLAlchemy models (ORM) │ ├── __init__.py # Exports all models + module discovery │ ├── base.py # Base class, mixins │ ├── user.py # User authentication │ ├── vendor.py # Vendor, VendorUser, Role │ ├── company.py # Company management │ ├── platform.py # Multi-platform support │ ├── media.py # MediaFile (cross-cutting) │ └── ... │ └── schema/ # Pydantic schemas (API validation) ├── __init__.py ├── auth.py # Login, tokens, password reset ├── vendor.py # Vendor CRUD schemas ├── company.py # Company schemas └── ... app/modules// # Domain modules ├── models/ # Module-specific database models │ ├── __init__.py # Canonical exports │ └── *.py # Model definitions │ └── schemas/ # Module-specific schemas ├── __init__.py # Canonical exports └── *.py # Schema definitions ``` --- ## Architecture Principles ### 1. CORE vs Module Models **CORE models** (`models/database/`) are framework-level entities used across multiple modules: - `User` - Authentication, used by all modules - `Vendor` - Multi-tenant anchor, used by all vendor-scoped modules - `Company` - Business entity management - `Platform` - Multi-platform CMS support - `MediaFile` - File storage, used by catalog, CMS, etc. **Module models** (`app/modules//models/`) are domain-specific: - `billing/models/` - Feature, SubscriptionTier, VendorSubscription - `catalog/models/` - Product, ProductTranslation, ProductMedia - `orders/models/` - Order, OrderItem, Invoice - `inventory/models/` - Inventory, InventoryTransaction ### 2. Schema Patterns The codebase uses **three valid patterns** for schemas: #### Pattern A: Dedicated Schema Files (Reusable) For schemas used across multiple endpoints or modules. ```python # app/modules/inventory/schemas/inventory.py class InventoryCreate(BaseModel): product_id: int location: str quantity: int class InventoryResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int product_id: int vendor_id: int quantity: int ``` #### Pattern B: Inline Schemas (Endpoint-Specific) For schemas only used by a single endpoint or closely related endpoints. ```python # app/api/v1/admin/platforms.py class PlatformResponse(BaseModel): """Platform response schema - only used in this file.""" id: int code: str name: str # ... class PlatformUpdateRequest(BaseModel): """Request schema - only used in this file.""" name: str | None = None description: str | None = None @router.get("/platforms/{code}", response_model=PlatformResponse) def get_platform(...): ... ``` #### Pattern C: No Schema (Internal Service Use) For models only accessed internally by services, not exposed via API. ```python # Internal association table - no API exposure class VendorPlatform(Base): """Links vendors to platforms - internal use only.""" __tablename__ = "vendor_platforms" vendor_id = Column(Integer, ForeignKey("vendors.id")) platform_id = Column(Integer, ForeignKey("platforms.id")) ``` ### 3. When to Use Each Pattern | Scenario | Pattern | Location | |----------|---------|----------| | Schema used by multiple endpoints | Dedicated file | `app/modules//schemas/` | | Schema used by single endpoint | Inline | In the route file | | Admin-only endpoint schemas | Inline | In the admin route file | | Model not exposed via API | No schema | N/A | | Cross-module utility schemas | Dedicated file | `models/schema/` | --- ## Data Flow Architecture ``` ┌─────────────────────────────────────────────────────────────────────┐ │ API Layer │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Request Schema │───▶│ Endpoint │───▶│ Response Schema │ │ │ │ (Pydantic) │ │ │ │ (Pydantic) │ │ │ └─────────────────┘ └────────┬────────┘ └─────────────────┘ │ └──────────────────────────────────┼──────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Service Layer │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Business Logic │ │ │ │ • Validation • Transformations • Business Rules │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Database Models (SQLAlchemy) │ │ │ │ • Direct access • Queries • Transactions │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Database Layer │ │ PostgreSQL / SQLite │ └─────────────────────────────────────────────────────────────────────┘ ``` **Key principles:** - **Services access database models directly** - No schema layer between service and database - **APIs use schemas for validation** - Request/response validation at API boundary - **Schemas are API contracts** - Define what clients send/receive --- ## Import Guidelines ### Canonical Imports (Preferred) ```python # CORE models - from models.database from models.database import User, Vendor, MediaFile # Module models - from app.modules..models from app.modules.billing.models import Feature, SubscriptionTier from app.modules.catalog.models import Product, ProductMedia from app.modules.orders.models import Order, OrderItem # CORE schemas - from models.schema from models.schema.auth import LoginRequest, TokenResponse # Module schemas - from app.modules..schemas from app.modules.inventory.schemas import InventoryCreate, InventoryResponse from app.modules.orders.schemas import OrderResponse ``` ### Legacy Re-exports (Backwards Compatibility) `models/database/__init__.py` re-exports module models for backwards compatibility: ```python # These work but prefer canonical imports from models.database import Product # Re-exported from catalog module from models.database import Order # Re-exported from orders module ``` --- ## Gap Analysis: Models vs Schemas Not every database model needs a dedicated schema file. Here's the current alignment: ### CORE Framework | Database Model | Schema | Notes | |----------------|--------|-------| | `user.py` | `auth.py` | Auth schemas cover user operations | | `vendor.py` | `vendor.py` | Full CRUD schemas | | `company.py` | `company.py` | Full CRUD schemas | | `media.py` | `media.py` | Upload/response schemas | | `platform.py` | Inline | Admin-only, in `platforms.py` route | | `platform_module.py` | Inline | Admin-only, in `modules.py` route | | `admin_menu_config.py` | Inline | Admin-only, in `menu_config.py` route | | `vendor_email_settings.py` | Inline | In `email_settings.py` route | | `vendor_email_template.py` | None | Internal email service use | | `vendor_platform.py` | None | Internal association table | ### Modules | Module | Models | Schemas | Alignment | |--------|--------|---------|-----------| | billing | feature, subscription | billing, subscription | ✅ | | cart | cart | cart | ✅ | | catalog | product, product_media, product_translation | catalog, product, vendor_product | ✅ | | checkout | - | checkout | Schema-only (orchestration) | | cms | content_page | content_page, homepage_sections | ✅ | | customers | customer, password_reset_token | customer, context | ✅ (password reset uses auth schemas) | | dev_tools | architecture_scan, test_run | Inline | Admin-only, inline in route files | | inventory | inventory, inventory_transaction | inventory | ✅ (transaction included in inventory.py) | | loyalty | 5 models | 5 schemas | ✅ | | marketplace | 5 models | 4 schemas | ✅ (translation in product schema) | | messaging | admin_notification, message | message, notification | ✅ | | orders | order, order_item, invoice | order, invoice, order_item_exception | ✅ | | payments | - | payment | Schema-only (external APIs) | | analytics | - | stats | Schema-only (aggregation views) | --- ## Creating New Models ### Database Model Checklist 1. **Determine location:** - Cross-module use → `models/database/` - Module-specific → `app/modules//models/` 2. **Create the model file:** ```python # app/modules/mymodule/models/my_entity.py from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from app.core.database import Base from models.database.base import TimestampMixin class MyEntity(Base, TimestampMixin): __tablename__ = "my_entities" id = Column(Integer, primary_key=True, index=True) vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) name = Column(String(255), nullable=False) vendor = relationship("Vendor") ``` 3. **Export in `__init__.py`:** ```python # app/modules/mymodule/models/__init__.py from app.modules.mymodule.models.my_entity import MyEntity __all__ = ["MyEntity"] ``` ### Schema Checklist 1. **Decide if schema is needed:** - Exposed via API? → Yes, create schema - Multiple endpoints use it? → Dedicated schema file - Single endpoint? → Consider inline schema - Internal only? → No schema needed 2. **Create schema (if needed):** ```python # app/modules/mymodule/schemas/my_entity.py from pydantic import BaseModel, ConfigDict, Field class MyEntityCreate(BaseModel): name: str = Field(..., min_length=1, max_length=255) class MyEntityResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int vendor_id: int name: str ``` 3. **Or use inline schema:** ```python # app/api/v1/vendor/my_entity.py from pydantic import BaseModel class MyEntityResponse(BaseModel): """Response schema - only used in this file.""" id: int name: str @router.get("/my-entities/{id}", response_model=MyEntityResponse) def get_entity(...): ... ``` --- ## Summary | Principle | Implementation | |-----------|----------------| | Services access DB directly | `from app.modules.X.models import Model` | | APIs validate with schemas | Request/response Pydantic models | | Reusable schemas | Dedicated files in `schemas/` | | Endpoint-specific schemas | Inline in route files | | Internal models | No schema needed | | CORE models | `models/database/` | | Module models | `app/modules//models/` |