flwr 1.23.0__py3-none-any.whl → 1.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +19 -0
- flwr/cli/{new/templates → app_cmd}/__init__.py +9 -1
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +262 -0
- flwr/cli/auth_plugin/auth_plugin.py +4 -5
- flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
- flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
- flwr/cli/build.py +60 -18
- flwr/cli/cli_account_auth_interceptor.py +24 -7
- flwr/cli/config_utils.py +101 -13
- flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +10 -1
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +318 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +211 -130
- flwr/cli/new/new.py +123 -331
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +71 -29
- flwr/cli/run_utils.py +148 -0
- flwr/cli/stop.py +26 -8
- flwr/cli/supernode/ls.py +25 -12
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +239 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +8 -9
- flwr/client/grpc_rere_client/connection.py +16 -14
- flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
- flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +18 -18
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/clientapp/utils.py +3 -3
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +5 -2
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +19 -0
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +38 -21
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +3 -3
- flwr/common/record/arrayrecord.py +18 -30
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +11 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +58 -37
- flwr/compat/client/app.py +38 -37
- flwr/compat/client/grpc_client/connection.py +11 -11
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +71 -52
- flwr/proto/control_pb2.pyi +277 -111
- flwr/proto/control_pb2_grpc.py +249 -40
- flwr/proto/control_pb2_grpc.pyi +185 -52
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +14 -4
- flwr/proto/fab_pb2.pyi +59 -31
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +24 -14
- flwr/proto/fleet_pb2.pyi +141 -61
- flwr/proto/fleet_pb2_grpc.py +189 -48
- flwr/proto/fleet_pb2_grpc.pyi +175 -61
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +15 -5
- flwr/proto/node_pb2.pyi +50 -25
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +158 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +39 -17
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +75 -30
- flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
- flwr/server/superlink/fleet/vce/vce_api.py +15 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +148 -149
- flwr/server/superlink/linkstate/linkstate.py +91 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +502 -436
- flwr/server/superlink/linkstate/utils.py +6 -6
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +10 -11
- flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
- flwr/simulation/run_simulation.py +43 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +34 -1
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -2
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +1 -2
- flwr/supercore/object_store/sqlite_object_store.py +8 -7
- flwr/supercore/primitives/asymmetric.py +1 -1
- flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
- flwr/supercore/sqlite_mixin.py +37 -34
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +190 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/auth_plugin.py +6 -9
- flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
- flwr/{cli/new/templates/app → superlink/federation}/__init__.py +10 -1
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +7 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +190 -23
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
- flwr/supernode/nodestate/nodestate.py +52 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +46 -10
- flwr/supernode/start_client_internal.py +165 -46
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/METADATA +9 -11
- flwr-1.25.0.dist-info/RECORD +393 -0
- flwr/cli/new/templates/app/.gitignore.tpl +0 -163
- flwr/cli/new/templates/app/LICENSE.tpl +0 -202
- flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
- flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
- flwr/cli/new/templates/app/README.md.tpl +0 -37
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.py +0 -15
- flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
- flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
- flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.23.0.dist-info/RECORD +0 -439
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py
CHANGED
|
@@ -20,7 +20,7 @@ import io
|
|
|
20
20
|
import json
|
|
21
21
|
import subprocess
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import Annotated, Any,
|
|
23
|
+
from typing import Annotated, Any, cast
|
|
24
24
|
|
|
25
25
|
import typer
|
|
26
26
|
from rich.console import Console
|
|
@@ -45,6 +45,8 @@ from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_
|
|
|
45
45
|
from flwr.common.typing import Fab
|
|
46
46
|
from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
|
|
47
47
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
48
|
+
from flwr.supercore.constant import NOOP_FEDERATION
|
|
49
|
+
from flwr.supercore.utils import parse_app_spec
|
|
48
50
|
|
|
49
51
|
from ..log import start_stream
|
|
50
52
|
from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
@@ -52,18 +54,18 @@ from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugi
|
|
|
52
54
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
|
53
55
|
|
|
54
56
|
|
|
55
|
-
# pylint: disable-next=too-many-locals, R0913, R0917
|
|
57
|
+
# pylint: disable-next=too-many-locals, too-many-branches, R0913, R0917
|
|
56
58
|
def run(
|
|
57
59
|
app: Annotated[
|
|
58
60
|
Path,
|
|
59
61
|
typer.Argument(help="Path of the Flower App to run."),
|
|
60
62
|
] = Path("."),
|
|
61
63
|
federation: Annotated[
|
|
62
|
-
|
|
64
|
+
str | None,
|
|
63
65
|
typer.Argument(help="Name of the federation to run the app on."),
|
|
64
66
|
] = None,
|
|
65
67
|
run_config_overrides: Annotated[
|
|
66
|
-
|
|
68
|
+
list[str] | None,
|
|
67
69
|
typer.Option(
|
|
68
70
|
"--run-config",
|
|
69
71
|
"-c",
|
|
@@ -71,7 +73,7 @@ def run(
|
|
|
71
73
|
),
|
|
72
74
|
] = None,
|
|
73
75
|
federation_config_overrides: Annotated[
|
|
74
|
-
|
|
76
|
+
list[str] | None,
|
|
75
77
|
typer.Option(
|
|
76
78
|
"--federation-config",
|
|
77
79
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -100,11 +102,32 @@ def run(
|
|
|
100
102
|
try:
|
|
101
103
|
if suppress_output:
|
|
102
104
|
redirect_output(captured_output)
|
|
105
|
+
|
|
106
|
+
# Determine if app is remote
|
|
107
|
+
app_spec = None
|
|
108
|
+
if (app_str := str(app)).startswith("@"):
|
|
109
|
+
# Validate app version and ID format
|
|
110
|
+
try:
|
|
111
|
+
_ = parse_app_spec(app_str)
|
|
112
|
+
except ValueError as e:
|
|
113
|
+
typer.secho(f"❌ {e}", fg=typer.colors.RED, err=True)
|
|
114
|
+
raise typer.Exit(code=1) from e
|
|
115
|
+
|
|
116
|
+
app_spec = app_str
|
|
117
|
+
# Set `app` to current directory for credential storage
|
|
118
|
+
app = Path(".")
|
|
119
|
+
is_remote_app = app_spec is not None
|
|
120
|
+
|
|
103
121
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
104
122
|
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
# Disable the validation for remote apps
|
|
124
|
+
pyproject_path = app / "pyproject.toml" if not is_remote_app else None
|
|
125
|
+
# `./pyproject.toml` will be loaded when `pyproject_path` is None
|
|
126
|
+
config, errors, warnings = load_and_validate(
|
|
127
|
+
pyproject_path, check_module=not is_remote_app
|
|
128
|
+
)
|
|
107
129
|
config = process_loaded_project_config(config, errors, warnings)
|
|
130
|
+
|
|
108
131
|
federation, federation_config = validate_federation_in_project_config(
|
|
109
132
|
federation, config, federation_config_overrides
|
|
110
133
|
)
|
|
@@ -117,6 +140,7 @@ def run(
|
|
|
117
140
|
run_config_overrides,
|
|
118
141
|
stream,
|
|
119
142
|
output_format,
|
|
143
|
+
app_spec,
|
|
120
144
|
)
|
|
121
145
|
else:
|
|
122
146
|
_run_without_control_api(
|
|
@@ -132,6 +156,7 @@ def run(
|
|
|
132
156
|
f"{err}",
|
|
133
157
|
fg=typer.colors.RED,
|
|
134
158
|
bold=True,
|
|
159
|
+
err=True,
|
|
135
160
|
)
|
|
136
161
|
finally:
|
|
137
162
|
if suppress_output:
|
|
@@ -144,22 +169,32 @@ def _run_with_control_api(
|
|
|
144
169
|
app: Path,
|
|
145
170
|
federation: str,
|
|
146
171
|
federation_config: dict[str, Any],
|
|
147
|
-
config_overrides:
|
|
172
|
+
config_overrides: list[str] | None,
|
|
148
173
|
stream: bool,
|
|
149
174
|
output_format: str,
|
|
175
|
+
app_spec: str | None,
|
|
150
176
|
) -> None:
|
|
151
177
|
channel = None
|
|
178
|
+
is_remote_app = app_spec is not None
|
|
152
179
|
try:
|
|
153
180
|
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
154
181
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
155
182
|
stub = ControlStub(channel)
|
|
156
183
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
184
|
+
# Build FAB if local app
|
|
185
|
+
if not is_remote_app:
|
|
186
|
+
fab_bytes = build_fab_from_disk(app)
|
|
187
|
+
fab_hash = hashlib.sha256(fab_bytes).hexdigest()
|
|
188
|
+
config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
|
|
189
|
+
fab_id, fab_version = get_metadata_from_config(config)
|
|
190
|
+
fab = Fab(fab_hash, fab_bytes, {})
|
|
191
|
+
# Skip FAB build if remote app
|
|
192
|
+
else:
|
|
193
|
+
# Use empty values for FAB
|
|
194
|
+
fab_id = fab_version = fab_hash = ""
|
|
195
|
+
fab = Fab(fab_hash, b"", {})
|
|
161
196
|
|
|
162
|
-
|
|
197
|
+
real_federation: str = federation_config.get("federation", NOOP_FEDERATION)
|
|
163
198
|
|
|
164
199
|
# Construct a `ConfigRecord` out of a flattened `UserConfig`
|
|
165
200
|
fed_config = flatten_dict(federation_config.get("options", {}))
|
|
@@ -168,7 +203,9 @@ def _run_with_control_api(
|
|
|
168
203
|
req = StartRunRequest(
|
|
169
204
|
fab=fab_to_proto(fab),
|
|
170
205
|
override_config=user_config_to_proto(parse_config_args(config_overrides)),
|
|
206
|
+
federation=real_federation,
|
|
171
207
|
federation_options=config_record_to_proto(c_record),
|
|
208
|
+
app_spec=app_spec or "",
|
|
172
209
|
)
|
|
173
210
|
with flwr_cli_grpc_exc_handler():
|
|
174
211
|
res = stub.StartRun(req)
|
|
@@ -178,23 +215,27 @@ def _run_with_control_api(
|
|
|
178
215
|
f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN
|
|
179
216
|
)
|
|
180
217
|
else:
|
|
181
|
-
typer.secho("❌ Failed to start run", fg=typer.colors.RED)
|
|
218
|
+
typer.secho("❌ Failed to start run", fg=typer.colors.RED, err=True)
|
|
182
219
|
raise typer.Exit(code=1)
|
|
183
220
|
|
|
184
221
|
if output_format == CliOutputFormat.JSON:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
# Only include FAB metadata if we actually built a local FAB
|
|
223
|
+
payload: dict[str, Any] = {
|
|
224
|
+
"success": res.HasField("run_id"),
|
|
225
|
+
"run-id": f"{res.run_id}" if res.HasField("run_id") else None,
|
|
226
|
+
}
|
|
227
|
+
if not is_remote_app:
|
|
228
|
+
payload.update(
|
|
229
|
+
{
|
|
230
|
+
"fab-id": fab_id,
|
|
231
|
+
"fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
|
|
232
|
+
"fab-version": fab_version,
|
|
233
|
+
"fab-hash": fab_hash[:8],
|
|
234
|
+
"fab-filename": get_fab_filename(config, fab_hash),
|
|
235
|
+
}
|
|
236
|
+
)
|
|
196
237
|
restore_output()
|
|
197
|
-
Console().print_json(
|
|
238
|
+
Console().print_json(json.dumps(payload))
|
|
198
239
|
|
|
199
240
|
if stream:
|
|
200
241
|
start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
|
|
@@ -204,14 +245,14 @@ def _run_with_control_api(
|
|
|
204
245
|
|
|
205
246
|
|
|
206
247
|
def _run_without_control_api(
|
|
207
|
-
app:
|
|
248
|
+
app: Path | None,
|
|
208
249
|
federation_config: dict[str, Any],
|
|
209
|
-
config_overrides:
|
|
250
|
+
config_overrides: list[str] | None,
|
|
210
251
|
federation: str,
|
|
211
252
|
) -> None:
|
|
212
253
|
try:
|
|
213
254
|
num_supernodes = federation_config["options"]["num-supernodes"]
|
|
214
|
-
verbose:
|
|
255
|
+
verbose: bool | None = federation_config["options"].get("verbose")
|
|
215
256
|
backend_cfg = federation_config["options"].get("backend", {})
|
|
216
257
|
except KeyError as err:
|
|
217
258
|
typer.secho(
|
|
@@ -222,6 +263,7 @@ def _run_without_control_api(
|
|
|
222
263
|
"options.num-supernodes = 10\n",
|
|
223
264
|
fg=typer.colors.RED,
|
|
224
265
|
bold=True,
|
|
266
|
+
err=True,
|
|
225
267
|
)
|
|
226
268
|
raise typer.Exit(code=1) from err
|
|
227
269
|
|
flwr/cli/run_utils.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Flower command line interface utils."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import datetime, timedelta
|
|
20
|
+
|
|
21
|
+
from flwr.common.date import isoformat8601_utc
|
|
22
|
+
from flwr.common.typing import Run
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class RunRow: # pylint: disable=too-many-instance-attributes
|
|
27
|
+
"""Represents a single run's data for display.
|
|
28
|
+
|
|
29
|
+
Attributes
|
|
30
|
+
----------
|
|
31
|
+
run_id : int
|
|
32
|
+
The unique identifier for the run.
|
|
33
|
+
federation : str
|
|
34
|
+
The federation name.
|
|
35
|
+
fab_id : str
|
|
36
|
+
The Flower App Bundle identifier.
|
|
37
|
+
fab_version : str
|
|
38
|
+
The FAB version string.
|
|
39
|
+
fab_hash : str
|
|
40
|
+
The SHA-256 hash of the FAB.
|
|
41
|
+
status_text : str
|
|
42
|
+
The formatted status text.
|
|
43
|
+
elapsed : float
|
|
44
|
+
The elapsed time in seconds.
|
|
45
|
+
pending_at : str
|
|
46
|
+
Timestamp when run entered pending state.
|
|
47
|
+
starting_at : str
|
|
48
|
+
Timestamp when run entered starting state.
|
|
49
|
+
running_at : str
|
|
50
|
+
Timestamp when run entered running state.
|
|
51
|
+
finished_at : str
|
|
52
|
+
Timestamp when run finished.
|
|
53
|
+
network_traffic_inbound : int
|
|
54
|
+
The total inbound network traffic (in bytes) used during the run.
|
|
55
|
+
It includes the traffic from SuperNodes to SuperLink.
|
|
56
|
+
network_traffic_outbound : int
|
|
57
|
+
The total outbound network traffic (in bytes) used during the run.
|
|
58
|
+
It includes the traffic from SuperLink to SuperNodes.
|
|
59
|
+
compute_time_serverapp : float
|
|
60
|
+
The total compute time (in seconds) of the ServerApp during the run.
|
|
61
|
+
compute_time_clientapp : float
|
|
62
|
+
The total compute time (in seconds) of all ClientApps during the run.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
run_id: int
|
|
66
|
+
federation: str
|
|
67
|
+
fab_id: str
|
|
68
|
+
fab_version: str
|
|
69
|
+
fab_hash: str
|
|
70
|
+
status_text: str
|
|
71
|
+
elapsed: float
|
|
72
|
+
pending_at: str
|
|
73
|
+
starting_at: str
|
|
74
|
+
running_at: str
|
|
75
|
+
finished_at: str
|
|
76
|
+
network_traffic_inbound: int
|
|
77
|
+
network_traffic_outbound: int
|
|
78
|
+
compute_time_serverapp: float
|
|
79
|
+
compute_time_clientapp: float
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_runs(runs: list[Run], now_isoformat: str) -> list[RunRow]:
|
|
83
|
+
"""Format runs to a list of RunRow objects.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
runs : list[Run]
|
|
88
|
+
List of Run objects to format.
|
|
89
|
+
now_isoformat : str
|
|
90
|
+
Current timestamp in ISO format for calculating elapsed time.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
list[RunRow]
|
|
95
|
+
List of formatted RunRow objects sorted by pending_at timestamp.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def _format_datetime(dt: datetime | None) -> str:
|
|
99
|
+
return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
|
|
100
|
+
|
|
101
|
+
run_list: list[RunRow] = []
|
|
102
|
+
|
|
103
|
+
# Add rows
|
|
104
|
+
for run in sorted(runs, key=lambda x: datetime.fromisoformat(x.pending_at)):
|
|
105
|
+
# Combine status and sub-status into a single string
|
|
106
|
+
if run.status.sub_status == "":
|
|
107
|
+
status_text = run.status.status
|
|
108
|
+
else:
|
|
109
|
+
status_text = f"{run.status.status}:{run.status.sub_status}"
|
|
110
|
+
|
|
111
|
+
# Convert isoformat to datetime
|
|
112
|
+
pending_at = datetime.fromisoformat(run.pending_at) if run.pending_at else None
|
|
113
|
+
starting_at = (
|
|
114
|
+
datetime.fromisoformat(run.starting_at) if run.starting_at else None
|
|
115
|
+
)
|
|
116
|
+
running_at = datetime.fromisoformat(run.running_at) if run.running_at else None
|
|
117
|
+
finished_at = (
|
|
118
|
+
datetime.fromisoformat(run.finished_at) if run.finished_at else None
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Calculate elapsed time
|
|
122
|
+
elapsed_time = timedelta()
|
|
123
|
+
if running_at:
|
|
124
|
+
if finished_at:
|
|
125
|
+
end_time = finished_at
|
|
126
|
+
else:
|
|
127
|
+
end_time = datetime.fromisoformat(now_isoformat)
|
|
128
|
+
elapsed_time = end_time - running_at
|
|
129
|
+
|
|
130
|
+
row = RunRow(
|
|
131
|
+
run_id=run.run_id,
|
|
132
|
+
federation=run.federation,
|
|
133
|
+
fab_id=run.fab_id,
|
|
134
|
+
fab_version=run.fab_version,
|
|
135
|
+
fab_hash=run.fab_hash,
|
|
136
|
+
status_text=status_text,
|
|
137
|
+
elapsed=elapsed_time.total_seconds(),
|
|
138
|
+
pending_at=_format_datetime(pending_at),
|
|
139
|
+
starting_at=_format_datetime(starting_at),
|
|
140
|
+
running_at=_format_datetime(running_at),
|
|
141
|
+
finished_at=_format_datetime(finished_at),
|
|
142
|
+
network_traffic_inbound=run.bytes_recv,
|
|
143
|
+
network_traffic_outbound=run.bytes_sent,
|
|
144
|
+
compute_time_serverapp=elapsed_time.total_seconds(),
|
|
145
|
+
compute_time_clientapp=run.clientapp_runtime,
|
|
146
|
+
)
|
|
147
|
+
run_list.append(row)
|
|
148
|
+
return run_list
|
flwr/cli/stop.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import io
|
|
19
19
|
import json
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Annotated
|
|
21
|
+
from typing import Annotated
|
|
22
22
|
|
|
23
23
|
import typer
|
|
24
24
|
from rich.console import Console
|
|
@@ -51,11 +51,11 @@ def stop( # pylint: disable=R0914
|
|
|
51
51
|
typer.Argument(help="Path of the Flower project"),
|
|
52
52
|
] = Path("."),
|
|
53
53
|
federation: Annotated[
|
|
54
|
-
|
|
54
|
+
str | None,
|
|
55
55
|
typer.Argument(help="Name of the federation"),
|
|
56
56
|
] = None,
|
|
57
57
|
federation_config_overrides: Annotated[
|
|
58
|
-
|
|
58
|
+
list[str] | None,
|
|
59
59
|
typer.Option(
|
|
60
60
|
"--federation-config",
|
|
61
61
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -70,7 +70,11 @@ def stop( # pylint: disable=R0914
|
|
|
70
70
|
),
|
|
71
71
|
] = CliOutputFormat.DEFAULT,
|
|
72
72
|
) -> None:
|
|
73
|
-
"""Stop a run.
|
|
73
|
+
"""Stop a Flower run.
|
|
74
|
+
|
|
75
|
+
This command stops a running Flower App execution by sending a stop request to the
|
|
76
|
+
SuperLink via the Control API.
|
|
77
|
+
"""
|
|
74
78
|
suppress_output = output_format == CliOutputFormat.JSON
|
|
75
79
|
captured_output = io.StringIO()
|
|
76
80
|
try:
|
|
@@ -81,7 +85,7 @@ def stop( # pylint: disable=R0914
|
|
|
81
85
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
82
86
|
|
|
83
87
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
84
|
-
config, errors, warnings = load_and_validate(
|
|
88
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
85
89
|
config = process_loaded_project_config(config, errors, warnings)
|
|
86
90
|
federation, federation_config = validate_federation_in_project_config(
|
|
87
91
|
federation, config, federation_config_overrides
|
|
@@ -101,6 +105,7 @@ def stop( # pylint: disable=R0914
|
|
|
101
105
|
f"❌ {err}",
|
|
102
106
|
fg=typer.colors.RED,
|
|
103
107
|
bold=True,
|
|
108
|
+
err=True,
|
|
104
109
|
)
|
|
105
110
|
raise typer.Exit(code=1) from err
|
|
106
111
|
finally:
|
|
@@ -116,6 +121,7 @@ def stop( # pylint: disable=R0914
|
|
|
116
121
|
f"{err}",
|
|
117
122
|
fg=typer.colors.RED,
|
|
118
123
|
bold=True,
|
|
124
|
+
err=True,
|
|
119
125
|
)
|
|
120
126
|
finally:
|
|
121
127
|
if suppress_output:
|
|
@@ -124,7 +130,17 @@ def stop( # pylint: disable=R0914
|
|
|
124
130
|
|
|
125
131
|
|
|
126
132
|
def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
|
|
127
|
-
"""Stop a run.
|
|
133
|
+
"""Stop a run and display the result.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
stub : ControlStub
|
|
138
|
+
The gRPC stub for Control API communication.
|
|
139
|
+
run_id : int
|
|
140
|
+
The unique identifier of the run to stop.
|
|
141
|
+
output_format : str
|
|
142
|
+
Output format ('default' or 'json').
|
|
143
|
+
"""
|
|
128
144
|
with flwr_cli_grpc_exc_handler():
|
|
129
145
|
response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
|
|
130
146
|
if response.success:
|
|
@@ -133,10 +149,12 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
|
|
|
133
149
|
run_output = json.dumps(
|
|
134
150
|
{
|
|
135
151
|
"success": True,
|
|
136
|
-
"run-id": run_id,
|
|
152
|
+
"run-id": f"{run_id}",
|
|
137
153
|
}
|
|
138
154
|
)
|
|
139
155
|
restore_output()
|
|
140
156
|
Console().print_json(run_output)
|
|
141
157
|
else:
|
|
142
|
-
typer.secho(
|
|
158
|
+
typer.secho(
|
|
159
|
+
f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED, err=True
|
|
160
|
+
)
|
flwr/cli/supernode/ls.py
CHANGED
|
@@ -19,7 +19,7 @@ import io
|
|
|
19
19
|
import json
|
|
20
20
|
from datetime import datetime, timedelta
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import Annotated,
|
|
22
|
+
from typing import Annotated, cast
|
|
23
23
|
|
|
24
24
|
import typer
|
|
25
25
|
from rich.console import Console
|
|
@@ -32,8 +32,8 @@ from flwr.cli.config_utils import (
|
|
|
32
32
|
process_loaded_project_config,
|
|
33
33
|
validate_federation_in_project_config,
|
|
34
34
|
)
|
|
35
|
-
from flwr.common.constant import FAB_CONFIG_FILE,
|
|
36
|
-
from flwr.common.date import
|
|
35
|
+
from flwr.common.constant import FAB_CONFIG_FILE, NOOP_ACCOUNT_NAME, CliOutputFormat
|
|
36
|
+
from flwr.common.date import isoformat8601_utc
|
|
37
37
|
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
38
38
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
39
39
|
ListNodesRequest,
|
|
@@ -41,10 +41,11 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
41
41
|
)
|
|
42
42
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
43
43
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
44
|
+
from flwr.supercore.utils import humanize_duration
|
|
44
45
|
|
|
45
46
|
from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
46
47
|
|
|
47
|
-
_NodeListType = tuple[int, str, str, str, str, str, str, str]
|
|
48
|
+
_NodeListType = tuple[int, str, str, str, str, str, str, str, float]
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def ls( # pylint: disable=R0914, R0913, R0917
|
|
@@ -54,7 +55,7 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
54
55
|
typer.Argument(help="Path of the Flower project"),
|
|
55
56
|
] = Path("."),
|
|
56
57
|
federation: Annotated[
|
|
57
|
-
|
|
58
|
+
str | None,
|
|
58
59
|
typer.Argument(help="Name of the federation"),
|
|
59
60
|
] = None,
|
|
60
61
|
output_format: Annotated[
|
|
@@ -86,7 +87,7 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
86
87
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
87
88
|
|
|
88
89
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
89
|
-
config, errors, warnings = load_and_validate(
|
|
90
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
90
91
|
config = process_loaded_project_config(config, errors, warnings)
|
|
91
92
|
federation, federation_config = validate_federation_in_project_config(
|
|
92
93
|
federation, config
|
|
@@ -138,7 +139,7 @@ def _format_nodes(
|
|
|
138
139
|
) -> list[_NodeListType]:
|
|
139
140
|
"""Format node information for display."""
|
|
140
141
|
|
|
141
|
-
def _format_datetime(dt_str:
|
|
142
|
+
def _format_datetime(dt_str: str | None) -> str:
|
|
142
143
|
dt = datetime.fromisoformat(dt_str) if dt_str else None
|
|
143
144
|
return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
|
|
144
145
|
|
|
@@ -160,12 +161,13 @@ def _format_nodes(
|
|
|
160
161
|
(
|
|
161
162
|
node.node_id,
|
|
162
163
|
node.owner_aid,
|
|
164
|
+
node.owner_name,
|
|
163
165
|
node.status,
|
|
164
166
|
_format_datetime(node.registered_at),
|
|
165
167
|
_format_datetime(node.last_activated_at),
|
|
166
168
|
_format_datetime(node.last_deactivated_at),
|
|
167
169
|
_format_datetime(node.unregistered_at),
|
|
168
|
-
|
|
170
|
+
elapsed_time_activated.total_seconds(),
|
|
169
171
|
)
|
|
170
172
|
)
|
|
171
173
|
|
|
@@ -188,7 +190,8 @@ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
|
|
|
188
190
|
for row in nodes_info:
|
|
189
191
|
(
|
|
190
192
|
node_id,
|
|
191
|
-
|
|
193
|
+
_,
|
|
194
|
+
owner_name,
|
|
192
195
|
status,
|
|
193
196
|
_,
|
|
194
197
|
last_activated_at,
|
|
@@ -216,9 +219,17 @@ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
|
|
|
216
219
|
|
|
217
220
|
formatted_row = (
|
|
218
221
|
f"[bold]{node_id}[/bold]",
|
|
219
|
-
|
|
222
|
+
(
|
|
223
|
+
f"{owner_name}"
|
|
224
|
+
if owner_name != NOOP_ACCOUNT_NAME
|
|
225
|
+
else f"[dim]{owner_name}[/dim]"
|
|
226
|
+
),
|
|
220
227
|
f"[{status_style}]{status}",
|
|
221
|
-
|
|
228
|
+
(
|
|
229
|
+
f"[cyan]{humanize_duration(elapse_activated)}[/cyan]"
|
|
230
|
+
if status == "online"
|
|
231
|
+
else ""
|
|
232
|
+
),
|
|
222
233
|
time_at,
|
|
223
234
|
)
|
|
224
235
|
table.add_row(*formatted_row)
|
|
@@ -233,6 +244,7 @@ def _to_json(nodes_info: list[_NodeListType], verbose: bool) -> str:
|
|
|
233
244
|
(
|
|
234
245
|
node_id,
|
|
235
246
|
owner_aid,
|
|
247
|
+
owner_name,
|
|
236
248
|
status,
|
|
237
249
|
created_at,
|
|
238
250
|
activated_at,
|
|
@@ -246,8 +258,9 @@ def _to_json(nodes_info: list[_NodeListType], verbose: bool) -> str:
|
|
|
246
258
|
|
|
247
259
|
nodes_list.append(
|
|
248
260
|
{
|
|
249
|
-
"node-id": node_id,
|
|
261
|
+
"node-id": f"{node_id}",
|
|
250
262
|
"owner-aid": owner_aid,
|
|
263
|
+
"owner-name": owner_name,
|
|
251
264
|
"status": status,
|
|
252
265
|
"created-at": created_at,
|
|
253
266
|
"online-at": activated_at,
|
flwr/cli/supernode/register.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import io
|
|
19
19
|
import json
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Annotated
|
|
21
|
+
from typing import Annotated
|
|
22
22
|
|
|
23
23
|
import typer
|
|
24
24
|
from cryptography.exceptions import UnsupportedAlgorithm
|
|
@@ -57,7 +57,7 @@ def register( # pylint: disable=R0914
|
|
|
57
57
|
typer.Argument(help="Path of the Flower project"),
|
|
58
58
|
] = Path("."),
|
|
59
59
|
federation: Annotated[
|
|
60
|
-
|
|
60
|
+
str | None,
|
|
61
61
|
typer.Argument(help="Name of the federation"),
|
|
62
62
|
] = None,
|
|
63
63
|
output_format: Annotated[
|
|
@@ -85,7 +85,7 @@ def register( # pylint: disable=R0914
|
|
|
85
85
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
86
86
|
|
|
87
87
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
88
|
-
config, errors, warnings = load_and_validate(
|
|
88
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
89
89
|
config = process_loaded_project_config(config, errors, warnings)
|
|
90
90
|
federation, federation_config = validate_federation_in_project_config(
|
|
91
91
|
federation, config
|
|
@@ -107,6 +107,7 @@ def register( # pylint: disable=R0914
|
|
|
107
107
|
f"❌ {err}",
|
|
108
108
|
fg=typer.colors.RED,
|
|
109
109
|
bold=True,
|
|
110
|
+
err=True,
|
|
110
111
|
)
|
|
111
112
|
raise typer.Exit(code=1) from err
|
|
112
113
|
finally:
|
|
@@ -123,6 +124,7 @@ def register( # pylint: disable=R0914
|
|
|
123
124
|
f"{err}",
|
|
124
125
|
fg=typer.colors.RED,
|
|
125
126
|
bold=True,
|
|
127
|
+
err=True,
|
|
126
128
|
)
|
|
127
129
|
finally:
|
|
128
130
|
if suppress_output:
|
|
@@ -151,7 +153,9 @@ def _register_node(stub: ControlStub, public_key: bytes, output_format: str) ->
|
|
|
151
153
|
restore_output()
|
|
152
154
|
Console().print_json(run_output)
|
|
153
155
|
else:
|
|
154
|
-
typer.secho(
|
|
156
|
+
typer.secho(
|
|
157
|
+
"❌ SuperNode couldn't be registered.", fg=typer.colors.RED, err=True
|
|
158
|
+
)
|
|
155
159
|
|
|
156
160
|
|
|
157
161
|
def try_load_public_key(public_key_path: Path) -> bytes:
|
|
@@ -161,6 +165,7 @@ def try_load_public_key(public_key_path: Path) -> bytes:
|
|
|
161
165
|
f"❌ Public key file '{public_key_path}' does not exist.",
|
|
162
166
|
fg=typer.colors.RED,
|
|
163
167
|
bold=True,
|
|
168
|
+
err=True,
|
|
164
169
|
)
|
|
165
170
|
raise typer.Exit(code=1)
|
|
166
171
|
|
flwr/cli/supernode/unregister.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import io
|
|
19
19
|
import json
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Annotated
|
|
21
|
+
from typing import Annotated
|
|
22
22
|
|
|
23
23
|
import typer
|
|
24
24
|
from rich.console import Console
|
|
@@ -49,7 +49,7 @@ def unregister( # pylint: disable=R0914
|
|
|
49
49
|
typer.Argument(help="Path of the Flower project"),
|
|
50
50
|
] = Path("."),
|
|
51
51
|
federation: Annotated[
|
|
52
|
-
|
|
52
|
+
str | None,
|
|
53
53
|
typer.Argument(help="Name of the federation"),
|
|
54
54
|
] = None,
|
|
55
55
|
output_format: Annotated[
|
|
@@ -73,7 +73,7 @@ def unregister( # pylint: disable=R0914
|
|
|
73
73
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
74
74
|
|
|
75
75
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
76
|
-
config, errors, warnings = load_and_validate(
|
|
76
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
77
77
|
config = process_loaded_project_config(config, errors, warnings)
|
|
78
78
|
federation, federation_config = validate_federation_in_project_config(
|
|
79
79
|
federation, config
|
|
@@ -93,6 +93,7 @@ def unregister( # pylint: disable=R0914
|
|
|
93
93
|
f"❌ {err}",
|
|
94
94
|
fg=typer.colors.RED,
|
|
95
95
|
bold=True,
|
|
96
|
+
err=True,
|
|
96
97
|
)
|
|
97
98
|
raise typer.Exit(code=1) from err
|
|
98
99
|
finally:
|
|
@@ -109,6 +110,7 @@ def unregister( # pylint: disable=R0914
|
|
|
109
110
|
f"{err}",
|
|
110
111
|
fg=typer.colors.RED,
|
|
111
112
|
bold=True,
|
|
113
|
+
err=True,
|
|
112
114
|
)
|
|
113
115
|
finally:
|
|
114
116
|
if suppress_output:
|