Application-agnostic plugin framework built on pluggy
Project description
PluginForge
Application-agnostic Python plugin framework built on pluggy.
PluginForge adds the layers that pluggy is missing: YAML configuration, plugin lifecycle management, enable/disable per config, dependency resolution, FastAPI integration, and i18n support.
Installation
pip install pluginforge
FastAPI integration is built in; install FastAPI alongside PluginForge if your application uses it:
pip install pluginforge fastapi
(The [fastapi] extra existed in pre-0.6.0 releases and was a non-functional shim; it was removed in v0.6.0. Install FastAPI as a normal dependency of your consuming application.)
Quickstart
1. Create a plugin
from pluginforge import BasePlugin
class HelloPlugin(BasePlugin):
name = "hello"
version = "1.0.0"
description = "A hello world plugin"
def activate(self):
print(f"Hello plugin activated with config: {self.config}")
def get_routes(self):
from fastapi import APIRouter
router = APIRouter()
@router.get("/hello")
def hello():
return {"message": self.config.get("greeting", "Hello!")}
return [router]
2. Configure your app
# config/app.yaml
app:
name: "MyApp"
version: "1.0.0"
default_language: "en"
plugins:
entry_point_group: "myapp.plugins"
enabled:
- "hello"
disabled: []
# config/plugins/hello.yaml
greeting: "Hello from PluginForge!"
3. Use PluginManager
from pluginforge import PluginManager
pm = PluginManager("config/app.yaml")
# Register plugins directly (or use entry points for auto-discovery)
result = pm.register_plugins([HelloPlugin])
print(f"Activated: {result.activated}") # ['hello']
print(f"Filtered: {result.filtered_out()}") # {} when all activated
# Access plugins
for plugin in pm.get_active_plugins():
print(f"Active: {plugin.name} v{plugin.version}")
# Mount FastAPI routes
from fastapi import FastAPI
app = FastAPI()
pm.mount_routes(app) # Routes under /api/ (configurable prefix)
Features
- YAML Configuration - App config, per-plugin config, and i18n strings
- Plugin Lifecycle - init, activate, deactivate with error handling
- Structured Diagnostics -
DiscoveryResultsurfaces per-pluginPluginState; structuredPluginErrors carry the cause exception, lifecycle phase, and severity.DiscoveryResult.by_filter_reason(reason)and (v0.10.0)DiscoveryDiff.by_filter_reason(reason)group plugins by filter outcome in one call - Hot-Reload - Reload an active plugin's module via
reload_plugin(name)without restarting the app - Entry Point Rediscovery - Pick up newly-installed plugins at runtime via
rediscover(), no process restart required - Enable/Disable - Control plugins via config lists
- Live Config Refresh -
refresh_config()replaces the app-config snapshot and notifies active plugins through theon_config_changedhook. v0.10.0 addsmerge_app_config(overlay, *, notify=True)for deep-merge overlay semantics (replaces the_app_config = ...hack consumers were using) plus anotify=Falsekwarg onrefresh_configfor the no-active-plugins startup path - Dependency Resolution - Topological sorting with circular dependency detection
- Extension Points - Query plugins by interface with
get_extensions(type) - Config Schema Validation - Declare expected config types per plugin
- Health Checks - Monitor plugin status via
health_check() - Pre-Activate Hooks - Reject plugins before activation (license checks, etc.)
- Version Gating - Enforce
api_versionandmin_app_versionwith configurable severity - Application Identity Gating - Declare
target_applicationon plugins andapp_idon the host. Plugins whosetarget_applicationmismatches the host'sapp_id, or whosetarget_applicationis not declared at all, refuse to activate. Permissive: hosts withoutapp_idsee no validation - Lifecycle Visibility -
PluginStatecarriesactivated_at/last_config_change/sourcetimestamps;inspect_plugin(name)aggregates state, config, health, hooks, routes, and identity into one snapshot;on_plugin_activated/on_plugin_deactivated/on_config_refreshedevent hooks notify subscribers after lifecycle transitions - FastAPI Integration - Mount plugin routes with configurable prefix
- Idempotent Route Mounting (v0.8.0) -
mount_routesis safe to re-call; no route-table accumulation acrossTestClientlifespans (closes the v0.7.0 recursion-cascade reported by downstream consumers) - Test Helpers (v0.8.0) -
pluginforge.testing.IsolatedPluginManagerandMockPluginfor consumer-app test wiring - Single-Router Convention (v0.8.0) - one router per
get_routes()is recommended; multi-router plugins emit aDeprecationWarning - Type Annotations (PEP 561) -
py.typedmarker shipped;mypy/pyrightconsume PluginForge's full type information - Alembic Support - Collect migration directories from plugins
- i18n - Multi-language strings from YAML with fallback
- Security - Plugin name validation and path traversal prevention
For detailed documentation, see the Wiki.
Entry Point Discovery
Register plugins as entry points in your pyproject.toml:
[project.entry-points."myapp.plugins"]
hello = "myapp.plugins.hello:HelloPlugin"
Then use discover_plugins() instead of register_plugins():
pm = PluginManager("config/app.yaml")
result = pm.discover_plugins() # Auto-discovers from entry points
# Later, after a new plugin is installed (e.g. poetry install in another shell):
diff = pm.rediscover()
print(f"Newly activated: {diff.added}")
print(f"Removed: {diff.removed}")
i18n
# config/i18n/en.yaml
common:
save: "Save"
cancel: "Cancel"
pm.get_text("common.save", "en") # "Save"
pm.get_text("common.save", "de") # "Speichern"
Documentation
The full documentation is available in the Wiki and the in-repo guides:
- Consumer Integration Guide - wire PluginForge into your FastAPI app from scratch (covers lifespan, mounting, hot reload, testing)
- Plugin Author Guide - write a PluginForge plugin (BasePlugin contract, identity gating, single-router convention, lifecycle hooks)
- Getting Started
- BasePlugin
- PluginManager
- Configuration
- Discovery and Dependencies
- Lifecycle
- Hooks
- Extensions
- FastAPI Integration
- Alembic Integration
- i18n
- Security
- Examples
- Changelog
- Roadmap
Development
make install-dev # Install with dev dependencies
make test # Run tests
make lint # Run ruff linter
make format # Format code
make ci # Full CI pipeline (lint + format-check + test)
make help # Show all available targets
Pre-commit hooks
This project ships a pre-commit configuration that runs ruff and ruff-format on every commit. After make install-dev, register the git hook once per checkout:
poetry run pre-commit install
From that point, every git commit runs the hooks; if ruff-format rewrites a file, the commit is aborted so you can re-stage and try again. To run the hooks against the entire repo on demand:
poetry run pre-commit run --all-files
The ruff version pinned in .pre-commit-config.yaml matches the ruff dev dependency in pyproject.toml. Bump both together when upgrading.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pluginforge-0.10.0.tar.gz.
File metadata
- Download URL: pluginforge-0.10.0.tar.gz
- Upload date:
- Size: 31.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.11.13 Linux/6.8.0-117-lowlatency
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91c5cae22b263c699d0644f441e05d8715e32881ed7a84af3ec1f84fdf9acd63
|
|
| MD5 |
8386e20772e8966a018a5b4668dd4b98
|
|
| BLAKE2b-256 |
371505f44ab468b779858e0d59c69b64c6ab0a2fbd53cc2c0f3c3b59d8b1e40d
|
File details
Details for the file pluginforge-0.10.0-py3-none-any.whl.
File metadata
- Download URL: pluginforge-0.10.0-py3-none-any.whl
- Upload date:
- Size: 35.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.11.13 Linux/6.8.0-117-lowlatency
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da5281a034834c4177a931c74b9e830cadf5f1e70910cf7c59752d2fbd6cf07b
|
|
| MD5 |
4bfbb4f06e0b18dc768c3062f7ee9aaa
|
|
| BLAKE2b-256 |
e99ba091f2c23101417473ffb9848e97f8a7757eddc9fdf311a3e6edda281ca6
|