|
6 | 6 | """ |
7 | 7 |
|
8 | 8 | from __future__ import annotations |
| 9 | +import json |
9 | 10 | from typing import TYPE_CHECKING, Any, Optional |
10 | 11 | from pydantic import ValidationError |
| 12 | +from pydantic_core import InitErrorDetails |
11 | 13 | from typing_extensions import Self |
12 | 14 | from collections.abc import Mapping |
13 | 15 | import logging |
@@ -212,14 +214,48 @@ def load_settings(self) -> None: |
212 | 214 |
|
213 | 215 | try: |
214 | 216 | with open(setting_storage_path, "r", encoding="utf-8") as file_obj: |
215 | | - settings_model = self.settings.model.model_validate_json( |
216 | | - file_obj.read() |
| 217 | + settings = json.load(file_obj) |
| 218 | + if not isinstance(settings, Mapping): |
| 219 | + raise TypeError("The settings file must be a JSON object.") |
| 220 | + validation_errors: dict[str, ValidationError] = {} |
| 221 | + for name, value in settings: |
| 222 | + try: |
| 223 | + setting = self.settings[name] |
| 224 | + # Load the key from the JSON file using the setting's model |
| 225 | + model = setting.model.model_validate(value) |
| 226 | + setting.set_without_emit_from_model(model) |
| 227 | + except ValidationError as e: |
| 228 | + validation_errors[name] = e |
| 229 | + except KeyError: |
| 230 | + self.logger.warning( |
| 231 | + f"An extra key {name} was found in the settings file. " |
| 232 | + "It will be deleted the next time settings are saved." |
| 233 | + ) |
| 234 | + if validation_errors: |
| 235 | + # If one or more settings didn't validate, combine them into a single |
| 236 | + # Pydantic validation error. |
| 237 | + error_details: list[InitErrorDetails] = [] |
| 238 | + for name, exc in validation_errors.items(): |
| 239 | + for err in exc.errors(): |
| 240 | + error_details.append( |
| 241 | + { |
| 242 | + "type": err["type"], |
| 243 | + "loc": (name,) + err["loc"], |
| 244 | + "input": err["input"], |
| 245 | + "ctx": err["ctx"] if "ctx" in err else {}, |
| 246 | + } |
| 247 | + ) |
| 248 | + raise ValidationError.from_exception_data( |
| 249 | + "Some settings were not valid.", |
| 250 | + error_details, |
217 | 251 | ) |
218 | | - for key, value in settings_model: |
219 | | - if value is None: |
220 | | - continue # `None` means the key was missing |
221 | | - self.settings[key].set_without_emit_from_model(value) |
222 | | - except (FileNotFoundError, JSONDecodeError, PermissionError, ValidationError): |
| 252 | + except ( |
| 253 | + FileNotFoundError, |
| 254 | + JSONDecodeError, |
| 255 | + PermissionError, |
| 256 | + ValidationError, |
| 257 | + TypeError, |
| 258 | + ): |
223 | 259 | # Note that if the settings file is missing, we should already have returned |
224 | 260 | # before attempting to load settings. |
225 | 261 | self.logger.warning( |
|
0 commit comments