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:
2026-07-03 16:48:10 +00:00
parent 826cf69c9a
commit cbeed4b26d
+70 -2
View File
@@ -6,6 +6,7 @@ import logging
import uuid
from typing import Any
from fastapi import HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.audit import log_audit
@@ -43,6 +44,7 @@ class PluginService:
Runs migrations, calls on_install hook, creates DB record.
Idempotent: returns existing record if already installed.
Checks that all declared dependencies are installed first.
"""
try:
record = await self._registry.install(db, name)
@@ -66,7 +68,13 @@ class PluginService:
"message": "Plugin installed successfully",
}
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:
raise MigrationValidationError(str(exc)) from None
@@ -80,6 +88,7 @@ class PluginService:
"""Activate a plugin by name.
Registers event listeners and routes. Idempotent.
Checks that all declared dependencies are active first.
"""
try:
record = await self._registry.activate(db, name)
@@ -106,7 +115,13 @@ class PluginService:
else "Plugin activated successfully",
}
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(
self,
@@ -187,6 +202,59 @@ class PluginService:
except ValueError as exc:
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]:
"""Return the manifest schema documentation."""
from app.plugins.manifest import MANIFEST_SCHEMA_DOC