From b53155d7a96bfa66c5e45d46b5d578584b33f1d1 Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Mon, 16 Feb 2026 16:54:55 +0000 Subject: [PATCH 1/2] Move config loading from CLI into ConfigLoader --- src/blueapi/cli/cli.py | 15 ++++++--------- src/blueapi/config.py | 16 +++++++++++----- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/blueapi/cli/cli.py b/src/blueapi/cli/cli.py index 9be9549c8..4e44935a1 100644 --- a/src/blueapi/cli/cli.py +++ b/src/blueapi/cli/cli.py @@ -83,26 +83,23 @@ def is_str_dict(val: Any) -> TypeGuard[TaskParameters]: "-c", "--config", type=Path, help="Path to configuration YAML file", multiple=True ) @click.pass_context -def main(ctx: click.Context, config: Path | None | tuple[Path, ...]) -> None: +def main(ctx: click.Context, config: tuple[Path, ...]) -> None: # if no command is supplied, run with the options passed # Set umask to DLS standard os.umask(stat.S_IWOTH) config_loader = ConfigLoader(ApplicationConfig) - if config is not None: - configs = (config,) if isinstance(config, Path) else config - for path in configs: - if path.exists(): - config_loader.use_values_from_yaml(path) - else: - raise FileNotFoundError(f"Cannot find file: {path}") + try: + config_loader.use_values_from_yaml(*config) + except FileNotFoundError as fnfe: + raise ClickException(f"Config file not found: {fnfe.filename}") from fnfe - ctx.ensure_object(dict) loaded_config: ApplicationConfig = config_loader.load() set_up_logging(loaded_config.logging) + ctx.ensure_object(dict) ctx.obj["config"] = loaded_config if ctx.invoked_subcommand is None: diff --git a/src/blueapi/config.py b/src/blueapi/config.py index 214016860..3e06021ae 100644 --- a/src/blueapi/config.py +++ b/src/blueapi/config.py @@ -358,9 +358,9 @@ def recursively_update_map(old: dict[str, Any], new: Mapping[str, Any]) -> None: recursively_update_map(self._values, values) - def use_values_from_yaml(self, path: Path) -> None: + def use_values_from_yaml(self, *paths: Path) -> None: """ - Use all values provided in a YAML/JSON file in the + Use all values provided in a YAML/JSON files in the config, override any defaults and values set by previous calls into this class. @@ -368,9 +368,15 @@ def use_values_from_yaml(self, path: Path) -> None: path (Path): Path to YAML/JSON file """ - with path.open("r") as stream: - values = yaml.load(stream, yaml.Loader) - self.use_values(values) + # Split reading and loading so that a missing file does not leave + # config in partially loaded state + configs = [] + for path in paths: + with path.open("r") as stream: + configs.append(yaml.load(stream, yaml.Loader)) + + for values in configs: + self.use_values(values) def load(self) -> C: """ From 136df8d9672344a223da270100eb762b339bfba2 Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Thu, 19 Feb 2026 16:37:47 +0000 Subject: [PATCH 2/2] Simplify multi-file config loading --- src/blueapi/config.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/blueapi/config.py b/src/blueapi/config.py index 3e06021ae..6f0ca4ae7 100644 --- a/src/blueapi/config.py +++ b/src/blueapi/config.py @@ -368,15 +368,9 @@ def use_values_from_yaml(self, *paths: Path) -> None: path (Path): Path to YAML/JSON file """ - # Split reading and loading so that a missing file does not leave - # config in partially loaded state - configs = [] for path in paths: with path.open("r") as stream: - configs.append(yaml.load(stream, yaml.Loader)) - - for values in configs: - self.use_values(values) + self.use_values(yaml.load(stream, yaml.Loader)) def load(self) -> C: """