Coverage for src / ezqt_app / shared / resources / __init__.py: 45.90%
49 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 07:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-26 07:07 +0000
1# ///////////////////////////////////////////////////////////////
2# SHARED.RESOURCES - Shared resource definitions
3# Project: ezqt_app
4# ///////////////////////////////////////////////////////////////
6"""Shared static resource path definitions."""
8from __future__ import annotations
10# ///////////////////////////////////////////////////////////////
11# IMPORTS
12# ///////////////////////////////////////////////////////////////
13import importlib.util
14import logging
15import sys
16from pathlib import Path
17from types import ModuleType
18from typing import Any # noqa: F401 # pyright: ignore[reportUnusedImport]
20# Local imports
21from ...resources import base_resources_rc # type: ignore # noqa: F401
22from ...utils.runtime_paths import get_bin_path
23from .icons import Icons as _PackageIcons
24from .images import Images as _PackageImages
26Icons = _PackageIcons
27Images = _PackageImages
29# Typed accessors for user-compiled resources — populated by load_runtime_rc().
30# Typed as Any: attributes are generated dynamically from bin/app_icons.py.
31#
32# WARNING — import pattern:
33# Always access via the module, not via a direct import, to ensure the
34# reference stays live after load_runtime_rc() reassigns the variable:
35#
36# ✅ from ezqt_app.shared import resources; resources.AppIcons.my_icon
37# ✅ from ezqt_app.shared.resources import AppIcons (only after init())
38# ❌ from ezqt_app.shared.resources import AppIcons (at module top-level,
39# before init() — captures None and never updates)
40AppIcons: Any = None
41AppImages: Any = None
44def _load_module_from_file(module_name: str, file_path: Path) -> ModuleType | None:
45 """Import a Python module directly from file when it exists."""
46 if not file_path.exists() or not file_path.is_file(): 46 ↛ 49line 46 didn't jump to line 49 because the condition on line 46 was always true
47 return None
49 spec = importlib.util.spec_from_file_location(module_name, file_path)
50 if spec is None or spec.loader is None:
51 return None
53 module = importlib.util.module_from_spec(spec)
54 try:
55 spec.loader.exec_module(module)
56 except Exception as e:
57 logging.getLogger(__name__).warning(
58 "Failed to load runtime module %s from %s: %s", module_name, file_path, e
59 )
60 return None
62 sys.modules[module_name] = module
63 return module
66# Best-effort loads at import time: succeed when the files already exist from
67# a previous init() run, silently skipped otherwise.
68_load_module_from_file(
69 "ezqt_app._runtime_resources_rc", get_bin_path() / "resources_rc.py"
70)
71_icons_mod = _load_module_from_file(
72 "ezqt_app._runtime_app_icons", get_bin_path() / "app_icons.py"
73)
74if _icons_mod is not None: 74 ↛ 75line 74 didn't jump to line 75 because the condition on line 74 was never true
75 AppIcons = getattr(_icons_mod, "AppIcons", None)
77_images_mod = _load_module_from_file(
78 "ezqt_app._runtime_app_images", get_bin_path() / "app_images.py"
79)
80if _images_mod is not None: 80 ↛ 81line 80 didn't jump to line 81 because the condition on line 80 was never true
81 AppImages = getattr(_images_mod, "AppImages", None)
84def load_runtime_rc() -> None:
85 """Load (or reload) the project ``resources_rc.py`` generated by init().
87 Also loads ``bin/app_icons.py`` and ``bin/app_images.py`` into
88 :data:`AppIcons` and :data:`AppImages` when present.
90 Called by the bootstrap sequence after QRC compilation so that user assets
91 are registered even when init() runs for the first time (i.e. when the
92 files did not exist at import time).
94 Note:
95 This function reassigns the module-level :data:`AppIcons` and
96 :data:`AppImages` variables. Code that imported these symbols
97 *before* ``init()`` was called holds a stale ``None`` reference.
98 Access them via the module after ``init()`` to get the live value::
100 # Correct — resolved at access time
101 from ezqt_app.shared import resources
102 icon_path = resources.AppIcons.my_icon
104 # Also correct when called after init()
105 from ezqt_app.shared.resources import AppIcons
106 icon_path = AppIcons.my_icon
107 """
108 global AppIcons, AppImages # noqa: PLW0603
110 module_name = "ezqt_app._runtime_resources_rc"
111 sys.modules.pop(module_name, None)
112 _load_module_from_file(module_name, get_bin_path() / "resources_rc.py")
114 sys.modules.pop("ezqt_app._runtime_app_icons", None)
115 icons_mod = _load_module_from_file(
116 "ezqt_app._runtime_app_icons", get_bin_path() / "app_icons.py"
117 )
118 if icons_mod is not None:
119 AppIcons = getattr(icons_mod, "AppIcons", None)
121 sys.modules.pop("ezqt_app._runtime_app_images", None)
122 images_mod = _load_module_from_file(
123 "ezqt_app._runtime_app_images", get_bin_path() / "app_images.py"
124 )
125 if images_mod is not None:
126 AppImages = getattr(images_mod, "AppImages", None)
129# ///////////////////////////////////////////////////////////////
130# PUBLIC API
131# ///////////////////////////////////////////////////////////////
132__all__ = ["Icons", "Images", "AppIcons", "AppImages", "load_runtime_rc"]