Coverage for src / ezqt_app / services / application / settings_loader.py: 97.62%
78 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# SERVICES.APPLICATION.SETTINGS_LOADER - Settings loader service
3# Project: ezqt_app
4# ///////////////////////////////////////////////////////////////
6"""Loads application settings from YAML and applies them to SettingsService."""
8from __future__ import annotations
10# ///////////////////////////////////////////////////////////////
11# IMPORTS
12# ///////////////////////////////////////////////////////////////
13# Standard library imports
14from pathlib import Path
15from typing import Any
17# Third-party imports
18import yaml
19from pydantic import BaseModel, ConfigDict, ValidationError
21from ...utils.printer import get_printer, set_global_debug
23# Local imports
24from ..config.config_service import get_config_service
25from ..settings import get_settings_service
28# ///////////////////////////////////////////////////////////////
29# PYDANTIC RUNTIME SCHEMAS
30# ///////////////////////////////////////////////////////////////
31class _RuntimeAppSectionSchema(BaseModel):
32 """Strict runtime schema for required app settings."""
34 model_config = ConfigDict(extra="forbid")
36 name: str
37 description: str
38 app_width: int
39 app_min_width: int
40 app_height: int
41 app_min_height: int
42 menu_panel_shrinked_width: int
43 menu_panel_extended_width: int
44 settings_panel_width: int
45 time_animation: int
46 debug_printer: bool = False
47 settings_storage_root: str | None = None
48 config_version: int | None = None
51class _RuntimeSettingsPanelOptionSchema(BaseModel):
52 """Schema for one settings panel option entry."""
54 model_config = ConfigDict(extra="forbid")
56 type: str | None = None
57 label: str | None = None
58 default: Any = None
59 description: str | None = None
60 enabled: bool | None = None
61 options: list[str] | None = None
62 min: int | None = None
63 max: int | None = None
64 unit: str | None = None
67class _RuntimeSettingsPanelSchema(BaseModel):
68 """Schema for settings panel section used at startup."""
70 model_config = ConfigDict(extra="forbid")
72 theme: _RuntimeSettingsPanelOptionSchema | None = None
73 language: _RuntimeSettingsPanelOptionSchema | None = None
74 notifications: _RuntimeSettingsPanelOptionSchema | None = None
75 auto_save: _RuntimeSettingsPanelOptionSchema | None = None
76 save_interval: _RuntimeSettingsPanelOptionSchema | None = None
79class _RuntimeAppConfigSchema(BaseModel):
80 """Top-level runtime schema for app configuration payload."""
82 model_config = ConfigDict(extra="forbid")
84 app: _RuntimeAppSectionSchema
85 settings_panel: _RuntimeSettingsPanelSchema | None = None
88# ///////////////////////////////////////////////////////////////
89# CLASSES
90# ///////////////////////////////////////////////////////////////
91class SettingsLoader:
92 """Loads ``app.config.yaml`` and populates the settings service.
94 Reads the YAML configuration file, extracts application and GUI
95 parameters, and injects them into the singleton ``SettingsService``.
96 """
98 # -----------------------------------------------------------
99 # Static API
100 # -----------------------------------------------------------
102 @staticmethod
103 def load_app_settings(
104 yaml_file: Path | None = None,
105 ) -> dict:
106 """Load application settings from YAML and apply to SettingsService.
108 Parameters
109 ----------
110 yaml_file:
111 Path to the YAML file. Defaults to the package ``app.config.yaml``.
113 Returns
114 -------
115 dict
116 Raw ``app`` section from the YAML file.
117 """
118 if yaml_file is None:
119 raw_data = get_config_service().load_config("app", force_reload=True)
120 else:
121 with open(yaml_file, encoding="utf-8") as file:
122 raw_data = yaml.safe_load(file)
124 if not isinstance(raw_data, dict): 124 ↛ 125line 124 didn't jump to line 125 because the condition on line 124 was never true
125 raw_data = {}
127 try:
128 config = _RuntimeAppConfigSchema.model_validate(raw_data)
129 except ValidationError as e:
130 get_printer().error(f"Invalid app runtime configuration: {e}")
131 raise
133 app_data = config.app.model_dump(mode="python")
135 settings_service = get_settings_service()
136 debug_printer_enabled = bool(config.app.debug_printer)
138 # App identity
139 settings_service.set_app_name(app_data["name"])
140 settings_service.set_app_description(app_data["description"])
141 settings_service.set_custom_title_bar_enabled(True)
142 settings_service.set_debug_enabled(debug_printer_enabled)
143 set_global_debug(debug_printer_enabled)
145 # Window dimensions
146 settings_service.set_app_min_size(
147 width=app_data["app_min_width"],
148 height=app_data["app_min_height"],
149 )
150 settings_service.set_app_dimensions(
151 width=app_data["app_width"],
152 height=app_data["app_height"],
153 )
155 # GUI settings
156 theme_default = "dark"
157 if (
158 config.settings_panel is not None
159 and config.settings_panel.theme is not None
160 and isinstance(config.settings_panel.theme.default, str)
161 ):
162 theme_default = config.settings_panel.theme.default
163 settings_service.set_theme(theme_default)
165 settings_service.set_menu_widths(
166 shrinked=app_data["menu_panel_shrinked_width"],
167 extended=app_data["menu_panel_extended_width"],
168 )
169 settings_service.set_settings_panel_width(app_data["settings_panel_width"])
170 settings_service.set_time_animation(app_data["time_animation"])
172 # Display summary through the globally configured printer instance.
173 get_printer().config_display(app_data)
175 return app_data