Anwendungsunabhaengiges Python-Plugin-Framework. Baut auf pluggy auf und ergaenzt: YAML-Config, Lifecycle, Enable/Disable, Abhaengigkeiten, FastAPI-Integration, i18n, Extension Points, Hot-Reload, Health Checks, Config Schema Validation, Security, Application-Identity-Gating (v0.7.0 + v0.9.0 hard-filter), Testing-Helpers (v0.8.0), Lifecycle-Visibility mit inspect_plugin + Event-Hooks (v0.9.0), Config-Overlay via merge_app_config + refresh_config(notify=) (v0.10.0).
Repository: https://github.com/astrapi69/pluginforge
Architektur: docs/ARCHITECTURE.md (lesen vor jeder Aenderung)
Wiki: https://github.com/astrapi69/pluginforge/wiki
Lizenz: MIT
Aktuelle Version: siehe pyproject.toml (poetry version -s fuer die aktuelle Nummer; vermeidet hartcodierte Versions-Drift).
Vor jeder nicht-trivialen Aenderung: .claude/rules/ lesen
(ai-workflow, coding-standards, code-hygiene, quality-checks,
release-workflow, lessons-learned). .claude/README.md ist der
Einstiegspunkt.
- Python 3.11+
- pluggy (Hook-System, NICHT selbst bauen - ab 1.1.0 new-style Hook Wrapper mit explizitem wrapper=True)
- PyYAML (Konfiguration)
- packaging (SemVer-Vergleiche fuer min_app_version)
- FastAPI (optional, nur wenn importiert)
- Alembic (optional, nur wenn importiert)
- Poetry (Paketmanagement, PEP 621 [project] table)
- pytest + pytest-cov (Tests)
- ruff (Linting + Formatierung)
- pre-commit (Hooks vor jedem Commit)
pluginforge/
├── pluginforge/
│ ├── __init__.py # Public API exportieren
│ ├── base.py # BasePlugin
│ ├── manager.py # PluginManager
│ ├── config.py # YAML-Config laden/mergen
│ ├── discovery.py # Entry Point Discovery + Dependency Resolution
│ ├── lifecycle.py # init/activate/deactivate Steuerung
│ ├── state.py # DiscoveryResult, DiscoveryDiff, PluginState, PluginError, PluginInspection
│ ├── security.py # Plugin-Name-Validierung, Path Traversal Prevention
│ ├── utils.py # Hilfsfunktionen
│ ├── fastapi_ext.py # FastAPI-Router mounten (optional)
│ ├── alembic_ext.py # Alembic-Migrations sammeln (optional)
│ └── i18n.py # Mehrsprachige Strings aus YAML
├── tests/
├── docs/
│ ├── ARCHITECTURE.md
│ ├── design/ # Design-Dokumente pro Release
│ └── ...
├── CHANGELOG.md # Aenderungen ab v0.6.0
├── pyproject.toml
├── README.md
├── LICENSE
├── Makefile
└── .gitignore
Stand v0.10.0, definiert in pluginforge/__init__.py.__all__:
BasePlugin- Basisklasse fuer Plugins (Lifecycle, Hooks, Metadaten).PluginManager- Verwaltet Discovery, Aktivierung, Hooks, Routes.DiscoveryResult- Per-Plugin-Status nach Discovery (states, activated, errors). Helperby_filter_reason(reason).DiscoveryDiff- Delta zwischen zwei Discovery-Laeufen. Helperby_filter_reason(reason)(v0.10.0 parity mit DiscoveryResult).PluginState- Plugin-Lifecycle-Zustand (+ v0.9.0: activated_at, last_config_change, source).PluginError- Strukturierter Fehler mit Phase + Severity.PluginInspection- Aggregierter Snapshot (state, config, health, hooks, routes, ...) viainspect_plugin(name)(v0.9.0).FilterReason- Literal-Alias mit 10 Werten (v0.10.0 am Package-Root re-exportiert; war zuvor nur viapluginforge.stateerreichbar).ErrorPhase- Literal-Alias mit 7 Werten (v0.10.0 am Package-Root re-exportiert).config_diff- Helper(old, new) -> {key: (old, new)}fuer on_config_changed-Konsumenten (v0.10.0 am Package-Root re-exportiert; viapluginforge.utilsebenfalls weiter erreichbar).CircularDependencyError- Wenn depends_on einen Zyklus enthaelt.InvalidPluginNameError- Wenn Plugin-Name die Sicherheits-Validierung scheitert.
- Typehints ueberall, kein
Anywo ein konkreter Typ moeglich ist - Keine Em-Dashes (--), stattdessen Bindestriche (-) oder Kommata
- Commit Messages: Englisch, konventionell (feat/fix/refactor/docs/test/chore)
- Docstrings: Google-Style (siehe
.claude/rules/code-hygiene.md) - Logging:
logging.getLogger(__name__), keine eigene Log-Config - Optional Dependencies: Lazy Import mit klarer Fehlermeldung wenn nicht installiert
- Keine hartcodierten Strings, alles via Config
- Plugin-Namen werden validiert (security.py) - keine Pfad-Separatoren, max 64 Zeichen
- Keine
Co-Authored-By-Trailer fuer KI-Mitwirkende (siehe coding-standards.md)
Ein Plugin ist beides gleichzeitig: eine BasePlugin-Subklasse (Lifecycle: init,
activate, deactivate, on_config_changed) und ein Objekt mit @hookimpl-dekorierten
Methoden (pluggy Hook-System). PluginForge managed den Lifecycle, pluggy managed
die Hooks. Worked example mit allen relevanten Attributen siehe
docs/guides/plugin-author.md.
Wichtig: Ab pluggy 1.1.0 brauchen Hook Wrapper explizit wrapper=True.
__init__(config_path, pre_activate=None, api_version="1", app_version=None, api_version_severity="warning", app_version_severity="error", app_id=None)
discover_plugins() / register_plugins([classes]) / register_plugin(instance):
laedt Entry Points oder direkte Klassen, filtert nach enabled/disabled, validiert
Identity (v0.7.0: wrong_application bei Mismatch; v0.9.0: missing_target_application
wenn Plugin target_application fehlt und Host app_id hat) und Version
(min_app_version vs app_version, v0.6.0), sortiert depends_on topologisch,
ruft pro Plugin: YAML laden, init + config-schema, optional pre_activate,
pluggy register, activate. Liefert DiscoveryResult(states, activated, errors).
Weitere oeffentliche Methoden:
call_hook/call_hook_safe: Hook-Dispatch via pluggy.get_extensions(ExtensionPoint): aktive Plugins fuer einen Typ.mount_routes(app, prefix="/api"): FastAPI-Router mounten (idempotent seit v0.8.0).reload_plugin(name): deactivate -> re-import -> re-init -> activate.rediscover() -> DiscoveryDiff: Delta zwischen Laeufen.refresh_config(new=None, *, notify=True) -> list[PluginError]: app-config Snapshot ersetzen +on_config_changed(v0.6.0;notifykwarg v0.10.0).merge_app_config(overlay, *, notify=True) -> list[PluginError]: Overlay rekursiv in den Snapshot mergen (Dicts mergen, Listen/Skalare ersetzen). Loest denmanager._app_config = ...-Hack ab (v0.10.0; privates Attribut bleibt durch v0.10.0 funktional, geplant fuer Entfernung in v1.0.0).health_check(): Aggregiertplugin.health()ueber alle aktiven Plugins.get_last_discovery_result() -> DiscoveryResult | None: letzter Lauf (v0.6.0).inspect_plugin(name) -> PluginInspection | None: Aggregator-Snapshot (v0.9.0).on_plugin_activated/deactivated/on_config_refreshed(cb): Event-Hooks mit Deregistrations-Closure (v0.9.0).deactivate_all(): umgekehrte Reihenfolge.
App-Config (config/app.yaml)
+ Plugin-Config (config/plugins/{name}.yaml) # merged in plugin.config
+ i18n (config/i18n/{lang}.yaml) # separat, via get_text()
Fehlende Config-Dateien sind kein Fehler, nur leere Defaults. Config-Schema wird validiert wenn config_schema auf dem Plugin definiert ist.
install / install-dev / test / test-fast / lint / format / ci
(lint+format-check+test) / bump-patch / bump-minor / build / publish
(ci+build+publish, siehe release-workflow.md) / clean. make help listet alles.
- Jede Komponente hat eigene Testdatei (siehe
tests/). conftest.py: Fixtures fuer temporaere YAML-Configs und Sample-Plugins.- Sample-Plugins als Klassen in tests/ definieren (nicht als Entry Points).
- Fuer Entry Point Tests:
pm.register()direkt nutzen. - Coverage-Ziel: >= 90% (gemaess
.claude/rules/quality-checks.md). - Aktuelle Test-Zahlen NICHT hier hartkodieren; per Befehl verifizieren (siehe ai-workflow.md "Numeric claims verification").
Per .claude/rules/ai-workflow.md: ROADMAP lesen, git log -10, make test gruen, dann Aufgabe.
Bibliogon und AdaptivLearner bauen darauf auf. PluginForge muss anwendungsunabhaengig bleiben. Bibliogon-spezifisches gehoert ins Bibliogon-Repo als Plugin oder Hook-Spec.
See docs/guides/consumer-integration.md (Quick Reference section).
See docs/guides/plugin-author.md (Quick Reference section).