-
Notifications
You must be signed in to change notification settings - Fork 0
PluginManager
PluginManager is the central class that orchestrates configuration, discovery, lifecycle, and hooks.
PluginManager(
config_path: str = "config/app.yaml",
pre_activate: Callable[[BasePlugin, dict], bool] | None = None,
api_version: str = "1",
)| Parameter | Description |
|---|---|
config_path |
Path to app.yaml configuration file |
pre_activate |
Optional callback (plugin, config) -> bool called before activation. Return False to reject a plugin. |
api_version |
Current hook spec version. Plugins with a different api_version will log a warning but still load. |
On creation, the manager:
- Loads the app config from
config_path - Creates a pluggy.PluginManager with the
entry_point_groupfrom config - Initializes the lifecycle tracker
- Initializes i18n with the
default_languagefrom config
Returns the loaded application configuration.
Loads and returns the config for a specific plugin from config/plugins/{name}.yaml.
Reload application config from disk. Reloads app.yaml and clears the i18n cache. Active plugins are not affected - call deactivate_all() + discover_plugins() to fully restart with new config.
pm.reload_config()Return names of all discoverable plugins from entry points without loading them. Useful for settings UIs.
available = pm.list_available_plugins()
# ["export", "analytics", "grammar"]Full automatic pipeline:
- Load plugins from entry points
- Filter by enabled/disabled config
- Check and skip plugins with missing dependencies
- Topologically sort by dependencies
- Init and activate each plugin
pm = PluginManager("config/app.yaml")
pm.discover_plugins()Register plugin classes directly without entry point discovery. Useful for testing or programmatic registration. Applies the same filtering, dependency checking, and lifecycle as discover_plugins().
pm.register_plugins([HelloPlugin, ExportPlugin])Register a single pre-instantiated plugin. Useful for tests or dynamically created plugins. If plugin_config is not provided, it is loaded from YAML.
plugin = HelloPlugin()
pm.register_plugin(plugin)
# Or with explicit config:
pm.register_plugin(plugin, plugin_config={"greeting": "Hi"})Activate a specific initialized plugin by name.
Deactivate a specific active plugin and unregister its hooks from pluggy. After deactivation, the plugin's @hookimpl methods are no longer called.
Deactivate all active plugins in reverse activation order (LIFO) and unregister hooks.
Hot-reload a plugin: deactivate, re-import module, re-init, activate. Code changes on disk take effect without restarting the application.
success = pm.reload_plugin("export")Returns True on success, False on failure (check get_load_errors() for details).
Get a plugin instance by name. Returns None if not found.
Return all currently active plugins.
Return errors from plugin loading/activation. Maps plugin name to error message. Tracks missing dependencies, init failures, activation failures, and pre-activate rejections.
errors = pm.get_load_errors()
# {"premium": "Rejected by pre-activate check", "broken": "Failed to initialize"}Run health checks on all active plugins. Each plugin's health() method is called. Exceptions are caught and reported.
status = pm.health_check()
# {"export": {"status": "ok"}, "grammar": {"status": "error", "error": "API unreachable"}}Register hook specifications from a module containing @hookspec-decorated functions.
pm.register_hookspecs(my_hookspecs)Call a named hook on all registered plugins. Returns a list of results. Logs a warning if the hook is not found. If any implementation throws, returns [].
results = pm.call_hook("on_document_save", document=doc)Call a hook, executing each implementation individually. Failed implementations are logged and skipped, others still execute. Recommended for non-critical hooks where partial results are acceptable.
results = pm.call_hook_safe("on_document_save", document=doc)Return hook names implemented by a specific plugin. Useful for debugging and settings UIs.
hooks = pm.get_plugin_hooks("export")
# ["export_execute", "on_document_save"]Return all registered hook spec names.
names = pm.get_all_hook_names()
# ["on_startup", "on_shutdown", "on_document_save"]Return all active plugins that implement a given extension point (class or ABC). See Extensions for the full pattern.
formats = pm.get_extensions(ExportFormat)Mount routes from all active plugins onto a FastAPI application. The prefix parameter controls the URL prefix (default: /api). Plugins bring their own route prefixes via their routers.
from fastapi import FastAPI
app = FastAPI()
pm.mount_routes(app)
pm.mount_routes(app, prefix="/v2/api") # custom prefixGet an internationalized string by dot-notation key. Falls back to default language if not found.
pm.get_text("common.save", "de") # "Speichern"Collect Alembic migration directories from all active plugins. Returns a dict mapping plugin name to directory path.
from pluginforge import PluginManager
# Setup with pre-activate callback (e.g. license check)
def check_license(plugin, config):
if plugin.name in premium_plugins:
return validate_license(plugin.name)
return True
pm = PluginManager(
"config/app.yaml",
pre_activate=check_license,
api_version="2",
)
pm.register_hookspecs(my_hooks)
pm.discover_plugins()
# Check for errors
for name, error in pm.get_load_errors().items():
print(f"Plugin '{name}' failed: {error}")
# Use hooks
results = pm.call_hook("on_startup")
# Query extension points
exporters = pm.get_extensions(ExportFormat)
# Health check
status = pm.health_check()
# i18n
title = pm.get_text("app.title", "de")
# FastAPI (optional)
from fastapi import FastAPI
app = FastAPI()
pm.mount_routes(app)
# Hot-reload during development
pm.reload_plugin("export")
# Shutdown
pm.deactivate_all()