Skip to main content

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 - DiscoveryResult surfaces per-plugin PluginState; structured PluginErrors 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 the on_config_changed hook. v0.10.0 adds merge_app_config(overlay, *, notify=True) for deep-merge overlay semantics (replaces the _app_config = ... hack consumers were using) plus a notify=False kwarg on refresh_config for 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_version and min_app_version with configurable severity
  • Application Identity Gating - Declare target_application on plugins and app_id on the host. Plugins whose target_application mismatches the host's app_id, or whose target_application is not declared at all, refuse to activate. Permissive: hosts without app_id see no validation
  • Lifecycle Visibility - PluginState carries activated_at / last_config_change / source timestamps; inspect_plugin(name) aggregates state, config, health, hooks, routes, and identity into one snapshot; on_plugin_activated / on_plugin_deactivated / on_config_refreshed event hooks notify subscribers after lifecycle transitions
  • FastAPI Integration - Mount plugin routes with configurable prefix
  • Idempotent Route Mounting (v0.8.0) - mount_routes is safe to re-call; no route-table accumulation across TestClient lifespans (closes the v0.7.0 recursion-cascade reported by downstream consumers)
  • Test Helpers (v0.8.0) - pluginforge.testing.IsolatedPluginManager and MockPlugin for consumer-app test wiring
  • Single-Router Convention (v0.8.0) - one router per get_routes() is recommended; multi-router plugins emit a DeprecationWarning
  • Type Annotations (PEP 561) - py.typed marker shipped; mypy / pyright consume 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:

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pluginforge-0.10.0.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pluginforge-0.10.0-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

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

Hashes for pluginforge-0.10.0.tar.gz
Algorithm Hash digest
SHA256 91c5cae22b263c699d0644f441e05d8715e32881ed7a84af3ec1f84fdf9acd63
MD5 8386e20772e8966a018a5b4668dd4b98
BLAKE2b-256 371505f44ab468b779858e0d59c69b64c6ab0a2fbd53cc2c0f3c3b59d8b1e40d

See more details on using hashes here.

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

Hashes for pluginforge-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 da5281a034834c4177a931c74b9e830cadf5f1e70910cf7c59752d2fbd6cf07b
MD5 4bfbb4f06e0b18dc768c3062f7ee9aaa
BLAKE2b-256 e99ba091f2c23101417473ffb9848e97f8a7757eddc9fdf311a3e6edda281ca6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page