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

1# /////////////////////////////////////////////////////////////// 

2# SERVICES.APPLICATION.SETTINGS_LOADER - Settings loader service 

3# Project: ezqt_app 

4# /////////////////////////////////////////////////////////////// 

5 

6"""Loads application settings from YAML and applies them to SettingsService.""" 

7 

8from __future__ import annotations 

9 

10# /////////////////////////////////////////////////////////////// 

11# IMPORTS 

12# /////////////////////////////////////////////////////////////// 

13# Standard library imports 

14from pathlib import Path 

15from typing import Any 

16 

17# Third-party imports 

18import yaml 

19from pydantic import BaseModel, ConfigDict, ValidationError 

20 

21from ...utils.printer import get_printer, set_global_debug 

22 

23# Local imports 

24from ..config.config_service import get_config_service 

25from ..settings import get_settings_service 

26 

27 

28# /////////////////////////////////////////////////////////////// 

29# PYDANTIC RUNTIME SCHEMAS 

30# /////////////////////////////////////////////////////////////// 

31class _RuntimeAppSectionSchema(BaseModel): 

32 """Strict runtime schema for required app settings.""" 

33 

34 model_config = ConfigDict(extra="forbid") 

35 

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 

49 

50 

51class _RuntimeSettingsPanelOptionSchema(BaseModel): 

52 """Schema for one settings panel option entry.""" 

53 

54 model_config = ConfigDict(extra="forbid") 

55 

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 

65 

66 

67class _RuntimeSettingsPanelSchema(BaseModel): 

68 """Schema for settings panel section used at startup.""" 

69 

70 model_config = ConfigDict(extra="forbid") 

71 

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 

77 

78 

79class _RuntimeAppConfigSchema(BaseModel): 

80 """Top-level runtime schema for app configuration payload.""" 

81 

82 model_config = ConfigDict(extra="forbid") 

83 

84 app: _RuntimeAppSectionSchema 

85 settings_panel: _RuntimeSettingsPanelSchema | None = None 

86 

87 

88# /////////////////////////////////////////////////////////////// 

89# CLASSES 

90# /////////////////////////////////////////////////////////////// 

91class SettingsLoader: 

92 """Loads ``app.config.yaml`` and populates the settings service. 

93 

94 Reads the YAML configuration file, extracts application and GUI 

95 parameters, and injects them into the singleton ``SettingsService``. 

96 """ 

97 

98 # ----------------------------------------------------------- 

99 # Static API 

100 # ----------------------------------------------------------- 

101 

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. 

107 

108 Parameters 

109 ---------- 

110 yaml_file: 

111 Path to the YAML file. Defaults to the package ``app.config.yaml``. 

112 

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) 

123 

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 = {} 

126 

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 

132 

133 app_data = config.app.model_dump(mode="python") 

134 

135 settings_service = get_settings_service() 

136 debug_printer_enabled = bool(config.app.debug_printer) 

137 

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) 

144 

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 ) 

154 

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) 

164 

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"]) 

171 

172 # Display summary through the globally configured printer instance. 

173 get_printer().config_display(app_data) 

174 

175 return app_data