- Add observability framework documentation (health checks, metrics, Sentry) - Add developer guide for creating modules - Add comprehensive module migration plan with Celery task integration - Update architecture overview with module system and observability sections - Update module-system.md with links to new docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
12 KiB
Creating Modules
This guide explains how to create new modules for the Wizamart platform, including both simple wrapper modules and self-contained modules with their own services, models, and migrations.
Module Types
| Type | Complexity | Use Case |
|---|---|---|
| Wrapper Module | Simple | Groups existing routes and features under a toggleable module |
| Self-Contained Module | Complex | Full isolation with own services, models, templates, migrations |
Quick Start: Wrapper Module
For a simple module that wraps existing functionality:
# app/modules/analytics/definition.py
from app.modules.base import ModuleDefinition
from models.database.admin_menu_config import FrontendType
analytics_module = ModuleDefinition(
code="analytics",
name="Analytics & Reports",
description="Business analytics, reports, and dashboards",
version="1.0.0",
features=[
"analytics_dashboard",
"sales_reports",
"customer_insights",
],
menu_items={
FrontendType.ADMIN: ["analytics-dashboard", "reports"],
FrontendType.VENDOR: ["analytics", "sales-reports"],
},
is_core=False,
is_internal=False,
)
Module Definition Fields
Required Fields
| Field | Type | Description |
|---|---|---|
code |
str | Unique identifier (e.g., "billing", "analytics") |
name |
str | Display name (e.g., "Billing & Subscriptions") |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
description |
str | "" | What the module provides |
version |
str | "1.0.0" | Semantic version |
requires |
list[str] | [] | Module codes this depends on |
features |
list[str] | [] | Feature codes for tier gating |
menu_items |
dict | {} | Menu items per frontend type |
permissions |
list[str] | [] | Permission codes defined |
is_core |
bool | False | Cannot be disabled if True |
is_internal |
bool | False | Admin-only if True |
Configuration Fields
| Field | Type | Description |
|---|---|---|
config_schema |
type[BaseModel] | Pydantic model for validation |
default_config |
dict | Default configuration values |
Lifecycle Hooks
| Field | Signature | Description |
|---|---|---|
on_enable |
(platform_id: int) -> None | Called when enabled |
on_disable |
(platform_id: int) -> None | Called when disabled |
on_startup |
() -> None | Called on app startup |
health_check |
() -> dict | Returns health status |
Module Classification
Core Modules
Cannot be disabled. Use for essential platform functionality.
core_module = ModuleDefinition(
code="tenancy",
name="Platform Tenancy",
is_core=True, # Cannot be disabled
# ...
)
Optional Modules
Can be enabled/disabled per platform.
billing_module = ModuleDefinition(
code="billing",
name="Billing",
is_core=False, # Can be toggled
is_internal=False,
# ...
)
Internal Modules
Admin-only tools not visible to customers/vendors.
devtools_module = ModuleDefinition(
code="dev-tools",
name="Developer Tools",
is_internal=True, # Admin-only
# ...
)
Module Dependencies
Declare dependencies using the requires field:
# orders module requires payments
orders_module = ModuleDefinition(
code="orders",
name="Orders",
requires=["payments"], # Must have payments enabled
# ...
)
Dependency Rules:
- Core modules cannot depend on optional modules
- Enabling a module auto-enables its dependencies
- Disabling a module auto-disables modules that depend on it
- Circular dependencies are not allowed
Self-Contained Module Structure
For modules with their own services, models, and templates:
app/modules/cms/
├── __init__.py
├── definition.py # ModuleDefinition
├── config.py # Configuration schema (optional)
├── exceptions.py # Module-specific exceptions
├── routes/
│ ├── __init__.py
│ ├── admin.py # Admin API routes
│ └── vendor.py # Vendor API routes
├── services/
│ ├── __init__.py
│ └── content_service.py
├── models/
│ ├── __init__.py
│ └── content_page.py
├── schemas/
│ ├── __init__.py
│ └── content.py
├── templates/
│ ├── admin/
│ └── vendor/
├── migrations/
│ └── versions/
│ ├── cms_001_create_content_pages.py
│ └── cms_002_add_seo_fields.py
└── locales/
├── en.json
└── fr.json
Self-Contained Definition
# app/modules/cms/definition.py
from pydantic import BaseModel, Field
from app.modules.base import ModuleDefinition
from models.database.admin_menu_config import FrontendType
class CMSConfig(BaseModel):
"""CMS module configuration."""
max_pages_per_vendor: int = Field(default=100, ge=1, le=1000)
enable_seo: bool = True
default_language: str = "en"
cms_module = ModuleDefinition(
# Identity
code="cms",
name="Content Management",
description="Content pages, media library, and themes",
version="1.0.0",
# Classification
is_core=True,
is_self_contained=True,
# Features
features=[
"content_pages",
"media_library",
"vendor_themes",
],
# Menu items
menu_items={
FrontendType.ADMIN: ["content-pages", "vendor-themes"],
FrontendType.VENDOR: ["content-pages", "media"],
},
# Configuration
config_schema=CMSConfig,
default_config={
"max_pages_per_vendor": 100,
"enable_seo": True,
},
# Self-contained paths
services_path="app.modules.cms.services",
models_path="app.modules.cms.models",
schemas_path="app.modules.cms.schemas",
templates_path="templates",
exceptions_path="app.modules.cms.exceptions",
locales_path="locales",
migrations_path="migrations",
# Lifecycle
health_check=lambda: {"status": "healthy"},
)
Creating Module Routes
Admin Routes
# app/modules/payments/routes/admin.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_db, get_current_admin_user
admin_router = APIRouter(prefix="/api/admin/payments", tags=["Admin - Payments"])
@admin_router.get("/gateways")
async def list_gateways(
db: Session = Depends(get_db),
current_user = Depends(get_current_admin_user),
):
"""List configured payment gateways."""
# Implementation
pass
Vendor Routes
# app/modules/payments/routes/vendor.py
from fastapi import APIRouter, Depends
from app.api.deps import get_db, get_current_vendor_user
vendor_router = APIRouter(prefix="/api/vendor/payments", tags=["Vendor - Payments"])
@vendor_router.get("/methods")
async def list_payment_methods(
db: Session = Depends(get_db),
current_user = Depends(get_current_vendor_user),
):
"""List vendor's stored payment methods."""
pass
Lazy Router Loading
To avoid circular imports, use lazy loading:
# app/modules/payments/definition.py
def _get_admin_router():
"""Lazy import to avoid circular imports."""
from app.modules.payments.routes.admin import admin_router
return admin_router
def _get_vendor_router():
from app.modules.payments.routes.vendor import vendor_router
return vendor_router
payments_module = ModuleDefinition(
code="payments",
# ...
)
def get_payments_module_with_routers():
"""Get module with routers attached."""
payments_module.admin_router = _get_admin_router()
payments_module.vendor_router = _get_vendor_router()
return payments_module
Module Migrations
Naming Convention
{module_code}_{sequence}_{description}.py
Examples:
cms_001_create_content_pages.pycms_002_add_seo_fields.pybilling_001_create_subscriptions.py
Migration Template
# app/modules/cms/migrations/versions/cms_001_create_content_pages.py
"""Create content pages table.
Revision ID: cms_001
Revises:
Create Date: 2026-01-27
"""
from alembic import op
import sqlalchemy as sa
revision = "cms_001"
down_revision = None # Or previous module migration
branch_labels = ("cms",)
depends_on = None
def upgrade() -> None:
op.create_table(
"cms_content_pages",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("vendor_id", sa.Integer(), sa.ForeignKey("vendors.id")),
sa.Column("title", sa.String(200), nullable=False),
sa.Column("slug", sa.String(200), nullable=False),
sa.Column("content", sa.Text()),
sa.Column("created_at", sa.DateTime(timezone=True)),
sa.Column("updated_at", sa.DateTime(timezone=True)),
)
def downgrade() -> None:
op.drop_table("cms_content_pages")
Module Configuration
Defining Configuration Schema
from pydantic import BaseModel, Field
class BillingConfig(BaseModel):
"""Billing module configuration."""
stripe_mode: Literal["test", "live"] = "test"
trial_days: int = Field(default=14, ge=0, le=365)
enable_invoices: bool = True
invoice_prefix: str = Field(default="INV-", max_length=10)
Using Configuration
from app.modules.service import module_service
# Get module config for platform
config = module_service.get_module_config(db, platform_id, "billing")
# Returns: {"stripe_mode": "test", "trial_days": 14, ...}
# Set module config
module_service.set_module_config(
db, platform_id, "billing",
{"trial_days": 30}
)
Health Checks
Defining Health Check
def check_billing_health() -> dict:
"""Check billing service dependencies."""
issues = []
# Check Stripe
try:
stripe.Account.retrieve()
except stripe.AuthenticationError:
issues.append("Stripe authentication failed")
if issues:
return {
"status": "unhealthy",
"message": "; ".join(issues),
}
return {
"status": "healthy",
"details": {"stripe": "connected"},
}
billing_module = ModuleDefinition(
code="billing",
health_check=check_billing_health,
# ...
)
Registering the Module
Add to Registry
# app/modules/registry.py
from app.modules.analytics.definition import analytics_module
OPTIONAL_MODULES: dict[str, ModuleDefinition] = {
# ... existing modules
"analytics": analytics_module,
}
MODULES = {**CORE_MODULES, **OPTIONAL_MODULES, **INTERNAL_MODULES}
Module Events
Subscribe to module lifecycle events:
from app.modules.events import module_event_bus, ModuleEvent, ModuleEventData
@module_event_bus.subscribe(ModuleEvent.ENABLED)
def on_analytics_enabled(data: ModuleEventData):
if data.module_code == "analytics":
# Initialize analytics for this platform
setup_analytics_dashboard(data.platform_id)
@module_event_bus.subscribe(ModuleEvent.DISABLED)
def on_analytics_disabled(data: ModuleEventData):
if data.module_code == "analytics":
# Cleanup
cleanup_analytics_data(data.platform_id)
Checklist
New Module Checklist
- Create module directory:
app/modules/{code}/ - Create
definition.pywith ModuleDefinition - Add module to appropriate dict in
registry.py - Create routes if needed (admin.py, vendor.py)
- Register menu items in menu registry
- Create migrations if adding database tables
- Add health check if module has dependencies
- Document features and configuration options
- Write tests for module functionality
Self-Contained Module Checklist
- All items from basic checklist
- Create
services/directory with business logic - Create
models/directory with SQLAlchemy models - Create
schemas/directory with Pydantic schemas - Create
exceptions.pyfor module-specific errors - Create
config.pywith Pydantic config model - Set up
migrations/versions/directory - Create
templates/if module has UI - Create
locales/if module needs translations - Set
is_self_contained=Trueand path attributes
Related Documentation
- Module System - Architecture overview
- Menu Management - Menu item integration
- Database Migrations - Migration guide