From 3bbd2dcb48439e03871f215c64453c05d059af29 Mon Sep 17 00:00:00 2001 From: RobinWts Date: Fri, 13 Feb 2026 12:40:52 +0100 Subject: [PATCH 1/2] dynamic plugin blueprint registration --- src/inkypi.py | 5 ++++- src/plugins/plugin_registry.py | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/inkypi.py b/src/inkypi.py index 5dc4de57b..62fbc37ec 100755 --- a/src/inkypi.py +++ b/src/inkypi.py @@ -31,7 +31,7 @@ from blueprints.playlist import playlist_bp from blueprints.apikeys import apikeys_bp from jinja2 import ChoiceLoader, FileSystemLoader -from plugins.plugin_registry import load_plugins +from plugins.plugin_registry import load_plugins, get_plugin_instance, register_plugin_blueprints from waitress import serve @@ -81,6 +81,9 @@ app.register_blueprint(playlist_bp) app.register_blueprint(apikeys_bp) + +# Register blueprints from plugins (generic mechanism - any plugin can expose blueprints) +register_plugin_blueprints(app) # Register opener for HEIF/HEIC images register_heif_opener() diff --git a/src/plugins/plugin_registry.py b/src/plugins/plugin_registry.py index 80f5a8d70..7c0f77056 100644 --- a/src/plugins/plugin_registry.py +++ b/src/plugins/plugin_registry.py @@ -49,4 +49,23 @@ def get_plugin_instance(plugin_config): # Initialize the plugin with its configuration return plugin_class else: - raise ValueError(f"Plugin '{plugin_id}' is not registered.") \ No newline at end of file + raise ValueError(f"Plugin '{plugin_id}' is not registered.") + +def register_plugin_blueprints(app): + """Register blueprints from plugins that expose them via get_blueprint() method. + + This is a generic mechanism that allows any plugin to register Flask blueprints + by implementing a get_blueprint() class method that returns a Blueprint instance. + + Args: + app: Flask application instance to register blueprints with + """ + for plugin_id, plugin_instance in PLUGIN_CLASSES.items(): + try: + if hasattr(plugin_instance, 'get_blueprint'): + bp = plugin_instance.get_blueprint() + if bp: + app.register_blueprint(bp) + logger.info(f"Registered blueprint for plugin '{plugin_id}'") + except Exception as e: + logger.warning(f"Failed to register blueprint for plugin '{plugin_id}': {e}") From ad5101cd9f8a9ef1020b831b450ecb2051cc0f13 Mon Sep 17 00:00:00 2001 From: RobinWts Date: Fri, 13 Feb 2026 12:44:31 +0100 Subject: [PATCH 2/2] Add documentation for optional API route implementation in plugins in plugin dev-doc. --- docs/building_plugins.md | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/building_plugins.md b/docs/building_plugins.md index 19231adcc..a4b7eb9c8 100644 --- a/docs/building_plugins.md +++ b/docs/building_plugins.md @@ -97,10 +97,53 @@ plugins/{plugin_id}/ ├── plugin-info.json # Plugin manifest file ├── icon.png # Plugin icon ├── settings.html # Optional: Plugin settings page (if applicable) + ├── api.py # Optional: API routes (see Adding API routes below) ├── render/ # Optional: If generating images from html and css files, store them here └── {other files/resources} # Any additional files or resources used by the plugin ``` +## Adding API routes (optional) + +If your plugin needs its own HTTP endpoints (for example, to receive webhooks, to be controlled by external automation, or to offer a small JSON API), you can register a Flask Blueprint. The core will discover and register it at startup so your routes are available without any changes to InkyPi core code. + +**When to use this:** + +- Your plugin needs endpoints that other systems call (e.g. Home Assistant, Node-RED, IFTTT, or custom scripts). +- You want to receive webhooks or callbacks (e.g. calendar events, GitHub notifications). +- You need a minimal API for your plugin's config or actions (e.g. backup tools, dashboards). + +**How it works:** InkyPi calls `register_plugin_blueprints(app)` after loading plugins. For each plugin that implements a `get_blueprint()` class method returning a Flask Blueprint, that blueprint is registered with the app. This is optional; plugins that don't implement `get_blueprint()` are unchanged. + +### Implementing API routes + +1. **Create a Blueprint** in your plugin directory (e.g. `api.py`): + + ```python + from flask import Blueprint, request, jsonify + + myplugin_bp = Blueprint("myplugin_api", __name__) + + @myplugin_bp.route("/myplugin-api/do-something", methods=["POST"]) + def do_something(): + data = request.get_json() or {} + # Your logic here + return jsonify({"success": True}) + ``` + +2. **Expose the Blueprint** from your plugin class by implementing `get_blueprint()`: + + ```python + class MyPlugin(BasePlugin): + @classmethod + def get_blueprint(cls): + from . import api + return api.myplugin_bp + ``` + +3. **Naming convention:** Use a URL prefix that includes your plugin id (e.g. `/-api/...`) to avoid clashes with core and other plugins. + +The Plugin Manager plugin is a full example: it uses a blueprint to expose install, uninstall, and update endpoints under `/pluginmanager-api/`. + ## Prepopulating forms for Plugin Instances When a plugin is added to a playlist, a "Plugin Instance" is created, and its settings are stored in the `src/config/device.json` file. These settings can be updated from the playlist page, so the form in settings.html should be prepopulated with the existing settings.