NEW: Fenrir v4.1.2 — 56 Bug Fixes, Performance Optimizations, 1331 Tests, 5-Framework Benchmark! Read the changelog
plugin-system.md
docs plugin-system.md

Plugin System

Fenrir v4.1.2 introduces a production-ready plugin system for extending the framework with modular, reusable components.

Overview

The plugin system provides:

  • Version Compatibility: Plugins declare compatible Fenrir versions
  • Dependency Resolution: Automatic ordering with circular dependency detection
  • Config Validation: Schema-based configuration validation
  • Hot-Reload: Enable/disable plugins at runtime
  • Auto-Discovery: Find plugins via entry points
  • Health Monitoring: Track plugin status and health
  • Namespace Isolation: Prevent naming conflicts between plugins
  • Thread Safety: Safe for concurrent access

Creating a Plugin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from fenrir.plugins import Plugin

class MyPlugin(Plugin):
    name = "my-plugin"
    version = "1.0.0"
    description = "My awesome Fenrir plugin"

    # Dependencies on other plugins
    requires = ["auth-plugin"]
    optional = ["cache-plugin"]

    # Version compatibility
    min_fenrir_version = "4.1.2"
    max_fenrir_version = "5.0.0"

    def setup(self, app, **kwargs):
        """Called when plugin is loaded."""
        self.app = app
        self.config = kwargs
        # Register routes, middleware, etc.

    def teardown(self, app):
        """Called when plugin is unloaded."""
        pass

    def health_check(self):
        """Return health status."""
        return {"status": "healthy", "version": self.version}

Registering Plugins

Direct Registration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from fenrir import Fenrir
from fenrir.plugins import PluginRegistry

app = Fenrir()
registry = PluginRegistry(app)

# Register a plugin
registry.register(MyPlugin())

# Register with config
registry.register(MyPlugin(), config={"api_key": "secret"})

Auto-Discovery via Entry Points

1
2
3
4
5
6
# In your plugin's pyproject.toml:
[project.entry-points."fenrir.plugins"]
my_plugin = "my_package:MyPlugin"

# In the app:
registry.discover()  # Finds all plugins via entry points

Plugin Registry

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from fenrir.plugins import PluginRegistry

app = Fenrir()
registry = PluginRegistry(app)

# Register plugins
registry.register(PluginA())
registry.register(PluginB())

# Enable/disable
registry.enable("plugin-a")
registry.disable("plugin-b")

# Get plugin object
plugin = registry.get("plugin-a")

# List all plugins with status
for name, info in registry.list_plugins().items():
    print(f"{name}: enabled={info['enabled']}, version={info['version']}")

Dependency Resolution

Plugins are loaded in dependency order:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class AuthPlugin(Plugin):
    name = "auth"
    requires = []  # No dependencies

class AdminPlugin(Plugin):
    name = "admin"
    requires = ["auth"]  # Depends on auth plugin

class DashboardPlugin(Plugin):
    name = "dashboard"
    requires = ["auth", "admin"]  # Depends on both

Circular dependencies are detected and raise an error:

1
2
3
4
5
6
7
class PluginA(Plugin):
    name = "a"
    requires = ["b"]  # Circular!

class PluginB(Plugin):
    name = "b"
    requires = ["a"]  # Circular!

Config Validation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from fenrir.plugins import Plugin

class MyPlugin(Plugin):
    name = "my-plugin"

    config_schema = {
        "api_key": {"type": "str", "required": True},
        "timeout": {"type": "int", "default": 30},
        "debug": {"type": "bool", "default": False},
    }

    def setup(self, app, **kwargs):
        # config is validated against schema
        self.api_key = kwargs["api_key"]
        self.timeout = kwargs["timeout"]

Health Monitoring

1
2
3
4
5
6
7
8
# Check plugin health
health = registry.get_plugin_health("my-plugin")
print(health.status)      # "healthy"

# Plugin also provides its own health_check method
plugin = registry.get("my-plugin")
status = plugin.health_check()
print(status)  # {"status": "healthy", "version": "1.0.0"}

Thread Safety

The plugin registry uses threading.RLock for safe concurrent access:

1
2
3
4
5
6
import threading

# Safe to call from multiple threads
registry.register(PluginA())  # Thread 1
registry.enable("plugin-a")   # Thread 2
registry.disable("plugin-a")  # Thread 3
Edit on GitHub Last Updated: Oct 20, 2018
© 2026 Fenrir Project.
main*
v4.1.2
Ln 1, Col 1
UTF-8
Prettier
Light Mode
Markdown