219 lines
7.7 KiB
Markdown
Executable File
219 lines
7.7 KiB
Markdown
Executable File
# 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. |