From be73dc50d1c22870dfa6eb722be33d27d6a1f613 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 21:13:54 +0200 Subject: [PATCH 1/2] refactor(framework:skip) Add return types to certain CLI functions (#3601) --- src/py/flwr/cli/build.py | 4 +++- src/py/flwr/cli/install.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index d279a8d11bc2..2981eacf925d 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -33,7 +33,7 @@ def build( Optional[Path], typer.Option(help="The Flower project directory to bundle into a FAB"), ] = None, -) -> None: +) -> str: """Build a Flower project into a Flower App Bundle (FAB). You can run `flwr build` without any argument to bundle the current directory: @@ -125,6 +125,8 @@ def build( f"🎊 Successfully built {fab_filename}.", fg=typer.colors.GREEN, bold=True ) + return fab_filename + def _load_gitignore(directory: Path) -> pathspec.PathSpec: """Load and parse .gitignore file, returning a pathspec.""" diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index d6d2ee55a47a..d953c650f3ac 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -84,7 +84,7 @@ def install_from_fab( fab_file: Union[Path, bytes], flwr_dir: Optional[Path], skip_prompt: bool = False, -) -> None: +) -> Path: """Install from a FAB file after extracting and validating.""" fab_file_archive: Union[Path, IO[bytes]] fab_name: Optional[str] @@ -124,7 +124,11 @@ def install_from_fab( shutil.rmtree(info_dir) - validate_and_install(tmpdir_path, fab_name, flwr_dir, skip_prompt) + installed_path = validate_and_install( + tmpdir_path, fab_name, flwr_dir, skip_prompt + ) + + return installed_path def validate_and_install( @@ -132,7 +136,7 @@ def validate_and_install( fab_name: Optional[str], flwr_dir: Optional[Path], skip_prompt: bool = False, -) -> None: +) -> Path: """Validate TOML files and install the project to the desired directory.""" config, _, _ = load_and_validate(project_dir / "pyproject.toml", check_module=False) @@ -185,7 +189,7 @@ def validate_and_install( bold=True, ) ): - return + return install_dir install_dir.mkdir(parents=True, exist_ok=True) @@ -202,6 +206,8 @@ def validate_and_install( bold=True, ) + return install_dir + def _verify_hashes(list_content: str, tmpdir: Path) -> bool: """Verify file hashes based on the LIST content.""" From c16cd7503465f9d13e98173a160bbef03bc1e666 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 21:32:30 +0200 Subject: [PATCH 2/2] refactor(framework:skip) Add function to get flwr dir (#3600) --- src/py/flwr/cli/install.py | 14 +++----------- src/py/flwr/client/supernode/app.py | 11 +++-------- src/py/flwr/common/config.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 src/py/flwr/common/config.py diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index d953c650f3ac..de9227bee450 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -15,7 +15,6 @@ """Flower command line interface `install` command.""" -import os import shutil import tempfile import zipfile @@ -26,6 +25,8 @@ import typer from typing_extensions import Annotated +from flwr.common.config import get_flwr_dir + from .config_utils import load_and_validate from .utils import get_sha256_hash @@ -165,16 +166,7 @@ def validate_and_install( raise typer.Exit(code=1) install_dir: Path = ( - ( - Path( - os.getenv( - "FLWR_HOME", - f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", - ) - ) - if not flwr_dir - else flwr_dir - ) + (get_flwr_dir() if not flwr_dir else flwr_dir) / "apps" / publisher / project_name diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 9ec9695fb51e..ddc547ad371b 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -15,7 +15,6 @@ """Flower SuperNode.""" import argparse -import os import sys from logging import DEBUG, INFO, WARN from pathlib import Path @@ -32,6 +31,7 @@ from flwr.cli.config_utils import validate_fields from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event +from flwr.common.config import get_flwr_dir from flwr.common.exit_handlers import register_exit_handlers from flwr.common.logger import log, warn_deprecated_feature from flwr.common.object_ref import load_app, validate @@ -170,12 +170,7 @@ def _get_load_client_app_fn( flwr_dir = Path("") if "flwr_dir" in args: if args.flwr_dir is None: - flwr_dir = Path( - os.getenv( - "FLWR_HOME", - f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", - ) - ) + flwr_dir = get_flwr_dir() else: flwr_dir = Path(args.flwr_dir) @@ -234,7 +229,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: # Load pyproject.toml file toml_path = project_dir / "pyproject.toml" - if not os.path.isfile(toml_path): + if not toml_path.is_file(): raise LoadClientAppError( f"Cannot find pyproject.toml in {project_dir}", ) from None diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py new file mode 100644 index 000000000000..2c5b5962e7bd --- /dev/null +++ b/src/py/flwr/common/config.py @@ -0,0 +1,28 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Provide functions for managing global Flower config.""" + +import os +from pathlib import Path + + +def get_flwr_dir() -> Path: + """Return the Flower home directory based on env variables.""" + return Path( + os.getenv( + "FLWR_HOME", + f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + ) + )