Advanced Application Framework for Python with a focus on Command Line Interfaces
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The plugin system provides plugin management framework for loading and managing application plugins. It supports plugin discovery, loading, and lifecycle management, enabling modular application architecture through external plugin modules.
Base interface for plugin management functionality that defines the contract for plugin operations.
class PluginHandler:
"""
Plugin handler interface for managing application plugins.
Provides methods for loading individual plugins, plugin lists,
and managing plugin lifecycle and state.
"""
def load_plugin(self, plugin_name: str) -> None:
"""
Load a single plugin by name.
Args:
plugin_name: Name of plugin to load
"""
def load_plugins(self, plugin_list: List[str]) -> None:
"""
Load multiple plugins from a list.
Args:
plugin_list: List of plugin names to load
"""
def get_loaded_plugins(self) -> List[str]:
"""
Get list of currently loaded plugins.
Returns:
List of loaded plugin names
"""
def get_enabled_plugins(self) -> List[str]:
"""
Get list of enabled plugins.
Returns:
List of enabled plugin names
"""
def get_disabled_plugins(self) -> List[str]:
"""
Get list of disabled plugins.
Returns:
List of disabled plugin names
"""from cement import App, Controller, ex, init_defaults
CONFIG = init_defaults('myapp')
CONFIG['myapp']['plugin_dirs'] = ['./plugins', '/usr/share/myapp/plugins']
CONFIG['myapp']['plugins'] = ['backup', 'monitoring', 'notifications']
class PluginController(Controller):
class Meta:
label = 'plugin'
stacked_on = 'base'
stacked_type = 'nested'
@ex(help='list loaded plugins')
def list(self):
"""List all loaded plugins."""
loaded = self.app.plugin.get_loaded_plugins()
enabled = self.app.plugin.get_enabled_plugins()
disabled = self.app.plugin.get_disabled_plugins()
print('Plugin Status:')
print(f' Loaded: {loaded}')
print(f' Enabled: {enabled}')
print(f' Disabled: {disabled}')
@ex(
help='load plugin',
arguments=[
(['plugin_name'], {'help': 'name of plugin to load'})
]
)
def load(self):
"""Load a specific plugin."""
plugin_name = self.app.pargs.plugin_name
try:
self.app.plugin.load_plugin(plugin_name)
print(f'Successfully loaded plugin: {plugin_name}')
except Exception as e:
print(f'Failed to load plugin {plugin_name}: {e}')
@ex(help='reload all plugins')
def reload(self):
"""Reload all configured plugins."""
plugins = self.app.config.get('myapp', 'plugins').split(',')
try:
self.app.plugin.load_plugins(plugins)
print(f'Reloaded plugins: {plugins}')
except Exception as e:
print(f'Failed to reload plugins: {e}')
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
config_defaults = CONFIG
handlers = [BaseController, PluginController]
with MyApp() as app:
app.run()
# Usage:
# myapp plugin list
# myapp plugin load backup
# myapp plugin reloadmyapp/
├── plugins/
│ ├── backup/
│ │ ├── __init__.py
│ │ └── plugin.py
│ ├── monitoring/
│ │ ├── __init__.py
│ │ └── plugin.py
│ └── notifications/
│ ├── __init__.py
│ └── plugin.py
└── myapp.py# plugins/backup/plugin.py
from cement import Controller, ex
class BackupController(Controller):
"""Plugin controller for backup functionality."""
class Meta:
label = 'backup'
stacked_on = 'base'
stacked_type = 'nested'
description = 'Backup and restore operations'
@ex(
help='create backup',
arguments=[
(['--target'], {
'help': 'backup target directory',
'default': './backups'
}),
(['--compress'], {
'action': 'store_true',
'help': 'compress backup files'
})
]
)
def create(self):
"""Create application backup."""
target = self.app.pargs.target
compress = self.app.pargs.compress
print(f'Creating backup to: {target}')
print(f'Compression: {"enabled" if compress else "disabled"}')
# Backup logic here
print('Backup completed successfully')
@ex(
help='restore backup',
arguments=[
(['backup_file'], {'help': 'backup file to restore'})
]
)
def restore(self):
"""Restore from backup file."""
backup_file = self.app.pargs.backup_file
print(f'Restoring from: {backup_file}')
# Restore logic here
print('Restore completed successfully')
def load(app):
"""Plugin load function."""
# Register the plugin controller
app.handler.register(BackupController)
app.log.info('Backup plugin loaded')
def unload(app):
"""Plugin unload function."""
app.log.info('Backup plugin unloaded')# plugins/monitoring/plugin.py
from cement import Handler, Controller, ex, init_defaults
class MonitoringHandler(Handler):
"""Handler for system monitoring."""
class Meta:
interface = 'monitoring'
label = 'system_monitor'
config_defaults = {
'check_interval': 60,
'alert_threshold': 80,
'log_metrics': True
}
def check_system_health(self):
"""Check system health metrics."""
import psutil
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
return {
'cpu_usage': cpu_percent,
'memory_usage': memory.percent,
'disk_usage': (disk.used / disk.total) * 100,
'timestamp': time.time()
}
class MonitoringController(Controller):
"""Controller for monitoring commands."""
class Meta:
label = 'monitor'
stacked_on = 'base'
stacked_type = 'nested'
@ex(help='check system status')
def status(self):
"""Check current system status."""
try:
monitor = self.app.handler.get('monitoring', 'system_monitor')()
metrics = monitor.check_system_health()
print('System Status:')
print(f' CPU Usage: {metrics["cpu_usage"]:.1f}%')
print(f' Memory Usage: {metrics["memory_usage"]:.1f}%')
print(f' Disk Usage: {metrics["disk_usage"]:.1f}%')
except Exception as e:
print(f'Failed to get system status: {e}')
@ex(help='start monitoring service')
def start(self):
"""Start continuous monitoring."""
interval = self.app.config.get('monitoring', 'check_interval')
threshold = self.app.config.get('monitoring', 'alert_threshold')
print(f'Starting monitoring (interval: {interval}s, threshold: {threshold}%)')
# Monitoring loop would go here
print('Monitoring service started')
def load(app):
"""Load monitoring plugin."""
from cement import Interface
# Define monitoring interface if not exists
class MonitoringInterface(Interface):
class Meta:
interface = 'monitoring'
def check_system_health(self):
raise NotImplementedError
# Register interface and handlers
app.interface.define(MonitoringInterface)
app.handler.register(MonitoringHandler)
app.handler.register(MonitoringController)
app.log.info('Monitoring plugin loaded')# plugins/notifications/plugin.py
from cement import Controller, ex
class NotificationsController(Controller):
"""Controller for notification functionality."""
class Meta:
label = 'notify'
stacked_on = 'base'
stacked_type = 'nested'
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Check plugin dependencies
self.check_dependencies()
def check_dependencies(self):
"""Check if required dependencies are available."""
try:
import requests
self.has_requests = True
except ImportError:
self.has_requests = False
self.app.log.warning('Notifications plugin: requests library not available')
@ex(
help='send email notification',
arguments=[
(['recipient'], {'help': 'email recipient'}),
(['message'], {'help': 'notification message'})
]
)
def email(self):
"""Send email notification."""
if not self.has_requests:
print('Email notifications require the requests library')
return
recipient = self.app.pargs.recipient
message = self.app.pargs.message
print(f'Sending email to {recipient}: {message}')
# Email sending logic here
print('Email sent successfully')
@ex(
help='send webhook notification',
arguments=[
(['url'], {'help': 'webhook URL'}),
(['message'], {'help': 'notification message'})
]
)
def webhook(self):
"""Send webhook notification."""
if not self.has_requests:
print('Webhook notifications require the requests library')
return
url = self.app.pargs.url
message = self.app.pargs.message
import requests
import json
try:
payload = {'message': message, 'timestamp': time.time()}
response = requests.post(url, json=payload)
if response.status_code == 200:
print('Webhook notification sent successfully')
else:
print(f'Webhook failed with status: {response.status_code}')
except Exception as e:
print(f'Failed to send webhook: {e}')
def load(app):
"""Load notifications plugin."""
app.handler.register(NotificationsController)
app.log.info('Notifications plugin loaded')
def get_plugin_info():
"""Return plugin information."""
return {
'name': 'notifications',
'version': '1.0.0',
'description': 'Email and webhook notifications',
'dependencies': ['requests'],
'author': 'Plugin Developer'
}from cement import App, Controller, ex
import os
import importlib.util
import json
class PluginManagerController(Controller):
"""Advanced plugin management controller."""
class Meta:
label = 'plugins'
stacked_on = 'base'
stacked_type = 'nested'
def discover_plugins(self):
"""Discover available plugins in plugin directories."""
plugin_dirs = self.app.config.get('myapp', 'plugin_dirs').split(',')
discovered = []
for plugin_dir in plugin_dirs:
plugin_dir = plugin_dir.strip()
if not os.path.exists(plugin_dir):
continue
for item in os.listdir(plugin_dir):
plugin_path = os.path.join(plugin_dir, item)
if os.path.isdir(plugin_path):
plugin_file = os.path.join(plugin_path, 'plugin.py')
if os.path.exists(plugin_file):
discovered.append({
'name': item,
'path': plugin_path,
'file': plugin_file
})
return discovered
@ex(help='discover available plugins')
def discover(self):
"""Discover and list available plugins."""
plugins = self.discover_plugins()
print(f'Discovered {len(plugins)} plugin(s):')
for plugin in plugins:
print(f' • {plugin["name"]} ({plugin["path"]})')
# Try to get plugin info
try:
spec = importlib.util.spec_from_file_location(
f'{plugin["name"]}.plugin', plugin['file']
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if hasattr(module, 'get_plugin_info'):
info = module.get_plugin_info()
print(f' Version: {info.get("version", "unknown")}')
print(f' Description: {info.get("description", "no description")}')
deps = info.get('dependencies', [])
if deps:
print(f' Dependencies: {", ".join(deps)}')
except Exception as e:
print(f' Error loading plugin info: {e}')
@ex(help='show plugin information')
def info(self):
"""Show detailed information about loaded plugins."""
loaded = self.app.plugin.get_loaded_plugins()
print('Loaded Plugin Information:')
for plugin_name in loaded:
print(f'\n{plugin_name}:')
print(f' Status: Loaded')
# Try to get additional plugin information
try:
# This would depend on how plugins store their metadata
print(f' Description: Plugin providing {plugin_name} functionality')
except Exception:
print(f' Description: No additional information available')
@ex(
help='validate plugin',
arguments=[
(['plugin_name'], {'help': 'name of plugin to validate'})
]
)
def validate(self):
"""Validate a plugin before loading."""
plugin_name = self.app.pargs.plugin_name
plugins = self.discover_plugins()
plugin = next((p for p in plugins if p['name'] == plugin_name), None)
if not plugin:
print(f'Plugin {plugin_name} not found')
return
print(f'Validating plugin: {plugin_name}')
try:
# Load plugin module for validation
spec = importlib.util.spec_from_file_location(
f'{plugin["name"]}.plugin', plugin['file']
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Check required functions
required_functions = ['load']
for func in required_functions:
if not hasattr(module, func):
print(f' ✗ Missing required function: {func}')
return
else:
print(f' ✓ Found required function: {func}')
# Check optional functions
optional_functions = ['unload', 'get_plugin_info']
for func in optional_functions:
if hasattr(module, func):
print(f' ✓ Found optional function: {func}')
else:
print(f' - Optional function not found: {func}')
print(f' ✓ Plugin {plugin_name} validation passed')
except Exception as e:
print(f' ✗ Plugin validation failed: {e}')
class BaseController(Controller):
class Meta:
label = 'base'
class AdvancedPluginApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
handlers = [BaseController, PluginManagerController]
with AdvancedPluginApp() as app:
app.run()
# Usage:
# myapp plugins discover
# myapp plugins info
# myapp plugins validate backupInstall with Tessl CLI
npx tessl i tessl/pypi-cement