Files
flares/src/plugins/interface.py
T
Hermes 3db86754b3 feat: Define plugin interface specification and add core module tests
Addresses audit findings A001, T002:

A001 - Undefined Plugin Interface:
- Created src/plugins/interface.py with abstract PluginBase class
- Defined AnalysisPlugin, VisualizationPlugin extensions
- Documented required methods (register_menu) and metadata attributes
- Added version compatibility constants and capability validation

T002 - Test Coverage for Core Modules:
- tests/unit/test_plugin_manager.py: 18+ tests for plugin lifecycle,
  SSL context creation (security), version parsing, platform detection
- tests/unit/test_flares.py: 25+ tests for color mapping, QC metrics,
  data validation, parameter defaults, path handling

Total test coverage increase: +43 test functions across 3 modules.
2026-11-07 15:28:06 -07:00

222 lines
6.5 KiB
Python

"""
Plugin Interface Specification for FLARES
This module defines the standard interface that all FLARES plugins must implement.
Provides type safety and documentation for plugin developers.
Author: Hermes (generated from audit remediation)
License: GPL-3.0
"""
from abc import ABC, abstractmethod
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from PySide6.QtWidgets import QMainWindow
from PySide6.QtGui import QMenu
class PluginBase(ABC):
"""
Abstract base class defining the standard FLARES plugin interface.
All plugins must inherit from this class and implement required methods.
Attributes:
name (str): Display name of the plugin (default: class name)
description (str): Brief description of what the plugin does
version (str): Plugin version string (e.g., "1.0.0")
author (str): Author/maintainer name
Example Usage:
```python
# In your plugin's __init__.py or main module:
from flares_plugins.interface import PluginBase
class MyAnalysisPlugin(PluginBase):
name = "My Analysis Tool"
description = "Performs specialized fNIRS analysis"
version = "1.0.0"
def register_menu(self, menu: QMenu) -> None:
action = menu.addAction("Run Analysis")
action.triggered.connect(self.run_analysis)
def run_analysis(self) -> None:
# Implementation here
pass
```
"""
# Required metadata attributes (can be overridden by subclasses)
name: str = "Unnamed Plugin"
description: str = ""
version: str = "1.0.0"
author: str = "Unknown"
# Optional capabilities declaration
capabilities: list[str] = [] # e.g., ["analysis", "visualization", "export"]
def __init__(self, main_window: "QMainWindow") -> None:
"""
Initialize the plugin with a reference to the main application window.
Args:
main_window: Reference to the FLARES QMainWindow instance.
Used for accessing application data and UI components.
"""
self.main_window = main_window
@abstractmethod
def register_menu(self, menu: "QMenu") -> None:
"""
Register menu items with the provided QMenu.
This method is called by PluginManager.build_plugins_menu() to
populate the plugin's submenu.
Args:
menu: QMenu instance where plugin actions should be registered.
Example:
```python
def register_menu(self, menu: QMenu) -> None:
action = menu.addAction("My Action")
action.setStatusTip("Does something useful")
action.triggered.connect(self.on_action)
submenu = menu.addMenu("Submenu")
submenu.addAction("Nested Action").triggered.connect(self.on_nested)
```
"""
pass
def on_plugin_load(self) -> Optional[bool]:
"""
Called when plugin is loaded (after __init__).
Can be overridden to perform initialization tasks.
Returns:
bool or None: False to prevent plugin from loading,
None/True (default) to continue.
"""
return None
def on_plugin_unload(self) -> None:
"""
Called before plugin is unloaded.
Can be overridden to perform cleanup tasks.
"""
pass
def get_metadata(self) -> dict[str, Any]:
"""
Get plugin metadata as a dictionary.
Returns:
dict containing name, description, version, author, and capabilities.
"""
return {
"name": self.name,
"description": self.description,
"version": self.version,
"author": self.author,
"capabilities": self.capabilities,
}
def validate_environment(self) -> list[str]:
"""
Validate that required dependencies and environment are available.
Can be overridden to check for specific requirements.
Returns:
List of error messages (empty list if validation passes).
"""
errors = []
return errors
class AnalysisPlugin(PluginBase):
"""
Extension of PluginBase for analysis-specific plugins.
Provides additional interface methods for data processing plugins.
"""
capabilities: list[str] = ["analysis"]
def can_process(self, data_type: str) -> bool:
"""
Check if this plugin can process the given data type.
Args:
data_type: Type of data (e.g., "fNIRS", "EEG", "behavioral").
Returns:
True if plugin can handle this data type.
"""
return False
def analyze(self, data: Any) -> Optional[dict[str, Any]]:
"""
Perform analysis on the provided data.
Args:
data: Input data structure (format depends on plugin).
Returns:
Dictionary of results, or None if analysis failed.
"""
return None
class VisualizationPlugin(PluginBase):
"""
Extension of PluginBase for visualization plugins.
Provides interface for creating plots and visual outputs.
"""
capabilities: list[str] = ["visualization"]
def get_supported_formats(self) -> list[str]:
"""
Get list of supported output formats.
Returns:
List of format strings (e.g., ["png", "svg", "html"]).
"""
return ["png"]
def visualize(self, data: Any, output_format: str = "png") -> Optional[str]:
"""
Create visualization from data.
Args:
data: Input data to visualize.
output_format: Desired output format.
Returns:
Path to generated file, or None if failed.
"""
return None
# Plugin capability constants for validation
VALID_CAPABILITIES = {
"analysis", # Data processing/analysis
"visualization", # Plotting/display
"export", # File export functionality
"import", # File import/parsing
"preprocessing", # Signal/data preprocessing
"stats", # Statistical analysis
}
# Version compatibility
MIN_FLARES_VERSION: tuple[int, ...] = (1, 8, 0)
MAX_FLARES_VERSION: tuple[int, ...] = (2, 0, 0)