CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cement

Advanced Application Framework for Python with a focus on Command Line Interfaces

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugins.mddocs/

Plugin System

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.

Capabilities

Plugin Handler Interface

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
        """

Usage Examples

Basic Plugin System

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 reload

Plugin Directory Structure

myapp/
├── plugins/
│   ├── backup/
│   │   ├── __init__.py
│   │   └── plugin.py
│   ├── monitoring/
│   │   ├── __init__.py
│   │   └── plugin.py
│   └── notifications/
│       ├── __init__.py
│       └── plugin.py
└── myapp.py

Sample Plugin Implementation

# 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')

Plugin Configuration

# 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')

Plugin with Dependencies

# 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'
    }

Plugin Discovery and Management

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 backup

Install with Tessl CLI

npx tessl i tessl/pypi-cement

docs

arguments.md

caching.md

configuration.md

controllers.md

extensions.md

foundation.md

hooks.md

index.md

interface-handler.md

logging.md

mail.md

output.md

plugins.md

templates.md

utilities.md

tile.json