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

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 - DiscoveryResult surfaces per-plugin PluginState; structured PluginErrors 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 the on_config_changed hook
  • 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; mismatched plugins refuse to activate. Permissive: enforcement only fires when the host has declared app_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.

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:

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.8.0.tar.gz (24.4 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.8.0-py3-none-any.whl (28.9 kB view details)

Uploaded Python 3

File details

Details for the file pluginforge-0.8.0.tar.gz.

File metadata

  • Download URL: pluginforge-0.8.0.tar.gz
  • Upload date:
  • Size: 24.4 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.8.0.tar.gz
Algorithm Hash digest
SHA256 29bc16464e2ffb7f3cf211311cbef2824bb1f60cbb0f1991e27cdd32bf736496
MD5 df2eb7f4efb311da772b8baed8ccbe5e
BLAKE2b-256 e0c54b431d3db5ea90a68a3cf4204718379c688e5e658e70677a906f3c4078fb

See more details on using hashes here.

File details

Details for the file pluginforge-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: pluginforge-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 28.9 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.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8de2668168e81525898108f0c6aaf44545e71a3ff9f4299c21563285ff7b9317
MD5 c9ffee4db1e22344dd76a08c7c32c236
BLAKE2b-256 64d52cdadd13fd5247b30f5588f48a230e00e7d818c3e6ad9bdc53368c3af19c

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