# Models & Schemas Architecture ## Overview This project uses a **module-based architecture** for models and schemas: 1. **Infrastructure** in `models/database/` and `models/schema/` - Base classes only 2. **Module models/schemas** in `app/modules//models/` and `app/modules//schemas/` - All domain entities 3. **Inline schemas** defined directly in API route files - Endpoint-specific request/response models ## Directory Structure ``` models/ # INFRASTRUCTURE ONLY ├── database/ # SQLAlchemy base classes │ ├── __init__.py # Exports Base, TimestampMixin only │ └── base.py # Base class, mixins │ └── schema/ # Pydantic base classes ├── __init__.py # Exports base and auth only ├── base.py # Base schema classes └── auth.py # Auth schemas (cross-cutting) app/modules// # Domain modules (ALL domain entities) ├── 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. Infrastructure vs Module Code **Infrastructure** (`models/database/`, `models/schema/`) provides base classes only: - `Base` - SQLAlchemy declarative base - `TimestampMixin` - created_at/updated_at columns - `BaseModel` patterns in `models/schema/base.py` - `auth.py` - Authentication schemas (cross-cutting concern) **Module code** (`app/modules//`) contains all domain-specific entities: | Module | Models | Schemas | |--------|--------|---------| | `tenancy` | User, Admin, Store, Merchant, Platform, StoreDomain | merchant, store, admin, team, store_domain | | `billing` | Feature, SubscriptionTier, StoreSubscription | billing, subscription | | `catalog` | Product, ProductTranslation, ProductMedia | catalog, product, store_product | | `orders` | Order, OrderItem, Invoice | order, invoice, order_item_exception | | `inventory` | Inventory, InventoryTransaction | inventory | | `cms` | ContentPage, MediaFile, StoreTheme | content_page, media, image, store_theme | | `messaging` | Email, StoreEmailSettings, StoreEmailTemplate, Message, Notification | email, message, notification | | `customers` | Customer, PasswordResetToken | customer, context | | `marketplace` | 5 models | 4 schemas | | `core` | AdminMenuConfig | (inline) | ### 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 store_id: int quantity: int ``` #### Pattern B: Inline Schemas (Endpoint-Specific) For schemas only used by a single endpoint or closely related endpoints. ```python # app/modules/tenancy/routes/api/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 StorePlatform(Base): """Links stores to platforms - internal use only.""" __tablename__ = "store_platforms" store_id = Column(Integer, ForeignKey("stores.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 | --- ## 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 (Required) ```python # Infrastructure - base classes only from models.database import Base, TimestampMixin from models.schema.auth import LoginRequest, TokenResponse # Module models - from app.modules..models from app.modules.tenancy.models import User, Store, Merchant from app.modules.billing.models import Feature, SubscriptionTier from app.modules.catalog.models import Product, ProductMedia from app.modules.orders.models import Order, OrderItem from app.modules.cms.models import MediaFile, StoreTheme from app.modules.messaging.models import Email, StoreEmailTemplate # Module schemas - from app.modules..schemas from app.modules.tenancy.schemas import StoreCreate, MerchantResponse from app.modules.cms.schemas import MediaItemResponse, StoreThemeResponse from app.modules.messaging.schemas import EmailTemplateResponse from app.modules.inventory.schemas import InventoryCreate, InventoryResponse from app.modules.orders.schemas import OrderResponse ``` ### Legacy Imports (DEPRECATED) The following import patterns are deprecated and will cause architecture validation errors: ```python # DEPRECATED - Don't import domain models from models.database from models.database import User, Store # WRONG # DEPRECATED - Don't import domain schemas from models.schema from models.schema.store import StoreCreate # WRONG from models.schema.merchant import MerchantResponse # WRONG ``` --- ## Module Ownership Reference ### Tenancy Module (`app/modules/tenancy/`) **Models:** - `User` - User authentication and profile - `Admin` - Admin user management - `Store` - Store/merchant entities - `StoreUser` - Store team members - `Merchant` - Merchant management - `Platform` - Multi-platform support - `AdminPlatform` - Admin-platform association - `StorePlatform` - Store-platform association - `PlatformModule` - Module configuration per platform - `StoreDomain` - Custom domain configuration **Schemas:** - `merchant.py` - Merchant CRUD schemas - `store.py` - Store CRUD and Letzshop export schemas - `admin.py` - Admin user and audit log schemas - `team.py` - Team management and invitation schemas - `store_domain.py` - Domain configuration schemas ### CMS Module (`app/modules/cms/`) **Models:** - `ContentPage` - CMS content pages - `MediaFile` - File storage and management - `StoreTheme` - Theme customization **Schemas:** - `content_page.py` - Content page schemas - `media.py` - Media upload/response schemas - `image.py` - Image handling schemas - `store_theme.py` - Theme configuration schemas ### Messaging Module (`app/modules/messaging/`) **Models:** - `Email` - Email records - `StoreEmailSettings` - Email configuration - `StoreEmailTemplate` - Email templates - `Message` - Internal messages - `AdminNotification` - Admin notifications **Schemas:** - `email.py` - Email template schemas - `message.py` - Message schemas - `notification.py` - Notification schemas ### Core Module (`app/modules/core/`) **Models:** - `AdminMenuConfig` - Menu visibility configuration **Schemas:** (inline in route files) --- ## Creating New Models ### Database Model Checklist 1. **Determine location:** - All domain models → `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 models.database import Base, TimestampMixin class MyEntity(Base, TimestampMixin): __tablename__ = "my_entities" id = Column(Integer, primary_key=True, index=True) store_id = Column(Integer, ForeignKey("stores.id"), nullable=False) name = Column(String(255), nullable=False) store = relationship("Store") ``` 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 store_id: int name: str ``` 3. **Or use inline schema:** ```python # app/modules/mymodule/routes/api/store.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 `app/modules//schemas/` | | Endpoint-specific schemas | Inline in route files | | Internal models | No schema needed | | All domain models | `app/modules//models/` | | Infrastructure only | `models/database/` (Base, TimestampMixin only) |