flwr 1.25.0__py3-none-any.whl → 1.26.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 +1 -1
- flwr/app/__init__.py +4 -1
- flwr/app/message_type.py +29 -0
- flwr/app/metadata.py +5 -2
- flwr/app/user_config.py +19 -0
- flwr/cli/app.py +37 -19
- flwr/cli/app_cmd/publish.py +25 -75
- flwr/cli/app_cmd/review.py +18 -69
- flwr/cli/auth_plugin/auth_plugin.py +5 -10
- flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
- flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
- flwr/cli/build.py +15 -28
- flwr/cli/config/__init__.py +21 -0
- flwr/cli/config/ls.py +71 -0
- flwr/cli/config_migration.py +297 -0
- flwr/cli/config_utils.py +63 -156
- flwr/cli/constant.py +71 -0
- flwr/cli/federation/__init__.py +0 -2
- flwr/cli/federation/ls.py +256 -64
- flwr/cli/flower_config.py +429 -0
- flwr/cli/install.py +23 -62
- flwr/cli/log.py +23 -37
- flwr/cli/login/login.py +29 -63
- flwr/cli/ls.py +28 -58
- flwr/cli/new/new.py +9 -29
- flwr/cli/pull.py +19 -37
- flwr/cli/run/run.py +85 -93
- flwr/cli/run_utils.py +1 -1
- flwr/cli/stop.py +32 -73
- flwr/cli/supernode/ls.py +25 -57
- flwr/cli/supernode/register.py +31 -80
- flwr/cli/supernode/unregister.py +24 -70
- flwr/cli/typing.py +200 -0
- flwr/cli/utils.py +160 -275
- flwr/client/grpc_rere_client/connection.py +3 -3
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/message_handler/message_handler.py +2 -1
- flwr/client/mod/centraldp_mods.py +1 -1
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
- flwr/client/run_info_store.py +2 -1
- flwr/clientapp/client_app.py +2 -1
- flwr/common/__init__.py +3 -2
- flwr/common/args.py +5 -5
- flwr/common/config.py +12 -17
- flwr/common/constant.py +3 -16
- flwr/common/context.py +2 -1
- flwr/common/exit/exit.py +4 -4
- flwr/common/exit/exit_code.py +6 -0
- flwr/common/grpc.py +2 -1
- flwr/common/logger.py +1 -1
- flwr/common/message.py +1 -1
- flwr/common/retry_invoker.py +13 -5
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
- flwr/common/serde.py +7 -5
- flwr/common/telemetry.py +1 -1
- flwr/common/typing.py +4 -3
- flwr/compat/client/app.py +6 -9
- flwr/compat/client/grpc_client/connection.py +2 -1
- flwr/compat/common/constant.py +29 -0
- flwr/compat/server/app.py +1 -1
- flwr/proto/clientappio_pb2.py +2 -2
- flwr/proto/clientappio_pb2_grpc.py +104 -88
- flwr/proto/clientappio_pb2_grpc.pyi +140 -80
- flwr/proto/federation_pb2.py +5 -3
- flwr/proto/federation_pb2.pyi +32 -2
- flwr/proto/run_pb2.py +5 -13
- flwr/proto/run_pb2.pyi +0 -57
- flwr/proto/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +138 -207
- flwr/proto/serverappio_pb2_grpc.pyi +189 -155
- flwr/proto/simulationio_pb2.py +2 -2
- flwr/proto/simulationio_pb2_grpc.py +62 -90
- flwr/proto/simulationio_pb2_grpc.pyi +95 -55
- flwr/server/app.py +6 -13
- flwr/server/compat/grid_client_proxy.py +2 -1
- flwr/server/grid/grpc_grid.py +5 -5
- flwr/server/serverapp/app.py +11 -4
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
- flwr/server/superlink/fleet/message_handler/message_handler.py +6 -5
- flwr/server/superlink/linkstate/__init__.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +2 -10
- flwr/server/superlink/linkstate/linkstate.py +2 -21
- flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
- flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +432 -534
- flwr/server/superlink/linkstate/utils.py +49 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
- flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
- flwr/server/utils/validator.py +1 -1
- flwr/server/workflow/default_workflows.py +2 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
- flwr/serverapp/strategy/bulyan.py +7 -1
- flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
- flwr/serverapp/strategy/fedavg.py +1 -1
- flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
- flwr/simulation/run_simulation.py +3 -12
- flwr/simulation/simulationio_connection.py +3 -3
- flwr/{common → supercore}/address.py +7 -33
- flwr/supercore/app_utils.py +2 -1
- flwr/supercore/constant.py +24 -2
- flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
- flwr/supercore/credential_store/__init__.py +33 -0
- flwr/supercore/credential_store/credential_store.py +34 -0
- flwr/supercore/credential_store/file_credential_store.py +76 -0
- flwr/{common → supercore}/date.py +0 -11
- flwr/supercore/ffs/disk_ffs.py +1 -1
- flwr/supercore/object_store/object_store_factory.py +14 -6
- flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
- flwr/supercore/sql_mixin.py +315 -0
- flwr/supercore/state/__init__.py +15 -0
- flwr/supercore/state/alembic/__init__.py +15 -0
- flwr/supercore/state/alembic/env.py +103 -0
- flwr/supercore/state/alembic/script.py.mako +43 -0
- flwr/supercore/state/alembic/utils.py +239 -0
- flwr/supercore/state/alembic/versions/__init__.py +15 -0
- flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
- flwr/supercore/state/schema/README.md +121 -0
- flwr/supercore/state/schema/__init__.py +15 -0
- flwr/supercore/state/schema/corestate_tables.py +36 -0
- flwr/supercore/state/schema/linkstate_tables.py +152 -0
- flwr/supercore/state/schema/objectstore_tables.py +90 -0
- flwr/supercore/superexec/run_superexec.py +2 -2
- flwr/supercore/utils.py +36 -1
- flwr/superlink/federation/federation_manager.py +2 -2
- flwr/superlink/federation/noop_federation_manager.py +8 -6
- flwr/superlink/servicer/control/control_servicer.py +19 -17
- flwr/supernode/cli/flower_supernode.py +2 -1
- flwr/supernode/runtime/run_clientapp.py +14 -14
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -8
- flwr/supernode/start_client_internal.py +10 -6
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/METADATA +7 -5
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/RECORD +137 -116
- flwr/cli/federation/show.py +0 -318
- flwr/common/pyproject.py +0 -42
- flwr/supercore/sqlite_mixin.py +0 -159
- /flwr/{common → supercore}/version.py +0 -0
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py
CHANGED
|
@@ -16,23 +16,23 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import hashlib
|
|
19
|
-
import io
|
|
20
19
|
import json
|
|
21
20
|
import subprocess
|
|
22
21
|
from pathlib import Path
|
|
23
|
-
from typing import Annotated, Any
|
|
22
|
+
from typing import Annotated, Any
|
|
24
23
|
|
|
24
|
+
import click
|
|
25
25
|
import typer
|
|
26
|
-
from rich.console import Console
|
|
27
26
|
|
|
28
27
|
from flwr.cli.build import build_fab_from_disk, get_fab_filename
|
|
29
|
-
from flwr.cli.
|
|
30
|
-
from flwr.cli.config_utils import
|
|
31
|
-
load_and_validate,
|
|
32
|
-
process_loaded_project_config,
|
|
33
|
-
validate_federation_in_project_config,
|
|
34
|
-
)
|
|
28
|
+
from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
|
|
29
|
+
from flwr.cli.config_utils import load_and_validate
|
|
35
30
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE, RUN_CONFIG_HELP_MESSAGE
|
|
31
|
+
from flwr.cli.flower_config import (
|
|
32
|
+
_serialize_simulation_options,
|
|
33
|
+
read_superlink_connection,
|
|
34
|
+
)
|
|
35
|
+
from flwr.cli.typing import SuperLinkConnection, SuperLinkSimulationOptions
|
|
36
36
|
from flwr.common.config import (
|
|
37
37
|
flatten_dict,
|
|
38
38
|
get_metadata_from_config,
|
|
@@ -40,16 +40,19 @@ from flwr.common.config import (
|
|
|
40
40
|
user_config_to_configrecord,
|
|
41
41
|
)
|
|
42
42
|
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
|
43
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
44
43
|
from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_to_proto
|
|
45
44
|
from flwr.common.typing import Fab
|
|
46
45
|
from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
|
|
47
46
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
48
|
-
from flwr.supercore.
|
|
49
|
-
from flwr.supercore.utils import parse_app_spec
|
|
47
|
+
from flwr.supercore.utils import check_federation_format, parse_app_spec
|
|
50
48
|
|
|
51
49
|
from ..log import start_stream
|
|
52
|
-
from ..utils import
|
|
50
|
+
from ..utils import (
|
|
51
|
+
cli_output_handler,
|
|
52
|
+
flwr_cli_grpc_exc_handler,
|
|
53
|
+
init_channel_from_connection,
|
|
54
|
+
print_json_to_stdout,
|
|
55
|
+
)
|
|
53
56
|
|
|
54
57
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
|
55
58
|
|
|
@@ -60,9 +63,17 @@ def run(
|
|
|
60
63
|
Path,
|
|
61
64
|
typer.Argument(help="Path of the Flower App to run."),
|
|
62
65
|
] = Path("."),
|
|
66
|
+
superlink: Annotated[
|
|
67
|
+
str | None,
|
|
68
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
69
|
+
] = None,
|
|
63
70
|
federation: Annotated[
|
|
64
71
|
str | None,
|
|
65
|
-
typer.
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--federation",
|
|
74
|
+
help="The federation to submit the run to; must be in the "
|
|
75
|
+
"format `@<account>/<federation>`.",
|
|
76
|
+
),
|
|
66
77
|
] = None,
|
|
67
78
|
run_config_overrides: Annotated[
|
|
68
79
|
list[str] | None,
|
|
@@ -77,6 +88,7 @@ def run(
|
|
|
77
88
|
typer.Option(
|
|
78
89
|
"--federation-config",
|
|
79
90
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
91
|
+
hidden=True,
|
|
80
92
|
),
|
|
81
93
|
] = None,
|
|
82
94
|
stream: Annotated[
|
|
@@ -97,95 +109,87 @@ def run(
|
|
|
97
109
|
] = CliOutputFormat.DEFAULT,
|
|
98
110
|
) -> None:
|
|
99
111
|
"""Run Flower App."""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
113
|
+
# Warn `--federation-config` is ignored
|
|
114
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
115
|
+
|
|
116
|
+
# Migrate legacy usage if any
|
|
117
|
+
migrate(str(app), [], ignore_legacy_usage=True)
|
|
118
|
+
|
|
119
|
+
# Read superlink connection configuration
|
|
120
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
105
121
|
|
|
106
122
|
# Determine if app is remote
|
|
107
123
|
app_spec = None
|
|
124
|
+
config: dict[str, Any] = {}
|
|
108
125
|
if (app_str := str(app)).startswith("@"):
|
|
109
126
|
# Validate app version and ID format
|
|
110
127
|
try:
|
|
111
128
|
_ = parse_app_spec(app_str)
|
|
112
129
|
except ValueError as e:
|
|
113
|
-
|
|
114
|
-
raise typer.Exit(code=1) from e
|
|
130
|
+
raise click.ClickException(str(e)) from e
|
|
115
131
|
|
|
116
132
|
app_spec = app_str
|
|
117
|
-
# Set `app` to current directory for credential storage
|
|
118
|
-
app = Path(".")
|
|
119
|
-
is_remote_app = app_spec is not None
|
|
120
|
-
|
|
121
|
-
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
122
133
|
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
# Validate TOML configuration for local app
|
|
135
|
+
else:
|
|
136
|
+
app = app.expanduser().resolve() # Resolve path to absolute
|
|
137
|
+
config, warnings = load_and_validate(app / FAB_CONFIG_FILE)
|
|
138
|
+
if warnings:
|
|
139
|
+
typer.secho(
|
|
140
|
+
f"Flower App configuration warnings in '{app / FAB_CONFIG_FILE}':\n"
|
|
141
|
+
+ "\n".join([f"- {line}" for line in warnings]),
|
|
142
|
+
fg=typer.colors.YELLOW,
|
|
143
|
+
bold=True,
|
|
144
|
+
)
|
|
134
145
|
|
|
135
|
-
if
|
|
146
|
+
if superlink_connection.address:
|
|
136
147
|
_run_with_control_api(
|
|
137
148
|
app,
|
|
149
|
+
config,
|
|
138
150
|
federation,
|
|
139
|
-
|
|
151
|
+
superlink_connection,
|
|
140
152
|
run_config_overrides,
|
|
141
153
|
stream,
|
|
142
|
-
|
|
154
|
+
is_json,
|
|
143
155
|
app_spec,
|
|
144
156
|
)
|
|
145
157
|
else:
|
|
146
158
|
_run_without_control_api(
|
|
147
|
-
app,
|
|
159
|
+
app=app,
|
|
160
|
+
simulation_options=superlink_connection.options, # type: ignore
|
|
161
|
+
config_overrides=run_config_overrides,
|
|
148
162
|
)
|
|
149
|
-
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
150
|
-
if suppress_output:
|
|
151
|
-
restore_output()
|
|
152
|
-
e_message = captured_output.getvalue()
|
|
153
|
-
print_json_error(e_message, err)
|
|
154
|
-
else:
|
|
155
|
-
typer.secho(
|
|
156
|
-
f"{err}",
|
|
157
|
-
fg=typer.colors.RED,
|
|
158
|
-
bold=True,
|
|
159
|
-
err=True,
|
|
160
|
-
)
|
|
161
|
-
finally:
|
|
162
|
-
if suppress_output:
|
|
163
|
-
restore_output()
|
|
164
|
-
captured_output.close()
|
|
165
163
|
|
|
166
164
|
|
|
167
165
|
# pylint: disable-next=R0913, R0914, R0917
|
|
168
166
|
def _run_with_control_api(
|
|
169
167
|
app: Path,
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
config: dict[str, Any],
|
|
169
|
+
federation: str | None,
|
|
170
|
+
superlink_connection: SuperLinkConnection,
|
|
172
171
|
config_overrides: list[str] | None,
|
|
173
172
|
stream: bool,
|
|
174
|
-
|
|
173
|
+
is_json: bool,
|
|
175
174
|
app_spec: str | None,
|
|
176
175
|
) -> None:
|
|
177
176
|
channel = None
|
|
178
177
|
is_remote_app = app_spec is not None
|
|
178
|
+
|
|
179
|
+
# Determine federation to use
|
|
180
|
+
if federation: # Override federation from CLI
|
|
181
|
+
check_federation_format(federation)
|
|
182
|
+
else: # Use federation from SuperLink connection if set
|
|
183
|
+
federation = superlink_connection.federation or ""
|
|
184
|
+
|
|
179
185
|
try:
|
|
180
|
-
|
|
181
|
-
channel = init_channel(app, federation_config, auth_plugin)
|
|
186
|
+
channel = init_channel_from_connection(superlink_connection)
|
|
182
187
|
stub = ControlStub(channel)
|
|
183
188
|
|
|
184
189
|
# Build FAB if local app
|
|
185
190
|
if not is_remote_app:
|
|
186
191
|
fab_bytes = build_fab_from_disk(app)
|
|
187
192
|
fab_hash = hashlib.sha256(fab_bytes).hexdigest()
|
|
188
|
-
config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
|
|
189
193
|
fab_id, fab_version = get_metadata_from_config(config)
|
|
190
194
|
fab = Fab(fab_hash, fab_bytes, {})
|
|
191
195
|
# Skip FAB build if remote app
|
|
@@ -194,16 +198,19 @@ def _run_with_control_api(
|
|
|
194
198
|
fab_id = fab_version = fab_hash = ""
|
|
195
199
|
fab = Fab(fab_hash, b"", {})
|
|
196
200
|
|
|
197
|
-
real_federation: str = federation_config.get("federation", NOOP_FEDERATION)
|
|
198
|
-
|
|
199
201
|
# Construct a `ConfigRecord` out of a flattened `UserConfig`
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
options = {}
|
|
203
|
+
if superlink_connection.options:
|
|
204
|
+
options = flatten_dict(
|
|
205
|
+
_serialize_simulation_options(superlink_connection.options)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
c_record = user_config_to_configrecord(options)
|
|
202
209
|
|
|
203
210
|
req = StartRunRequest(
|
|
204
211
|
fab=fab_to_proto(fab),
|
|
205
212
|
override_config=user_config_to_proto(parse_config_args(config_overrides)),
|
|
206
|
-
federation=
|
|
213
|
+
federation=federation,
|
|
207
214
|
federation_options=config_record_to_proto(c_record),
|
|
208
215
|
app_spec=app_spec or "",
|
|
209
216
|
)
|
|
@@ -215,10 +222,9 @@ def _run_with_control_api(
|
|
|
215
222
|
f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN
|
|
216
223
|
)
|
|
217
224
|
else:
|
|
218
|
-
|
|
219
|
-
raise typer.Exit(code=1)
|
|
225
|
+
raise click.ClickException("Failed to start run")
|
|
220
226
|
|
|
221
|
-
if
|
|
227
|
+
if is_json:
|
|
222
228
|
# Only include FAB metadata if we actually built a local FAB
|
|
223
229
|
payload: dict[str, Any] = {
|
|
224
230
|
"success": res.HasField("run_id"),
|
|
@@ -234,8 +240,7 @@ def _run_with_control_api(
|
|
|
234
240
|
"fab-filename": get_fab_filename(config, fab_hash),
|
|
235
241
|
}
|
|
236
242
|
)
|
|
237
|
-
|
|
238
|
-
Console().print_json(json.dumps(payload))
|
|
243
|
+
print_json_to_stdout(payload)
|
|
239
244
|
|
|
240
245
|
if stream:
|
|
241
246
|
start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
|
|
@@ -246,26 +251,12 @@ def _run_with_control_api(
|
|
|
246
251
|
|
|
247
252
|
def _run_without_control_api(
|
|
248
253
|
app: Path | None,
|
|
249
|
-
|
|
254
|
+
simulation_options: SuperLinkSimulationOptions,
|
|
250
255
|
config_overrides: list[str] | None,
|
|
251
|
-
federation: str,
|
|
252
256
|
) -> None:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
backend_cfg = federation_config["options"].get("backend", {})
|
|
257
|
-
except KeyError as err:
|
|
258
|
-
typer.secho(
|
|
259
|
-
"❌ The project's `pyproject.toml` needs to declare the number of"
|
|
260
|
-
" SuperNodes in the simulation. To simulate 10 SuperNodes,"
|
|
261
|
-
" use the following notation:\n\n"
|
|
262
|
-
f"[tool.flwr.federations.{federation}]\n"
|
|
263
|
-
"options.num-supernodes = 10\n",
|
|
264
|
-
fg=typer.colors.RED,
|
|
265
|
-
bold=True,
|
|
266
|
-
err=True,
|
|
267
|
-
)
|
|
268
|
-
raise typer.Exit(code=1) from err
|
|
257
|
+
|
|
258
|
+
num_supernodes = simulation_options.num_supernodes
|
|
259
|
+
verbose = simulation_options.verbose or False
|
|
269
260
|
|
|
270
261
|
command = [
|
|
271
262
|
"flower-simulation",
|
|
@@ -275,9 +266,10 @@ def _run_without_control_api(
|
|
|
275
266
|
f"{num_supernodes}",
|
|
276
267
|
]
|
|
277
268
|
|
|
278
|
-
if
|
|
269
|
+
if simulation_options.backend:
|
|
279
270
|
# Stringify as JSON
|
|
280
|
-
|
|
271
|
+
backend_serial = _serialize_simulation_options(simulation_options)
|
|
272
|
+
command.extend(["--backend-config", json.dumps(backend_serial)])
|
|
281
273
|
|
|
282
274
|
if verbose:
|
|
283
275
|
command.extend(["--verbose"])
|
flwr/cli/run_utils.py
CHANGED
flwr/cli/stop.py
CHANGED
|
@@ -15,50 +15,45 @@
|
|
|
15
15
|
"""Flower command line interface `stop` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
|
-
import json
|
|
20
|
-
from pathlib import Path
|
|
21
18
|
from typing import Annotated
|
|
22
19
|
|
|
20
|
+
import click
|
|
23
21
|
import typer
|
|
24
|
-
from rich.console import Console
|
|
25
22
|
|
|
26
|
-
from flwr.cli.
|
|
27
|
-
exit_if_no_address,
|
|
28
|
-
load_and_validate,
|
|
29
|
-
process_loaded_project_config,
|
|
30
|
-
validate_federation_in_project_config,
|
|
31
|
-
)
|
|
23
|
+
from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
|
|
32
24
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
33
|
-
from flwr.
|
|
34
|
-
from flwr.common.
|
|
25
|
+
from flwr.cli.flower_config import read_superlink_connection
|
|
26
|
+
from flwr.common.constant import CliOutputFormat
|
|
35
27
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
36
28
|
StopRunRequest,
|
|
37
29
|
StopRunResponse,
|
|
38
30
|
)
|
|
39
31
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
40
32
|
|
|
41
|
-
from .utils import
|
|
33
|
+
from .utils import (
|
|
34
|
+
cli_output_handler,
|
|
35
|
+
flwr_cli_grpc_exc_handler,
|
|
36
|
+
init_channel_from_connection,
|
|
37
|
+
print_json_to_stdout,
|
|
38
|
+
)
|
|
42
39
|
|
|
43
40
|
|
|
44
41
|
def stop( # pylint: disable=R0914
|
|
42
|
+
ctx: typer.Context,
|
|
45
43
|
run_id: Annotated[ # pylint: disable=unused-argument
|
|
46
44
|
int,
|
|
47
45
|
typer.Argument(help="The Flower run ID to stop"),
|
|
48
46
|
],
|
|
49
|
-
|
|
50
|
-
Path,
|
|
51
|
-
typer.Argument(help="Path of the Flower project"),
|
|
52
|
-
] = Path("."),
|
|
53
|
-
federation: Annotated[
|
|
47
|
+
superlink: Annotated[
|
|
54
48
|
str | None,
|
|
55
|
-
typer.Argument(help="Name of the
|
|
49
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
56
50
|
] = None,
|
|
57
51
|
federation_config_overrides: Annotated[
|
|
58
52
|
list[str] | None,
|
|
59
53
|
typer.Option(
|
|
60
54
|
"--federation-config",
|
|
61
55
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
56
|
+
hidden=True,
|
|
62
57
|
),
|
|
63
58
|
] = None,
|
|
64
59
|
output_format: Annotated[
|
|
@@ -75,61 +70,29 @@ def stop( # pylint: disable=R0914
|
|
|
75
70
|
This command stops a running Flower App execution by sending a stop request to the
|
|
76
71
|
SuperLink via the Control API.
|
|
77
72
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
88
|
-
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
89
|
-
config = process_loaded_project_config(config, errors, warnings)
|
|
90
|
-
federation, federation_config = validate_federation_in_project_config(
|
|
91
|
-
federation, config, federation_config_overrides
|
|
92
|
-
)
|
|
93
|
-
exit_if_no_address(federation_config, "stop")
|
|
73
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
74
|
+
# Warn `--federation-config` is ignored
|
|
75
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
76
|
+
|
|
77
|
+
migrate(superlink, args=ctx.args)
|
|
78
|
+
|
|
79
|
+
# Read superlink connection configuration
|
|
80
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
94
81
|
channel = None
|
|
82
|
+
|
|
95
83
|
try:
|
|
96
|
-
|
|
97
|
-
channel = init_channel(app, federation_config, auth_plugin)
|
|
84
|
+
channel = init_channel_from_connection(superlink_connection)
|
|
98
85
|
stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
|
|
99
86
|
|
|
100
87
|
typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
|
|
101
|
-
_stop_run(stub=stub, run_id=run_id,
|
|
102
|
-
|
|
103
|
-
except ValueError as err:
|
|
104
|
-
typer.secho(
|
|
105
|
-
f"❌ {err}",
|
|
106
|
-
fg=typer.colors.RED,
|
|
107
|
-
bold=True,
|
|
108
|
-
err=True,
|
|
109
|
-
)
|
|
110
|
-
raise typer.Exit(code=1) from err
|
|
88
|
+
_stop_run(stub=stub, run_id=run_id, is_json=is_json)
|
|
89
|
+
|
|
111
90
|
finally:
|
|
112
91
|
if channel:
|
|
113
92
|
channel.close()
|
|
114
|
-
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
115
|
-
if suppress_output:
|
|
116
|
-
restore_output()
|
|
117
|
-
e_message = captured_output.getvalue()
|
|
118
|
-
print_json_error(e_message, err)
|
|
119
|
-
else:
|
|
120
|
-
typer.secho(
|
|
121
|
-
f"{err}",
|
|
122
|
-
fg=typer.colors.RED,
|
|
123
|
-
bold=True,
|
|
124
|
-
err=True,
|
|
125
|
-
)
|
|
126
|
-
finally:
|
|
127
|
-
if suppress_output:
|
|
128
|
-
restore_output()
|
|
129
|
-
captured_output.close()
|
|
130
93
|
|
|
131
94
|
|
|
132
|
-
def _stop_run(stub: ControlStub, run_id: int,
|
|
95
|
+
def _stop_run(stub: ControlStub, run_id: int, is_json: bool) -> None:
|
|
133
96
|
"""Stop a run and display the result.
|
|
134
97
|
|
|
135
98
|
Parameters
|
|
@@ -138,23 +101,19 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
|
|
|
138
101
|
The gRPC stub for Control API communication.
|
|
139
102
|
run_id : int
|
|
140
103
|
The unique identifier of the run to stop.
|
|
141
|
-
|
|
142
|
-
|
|
104
|
+
is_json : bool
|
|
105
|
+
Whether JSON output format is requested.
|
|
143
106
|
"""
|
|
144
107
|
with flwr_cli_grpc_exc_handler():
|
|
145
108
|
response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
|
|
146
109
|
if response.success:
|
|
147
110
|
typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
|
|
148
|
-
if
|
|
149
|
-
|
|
111
|
+
if is_json:
|
|
112
|
+
print_json_to_stdout(
|
|
150
113
|
{
|
|
151
114
|
"success": True,
|
|
152
115
|
"run-id": f"{run_id}",
|
|
153
116
|
}
|
|
154
117
|
)
|
|
155
|
-
restore_output()
|
|
156
|
-
Console().print_json(run_output)
|
|
157
118
|
else:
|
|
158
|
-
|
|
159
|
-
f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED, err=True
|
|
160
|
-
)
|
|
119
|
+
raise click.ClickException(f"Run {run_id} couldn't be stopped.")
|
flwr/cli/supernode/ls.py
CHANGED
|
@@ -15,48 +15,42 @@
|
|
|
15
15
|
"""Flower command line interface `supernode list` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
18
|
import json
|
|
20
19
|
from datetime import datetime, timedelta
|
|
21
|
-
from
|
|
22
|
-
from typing import Annotated, cast
|
|
20
|
+
from typing import Annotated
|
|
23
21
|
|
|
24
22
|
import typer
|
|
25
23
|
from rich.console import Console
|
|
26
24
|
from rich.table import Table
|
|
27
25
|
from rich.text import Text
|
|
28
26
|
|
|
29
|
-
from flwr.cli.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
process_loaded_project_config,
|
|
33
|
-
validate_federation_in_project_config,
|
|
34
|
-
)
|
|
35
|
-
from flwr.common.constant import FAB_CONFIG_FILE, NOOP_ACCOUNT_NAME, CliOutputFormat
|
|
36
|
-
from flwr.common.date import isoformat8601_utc
|
|
37
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
27
|
+
from flwr.cli.config_migration import migrate
|
|
28
|
+
from flwr.cli.flower_config import read_superlink_connection
|
|
29
|
+
from flwr.common.constant import NOOP_ACCOUNT_NAME, CliOutputFormat
|
|
38
30
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
39
31
|
ListNodesRequest,
|
|
40
32
|
ListNodesResponse,
|
|
41
33
|
)
|
|
42
34
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
43
35
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
36
|
+
from flwr.supercore.date import isoformat8601_utc
|
|
44
37
|
from flwr.supercore.utils import humanize_duration
|
|
45
38
|
|
|
46
|
-
from ..utils import
|
|
39
|
+
from ..utils import (
|
|
40
|
+
cli_output_handler,
|
|
41
|
+
flwr_cli_grpc_exc_handler,
|
|
42
|
+
init_channel_from_connection,
|
|
43
|
+
print_json_to_stdout,
|
|
44
|
+
)
|
|
47
45
|
|
|
48
46
|
_NodeListType = tuple[int, str, str, str, str, str, str, str, float]
|
|
49
47
|
|
|
50
48
|
|
|
51
49
|
def ls( # pylint: disable=R0914, R0913, R0917
|
|
52
50
|
ctx: typer.Context,
|
|
53
|
-
|
|
54
|
-
Path,
|
|
55
|
-
typer.Argument(help="Path of the Flower project"),
|
|
56
|
-
] = Path("."),
|
|
57
|
-
federation: Annotated[
|
|
51
|
+
superlink: Annotated[
|
|
58
52
|
str | None,
|
|
59
|
-
typer.Argument(help="Name of the
|
|
53
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
60
54
|
] = None,
|
|
61
55
|
output_format: Annotated[
|
|
62
56
|
str,
|
|
@@ -75,55 +69,29 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
75
69
|
),
|
|
76
70
|
] = False,
|
|
77
71
|
) -> None:
|
|
78
|
-
"""List SuperNodes in the federation."""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if suppress_output:
|
|
86
|
-
redirect_output(captured_output)
|
|
87
|
-
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
88
|
-
|
|
89
|
-
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
90
|
-
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
91
|
-
config = process_loaded_project_config(config, errors, warnings)
|
|
92
|
-
federation, federation_config = validate_federation_in_project_config(
|
|
93
|
-
federation, config
|
|
94
|
-
)
|
|
95
|
-
exit_if_no_address(federation_config, f"supernode {command_name}")
|
|
72
|
+
"""List SuperNodes in the federation (alias: ls)."""
|
|
73
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
74
|
+
# Migrate legacy usage if any
|
|
75
|
+
migrate(superlink, args=ctx.args)
|
|
76
|
+
|
|
77
|
+
# Read superlink connection configuration
|
|
78
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
96
79
|
channel = None
|
|
80
|
+
|
|
97
81
|
try:
|
|
98
|
-
|
|
99
|
-
channel = init_channel(app, federation_config, auth_plugin)
|
|
82
|
+
channel = init_channel_from_connection(superlink_connection)
|
|
100
83
|
stub = ControlStub(channel)
|
|
101
84
|
typer.echo("📄 Listing all nodes...")
|
|
102
85
|
formatted_nodes = _list_nodes(stub)
|
|
103
|
-
|
|
104
|
-
if
|
|
105
|
-
|
|
86
|
+
|
|
87
|
+
if is_json:
|
|
88
|
+
print_json_to_stdout(_to_json(formatted_nodes, verbose=verbose))
|
|
106
89
|
else:
|
|
107
90
|
Console().print(_to_table(formatted_nodes, verbose=verbose))
|
|
108
91
|
|
|
109
92
|
finally:
|
|
110
93
|
if channel:
|
|
111
94
|
channel.close()
|
|
112
|
-
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
113
|
-
if suppress_output:
|
|
114
|
-
restore_output()
|
|
115
|
-
e_message = captured_output.getvalue()
|
|
116
|
-
print_json_error(e_message, err)
|
|
117
|
-
else:
|
|
118
|
-
typer.secho(
|
|
119
|
-
f"{err}",
|
|
120
|
-
fg=typer.colors.RED,
|
|
121
|
-
bold=True,
|
|
122
|
-
)
|
|
123
|
-
finally:
|
|
124
|
-
if suppress_output:
|
|
125
|
-
restore_output()
|
|
126
|
-
captured_output.close()
|
|
127
95
|
|
|
128
96
|
|
|
129
97
|
def _list_nodes(stub: ControlStub) -> list[_NodeListType]:
|