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
With optional FastAPI support:
pip install pluginforge[fastapi]
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 - 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 - 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.
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"
Known Limitations
No Application Identity
v0.6.0 has no built-in concept of which application a plugin targets. The current isolation mechanism is entry-point group separation: each host application configures its own plugins.entry_point_group (default pluginforge.plugins), and plugin publishers register their entry points under the matching group in pyproject.toml.
This is a publishing convention, not an enforced invariant:
- A plugin can register under multiple entry-point groups.
- A plugin's own code cannot assert which application it targets.
- There is no
FilterReasonfor application mismatch.
Hosts running multiple PluginForge-consuming applications in the same Python environment should configure distinct entry-point groups per application.
Tracked for v0.7.0 design.
Documentation
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
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.6.1.tar.gz.
File metadata
- Download URL: pluginforge-0.6.1.tar.gz
- Upload date:
- Size: 20.5 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 |
cbf2387d4726760e71284909c72cda42b28567a4ea3395b6f0724b75d7edeb36
|
|
| MD5 |
d8b95fb219da0c78a3057de6d3ffc7e9
|
|
| BLAKE2b-256 |
4ba6ff266ee0512df904524def1bf1c36d28e59ee4b734bf283259defd706527
|
File details
Details for the file pluginforge-0.6.1-py3-none-any.whl.
File metadata
- Download URL: pluginforge-0.6.1-py3-none-any.whl
- Upload date:
- Size: 23.2 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 |
5e79d4ee2298d15d060e02d67a75510628cee1bbf42185efcc20aa44d266f329
|
|
| MD5 |
751918bed0e267486be7cbf9b1cfb9c1
|
|
| BLAKE2b-256 |
3c4277454547915d9c756d64798c2131d8e8bd23bdc43cb94f52dca3ea74c313
|