flwr 1.22.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 +34 -1
- 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/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- 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 +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- 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 +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → 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/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- 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 +39 -10
- 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 +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- 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/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- 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 +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- 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 +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- 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 +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- 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 +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- 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 +173 -127
- 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 +19 -8
- 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 +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- 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 +12 -10
- 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 +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- 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 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- 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 +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- 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_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- 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 +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
|
@@ -14,10 +14,10 @@ description = ""
|
|
|
14
14
|
license = "Apache-2.0"
|
|
15
15
|
# Dependencies for your Flower App
|
|
16
16
|
dependencies = [
|
|
17
|
-
"flwr[simulation]>=1.
|
|
17
|
+
"flwr[simulation]>=1.24.0",
|
|
18
18
|
"flwr-datasets[vision]>=0.5.0",
|
|
19
|
-
"torch
|
|
20
|
-
"torchvision
|
|
19
|
+
"torch>=2.7.1",
|
|
20
|
+
"torchvision>=0.22.1",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[tool.hatch.build.targets.wheel]
|
|
@@ -14,9 +14,9 @@ description = ""
|
|
|
14
14
|
license = "Apache-2.0"
|
|
15
15
|
# Dependencies for your Flower App
|
|
16
16
|
dependencies = [
|
|
17
|
-
"flwr[simulation]>=1.
|
|
17
|
+
"flwr[simulation]>=1.24.0",
|
|
18
18
|
"flwr-datasets[vision]>=0.5.0",
|
|
19
|
-
"tensorflow>=2.
|
|
19
|
+
"tensorflow>=2.18.0",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
[tool.hatch.build.targets.wheel]
|
flwr/cli/pull.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
|
|
|
@@ -34,7 +34,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
34
34
|
)
|
|
35
35
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
36
36
|
|
|
37
|
-
from .utils import flwr_cli_grpc_exc_handler, init_channel,
|
|
37
|
+
from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def pull( # pylint: disable=R0914
|
|
@@ -50,22 +50,26 @@ def pull( # 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."),
|
|
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,
|
|
61
61
|
),
|
|
62
62
|
] = None,
|
|
63
63
|
) -> None:
|
|
64
|
-
"""Pull artifacts from a Flower run.
|
|
64
|
+
"""Pull artifacts from a Flower run.
|
|
65
|
+
|
|
66
|
+
Retrieve a download URL for artifacts generated during a completed Flower run. The
|
|
67
|
+
artifacts can then be downloaded from the provided URL.
|
|
68
|
+
"""
|
|
65
69
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
66
70
|
|
|
67
71
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
68
|
-
config, errors, warnings = load_and_validate(
|
|
72
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
69
73
|
config = process_loaded_project_config(config, errors, warnings)
|
|
70
74
|
federation, federation_config = validate_federation_in_project_config(
|
|
71
75
|
federation, config, federation_config_overrides
|
|
@@ -74,7 +78,7 @@ def pull( # pylint: disable=R0914
|
|
|
74
78
|
channel = None
|
|
75
79
|
try:
|
|
76
80
|
|
|
77
|
-
auth_plugin =
|
|
81
|
+
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
78
82
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
79
83
|
stub = ControlStub(channel)
|
|
80
84
|
with flwr_cli_grpc_exc_handler():
|
|
@@ -88,6 +92,7 @@ def pull( # pylint: disable=R0914
|
|
|
88
92
|
"obtained.",
|
|
89
93
|
fg=typer.colors.RED,
|
|
90
94
|
bold=True,
|
|
95
|
+
err=True,
|
|
91
96
|
)
|
|
92
97
|
raise typer.Exit(code=1)
|
|
93
98
|
|
flwr/cli/run/run.py
CHANGED
|
@@ -15,16 +15,18 @@
|
|
|
15
15
|
"""Flower command line interface `run` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import hashlib
|
|
18
19
|
import io
|
|
19
20
|
import json
|
|
20
21
|
import subprocess
|
|
21
22
|
from pathlib import Path
|
|
22
|
-
from typing import Annotated, Any,
|
|
23
|
+
from typing import Annotated, Any, cast
|
|
23
24
|
|
|
24
25
|
import typer
|
|
25
26
|
from rich.console import Console
|
|
26
27
|
|
|
27
|
-
from flwr.cli.build import
|
|
28
|
+
from flwr.cli.build import build_fab_from_disk, get_fab_filename
|
|
29
|
+
from flwr.cli.config_utils import load as load_toml
|
|
28
30
|
from flwr.cli.config_utils import (
|
|
29
31
|
load_and_validate,
|
|
30
32
|
process_loaded_project_config,
|
|
@@ -37,31 +39,37 @@ from flwr.common.config import (
|
|
|
37
39
|
parse_config_args,
|
|
38
40
|
user_config_to_configrecord,
|
|
39
41
|
)
|
|
40
|
-
from flwr.common.constant import CliOutputFormat
|
|
42
|
+
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
|
41
43
|
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
42
44
|
from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_to_proto
|
|
43
45
|
from flwr.common.typing import Fab
|
|
44
46
|
from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
|
|
45
47
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
48
|
+
from flwr.supercore.constant import NOOP_FEDERATION
|
|
46
49
|
|
|
47
50
|
from ..log import start_stream
|
|
48
|
-
from ..utils import
|
|
51
|
+
from ..utils import (
|
|
52
|
+
flwr_cli_grpc_exc_handler,
|
|
53
|
+
init_channel,
|
|
54
|
+
load_cli_auth_plugin,
|
|
55
|
+
parse_app_spec,
|
|
56
|
+
)
|
|
49
57
|
|
|
50
58
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
|
51
59
|
|
|
52
60
|
|
|
53
|
-
# pylint: disable-next=too-many-locals, R0913, R0917
|
|
61
|
+
# pylint: disable-next=too-many-locals, too-many-branches, R0913, R0917
|
|
54
62
|
def run(
|
|
55
63
|
app: Annotated[
|
|
56
64
|
Path,
|
|
57
65
|
typer.Argument(help="Path of the Flower App to run."),
|
|
58
66
|
] = Path("."),
|
|
59
67
|
federation: Annotated[
|
|
60
|
-
|
|
68
|
+
str | None,
|
|
61
69
|
typer.Argument(help="Name of the federation to run the app on."),
|
|
62
70
|
] = None,
|
|
63
71
|
run_config_overrides: Annotated[
|
|
64
|
-
|
|
72
|
+
list[str] | None,
|
|
65
73
|
typer.Option(
|
|
66
74
|
"--run-config",
|
|
67
75
|
"-c",
|
|
@@ -69,7 +77,7 @@ def run(
|
|
|
69
77
|
),
|
|
70
78
|
] = None,
|
|
71
79
|
federation_config_overrides: Annotated[
|
|
72
|
-
|
|
80
|
+
list[str] | None,
|
|
73
81
|
typer.Option(
|
|
74
82
|
"--federation-config",
|
|
75
83
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
@@ -98,11 +106,25 @@ def run(
|
|
|
98
106
|
try:
|
|
99
107
|
if suppress_output:
|
|
100
108
|
redirect_output(captured_output)
|
|
109
|
+
|
|
110
|
+
# Determine if app is remote
|
|
111
|
+
app_spec = None
|
|
112
|
+
if (app_str := str(app)).startswith("@"):
|
|
113
|
+
# Validate app version and ID format
|
|
114
|
+
_ = parse_app_spec(app_str)
|
|
115
|
+
app_spec = app_str
|
|
116
|
+
is_remote_app = app_spec is not None
|
|
117
|
+
|
|
101
118
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
102
119
|
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
# Disable the validation for remote apps
|
|
121
|
+
pyproject_path = app / "pyproject.toml" if not is_remote_app else None
|
|
122
|
+
# `./pyproject.toml` will be loaded when `pyproject_path` is None
|
|
123
|
+
config, errors, warnings = load_and_validate(
|
|
124
|
+
pyproject_path, check_module=not is_remote_app
|
|
125
|
+
)
|
|
105
126
|
config = process_loaded_project_config(config, errors, warnings)
|
|
127
|
+
|
|
106
128
|
federation, federation_config = validate_federation_in_project_config(
|
|
107
129
|
federation, config, federation_config_overrides
|
|
108
130
|
)
|
|
@@ -115,6 +137,7 @@ def run(
|
|
|
115
137
|
run_config_overrides,
|
|
116
138
|
stream,
|
|
117
139
|
output_format,
|
|
140
|
+
app_spec,
|
|
118
141
|
)
|
|
119
142
|
else:
|
|
120
143
|
_run_without_control_api(
|
|
@@ -130,6 +153,7 @@ def run(
|
|
|
130
153
|
f"{err}",
|
|
131
154
|
fg=typer.colors.RED,
|
|
132
155
|
bold=True,
|
|
156
|
+
err=True,
|
|
133
157
|
)
|
|
134
158
|
finally:
|
|
135
159
|
if suppress_output:
|
|
@@ -142,20 +166,32 @@ def _run_with_control_api(
|
|
|
142
166
|
app: Path,
|
|
143
167
|
federation: str,
|
|
144
168
|
federation_config: dict[str, Any],
|
|
145
|
-
config_overrides:
|
|
169
|
+
config_overrides: list[str] | None,
|
|
146
170
|
stream: bool,
|
|
147
171
|
output_format: str,
|
|
172
|
+
app_spec: str | None,
|
|
148
173
|
) -> None:
|
|
149
174
|
channel = None
|
|
175
|
+
is_remote_app = app_spec is not None
|
|
150
176
|
try:
|
|
151
|
-
auth_plugin =
|
|
177
|
+
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
152
178
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
153
179
|
stub = ControlStub(channel)
|
|
154
180
|
|
|
155
|
-
|
|
156
|
-
|
|
181
|
+
# Build FAB if local app
|
|
182
|
+
if not is_remote_app:
|
|
183
|
+
fab_bytes = build_fab_from_disk(app)
|
|
184
|
+
fab_hash = hashlib.sha256(fab_bytes).hexdigest()
|
|
185
|
+
config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
|
|
186
|
+
fab_id, fab_version = get_metadata_from_config(config)
|
|
187
|
+
fab = Fab(fab_hash, fab_bytes, {})
|
|
188
|
+
# Skip FAB build if remote app
|
|
189
|
+
else:
|
|
190
|
+
# Use empty values for FAB
|
|
191
|
+
fab_id = fab_version = fab_hash = ""
|
|
192
|
+
fab = Fab(fab_hash, b"", {})
|
|
157
193
|
|
|
158
|
-
|
|
194
|
+
real_federation: str = federation_config.get("federation", NOOP_FEDERATION)
|
|
159
195
|
|
|
160
196
|
# Construct a `ConfigRecord` out of a flattened `UserConfig`
|
|
161
197
|
fed_config = flatten_dict(federation_config.get("options", {}))
|
|
@@ -164,7 +200,9 @@ def _run_with_control_api(
|
|
|
164
200
|
req = StartRunRequest(
|
|
165
201
|
fab=fab_to_proto(fab),
|
|
166
202
|
override_config=user_config_to_proto(parse_config_args(config_overrides)),
|
|
203
|
+
federation=real_federation,
|
|
167
204
|
federation_options=config_record_to_proto(c_record),
|
|
205
|
+
app_spec=app_spec or "",
|
|
168
206
|
)
|
|
169
207
|
with flwr_cli_grpc_exc_handler():
|
|
170
208
|
res = stub.StartRun(req)
|
|
@@ -174,23 +212,35 @@ def _run_with_control_api(
|
|
|
174
212
|
f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN
|
|
175
213
|
)
|
|
176
214
|
else:
|
|
177
|
-
|
|
215
|
+
if is_remote_app:
|
|
216
|
+
typer.secho(
|
|
217
|
+
"❌ Failed to start run. Please check that the provided "
|
|
218
|
+
"app identifier (@account_name/app_name) is correct.",
|
|
219
|
+
fg=typer.colors.RED,
|
|
220
|
+
err=True,
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
typer.secho("❌ Failed to start run", fg=typer.colors.RED, err=True)
|
|
178
224
|
raise typer.Exit(code=1)
|
|
179
225
|
|
|
180
226
|
if output_format == CliOutputFormat.JSON:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
227
|
+
# Only include FAB metadata if we actually built a local FAB
|
|
228
|
+
payload: dict[str, Any] = {
|
|
229
|
+
"success": res.HasField("run_id"),
|
|
230
|
+
"run-id": res.run_id if res.HasField("run_id") else None,
|
|
231
|
+
}
|
|
232
|
+
if not is_remote_app:
|
|
233
|
+
payload.update(
|
|
234
|
+
{
|
|
235
|
+
"fab-id": fab_id,
|
|
236
|
+
"fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
|
|
237
|
+
"fab-version": fab_version,
|
|
238
|
+
"fab-hash": fab_hash[:8],
|
|
239
|
+
"fab-filename": get_fab_filename(config, fab_hash),
|
|
240
|
+
}
|
|
241
|
+
)
|
|
192
242
|
restore_output()
|
|
193
|
-
Console().print_json(
|
|
243
|
+
Console().print_json(json.dumps(payload))
|
|
194
244
|
|
|
195
245
|
if stream:
|
|
196
246
|
start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
|
|
@@ -200,14 +250,14 @@ def _run_with_control_api(
|
|
|
200
250
|
|
|
201
251
|
|
|
202
252
|
def _run_without_control_api(
|
|
203
|
-
app:
|
|
253
|
+
app: Path | None,
|
|
204
254
|
federation_config: dict[str, Any],
|
|
205
|
-
config_overrides:
|
|
255
|
+
config_overrides: list[str] | None,
|
|
206
256
|
federation: str,
|
|
207
257
|
) -> None:
|
|
208
258
|
try:
|
|
209
259
|
num_supernodes = federation_config["options"]["num-supernodes"]
|
|
210
|
-
verbose:
|
|
260
|
+
verbose: bool | None = federation_config["options"].get("verbose")
|
|
211
261
|
backend_cfg = federation_config["options"].get("backend", {})
|
|
212
262
|
except KeyError as err:
|
|
213
263
|
typer.secho(
|
|
@@ -218,6 +268,7 @@ def _run_without_control_api(
|
|
|
218
268
|
"options.num-supernodes = 10\n",
|
|
219
269
|
fg=typer.colors.RED,
|
|
220
270
|
bold=True,
|
|
271
|
+
err=True,
|
|
221
272
|
)
|
|
222
273
|
raise typer.Exit(code=1) from err
|
|
223
274
|
|
flwr/cli/run_utils.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
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 format_timedelta, 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 : str
|
|
44
|
+
The formatted elapsed time.
|
|
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
|
+
"""
|
|
54
|
+
|
|
55
|
+
run_id: int
|
|
56
|
+
federation: str
|
|
57
|
+
fab_id: str
|
|
58
|
+
fab_version: str
|
|
59
|
+
fab_hash: str
|
|
60
|
+
status_text: str
|
|
61
|
+
elapsed: str
|
|
62
|
+
pending_at: str
|
|
63
|
+
starting_at: str
|
|
64
|
+
running_at: str
|
|
65
|
+
finished_at: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_runs(runs: list[Run], now_isoformat: str) -> list[RunRow]:
|
|
69
|
+
"""Format runs to a list of RunRow objects.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
runs : list[Run]
|
|
74
|
+
List of Run objects to format.
|
|
75
|
+
now_isoformat : str
|
|
76
|
+
Current timestamp in ISO format for calculating elapsed time.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
list[RunRow]
|
|
81
|
+
List of formatted RunRow objects sorted by pending_at timestamp.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def _format_datetime(dt: datetime | None) -> str:
|
|
85
|
+
return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
|
|
86
|
+
|
|
87
|
+
run_list: list[RunRow] = []
|
|
88
|
+
|
|
89
|
+
# Add rows
|
|
90
|
+
for run in sorted(runs, key=lambda x: datetime.fromisoformat(x.pending_at)):
|
|
91
|
+
# Combine status and sub-status into a single string
|
|
92
|
+
if run.status.sub_status == "":
|
|
93
|
+
status_text = run.status.status
|
|
94
|
+
else:
|
|
95
|
+
status_text = f"{run.status.status}:{run.status.sub_status}"
|
|
96
|
+
|
|
97
|
+
# Convert isoformat to datetime
|
|
98
|
+
pending_at = datetime.fromisoformat(run.pending_at) if run.pending_at else None
|
|
99
|
+
starting_at = (
|
|
100
|
+
datetime.fromisoformat(run.starting_at) if run.starting_at else None
|
|
101
|
+
)
|
|
102
|
+
running_at = datetime.fromisoformat(run.running_at) if run.running_at else None
|
|
103
|
+
finished_at = (
|
|
104
|
+
datetime.fromisoformat(run.finished_at) if run.finished_at else None
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Calculate elapsed time
|
|
108
|
+
elapsed_time = timedelta()
|
|
109
|
+
if running_at:
|
|
110
|
+
if finished_at:
|
|
111
|
+
end_time = finished_at
|
|
112
|
+
else:
|
|
113
|
+
end_time = datetime.fromisoformat(now_isoformat)
|
|
114
|
+
elapsed_time = end_time - running_at
|
|
115
|
+
|
|
116
|
+
row = RunRow(
|
|
117
|
+
run_id=run.run_id,
|
|
118
|
+
federation=run.federation,
|
|
119
|
+
fab_id=run.fab_id,
|
|
120
|
+
fab_version=run.fab_version,
|
|
121
|
+
fab_hash=run.fab_hash,
|
|
122
|
+
status_text=status_text,
|
|
123
|
+
elapsed=format_timedelta(elapsed_time),
|
|
124
|
+
pending_at=_format_datetime(pending_at),
|
|
125
|
+
starting_at=_format_datetime(starting_at),
|
|
126
|
+
running_at=_format_datetime(running_at),
|
|
127
|
+
finished_at=_format_datetime(finished_at),
|
|
128
|
+
)
|
|
129
|
+
run_list.append(row)
|
|
130
|
+
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
|
|
@@ -38,7 +38,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
38
38
|
)
|
|
39
39
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
40
40
|
|
|
41
|
-
from .utils import flwr_cli_grpc_exc_handler, init_channel,
|
|
41
|
+
from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def stop( # pylint: disable=R0914
|
|
@@ -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
|
|
@@ -89,7 +93,7 @@ def stop( # pylint: disable=R0914
|
|
|
89
93
|
exit_if_no_address(federation_config, "stop")
|
|
90
94
|
channel = None
|
|
91
95
|
try:
|
|
92
|
-
auth_plugin =
|
|
96
|
+
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
93
97
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
94
98
|
stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
|
|
95
99
|
|
|
@@ -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:
|
|
@@ -139,4 +155,6 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
|
|
|
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
|
+
)
|