diff --git a/src/common/README.md b/src/common/README.md index cccaa40b..4246ccff 100644 --- a/src/common/README.md +++ b/src/common/README.md @@ -99,11 +99,6 @@ Helpers for ensuring directory permissions and ownership are correct when running as a service (used by `CacheManager` to set up its persistent cache directory). -## CLI Helpers (`cli.py`) - -Shared CLI argument parsing helpers used by `scripts/dev/*` and other -command-line entry points. - ## Best Practices 1. **Use centralized logging**: Import from `src.logging_config` instead of creating loggers directly diff --git a/src/common/cli.py b/src/common/cli.py deleted file mode 100644 index ec33caaa..00000000 --- a/src/common/cli.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -LEDMatrix Common CLI - -Command-line interface for LEDMatrix Common utilities. -""" - -import argparse -import sys -from pathlib import Path - - -def main(): - """Main CLI entry point.""" - parser = argparse.ArgumentParser( - description="LEDMatrix Common Utilities", - prog="ledmatrix-common" - ) - - subparsers = parser.add_subparsers(dest='command', help='Available commands') - - # Test command - test_parser = subparsers.add_parser('test', help='Test common utilities') - test_parser.add_argument('--display-width', type=int, default=128, help='Display width') - test_parser.add_argument('--display-height', type=int, default=64, help='Display height') - - # Validate command - validate_parser = subparsers.add_parser('validate', help='Validate configuration') - validate_parser.add_argument('config_file', help='Configuration file to validate') - - args = parser.parse_args() - - if args.command == 'test': - test_utilities(args.display_width, args.display_height) - elif args.command == 'validate': - validate_config(args.config_file) - else: - parser.print_help() - - -def test_utilities(display_width: int, display_height: int): - """Test common utilities.""" - print(f"Testing LEDMatrix Common utilities with {display_width}x{display_height} display") - - try: - from ledmatrix_common import LogoHelper, TextHelper, DisplayHelper, GameHelper, ConfigHelper - - # Test LogoHelper - print("Testing LogoHelper...") - logo_helper = LogoHelper(display_width, display_height) - print(f"Logo cache stats: {logo_helper.get_cache_stats()}") - - # Test TextHelper - print("Testing TextHelper...") - text_helper = TextHelper() - fonts = text_helper.load_fonts() - print(f"Loaded {len(fonts)} fonts") - - # Test DisplayHelper - print("Testing DisplayHelper...") - display_helper = DisplayHelper(display_width, display_height) - img = display_helper.create_base_image() - print(f"Created {img.size} base image") - - # Test GameHelper - print("Testing GameHelper...") - GameHelper() - print("GameHelper initialized") - - # Test ConfigHelper - print("Testing ConfigHelper...") - ConfigHelper() - print("ConfigHelper initialized") - - print("All tests passed!") - - except ImportError as e: - print(f"Import error: {e}") - sys.exit(1) - except Exception as e: - print(f"Test error: {e}") - sys.exit(1) - - -def validate_config(config_file: str): - """Validate configuration file.""" - config_path = Path(config_file) - - if not config_path.exists(): - print(f"Configuration file not found: {config_file}") - sys.exit(1) - - try: - from ledmatrix_common import ConfigHelper - - config_helper = ConfigHelper() - config = config_helper.load_config(config_path) - - if config: - print(f"Configuration loaded successfully from {config_file}") - print(f"Found {len(config)} top-level keys") - else: - print(f"Failed to load configuration from {config_file}") - sys.exit(1) - - except Exception as e: - print(f"Validation error: {e}") - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/src/config_manager.py b/src/config_manager.py index 3eb888f7..8a7edbb1 100644 --- a/src/config_manager.py +++ b/src/config_manager.py @@ -449,10 +449,6 @@ class ConfigManager: """Get display configuration.""" return self.config.get('display', {}) - def get_clock_config(self) -> Dict[str, Any]: - """Get clock configuration.""" - return self.config.get('clock', {}) - def get_config(self) -> Dict[str, Any]: """Get the full configuration dictionary. diff --git a/src/web_interface/error_handler.py b/src/web_interface/error_handler.py index c15d373c..0af53732 100644 --- a/src/web_interface/error_handler.py +++ b/src/web_interface/error_handler.py @@ -1,11 +1,10 @@ """ Centralized error handling for web interface. -Provides decorators and helpers for consistent error handling across API endpoints. +Provides helpers for consistent error responses across API endpoints. """ -import functools -from typing import Callable, Any, Optional +from typing import Any, Optional from flask import jsonify from src.web_interface.errors import ( @@ -17,70 +16,6 @@ from src.logging_config import get_logger logger = get_logger(__name__) -def handle_errors( - default_error_code: Optional[ErrorCode] = None, - default_category: Optional[ErrorCategory] = None, - log_error: bool = True -): - """ - Decorator to handle errors in API endpoints. - - Catches exceptions and converts them to structured error responses. - - Args: - default_error_code: Default error code if exception doesn't match known types - default_category: Default error category - log_error: Whether to log the error - """ - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except WebInterfaceError as e: - # Already a structured error - if log_error: - logger.error( - f"Error in {func.__name__}: {e.message}", - extra={ - 'error_code': e.error_code.value, - 'category': e.category.value, - 'context': e.context - } - ) - return jsonify(e.to_dict()), 500 - - except Exception as e: - # Convert to structured error - web_error = WebInterfaceError.from_exception( - e, - error_code=default_error_code, - context={ - 'function': func.__name__, - 'endpoint': getattr(func, '__name__', 'unknown') - } - ) - - if default_category: - web_error.category = default_category - - if log_error: - logger.error( - f"Unhandled error in {func.__name__}: {e}", - exc_info=True, - extra={ - 'error_code': web_error.error_code.value, - 'category': web_error.category.value, - 'context': web_error.context - } - ) - - return jsonify(web_error.to_dict()), 500 - - return wrapper - return decorator - - def create_error_response( error_code: ErrorCode, message: str, diff --git a/src/web_interface/logging_config.py b/src/web_interface/logging_config.py deleted file mode 100644 index 010130d1..00000000 --- a/src/web_interface/logging_config.py +++ /dev/null @@ -1,160 +0,0 @@ -""" -Structured logging configuration for web interface. - -Provides JSON-formatted structured logging for better debugging and monitoring. -""" - -import json -import logging -import sys -from datetime import datetime -from typing import Dict, Any, Optional - - -class StructuredFormatter(logging.Formatter): - """ - JSON formatter for structured logging. - - Formats log records as JSON for easy parsing and analysis. - """ - - def format(self, record: logging.LogRecord) -> str: - """Format log record as JSON.""" - log_data = { - 'timestamp': datetime.utcnow().isoformat(), - 'level': record.levelname, - 'logger': record.name, - 'message': record.getMessage(), - 'module': record.module, - 'function': record.funcName, - 'line': record.lineno - } - - # Add exception info if present - if record.exc_info: - log_data['exception'] = self.formatException(record.exc_info) - - # Add extra fields from record - if hasattr(record, 'extra'): - log_data.update(record.extra) - - # Add context from record - if hasattr(record, 'context'): - log_data['context'] = record.context - - return json.dumps(log_data) - - def formatException(self, exc_info) -> Dict[str, Any]: - """Format exception as structured data.""" - import traceback - return { - 'type': exc_info[0].__name__ if exc_info[0] else None, - 'message': str(exc_info[1]) if exc_info[1] else None, - 'traceback': traceback.format_exception(*exc_info) - } - - -def setup_structured_logging( - level: int = logging.INFO, - use_json: bool = False, - output_stream = sys.stdout -) -> None: - """ - Set up structured logging for web interface. - - Args: - level: Logging level - use_json: Whether to use JSON formatting - output_stream: Output stream for logs - """ - root_logger = logging.getLogger() - root_logger.setLevel(level) - - # Remove existing handlers - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - # Create handler - handler = logging.StreamHandler(output_stream) - handler.setLevel(level) - - # Set formatter - if use_json: - formatter = StructuredFormatter() - else: - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s' - ) - - handler.setFormatter(formatter) - root_logger.addHandler(handler) - - -def log_plugin_operation( - logger: logging.Logger, - operation: str, - plugin_id: str, - status: str, - context: Optional[Dict[str, Any]] = None -) -> None: - """ - Log a plugin operation with structured data. - - Args: - logger: Logger instance - operation: Operation name (install, update, uninstall, etc.) - plugin_id: Plugin identifier - status: Operation status (success, failed, etc.) - context: Optional additional context - """ - extra = { - 'operation': operation, - 'plugin_id': plugin_id, - 'status': status - } - - if context: - extra['context'] = context - - logger.info( - f"Plugin operation: {operation} for {plugin_id} - {status}", - extra=extra - ) - - -def log_config_change( - logger: logging.Logger, - config_key: str, - action: str, - before: Optional[Dict[str, Any]] = None, - after: Optional[Dict[str, Any]] = None, - context: Optional[Dict[str, Any]] = None -) -> None: - """ - Log a configuration change with before/after values. - - Args: - logger: Logger instance - config_key: Configuration key that changed - action: Action performed (save, update, delete, etc.) - before: Configuration before change - after: Configuration after change - context: Optional additional context - """ - extra = { - 'config_key': config_key, - 'action': action - } - - if before: - extra['before'] = before - if after: - extra['after'] = after - if context: - extra['context'] = context - - logger.info( - f"Config change: {action} on {config_key}", - extra=extra - ) - diff --git a/test/test_config_manager.py b/test/test_config_manager.py index 29705f36..68c411e9 100644 --- a/test/test_config_manager.py +++ b/test/test_config_manager.py @@ -383,19 +383,6 @@ class TestConfigHelpers: display_config = manager.get_display_config() assert display_config["hardware"]["rows"] == 32 - def test_get_clock_config(self, tmp_path): - """Test getting clock config.""" - config_file = tmp_path / "config.json" - config_data = {"clock": {"format": "12h"}} - - with open(config_file, 'w') as f: - json.dump(config_data, f) - - manager = ConfigManager(config_path=str(config_file)) - manager.load_config() - - clock_config = manager.get_clock_config() - assert clock_config["format"] == "12h" class TestPluginConfigManagement: