mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-06 23:35:08 +00:00
feat: deprecate unused plugin-facing methods for removal in 3.7.0 (#610)
35 methods on CacheManager, DisplayManager, FontManager and PluginManager have no caller in core, the ledmatrix-plugins monorepo or the registry's third-party plugins, but plugins live elsewhere, so they stay for one release. src.deprecation.deprecated logs a warning (and emits a DeprecationWarning) the first time each is called in a process, naming the release that removes it. The list and replacements are in CHANGELOG and PLUGIN_API_REFERENCE's new Deprecated APIs section; a test pins the set. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,24 @@ accepts both, but the store flags the old spelling as deprecated
|
|||||||
- `src.wifi_manager.get_wifi_status_path()` — where WiFi status messages for
|
- `src.wifi_manager.get_wifi_status_path()` — where WiFi status messages for
|
||||||
the display are written (`config/wifi_status.json`).
|
the display are written (`config/wifi_status.json`).
|
||||||
|
|
||||||
|
Deprecated, removed in 3.7.0 (each logs a warning on first use; see
|
||||||
|
`docs/PLUGIN_API_REFERENCE.md#deprecated-apis` for replacements). Nothing in
|
||||||
|
core, the monorepo or the registry's third-party plugins calls them:
|
||||||
|
|
||||||
|
- `CacheManager`: `has_data_changed`, `update_cache`, `setup_persistent_cache`,
|
||||||
|
`get_sport_live_interval`, `get_sport_key_from_cache_key`,
|
||||||
|
`get_background_cached_data`, `is_background_data_available`,
|
||||||
|
`record_cache_hit`, `record_cache_miss`, `record_fetch_time`,
|
||||||
|
`get_cache_metrics`, `log_cache_metrics`, `get_memory_cache_stats`.
|
||||||
|
- `DisplayManager`: `draw_weather_icon`, `draw_sun`, `draw_cloud`, `draw_rain`,
|
||||||
|
`draw_snow`, `draw_text_with_icons`, `get_scrolling_stats`.
|
||||||
|
- `FontManager`: `set_override`, `remove_override`, `get_overrides`,
|
||||||
|
`add_font`, `remove_font`, `validate_font`, `get_font_catalog`,
|
||||||
|
`get_available_fonts`, `get_size_tokens`, `get_performance_stats`,
|
||||||
|
`get_manager_fonts`, `get_detected_fonts`, `get_plugin_fonts`,
|
||||||
|
`unregister_plugin_fonts`.
|
||||||
|
- `PluginManager.get_enabled_plugins`.
|
||||||
|
|
||||||
## 3.5.0
|
## 3.5.0
|
||||||
|
|
||||||
New modules a plugin may import via `src.*` (floor on 3.5.0):
|
New modules a plugin may import via `src.*` (floor on 3.5.0):
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Complete API reference for plugin developers. This document describes all method
|
|||||||
- [Display Manager](#display-manager)
|
- [Display Manager](#display-manager)
|
||||||
- [Cache Manager](#cache-manager)
|
- [Cache Manager](#cache-manager)
|
||||||
- [Plugin Manager](#plugin-manager)
|
- [Plugin Manager](#plugin-manager)
|
||||||
|
- [Deprecated APIs](#deprecated-apis)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1074,3 +1075,23 @@ if "weather" in enabled_plugins:
|
|||||||
- [Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md) - Complete development guide
|
- [Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md) - Complete development guide
|
||||||
- [Advanced Plugin Development](ADVANCED_PLUGIN_DEVELOPMENT.md) - Advanced patterns and examples
|
- [Advanced Plugin Development](ADVANCED_PLUGIN_DEVELOPMENT.md) - Advanced patterns and examples
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deprecated APIs
|
||||||
|
|
||||||
|
These still work in 3.6 but log a warning the first time they are called
|
||||||
|
(`journalctl -u ledmatrix` shows which one), and are **removed in 3.7.0**.
|
||||||
|
Nothing in core, the official plugins or the third-party plugins in the
|
||||||
|
registry calls them.
|
||||||
|
|
||||||
|
| Object | Methods | Instead |
|
||||||
|
|---|---|---|
|
||||||
|
| `cache_manager` | `update_cache` | `set()` |
|
||||||
|
| `cache_manager` | `get_background_cached_data`, `is_background_data_available` | `get()` |
|
||||||
|
| `cache_manager` | `has_data_changed`, `setup_persistent_cache`, `get_sport_live_interval`, `get_sport_key_from_cache_key`, `record_cache_hit`, `record_cache_miss`, `record_fetch_time`, `get_cache_metrics`, `log_cache_metrics`, `get_memory_cache_stats` | no replacement |
|
||||||
|
| `display_manager` | `draw_weather_icon`, `draw_sun`, `draw_cloud`, `draw_rain`, `draw_snow`, `draw_text_with_icons` | draw your own icons (the weather plugin ships `WeatherIcons`) |
|
||||||
|
| `display_manager` | `get_scrolling_stats` | no replacement |
|
||||||
|
| `font_manager` | `get_font_catalog`, `get_available_fonts` | read `font_catalog` |
|
||||||
|
| `font_manager` | `set_override`, `remove_override`, `get_overrides`, `add_font`, `remove_font`, `validate_font`, `get_size_tokens`, `get_performance_stats`, `get_manager_fonts`, `get_detected_fonts`, `get_plugin_fonts`, `unregister_plugin_fonts` | no replacement |
|
||||||
|
| `plugin_manager` | `get_enabled_plugins` | check `enabled` on the entries in `plugin_manager.plugins` |
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from src.cache.disk_cache import DiskCache
|
|||||||
from src.cache.cache_strategy import CacheStrategy
|
from src.cache.cache_strategy import CacheStrategy
|
||||||
from src.cache.cache_metrics import CacheMetrics
|
from src.cache.cache_metrics import CacheMetrics
|
||||||
from src.logging_config import get_logger
|
from src.logging_config import get_logger
|
||||||
|
from src.deprecation import deprecated
|
||||||
|
|
||||||
# Canonical implementation lives in src.cache.disk_cache; re-exported here
|
# Canonical implementation lives in src.cache.disk_cache; re-exported here
|
||||||
# because this module's docstring documents it and external code may import
|
# because this module's docstring documents it and external code may import
|
||||||
@@ -473,6 +474,7 @@ class CacheManager:
|
|||||||
"""Get the cache directory path."""
|
"""Get the cache directory path."""
|
||||||
return self.cache_dir
|
return self.cache_dir
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def has_data_changed(self, data_type: str, new_data: Dict[str, Any]) -> bool:
|
def has_data_changed(self, data_type: str, new_data: Dict[str, Any]) -> bool:
|
||||||
"""Check if data has changed from cached version."""
|
"""Check if data has changed from cached version."""
|
||||||
cached_data = self.load_cache(data_type)
|
cached_data = self.load_cache(data_type)
|
||||||
@@ -578,6 +580,7 @@ class CacheManager:
|
|||||||
"""Check if the US stock market is currently open."""
|
"""Check if the US stock market is currently open."""
|
||||||
return self._strategy_component.is_market_open()
|
return self._strategy_component.is_market_open()
|
||||||
|
|
||||||
|
@deprecated("3.7.0", "use set()")
|
||||||
def update_cache(self, data_type: str, data: Dict[str, Any]) -> bool:
|
def update_cache(self, data_type: str, data: Dict[str, Any]) -> bool:
|
||||||
"""Update cache with new data."""
|
"""Update cache with new data."""
|
||||||
cache_data = {
|
cache_data = {
|
||||||
@@ -623,6 +626,7 @@ class CacheManager:
|
|||||||
cache_data['ttl'] = ttl
|
cache_data['ttl'] = ttl
|
||||||
self.save_cache(key, cache_data)
|
self.save_cache(key, cache_data)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def setup_persistent_cache(self) -> bool:
|
def setup_persistent_cache(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Set up a persistent cache directory with proper permissions.
|
Set up a persistent cache directory with proper permissions.
|
||||||
@@ -834,6 +838,7 @@ class CacheManager:
|
|||||||
else:
|
else:
|
||||||
self.logger.info("Disk cache cleanup thread stopped successfully")
|
self.logger.info("Disk cache cleanup thread stopped successfully")
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_sport_live_interval(self, sport_key: str) -> int:
|
def get_sport_live_interval(self, sport_key: str) -> int:
|
||||||
"""
|
"""
|
||||||
Get the live_update_interval for a specific sport from config.
|
Get the live_update_interval for a specific sport from config.
|
||||||
@@ -855,6 +860,7 @@ class CacheManager:
|
|||||||
"""
|
"""
|
||||||
return self._strategy_component.get_data_type_from_key(key)
|
return self._strategy_component.get_data_type_from_key(key)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_sport_key_from_cache_key(self, key: str) -> Optional[str]:
|
def get_sport_key_from_cache_key(self, key: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Extract sport key from cache key to determine appropriate live_update_interval.
|
Extract sport key from cache key to determine appropriate live_update_interval.
|
||||||
@@ -894,6 +900,7 @@ class CacheManager:
|
|||||||
data_type = self.get_data_type_from_key(key)
|
data_type = self.get_data_type_from_key(key)
|
||||||
return self.get_cached_data_with_strategy(key, data_type)
|
return self.get_cached_data_with_strategy(key, data_type)
|
||||||
|
|
||||||
|
@deprecated("3.7.0", "use get()")
|
||||||
def get_background_cached_data(self, key: str, sport_key: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
def get_background_cached_data(self, key: str, sport_key: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Get data from background service cache with appropriate strategy.
|
Get data from background service cache with appropriate strategy.
|
||||||
@@ -931,6 +938,7 @@ class CacheManager:
|
|||||||
self.record_cache_miss('background')
|
self.record_cache_miss('background')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@deprecated("3.7.0", "use get()")
|
||||||
def is_background_data_available(self, key: str, sport_key: Optional[str] = None) -> bool:
|
def is_background_data_available(self, key: str, sport_key: Optional[str] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if background service has fresh data available.
|
Check if background service has fresh data available.
|
||||||
@@ -960,26 +968,32 @@ class CacheManager:
|
|||||||
date_str = datetime.now(pytz.utc).strftime('%Y%m%d')
|
date_str = datetime.now(pytz.utc).strftime('%Y%m%d')
|
||||||
return f"{sport}_{date_str}"
|
return f"{sport}_{date_str}"
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def record_cache_hit(self, cache_type: str = 'regular') -> None:
|
def record_cache_hit(self, cache_type: str = 'regular') -> None:
|
||||||
"""Record a cache hit for performance monitoring."""
|
"""Record a cache hit for performance monitoring."""
|
||||||
self._metrics_component.record_hit(cache_type)
|
self._metrics_component.record_hit(cache_type)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def record_cache_miss(self, cache_type: str = 'regular') -> None:
|
def record_cache_miss(self, cache_type: str = 'regular') -> None:
|
||||||
"""Record a cache miss for performance monitoring."""
|
"""Record a cache miss for performance monitoring."""
|
||||||
self._metrics_component.record_miss(cache_type)
|
self._metrics_component.record_miss(cache_type)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def record_fetch_time(self, duration: float) -> None:
|
def record_fetch_time(self, duration: float) -> None:
|
||||||
"""Record fetch operation duration for performance monitoring."""
|
"""Record fetch operation duration for performance monitoring."""
|
||||||
self._metrics_component.record_fetch_time(duration)
|
self._metrics_component.record_fetch_time(duration)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_cache_metrics(self) -> Dict[str, Any]:
|
def get_cache_metrics(self) -> Dict[str, Any]:
|
||||||
"""Get current cache performance metrics."""
|
"""Get current cache performance metrics."""
|
||||||
return self._metrics_component.get_metrics()
|
return self._metrics_component.get_metrics()
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def log_cache_metrics(self) -> None:
|
def log_cache_metrics(self) -> None:
|
||||||
"""Log current cache performance metrics."""
|
"""Log current cache performance metrics."""
|
||||||
self._metrics_component.log_metrics()
|
self._metrics_component.log_metrics()
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_memory_cache_stats(self) -> Dict[str, Any]:
|
def get_memory_cache_stats(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get statistics about the memory cache.
|
Get statistics about the memory cache.
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
"""Marking plugin-facing core APIs for removal.
|
||||||
|
|
||||||
|
Plugins live in other repositories, so a method nothing in core calls may
|
||||||
|
still be called by a plugin nobody has checked. Such methods get
|
||||||
|
``@deprecated`` for one release before they are removed: the first call in a
|
||||||
|
process logs a warning naming the method and the release that removes it
|
||||||
|
(visible in ``journalctl -u ledmatrix``), and emits a DeprecationWarning for
|
||||||
|
tooling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import threading
|
||||||
|
import warnings
|
||||||
|
from typing import Callable, Optional, TypeVar
|
||||||
|
|
||||||
|
from src.logging_config import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
F = TypeVar("F", bound=Callable)
|
||||||
|
|
||||||
|
_warned = set()
|
||||||
|
_warned_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated(removal: str, alternative: Optional[str] = None) -> Callable[[F], F]:
|
||||||
|
"""Decorate a function or method that will be removed in ``removal``."""
|
||||||
|
|
||||||
|
def decorate(func: F) -> F:
|
||||||
|
message = f"{func.__qualname__}() is deprecated and will be removed in LEDMatrix {removal}"
|
||||||
|
if alternative:
|
||||||
|
message += f"; {alternative}"
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with _warned_lock:
|
||||||
|
first = func.__qualname__ not in _warned
|
||||||
|
_warned.add(func.__qualname__)
|
||||||
|
if first:
|
||||||
|
logger.warning(message)
|
||||||
|
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
wrapper.__deprecated__ = message
|
||||||
|
return wrapper # type: ignore[return-value]
|
||||||
|
|
||||||
|
return decorate
|
||||||
@@ -52,6 +52,7 @@ import zlib
|
|||||||
import freetype
|
import freetype
|
||||||
|
|
||||||
from src.common import snapshot_policy
|
from src.common import snapshot_policy
|
||||||
|
from src.deprecation import deprecated
|
||||||
from src.common.permission_utils import (
|
from src.common.permission_utils import (
|
||||||
ensure_directory_permissions,
|
ensure_directory_permissions,
|
||||||
ensure_file_permissions,
|
ensure_file_permissions,
|
||||||
@@ -1139,6 +1140,7 @@ class DisplayManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error drawing text: {e}", exc_info=True)
|
logger.error(f"Error drawing text: {e}", exc_info=True)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def draw_sun(self, x: int, y: int, size: int = 16):
|
def draw_sun(self, x: int, y: int, size: int = 16):
|
||||||
"""Draw a sun icon using yellow circles and lines."""
|
"""Draw a sun icon using yellow circles and lines."""
|
||||||
center = (x + size//2, y + size//2)
|
center = (x + size//2, y + size//2)
|
||||||
@@ -1159,6 +1161,7 @@ class DisplayManager:
|
|||||||
end_y = center[1] + ((radius + ray_length) * math.sin(rad))
|
end_y = center[1] + ((radius + ray_length) * math.sin(rad))
|
||||||
self.draw.line([start_x, start_y, end_x, end_y], fill=(255, 255, 0), width=2)
|
self.draw.line([start_x, start_y, end_x, end_y], fill=(255, 255, 0), width=2)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def draw_cloud(self, x: int, y: int, size: int = 16, color=(200, 200, 200)):
|
def draw_cloud(self, x: int, y: int, size: int = 16, color=(200, 200, 200)):
|
||||||
"""Draw a cloud icon."""
|
"""Draw a cloud icon."""
|
||||||
# Draw multiple circles to form a cloud shape
|
# Draw multiple circles to form a cloud shape
|
||||||
@@ -1166,6 +1169,7 @@ class DisplayManager:
|
|||||||
self.draw.ellipse([x+size//2, y+size//3, x+size//2+size//2, y+size//3+size//2], fill=color)
|
self.draw.ellipse([x+size//2, y+size//3, x+size//2+size//2, y+size//3+size//2], fill=color)
|
||||||
self.draw.ellipse([x+size//3, y+size//6, x+size//3+size//2, y+size//6+size//2], fill=color)
|
self.draw.ellipse([x+size//3, y+size//6, x+size//3+size//2, y+size//6+size//2], fill=color)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def draw_rain(self, x: int, y: int, size: int = 16):
|
def draw_rain(self, x: int, y: int, size: int = 16):
|
||||||
"""Draw rain icon with cloud and droplets."""
|
"""Draw rain icon with cloud and droplets."""
|
||||||
# Draw cloud
|
# Draw cloud
|
||||||
@@ -1180,6 +1184,7 @@ class DisplayManager:
|
|||||||
self.draw.line([drop_x, drop_y, drop_x, drop_y+drop_size],
|
self.draw.line([drop_x, drop_y, drop_x, drop_y+drop_size],
|
||||||
fill=drop_color, width=2)
|
fill=drop_color, width=2)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def draw_snow(self, x: int, y: int, size: int = 16):
|
def draw_snow(self, x: int, y: int, size: int = 16):
|
||||||
"""Draw snow icon with cloud and snowflakes."""
|
"""Draw snow icon with cloud and snowflakes."""
|
||||||
# Draw cloud
|
# Draw cloud
|
||||||
@@ -1300,6 +1305,7 @@ class DisplayManager:
|
|||||||
]
|
]
|
||||||
self.draw.polygon(bolt_points, fill=bolt_color)
|
self.draw.polygon(bolt_points, fill=bolt_color)
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def draw_weather_icon(self, condition: str, x: int, y: int, size: int = 16) -> None:
|
def draw_weather_icon(self, condition: str, x: int, y: int, size: int = 16) -> None:
|
||||||
"""Draw a weather icon based on the condition."""
|
"""Draw a weather icon based on the condition."""
|
||||||
if condition.lower() in ['clear', 'sunny']:
|
if condition.lower() in ['clear', 'sunny']:
|
||||||
@@ -1316,6 +1322,7 @@ class DisplayManager:
|
|||||||
self._draw_sun(x, y, size)
|
self._draw_sun(x, y, size)
|
||||||
# Note: No update_display() here - let the caller handle the update
|
# Note: No update_display() here - let the caller handle the update
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def draw_text_with_icons(self, text: str, icons: List[tuple] = None, x: int = None, y: int = None,
|
def draw_text_with_icons(self, text: str, icons: List[tuple] = None, x: int = None, y: int = None,
|
||||||
color: tuple = (255, 255, 255)):
|
color: tuple = (255, 255, 255)):
|
||||||
"""Draw text with weather icons at specified positions."""
|
"""Draw text with weather icons at specified positions."""
|
||||||
@@ -1600,6 +1607,7 @@ class DisplayManager:
|
|||||||
if removed_count > 0:
|
if removed_count > 0:
|
||||||
logger.debug(f"Cleaned up {removed_count} expired deferred updates")
|
logger.debug(f"Cleaned up {removed_count} expired deferred updates")
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_scrolling_stats(self) -> dict:
|
def get_scrolling_stats(self) -> dict:
|
||||||
"""Get current scrolling statistics for debugging."""
|
"""Get current scrolling statistics for debugging."""
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from pathlib import Path
|
|||||||
from PIL import ImageFont
|
from PIL import ImageFont
|
||||||
from src.common.font_layout import load_truetype, resolve_asset_path
|
from src.common.font_layout import load_truetype, resolve_asset_path
|
||||||
from typing import Dict, Tuple, Optional, Union, Any, List
|
from typing import Dict, Tuple, Optional, Union, Any, List
|
||||||
|
from src.deprecation import deprecated
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -167,6 +168,7 @@ class FontManager:
|
|||||||
|
|
||||||
logger.debug(f"Registered font for {manager_id}.{element_key}: {family}@{size_px}px")
|
logger.debug(f"Registered font for {manager_id}.{element_key}: {family}@{size_px}px")
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_manager_fonts(self, manager_id: Optional[str] = None) -> Dict[str, Any]:
|
def get_manager_fonts(self, manager_id: Optional[str] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get registered fonts for a specific manager or all managers.
|
Get registered fonts for a specific manager or all managers.
|
||||||
@@ -181,6 +183,7 @@ class FontManager:
|
|||||||
return self.manager_fonts.get(manager_id, {})
|
return self.manager_fonts.get(manager_id, {})
|
||||||
return self.manager_fonts.copy()
|
return self.manager_fonts.copy()
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_detected_fonts(self) -> Dict[str, Dict[str, Any]]:
|
def get_detected_fonts(self) -> Dict[str, Dict[str, Any]]:
|
||||||
"""Get all detected font usage across managers."""
|
"""Get all detected font usage across managers."""
|
||||||
return self.detected_fonts.copy()
|
return self.detected_fonts.copy()
|
||||||
@@ -357,6 +360,7 @@ class FontManager:
|
|||||||
logger.error(f"Plugin font not found: {font_path}")
|
logger.error(f"Plugin font not found: {font_path}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def unregister_plugin_fonts(self, plugin_id: str) -> bool:
|
def unregister_plugin_fonts(self, plugin_id: str) -> bool:
|
||||||
"""Unregister all fonts for a plugin."""
|
"""Unregister all fonts for a plugin."""
|
||||||
try:
|
try:
|
||||||
@@ -393,6 +397,7 @@ class FontManager:
|
|||||||
for key in keys_to_remove:
|
for key in keys_to_remove:
|
||||||
del self.font_cache[key]
|
del self.font_cache[key]
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_plugin_fonts(self, plugin_id: str) -> List[str]:
|
def get_plugin_fonts(self, plugin_id: str) -> List[str]:
|
||||||
"""Get list of font families registered by a plugin."""
|
"""Get list of font families registered by a plugin."""
|
||||||
if plugin_id in self.plugin_font_catalogs:
|
if plugin_id in self.plugin_font_catalogs:
|
||||||
@@ -637,6 +642,7 @@ class FontManager:
|
|||||||
|
|
||||||
# ==================== Override Management ====================
|
# ==================== Override Management ====================
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def set_override(self, element_key: str, family: str = None, size_px: int = None):
|
def set_override(self, element_key: str, family: str = None, size_px: int = None):
|
||||||
"""Set font override for a specific element."""
|
"""Set font override for a specific element."""
|
||||||
if element_key not in self.font_overrides:
|
if element_key not in self.font_overrides:
|
||||||
@@ -656,6 +662,7 @@ class FontManager:
|
|||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
logger.info(f"Font override set for {element_key}: {self.font_overrides.get(element_key, {})}")
|
logger.info(f"Font override set for {element_key}: {self.font_overrides.get(element_key, {})}")
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def remove_override(self, element_key: str):
|
def remove_override(self, element_key: str):
|
||||||
"""Remove font override for a specific element."""
|
"""Remove font override for a specific element."""
|
||||||
if element_key in self.font_overrides:
|
if element_key in self.font_overrides:
|
||||||
@@ -664,6 +671,7 @@ class FontManager:
|
|||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
logger.info(f"Font override removed for {element_key}")
|
logger.info(f"Font override removed for {element_key}")
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_overrides(self) -> Dict[str, Dict[str, str]]:
|
def get_overrides(self) -> Dict[str, Dict[str, str]]:
|
||||||
"""Get current font overrides."""
|
"""Get current font overrides."""
|
||||||
return self.font_overrides.copy()
|
return self.font_overrides.copy()
|
||||||
@@ -753,10 +761,12 @@ class FontManager:
|
|||||||
self.metrics_cache.clear()
|
self.metrics_cache.clear()
|
||||||
logger.info("Font cache cleared")
|
logger.info("Font cache cleared")
|
||||||
|
|
||||||
|
@deprecated("3.7.0", "read font_catalog")
|
||||||
def get_available_fonts(self) -> Dict[str, str]:
|
def get_available_fonts(self) -> Dict[str, str]:
|
||||||
"""Get dictionary of available font families and their paths."""
|
"""Get dictionary of available font families and their paths."""
|
||||||
return self.font_catalog.copy()
|
return self.font_catalog.copy()
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_size_tokens(self) -> Dict[str, int]:
|
def get_size_tokens(self) -> Dict[str, int]:
|
||||||
"""Get available size tokens."""
|
"""Get available size tokens."""
|
||||||
return self.size_tokens.copy()
|
return self.size_tokens.copy()
|
||||||
@@ -767,6 +777,7 @@ class FontManager:
|
|||||||
self.performance_stats[operation] = {}
|
self.performance_stats[operation] = {}
|
||||||
self.performance_stats[operation][font_key] = duration
|
self.performance_stats[operation][font_key] = duration
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def get_performance_stats(self) -> Dict[str, Any]:
|
def get_performance_stats(self) -> Dict[str, Any]:
|
||||||
"""Get performance statistics."""
|
"""Get performance statistics."""
|
||||||
uptime = time.time() - self.performance_stats["start_time"]
|
uptime = time.time() - self.performance_stats["start_time"]
|
||||||
@@ -788,10 +799,12 @@ class FontManager:
|
|||||||
"detected_fonts": len(self.detected_fonts)
|
"detected_fonts": len(self.detected_fonts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@deprecated("3.7.0", "read font_catalog")
|
||||||
def get_font_catalog(self) -> Dict[str, str]:
|
def get_font_catalog(self) -> Dict[str, str]:
|
||||||
"""Get the current font catalog."""
|
"""Get the current font catalog."""
|
||||||
return self.font_catalog.copy()
|
return self.font_catalog.copy()
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def add_font(self, font_file_path: str, family_name: str) -> bool:
|
def add_font(self, font_file_path: str, family_name: str) -> bool:
|
||||||
"""Add a new font to the catalog."""
|
"""Add a new font to the catalog."""
|
||||||
try:
|
try:
|
||||||
@@ -824,6 +837,7 @@ class FontManager:
|
|||||||
logger.error(f"Error adding font {family_name}: {e}")
|
logger.error(f"Error adding font {family_name}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def remove_font(self, family_name: str) -> bool:
|
def remove_font(self, family_name: str) -> bool:
|
||||||
"""Remove a font from the catalog."""
|
"""Remove a font from the catalog."""
|
||||||
try:
|
try:
|
||||||
@@ -851,6 +865,7 @@ class FontManager:
|
|||||||
logger.error(f"Error removing font {family_name}: {e}")
|
logger.error(f"Error removing font {family_name}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@deprecated("3.7.0")
|
||||||
def validate_font(self, font_path: str) -> Dict[str, Any]:
|
def validate_font(self, font_path: str) -> Dict[str, Any]:
|
||||||
"""Validate a font file."""
|
"""Validate a font file."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from src.plugin_system.schema_manager import (
|
|||||||
CORE_VEGAS_TUNING_KEYS, SchemaManager, normalize_legacy_booleans,
|
CORE_VEGAS_TUNING_KEYS, SchemaManager, normalize_legacy_booleans,
|
||||||
)
|
)
|
||||||
from src.common.path_safety import safe_path_component
|
from src.common.path_safety import safe_path_component
|
||||||
|
from src.deprecation import deprecated
|
||||||
from src.common.permission_utils import (
|
from src.common.permission_utils import (
|
||||||
ensure_directory_permissions,
|
ensure_directory_permissions,
|
||||||
get_plugin_dir_mode
|
get_plugin_dir_mode
|
||||||
@@ -698,6 +699,7 @@ class PluginManager:
|
|||||||
"""
|
"""
|
||||||
return self.plugins.copy()
|
return self.plugins.copy()
|
||||||
|
|
||||||
|
@deprecated("3.7.0", "check each plugin's enabled flag in plugins")
|
||||||
def get_enabled_plugins(self) -> List[str]:
|
def get_enabled_plugins(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Get list of enabled plugin IDs.
|
Get list of enabled plugin IDs.
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""@deprecated: plugin-facing APIs nothing in core, the monorepo or the
|
||||||
|
registry's third-party plugins calls, kept for one release with a warning."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
os.environ.setdefault("EMULATOR", "true")
|
||||||
|
|
||||||
|
from src import deprecation
|
||||||
|
from src.deprecation import deprecated
|
||||||
|
|
||||||
|
#: Everything deprecated for removal in 3.7.0. Removing one of these, or
|
||||||
|
#: deprecating another, should be a deliberate edit here too.
|
||||||
|
DEPRECATED = {
|
||||||
|
"src.cache_manager.CacheManager": [
|
||||||
|
"has_data_changed", "update_cache", "setup_persistent_cache",
|
||||||
|
"get_sport_live_interval", "get_sport_key_from_cache_key",
|
||||||
|
"get_background_cached_data", "is_background_data_available",
|
||||||
|
"record_cache_hit", "record_cache_miss", "record_fetch_time",
|
||||||
|
"get_cache_metrics", "log_cache_metrics", "get_memory_cache_stats",
|
||||||
|
],
|
||||||
|
"src.display_manager.DisplayManager": [
|
||||||
|
"draw_sun", "draw_cloud", "draw_rain", "draw_snow", "draw_weather_icon",
|
||||||
|
"draw_text_with_icons", "get_scrolling_stats",
|
||||||
|
],
|
||||||
|
"src.font_manager.FontManager": [
|
||||||
|
"get_manager_fonts", "get_detected_fonts", "unregister_plugin_fonts",
|
||||||
|
"get_plugin_fonts", "set_override", "remove_override", "get_overrides",
|
||||||
|
"get_available_fonts", "get_size_tokens", "get_performance_stats",
|
||||||
|
"get_font_catalog", "add_font", "remove_font", "validate_font",
|
||||||
|
],
|
||||||
|
"src.plugin_system.plugin_manager.PluginManager": ["get_enabled_plugins"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _cls(path):
|
||||||
|
import importlib
|
||||||
|
module, name = path.rsplit(".", 1)
|
||||||
|
return getattr(importlib.import_module(module), name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("path", sorted(DEPRECATED))
|
||||||
|
def test_exactly_these_methods_are_deprecated(path):
|
||||||
|
cls = _cls(path)
|
||||||
|
marked = sorted(name for name, value in vars(cls).items()
|
||||||
|
if hasattr(value, "__deprecated__"))
|
||||||
|
assert marked == sorted(DEPRECATED[path])
|
||||||
|
for name in marked:
|
||||||
|
assert "3.7.0" in getattr(cls, name).__deprecated__
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fresh(monkeypatch):
|
||||||
|
monkeypatch.setattr(deprecation, "_warned", set())
|
||||||
|
|
||||||
|
|
||||||
|
def test_first_call_warns_and_logs_then_stays_quiet(fresh, caplog):
|
||||||
|
@deprecated("9.9.9", "use other()")
|
||||||
|
def old(x):
|
||||||
|
"""Doc."""
|
||||||
|
return x * 2
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as caught, caplog.at_level(logging.WARNING):
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
assert old(2) == 4
|
||||||
|
assert old(3) == 6
|
||||||
|
|
||||||
|
assert [str(w.message) for w in caught] == [
|
||||||
|
"test_first_call_warns_and_logs_then_stays_quiet.<locals>.old() is deprecated "
|
||||||
|
"and will be removed in LEDMatrix 9.9.9; use other()"]
|
||||||
|
assert caught[0].category is DeprecationWarning
|
||||||
|
assert caught[0].filename == __file__ # points at the caller
|
||||||
|
assert sum("will be removed in LEDMatrix 9.9.9" in r.message for r in caplog.records) == 1
|
||||||
|
assert old.__name__ == "old" and old.__doc__ == "Doc."
|
||||||
|
|
||||||
|
|
||||||
|
def test_decorated_methods_still_work(fresh):
|
||||||
|
from src.font_manager import FontManager
|
||||||
|
fm = FontManager({})
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
assert fm.get_font_catalog() == fm.font_catalog
|
||||||
Reference in New Issue
Block a user