Document the current architecture for models and schemas: - CORE models (models/database/) vs module models (app/modules/*/models/) - Three schema patterns: dedicated files, inline, and no-schema - Data flow diagram showing services access DB directly - Gap analysis table showing model-schema alignment - Import guidelines with canonical vs legacy re-exports - Checklists for creating new models and schemas Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
Models & Schemas Architecture
Overview
This project uses a hybrid architecture for models and schemas:
- CORE models/schemas in
models/database/andmodels/schema/- Framework-level entities used across modules - Module models/schemas in
app/modules/<module>/models/andapp/modules/<module>/schemas/- Domain-specific entities - 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/<module>/ # 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 modulesVendor- Multi-tenant anchor, used by all vendor-scoped modulesCompany- Business entity managementPlatform- Multi-platform CMS supportMediaFile- File storage, used by catalog, CMS, etc.
Module models (app/modules/<module>/models/) are domain-specific:
billing/models/- Feature, SubscriptionTier, VendorSubscriptioncatalog/models/- Product, ProductTranslation, ProductMediaorders/models/- Order, OrderItem, Invoiceinventory/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.
# 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.
# 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.
# 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/<module>/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)
# CORE models - from models.database
from models.database import User, Vendor, MediaFile
# Module models - from app.modules.<module>.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.<module>.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:
# 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
-
Determine location:
- Cross-module use →
models/database/ - Module-specific →
app/modules/<module>/models/
- Cross-module use →
-
Create the model file:
# 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") -
Export in
__init__.py:# app/modules/mymodule/models/__init__.py from app.modules.mymodule.models.my_entity import MyEntity __all__ = ["MyEntity"]
Schema Checklist
-
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
-
Create schema (if needed):
# 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 -
Or use inline schema:
# 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/<module>/models/ |