T03: plugin system framework + lifecycle + migrations + event bus + DI

- Plugin registry with discover/install/activate/deactivate/uninstall lifecycle
- PluginManifest Pydantic v2 schema (name, version, dependencies, routes, events, migrations)
- BasePlugin abstract class with lifecycle hooks (on_install/activate/deactivate/uninstall)
- Migration runner with tenant_id validator (rejects tables without tenant_id)
- Event bus integration: register/unregister listeners on activate/deactivate
- Service container DI: plugins receive db, cache, event_bus, storage, notifications
- Idempotent operations (activate active=200, deactivate inactive=200)
- UI registry for frontend component registration
- 47 new tests (14 ACs + 33 unit tests), 103 total tests pass
- Migration 0003: plugins + plugin_migrations tables
- Coverage: 85.92% for plugin modules
This commit is contained in:
leocrm-bot
2026-06-29 01:18:46 +02:00
parent dd16940bb2
commit 9678344f0e
21 changed files with 2414 additions and 3 deletions
+2
View File
@@ -1 +1,3 @@
"""Pydantic schemas package."""
from app.schemas.plugin import PluginInfo, PluginListResponse, PluginActionResponse, PluginUninstallResponse
+61
View File
@@ -0,0 +1,61 @@
"""Pydantic schemas for plugin API responses."""
from __future__ import annotations
from pydantic import BaseModel, Field
class PluginInfo(BaseModel):
"""Plugin status information returned in list endpoint."""
name: str
display_name: str
version: str
status: str = Field(description="discovered | installed | active | inactive")
installed: bool = False
active: bool = False
description: str = ""
dependencies: list[str] = Field(default_factory=list)
events: list[str] = Field(default_factory=list)
migrations: list[str] = Field(default_factory=list)
permissions: list[str] = Field(default_factory=list)
class PluginListResponse(BaseModel):
"""Response for GET /api/v1/plugins."""
plugins: list[PluginInfo]
total: int
class PluginActionResponse(BaseModel):
"""Response for install/activate/deactivate/uninstall actions."""
name: str
display_name: str
version: str
status: str
installed: bool = False
active: bool = False
dropped_tables: list[str] = Field(default_factory=list, description="Tables dropped (uninstall with remove_data=true)")
message: str = ""
class PluginUninstallResponse(PluginActionResponse):
"""Response for DELETE /api/v1/plugins/{name}."""
pass
class ManifestFieldDoc(BaseModel):
"""Documentation for a single manifest field."""
type: str
required: str
description: str
class ManifestSchemaResponse(BaseModel):
"""Response for GET /api/v1/plugins/manifest."""
fields: dict[str, ManifestFieldDoc]
example: dict