# [DEF:DebugPluginModule:Module] # @SEMANTICS: plugin, debug, api, database, superset # @PURPOSE: Implements a plugin for system diagnostics and debugging Superset API responses. # @LAYER: Plugins # @RELATION: Inherits from PluginBase. Uses SupersetClient from core. # @RELATION: USES -> TaskContext # @CONSTRAINT: Must use belief_scope for logging. # [SECTION: IMPORTS] from typing import Dict, Any, Optional from ..core.plugin_base import PluginBase from ..core.superset_client import SupersetClient from ..core.logger import logger, belief_scope from ..core.task_manager.context import TaskContext # [/SECTION] # [DEF:DebugPlugin:Class] # @PURPOSE: Plugin for system diagnostics and debugging. class DebugPlugin(PluginBase): """ Plugin for system diagnostics and debugging. """ @property # [DEF:id:Function] # @PURPOSE: Returns the unique identifier for the debug plugin. # @PRE: Plugin instance exists. # @POST: Returns string ID. # @RETURN: str - "system-debug" def id(self) -> str: with belief_scope("id"): return "system-debug" # [/DEF:id:Function] @property # [DEF:name:Function] # @PURPOSE: Returns the human-readable name of the debug plugin. # @PRE: Plugin instance exists. # @POST: Returns string name. # @RETURN: str - Plugin name. def name(self) -> str: with belief_scope("name"): return "System Debug" # [/DEF:name:Function] @property # [DEF:description:Function] # @PURPOSE: Returns a description of the debug plugin. # @PRE: Plugin instance exists. # @POST: Returns string description. # @RETURN: str - Plugin description. def description(self) -> str: with belief_scope("description"): return "Run system diagnostics and debug Superset API responses." # [/DEF:description:Function] @property # [DEF:version:Function] # @PURPOSE: Returns the version of the debug plugin. # @PRE: Plugin instance exists. # @POST: Returns string version. # @RETURN: str - "1.0.0" def version(self) -> str: with belief_scope("version"): return "1.0.0" # [/DEF:version:Function] @property # [DEF:ui_route:Function] # @PURPOSE: Returns the frontend route for the debug plugin. # @RETURN: str - "/tools/debug" def ui_route(self) -> str: with belief_scope("ui_route"): return "/tools/debug" # [/DEF:ui_route:Function] # [DEF:get_schema:Function] # @PURPOSE: Returns the JSON schema for the debug plugin parameters. # @PRE: Plugin instance exists. # @POST: Returns dictionary schema. # @RETURN: Dict[str, Any] - JSON schema. def get_schema(self) -> Dict[str, Any]: with belief_scope("get_schema"): return { "type": "object", "properties": { "action": { "type": "string", "title": "Action", "enum": ["test-db-api", "get-dataset-structure"], "default": "test-db-api" }, "env": { "type": "string", "title": "Environment", "description": "The Superset environment (for dataset structure)." }, "dataset_id": { "type": "integer", "title": "Dataset ID", "description": "The ID of the dataset (for dataset structure)." }, "source_env": { "type": "string", "title": "Source Environment", "description": "Source env for DB API test." }, "target_env": { "type": "string", "title": "Target Environment", "description": "Target env for DB API test." } }, "required": ["action"] } # [/DEF:get_schema:Function] # [DEF:execute:Function] # @PURPOSE: Executes the debug logic with TaskContext support. # @PARAM: params (Dict[str, Any]) - Debug parameters. # @PARAM: context (Optional[TaskContext]) - Task context for logging with source attribution. # @PRE: action must be provided in params. # @POST: Debug action is executed and results returned. # @RETURN: Dict[str, Any] - Execution results. async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None) -> Dict[str, Any]: with belief_scope("execute"): action = params.get("action") # Use TaskContext logger if available, otherwise fall back to app logger log = context.logger if context else logger debug_log = log.with_source("debug") if context else log superset_log = log.with_source("superset_api") if context else log debug_log.info(f"Executing debug action: {action}") if action == "test-db-api": return await self._test_db_api(params, superset_log) elif action == "get-dataset-structure": return await self._get_dataset_structure(params, superset_log) else: debug_log.error(f"Unknown action: {action}") raise ValueError(f"Unknown action: {action}") # [/DEF:execute:Function] # [DEF:_test_db_api:Function] # @PURPOSE: Tests database API connectivity for source and target environments. # @PRE: source_env and target_env params exist in params. # @POST: Returns DB counts for both envs. # @PARAM: params (Dict) - Plugin parameters. # @PARAM: log - Logger instance for superset_api source. # @RETURN: Dict - Comparison results. async def _test_db_api(self, params: Dict[str, Any], log) -> Dict[str, Any]: with belief_scope("_test_db_api"): source_env_name = params.get("source_env") target_env_name = params.get("target_env") if not source_env_name or not target_env_name: raise ValueError("source_env and target_env are required for test-db-api") from ..dependencies import get_config_manager config_manager = get_config_manager() results = {} for name in [source_env_name, target_env_name]: log.info(f"Testing database API for environment: {name}") env_config = config_manager.get_environment(name) if not env_config: log.error(f"Environment '{name}' not found.") raise ValueError(f"Environment '{name}' not found.") client = SupersetClient(env_config) client.authenticate() count, dbs = client.get_databases() log.debug(f"Found {count} databases in {name}") results[name] = { "count": count, "databases": dbs } return results # [/DEF:_test_db_api:Function] # [DEF:_get_dataset_structure:Function] # @PURPOSE: Retrieves the structure of a dataset. # @PRE: env and dataset_id params exist in params. # @POST: Returns dataset JSON structure. # @PARAM: params (Dict) - Plugin parameters. # @PARAM: log - Logger instance for superset_api source. # @RETURN: Dict - Dataset structure. async def _get_dataset_structure(self, params: Dict[str, Any], log) -> Dict[str, Any]: with belief_scope("_get_dataset_structure"): env_name = params.get("env") dataset_id = params.get("dataset_id") if not env_name or dataset_id is None: raise ValueError("env and dataset_id are required for get-dataset-structure") log.info(f"Fetching structure for dataset {dataset_id} in {env_name}") from ..dependencies import get_config_manager config_manager = get_config_manager() env_config = config_manager.get_environment(env_name) if not env_config: log.error(f"Environment '{env_name}' not found.") raise ValueError(f"Environment '{env_name}' not found.") client = SupersetClient(env_config) client.authenticate() dataset_response = client.get_dataset(dataset_id) log.debug(f"Retrieved dataset structure for {dataset_id}") return dataset_response.get('result') or {} # [/DEF:_get_dataset_structure:Function] # [/DEF:DebugPlugin:Class] # [/DEF:DebugPluginModule:Module]