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.
pip install pluginforgeWith optional FastAPI support:
pip install pluginforge[fastapi]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]# 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!"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)- 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 - 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 - 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; mismatched plugins refuse to activate. Permissive: enforcement only fires when the host has declaredapp_id - FastAPI Integration - Mount plugin routes with configurable prefix
- 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.
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}")# config/i18n/en.yaml
common:
save: "Save"
cancel: "Cancel"pm.get_text("common.save", "en") # "Save"
pm.get_text("common.save", "de") # "Speichern"The full documentation is available in the Wiki:
- Getting Started
- BasePlugin
- PluginManager
- Configuration
- Discovery and Dependencies
- Lifecycle
- Hooks
- Extensions
- FastAPI Integration
- Alembic Integration
- i18n
- Security
- Examples
- Changelog
- Roadmap
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 targetsThis 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 installFrom 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-filesThe ruff version pinned in .pre-commit-config.yaml matches the ruff dev dependency in pyproject.toml. Bump both together when upgrading.
MIT