Files
ss-tools/docs/plugin_dev.md
2026-02-08 22:53:54 +03:00

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.