diff --git a/data_safe_haven/context/context_settings.py b/data_safe_haven/context/context_settings.py index fae19ba036..2c1b35a9fd 100644 --- a/data_safe_haven/context/context_settings.py +++ b/data_safe_haven/context/context_settings.py @@ -10,8 +10,7 @@ import yaml from azure.keyvault.keys import KeyVaultKey -from pydantic import BaseModel, Field, ValidationError, model_validator -from yaml import YAMLError +from pydantic import BaseModel, Field, model_validator from data_safe_haven import __version__ from data_safe_haven.exceptions import ( @@ -20,7 +19,7 @@ ) from data_safe_haven.external import AzureApi from data_safe_haven.functions import alphanumeric -from data_safe_haven.utility import LoggingSingleton, config_dir +from data_safe_haven.utility import LoggingSingleton, YAMLSerialisableModel, config_dir from data_safe_haven.utility.annotated_types import ( AzureLocation, AzureLongName, @@ -101,7 +100,7 @@ def to_yaml(self) -> str: return yaml.dump(self.model_dump(), indent=2) -class ContextSettings(BaseModel, validate_assignment=True): +class ContextSettings(YAMLSerialisableModel, validate_assignment=True): """Load global and local settings from dotfiles with structure like the following selected: acme_deployment @@ -220,24 +219,6 @@ def remove(self, key: str) -> None: if key == self.selected: self.selected = None - @classmethod - def from_yaml(cls, settings_yaml: str) -> ContextSettings: - try: - settings_dict = yaml.safe_load(settings_yaml) - except YAMLError as exc: - msg = f"Could not parse context settings as YAML.\n{exc}" - raise DataSafeHavenConfigError(msg) from exc - - if not isinstance(settings_dict, dict): - msg = "Unable to parse context settings as a dict." - raise DataSafeHavenConfigError(msg) - - try: - return ContextSettings.model_validate(settings_dict) - except ValidationError as exc: - msg = f"Could not load context settings.\n{exc}" - raise DataSafeHavenParameterError(msg) from exc - @classmethod def from_file(cls, config_file_path: Path | None = None) -> ContextSettings: if config_file_path is None: @@ -245,24 +226,11 @@ def from_file(cls, config_file_path: Path | None = None) -> ContextSettings: cls.logger.info( f"Reading project settings from '[green]{config_file_path}[/]'." ) - try: - with open(config_file_path, encoding="utf-8") as f_yaml: - settings_yaml = f_yaml.read() - return cls.from_yaml(settings_yaml) - except FileNotFoundError as exc: - msg = f"Could not find file {config_file_path}.\n{exc}" - raise DataSafeHavenConfigError(msg) from exc - - def to_yaml(self) -> str: - return yaml.dump(self.model_dump(by_alias=True), indent=2) + return cls.from_filepath(config_file_path) def write(self, config_file_path: Path | None = None) -> None: """Write settings to YAML file""" if config_file_path is None: config_file_path = self.default_config_file_path() - # Create the parent directory if it does not exist then write YAML - config_file_path.parent.mkdir(parents=True, exist_ok=True) - - with open(config_file_path, "w", encoding="utf-8") as f_yaml: - f_yaml.write(self.to_yaml()) + self.to_filepath(config_file_path) self.logger.info(f"Saved context settings to '[green]{config_file_path}[/]'.") diff --git a/data_safe_haven/utility/yaml_serialisable_model.py b/data_safe_haven/utility/yaml_serialisable_model.py index 5f6008a371..c6436f3d33 100644 --- a/data_safe_haven/utility/yaml_serialisable_model.py +++ b/data_safe_haven/utility/yaml_serialisable_model.py @@ -22,7 +22,7 @@ class YAMLSerialisableModel(BaseModel, validate_assignment=True): """ @classmethod - def from_file(cls: type[T], config_file_path: PathType) -> T: + def from_filepath(cls: type[T], config_file_path: PathType) -> T: """Construct a YAMLSerialisableModel from a YAML file""" try: with open(Path(config_file_path), encoding="utf-8") as f_yaml: @@ -51,7 +51,7 @@ def from_yaml(cls: type[T], settings_yaml: str) -> T: msg = f"Could not load context settings.\n{exc}" raise DataSafeHavenParameterError(msg) from exc - def to_file(self, config_file_path: PathType) -> None: + def to_filepath(self, config_file_path: PathType) -> None: """Serialise a YAMLSerialisableModel to a YAML file""" # Create the parent directory if it does not exist then write YAML _config_file_path = Path(config_file_path)