A powerful and flexible Python plugin system that enables dynamic loading, hot-reloading, and management of plugins in your applications. Build extensible software with ease!
- Dynamic Plugin Loading: Load plugins at runtime from files or directories
- Hot Reload: Automatically detect and reload plugin changes during development
- Event System: Inter-plugin communication through events and hooks
- Lifecycle Management: Complete plugin lifecycle with load/unload hooks
- Type Safety: Full type hints support for better development experience
- Error Isolation: Plugin errors don't crash your application
- Package Support: Support for both single-file and package-style plugins
- Priority System: Control plugin loading order with priority settings
- Context Sharing: Share application context between plugins
- Framework Agnostic: Works with any Python application or framework
Install PlugFlow using pip:
pip install plugflowOr install from source:
git clone https://github.com/keklick1337/plugflow.git
cd plugflow
pip install -e .from plugflow import PluginManager
# Create plugin manager with plugins directory
manager = PluginManager(plugins_paths=["plugins/"])
# Load all plugins from configured paths
manager.load_all()
# Process a command through plugins
result = manager.handle_message("/hello world")
print(result) # Output from plugin that handles "hello" command
# Send events to plugins
manager.dispatch_event("user_login", {"user_id": 123})Create a file plugins/greeter.py:
from plugflow import BasePlugin
class GreeterPlugin(BasePlugin):
name = "greeter"
version = "1.0.0"
priority = 10 # Higher priority loads first
def on_load(self, manager):
"""Called when plugin is loaded"""
print(f"Greeter plugin v{self.version} loaded!")
def handle_command(self, command: str, args: str):
"""Handle commands"""
if command == "hello":
return f"Hello, {args}!"
elif command == "goodbye":
return f"Goodbye, {args}!"
return None # Command not handled
def on_event(self, event: str, data, manager):
"""Handle events from other plugins"""
if event == "user_login":
print(f"User {data['user_id']} logged in!")Create a directory plugins/advanced_greeter/:
plugins/advanced_greeter/
├── __init__.py
├── handlers.py
└── utils.py
plugins/advanced_greeter/__init__.py:
from plugflow import BasePlugin
from .handlers import CommandHandler
from .utils import format_message
class AdvancedGreeterPlugin(BasePlugin):
name = "advanced_greeter"
version = "2.0.0"
def __init__(self):
super().__init__()
self.handler = CommandHandler()
def handle_command(self, command: str, args: str):
if command in ["greet", "welcome"]:
return self.handler.handle_greeting(command, args)
return NoneThe main class that manages plugin lifecycle and coordination:
from plugflow import PluginManager
manager = PluginManager(
hot_reload=True, # Enable hot reload
context={"app": my_app} # Share context with plugins
)
# Load plugins
manager.load_from_path(Path("path/to/plugin.py"))
manager.load_all() # Load all plugins from configured paths
# Manage plugins
manager.unload_plugin("plugin_name")
manager.reload_plugin("plugin_name")
# Plugin communication
result = manager.handle_message("/command args") # Handle chat-style messages
manager.dispatch_event("event_name", data)
# Get plugin information
plugins = manager.list_plugins()
plugin = manager.get("plugin_name")Base class for all plugins:
from plugflow import BasePlugin
from typing import Optional, Any
class MyPlugin(BasePlugin):
name = "my_plugin" # Required: unique plugin name
version = "1.0.0" # Required: plugin version
priority = 10 # Optional: loading priority (higher first)
dependencies = ["other"] # Optional: plugin dependencies
def on_load(self, manager) -> None:
"""Called when plugin is loaded"""
pass
def on_unload(self, manager) -> None:
"""Called when plugin is unloaded"""
pass
def handle_command(self, command: str, args: str) -> Optional[str]:
"""Handle commands - return result or None if not handled"""
return None
def filter_message(self, text: str) -> Optional[str]:
"""Filter/modify messages - return modified text or None"""
return None
def on_event(self, event: str, data: Any, manager) -> None:
"""Handle events from other plugins"""
passEnable automatic plugin reloading during development:
manager = PluginManager(
plugins_paths=["plugins/"],
hot_reload=True # Enable hot reload for development
)
manager.load_all()
# Now edit your plugins - changes will be detected automatically!Share application state and resources with plugins:
# Create manager with shared context
context = {
"database": db_connection,
"config": app_config,
"logger": logger
}
manager = PluginManager(context=context)
# Plugins can access context
class DatabasePlugin(BasePlugin):
def on_load(self, manager):
db = self.context.get("database")
logger = self.context.get("logger")
logger.info("Database plugin connected!")Plugins can communicate through events:
# Plugin A sends event
class PublisherPlugin(BasePlugin):
def handle_command(self, command, args):
if command == "notify":
# Dispatch event to all plugins
self.manager.dispatch_event("notification", {
"message": args,
"timestamp": time.time()
})
# Plugin B receives event
class SubscriberPlugin(BasePlugin):
def on_event(self, event, data, manager):
if event == "notification":
print(f"Received: {data['message']}")Specify plugin loading order with dependencies:
class DatabasePlugin(BasePlugin):
name = "database"
priority = 100 # Load first
class UserPlugin(BasePlugin):
name = "user_manager"
dependencies = ["database"] # Load after database
def on_load(self, manager):
# Database plugin is guaranteed to be loaded
db_plugin = manager.get_plugin("database")Plugins are isolated - errors don't crash your application:
class FaultyPlugin(BasePlugin):
def handle_command(self, command, args):
if command == "crash":
raise Exception("Plugin error!")
return None
# Manager handles plugin errors gracefully
try:
result = manager.handle_command("crash", "")
except Exception as e:
print(f"Plugin error handled: {e}")
# Application continues runningPlugFlow includes comprehensive examples in the examples/ directory:
Professional command-line utility with plugin-based commands:
cd examples/cli_tool
python cli.py help # Show all commands
python cli.py hash md5 "test" # Cryptographic operations
python cli.py tree . 2 # File system utilitiesFeatures: Type-safe plugins, clean output, debug mode, comprehensive help
Production-ready bot simulation with advanced features:
cd examples/tg_stub
python bot.py # Clean production mode
python bot.py --debug # Development mode with logsFeatures: Command handling, message filtering, dynamic help, hot reload
Sophisticated tkinter application with plugin integration:
cd examples/tk_app
python app.pyFeatures: Menu integration, real-time logging, file operations, event system
Simple HTTP server with plugin routes:
cd examples/web_server
python server.pyFlask-based web application with plugin routes:
cd examples/flask_basic/
# Optional
python3 -m venv venv
source venv/bin/activate
pip3 install flask plugflow
python app.pyFeatures: Dynamic routing, middleware, API endpoints, template system
from plugflow import BasePlugin
from typing import Optional
class MyPlugin(BasePlugin):
# Plugin metadata
name = "my_plugin"
version = "1.0.0"
description = "My awesome plugin"
author = "Your Name"
def on_load(self, manager):
"""Initialize plugin resources"""
self.data = {}
print(f"{self.name} loaded!")
def on_unload(self, manager):
"""Clean up plugin resources"""
self.data.clear()
print(f"{self.name} unloaded!")
def handle_command(self, command: str, args: str) -> Optional[str]:
"""Process commands"""
commands = {
"status": self._status,
"set": self._set_data,
"get": self._get_data
}
if command in commands:
return commands[command](args)
return None
def _status(self, args: str) -> str:
return f"Plugin {self.name} v{self.version} - {len(self.data)} items"
def _set_data(self, args: str) -> str:
key, value = args.split("=", 1)
self.data[key] = value
return f"Set {key} = {value}"
def _get_data(self, args: str) -> str:
return self.data.get(args, "Key not found")- Error Handling: Always handle exceptions gracefully
- Resource Cleanup: Implement
on_unloadfor proper cleanup - Type Hints: Use type annotations for better code quality
- Documentation: Document your plugin's commands and features
- Testing: Create unit tests for your plugins
- Versioning: Use semantic versioning for your plugins
import unittest
from plugflow import PluginManager
from my_plugin import MyPlugin
class TestMyPlugin(unittest.TestCase):
def setUp(self):
self.manager = PluginManager()
# Manually add plugin for testing
from plugflow.manager import PluginRecord
self.plugin = MyPlugin()
record = PluginRecord(self.plugin, Path("test"), None)
self.manager._records[self.plugin.name] = record
def test_command_handling(self):
result = self.manager.handle_message("/status")
self.assertTrue(any("Plugin my_plugin" in r for r in result))
def test_data_operations(self):
self.manager.handle_message("/set key=value")
result = self.manager.handle_message("/get key")
self.assertTrue(any("value" in r for r in result))load_from_path(path: Path) -> None: Load plugins from a file or directoryload_all() -> None: Load all plugins from configured pathsunload_plugin(name: str) -> bool: Unload a plugin by namereload_plugin(name: str) -> bool: Reload a plugin by namehandle_message(text: str) -> List[str]: Process message through plugins (supports /commands and filters)dispatch_event(event: str, data: Any = None) -> List[Any]: Send event to all pluginsbroadcast(method: str, *args, **kwargs) -> List[Any]: Call method on all plugins that have itlist_plugins() -> List[str]: Get list of loaded plugin namesget(name: str) -> Optional[BasePlugin]: Get plugin instance by namestop() -> None: Stop hot reload watchers
hot_reload: bool: Enable/disable hot reloadcontext: Dict[str, Any]: Shared context dictionary
name: str: Unique plugin identifierversion: str: Plugin version
priority: int: Loading priority (default: 0)dependencies: List[str]: Required pluginsdescription: str: Plugin descriptionauthor: str: Plugin author
on_load(manager: PluginManager) -> None: Initialization hookon_unload(manager: PluginManager) -> None: Cleanup hookhandle_command(command: str, args: str) -> Optional[str]: Command handlerfilter_message(text: str) -> Optional[str]: Message filteron_event(event: str, data: Any, manager: PluginManager) -> None: Event handler
- Lazy Loading: Load plugins only when needed
- Caching: Cache plugin results for expensive operations
- Priority Optimization: Use priorities to control loading order
- Resource Management: Properly clean up plugin resources
- Event Filtering: Only subscribe to relevant events
Plugin Not Loading
# Check plugin file syntax
manager.load_from_path(Path("plugin.py"))
# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)Import Errors
# Ensure plugin directory is in Python path
import sys
sys.path.append("plugins")Hot Reload Not Working
# Ensure hot_reload is enabled
manager = PluginManager(hot_reload=True)
# Check file permissions and watching capabilityEnable verbose logging:
import logging
logging.basicConfig(level=logging.DEBUG)
manager = PluginManager(hot_reload=True)
# Now you'll see detailed plugin loading informationWe welcome contributions!
git clone https://github.com/keklick1337/plugflow.git
cd plugflow
pip install -e ".[dev]"
pre-commit installpytest tests/This project is licensed under the MIT License - see the LICENSE file for details.
Built with love for the Python community
PlugFlow makes it easy to create extensible applications. Start building your plugin ecosystem today!