Skip to content

BasePlugin

Asterios Raptis edited this page Mar 27, 2026 · 2 revisions

BasePlugin

BasePlugin is the abstract base class that all PluginForge plugins must inherit from.

Class Attributes

Attribute Type Default Description
name str (required) Unique plugin identifier
version str "0.1.0" Plugin version
api_version str "1" Hook spec compatibility version
description str "" Human-readable description
author str "" Plugin author
depends_on list[str] [] Plugin names this plugin depends on
app_config dict {} Global app config (populated by init())
config dict {} Plugin-specific config (populated by init())
config_schema dict[str, type] | None None Optional config type validation schema

Lifecycle Methods

init(app_config, plugin_config)

Called when the plugin is loaded. Receives both the global application config and the plugin-specific config from YAML. The default implementation stores both:

def init(self, app_config: dict, plugin_config: dict) -> None:
    self.app_config = app_config
    self.config = plugin_config

Override to perform custom initialization:

def init(self, app_config: dict, plugin_config: dict) -> None:
    super().init(app_config, plugin_config)
    self.db_url = app_config.get("database", {}).get("url")
    self.output_dir = self.config.get("output_dir", "/tmp")

activate()

Called after init() when the plugin is activated. Use this to start services, open connections, or register resources.

def activate(self) -> None:
    self.connection = create_connection(self.db_url)

deactivate()

Called when the plugin is being shut down. Release resources here.

def deactivate(self) -> None:
    if self.connection:
        self.connection.close()

Optional Extension Methods

get_routes()

Return a list of FastAPI APIRouter instances. Only relevant when using FastAPI integration.

from fastapi import APIRouter

def get_routes(self) -> list:
    router = APIRouter()

    @router.get("/hello")
    def hello():
        return {"message": self.config.get("greeting", "Hello")}

    return [router]

get_frontend_manifest()

Return a dict describing frontend UI components. Useful for apps with plugin-driven UIs.

def get_frontend_manifest(self) -> dict | None:
    return {
        "components": ["ExportDialog", "FormatSelector"],
        "css": "/static/plugins/export/style.css",
    }

Returns None by default.

health()

Return plugin health status. Override to check external dependencies (APIs, databases, etc.).

def health(self) -> dict:
    try:
        self.api_client.ping()
        return {"status": "ok", "latency_ms": 12}
    except Exception as e:
        return {"status": "error", "error": str(e)}

Returns {"status": "ok"} by default.

get_migrations_dir()

Return the path to Alembic migration scripts. Only relevant when using Alembic integration.

def get_migrations_dir(self) -> str | None:
    return str(Path(__file__).parent / "migrations")

Config Schema Validation

Plugins can declare expected config types via config_schema. Validation happens automatically during init():

class ExportPlugin(BasePlugin):
    name = "export"
    config_schema = {
        "pandoc_path": str,
        "toc_depth": int,
        "default_format": str,
    }

If a config value has the wrong type, the plugin is skipped and the error is reported in pm.get_load_errors(). Missing keys are not an error - only present keys with wrong types are rejected.

Complete Example

import pluggy
from pluginforge import BasePlugin

hookimpl = pluggy.HookimplMarker("myapp")

class ExportPlugin(BasePlugin):
    name = "export"
    version = "1.0.0"
    description = "Export documents to various formats"
    author = "Asterios Raptis"
    depends_on = ["storage"]
    config_schema = {"formats": list, "pandoc_path": str}

    def init(self, app_config: dict, plugin_config: dict) -> None:
        super().init(app_config, plugin_config)
        self.formats = self.config.get("formats", ["pdf"])

    def activate(self) -> None:
        self.engine = self._create_engine()

    def deactivate(self) -> None:
        self.engine = None

    def health(self) -> dict:
        return {"status": "ok", "formats": self.formats}

    @hookimpl
    def on_document_save(self, document: dict) -> None:
        for fmt in self.formats:
            self.engine.export(document, fmt)

Design Note: BasePlugin + pluggy

A plugin is both:

  • A class inheriting BasePlugin (lifecycle: init, activate, deactivate)
  • An object with @hookimpl-decorated methods (pluggy hook system)

PluginForge manages the lifecycle around the plugin. pluggy manages the hooks. These are complementary, not competing.

Clone this wiki locally