GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - astrapi69/pluginforge: PluginForge adds the layers that pluggy is missing: YAML configuration, plugin lifecycle management, enable/disable per config, dependency resolution, FastAPI integration, and i18n support.

BranchesTags
Open more actions menu

Latest commit

 

History

86 Commits
86 Commits

Folders and files

NameName
Last commit message
Last commit date
.claude
.claude
 
 
.idea
.idea
 
 
docs
docs
 
 
examples
examples
 
 
pluginforge
pluginforge
 
 
tests
tests
 
 
.gitignore
.gitignore
 
 
.pre-commit-config.yaml
.pre-commit-config.yaml
 
 
CHANGELOG.md
CHANGELOG.md
 
 
CLAUDE.md
CLAUDE.md
 
 
LICENSE
LICENSE
 
 
Makefile
Makefile
 
 
README.md
README.md
 
 
poetry.lock
poetry.lock
 
 
pyproject.toml
pyproject.toml
 
 

Repository files navigation

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:

  • Consumer Integration Guide - wire PluginForge into your FastAPI app from scratch (covers lifespan, mounting, hot reload, testing)
  • Plugin Author Guide - write a PluginForge plugin (BasePlugin contract, identity gating, single-router convention, lifecycle hooks)
  • 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

About

PluginForge adds the layers that pluggy is missing: YAML configuration, plugin lifecycle management, enable/disable per config, dependency resolution, FastAPI integration, and i18n support.

Resources

Readme
MIT license
Activity

Stars

1 star

Watchers

0 watching

Forks

0 forks
Report repository

Releases

Packages

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.