"""Base plugin class that all plugins must inherit from.""" from abc import ABC, abstractmethod from typing import List, Dict, Optional, Type from dataclasses import dataclass, field from flask import Flask, Blueprint @dataclass class PluginMeta: """Plugin metadata container.""" name: str version: str description: str author: str = "" dependencies: List[str] = field(default_factory=list) core_version: str = ">=1.0.0" api_prefix: str = None def __post_init__(self): if self.api_prefix is None: self.api_prefix = f"/api/{self.name.replace('_', '-')}" class BasePlugin(ABC): """ Base class for all ShopDB plugins. Plugins must implement: - meta: PluginMeta instance - get_blueprint(): Return Flask Blueprint for API routes - get_models(): Return list of SQLAlchemy model classes Optionally implement: - init_app(app, db): Custom initialization - get_cli_commands(): Return Click commands - get_services(): Return service classes - on_install(): Called when plugin is installed - on_uninstall(): Called when plugin is uninstalled - on_enable(): Called when plugin is enabled - on_disable(): Called when plugin is disabled """ @property @abstractmethod def meta(self) -> PluginMeta: """Return plugin metadata.""" pass @abstractmethod def get_blueprint(self) -> Optional[Blueprint]: """Return Flask Blueprint with API routes.""" pass @abstractmethod def get_models(self) -> List[Type]: """Return list of SQLAlchemy model classes.""" pass def init_app(self, app: Flask, db) -> None: """ Initialize plugin with Flask app. Override for custom initialization. """ pass def get_cli_commands(self) -> List: """Return list of Click command groups/commands.""" return [] def get_services(self) -> Dict[str, Type]: """Return dict of service name -> service class.""" return {} def get_event_handlers(self) -> Dict[str, callable]: """Return dict of event name -> handler function.""" return {} def on_install(self, app: Flask) -> None: """Called when plugin is installed via CLI.""" pass def on_uninstall(self, app: Flask) -> None: """Called when plugin is uninstalled via CLI.""" pass def on_enable(self, app: Flask) -> None: """Called when plugin is enabled.""" pass def on_disable(self, app: Flask) -> None: """Called when plugin is disabled.""" pass def get_dashboard_widgets(self) -> List[Dict]: """ Return dashboard widget definitions. Each widget: { 'name': str, 'component': str, # Frontend component name 'endpoint': str, # API endpoint for data 'size': str, # 'small', 'medium', 'large' 'position': int # Order on dashboard } """ return [] def get_navigation_items(self) -> List[Dict]: """ Return navigation menu items. Each item: { 'name': str, 'icon': str, 'route': str, 'position': int, 'children': [] } """ return [] def get_searchable_fields(self) -> List[Dict]: """ Return fields this plugin contributes to global search. Each field: { 'model': Type, # SQLAlchemy model class 'field': str, # Column name to search 'result_type': str, # Type identifier for search results 'url_template': str, # URL template with {id} placeholder 'title_field': str, # Field to use for result title 'subtitle_field': str, # Optional field for subtitle 'relevance_boost': int # Optional relevance score multiplier } Example for equipment plugin: return [{ 'model': Equipment, 'join_model': Asset, 'join_condition': Equipment.assetid == Asset.assetid, 'search_fields': ['assetnumber', 'name', 'serialnumber'], 'result_type': 'equipment', 'url_template': '/equipment/{id}', 'title_field': 'assetnumber', 'subtitle_field': 'name', }] """ return []