fix(plugin): convert dependency errors to HTTPException 400, add config API
Bug 3: install_plugin() and activate_plugin() now catch dependency-related ValueErrors and raise HTTPException 400 with the error detail. Bug 4: Added get_plugin_config() and update_plugin_config() methods to the service layer for the plugin config API endpoints.
This commit is contained in:
@@ -6,6 +6,7 @@ import logging
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import HTTPException, status
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.core.audit import log_audit
|
from app.core.audit import log_audit
|
||||||
@@ -43,6 +44,7 @@ class PluginService:
|
|||||||
|
|
||||||
Runs migrations, calls on_install hook, creates DB record.
|
Runs migrations, calls on_install hook, creates DB record.
|
||||||
Idempotent: returns existing record if already installed.
|
Idempotent: returns existing record if already installed.
|
||||||
|
Checks that all declared dependencies are installed first.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
record = await self._registry.install(db, name)
|
record = await self._registry.install(db, name)
|
||||||
@@ -66,7 +68,13 @@ class PluginService:
|
|||||||
"message": "Plugin installed successfully",
|
"message": "Plugin installed successfully",
|
||||||
}
|
}
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(str(exc)) from None
|
msg = str(exc)
|
||||||
|
if "dependencies" in msg.lower():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=msg,
|
||||||
|
) from None
|
||||||
|
raise ValueError(msg) from None
|
||||||
except MigrationValidationError as exc:
|
except MigrationValidationError as exc:
|
||||||
raise MigrationValidationError(str(exc)) from None
|
raise MigrationValidationError(str(exc)) from None
|
||||||
|
|
||||||
@@ -80,6 +88,7 @@ class PluginService:
|
|||||||
"""Activate a plugin by name.
|
"""Activate a plugin by name.
|
||||||
|
|
||||||
Registers event listeners and routes. Idempotent.
|
Registers event listeners and routes. Idempotent.
|
||||||
|
Checks that all declared dependencies are active first.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
record = await self._registry.activate(db, name)
|
record = await self._registry.activate(db, name)
|
||||||
@@ -106,7 +115,13 @@ class PluginService:
|
|||||||
else "Plugin activated successfully",
|
else "Plugin activated successfully",
|
||||||
}
|
}
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(str(exc)) from None
|
msg = str(exc)
|
||||||
|
if "dependencies" in msg.lower():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=msg,
|
||||||
|
) from None
|
||||||
|
raise ValueError(msg) from None
|
||||||
|
|
||||||
async def deactivate_plugin(
|
async def deactivate_plugin(
|
||||||
self,
|
self,
|
||||||
@@ -187,6 +202,59 @@ class PluginService:
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(str(exc)) from None
|
raise ValueError(str(exc)) from None
|
||||||
|
|
||||||
|
async def get_plugin_config(self, db: AsyncSession, name: str) -> dict[str, Any]:
|
||||||
|
"""Get the configuration for a plugin.
|
||||||
|
|
||||||
|
Returns the plugin's config field parsed from JSON string.
|
||||||
|
"""
|
||||||
|
record = await self._registry._get_plugin_record(db, name)
|
||||||
|
if record is None:
|
||||||
|
raise ValueError(f"Plugin '{name}' is not installed")
|
||||||
|
import json
|
||||||
|
config_str = getattr(record, "config", None) or "{}"
|
||||||
|
try:
|
||||||
|
return json.loads(config_str) if isinstance(config_str, str) else config_str or {}
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def update_plugin_config(
|
||||||
|
self,
|
||||||
|
db: AsyncSession,
|
||||||
|
name: str,
|
||||||
|
config: dict[str, Any],
|
||||||
|
tenant_id: uuid.UUID | None = None,
|
||||||
|
user_id: uuid.UUID | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Update the configuration for a plugin.
|
||||||
|
|
||||||
|
Stores the config as a JSON string in the plugin's config field.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
record = await self._registry._get_plugin_record(db, name)
|
||||||
|
if record is None:
|
||||||
|
raise ValueError(f"Plugin '{name}' is not installed")
|
||||||
|
|
||||||
|
record.config = json.dumps(config)
|
||||||
|
await db.flush()
|
||||||
|
|
||||||
|
if tenant_id and user_id:
|
||||||
|
await log_audit(
|
||||||
|
db,
|
||||||
|
tenant_id,
|
||||||
|
user_id,
|
||||||
|
action="plugin.update_config",
|
||||||
|
entity_type="plugin",
|
||||||
|
entity_id=getattr(record, "id", None),
|
||||||
|
changes={"name": name, "config": config},
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"config": config,
|
||||||
|
"message": "Plugin configuration updated successfully",
|
||||||
|
}
|
||||||
|
|
||||||
def get_manifest_schema(self) -> dict[str, Any]:
|
def get_manifest_schema(self) -> dict[str, Any]:
|
||||||
"""Return the manifest schema documentation."""
|
"""Return the manifest schema documentation."""
|
||||||
from app.plugins.manifest import MANIFEST_SCHEMA_DOC
|
from app.plugins.manifest import MANIFEST_SCHEMA_DOC
|
||||||
|
|||||||
Reference in New Issue
Block a user