From dcad8ba3f618ff5dfb271c819a0a20235301a676 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Mon, 10 Feb 2025 20:10:29 +0800 Subject: [PATCH 1/2] refactor(framework) Order CLI args alphabetically --- src/py/flwr/client/clientapp/app.py | 2 ++ src/py/flwr/client/supernode/app.py | 2 ++ src/py/flwr/common/args_utils.py | 42 ++++++++++++++++++++++++++++ src/py/flwr/server/app.py | 43 +++++++++++++++++++++++++++++ src/py/flwr/server/serverapp/app.py | 2 ++ 5 files changed, 91 insertions(+) create mode 100644 src/py/flwr/common/args_utils.py diff --git a/src/py/flwr/client/clientapp/app.py b/src/py/flwr/client/clientapp/app.py index cef822a14e86..1b00e05be6ca 100644 --- a/src/py/flwr/client/clientapp/app.py +++ b/src/py/flwr/client/clientapp/app.py @@ -26,6 +26,7 @@ from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import Context, Message from flwr.common.args import add_args_flwr_app_common +from flwr.common.args_utils import SortingHelpFormatter from flwr.common.config import get_flwr_dir from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ErrorCode from flwr.common.exit import ExitCode, flwr_exit @@ -235,6 +236,7 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser: """Parse flwr-clientapp command line arguments.""" parser = argparse.ArgumentParser( description="Run a Flower ClientApp", + formatter_class=SortingHelpFormatter, ) parser.add_argument( "--clientappio-api-address", diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index af147de64b0f..acbf30ef2948 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -29,6 +29,7 @@ from flwr.common import EventType, event from flwr.common.args import try_obtain_root_certificates +from flwr.common.args_utils import SortingHelpFormatter from flwr.common.config import parse_config_args from flwr.common.constant import ( CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS, @@ -112,6 +113,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser: """Parse flower-supernode command line arguments.""" parser = argparse.ArgumentParser( description="Start a Flower SuperNode", + formatter_class=SortingHelpFormatter, ) _parse_args_common(parser) parser.add_argument( diff --git a/src/py/flwr/common/args_utils.py b/src/py/flwr/common/args_utils.py new file mode 100644 index 000000000000..df2295e63905 --- /dev/null +++ b/src/py/flwr/common/args_utils.py @@ -0,0 +1,42 @@ +# Copyright 2025 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. +# ============================================================================== +"""Flower argument utilities.""" + + +import argparse +from argparse import Action, _MutuallyExclusiveGroup +from typing import Iterable, List, Optional + + +class SortingHelpFormatter(argparse.HelpFormatter): + """Sort the arguments alphabetically in the help text.""" + + def add_usage( + self, + usage: Optional[str], + actions: Iterable[Action], + groups: Iterable[_MutuallyExclusiveGroup], + prefix: Optional[str] = None, + ) -> None: + + # def add_usage(self, usage, actions, groups, prefix=None) -> None: + # Sort the usage actions alphabetically + sorted_actions: List[Action] = sorted(actions, key=lambda action: action.dest) + super().add_usage(usage, sorted_actions, groups, prefix) + + def add_arguments(self, actions: Iterable[Action]) -> None: + # Sort the argument actions alphabetically + sorted_actions: List[Action] = sorted(actions, key=lambda action: action.dest) + super().add_arguments(sorted_actions) diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index 97b899d555e5..e4e8630863e4 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -37,6 +37,7 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event from flwr.common.address import parse_address from flwr.common.args import try_obtain_server_certificates +from flwr.common.args_utils import SortingHelpFormatter from flwr.common.auth_plugin import ExecAuthPlugin from flwr.common.config import get_flwr_dir, parse_config_args from flwr.common.constant import ( @@ -691,8 +692,50 @@ def _run_fleet_api_rest( def _parse_args_run_superlink() -> argparse.ArgumentParser: """Parse command line arguments for both ServerAppIo API and Fleet API.""" + + # Custom ArgumentParser to sort arguments + # class SortingHelpFormatter(argparse.HelpFormatter): + # def add_arguments(self, actions): + # actions = sorted(actions, key=lambda action: action.dest) + # super(SortingHelpFormatter, self).add_arguments(actions) + # class SortingHelpFormatter(argparse.HelpFormatter): + # def add_usage(self, usage, actions, groups, prefix=None): + # # Sort all actions (both optionals and positionals) alphabetically. + # # For optionals, sort by their option strings; for positionals, sort by their destination. + # actions = sorted(actions, key=lambda action: action.dest) + # # actions = sorted( + # # actions, + # # key=lambda a: a.option_strings if a.option_strings else [a.dest], + # # ) + # super(SortingHelpFormatter, self).add_usage(usage, actions, groups, prefix) + + # def add_arguments(self, actions): + # # Sort the actions the same way for the help text. + # actions = sorted(actions, key=lambda action: action.dest) + # # actions = sorted( + # # actions, + # # key=lambda a: a.option_strings if a.option_strings else [a.dest], + # # ) + # super(SortingHelpFormatter, self).add_arguments(actions) + + # class SortedUsageFormatter(argparse.HelpFormatter): + # def _format_usage(self, usage, actions, groups, prefix): + # # Sort actions: if option_strings exist, sort by the first one, + # # otherwise (for positionals) sort by the destination name. + # actions = sorted(actions, key=lambda action: action.dest) + # # sorted_actions = sorted( + # # actions, + # # key=lambda action: ( + # # action.option_strings[0] if action.option_strings else action.dest + # # ), + # # ) + # return super(SortedUsageFormatter, self)._format_usage( + # usage, actions, groups, prefix + # ) + parser = argparse.ArgumentParser( description="Start a Flower SuperLink", + formatter_class=SortingHelpFormatter, ) _add_args_common(parser=parser) diff --git a/src/py/flwr/server/serverapp/app.py b/src/py/flwr/server/serverapp/app.py index 113baa874c20..54e2ab46950d 100644 --- a/src/py/flwr/server/serverapp/app.py +++ b/src/py/flwr/server/serverapp/app.py @@ -26,6 +26,7 @@ from flwr.cli.install import install_from_fab from flwr.cli.utils import get_sha256_hash from flwr.common.args import add_args_flwr_app_common +from flwr.common.args_utils import SortingHelpFormatter from flwr.common.config import ( get_flwr_dir, get_fused_config_from_dir, @@ -240,6 +241,7 @@ def _parse_args_run_flwr_serverapp() -> argparse.ArgumentParser: """Parse flwr-serverapp command line arguments.""" parser = argparse.ArgumentParser( description="Run a Flower ServerApp", + formatter_class=SortingHelpFormatter, ) parser.add_argument( "--serverappio-api-address", From 4652f5021762545639aacae0524d33778a63a65a Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Mon, 10 Feb 2025 20:14:40 +0800 Subject: [PATCH 2/2] Remove unused lines --- src/py/flwr/server/app.py | 40 --------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index e4e8630863e4..3f30281d3f4c 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -693,46 +693,6 @@ def _run_fleet_api_rest( def _parse_args_run_superlink() -> argparse.ArgumentParser: """Parse command line arguments for both ServerAppIo API and Fleet API.""" - # Custom ArgumentParser to sort arguments - # class SortingHelpFormatter(argparse.HelpFormatter): - # def add_arguments(self, actions): - # actions = sorted(actions, key=lambda action: action.dest) - # super(SortingHelpFormatter, self).add_arguments(actions) - # class SortingHelpFormatter(argparse.HelpFormatter): - # def add_usage(self, usage, actions, groups, prefix=None): - # # Sort all actions (both optionals and positionals) alphabetically. - # # For optionals, sort by their option strings; for positionals, sort by their destination. - # actions = sorted(actions, key=lambda action: action.dest) - # # actions = sorted( - # # actions, - # # key=lambda a: a.option_strings if a.option_strings else [a.dest], - # # ) - # super(SortingHelpFormatter, self).add_usage(usage, actions, groups, prefix) - - # def add_arguments(self, actions): - # # Sort the actions the same way for the help text. - # actions = sorted(actions, key=lambda action: action.dest) - # # actions = sorted( - # # actions, - # # key=lambda a: a.option_strings if a.option_strings else [a.dest], - # # ) - # super(SortingHelpFormatter, self).add_arguments(actions) - - # class SortedUsageFormatter(argparse.HelpFormatter): - # def _format_usage(self, usage, actions, groups, prefix): - # # Sort actions: if option_strings exist, sort by the first one, - # # otherwise (for positionals) sort by the destination name. - # actions = sorted(actions, key=lambda action: action.dest) - # # sorted_actions = sorted( - # # actions, - # # key=lambda action: ( - # # action.option_strings[0] if action.option_strings else action.dest - # # ), - # # ) - # return super(SortedUsageFormatter, self)._format_usage( - # usage, actions, groups, prefix - # ) - parser = argparse.ArgumentParser( description="Start a Flower SuperLink", formatter_class=SortingHelpFormatter,