mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
src/common/__init__.py and src/plugin_system/__init__.py resolve their re-exports lazily (PEP 562 __getattr__, __all__ and __dir__ unchanged, TYPE_CHECKING imports for mypy), and sync_manager imports numpy only where send_frame uses it. The web process no longer loads numpy, freetype helpers and PluginManager just to import path_safety, store_manager or schema_manager (~67 MB to ~54 MB RSS on a Pi 4). from src.common import X and from src.plugin_system import X keep working, including submodule imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
"""
|
|
Common utilities and helpers for LEDMatrix.
|
|
|
|
This package provides reusable functionality for plugins and core modules:
|
|
- API helpers
|
|
- Logo helpers
|
|
- Text/scroll helpers
|
|
- Adaptive layout and image helpers
|
|
|
|
The names below are imported on first use (PEP 562), not when the package is
|
|
imported. ``from src.common import ScrollHelper`` and
|
|
``src.common.ScrollHelper`` work as before and return the same objects, but
|
|
``import src.common`` -- or importing any submodule, such as
|
|
``src.common.path_safety`` -- no longer loads numpy, requests and freetype
|
|
along with every helper. The web interface imports src.common only for a few
|
|
small modules and never needs those.
|
|
"""
|
|
|
|
import importlib
|
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|
|
|
if TYPE_CHECKING:
|
|
# What mypy and editors see: the real names and their types.
|
|
from src.common.api_helper import APIHelper
|
|
from src.common.scroll_helper import ScrollHelper
|
|
from src.common import scroll_config
|
|
from src.common.scroll_config import (
|
|
ScrollSettings,
|
|
configure as configure_scroll,
|
|
resolve as resolve_scroll_settings,
|
|
refresh_hz_from_config,
|
|
)
|
|
from src.common.logo_helper import LogoHelper
|
|
from src.common.text_helper import TextHelper
|
|
|
|
# Adaptive layout & images (canonical homes: src.adaptive_layout /
|
|
# src.adaptive_images — re-exported here so plugin authors find them in the
|
|
# blessed-helpers package). See docs/ADAPTIVE_LAYOUT.md.
|
|
from src.adaptive_layout import (
|
|
Region,
|
|
LayoutContext,
|
|
FontStep,
|
|
FontLadder,
|
|
LADDER_GRID,
|
|
LADDER_ARCADE,
|
|
FitResult,
|
|
draw_fitted_text,
|
|
ScoreboardRegions,
|
|
scoreboard_regions,
|
|
MediaRow,
|
|
media_row,
|
|
)
|
|
from src.adaptive_images import (
|
|
ImageFitResult,
|
|
fit_image,
|
|
draw_fitted_image,
|
|
RESAMPLE_LANCZOS,
|
|
RESAMPLE_NEAREST,
|
|
)
|
|
|
|
#: Exported name -> (module it lives in, attribute name there). An attribute
|
|
#: of None means the name is the module itself. Keep in step with the
|
|
#: TYPE_CHECKING imports above and with __all__.
|
|
_LAZY: Dict[str, Tuple[str, Optional[str]]] = {
|
|
'APIHelper': ('src.common.api_helper', 'APIHelper'),
|
|
'ScrollHelper': ('src.common.scroll_helper', 'ScrollHelper'),
|
|
'scroll_config': ('src.common.scroll_config', None),
|
|
'ScrollSettings': ('src.common.scroll_config', 'ScrollSettings'),
|
|
'configure_scroll': ('src.common.scroll_config', 'configure'),
|
|
'resolve_scroll_settings': ('src.common.scroll_config', 'resolve'),
|
|
'refresh_hz_from_config': ('src.common.scroll_config', 'refresh_hz_from_config'),
|
|
'LogoHelper': ('src.common.logo_helper', 'LogoHelper'),
|
|
'TextHelper': ('src.common.text_helper', 'TextHelper'),
|
|
# adaptive layout & images
|
|
'Region': ('src.adaptive_layout', 'Region'),
|
|
'LayoutContext': ('src.adaptive_layout', 'LayoutContext'),
|
|
'FontStep': ('src.adaptive_layout', 'FontStep'),
|
|
'FontLadder': ('src.adaptive_layout', 'FontLadder'),
|
|
'LADDER_GRID': ('src.adaptive_layout', 'LADDER_GRID'),
|
|
'LADDER_ARCADE': ('src.adaptive_layout', 'LADDER_ARCADE'),
|
|
'FitResult': ('src.adaptive_layout', 'FitResult'),
|
|
'draw_fitted_text': ('src.adaptive_layout', 'draw_fitted_text'),
|
|
'ScoreboardRegions': ('src.adaptive_layout', 'ScoreboardRegions'),
|
|
'scoreboard_regions': ('src.adaptive_layout', 'scoreboard_regions'),
|
|
'MediaRow': ('src.adaptive_layout', 'MediaRow'),
|
|
'media_row': ('src.adaptive_layout', 'media_row'),
|
|
'ImageFitResult': ('src.adaptive_images', 'ImageFitResult'),
|
|
'fit_image': ('src.adaptive_images', 'fit_image'),
|
|
'draw_fitted_image': ('src.adaptive_images', 'draw_fitted_image'),
|
|
'RESAMPLE_LANCZOS': ('src.adaptive_images', 'RESAMPLE_LANCZOS'),
|
|
'RESAMPLE_NEAREST': ('src.adaptive_images', 'RESAMPLE_NEAREST'),
|
|
}
|
|
|
|
__all__ = [
|
|
'APIHelper',
|
|
'ScrollHelper',
|
|
'scroll_config',
|
|
'ScrollSettings',
|
|
'configure_scroll',
|
|
'resolve_scroll_settings',
|
|
'refresh_hz_from_config',
|
|
'LogoHelper',
|
|
'TextHelper',
|
|
# adaptive layout & images
|
|
'Region',
|
|
'LayoutContext',
|
|
'FontStep',
|
|
'FontLadder',
|
|
'LADDER_GRID',
|
|
'LADDER_ARCADE',
|
|
'FitResult',
|
|
'draw_fitted_text',
|
|
'ScoreboardRegions',
|
|
'scoreboard_regions',
|
|
'MediaRow',
|
|
'media_row',
|
|
'ImageFitResult',
|
|
'fit_image',
|
|
'draw_fitted_image',
|
|
'RESAMPLE_LANCZOS',
|
|
'RESAMPLE_NEAREST',
|
|
]
|
|
|
|
|
|
def __getattr__(name: str) -> Any:
|
|
"""Import an exported name on first access (PEP 562).
|
|
|
|
Only called for names not already in the module namespace, so after the
|
|
first access the cached value below is returned directly. Unknown names
|
|
raise AttributeError, which ``from src.common import <submodule>`` relies
|
|
on to fall through to importing the submodule.
|
|
"""
|
|
try:
|
|
module_name, attr = _LAZY[name]
|
|
except KeyError:
|
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
|
|
module = importlib.import_module(module_name) # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import -- module_name comes from the fixed _LAZY table
|
|
value = module if attr is None else getattr(module, attr)
|
|
globals()[name] = value
|
|
return value
|
|
|
|
|
|
def __dir__() -> List[str]:
|
|
return sorted(set(globals()) | set(__all__))
|