From e562a857a8d35b0ae50bfd0b4ce23ee9c6f734f3 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 10:19:00 +0200 Subject: [PATCH 1/2] feat(framework) Allow multiple separated run-config arguments --- src/py/flwr/cli/run/run.py | 8 ++++---- src/py/flwr/common/config.py | 27 ++++++++++++++------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 5db575abcb8..f627a917eb5 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -18,7 +18,7 @@ import sys from logging import DEBUG from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional import typer from typing_extensions import Annotated @@ -43,7 +43,7 @@ def run( typer.Argument(help="Name of the federation to run the app on"), ] = None, config_overrides: Annotated[ - Optional[str], + Optional[List[str]], typer.Option( "--run-config", "-c", @@ -113,7 +113,7 @@ def run( def _run_with_superexec( federation: Dict[str, str], directory: Optional[Path], - config_overrides: Optional[str], + config_overrides: Optional[List[str]], ) -> None: def on_channel_state_change(channel_connectivity: str) -> None: @@ -172,7 +172,7 @@ def _run_without_superexec( app_path: Optional[Path], federation: Dict[str, Any], federation_name: str, - config_overrides: Optional[str], + config_overrides: Optional[List[str]], ) -> None: try: num_supernodes = federation["options"]["num-supernodes"] diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 6049fcbccee..3bd7a103f4c 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -130,7 +130,7 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st def parse_config_args( - config: Optional[str], + config: Optional[List[str]], separator: str = ",", ) -> Dict[str, str]: """Parse separator separated list of key-value pairs separated by '='.""" @@ -139,17 +139,18 @@ def parse_config_args( if config is None: return overrides - overrides_list = config.split(separator) - if ( - len(overrides_list) == 1 - and "=" not in overrides_list - and overrides_list[0].endswith(".toml") - ): - with Path(overrides_list[0]).open("rb") as config_file: - overrides = flatten_dict(tomli.load(config_file)) - else: - for kv_pair in overrides_list: - key, value = kv_pair.split("=") - overrides[key] = value + for config_line in config: + overrides_list = config_line.split(separator) + if ( + len(overrides_list) == 1 + and "=" not in overrides_list + and overrides_list[0].endswith(".toml") + ): + with Path(overrides_list[0]).open("rb") as config_file: + overrides = flatten_dict(tomli.load(config_file)) + else: + for kv_pair in overrides_list: + key, value = kv_pair.split("=") + overrides[key] = value return overrides From da2ead66129600184fd9f2511c551b0a6b2b2d2d Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 10:44:43 +0200 Subject: [PATCH 2/2] Fix test --- src/py/flwr/common/config_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index feef89e7d5c..e1597aa5a2e 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -230,7 +230,12 @@ def test_parse_config_args_none() -> None: def test_parse_config_args_overrides() -> None: """Test parse_config_args with key-value pairs.""" - assert parse_config_args("key1=value1,key2=value2") == { + assert parse_config_args( + ["key1=value1,key2=value2", "key3=value3", "key4=value4,key5=value5"] + ) == { "key1": "value1", "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5", }