flwr-nightly 1.26.0.dev20260128__py3-none-any.whl → 1.26.0.dev20260130__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 +10 -12
- flwr/cli/config/ls.py +5 -33
- flwr/cli/config_migration.py +9 -12
- 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 +6 -6
- 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 +19 -39
- 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 +3 -3
- flwr/common/args.py +5 -5
- flwr/common/config.py +5 -7
- flwr/common/constant.py +1 -1
- flwr/common/exit/exit.py +3 -3
- flwr/common/logger.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/app.py +1 -1
- flwr/server/grid/grpc_grid.py +3 -3
- flwr/server/serverapp/app.py +11 -4
- flwr/server/superlink/linkstate/linkstate_factory.py +12 -4
- flwr/simulation/run_simulation.py +1 -11
- flwr/simulation/simulationio_connection.py +3 -3
- flwr/supercore/constant.py +2 -2
- flwr/supercore/ffs/disk_ffs.py +1 -1
- flwr/supercore/object_store/object_store_factory.py +10 -2
- flwr/supercore/sql_mixin.py +8 -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/cli/flower_supernode.py +2 -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.dev20260130.dist-info}/METADATA +2 -2
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260130.dist-info}/RECORD +49 -49
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260130.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.26.0.dev20260128.dist-info → flwr_nightly-1.26.0.dev20260130.dist-info}/entry_points.txt +0 -0
flwr/cli/app_cmd/review.py
CHANGED
|
@@ -77,15 +77,15 @@ def review(
|
|
|
77
77
|
url = f"{PLATFORM_API_URL}/hub/fetch-fab"
|
|
78
78
|
try:
|
|
79
79
|
presigned_url, _ = request_download_link(app_id, app_version, url, "fab_url")
|
|
80
|
-
except ValueError as e:
|
|
81
|
-
raise click.ClickException(str(e)) from e
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
fab_bytes = _download_fab(presigned_url)
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
# Unpack FAB
|
|
84
|
+
typer.secho("Unpacking FAB... ", fg=typer.colors.BLUE)
|
|
85
|
+
review_dir = _create_review_dir()
|
|
86
|
+
review_app_path = install_from_fab(fab_bytes, review_dir)
|
|
87
|
+
except ValueError as e:
|
|
88
|
+
raise click.ClickException(str(e)) from e
|
|
89
89
|
|
|
90
90
|
# Extract app version
|
|
91
91
|
version_pattern = re.compile(r"\b(\d+\.\d+\.\d+)\b")
|
flwr/cli/build.py
CHANGED
|
@@ -71,7 +71,7 @@ def get_fab_filename(config: dict[str, Any], fab_hash: str) -> str:
|
|
|
71
71
|
Parameters
|
|
72
72
|
----------
|
|
73
73
|
config : dict[str, Any]
|
|
74
|
-
The
|
|
74
|
+
The Flower App configuration dictionary.
|
|
75
75
|
fab_hash : str
|
|
76
76
|
The SHA-256 hash of the FAB file.
|
|
77
77
|
|
|
@@ -106,7 +106,7 @@ def build(
|
|
|
106
106
|
if app is None:
|
|
107
107
|
app = Path.cwd()
|
|
108
108
|
|
|
109
|
-
app = app.resolve()
|
|
109
|
+
app = app.expanduser().resolve()
|
|
110
110
|
if not app.is_dir():
|
|
111
111
|
raise click.ClickException(
|
|
112
112
|
f"The path {app} is not a valid path to a Flower app."
|
|
@@ -114,23 +114,21 @@ def build(
|
|
|
114
114
|
|
|
115
115
|
if not is_valid_project_name(app.name):
|
|
116
116
|
raise click.ClickException(
|
|
117
|
-
f"The
|
|
118
|
-
"a valid
|
|
117
|
+
f"The Flower App name {app.name} is invalid, "
|
|
118
|
+
"a valid app name must start with a letter, "
|
|
119
119
|
"and can only contain letters, digits, and hyphens."
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
+ "\n".join([f"- {line}" for line in errors])
|
|
127
|
-
)
|
|
122
|
+
try:
|
|
123
|
+
config, warnings = load_and_validate(app / "pyproject.toml")
|
|
124
|
+
except ValueError as e:
|
|
125
|
+
raise click.ClickException(str(e)) from None
|
|
128
126
|
|
|
129
127
|
if warnings:
|
|
130
128
|
typer.secho(
|
|
131
|
-
"
|
|
129
|
+
"Flower App configuration (pyproject.toml) is missing the following "
|
|
132
130
|
"recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]),
|
|
133
|
-
fg=typer.colors.
|
|
131
|
+
fg=typer.colors.YELLOW,
|
|
134
132
|
bold=True,
|
|
135
133
|
)
|
|
136
134
|
|
flwr/cli/config/ls.py
CHANGED
|
@@ -15,19 +15,15 @@
|
|
|
15
15
|
"""Flower command line interface `config list` 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.common.constant import CliOutputFormat
|
|
27
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
28
23
|
|
|
29
24
|
from ..constant import SuperLinkConnectionTomlKey
|
|
30
25
|
from ..flower_config import read_flower_config
|
|
26
|
+
from ..utils import cli_output_handler, print_json_to_stdout
|
|
31
27
|
|
|
32
28
|
|
|
33
29
|
def ls(
|
|
@@ -41,14 +37,7 @@ def ls(
|
|
|
41
37
|
] = CliOutputFormat.DEFAULT,
|
|
42
38
|
) -> None:
|
|
43
39
|
"""List all SuperLink connections (alias: ls)."""
|
|
44
|
-
|
|
45
|
-
captured_output = io.StringIO()
|
|
46
|
-
config_path = None
|
|
47
|
-
|
|
48
|
-
if suppress_output:
|
|
49
|
-
redirect_output(captured_output)
|
|
50
|
-
|
|
51
|
-
try:
|
|
40
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
52
41
|
# Load Flower Config
|
|
53
42
|
config, config_path = read_flower_config()
|
|
54
43
|
|
|
@@ -59,13 +48,13 @@ def ls(
|
|
|
59
48
|
default = superlink_connections.pop(SuperLinkConnectionTomlKey.DEFAULT, None)
|
|
60
49
|
|
|
61
50
|
connection_names = list(superlink_connections.keys())
|
|
62
|
-
|
|
63
|
-
if
|
|
51
|
+
|
|
52
|
+
if is_json:
|
|
64
53
|
conn = {
|
|
65
54
|
SuperLinkConnectionTomlKey.SUPERLINK: connection_names,
|
|
66
55
|
SuperLinkConnectionTomlKey.DEFAULT: default,
|
|
67
56
|
}
|
|
68
|
-
|
|
57
|
+
print_json_to_stdout(conn)
|
|
69
58
|
else:
|
|
70
59
|
typer.secho("Flower Config file: ", fg=typer.colors.BLUE, nl=False)
|
|
71
60
|
typer.secho(f"{config_path}", fg=typer.colors.GREEN)
|
|
@@ -80,20 +69,3 @@ def ls(
|
|
|
80
69
|
nl=False,
|
|
81
70
|
)
|
|
82
71
|
typer.echo()
|
|
83
|
-
|
|
84
|
-
except Exception as err: # pylint: disable=broad-except
|
|
85
|
-
# log the error if json format requested
|
|
86
|
-
if suppress_output:
|
|
87
|
-
restore_output()
|
|
88
|
-
e_message = captured_output.getvalue()
|
|
89
|
-
print_json_error(e_message, err)
|
|
90
|
-
else:
|
|
91
|
-
raise click.ClickException(
|
|
92
|
-
f"An unexpected error occurred while listing the SuperLink "
|
|
93
|
-
f"connections in the Flower configuration file ({config_path}): {err}"
|
|
94
|
-
) from err
|
|
95
|
-
|
|
96
|
-
finally:
|
|
97
|
-
if suppress_output:
|
|
98
|
-
restore_output()
|
|
99
|
-
captured_output.close()
|
flwr/cli/config_migration.py
CHANGED
|
@@ -94,13 +94,12 @@ def _is_migratable(app: Path) -> tuple[bool, str | None]:
|
|
|
94
94
|
toml_path = app / "pyproject.toml"
|
|
95
95
|
if not toml_path.exists():
|
|
96
96
|
return False, f"No pyproject.toml found in '{app}'"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return False, err_msg
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
config, _ = load_and_validate(toml_path, check_module=False)
|
|
100
|
+
except ValueError as e:
|
|
101
|
+
return False, str(e)
|
|
102
|
+
|
|
104
103
|
try:
|
|
105
104
|
_ = config["tool"]["flwr"]["federations"]
|
|
106
105
|
return True, None
|
|
@@ -114,9 +113,7 @@ def _migrate_pyproject_toml_to_flower_config(
|
|
|
114
113
|
"""Migrate old TOML configuration to Flower config."""
|
|
115
114
|
# Load and validate the old TOML configuration
|
|
116
115
|
toml_path = app / "pyproject.toml"
|
|
117
|
-
config, _
|
|
118
|
-
if config is None:
|
|
119
|
-
raise ValueError(f"Failed to load TOML configuration: {toml_path}")
|
|
116
|
+
config, _ = load_and_validate(toml_path, check_module=False)
|
|
120
117
|
validate_federation_in_project_config(toml_federation, config)
|
|
121
118
|
|
|
122
119
|
# Construct SuperLinkConnection
|
|
@@ -128,7 +125,7 @@ def _migrate_pyproject_toml_to_flower_config(
|
|
|
128
125
|
if cert_path := toml_fed_config.get("root-certificates"):
|
|
129
126
|
if not Path(cert_path).is_absolute():
|
|
130
127
|
toml_fed_config["root-certificates"] = str(
|
|
131
|
-
(app / cert_path).resolve()
|
|
128
|
+
(app / cert_path).expanduser().resolve()
|
|
132
129
|
)
|
|
133
130
|
# Parse and write SuperLink connection
|
|
134
131
|
conn = parse_superlink_connection(toml_fed_config, name)
|
|
@@ -226,7 +223,7 @@ def migrate(
|
|
|
226
223
|
# Determine app path for migration
|
|
227
224
|
arg1 = positional_arg_1
|
|
228
225
|
app = Path(arg1) if arg1 else Path(".")
|
|
229
|
-
app = app.resolve()
|
|
226
|
+
app = app.expanduser().resolve()
|
|
230
227
|
|
|
231
228
|
# Check if migration is applicable and if legacy usage is detected
|
|
232
229
|
is_migratable, reason = _is_migratable(app)
|
flwr/cli/config_utils.py
CHANGED
|
@@ -51,7 +51,7 @@ def get_fab_metadata(fab_file: Path | bytes) -> tuple[str, str]:
|
|
|
51
51
|
def load_and_validate(
|
|
52
52
|
path: Path | None = None,
|
|
53
53
|
check_module: bool = True,
|
|
54
|
-
) -> tuple[dict[str, Any]
|
|
54
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
55
55
|
"""Load and validate pyproject.toml as dict.
|
|
56
56
|
|
|
57
57
|
Parameters
|
|
@@ -66,28 +66,34 @@ def load_and_validate(
|
|
|
66
66
|
|
|
67
67
|
Returns
|
|
68
68
|
-------
|
|
69
|
-
|
|
70
|
-
A tuple
|
|
71
|
-
|
|
69
|
+
tuple[dict[str, Any], list[str]]
|
|
70
|
+
A tuple of the loaded configuration as dictionary and a list of warnings.
|
|
71
|
+
|
|
72
|
+
Raises
|
|
73
|
+
------
|
|
74
|
+
ValueError
|
|
75
|
+
If the configuration is invalid or the file cannot be loaded.
|
|
72
76
|
"""
|
|
73
77
|
if path is None:
|
|
74
78
|
path = Path.cwd() / "pyproject.toml"
|
|
75
|
-
|
|
79
|
+
path = path.resolve()
|
|
76
80
|
config = load(path)
|
|
77
81
|
|
|
78
82
|
if config is None:
|
|
79
|
-
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
return (None, errors, [])
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Failed to load Flower App configuration in '{path}'. "
|
|
85
|
+
"File may be missing or invalid."
|
|
86
|
+
)
|
|
84
87
|
|
|
85
88
|
is_valid, errors, warnings = validate_config(config, check_module, path.parent)
|
|
86
89
|
|
|
87
90
|
if not is_valid:
|
|
88
|
-
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Invalid Flower App configuration in '{path}':\n"
|
|
93
|
+
+ "\n".join([f"- {line}" for line in errors])
|
|
94
|
+
)
|
|
89
95
|
|
|
90
|
-
return
|
|
96
|
+
return config, warnings
|
|
91
97
|
|
|
92
98
|
|
|
93
99
|
def load(toml_path: Path) -> dict[str, Any] | None:
|
flwr/cli/constant.py
CHANGED
flwr/cli/federation/ls.py
CHANGED
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
"""Flower command line interface `federation list` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
18
|
from typing import Annotated, Any
|
|
20
19
|
|
|
21
|
-
import click
|
|
22
20
|
import typer
|
|
23
21
|
from rich.console import Console
|
|
24
22
|
from rich.table import Table
|
|
@@ -28,7 +26,6 @@ from flwr.cli.config_migration import migrate
|
|
|
28
26
|
from flwr.cli.flower_config import read_superlink_connection
|
|
29
27
|
from flwr.cli.ls import _get_status_style
|
|
30
28
|
from flwr.common.constant import NOOP_ACCOUNT_NAME, CliOutputFormat
|
|
31
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
32
29
|
from flwr.common.serde import run_from_proto
|
|
33
30
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
34
31
|
ListFederationsRequest,
|
|
@@ -42,7 +39,12 @@ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
|
42
39
|
from flwr.supercore.utils import humanize_duration
|
|
43
40
|
|
|
44
41
|
from ..run_utils import RunRow, format_runs
|
|
45
|
-
from ..utils import
|
|
42
|
+
from ..utils import (
|
|
43
|
+
cli_output_handler,
|
|
44
|
+
flwr_cli_grpc_exc_handler,
|
|
45
|
+
init_channel_from_connection,
|
|
46
|
+
print_json_to_stdout,
|
|
47
|
+
)
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
@@ -69,20 +71,14 @@ def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
|
69
71
|
] = None,
|
|
70
72
|
) -> None:
|
|
71
73
|
"""List available federations or details of a specific federation (alias: ls)."""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if suppress_output:
|
|
76
|
-
redirect_output(captured_output)
|
|
77
|
-
|
|
78
|
-
# Migrate legacy usage if any
|
|
79
|
-
migrate(superlink, args=ctx.args)
|
|
74
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
75
|
+
# Migrate legacy usage if any
|
|
76
|
+
migrate(superlink, args=ctx.args)
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
# Read superlink connection configuration
|
|
79
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
80
|
+
channel = None
|
|
84
81
|
|
|
85
|
-
try:
|
|
86
82
|
try:
|
|
87
83
|
channel = init_channel_from_connection(superlink_connection)
|
|
88
84
|
stub = ControlStub(channel)
|
|
@@ -92,10 +88,9 @@ def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
|
92
88
|
typer.echo(f"📄 Showing '{federation}' federation ...")
|
|
93
89
|
members, nodes, runs = _show_federation(stub, federation)
|
|
94
90
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
data=_to_json(members=members, nodes=nodes, runs=runs)
|
|
91
|
+
if is_json:
|
|
92
|
+
print_json_to_stdout(
|
|
93
|
+
_to_json(members=members, nodes=nodes, runs=runs)
|
|
99
94
|
)
|
|
100
95
|
else:
|
|
101
96
|
Console().print(_to_members_table(members))
|
|
@@ -105,25 +100,14 @@ def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
|
105
100
|
# List federations
|
|
106
101
|
typer.echo("📄 Listing federations...")
|
|
107
102
|
federations = _list_federations(stub)
|
|
108
|
-
|
|
109
|
-
if
|
|
110
|
-
|
|
103
|
+
|
|
104
|
+
if is_json:
|
|
105
|
+
print_json_to_stdout(_to_json(federations=federations))
|
|
111
106
|
else:
|
|
112
107
|
Console().print(_to_table(federations))
|
|
113
108
|
finally:
|
|
114
109
|
if channel:
|
|
115
110
|
channel.close()
|
|
116
|
-
except Exception as err: # pylint: disable=broad-except
|
|
117
|
-
if suppress_output:
|
|
118
|
-
restore_output()
|
|
119
|
-
e_message = captured_output.getvalue()
|
|
120
|
-
print_json_error(e_message, err)
|
|
121
|
-
else:
|
|
122
|
-
raise click.ClickException(str(err)) from None
|
|
123
|
-
finally:
|
|
124
|
-
if suppress_output:
|
|
125
|
-
restore_output()
|
|
126
|
-
captured_output.close()
|
|
127
111
|
|
|
128
112
|
|
|
129
113
|
def _list_federations(stub: ControlStub) -> list[Federation]:
|
|
@@ -206,7 +190,7 @@ def _show_federation(
|
|
|
206
190
|
Returns
|
|
207
191
|
-------
|
|
208
192
|
tuple[list[str], list[NodeInfo], list[RunRow]]
|
|
209
|
-
A tuple containing (
|
|
193
|
+
A tuple containing (account_names, nodes, runs).
|
|
210
194
|
"""
|
|
211
195
|
with flwr_cli_grpc_exc_handler():
|
|
212
196
|
res: ShowFederationResponse = stub.ShowFederation(
|
|
@@ -217,16 +201,20 @@ def _show_federation(
|
|
|
217
201
|
runs = [run_from_proto(run_proto) for run_proto in fed_proto.runs]
|
|
218
202
|
formatted_runs = format_runs(runs, res.now)
|
|
219
203
|
|
|
220
|
-
return
|
|
204
|
+
return (
|
|
205
|
+
[account.name for account in fed_proto.accounts],
|
|
206
|
+
list(fed_proto.nodes),
|
|
207
|
+
formatted_runs,
|
|
208
|
+
)
|
|
221
209
|
|
|
222
210
|
|
|
223
|
-
def _to_members_table(
|
|
211
|
+
def _to_members_table(account_names: list[str]) -> Table:
|
|
224
212
|
"""Format the provided list of federation members as a rich Table.
|
|
225
213
|
|
|
226
214
|
Parameters
|
|
227
215
|
----------
|
|
228
|
-
|
|
229
|
-
List of member account
|
|
216
|
+
account_names : list[str]
|
|
217
|
+
List of member account names.
|
|
230
218
|
|
|
231
219
|
Returns
|
|
232
220
|
-------
|
|
@@ -236,12 +224,12 @@ def _to_members_table(member_aids: list[str]) -> Table:
|
|
|
236
224
|
table = Table(title="Federation Members", header_style="bold cyan", show_lines=True)
|
|
237
225
|
|
|
238
226
|
table.add_column(
|
|
239
|
-
Text("Account
|
|
227
|
+
Text("Account Name", justify="center"), style="bright_black", no_wrap=True
|
|
240
228
|
)
|
|
241
229
|
table.add_column(Text("Role", justify="center"), style="bright_black", no_wrap=True)
|
|
242
230
|
|
|
243
|
-
for
|
|
244
|
-
table.add_row(
|
|
231
|
+
for account_name in account_names:
|
|
232
|
+
table.add_row(account_name, "Member")
|
|
245
233
|
|
|
246
234
|
return table
|
|
247
235
|
|
flwr/cli/flower_config.py
CHANGED
|
@@ -48,6 +48,7 @@ from flwr.supercore.utils import get_flwr_home
|
|
|
48
48
|
def _parse_simulation_options(options: dict[str, Any]) -> SuperLinkSimulationOptions:
|
|
49
49
|
"""Parse simulation options from a dictionary in a SuperLink connection."""
|
|
50
50
|
num_supernodes = options.get(SuperLinkSimulationOptionsTomlKey.NUM_SUPERNODES)
|
|
51
|
+
verbose = options.get(SuperLinkSimulationOptionsTomlKey.VERBOSE)
|
|
51
52
|
# Validation handled in SuperLinkSimulationOptions.__post_init__
|
|
52
53
|
|
|
53
54
|
backend_dict = options.get(SuperLinkSimulationOptionsTomlKey.BACKEND)
|
|
@@ -94,6 +95,7 @@ def _parse_simulation_options(options: dict[str, Any]) -> SuperLinkSimulationOpt
|
|
|
94
95
|
return SuperLinkSimulationOptions(
|
|
95
96
|
num_supernodes=cast(int, num_supernodes),
|
|
96
97
|
backend=simulation_backend,
|
|
98
|
+
verbose=verbose,
|
|
97
99
|
)
|
|
98
100
|
|
|
99
101
|
|
|
@@ -105,6 +107,9 @@ def _serialize_simulation_options(
|
|
|
105
107
|
SuperLinkSimulationOptionsTomlKey.NUM_SUPERNODES: options.num_supernodes
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
if options.verbose is not None:
|
|
111
|
+
options_dict[SuperLinkSimulationOptionsTomlKey.VERBOSE] = options.verbose
|
|
112
|
+
|
|
108
113
|
if options.backend is not None:
|
|
109
114
|
backend = options.backend
|
|
110
115
|
|
flwr/cli/install.py
CHANGED
|
@@ -63,7 +63,7 @@ def install(
|
|
|
63
63
|
if source is None:
|
|
64
64
|
source = Path(typer.prompt("Enter the source FAB file"))
|
|
65
65
|
|
|
66
|
-
source = source.resolve()
|
|
66
|
+
source = source.expanduser().resolve()
|
|
67
67
|
if not source.exists() or not source.is_file():
|
|
68
68
|
raise click.ClickException(
|
|
69
69
|
f"The source {source} does not exist or is not a file."
|
|
@@ -72,7 +72,10 @@ def install(
|
|
|
72
72
|
if source.suffix != ".fab":
|
|
73
73
|
raise click.ClickException(f"The source {source} is not a `.fab` file.")
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
try:
|
|
76
|
+
install_from_fab(source, flwr_dir)
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
raise click.ClickException(str(e)) from None
|
|
76
79
|
|
|
77
80
|
|
|
78
81
|
def install_from_fab(
|
|
@@ -171,10 +174,7 @@ def validate_and_install(
|
|
|
171
174
|
click.ClickException
|
|
172
175
|
If configuration is invalid or metadata doesn't match.
|
|
173
176
|
"""
|
|
174
|
-
config, _
|
|
175
|
-
|
|
176
|
-
if config is None:
|
|
177
|
-
raise click.ClickException("Invalid config inside FAB file.")
|
|
177
|
+
config, _ = load_and_validate(project_dir / "pyproject.toml", check_module=False)
|
|
178
178
|
|
|
179
179
|
fab_id, version = get_metadata_from_config(config)
|
|
180
180
|
publisher, project_name = fab_id.split("/")
|
flwr/cli/ls.py
CHANGED
|
@@ -15,11 +15,9 @@
|
|
|
15
15
|
"""Flower command line interface `ls` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
18
|
import json
|
|
20
19
|
from typing import Annotated
|
|
21
20
|
|
|
22
|
-
import click
|
|
23
21
|
import typer
|
|
24
22
|
from rich.console import Console
|
|
25
23
|
from rich.table import Table
|
|
@@ -29,7 +27,6 @@ from flwr.cli.config_migration import migrate, warn_if_federation_config_overrid
|
|
|
29
27
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
30
28
|
from flwr.cli.flower_config import read_superlink_connection
|
|
31
29
|
from flwr.common.constant import CliOutputFormat, Status, SubStatus
|
|
32
|
-
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
33
30
|
from flwr.common.serde import run_from_proto
|
|
34
31
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
35
32
|
ListRunsRequest,
|
|
@@ -39,7 +36,12 @@ from flwr.proto.control_pb2_grpc import ControlStub
|
|
|
39
36
|
from flwr.supercore.utils import humanize_bytes, humanize_duration
|
|
40
37
|
|
|
41
38
|
from .run_utils import RunRow, format_runs
|
|
42
|
-
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
|
+
)
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
@@ -92,23 +94,17 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
92
94
|
|
|
93
95
|
All timestamps follow ISO 8601, UTC and are formatted as ``YYYY-MM-DD HH:MM:SSZ``.
|
|
94
96
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if suppress_output:
|
|
99
|
-
redirect_output(captured_output)
|
|
100
|
-
|
|
101
|
-
# Warn `--federation-config` is ignored
|
|
102
|
-
warn_if_federation_config_overrides(federation_config_overrides)
|
|
97
|
+
with cli_output_handler(output_format=output_format) as is_json:
|
|
98
|
+
# Warn `--federation-config` is ignored
|
|
99
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
103
100
|
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
# Migrate legacy usage if any
|
|
102
|
+
migrate(superlink, args=ctx.args)
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
# Read superlink connection configuration
|
|
105
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
106
|
+
channel = None
|
|
110
107
|
|
|
111
|
-
try:
|
|
112
108
|
try:
|
|
113
109
|
if runs and run_id is not None:
|
|
114
110
|
raise ValueError(
|
|
@@ -125,9 +121,9 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
125
121
|
else:
|
|
126
122
|
typer.echo("📄 Listing all runs...")
|
|
127
123
|
formatted_runs = _list_runs(stub)
|
|
128
|
-
|
|
129
|
-
if
|
|
130
|
-
|
|
124
|
+
|
|
125
|
+
if is_json:
|
|
126
|
+
print_json_to_stdout(_to_json(formatted_runs))
|
|
131
127
|
else:
|
|
132
128
|
if run_id is not None:
|
|
133
129
|
Console().print(_to_detail_table(formatted_runs[0]))
|
|
@@ -136,17 +132,6 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
136
132
|
finally:
|
|
137
133
|
if channel:
|
|
138
134
|
channel.close()
|
|
139
|
-
except Exception as err: # pylint: disable=broad-except
|
|
140
|
-
if suppress_output:
|
|
141
|
-
restore_output()
|
|
142
|
-
e_message = captured_output.getvalue()
|
|
143
|
-
print_json_error(e_message, err)
|
|
144
|
-
else:
|
|
145
|
-
raise click.ClickException(str(err)) from None
|
|
146
|
-
finally:
|
|
147
|
-
if suppress_output:
|
|
148
|
-
restore_output()
|
|
149
|
-
captured_output.close()
|
|
150
135
|
|
|
151
136
|
|
|
152
137
|
def _get_status_style(status_text: str) -> str:
|