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
  • 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 FilterReason for 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:

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.6.1.tar.gz (20.5 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.6.1-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

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

Hashes for pluginforge-0.6.1.tar.gz
Algorithm Hash digest
SHA256 cbf2387d4726760e71284909c72cda42b28567a4ea3395b6f0724b75d7edeb36
MD5 d8b95fb219da0c78a3057de6d3ffc7e9
BLAKE2b-256 4ba6ff266ee0512df904524def1bf1c36d28e59ee4b734bf283259defd706527

See more details on using hashes here.

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

Hashes for pluginforge-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5e79d4ee2298d15d060e02d67a75510628cee1bbf42185efcc20aa44d266f329
MD5 751918bed0e267486be7cbf9b1cfb9c1
BLAKE2b-256 3c4277454547915d9c756d64798c2131d8e8bd23bdc43cb94f52dca3ea74c313

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