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. 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.9.0.tar.gz (29.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.9.0-py3-none-any.whl (33.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pluginforge-0.9.0.tar.gz
  • Upload date:
  • Size: 29.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.9.0.tar.gz
Algorithm Hash digest
SHA256 6efaa0749ed0b626a3ef5f679f16cd010b0e406bad46070205b7dc0b0a2251e6
MD5 e6b9f0fce0ccf836d37d825a0f66604f
BLAKE2b-256 580965c1d81633e137374e6c7a8d9f54912c19e6d63c32477147280016a0a7af

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pluginforge-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 33.3 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.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5c755e6011396799a9270f08e28ac63e0be6379816d78baec0e729192a8a8758
MD5 40d9987813e1ff3f468bf778f14855ba
BLAKE2b-256 c80be9537a714ec2e64d23b3213ae27d51632b09ac1134ffba9804dfdcad835e

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