# Plugin Development Guide This guide explains how to create new plugins for the Superset Tools application. ## 1. Plugin Structure A plugin is a single Python file located in the `backend/src/plugins/` directory. Each plugin file must contain a class that inherits from `PluginBase`. ## 2. Implementing `PluginBase` The `PluginBase` class is an abstract base class that defines the interface for all plugins. You must implement the following properties and methods: - **`id`**: A unique string identifier for your plugin (e.g., `"my-cool-plugin"`). - **`name`**: A human-readable name for your plugin (e.g., `"My Cool Plugin"`). - **`description`**: A brief description of what your plugin does. - **`version`**: The version of your plugin (e.g., `"1.0.0"`). - **`get_schema()`**: A method that returns a JSON schema dictionary defining the input parameters for your plugin. This schema is used to automatically generate a form in the frontend. - **`execute(params: Dict[str, Any])`**: An `async` method that contains the main logic of your plugin. The `params` argument is a dictionary containing the input data from the user, validated against the schema you defined. ## 3. Example Plugin Here is an example of a simple "Hello World" plugin: ```python # backend/src/plugins/hello.py # [DEF:HelloWorldPlugin:Plugin] # @SEMANTICS: hello, world, example, plugin # @PURPOSE: A simple "Hello World" plugin example. # @LAYER: Domain (Plugin) # @RELATION: Inherits from PluginBase # @PUBLIC_API: execute from typing import Dict, Any from ..core.plugin_base import PluginBase class HelloWorldPlugin(PluginBase): @property def id(self) -> str: return "hello-world" @property def name(self) -> str: return "Hello World" @property def description(self) -> str: return "A simple plugin that prints a greeting." @property def version(self) -> str: return "1.0.0" def get_schema(self) -> Dict[str, Any]: return { "type": "object", "properties": { "name": { "type": "string", "title": "Name", "description": "The name to greet.", "default": "World", } }, "required": ["name"], } async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): name = params["name"] if context: context.logger.info(f"Hello, {name}!") else: print(f"Hello, {name}!") ``` ## 4. Logging with TaskContext Plugins now support TaskContext for structured logging with source attribution. The `context` parameter provides access to a logger that automatically tags logs with the task ID and a source identifier. ### 4.1. Basic Logging Use `context.logger` to log messages with automatic source attribution: ```python from typing import Dict, Any, Optional from ..core.plugin_base import PluginBase from ..core.task_manager.context import TaskContext async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): if context: # Use TaskContext logger for structured logging context.logger.info("My plugin is running!") else: # Fallback to global logger for backward compatibility from ..core.logger import logger logger.info("My plugin is running!") ``` ### 4.2. Source Attribution For better log organization, create sub-loggers for different components: ```python async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): if context: # Create sub-loggers for different components api_log = context.logger.with_source("api") storage_log = context.logger.with_source("storage") api_log.info("Connecting to API...") storage_log.info("Saving file...") else: # Fallback to global logger from ..core.logger import logger logger.info("My plugin is running!") ``` ### 4.3. Log Levels The logger supports standard log levels. Use them appropriately: | Level | Usage | |-------|-------| | `DEBUG` | Detailed diagnostic information (API responses, internal state). Only visible when log level is set to DEBUG. | | `INFO` | General operational messages (start/complete notifications, progress updates). | | `WARNING` | Non-critical issues that don't stop execution (deprecated APIs, retry attempts). | | `ERROR` | Failures that prevent an operation from completing (API errors, validation failures). | ```python # Good: Use DEBUG for verbose diagnostic info api_log.debug(f"API response: {response.json()}") # Good: Use INFO for operational milestones log.info(f"Starting backup for environment: {env}") # Good: Use WARNING for recoverable issues log.warning(f"Rate limit hit, retrying in {delay}s") # Good: Use ERROR for failures log.error(f"Failed to connect to database: {e}") ``` ### 4.4. Progress Logging For operations that report progress, use the `progress` method: ```python async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): if context: total_items = 100 for i, item in enumerate(items): # Report progress with percentage percent = (i + 1) / total_items * 100 context.logger.progress(f"Processing {item}", percent=percent) else: # Fallback from ..core.logger import logger logger.info("My plugin is running!") ``` ### 4.5. Logging with Metadata You can include structured metadata with log entries: ```python async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): if context: context.logger.error( "Operation failed", metadata={"error_code": 500, "details": "Connection timeout"} ) else: from ..core.logger import logger logger.error("Operation failed") ``` ### 4.6. Common Source Names For consistency across plugins, use these standard source names: | Source | Usage | |--------|-------| | `superset_api` | Superset REST API calls | | `postgres` | PostgreSQL database operations | | `storage` | File system operations | | `git` | Git operations | | `llm` | LLM API calls | | `screenshot` | Screenshot capture operations | | `migration` | Migration-specific logic | | `backup` | Backup operations | | `debug` | Debug/diagnostic operations | | `search` | Search operations | ### 4.7. Best Practices 1. **Always check for context**: Support backward compatibility by checking if `context` is available: ```python log = context.logger if context else logger ``` 2. **Use source attribution**: Create sub-loggers for different components to make filtering easier in the UI. 3. **Use appropriate log levels**: - `DEBUG`: Verbose diagnostic info (API responses, internal state) - `INFO`: Operational milestones (start, complete, progress) - `WARNING`: Recoverable issues (rate limits, deprecated APIs) - `ERROR`: Failures that stop an operation 4. **Log progress for long operations**: Use `progress()` for operations that take time: ```python for i, item in enumerate(items): percent = (i + 1) / len(items) * 100 log.progress(f"Processing {item}", percent=percent) ``` 5. **Keep DEBUG logs verbose, INFO logs concise**: DEBUG logs can include full API responses, while INFO logs should be one-line summaries. ## 5. Testing To test your plugin, simply run the application and navigate to the web UI. Your plugin should appear in the list of available tools.