diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index f63d0acd5d73..599ce613698c 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -85,7 +85,7 @@ def build( # Set the name of the zip file fab_filename = ( - f"{conf['flower']['publisher']}" + f"{conf['tool']['flwr']['publisher']}" f".{directory.name}" f".{conf['project']['version'].replace('.', '-')}.fab" ) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index 33bf12e34b04..9147ebba4995 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -60,7 +60,7 @@ def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]: return ( conf["project"]["version"], - f"{conf['flower']['publisher']}/{conf['project']['name']}", + f"{conf['tool']['flwr']['publisher']}/{conf['project']['name']}", ) @@ -136,20 +136,20 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]] if "authors" not in config["project"]: warnings.append('Recommended property "authors" missing in [project]') - if "flower" not in config: - errors.append("Missing [flower] section") + if "tool" not in config or "flwr" not in config["tool"]: + errors.append("Missing [tool.flwr] section") else: - if "publisher" not in config["flower"]: - errors.append('Property "publisher" missing in [flower]') - if "config" in config["flower"]: - _validate_run_config(config["flower"]["config"], errors) - if "components" not in config["flower"]: - errors.append("Missing [flower.components] section") + if "publisher" not in config["tool"]["flwr"]: + errors.append('Property "publisher" missing in [tool.flwr]') + if "config" in config["tool"]["flwr"]: + _validate_run_config(config["tool"]["flwr"]["config"], errors) + if "components" not in config["tool"]["flwr"]: + errors.append("Missing [tool.flwr.components] section") else: - if "serverapp" not in config["flower"]["components"]: - errors.append('Property "serverapp" missing in [flower.components]') - if "clientapp" not in config["flower"]["components"]: - errors.append('Property "clientapp" missing in [flower.components]') + if "serverapp" not in config["tool"]["flwr"]["components"]: + errors.append('Property "serverapp" missing in [tool.flwr.components]') + if "clientapp" not in config["tool"]["flwr"]["components"]: + errors.append('Property "clientapp" missing in [tool.flwr.components]') return len(errors) == 0, errors, warnings @@ -165,14 +165,14 @@ def validate( # Validate serverapp is_valid, reason = object_ref.validate( - config["flower"]["components"]["serverapp"], check_module + config["tool"]["flwr"]["components"]["serverapp"], check_module ) if not is_valid and isinstance(reason, str): return False, [reason], [] # Validate clientapp is_valid, reason = object_ref.validate( - config["flower"]["components"]["clientapp"], check_module + config["tool"]["flwr"]["components"]["clientapp"], check_module ) if not is_valid and isinstance(reason, str): diff --git a/src/py/flwr/cli/config_utils_test.py b/src/py/flwr/cli/config_utils_test.py index b24425cd08f4..35d9900703b6 100644 --- a/src/py/flwr/cli/config_utils_test.py +++ b/src/py/flwr/cli/config_utils_test.py @@ -34,27 +34,18 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: name = "fedgpt" version = "1.0.0" description = "" - authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, - ] license = {text = "Apache License (2.0)"} dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - - [flower.engine] - name = "simulation" # optional - - [flower.engine.simulation.supernode] - count = 10 # optional """ expected_config = { "build-system": {"build-backend": "hatchling.build", "requires": ["hatchling"]}, @@ -62,19 +53,16 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: "name": "fedgpt", "version": "1.0.0", "description": "", - "authors": [{"email": "hello@flower.ai", "name": "The Flower Authors"}], "license": {"text": "Apache License (2.0)"}, "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "engine": { - "name": "simulation", - "simulation": {"supernode": {"count": 10}}, + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, }, }, } @@ -109,27 +97,18 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: name = "fedgpt" version = "1.0.0" description = "" - authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, - ] license = {text = "Apache License (2.0)"} dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - - [flower.engine] - name = "simulation" # optional - - [flower.engine.simulation.supernode] - count = 10 # optional """ expected_config = { "build-system": {"build-backend": "hatchling.build", "requires": ["hatchling"]}, @@ -137,19 +116,16 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: "name": "fedgpt", "version": "1.0.0", "description": "", - "authors": [{"email": "hello@flower.ai", "name": "The Flower Authors"}], "license": {"text": "Apache License (2.0)"}, "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "engine": { - "name": "simulation", - "simulation": {"supernode": {"count": 10}}, + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, }, }, } @@ -219,7 +195,7 @@ def test_validate_pyproject_toml_fields_no_flower_components() -> None: "license": "", "authors": [], }, - "flower": {}, + "tool": {"flwr": {}}, } # Execute @@ -242,7 +218,7 @@ def test_validate_pyproject_toml_fields_no_server_and_client_app() -> None: "license": "", "authors": [], }, - "flower": {"components": {}}, + "tool": {"flwr": {"components": {}}}, } # Execute @@ -265,9 +241,11 @@ def test_validate_pyproject_toml_fields() -> None: "license": "", "authors": [], }, - "flower": { - "publisher": "flwrlabs", - "components": {"serverapp": "", "clientapp": ""}, + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": {"serverapp": "", "clientapp": ""}, + }, }, } @@ -291,11 +269,13 @@ def test_validate_pyproject_toml() -> None: "license": "", "authors": [], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "flwr.cli.run:run", - "clientapp": "flwr.cli.run:run", + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "flwr.cli.run:run", + "clientapp": "flwr.cli.run:run", + }, }, }, } @@ -320,11 +300,13 @@ def test_validate_pyproject_toml_fail() -> None: "license": "", "authors": [], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "flwr.cli.run:run", - "clientapp": "flwr.cli.run:runa", + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "flwr.cli.run:run", + "clientapp": "flwr.cli.run:runa", + }, }, }, } diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index de9227bee450..7444f10c1eb7 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -149,7 +149,7 @@ def validate_and_install( ) raise typer.Exit(code=1) - publisher = config["flower"]["publisher"] + publisher = config["tool"]["flwr"]["publisher"] project_name = config["project"]["name"] version = config["project"]["version"] diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index 109cbf66a35b..17630dd9d0dc 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -22,15 +22,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.app:server" clientapp = "$import_name.app:client" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 6c7e50393098..6f46d6de5bf5 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -20,15 +20,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index f5c66cc729b8..045a1f4e57eb 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -17,15 +17,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index eaeec144adb2..5ea2c420d6f8 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -17,15 +17,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index 6f386990ba6e..d166616bb616 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -15,15 +15,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index 4313079fa74a..c0323126516d 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -17,15 +17,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 8ab7c10d0107..0e63375aab00 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -16,15 +16,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index a64dfbe6bf77..aeca4a17805f 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -16,15 +16,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 1ae4017492b0..c39ae0decd4b 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -77,7 +77,9 @@ def run( typer.secho("Success", fg=typer.colors.GREEN) - federation_name = federation_name or config["flower"]["federations"].get("default") + federation_name = federation_name or config["tool"]["flwr"]["federations"].get( + "default" + ) if federation_name is None: typer.secho( @@ -90,9 +92,9 @@ def run( raise typer.Exit(code=1) # Validate the federation exists in the configuration - federation = config["flower"]["federations"].get(federation_name) + federation = config["tool"]["flwr"]["federations"].get(federation_name) if federation is None: - available_feds = list(config["flower"]["federations"]) + available_feds = list(config["tool"]["flwr"]["federations"]) typer.secho( f"❌ There is no `{federation_name}` federation declared in the " "`pyproject.toml`.\n The following federations were found:\n\n" @@ -141,8 +143,8 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( config: Dict[str, Any], federation: Dict[str, Any], federation_name: str ) -> None: - server_app_ref = config["flower"]["components"]["serverapp"] - client_app_ref = config["flower"]["components"]["clientapp"] + server_app_ref = config["tool"]["flwr"]["components"]["serverapp"] + client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] try: num_supernodes = federation["options"]["num-supernodes"] @@ -151,7 +153,7 @@ def _run_without_superexec( "❌ The project's `pyproject.toml` needs to declare the number of" " SuperNodes in the simulation. To simulate 10 SuperNodes," " use the following notation:\n\n" - f"[flower.federations.{federation_name}]\n" + f"[tool.flwr.federations.{federation_name}]\n" "options.num-supernodes = 10\n", fg=typer.colors.RED, bold=True, diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 2f2fa58b428c..027c3376b7f3 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -248,7 +248,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: dir_path = Path(project_dir).absolute() # Set app reference - client_app_ref = config["flower"]["components"]["clientapp"] + client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] # Set sys.path nonlocal inserted_path diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 54d74353e4ed..e2b06ff86110 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -97,7 +97,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir) - default_config = get_project_config(project_dir)["flower"].get("config", {}) + default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {}) flat_default_config = flatten_dict(default_config) return _fuse_dicts(flat_default_config, run.override_config) diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index fe429bab9cb5..899240c1e76a 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -93,20 +93,20 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - [flower.config] + [tool.flwr.config] num_server_rounds = "10" momentum = "0.1" lr = "0.01" serverapp.test = "key" - [flower.config.clientapp] + [tool.flwr.config.clientapp] test = "key" """ overrides = { @@ -131,7 +131,7 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: f.write(textwrap.dedent(pyproject_toml_content)) # Execute - default_config = get_project_config(tmp_path)["flower"].get("config", {}) + default_config = get_project_config(tmp_path)["tool"]["flwr"].get("config", {}) config = _fuse_dicts(flatten_dict(default_config), overrides) @@ -158,14 +158,14 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - [flower.config] + [tool.flwr.config] num_server_rounds = "10" momentum = "0.1" lr = "0.01" @@ -179,16 +179,18 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: "license": {"text": "Apache License (2.0)"}, "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "config": { - "num_server_rounds": "10", - "momentum": "0.1", - "lr": "0.01", + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, + "config": { + "num_server_rounds": "10", + "momentum": "0.1", + "lr": "0.01", + }, }, }, } diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 4cc25feb7e0e..efaba24f05f9 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -186,7 +186,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches run_ = driver.run server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir)) config = get_project_config(server_app_dir) - server_app_attr = config["flower"]["components"]["serverapp"] + server_app_attr = config["tool"]["flwr"]["components"]["serverapp"] server_app_run_config = get_fused_config(run_, flwr_dir) else: # User provided `server-app`, but not `--run-id` diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 9a8e19365ab9..fa7a8ad9b0d3 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -112,7 +112,7 @@ def start_run( ) # Get ClientApp and SeverApp components - flower_components = config["flower"]["components"] + flower_components = config["tool"]["flwr"]["components"] clientapp = flower_components["clientapp"] serverapp = flower_components["serverapp"]