forked from HomeLab/unraid-mcp
fix: address 54 MEDIUM/LOW priority PR review issues
Comprehensive fixes across Python code, shell scripts, and documentation addressing all remaining MEDIUM and LOW priority review comments. Python Code Fixes (27 fixes): - tools/info.py: Simplified dispatch with lookup tables, defensive guards, CPU fallback formatting, !s conversion flags, module-level sync assertion - tools/docker.py: Case-insensitive container ID regex, keyword-only confirm, module-level ALL_ACTIONS constant - tools/virtualization.py: Normalized single-VM dict responses, unified list/details queries - core/client.py: Fixed HTTP client singleton race condition, compound key substring matching for sensitive data redaction - subscriptions/: Extracted SSL context creation to shared helper in utils.py, replaced deprecated ssl._create_unverified_context API - tools/array.py: Renamed parity_history to parity_status, hoisted ALL_ACTIONS - tools/storage.py: Fixed dict(None) risks, temperature 0 falsiness bug - tools/notifications.py, keys.py, rclone.py: Fixed dict(None) TypeError risks - tests/: Fixed generator type annotations, added coverage for compound keys Shell Script Fixes (13 fixes): - dashboard.sh: Dynamic server discovery, conditional debug output, null-safe jq, notification count guard order, removed unused variables - unraid-query.sh: Proper JSON escaping via jq, --ignore-errors and --insecure CLI flags, TLS verification now on by default - validate-marketplace.sh: Removed unused YELLOW variable, defensive jq, simplified repository URL output Documentation Fixes (24+ fixes): - Version consistency: Updated all references to v0.2.0 across pyproject.toml, plugin.json, marketplace.json, MARKETPLACE.md, __init__.py, README files - Tool count updates: Changed all "26 tools" references to "10 tools, 90 actions" - Markdown lint: Fixed MD022, MD031, MD047 issues across multiple files - Research docs: Fixed auth headers, removed web artifacts, corrected stale info - Skills docs: Fixed query examples, endpoint counts, env var references All 227 tests pass, ruff and ty checks clean.
This commit is contained in:
@@ -5,8 +5,8 @@ that cap at 10MB and start over (no rotation) for consistent use across all modu
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytz
|
||||
from rich.align import Align
|
||||
@@ -16,6 +16,7 @@ from rich.panel import Panel
|
||||
from rich.rule import Rule
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
try:
|
||||
from fastmcp.utilities.logging import get_logger as get_fastmcp_logger
|
||||
FASTMCP_AVAILABLE = True
|
||||
@@ -24,6 +25,7 @@ except ImportError:
|
||||
|
||||
from .settings import LOG_FILE_PATH, LOG_LEVEL_STR
|
||||
|
||||
|
||||
# Global Rich console for consistent formatting
|
||||
console = Console(stderr=True, force_terminal=True)
|
||||
|
||||
@@ -31,7 +33,7 @@ console = Console(stderr=True, force_terminal=True)
|
||||
class OverwriteFileHandler(logging.FileHandler):
|
||||
"""Custom file handler that overwrites the log file when it reaches max size."""
|
||||
|
||||
def __init__(self, filename, max_bytes=10*1024*1024, mode='a', encoding=None, delay=False):
|
||||
def __init__(self, filename, max_bytes=10*1024*1024, mode="a", encoding=None, delay=False):
|
||||
"""Initialize the handler.
|
||||
|
||||
Args:
|
||||
@@ -47,18 +49,19 @@ class OverwriteFileHandler(logging.FileHandler):
|
||||
def emit(self, record):
|
||||
"""Emit a record, checking file size and overwriting if needed."""
|
||||
# Check file size before writing
|
||||
if self.stream and hasattr(self.stream, 'name'):
|
||||
if self.stream and hasattr(self.stream, "name"):
|
||||
try:
|
||||
if os.path.exists(self.baseFilename):
|
||||
file_size = os.path.getsize(self.baseFilename)
|
||||
base_path = Path(self.baseFilename)
|
||||
if base_path.exists():
|
||||
file_size = base_path.stat().st_size
|
||||
if file_size >= self.max_bytes:
|
||||
# Close current stream
|
||||
if self.stream:
|
||||
self.stream.close()
|
||||
|
||||
# Remove the old file and start fresh
|
||||
if os.path.exists(self.baseFilename):
|
||||
os.remove(self.baseFilename)
|
||||
if base_path.exists():
|
||||
base_path.unlink()
|
||||
|
||||
# Reopen with truncate mode
|
||||
self.stream = self._open()
|
||||
@@ -75,9 +78,10 @@ class OverwriteFileHandler(logging.FileHandler):
|
||||
)
|
||||
super().emit(reset_record)
|
||||
|
||||
except OSError:
|
||||
# If there's an issue checking file size, just continue normally
|
||||
pass
|
||||
except OSError as e:
|
||||
import sys
|
||||
print(f"WARNING: Log file size check failed: {e}. Continuing without rotation.",
|
||||
file=sys.stderr)
|
||||
|
||||
# Emit the original record
|
||||
super().emit(record)
|
||||
@@ -119,11 +123,11 @@ def setup_logger(name: str = "UnraidMCPServer") -> logging.Logger:
|
||||
file_handler = OverwriteFileHandler(
|
||||
LOG_FILE_PATH,
|
||||
max_bytes=10*1024*1024,
|
||||
encoding='utf-8'
|
||||
encoding="utf-8"
|
||||
)
|
||||
file_handler.setLevel(numeric_log_level)
|
||||
file_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s"
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(file_handler)
|
||||
@@ -163,11 +167,11 @@ def configure_fastmcp_logger_with_rich() -> logging.Logger | None:
|
||||
file_handler = OverwriteFileHandler(
|
||||
LOG_FILE_PATH,
|
||||
max_bytes=10*1024*1024,
|
||||
encoding='utf-8'
|
||||
encoding="utf-8"
|
||||
)
|
||||
file_handler.setLevel(numeric_log_level)
|
||||
file_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s'
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s"
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
fastmcp_logger.addHandler(file_handler)
|
||||
@@ -196,7 +200,7 @@ def configure_fastmcp_logger_with_rich() -> logging.Logger | None:
|
||||
root_file_handler = OverwriteFileHandler(
|
||||
LOG_FILE_PATH,
|
||||
max_bytes=10*1024*1024,
|
||||
encoding='utf-8'
|
||||
encoding="utf-8"
|
||||
)
|
||||
root_file_handler.setLevel(numeric_log_level)
|
||||
root_file_handler.setFormatter(file_formatter)
|
||||
@@ -225,12 +229,12 @@ def log_configuration_status(logger: logging.Logger) -> None:
|
||||
config = get_config_summary()
|
||||
|
||||
# Log configuration status
|
||||
if config['api_url_configured']:
|
||||
if config["api_url_configured"]:
|
||||
logger.info(f"UNRAID_API_URL loaded: {config['api_url_preview']}")
|
||||
else:
|
||||
logger.warning("UNRAID_API_URL not found in environment or .env file.")
|
||||
|
||||
if config['api_key_configured']:
|
||||
if config["api_key_configured"]:
|
||||
logger.info("UNRAID_API_KEY loaded: ****") # Don't log the key itself
|
||||
else:
|
||||
logger.warning("UNRAID_API_KEY not found in environment or .env file.")
|
||||
@@ -240,14 +244,14 @@ def log_configuration_status(logger: logging.Logger) -> None:
|
||||
logger.info(f"UNRAID_MCP_TRANSPORT set to: {config['transport']}")
|
||||
logger.info(f"UNRAID_MCP_LOG_LEVEL set to: {config['log_level']}")
|
||||
|
||||
if not config['config_valid']:
|
||||
if not config["config_valid"]:
|
||||
logger.error(f"Missing required configuration: {config['missing_config']}")
|
||||
|
||||
|
||||
# Development logging helpers for Rich formatting
|
||||
def get_est_timestamp() -> str:
|
||||
"""Get current timestamp in EST timezone with YY/MM/DD format."""
|
||||
est = pytz.timezone('US/Eastern')
|
||||
est = pytz.timezone("US/Eastern")
|
||||
now = datetime.now(est)
|
||||
return now.strftime("%y/%m/%d %H:%M:%S")
|
||||
|
||||
@@ -271,7 +275,7 @@ def log_with_level_and_indent(message: str, level: str = "info", indent: int = 0
|
||||
"error": {"color": "#BF616A", "icon": "❌", "style": "bold"}, # Nordic red
|
||||
"warning": {"color": "#EBCB8B", "icon": "⚠️", "style": ""}, # Nordic yellow
|
||||
"success": {"color": "#A3BE8C", "icon": "✅", "style": "bold"}, # Nordic green
|
||||
"info": {"color": "#5E81AC", "icon": "ℹ️", "style": "bold"}, # Nordic blue (bold)
|
||||
"info": {"color": "#5E81AC", "icon": "\u2139\ufe0f", "style": "bold"}, # Nordic blue (bold)
|
||||
"status": {"color": "#81A1C1", "icon": "🔍", "style": ""}, # Light Nordic blue
|
||||
"debug": {"color": "#4C566A", "icon": "🐛", "style": ""}, # Nordic dark gray
|
||||
}
|
||||
@@ -328,11 +332,7 @@ def log_status(message: str, indent: int = 0) -> None:
|
||||
if FASTMCP_AVAILABLE:
|
||||
# Use FastMCP logger with Rich formatting
|
||||
_fastmcp_logger = configure_fastmcp_logger_with_rich()
|
||||
if _fastmcp_logger is not None:
|
||||
logger = _fastmcp_logger
|
||||
else:
|
||||
# Fallback to our custom logger if FastMCP configuration fails
|
||||
logger = setup_logger()
|
||||
logger = _fastmcp_logger if _fastmcp_logger is not None else setup_logger()
|
||||
else:
|
||||
# Fallback to our custom logger if FastMCP is not available
|
||||
logger = setup_logger()
|
||||
|
||||
Reference in New Issue
Block a user