Coverage for src / ezqt_app / shared / resources / __init__.py: 87.10%
50 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-06 13:12 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-06 13:12 +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 sys
15from pathlib import Path
16from types import ModuleType
17from typing import Any # noqa: F401 # pyright: ignore[reportUnusedImport]
19# Third-party imports
20from ezplog.lib_mode import get_logger
22# Local imports
23from ...resources import (
24 base_resources_rc, # noqa: F401 # pyright: ignore[reportUnusedImport]
25)
26from ...utils.runtime_paths import get_bin_path
27from .icons import Icons as _PackageIcons
28from .images import Images as _PackageImages
30Icons = _PackageIcons
31Images = _PackageImages
33# Typed accessors for user-compiled resources — populated by load_runtime_rc().
34# Typed as Any: attributes are generated dynamically from bin/app_icons.py.
35#
36# WARNING — import pattern:
37# Always access via the module, not via a direct import, to ensure the
38# reference stays live after load_runtime_rc() reassigns the variable:
39#
40# ✅ from ezqt_app.shared import resources; resources.AppIcons.my_icon
41# ✅ from ezqt_app.shared.resources import AppIcons (only after init())
42# ❌ from ezqt_app.shared.resources import AppIcons (at module top-level,
43# before init() — captures None and never updates)
44AppIcons: Any = None
45AppImages: Any = None
47_LOGGER = get_logger(__name__)
50def _load_module_from_file(module_name: str, file_path: Path) -> ModuleType | None:
51 """Import a Python module directly from file when it exists."""
52 if not file_path.exists() or not file_path.is_file():
53 return None
55 spec = importlib.util.spec_from_file_location(module_name, file_path)
56 if spec is None or spec.loader is None: 56 ↛ 57line 56 didn't jump to line 57 because the condition on line 56 was never true
57 return None
59 module = importlib.util.module_from_spec(spec)
60 try:
61 spec.loader.exec_module(module)
62 except Exception as e:
63 _LOGGER.warning(
64 "Failed to load runtime module %s from %s: %s", module_name, file_path, e
65 )
66 return None
68 sys.modules[module_name] = module
69 return module
72# Best-effort loads at import time: succeed when the files already exist from
73# a previous init() run, silently skipped otherwise.
74_load_module_from_file(
75 "ezqt_app._runtime_resources_rc", get_bin_path() / "resources_rc.py"
76)
77_icons_mod = _load_module_from_file(
78 "ezqt_app._runtime_app_icons", get_bin_path() / "app_icons.py"
79)
80if _icons_mod is not None: 80 ↛ 81line 80 didn't jump to line 81 because the condition on line 80 was never true
81 AppIcons = getattr(_icons_mod, "AppIcons", None)
83_images_mod = _load_module_from_file(
84 "ezqt_app._runtime_app_images", get_bin_path() / "app_images.py"
85)
86if _images_mod is not None: 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 AppImages = getattr(_images_mod, "AppImages", None)
90def load_runtime_rc() -> None:
91 """Load (or reload) the project ``resources_rc.py`` generated by init().
93 Also loads ``bin/app_icons.py`` and ``bin/app_images.py`` into
94 :data:`AppIcons` and :data:`AppImages` when present.
96 Called by the bootstrap sequence after QRC compilation so that user assets
97 are registered even when init() runs for the first time (i.e. when the
98 files did not exist at import time).
100 Note:
101 This function reassigns the module-level :data:`AppIcons` and
102 :data:`AppImages` variables. Code that imported these symbols
103 *before* ``init()`` was called holds a stale ``None`` reference.
104 Access them via the module after ``init()`` to get the live value::
106 # Correct — resolved at access time
107 from ezqt_app.shared import resources
108 icon_path = resources.AppIcons.my_icon
110 # Also correct when called after init()
111 from ezqt_app.shared.resources import AppIcons
112 icon_path = AppIcons.my_icon
113 """
114 global AppIcons, AppImages # noqa: PLW0603
116 module_name = "ezqt_app._runtime_resources_rc"
117 sys.modules.pop(module_name, None)
118 _load_module_from_file(module_name, get_bin_path() / "resources_rc.py")
120 sys.modules.pop("ezqt_app._runtime_app_icons", None)
121 icons_mod = _load_module_from_file(
122 "ezqt_app._runtime_app_icons", get_bin_path() / "app_icons.py"
123 )
124 if icons_mod is not None: 124 ↛ 127line 124 didn't jump to line 127 because the condition on line 124 was always true
125 AppIcons = getattr(icons_mod, "AppIcons", None)
127 sys.modules.pop("ezqt_app._runtime_app_images", None)
128 images_mod = _load_module_from_file(
129 "ezqt_app._runtime_app_images", get_bin_path() / "app_images.py"
130 )
131 if images_mod is not None: 131 ↛ exitline 131 didn't return from function 'load_runtime_rc' because the condition on line 131 was always true
132 AppImages = getattr(images_mod, "AppImages", None)
135# ///////////////////////////////////////////////////////////////
136# PUBLIC API
137# ///////////////////////////////////////////////////////////////
138__all__ = ["Icons", "Images", "AppIcons", "AppImages", "load_runtime_rc"]