flwr 1.23.0__py3-none-any.whl → 1.24.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/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -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/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +170 -130
- flwr/cli/new/new.py +33 -50
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +77 -30
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +25 -7
- flwr/cli/supernode/ls.py +16 -8
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +376 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +6 -7
- flwr/client/grpc_rere_client/connection.py +10 -11
- 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 +12 -14
- 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 +5 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +52 -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 +14 -4
- flwr/proto/fleet_pb2.pyi +137 -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 +149 -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 +38 -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 +34 -28
- 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 +115 -150
- flwr/server/superlink/linkstate/linkstate.py +59 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +447 -438
- 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 +31 -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/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/superlink/federation/__init__.py +24 -0
- 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 +5 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +102 -18
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +158 -42
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- 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.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.24.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,18 @@ 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
|
|
46
43
|
|
|
44
|
+
from .run_utils import RunRow, format_runs
|
|
47
45
|
from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
48
46
|
|
|
49
|
-
_RunListType = tuple[int, str, str, str, str, str, str, str, str]
|
|
50
|
-
|
|
51
47
|
|
|
52
48
|
def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
53
49
|
ctx: typer.Context,
|
|
@@ -56,11 +52,11 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
56
52
|
typer.Argument(help="Path of the Flower project"),
|
|
57
53
|
] = Path("."),
|
|
58
54
|
federation: Annotated[
|
|
59
|
-
|
|
55
|
+
str | None,
|
|
60
56
|
typer.Argument(help="Name of the federation"),
|
|
61
57
|
] = None,
|
|
62
58
|
federation_config_overrides: Annotated[
|
|
63
|
-
|
|
59
|
+
list[str] | None,
|
|
64
60
|
typer.Option(
|
|
65
61
|
"--federation-config",
|
|
66
62
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -74,7 +70,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
74
70
|
),
|
|
75
71
|
] = False,
|
|
76
72
|
run_id: Annotated[
|
|
77
|
-
|
|
73
|
+
int | None,
|
|
78
74
|
typer.Option(
|
|
79
75
|
"--run-id",
|
|
80
76
|
help="Specific run ID to display",
|
|
@@ -94,12 +90,11 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
94
90
|
The following details are displayed:
|
|
95
91
|
|
|
96
92
|
- **Run ID:** Unique identifier for the run.
|
|
97
|
-
- **
|
|
93
|
+
- **Federation:** The federation to which the run belongs.
|
|
94
|
+
- **App:** The App associated with the run (``<APP_ID>==<APP_VERSION>``).
|
|
98
95
|
- **Status:** Current status of the run (pending, starting, running, finished).
|
|
99
96
|
- **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.
|
|
97
|
+
- **Status Changed @:** Timestamp of the most recent status change.
|
|
103
98
|
|
|
104
99
|
All timestamps follow ISO 8601, UTC and are formatted as ``YYYY-MM-DD HH:MM:SSZ``.
|
|
105
100
|
"""
|
|
@@ -115,7 +110,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
115
110
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
116
111
|
|
|
117
112
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
118
|
-
config, errors, warnings = load_and_validate(
|
|
113
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
119
114
|
config = process_loaded_project_config(config, errors, warnings)
|
|
120
115
|
federation, federation_config = validate_federation_in_project_config(
|
|
121
116
|
federation, config, federation_config_overrides
|
|
@@ -143,7 +138,10 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
143
138
|
if output_format == CliOutputFormat.JSON:
|
|
144
139
|
Console().print_json(_to_json(formatted_runs))
|
|
145
140
|
else:
|
|
146
|
-
|
|
141
|
+
if run_id is not None:
|
|
142
|
+
Console().print(_to_detail_table(formatted_runs[0]))
|
|
143
|
+
else:
|
|
144
|
+
Console().print(_to_table(formatted_runs))
|
|
147
145
|
finally:
|
|
148
146
|
if channel:
|
|
149
147
|
channel.close()
|
|
@@ -157,6 +155,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
157
155
|
f"{err}",
|
|
158
156
|
fg=typer.colors.RED,
|
|
159
157
|
bold=True,
|
|
158
|
+
err=True,
|
|
160
159
|
)
|
|
161
160
|
finally:
|
|
162
161
|
if suppress_output:
|
|
@@ -164,156 +163,197 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
164
163
|
captured_output.close()
|
|
165
164
|
|
|
166
165
|
|
|
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
|
-
)
|
|
191
|
-
|
|
192
|
-
# Calculate elapsed time
|
|
193
|
-
elapsed_time = timedelta()
|
|
194
|
-
if running_at:
|
|
195
|
-
if finished_at:
|
|
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
|
|
166
|
+
def _get_status_style(status_text: str) -> str:
|
|
167
|
+
"""Determine the display style/color for a status.
|
|
215
168
|
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
status_text : str
|
|
172
|
+
The status text to determine color for.
|
|
216
173
|
|
|
217
|
-
|
|
218
|
-
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
str
|
|
177
|
+
Color name for rich console styling (e.g., 'green', 'red', 'blue').
|
|
178
|
+
"""
|
|
179
|
+
status = status_text.lower()
|
|
180
|
+
sub_status = status_text.rsplit(":", maxsplit=1)[-1]
|
|
181
|
+
|
|
182
|
+
if sub_status == SubStatus.COMPLETED: # finished:completed
|
|
183
|
+
return "green"
|
|
184
|
+
if sub_status == SubStatus.FAILED: # finished:failed
|
|
185
|
+
return "red"
|
|
186
|
+
if sub_status == SubStatus.STOPPED: # finished:stopped
|
|
187
|
+
return "yellow"
|
|
188
|
+
if status in (Status.STARTING, Status.RUNNING): # starting, running
|
|
189
|
+
return "blue"
|
|
190
|
+
return "bright_black" # pending
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _to_table(run_list: list[RunRow]) -> Table:
|
|
194
|
+
"""Format the provided run list to a rich Table.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
run_list : list[RunRow]
|
|
199
|
+
List of run information to display.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
Table
|
|
204
|
+
Rich Table object with formatted run information.
|
|
205
|
+
"""
|
|
219
206
|
table = Table(header_style="bold cyan", show_lines=True)
|
|
220
207
|
|
|
221
208
|
# Add columns
|
|
222
|
-
table.add_column(
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
table.add_column(Text("FAB", justify="center"), style="bright_black")
|
|
209
|
+
table.add_column(Text("Run ID", justify="center"), no_wrap=True)
|
|
210
|
+
table.add_column(Text("Federation", justify="center"))
|
|
211
|
+
table.add_column(Text("App", justify="center"))
|
|
226
212
|
table.add_column(Text("Status", justify="center"))
|
|
227
213
|
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")
|
|
214
|
+
table.add_column(Text("Status Changed @", justify="center"))
|
|
231
215
|
|
|
232
216
|
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"
|
|
217
|
+
status_style = _get_status_style(row.status_text)
|
|
218
|
+
|
|
219
|
+
# Use the most recent timestamp
|
|
220
|
+
if row.finished_at != "N/A":
|
|
221
|
+
status_changed_at = row.finished_at
|
|
222
|
+
elif row.running_at != "N/A":
|
|
223
|
+
status_changed_at = row.running_at
|
|
224
|
+
elif row.starting_at != "N/A":
|
|
225
|
+
status_changed_at = row.starting_at
|
|
250
226
|
else:
|
|
251
|
-
|
|
227
|
+
status_changed_at = row.pending_at
|
|
252
228
|
|
|
253
229
|
formatted_row = (
|
|
254
|
-
f"[bold]{run_id}[/bold]",
|
|
255
|
-
|
|
256
|
-
f"
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
finished_at,
|
|
230
|
+
f"[bold]{row.run_id}[/bold]",
|
|
231
|
+
row.federation,
|
|
232
|
+
f"@{row.fab_id}=={row.fab_version}",
|
|
233
|
+
f"[{status_style}]{row.status_text}[/{status_style}]",
|
|
234
|
+
row.elapsed,
|
|
235
|
+
status_changed_at,
|
|
261
236
|
)
|
|
262
237
|
table.add_row(*formatted_row)
|
|
263
238
|
|
|
264
239
|
return table
|
|
265
240
|
|
|
266
241
|
|
|
267
|
-
def
|
|
268
|
-
"""Format run
|
|
242
|
+
def _to_detail_table(run: RunRow) -> Table:
|
|
243
|
+
"""Format a single run's details in a vertical table layout.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
run : RunRow
|
|
248
|
+
The run information to display.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
Table
|
|
253
|
+
Rich Table object with detailed run information in vertical format.
|
|
254
|
+
"""
|
|
255
|
+
status_style = _get_status_style(run.status_text)
|
|
256
|
+
|
|
257
|
+
# Create vertical table with field names on the left
|
|
258
|
+
table = Table(show_header=False, show_lines=False)
|
|
259
|
+
table.add_column("Field", style="bold cyan", no_wrap=True)
|
|
260
|
+
table.add_column("Value")
|
|
261
|
+
|
|
262
|
+
# Add rows with all details
|
|
263
|
+
table.add_row("Run ID", f"[bold]{run.run_id}[/bold]")
|
|
264
|
+
table.add_row("Federation", run.federation)
|
|
265
|
+
table.add_row("App", f"@{run.fab_id}=={run.fab_version}")
|
|
266
|
+
table.add_row("FAB Hash", f"{run.fab_hash[:8]}...{run.fab_hash[-8:]}")
|
|
267
|
+
table.add_row("Status", f"[{status_style}]{run.status_text}[/{status_style}]")
|
|
268
|
+
table.add_row("Elapsed", f"[blue]{run.elapsed}[/blue]")
|
|
269
|
+
table.add_row("Pending At", run.pending_at)
|
|
270
|
+
table.add_row("Starting At", run.starting_at)
|
|
271
|
+
table.add_row("Running At", run.running_at)
|
|
272
|
+
table.add_row("Finished At", run.finished_at)
|
|
273
|
+
|
|
274
|
+
return table
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _to_json(run_list: list[RunRow]) -> str:
|
|
278
|
+
"""Format run status list to a JSON formatted string.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
run_list : list[RunRow]
|
|
283
|
+
List of run information to serialize.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
str
|
|
288
|
+
JSON string containing formatted run information.
|
|
289
|
+
"""
|
|
269
290
|
runs_list = []
|
|
270
291
|
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
292
|
runs_list.append(
|
|
283
293
|
{
|
|
284
|
-
"run-id": run_id,
|
|
285
|
-
"
|
|
286
|
-
"fab-
|
|
287
|
-
"fab-
|
|
288
|
-
"fab-
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
+
"run-id": row.run_id,
|
|
295
|
+
"federation": row.federation,
|
|
296
|
+
"fab-id": row.fab_id,
|
|
297
|
+
"fab-name": row.fab_id.split("/")[-1],
|
|
298
|
+
"fab-version": row.fab_version,
|
|
299
|
+
"fab-hash": row.fab_hash[:8],
|
|
300
|
+
"status": row.status_text,
|
|
301
|
+
"elapsed": row.elapsed,
|
|
302
|
+
"pending-at": row.pending_at,
|
|
303
|
+
"starting-at": row.starting_at,
|
|
304
|
+
"running-at": row.running_at,
|
|
305
|
+
"finished-at": row.finished_at,
|
|
294
306
|
}
|
|
295
307
|
)
|
|
296
308
|
|
|
297
309
|
return json.dumps({"success": True, "runs": runs_list})
|
|
298
310
|
|
|
299
311
|
|
|
300
|
-
def _list_runs(stub: ControlStub) -> list[
|
|
301
|
-
"""List all runs.
|
|
312
|
+
def _list_runs(stub: ControlStub) -> list[RunRow]:
|
|
313
|
+
"""List all runs.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
stub : ControlStub
|
|
318
|
+
The gRPC stub for Control API communication.
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
list[RunRow]
|
|
323
|
+
List of formatted run information for all runs.
|
|
324
|
+
"""
|
|
302
325
|
with flwr_cli_grpc_exc_handler():
|
|
303
326
|
res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
|
|
304
|
-
|
|
327
|
+
runs = [run_from_proto(proto) for proto in res.run_dict.values()]
|
|
328
|
+
|
|
329
|
+
return format_runs(runs, res.now)
|
|
330
|
+
|
|
305
331
|
|
|
306
|
-
|
|
332
|
+
def _display_one_run(stub: ControlStub, run_id: int) -> list[RunRow]:
|
|
333
|
+
"""Display information about a specific run.
|
|
307
334
|
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
stub : ControlStub
|
|
338
|
+
The gRPC stub for Control API communication.
|
|
339
|
+
run_id : int
|
|
340
|
+
The unique identifier of the run to display.
|
|
308
341
|
|
|
309
|
-
|
|
310
|
-
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
list[RunRow]
|
|
345
|
+
List containing the formatted run information (single item).
|
|
346
|
+
|
|
347
|
+
Raises
|
|
348
|
+
------
|
|
349
|
+
ValueError
|
|
350
|
+
If the run_id is not found.
|
|
351
|
+
"""
|
|
311
352
|
with flwr_cli_grpc_exc_handler():
|
|
312
353
|
res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
|
|
313
354
|
if not res.run_dict:
|
|
314
355
|
# This won't be reached as an gRPC error is raised if run_id is invalid
|
|
315
356
|
raise ValueError(f"Run ID {run_id} not found")
|
|
316
357
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return _format_runs(run_dict, res.now)
|
|
358
|
+
runs = [run_from_proto(proto) for proto in res.run_dict.values()]
|
|
359
|
+
return format_runs(runs, res.now)
|