# 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: ```python # 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. ```python core_module = ModuleDefinition( code="tenancy", name="Platform Tenancy", is_core=True, # Cannot be disabled # ... ) ``` ### Optional Modules Can be enabled/disabled per platform. ```python 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. ```python devtools_module = ModuleDefinition( code="dev-tools", name="Developer Tools", is_internal=True, # Admin-only # ... ) ``` ## Module Dependencies Declare dependencies using the `requires` field: ```python # orders module requires payments orders_module = ModuleDefinition( code="orders", name="Orders", requires=["payments"], # Must have payments enabled # ... ) ``` **Dependency Rules:** 1. Core modules cannot depend on optional modules 2. Enabling a module auto-enables its dependencies 3. Disabling a module auto-disables modules that depend on it 4. 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 ```python # 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 ```python # 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 ```python # 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: ```python # 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.py` - `cms_002_add_seo_fields.py` - `billing_001_create_subscriptions.py` ### Migration Template ```python # 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 ```python 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 ```python 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 ```python 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 ```python # 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: ```python 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.py` with 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.py` for module-specific errors - [ ] Create `config.py` with Pydantic config model - [ ] Set up `migrations/versions/` directory - [ ] Create `templates/` if module has UI - [ ] Create `locales/` if module needs translations - [ ] Set `is_self_contained=True` and path attributes ## Related Documentation - [Module System](../architecture/module-system.md) - Architecture overview - [Menu Management](../architecture/menu-management.md) - Menu item integration - [Database Migrations](migration/database-migrations.md) - Migration guide