flwr-nightly 1.26.0.dev20260128__py3-none-any.whl → 1.26.0.dev20260129__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/cli/app_cmd/review.py +7 -7
- flwr/cli/build.py +9 -11
- flwr/cli/config/ls.py +5 -33
- flwr/cli/config_migration.py +7 -10
- flwr/cli/config_utils.py +18 -12
- flwr/cli/constant.py +1 -0
- flwr/cli/federation/ls.py +30 -42
- flwr/cli/flower_config.py +5 -0
- flwr/cli/install.py +5 -5
- flwr/cli/ls.py +17 -32
- flwr/cli/run/run.py +39 -41
- flwr/cli/stop.py +21 -41
- flwr/cli/supernode/ls.py +15 -33
- flwr/cli/supernode/register.py +18 -37
- flwr/cli/supernode/unregister.py +16 -38
- flwr/cli/typing.py +3 -0
- flwr/cli/utils.py +51 -1
- flwr/client/grpc_rere_client/connection.py +2 -2
- flwr/common/args.py +1 -1
- flwr/common/config.py +3 -5
- flwr/common/constant.py +1 -1
- flwr/common/retry_invoker.py +13 -5
- flwr/common/typing.py +0 -1
- flwr/proto/federation_pb2.py +2 -2
- flwr/proto/federation_pb2.pyi +1 -5
- flwr/server/grid/grpc_grid.py +3 -3
- flwr/simulation/run_simulation.py +1 -11
- flwr/simulation/simulationio_connection.py +3 -3
- flwr/supercore/constant.py +2 -2
- flwr/supercore/state/alembic/env.py +33 -11
- flwr/supercore/state/alembic/utils.py +191 -1
- flwr/supercore/superexec/run_superexec.py +2 -2
- flwr/superlink/federation/noop_federation_manager.py +0 -1
- flwr/superlink/servicer/control/control_servicer.py +1 -1
- flwr/supernode/runtime/run_clientapp.py +2 -2
- flwr/supernode/start_client_internal.py +2 -2
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260129.dist-info}/METADATA +2 -2
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260129.dist-info}/RECORD +40 -40
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260129.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260129.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py
CHANGED
|
@@ -16,19 +16,17 @@
|
|
|
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
|
|
|
25
24
|
import click
|
|
26
25
|
import typer
|
|
27
|
-
from rich.console import Console
|
|
28
26
|
|
|
29
27
|
from flwr.cli.build import build_fab_from_disk, get_fab_filename
|
|
30
28
|
from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
|
|
31
|
-
from flwr.cli.config_utils import
|
|
29
|
+
from flwr.cli.config_utils import load_and_validate
|
|
32
30
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE, RUN_CONFIG_HELP_MESSAGE
|
|
33
31
|
from flwr.cli.flower_config import (
|
|
34
32
|
_serialize_simulation_options,
|
|
@@ -36,12 +34,12 @@ from flwr.cli.flower_config import (
|
|
|
36
34
|
)
|
|
37
35
|
from flwr.cli.typing import SuperLinkConnection, SuperLinkSimulationOptions
|
|
38
36
|
from flwr.common.config import (
|
|
37
|
+
flatten_dict,
|
|
39
38
|
get_metadata_from_config,
|
|
40
39
|
parse_config_args,
|
|
41
40
|
user_config_to_configrecord,
|
|
42
41
|
)
|
|
43
42
|
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
|
44
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
45
43
|
from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_to_proto
|
|
46
44
|
from flwr.common.typing import Fab
|
|
47
45
|
from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
|
|
@@ -49,7 +47,12 @@ from flwr.proto.control_pb2_grpc import ControlStub
|
|
|
49
47
|
from flwr.supercore.utils import 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
|
|
|
@@ -98,25 +101,19 @@ def run(
|
|
|
98
101
|
] = CliOutputFormat.DEFAULT,
|
|
99
102
|
) -> None:
|
|
100
103
|
"""Run Flower App."""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if suppress_output:
|
|
105
|
-
redirect_output(captured_output)
|
|
106
|
-
|
|
107
|
-
# Warn `--federation-config` is ignored
|
|
108
|
-
warn_if_federation_config_overrides(federation_config_overrides)
|
|
104
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
105
|
+
# Warn `--federation-config` is ignored
|
|
106
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
# Migrate legacy usage if any
|
|
109
|
+
migrate(str(app), [], ignore_legacy_usage=True)
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
try:
|
|
111
|
+
# Read superlink connection configuration
|
|
112
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
117
113
|
|
|
118
114
|
# Determine if app is remote
|
|
119
115
|
app_spec = None
|
|
116
|
+
config: dict[str, Any] = {}
|
|
120
117
|
if (app_str := str(app)).startswith("@"):
|
|
121
118
|
# Validate app version and ID format
|
|
122
119
|
try:
|
|
@@ -125,16 +122,27 @@ def run(
|
|
|
125
122
|
raise click.ClickException(str(e)) from e
|
|
126
123
|
|
|
127
124
|
app_spec = app_str
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
|
|
126
|
+
# Validate TOML configuration for local app
|
|
127
|
+
else:
|
|
128
|
+
config, warnings = load_and_validate(app / FAB_CONFIG_FILE)
|
|
129
|
+
if warnings:
|
|
130
|
+
typer.secho(
|
|
131
|
+
"Missing recommended fields in Flower App configuration "
|
|
132
|
+
"(pyproject.toml):\n"
|
|
133
|
+
+ "\n".join([f"- {line}" for line in warnings]),
|
|
134
|
+
fg=typer.colors.YELLOW,
|
|
135
|
+
bold=True,
|
|
136
|
+
)
|
|
130
137
|
|
|
131
138
|
if superlink_connection.address:
|
|
132
139
|
_run_with_control_api(
|
|
133
140
|
app,
|
|
141
|
+
config,
|
|
134
142
|
superlink_connection,
|
|
135
143
|
run_config_overrides,
|
|
136
144
|
stream,
|
|
137
|
-
|
|
145
|
+
is_json,
|
|
138
146
|
app_spec,
|
|
139
147
|
)
|
|
140
148
|
else:
|
|
@@ -143,26 +151,16 @@ def run(
|
|
|
143
151
|
simulation_options=superlink_connection.options, # type: ignore
|
|
144
152
|
config_overrides=run_config_overrides,
|
|
145
153
|
)
|
|
146
|
-
except Exception as err: # pylint: disable=broad-except
|
|
147
|
-
if suppress_output:
|
|
148
|
-
restore_output()
|
|
149
|
-
e_message = captured_output.getvalue()
|
|
150
|
-
print_json_error(e_message, err)
|
|
151
|
-
else:
|
|
152
|
-
raise click.ClickException(str(err)) from None
|
|
153
|
-
finally:
|
|
154
|
-
if suppress_output:
|
|
155
|
-
restore_output()
|
|
156
|
-
captured_output.close()
|
|
157
154
|
|
|
158
155
|
|
|
159
156
|
# pylint: disable-next=R0913, R0914, R0917
|
|
160
157
|
def _run_with_control_api(
|
|
161
158
|
app: Path,
|
|
159
|
+
config: dict[str, Any],
|
|
162
160
|
superlink_connection: SuperLinkConnection,
|
|
163
161
|
config_overrides: list[str] | None,
|
|
164
162
|
stream: bool,
|
|
165
|
-
|
|
163
|
+
is_json: bool,
|
|
166
164
|
app_spec: str | None,
|
|
167
165
|
) -> None:
|
|
168
166
|
channel = None
|
|
@@ -175,7 +173,6 @@ def _run_with_control_api(
|
|
|
175
173
|
if not is_remote_app:
|
|
176
174
|
fab_bytes = build_fab_from_disk(app)
|
|
177
175
|
fab_hash = hashlib.sha256(fab_bytes).hexdigest()
|
|
178
|
-
config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
|
|
179
176
|
fab_id, fab_version = get_metadata_from_config(config)
|
|
180
177
|
fab = Fab(fab_hash, fab_bytes, {})
|
|
181
178
|
# Skip FAB build if remote app
|
|
@@ -187,7 +184,9 @@ def _run_with_control_api(
|
|
|
187
184
|
# Construct a `ConfigRecord` out of a flattened `UserConfig`
|
|
188
185
|
options = {}
|
|
189
186
|
if superlink_connection.options:
|
|
190
|
-
options =
|
|
187
|
+
options = flatten_dict(
|
|
188
|
+
_serialize_simulation_options(superlink_connection.options)
|
|
189
|
+
)
|
|
191
190
|
|
|
192
191
|
c_record = user_config_to_configrecord(options)
|
|
193
192
|
|
|
@@ -208,7 +207,7 @@ def _run_with_control_api(
|
|
|
208
207
|
else:
|
|
209
208
|
raise click.ClickException("Failed to start run")
|
|
210
209
|
|
|
211
|
-
if
|
|
210
|
+
if is_json:
|
|
212
211
|
# Only include FAB metadata if we actually built a local FAB
|
|
213
212
|
payload: dict[str, Any] = {
|
|
214
213
|
"success": res.HasField("run_id"),
|
|
@@ -224,8 +223,7 @@ def _run_with_control_api(
|
|
|
224
223
|
"fab-filename": get_fab_filename(config, fab_hash),
|
|
225
224
|
}
|
|
226
225
|
)
|
|
227
|
-
|
|
228
|
-
Console().print_json(json.dumps(payload))
|
|
226
|
+
print_json_to_stdout(payload)
|
|
229
227
|
|
|
230
228
|
if stream:
|
|
231
229
|
start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
|
|
@@ -241,7 +239,7 @@ def _run_without_control_api(
|
|
|
241
239
|
) -> None:
|
|
242
240
|
|
|
243
241
|
num_supernodes = simulation_options.num_supernodes
|
|
244
|
-
verbose =
|
|
242
|
+
verbose = simulation_options.verbose or False
|
|
245
243
|
|
|
246
244
|
command = [
|
|
247
245
|
"flower-simulation",
|
flwr/cli/stop.py
CHANGED
|
@@ -15,26 +15,27 @@
|
|
|
15
15
|
"""Flower command line interface `stop` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
|
-
import json
|
|
20
18
|
from typing import Annotated
|
|
21
19
|
|
|
22
20
|
import click
|
|
23
21
|
import typer
|
|
24
|
-
from rich.console import Console
|
|
25
22
|
|
|
26
23
|
from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
|
|
27
24
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
28
25
|
from flwr.cli.flower_config import read_superlink_connection
|
|
29
26
|
from flwr.common.constant import CliOutputFormat
|
|
30
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
31
27
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
32
28
|
StopRunRequest,
|
|
33
29
|
StopRunResponse,
|
|
34
30
|
)
|
|
35
31
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
36
32
|
|
|
37
|
-
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
|
+
)
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def stop( # pylint: disable=R0914
|
|
@@ -69,48 +70,29 @@ def stop( # pylint: disable=R0914
|
|
|
69
70
|
This command stops a running Flower App execution by sending a stop request to the
|
|
70
71
|
SuperLink via the Control API.
|
|
71
72
|
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if suppress_output:
|
|
76
|
-
redirect_output(captured_output)
|
|
77
|
-
|
|
78
|
-
# Warn `--federation-config` is ignored
|
|
79
|
-
warn_if_federation_config_overrides(federation_config_overrides)
|
|
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)
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
migrate(superlink, args=ctx.args)
|
|
82
78
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
# Read superlink connection configuration
|
|
80
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
81
|
+
channel = None
|
|
86
82
|
|
|
87
|
-
try:
|
|
88
83
|
try:
|
|
89
84
|
channel = init_channel_from_connection(superlink_connection)
|
|
90
85
|
stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
|
|
91
86
|
|
|
92
87
|
typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
|
|
93
|
-
_stop_run(stub=stub, run_id=run_id,
|
|
88
|
+
_stop_run(stub=stub, run_id=run_id, is_json=is_json)
|
|
94
89
|
|
|
95
|
-
except ValueError as err:
|
|
96
|
-
raise click.ClickException(str(err)) from err
|
|
97
90
|
finally:
|
|
98
91
|
if channel:
|
|
99
92
|
channel.close()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
e_message = captured_output.getvalue()
|
|
104
|
-
print_json_error(e_message, err)
|
|
105
|
-
else:
|
|
106
|
-
raise click.ClickException(str(err)) from None
|
|
107
|
-
finally:
|
|
108
|
-
if suppress_output:
|
|
109
|
-
restore_output()
|
|
110
|
-
captured_output.close()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _stop_run(stub: ControlStub, run_id: int, is_json: bool) -> None:
|
|
114
96
|
"""Stop a run and display the result.
|
|
115
97
|
|
|
116
98
|
Parameters
|
|
@@ -119,21 +101,19 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
|
|
|
119
101
|
The gRPC stub for Control API communication.
|
|
120
102
|
run_id : int
|
|
121
103
|
The unique identifier of the run to stop.
|
|
122
|
-
|
|
123
|
-
|
|
104
|
+
is_json : bool
|
|
105
|
+
Whether JSON output format is requested.
|
|
124
106
|
"""
|
|
125
107
|
with flwr_cli_grpc_exc_handler():
|
|
126
108
|
response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
|
|
127
109
|
if response.success:
|
|
128
110
|
typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
|
|
129
|
-
if
|
|
130
|
-
|
|
111
|
+
if is_json:
|
|
112
|
+
print_json_to_stdout(
|
|
131
113
|
{
|
|
132
114
|
"success": True,
|
|
133
115
|
"run-id": f"{run_id}",
|
|
134
116
|
}
|
|
135
117
|
)
|
|
136
|
-
restore_output()
|
|
137
|
-
Console().print_json(run_output)
|
|
138
118
|
else:
|
|
139
119
|
raise click.ClickException(f"Run {run_id} couldn't be stopped.")
|
flwr/cli/supernode/ls.py
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
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
20
|
from typing import Annotated
|
|
@@ -28,7 +27,6 @@ from rich.text import Text
|
|
|
28
27
|
from flwr.cli.config_migration import migrate
|
|
29
28
|
from flwr.cli.flower_config import read_superlink_connection
|
|
30
29
|
from flwr.common.constant import NOOP_ACCOUNT_NAME, CliOutputFormat
|
|
31
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
32
30
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
33
31
|
ListNodesRequest,
|
|
34
32
|
ListNodesResponse,
|
|
@@ -38,7 +36,12 @@ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
|
38
36
|
from flwr.supercore.date import isoformat8601_utc
|
|
39
37
|
from flwr.supercore.utils import humanize_duration
|
|
40
38
|
|
|
41
|
-
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
|
+
)
|
|
42
45
|
|
|
43
46
|
_NodeListType = tuple[int, str, str, str, str, str, str, str, float]
|
|
44
47
|
|
|
@@ -67,49 +70,28 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
67
70
|
] = False,
|
|
68
71
|
) -> None:
|
|
69
72
|
"""List SuperNodes in the federation (alias: ls)."""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if suppress_output:
|
|
74
|
-
redirect_output(captured_output)
|
|
75
|
-
|
|
76
|
-
# Migrate legacy usage if any
|
|
77
|
-
migrate(superlink, args=ctx.args)
|
|
73
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
74
|
+
# Migrate legacy usage if any
|
|
75
|
+
migrate(superlink, args=ctx.args)
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
# Read superlink connection configuration
|
|
78
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
79
|
+
channel = None
|
|
82
80
|
|
|
83
|
-
try:
|
|
84
81
|
try:
|
|
85
82
|
channel = init_channel_from_connection(superlink_connection)
|
|
86
83
|
stub = ControlStub(channel)
|
|
87
84
|
typer.echo("📄 Listing all nodes...")
|
|
88
85
|
formatted_nodes = _list_nodes(stub)
|
|
89
|
-
|
|
90
|
-
if
|
|
91
|
-
|
|
86
|
+
|
|
87
|
+
if is_json:
|
|
88
|
+
print_json_to_stdout(_to_json(formatted_nodes, verbose=verbose))
|
|
92
89
|
else:
|
|
93
90
|
Console().print(_to_table(formatted_nodes, verbose=verbose))
|
|
94
91
|
|
|
95
92
|
finally:
|
|
96
93
|
if channel:
|
|
97
94
|
channel.close()
|
|
98
|
-
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
99
|
-
if suppress_output:
|
|
100
|
-
restore_output()
|
|
101
|
-
e_message = captured_output.getvalue()
|
|
102
|
-
print_json_error(e_message, err)
|
|
103
|
-
else:
|
|
104
|
-
typer.secho(
|
|
105
|
-
f"{err}",
|
|
106
|
-
fg=typer.colors.RED,
|
|
107
|
-
bold=True,
|
|
108
|
-
)
|
|
109
|
-
finally:
|
|
110
|
-
if suppress_output:
|
|
111
|
-
restore_output()
|
|
112
|
-
captured_output.close()
|
|
113
95
|
|
|
114
96
|
|
|
115
97
|
def _list_nodes(stub: ControlStub) -> list[_NodeListType]:
|
flwr/cli/supernode/register.py
CHANGED
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
"""Flower command line interface `supernode register` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
|
-
import json
|
|
20
18
|
from pathlib import Path
|
|
21
19
|
from typing import Annotated
|
|
22
20
|
|
|
@@ -25,13 +23,11 @@ import typer
|
|
|
25
23
|
from cryptography.exceptions import UnsupportedAlgorithm
|
|
26
24
|
from cryptography.hazmat.primitives import serialization
|
|
27
25
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
28
|
-
from rich.console import Console
|
|
29
26
|
|
|
30
27
|
from flwr.cli.config_migration import migrate
|
|
31
28
|
from flwr.cli.flower_config import read_superlink_connection
|
|
32
29
|
from flwr.common.constant import CliOutputFormat
|
|
33
30
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
34
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
35
31
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
36
32
|
RegisterNodeRequest,
|
|
37
33
|
RegisterNodeResponse,
|
|
@@ -39,7 +35,12 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
39
35
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
40
36
|
from flwr.supercore.primitives.asymmetric import public_key_to_bytes, uses_nist_ec_curve
|
|
41
37
|
|
|
42
|
-
from ..utils import
|
|
38
|
+
from ..utils import (
|
|
39
|
+
cli_output_handler,
|
|
40
|
+
flwr_cli_grpc_exc_handler,
|
|
41
|
+
init_channel_from_connection,
|
|
42
|
+
print_json_to_stdout,
|
|
43
|
+
)
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def register( # pylint: disable=R0914
|
|
@@ -64,52 +65,34 @@ def register( # pylint: disable=R0914
|
|
|
64
65
|
] = CliOutputFormat.DEFAULT,
|
|
65
66
|
) -> None:
|
|
66
67
|
"""Add a SuperNode to the federation."""
|
|
67
|
-
suppress_output = output_format == CliOutputFormat.JSON
|
|
68
|
-
captured_output = io.StringIO()
|
|
69
|
-
|
|
70
68
|
# Load public key
|
|
71
69
|
public_key_path = Path(public_key)
|
|
72
70
|
public_key_bytes = try_load_public_key(public_key_path)
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
73
|
+
# Migrate legacy usage if any
|
|
74
|
+
migrate(superlink, args=ctx.args)
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
# Read superlink connection configuration
|
|
77
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
78
|
+
channel = None
|
|
79
79
|
|
|
80
|
-
# Read superlink connection configuration
|
|
81
|
-
superlink_connection = read_superlink_connection(superlink)
|
|
82
|
-
channel = None
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
80
|
try:
|
|
86
81
|
channel = init_channel_from_connection(superlink_connection)
|
|
87
82
|
stub = ControlStub(channel)
|
|
88
83
|
|
|
89
84
|
_register_node(
|
|
90
|
-
stub=stub,
|
|
85
|
+
stub=stub,
|
|
86
|
+
public_key=public_key_bytes,
|
|
87
|
+
is_json=is_json,
|
|
91
88
|
)
|
|
92
89
|
|
|
93
|
-
except ValueError as err:
|
|
94
|
-
raise click.ClickException(str(err)) from err
|
|
95
90
|
finally:
|
|
96
91
|
if channel:
|
|
97
92
|
channel.close()
|
|
98
93
|
|
|
99
|
-
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
100
|
-
if suppress_output:
|
|
101
|
-
restore_output()
|
|
102
|
-
e_message = captured_output.getvalue()
|
|
103
|
-
print_json_error(e_message, err)
|
|
104
|
-
else:
|
|
105
|
-
raise click.ClickException(str(err)) from None
|
|
106
|
-
finally:
|
|
107
|
-
if suppress_output:
|
|
108
|
-
restore_output()
|
|
109
|
-
captured_output.close()
|
|
110
|
-
|
|
111
94
|
|
|
112
|
-
def _register_node(stub: ControlStub, public_key: bytes,
|
|
95
|
+
def _register_node(stub: ControlStub, public_key: bytes, is_json: bool) -> None:
|
|
113
96
|
"""Register a node."""
|
|
114
97
|
with flwr_cli_grpc_exc_handler():
|
|
115
98
|
response: RegisterNodeResponse = stub.RegisterNode(
|
|
@@ -120,15 +103,13 @@ def _register_node(stub: ControlStub, public_key: bytes, output_format: str) ->
|
|
|
120
103
|
f"✅ SuperNode {response.node_id} registered successfully.",
|
|
121
104
|
fg=typer.colors.GREEN,
|
|
122
105
|
)
|
|
123
|
-
if
|
|
124
|
-
|
|
106
|
+
if is_json:
|
|
107
|
+
print_json_to_stdout(
|
|
125
108
|
{
|
|
126
109
|
"success": True,
|
|
127
110
|
"node-id": response.node_id,
|
|
128
111
|
}
|
|
129
112
|
)
|
|
130
|
-
restore_output()
|
|
131
|
-
Console().print_json(run_output)
|
|
132
113
|
else:
|
|
133
114
|
raise click.ClickException("SuperNode couldn't be registered.")
|
|
134
115
|
|
flwr/cli/supernode/unregister.py
CHANGED
|
@@ -15,22 +15,22 @@
|
|
|
15
15
|
"""Flower command line interface `supernode unregister` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
|
-
import json
|
|
20
18
|
from typing import Annotated
|
|
21
19
|
|
|
22
|
-
import click
|
|
23
20
|
import typer
|
|
24
|
-
from rich.console import Console
|
|
25
21
|
|
|
26
22
|
from flwr.cli.config_migration import migrate
|
|
27
23
|
from flwr.cli.flower_config import read_superlink_connection
|
|
28
24
|
from flwr.common.constant import CliOutputFormat
|
|
29
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
30
25
|
from flwr.proto.control_pb2 import UnregisterNodeRequest # pylint: disable=E0611
|
|
31
26
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
32
27
|
|
|
33
|
-
from ..utils import
|
|
28
|
+
from ..utils import (
|
|
29
|
+
cli_output_handler,
|
|
30
|
+
flwr_cli_grpc_exc_handler,
|
|
31
|
+
init_channel_from_connection,
|
|
32
|
+
print_json_to_stdout,
|
|
33
|
+
)
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def unregister( # pylint: disable=R0914
|
|
@@ -55,49 +55,29 @@ def unregister( # pylint: disable=R0914
|
|
|
55
55
|
] = CliOutputFormat.DEFAULT,
|
|
56
56
|
) -> None:
|
|
57
57
|
"""Unregister a SuperNode from the federation."""
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
59
|
+
# Migrate legacy usage if any
|
|
60
|
+
migrate(superlink, args=ctx.args)
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
# Read superlink connection configuration
|
|
63
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
64
|
+
channel = None
|
|
63
65
|
|
|
64
|
-
# Migrate legacy usage if any
|
|
65
|
-
migrate(superlink, args=ctx.args)
|
|
66
|
-
|
|
67
|
-
# Read superlink connection configuration
|
|
68
|
-
superlink_connection = read_superlink_connection(superlink)
|
|
69
|
-
channel = None
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
66
|
try:
|
|
73
67
|
channel = init_channel_from_connection(superlink_connection)
|
|
74
68
|
stub = ControlStub(channel)
|
|
75
69
|
|
|
76
|
-
_unregister_node(stub=stub, node_id=node_id,
|
|
70
|
+
_unregister_node(stub=stub, node_id=node_id, is_json=is_json)
|
|
77
71
|
|
|
78
|
-
except ValueError as err:
|
|
79
|
-
raise click.ClickException(str(err)) from err
|
|
80
72
|
finally:
|
|
81
73
|
if channel:
|
|
82
74
|
channel.close()
|
|
83
75
|
|
|
84
|
-
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
85
|
-
if suppress_output:
|
|
86
|
-
restore_output()
|
|
87
|
-
e_message = captured_output.getvalue()
|
|
88
|
-
print_json_error(e_message, err)
|
|
89
|
-
else:
|
|
90
|
-
raise click.ClickException(str(err)) from None
|
|
91
|
-
finally:
|
|
92
|
-
if suppress_output:
|
|
93
|
-
restore_output()
|
|
94
|
-
captured_output.close()
|
|
95
|
-
|
|
96
76
|
|
|
97
77
|
def _unregister_node(
|
|
98
78
|
stub: ControlStub,
|
|
99
79
|
node_id: int,
|
|
100
|
-
|
|
80
|
+
is_json: bool,
|
|
101
81
|
) -> None:
|
|
102
82
|
"""Unregister a SuperNode from the federation."""
|
|
103
83
|
with flwr_cli_grpc_exc_handler():
|
|
@@ -105,12 +85,10 @@ def _unregister_node(
|
|
|
105
85
|
typer.secho(
|
|
106
86
|
f"✅ SuperNode {node_id} unregistered successfully.", fg=typer.colors.GREEN
|
|
107
87
|
)
|
|
108
|
-
if
|
|
109
|
-
|
|
88
|
+
if is_json:
|
|
89
|
+
print_json_to_stdout(
|
|
110
90
|
{
|
|
111
91
|
"success": True,
|
|
112
92
|
"node-id": node_id,
|
|
113
93
|
}
|
|
114
94
|
)
|
|
115
|
-
restore_output()
|
|
116
|
-
Console().print_json(run_output)
|
flwr/cli/typing.py
CHANGED
|
@@ -83,6 +83,7 @@ class SuperLinkSimulationOptions:
|
|
|
83
83
|
|
|
84
84
|
num_supernodes: int
|
|
85
85
|
backend: SimulationBackendConfig | None = None
|
|
86
|
+
verbose: bool | None = None
|
|
86
87
|
|
|
87
88
|
def __post_init__(self) -> None:
|
|
88
89
|
"""Validate simulation options."""
|
|
@@ -90,6 +91,8 @@ class SuperLinkSimulationOptions:
|
|
|
90
91
|
raise ValueError(
|
|
91
92
|
"Invalid simulation options: num-supernodes must be an integer."
|
|
92
93
|
)
|
|
94
|
+
if self.verbose is not None and not isinstance(self.verbose, bool):
|
|
95
|
+
raise ValueError("Invalid simulation options: verbose must be a boolean.")
|
|
93
96
|
|
|
94
97
|
|
|
95
98
|
@dataclass
|