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/log.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import time
|
|
19
19
|
from logging import DEBUG, ERROR, INFO
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Annotated, Any,
|
|
21
|
+
from typing import Annotated, Any, cast
|
|
22
22
|
|
|
23
23
|
import grpc
|
|
24
24
|
import typer
|
|
@@ -39,13 +39,27 @@ from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class AllLogsRetrieved(BaseException):
|
|
42
|
-
"""
|
|
42
|
+
"""Exception raised when all available logs have been retrieved.
|
|
43
|
+
|
|
44
|
+
This exception is used internally to signal that the log stream has reached the end
|
|
45
|
+
and all logs have been successfully retrieved.
|
|
46
|
+
"""
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
def start_stream(
|
|
46
50
|
run_id: int, channel: grpc.Channel, refresh_period: int = CONN_REFRESH_PERIOD
|
|
47
51
|
) -> None:
|
|
48
|
-
"""Start log streaming for a given run ID.
|
|
52
|
+
"""Start log streaming for a given run ID.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
run_id : int
|
|
57
|
+
The unique identifier of the run to stream logs from.
|
|
58
|
+
channel : grpc.Channel
|
|
59
|
+
The gRPC channel for communication.
|
|
60
|
+
refresh_period : int (default: CONN_REFRESH_PERIOD)
|
|
61
|
+
Connection refresh period in seconds.
|
|
62
|
+
"""
|
|
49
63
|
stub = ControlStub(channel)
|
|
50
64
|
after_timestamp = 0.0
|
|
51
65
|
try:
|
|
@@ -111,7 +125,17 @@ def stream_logs(
|
|
|
111
125
|
|
|
112
126
|
|
|
113
127
|
def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
|
114
|
-
"""Print logs from the beginning of a run.
|
|
128
|
+
"""Print logs from the beginning of a run.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
run_id : int
|
|
133
|
+
The unique identifier of the run to retrieve logs from.
|
|
134
|
+
channel : grpc.Channel
|
|
135
|
+
The gRPC channel for communication.
|
|
136
|
+
timeout : int
|
|
137
|
+
Timeout duration in seconds for the log retrieval request.
|
|
138
|
+
"""
|
|
115
139
|
stub = ControlStub(channel)
|
|
116
140
|
req = StreamLogsRequest(run_id=run_id, after_timestamp=0.0)
|
|
117
141
|
|
|
@@ -143,11 +167,11 @@ def log(
|
|
|
143
167
|
typer.Argument(help="Path of the Flower project to run"),
|
|
144
168
|
] = Path("."),
|
|
145
169
|
federation: Annotated[
|
|
146
|
-
|
|
170
|
+
str | None,
|
|
147
171
|
typer.Argument(help="Name of the federation to run the app on"),
|
|
148
172
|
] = None,
|
|
149
173
|
federation_config_overrides: Annotated[
|
|
150
|
-
|
|
174
|
+
list[str] | None,
|
|
151
175
|
typer.Option(
|
|
152
176
|
"--federation-config",
|
|
153
177
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -161,11 +185,15 @@ def log(
|
|
|
161
185
|
),
|
|
162
186
|
] = True,
|
|
163
187
|
) -> None:
|
|
164
|
-
"""Get logs from a
|
|
188
|
+
"""Get logs from a run.
|
|
189
|
+
|
|
190
|
+
Retrieve and display logs from a Flower run. Logs can be streamed in real-time (with
|
|
191
|
+
--stream) or printed once (with --show).
|
|
192
|
+
"""
|
|
165
193
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
166
194
|
|
|
167
195
|
pyproject_path = app / "pyproject.toml" if app else None
|
|
168
|
-
config, errors, warnings = load_and_validate(
|
|
196
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
169
197
|
config = process_loaded_project_config(config, errors, warnings)
|
|
170
198
|
federation, federation_config = validate_federation_in_project_config(
|
|
171
199
|
federation, config, federation_config_overrides
|
|
@@ -175,7 +203,7 @@ def log(
|
|
|
175
203
|
try:
|
|
176
204
|
_log_with_control_api(app, federation, federation_config, run_id, stream)
|
|
177
205
|
except Exception as err: # pylint: disable=broad-except
|
|
178
|
-
typer.secho(str(err), fg=typer.colors.RED, bold=True)
|
|
206
|
+
typer.secho(str(err), fg=typer.colors.RED, bold=True, err=True)
|
|
179
207
|
raise typer.Exit(code=1) from None
|
|
180
208
|
|
|
181
209
|
|
|
@@ -186,6 +214,21 @@ def _log_with_control_api(
|
|
|
186
214
|
run_id: int,
|
|
187
215
|
stream: bool,
|
|
188
216
|
) -> None:
|
|
217
|
+
"""Retrieve logs using the Control API.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
app : Path
|
|
222
|
+
Path to the Flower app directory.
|
|
223
|
+
federation : str
|
|
224
|
+
Name of the federation.
|
|
225
|
+
federation_config : dict[str, Any]
|
|
226
|
+
Federation configuration dictionary.
|
|
227
|
+
run_id : int
|
|
228
|
+
The unique identifier of the run to retrieve logs from.
|
|
229
|
+
stream : bool
|
|
230
|
+
If True, stream logs continuously; if False, print once.
|
|
231
|
+
"""
|
|
189
232
|
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
190
233
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
191
234
|
|
flwr/cli/login/login.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Annotated
|
|
19
|
+
from typing import Annotated
|
|
20
20
|
|
|
21
21
|
import typer
|
|
22
22
|
|
|
@@ -50,11 +50,11 @@ def login( # pylint: disable=R0914
|
|
|
50
50
|
typer.Argument(help="Path of the Flower App to run."),
|
|
51
51
|
] = Path("."),
|
|
52
52
|
federation: Annotated[
|
|
53
|
-
|
|
53
|
+
str | None,
|
|
54
54
|
typer.Argument(help="Name of the federation to login into."),
|
|
55
55
|
] = None,
|
|
56
56
|
federation_config_overrides: Annotated[
|
|
57
|
-
|
|
57
|
+
list[str] | None,
|
|
58
58
|
typer.Option(
|
|
59
59
|
"--federation-config",
|
|
60
60
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -65,7 +65,7 @@ def login( # pylint: disable=R0914
|
|
|
65
65
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
66
66
|
|
|
67
67
|
pyproject_path = app / "pyproject.toml" if app else None
|
|
68
|
-
config, errors, warnings = load_and_validate(
|
|
68
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
69
69
|
|
|
70
70
|
config = process_loaded_project_config(config, errors, warnings)
|
|
71
71
|
federation, federation_config = validate_federation_in_project_config(
|
|
@@ -82,6 +82,7 @@ def login( # pylint: disable=R0914
|
|
|
82
82
|
"in the federation configuration.",
|
|
83
83
|
fg=typer.colors.RED,
|
|
84
84
|
bold=True,
|
|
85
|
+
err=True,
|
|
85
86
|
)
|
|
86
87
|
raise typer.Exit(code=1)
|
|
87
88
|
# Check if insecure flag is set to `True`
|
|
@@ -92,6 +93,7 @@ def login( # pylint: disable=R0914
|
|
|
92
93
|
"`true` in the federation configuration.",
|
|
93
94
|
fg=typer.colors.RED,
|
|
94
95
|
bold=True,
|
|
96
|
+
err=True,
|
|
95
97
|
)
|
|
96
98
|
raise typer.Exit(code=1)
|
|
97
99
|
|
|
@@ -127,6 +129,7 @@ def login( # pylint: disable=R0914
|
|
|
127
129
|
f"❌ Login failed: {e.message}",
|
|
128
130
|
fg=typer.colors.RED,
|
|
129
131
|
bold=True,
|
|
132
|
+
err=True,
|
|
130
133
|
)
|
|
131
134
|
raise typer.Exit(code=1) from None
|
|
132
135
|
|
flwr/cli/ls.py
CHANGED
|
@@ -17,9 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import io
|
|
19
19
|
import json
|
|
20
|
-
from datetime import datetime, timedelta
|
|
21
20
|
from pathlib import Path
|
|
22
|
-
from typing import Annotated,
|
|
21
|
+
from typing import Annotated, cast
|
|
23
22
|
|
|
24
23
|
import typer
|
|
25
24
|
from rich.console import Console
|
|
@@ -33,21 +32,19 @@ from flwr.cli.config_utils import (
|
|
|
33
32
|
validate_federation_in_project_config,
|
|
34
33
|
)
|
|
35
34
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
36
|
-
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat, SubStatus
|
|
37
|
-
from flwr.common.date import format_timedelta, isoformat8601_utc
|
|
35
|
+
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat, Status, SubStatus
|
|
38
36
|
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
39
37
|
from flwr.common.serde import run_from_proto
|
|
40
|
-
from flwr.common.typing import Run
|
|
41
38
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
42
39
|
ListRunsRequest,
|
|
43
40
|
ListRunsResponse,
|
|
44
41
|
)
|
|
45
42
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
43
|
+
from flwr.supercore.utils import humanize_bytes, humanize_duration
|
|
46
44
|
|
|
45
|
+
from .run_utils import RunRow, format_runs
|
|
47
46
|
from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
48
47
|
|
|
49
|
-
_RunListType = tuple[int, str, str, str, str, str, str, str, str]
|
|
50
|
-
|
|
51
48
|
|
|
52
49
|
def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
53
50
|
ctx: typer.Context,
|
|
@@ -56,11 +53,11 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
56
53
|
typer.Argument(help="Path of the Flower project"),
|
|
57
54
|
] = Path("."),
|
|
58
55
|
federation: Annotated[
|
|
59
|
-
|
|
56
|
+
str | None,
|
|
60
57
|
typer.Argument(help="Name of the federation"),
|
|
61
58
|
] = None,
|
|
62
59
|
federation_config_overrides: Annotated[
|
|
63
|
-
|
|
60
|
+
list[str] | None,
|
|
64
61
|
typer.Option(
|
|
65
62
|
"--federation-config",
|
|
66
63
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -74,7 +71,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
74
71
|
),
|
|
75
72
|
] = False,
|
|
76
73
|
run_id: Annotated[
|
|
77
|
-
|
|
74
|
+
int | None,
|
|
78
75
|
typer.Option(
|
|
79
76
|
"--run-id",
|
|
80
77
|
help="Specific run ID to display",
|
|
@@ -94,12 +91,11 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
94
91
|
The following details are displayed:
|
|
95
92
|
|
|
96
93
|
- **Run ID:** Unique identifier for the run.
|
|
97
|
-
- **
|
|
94
|
+
- **Federation:** The federation to which the run belongs.
|
|
95
|
+
- **App:** The App associated with the run (``<APP_ID>==<APP_VERSION>``).
|
|
98
96
|
- **Status:** Current status of the run (pending, starting, running, finished).
|
|
99
97
|
- **Elapsed:** Time elapsed since the run started (``HH:MM:SS``).
|
|
100
|
-
- **
|
|
101
|
-
- **Running At:** Timestamp when the run started running.
|
|
102
|
-
- **Finished At:** Timestamp when the run finished.
|
|
98
|
+
- **Status Changed @:** Timestamp of the most recent status change.
|
|
103
99
|
|
|
104
100
|
All timestamps follow ISO 8601, UTC and are formatted as ``YYYY-MM-DD HH:MM:SSZ``.
|
|
105
101
|
"""
|
|
@@ -115,7 +111,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
115
111
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
116
112
|
|
|
117
113
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
118
|
-
config, errors, warnings = load_and_validate(
|
|
114
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
119
115
|
config = process_loaded_project_config(config, errors, warnings)
|
|
120
116
|
federation, federation_config = validate_federation_in_project_config(
|
|
121
117
|
federation, config, federation_config_overrides
|
|
@@ -143,7 +139,10 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
143
139
|
if output_format == CliOutputFormat.JSON:
|
|
144
140
|
Console().print_json(_to_json(formatted_runs))
|
|
145
141
|
else:
|
|
146
|
-
|
|
142
|
+
if run_id is not None:
|
|
143
|
+
Console().print(_to_detail_table(formatted_runs[0]))
|
|
144
|
+
else:
|
|
145
|
+
Console().print(_to_table(formatted_runs))
|
|
147
146
|
finally:
|
|
148
147
|
if channel:
|
|
149
148
|
channel.close()
|
|
@@ -157,6 +156,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
157
156
|
f"{err}",
|
|
158
157
|
fg=typer.colors.RED,
|
|
159
158
|
bold=True,
|
|
159
|
+
err=True,
|
|
160
160
|
)
|
|
161
161
|
finally:
|
|
162
162
|
if suppress_output:
|
|
@@ -164,156 +164,237 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
164
164
|
captured_output.close()
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
def
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
def _format_datetime(dt: Optional[datetime]) -> str:
|
|
171
|
-
return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
|
|
172
|
-
|
|
173
|
-
run_list: list[_RunListType] = []
|
|
174
|
-
|
|
175
|
-
# Add rows
|
|
176
|
-
for run in sorted(
|
|
177
|
-
run_dict.values(), key=lambda x: datetime.fromisoformat(x.pending_at)
|
|
178
|
-
):
|
|
179
|
-
# Combine status and sub-status into a single string
|
|
180
|
-
if run.status.sub_status == "":
|
|
181
|
-
status_text = run.status.status
|
|
182
|
-
else:
|
|
183
|
-
status_text = f"{run.status.status}:{run.status.sub_status}"
|
|
184
|
-
|
|
185
|
-
# Convert isoformat to datetime
|
|
186
|
-
pending_at = datetime.fromisoformat(run.pending_at) if run.pending_at else None
|
|
187
|
-
running_at = datetime.fromisoformat(run.running_at) if run.running_at else None
|
|
188
|
-
finished_at = (
|
|
189
|
-
datetime.fromisoformat(run.finished_at) if run.finished_at else None
|
|
190
|
-
)
|
|
167
|
+
def _get_status_style(status_text: str) -> str:
|
|
168
|
+
"""Determine the display style/color for a status.
|
|
191
169
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
end_time = finished_at
|
|
197
|
-
else:
|
|
198
|
-
end_time = datetime.fromisoformat(now_isoformat)
|
|
199
|
-
elapsed_time = end_time - running_at
|
|
200
|
-
|
|
201
|
-
run_list.append(
|
|
202
|
-
(
|
|
203
|
-
run.run_id,
|
|
204
|
-
run.fab_id,
|
|
205
|
-
run.fab_version,
|
|
206
|
-
run.fab_hash,
|
|
207
|
-
status_text,
|
|
208
|
-
format_timedelta(elapsed_time),
|
|
209
|
-
_format_datetime(pending_at),
|
|
210
|
-
_format_datetime(running_at),
|
|
211
|
-
_format_datetime(finished_at),
|
|
212
|
-
)
|
|
213
|
-
)
|
|
214
|
-
return run_list
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
status_text : str
|
|
173
|
+
The status text to determine color for.
|
|
215
174
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
str
|
|
178
|
+
Color name for rich console styling (e.g., 'green', 'red', 'blue').
|
|
179
|
+
"""
|
|
180
|
+
status = status_text.lower()
|
|
181
|
+
sub_status = status_text.rsplit(":", maxsplit=1)[-1]
|
|
182
|
+
|
|
183
|
+
if sub_status == SubStatus.COMPLETED: # finished:completed
|
|
184
|
+
return "green"
|
|
185
|
+
if sub_status == SubStatus.FAILED: # finished:failed
|
|
186
|
+
return "red"
|
|
187
|
+
if sub_status == SubStatus.STOPPED: # finished:stopped
|
|
188
|
+
return "yellow"
|
|
189
|
+
if status in (Status.STARTING, Status.RUNNING): # starting, running
|
|
190
|
+
return "blue"
|
|
191
|
+
return "bright_black" # pending
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _to_table(run_list: list[RunRow]) -> Table:
|
|
195
|
+
"""Format the provided run list to a rich Table.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
run_list : list[RunRow]
|
|
200
|
+
List of run information to display.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
Table
|
|
205
|
+
Rich Table object with formatted run information.
|
|
206
|
+
"""
|
|
219
207
|
table = Table(header_style="bold cyan", show_lines=True)
|
|
220
208
|
|
|
221
209
|
# Add columns
|
|
222
|
-
table.add_column(
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
table.add_column(Text("FAB", justify="center"), style="bright_black")
|
|
210
|
+
table.add_column(Text("Run ID", justify="center"), no_wrap=True)
|
|
211
|
+
table.add_column(Text("Federation", justify="center"))
|
|
212
|
+
table.add_column(Text("App", justify="center"))
|
|
226
213
|
table.add_column(Text("Status", justify="center"))
|
|
227
214
|
table.add_column(Text("Elapsed", justify="center"), style="blue")
|
|
228
|
-
table.add_column(Text("
|
|
229
|
-
table.add_column(Text("Running At", justify="center"), style="bright_black")
|
|
230
|
-
table.add_column(Text("Finished At", justify="center"), style="bright_black")
|
|
215
|
+
table.add_column(Text("Status Changed @", justify="center"))
|
|
231
216
|
|
|
232
217
|
for row in run_list:
|
|
233
|
-
(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
finished_at,
|
|
243
|
-
) = row
|
|
244
|
-
# Style the status based on its value
|
|
245
|
-
sub_status = status_text.rsplit(":", maxsplit=1)[-1]
|
|
246
|
-
if sub_status == SubStatus.COMPLETED:
|
|
247
|
-
status_style = "green"
|
|
248
|
-
elif sub_status == SubStatus.FAILED:
|
|
249
|
-
status_style = "red"
|
|
218
|
+
status_style = _get_status_style(row.status_text)
|
|
219
|
+
|
|
220
|
+
# Use the most recent timestamp
|
|
221
|
+
if row.finished_at != "N/A":
|
|
222
|
+
status_changed_at = row.finished_at
|
|
223
|
+
elif row.running_at != "N/A":
|
|
224
|
+
status_changed_at = row.running_at
|
|
225
|
+
elif row.starting_at != "N/A":
|
|
226
|
+
status_changed_at = row.starting_at
|
|
250
227
|
else:
|
|
251
|
-
|
|
228
|
+
status_changed_at = row.pending_at
|
|
252
229
|
|
|
253
230
|
formatted_row = (
|
|
254
|
-
f"[bold]{run_id}[/bold]",
|
|
255
|
-
|
|
256
|
-
f"
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
finished_at,
|
|
231
|
+
f"[bold]{row.run_id}[/bold]",
|
|
232
|
+
row.federation,
|
|
233
|
+
f"@{row.fab_id}=={row.fab_version}",
|
|
234
|
+
f"[{status_style}]{row.status_text}[/{status_style}]",
|
|
235
|
+
humanize_duration(row.elapsed),
|
|
236
|
+
status_changed_at,
|
|
261
237
|
)
|
|
262
238
|
table.add_row(*formatted_row)
|
|
263
239
|
|
|
264
240
|
return table
|
|
265
241
|
|
|
266
242
|
|
|
267
|
-
def
|
|
268
|
-
"""Format run
|
|
243
|
+
def _to_detail_table(run: RunRow) -> Table:
|
|
244
|
+
"""Format a single run's details in a vertical table layout.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
run : RunRow
|
|
249
|
+
The run information to display.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
Table
|
|
254
|
+
Rich Table object with detailed run information in vertical format.
|
|
255
|
+
"""
|
|
256
|
+
status_style = _get_status_style(run.status_text)
|
|
257
|
+
|
|
258
|
+
# Create vertical table with field names on the left
|
|
259
|
+
table = Table(show_header=False, show_lines=False)
|
|
260
|
+
table.add_column("Field", style="bold cyan", no_wrap=True)
|
|
261
|
+
table.add_column("Value")
|
|
262
|
+
|
|
263
|
+
# Add rows with all details
|
|
264
|
+
table.add_row("Run ID", f"[bold]{run.run_id}[/bold]")
|
|
265
|
+
table.add_row("Federation", run.federation)
|
|
266
|
+
table.add_row("App", f"@{run.fab_id}=={run.fab_version}")
|
|
267
|
+
table.add_row("FAB Hash", f"{run.fab_hash[:8]}...{run.fab_hash[-8:]}")
|
|
268
|
+
table.add_row("Status", f"[{status_style}]{run.status_text}[/{status_style}]")
|
|
269
|
+
table.add_row("Elapsed", f"[blue]{humanize_duration(run.elapsed)}[/blue]")
|
|
270
|
+
table.add_row("Pending At", run.pending_at)
|
|
271
|
+
table.add_row("Starting At", run.starting_at)
|
|
272
|
+
table.add_row("Running At", run.running_at)
|
|
273
|
+
table.add_row("Finished At", run.finished_at)
|
|
274
|
+
table.add_row(
|
|
275
|
+
"Network traffic (inbound)",
|
|
276
|
+
f"[blue]{humanize_bytes(run.network_traffic_inbound)}[/blue]",
|
|
277
|
+
)
|
|
278
|
+
table.add_row(
|
|
279
|
+
"Network traffic (outbound)",
|
|
280
|
+
f"[blue]{humanize_bytes(run.network_traffic_outbound)}[/blue]",
|
|
281
|
+
)
|
|
282
|
+
table.add_row(
|
|
283
|
+
"Network Traffic (total)",
|
|
284
|
+
"[blue]"
|
|
285
|
+
f"{humanize_bytes(run.network_traffic_inbound + run.network_traffic_outbound)}"
|
|
286
|
+
"[/blue]",
|
|
287
|
+
)
|
|
288
|
+
table.add_row(
|
|
289
|
+
"Compute Time (ServerApp)",
|
|
290
|
+
f"[blue]{humanize_duration(run.compute_time_serverapp)}[/blue]",
|
|
291
|
+
)
|
|
292
|
+
table.add_row(
|
|
293
|
+
"Compute Time (ClientApp)",
|
|
294
|
+
f"[blue]{humanize_duration(run.compute_time_clientapp)}[/blue]",
|
|
295
|
+
)
|
|
296
|
+
table.add_row(
|
|
297
|
+
"Compute Time (total)",
|
|
298
|
+
"[blue]"
|
|
299
|
+
f"{humanize_duration(run.compute_time_serverapp + run.compute_time_clientapp)}"
|
|
300
|
+
"[/blue]",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return table
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _to_json(run_list: list[RunRow]) -> str:
|
|
307
|
+
"""Format run status list to a JSON formatted string.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
run_list : list[RunRow]
|
|
312
|
+
List of run information to serialize.
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
str
|
|
317
|
+
JSON string containing formatted run information.
|
|
318
|
+
"""
|
|
269
319
|
runs_list = []
|
|
270
320
|
for row in run_list:
|
|
271
|
-
(
|
|
272
|
-
run_id,
|
|
273
|
-
fab_id,
|
|
274
|
-
fab_version,
|
|
275
|
-
fab_hash,
|
|
276
|
-
status_text,
|
|
277
|
-
elapsed,
|
|
278
|
-
created_at,
|
|
279
|
-
running_at,
|
|
280
|
-
finished_at,
|
|
281
|
-
) = row
|
|
282
321
|
runs_list.append(
|
|
283
322
|
{
|
|
284
|
-
"run-id": run_id,
|
|
285
|
-
"
|
|
286
|
-
"fab-
|
|
287
|
-
"fab-
|
|
288
|
-
"fab-
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
323
|
+
"run-id": f"{row.run_id}",
|
|
324
|
+
"federation": row.federation,
|
|
325
|
+
"fab-id": row.fab_id,
|
|
326
|
+
"fab-name": row.fab_id.split("/")[-1],
|
|
327
|
+
"fab-version": row.fab_version,
|
|
328
|
+
"fab-hash": row.fab_hash[:8],
|
|
329
|
+
"status": row.status_text,
|
|
330
|
+
"elapsed": row.elapsed,
|
|
331
|
+
"pending-at": row.pending_at,
|
|
332
|
+
"starting-at": row.starting_at,
|
|
333
|
+
"running-at": row.running_at,
|
|
334
|
+
"finished-at": row.finished_at,
|
|
335
|
+
"network-traffic": {
|
|
336
|
+
"inbound-bytes": row.network_traffic_inbound,
|
|
337
|
+
"outbound-bytes": row.network_traffic_outbound,
|
|
338
|
+
"total-bytes": row.network_traffic_inbound
|
|
339
|
+
+ row.network_traffic_outbound,
|
|
340
|
+
},
|
|
341
|
+
"compute-time": {
|
|
342
|
+
"serverapp-seconds": row.compute_time_serverapp,
|
|
343
|
+
"clientapp-seconds": row.compute_time_clientapp,
|
|
344
|
+
"total-seconds": row.compute_time_serverapp
|
|
345
|
+
+ row.compute_time_clientapp,
|
|
346
|
+
},
|
|
294
347
|
}
|
|
295
348
|
)
|
|
296
349
|
|
|
297
350
|
return json.dumps({"success": True, "runs": runs_list})
|
|
298
351
|
|
|
299
352
|
|
|
300
|
-
def _list_runs(stub: ControlStub) -> list[
|
|
301
|
-
"""List all runs.
|
|
353
|
+
def _list_runs(stub: ControlStub) -> list[RunRow]:
|
|
354
|
+
"""List all runs.
|
|
355
|
+
|
|
356
|
+
Parameters
|
|
357
|
+
----------
|
|
358
|
+
stub : ControlStub
|
|
359
|
+
The gRPC stub for Control API communication.
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
list[RunRow]
|
|
364
|
+
List of formatted run information for all runs.
|
|
365
|
+
"""
|
|
302
366
|
with flwr_cli_grpc_exc_handler():
|
|
303
367
|
res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
|
|
304
|
-
|
|
368
|
+
runs = [run_from_proto(proto) for proto in res.run_dict.values()]
|
|
369
|
+
|
|
370
|
+
return format_runs(runs, res.now)
|
|
305
371
|
|
|
306
|
-
return _format_runs(run_dict, res.now)
|
|
307
372
|
|
|
373
|
+
def _display_one_run(stub: ControlStub, run_id: int) -> list[RunRow]:
|
|
374
|
+
"""Display information about a specific run.
|
|
308
375
|
|
|
309
|
-
|
|
310
|
-
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
stub : ControlStub
|
|
379
|
+
The gRPC stub for Control API communication.
|
|
380
|
+
run_id : int
|
|
381
|
+
The unique identifier of the run to display.
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
list[RunRow]
|
|
386
|
+
List containing the formatted run information (single item).
|
|
387
|
+
|
|
388
|
+
Raises
|
|
389
|
+
------
|
|
390
|
+
ValueError
|
|
391
|
+
If the run_id is not found.
|
|
392
|
+
"""
|
|
311
393
|
with flwr_cli_grpc_exc_handler():
|
|
312
394
|
res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
|
|
313
395
|
if not res.run_dict:
|
|
314
396
|
# This won't be reached as an gRPC error is raised if run_id is invalid
|
|
315
397
|
raise ValueError(f"Run ID {run_id} not found")
|
|
316
398
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return _format_runs(run_dict, res.now)
|
|
399
|
+
runs = [run_from_proto(proto) for proto in res.run_dict.values()]
|
|
400
|
+
return format_runs(runs, res.now)
|