Skip to content

Commit 5f8b28e

Browse files
committed
Load partially valid settings files.
This will load all the keys that are valid and log errors for the rest.
1 parent 0e08ee4 commit 5f8b28e

1 file changed

Lines changed: 43 additions & 7 deletions

File tree

src/labthings_fastapi/thing.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
"""
77

88
from __future__ import annotations
9+
import json
910
from typing import TYPE_CHECKING, Any, Optional
1011
from pydantic import ValidationError
12+
from pydantic_core import InitErrorDetails
1113
from typing_extensions import Self
1214
from collections.abc import Mapping
1315
import logging
@@ -212,14 +214,48 @@ def load_settings(self) -> None:
212214

213215
try:
214216
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,
217251
)
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+
):
223259
# Note that if the settings file is missing, we should already have returned
224260
# before attempting to load settings.
225261
self.logger.warning(

0 commit comments

Comments
 (0)