mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-06 19:28:06 +00:00
chore: remove dead modules confirmed unused in-repo and across all store plugins
- src/common/cli.py: imports a 'ledmatrix_common' package that exists nowhere (not in this repo, any requirements file, or the plugin monorepo), so it cannot ever have run; its README section claimed scripts/dev/* used it, which was also untrue. - src/web_interface/logging_config.py: zero callers — the web app uses web_interface/logging_config.py (a different module), and nothing imports the src copy. - handle_errors decorator in src/web_interface/error_handler.py: zero call sites (the module's response helpers stay — they are used). - ConfigManager.get_clock_config(): reads a 'clock' config key that no longer exists anywhere; only caller was its own unit test. Deliberately kept despite zero in-repo callers: DisplayError, src/common/config_helper.py and display_helper.py — all documented as plugin-facing API (docs/PLUGIN_ERROR_HANDLING.md, src/common/README.md), and third-party plugins outside the official monorepo cannot be enumerated. Verified against a fresh clone of ledmatrix-plugins (43 plugins): zero references to any removed symbol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXb4mKcAkVaxkeTb3YnAdr
This commit is contained in:
@@ -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
|
when running as a service (used by `CacheManager` to set up its
|
||||||
persistent cache directory).
|
persistent cache directory).
|
||||||
|
|
||||||
## CLI Helpers (`cli.py`)
|
|
||||||
|
|
||||||
Shared CLI argument parsing helpers used by `scripts/dev/*` and other
|
|
||||||
command-line entry points.
|
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Use centralized logging**: Import from `src.logging_config` instead of creating loggers directly
|
1. **Use centralized logging**: Import from `src.logging_config` instead of creating loggers directly
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -449,10 +449,6 @@ class ConfigManager:
|
|||||||
"""Get display configuration."""
|
"""Get display configuration."""
|
||||||
return self.config.get('display', {})
|
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]:
|
def get_config(self) -> Dict[str, Any]:
|
||||||
"""Get the full configuration dictionary.
|
"""Get the full configuration dictionary.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
Centralized error handling for web interface.
|
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 Any, Optional
|
||||||
from typing import Callable, Any, Optional
|
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
|
|
||||||
from src.web_interface.errors import (
|
from src.web_interface.errors import (
|
||||||
@@ -17,70 +16,6 @@ from src.logging_config import get_logger
|
|||||||
logger = get_logger(__name__)
|
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(
|
def create_error_response(
|
||||||
error_code: ErrorCode,
|
error_code: ErrorCode,
|
||||||
message: str,
|
message: str,
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -383,19 +383,6 @@ class TestConfigHelpers:
|
|||||||
display_config = manager.get_display_config()
|
display_config = manager.get_display_config()
|
||||||
assert display_config["hardware"]["rows"] == 32
|
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:
|
class TestPluginConfigManagement:
|
||||||
|
|||||||
Reference in New Issue
Block a user