flwr-nightly 1.26.0.dev20260123__py3-none-any.whl → 1.26.0.dev20260127__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.py +0 -2
- flwr/cli/app_cmd/publish.py +18 -44
- flwr/cli/app_cmd/review.py +8 -25
- flwr/cli/auth_plugin/oidc_cli_plugin.py +3 -6
- flwr/cli/build.py +8 -19
- flwr/cli/config/ls.py +8 -13
- flwr/cli/config_migration.py +4 -4
- flwr/cli/config_utils.py +19 -39
- flwr/cli/constant.py +0 -2
- flwr/cli/federation/ls.py +3 -7
- flwr/cli/flower_config.py +28 -51
- flwr/cli/install.py +18 -57
- flwr/cli/log.py +2 -2
- flwr/cli/login/login.py +5 -26
- flwr/cli/ls.py +3 -7
- flwr/cli/new/new.py +9 -29
- flwr/cli/pull.py +3 -7
- flwr/cli/run/run.py +6 -15
- flwr/cli/stop.py +5 -17
- flwr/cli/supernode/register.py +6 -22
- flwr/cli/supernode/unregister.py +3 -13
- flwr/cli/typing.py +0 -19
- flwr/cli/utils.py +68 -114
- flwr/common/config.py +5 -9
- flwr/common/constant.py +2 -0
- flwr/server/superlink/fleet/message_handler/message_handler.py +6 -5
- flwr/server/superlink/linkstate/sql_linkstate.py +38 -10
- flwr/supercore/app_utils.py +2 -1
- flwr/supercore/state/schema/README.md +2 -6
- flwr/superlink/servicer/control/control_servicer.py +11 -12
- {flwr_nightly-1.26.0.dev20260123.dist-info → flwr_nightly-1.26.0.dev20260127.dist-info}/METADATA +1 -1
- {flwr_nightly-1.26.0.dev20260123.dist-info → flwr_nightly-1.26.0.dev20260127.dist-info}/RECORD +34 -34
- {flwr_nightly-1.26.0.dev20260123.dist-info → flwr_nightly-1.26.0.dev20260127.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.26.0.dev20260123.dist-info → flwr_nightly-1.26.0.dev20260127.dist-info}/entry_points.txt +0 -0
flwr/cli/app.py
CHANGED
|
@@ -27,7 +27,6 @@ from .app_cmd import review as app_review
|
|
|
27
27
|
from .build import build
|
|
28
28
|
from .config import ls as config_list
|
|
29
29
|
from .federation import ls as federation_list
|
|
30
|
-
from .flower_config import init_flwr_config
|
|
31
30
|
from .install import install
|
|
32
31
|
from .log import log
|
|
33
32
|
from .login import login
|
|
@@ -115,7 +114,6 @@ def main(
|
|
|
115
114
|
),
|
|
116
115
|
) -> None:
|
|
117
116
|
"""Flower CLI."""
|
|
118
|
-
init_flwr_config()
|
|
119
117
|
if version:
|
|
120
118
|
typer.secho(f"Flower version: {package_version}", fg="blue")
|
|
121
119
|
raise typer.Exit()
|
flwr/cli/app_cmd/publish.py
CHANGED
|
@@ -19,6 +19,7 @@ from contextlib import ExitStack
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import IO, Annotated
|
|
21
21
|
|
|
22
|
+
import click
|
|
22
23
|
import requests
|
|
23
24
|
import typer
|
|
24
25
|
from requests import Response
|
|
@@ -62,12 +63,7 @@ def publish(
|
|
|
62
63
|
auth_plugin = load_cli_auth_plugin_from_connection(SUPERGRID_ADDRESS)
|
|
63
64
|
auth_plugin.load_tokens()
|
|
64
65
|
if not isinstance(auth_plugin, OidcCliPlugin) or not auth_plugin.access_token:
|
|
65
|
-
|
|
66
|
-
"❌ Please log in before publishing app.",
|
|
67
|
-
fg=typer.colors.RED,
|
|
68
|
-
err=True,
|
|
69
|
-
)
|
|
70
|
-
raise typer.Exit(code=1)
|
|
66
|
+
raise click.ClickException("Please log in before publishing app.")
|
|
71
67
|
|
|
72
68
|
# Load token from the plugin
|
|
73
69
|
token = auth_plugin.access_token
|
|
@@ -82,19 +78,17 @@ def publish(
|
|
|
82
78
|
try:
|
|
83
79
|
resp = _post_files(files_param, token)
|
|
84
80
|
except requests.RequestException as err:
|
|
85
|
-
|
|
86
|
-
raise typer.Exit(code=1) from err
|
|
81
|
+
raise click.ClickException(f"Network error: {err}") from err
|
|
87
82
|
|
|
88
83
|
if resp.ok:
|
|
89
84
|
typer.secho("🎊 Upload successful", fg=typer.colors.GREEN, bold=True)
|
|
90
85
|
return # success
|
|
91
86
|
|
|
92
87
|
# Error path:
|
|
93
|
-
msg = f"
|
|
88
|
+
msg = f"Upload failed with status {resp.status_code}"
|
|
94
89
|
if resp.text:
|
|
95
90
|
msg += f": {resp.text}"
|
|
96
|
-
|
|
97
|
-
raise typer.Exit(code=1)
|
|
91
|
+
raise click.ClickException(msg)
|
|
98
92
|
|
|
99
93
|
|
|
100
94
|
def _depth_of(relative_path_to_root: Path) -> int:
|
|
@@ -138,14 +132,9 @@ def _collect_file_paths(root: Path) -> list[Path]:
|
|
|
138
132
|
|
|
139
133
|
# Check max depth
|
|
140
134
|
if _depth_of(relative_path) > MAX_DIR_DEPTH:
|
|
141
|
-
|
|
142
|
-
f"
|
|
143
|
-
f"exceeds the maximum directory depth "
|
|
144
|
-
f"of {MAX_DIR_DEPTH}.",
|
|
145
|
-
fg=typer.colors.RED,
|
|
146
|
-
err=True,
|
|
135
|
+
raise click.ClickException(
|
|
136
|
+
f"'{path}' exceeds the maximum directory depth of {MAX_DIR_DEPTH}."
|
|
147
137
|
)
|
|
148
|
-
raise typer.Exit(code=2)
|
|
149
138
|
|
|
150
139
|
file_paths.append(path)
|
|
151
140
|
|
|
@@ -160,21 +149,15 @@ def _validate_files(file_paths: list[Path]) -> None:
|
|
|
160
149
|
Checks file count, individual file size, total size, and UTF-8 encoding.
|
|
161
150
|
"""
|
|
162
151
|
if len(file_paths) == 0:
|
|
163
|
-
|
|
152
|
+
raise click.ClickException(
|
|
164
153
|
"Nothing to upload: no files matched after applying .gitignore and "
|
|
165
|
-
"allowed extensions."
|
|
166
|
-
fg=typer.colors.RED,
|
|
167
|
-
err=True,
|
|
154
|
+
"allowed extensions."
|
|
168
155
|
)
|
|
169
|
-
raise typer.Exit(code=2)
|
|
170
156
|
|
|
171
157
|
if len(file_paths) > MAX_FILE_COUNT:
|
|
172
|
-
|
|
173
|
-
f"Too many files: {len(file_paths)} > allowed maximum of {MAX_FILE_COUNT}."
|
|
174
|
-
fg=typer.colors.RED,
|
|
175
|
-
err=True,
|
|
158
|
+
raise click.ClickException(
|
|
159
|
+
f"Too many files: {len(file_paths)} > allowed maximum of {MAX_FILE_COUNT}."
|
|
176
160
|
)
|
|
177
|
-
raise typer.Exit(code=2)
|
|
178
161
|
|
|
179
162
|
# Calculate files size
|
|
180
163
|
total_size = 0
|
|
@@ -184,34 +167,25 @@ def _validate_files(file_paths: list[Path]) -> None:
|
|
|
184
167
|
|
|
185
168
|
# Check single file size
|
|
186
169
|
if file_size > MAX_FILE_BYTES:
|
|
187
|
-
|
|
170
|
+
raise click.ClickException(
|
|
188
171
|
f"File too large: '{path.as_posix()}' is {file_size:,} bytes, "
|
|
189
|
-
f"exceeding the per-file limit of {MAX_FILE_BYTES:,} bytes."
|
|
190
|
-
fg=typer.colors.RED,
|
|
191
|
-
err=True,
|
|
172
|
+
f"exceeding the per-file limit of {MAX_FILE_BYTES:,} bytes."
|
|
192
173
|
)
|
|
193
|
-
raise typer.Exit(code=2)
|
|
194
174
|
|
|
195
175
|
# Ensure we can decode as UTF-8.
|
|
196
176
|
try:
|
|
197
177
|
path.read_text(encoding=UTF8)
|
|
198
178
|
except UnicodeDecodeError as err:
|
|
199
|
-
|
|
200
|
-
f"Encoding error: '{path}' is not UTF-8 encoded."
|
|
201
|
-
|
|
202
|
-
err=True,
|
|
203
|
-
)
|
|
204
|
-
raise typer.Exit(code=2) from err
|
|
179
|
+
raise click.ClickException(
|
|
180
|
+
f"Encoding error: '{path}' is not UTF-8 encoded."
|
|
181
|
+
) from err
|
|
205
182
|
|
|
206
183
|
# Check total files size
|
|
207
184
|
if total_size > MAX_TOTAL_BYTES:
|
|
208
|
-
|
|
185
|
+
raise click.ClickException(
|
|
209
186
|
"Total size of all files is too large: "
|
|
210
|
-
f"{total_size:,} bytes > {MAX_TOTAL_BYTES:,} bytes."
|
|
211
|
-
fg=typer.colors.RED,
|
|
212
|
-
err=True,
|
|
187
|
+
f"{total_size:,} bytes > {MAX_TOTAL_BYTES:,} bytes."
|
|
213
188
|
)
|
|
214
|
-
raise typer.Exit(code=2)
|
|
215
189
|
|
|
216
190
|
# Print validation passed prompt
|
|
217
191
|
typer.echo(typer.style("✅ Validation passed", fg=typer.colors.GREEN, bold=True))
|
flwr/cli/app_cmd/review.py
CHANGED
|
@@ -21,6 +21,7 @@ import re
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Annotated
|
|
23
23
|
|
|
24
|
+
import click
|
|
24
25
|
import requests
|
|
25
26
|
import typer
|
|
26
27
|
from cryptography.exceptions import UnsupportedAlgorithm
|
|
@@ -60,12 +61,7 @@ def review(
|
|
|
60
61
|
|
|
61
62
|
auth_plugin.load_tokens()
|
|
62
63
|
if not isinstance(auth_plugin, OidcCliPlugin) or not auth_plugin.access_token:
|
|
63
|
-
|
|
64
|
-
"❌ Please log in before reviewing app.",
|
|
65
|
-
fg=typer.colors.RED,
|
|
66
|
-
err=True,
|
|
67
|
-
)
|
|
68
|
-
raise typer.Exit(code=1)
|
|
64
|
+
raise click.ClickException("Please log in before reviewing app.")
|
|
69
65
|
|
|
70
66
|
# Load token from the plugin
|
|
71
67
|
token = auth_plugin.access_token
|
|
@@ -74,8 +70,7 @@ def review(
|
|
|
74
70
|
try:
|
|
75
71
|
app_id, app_version = parse_app_spec(app_spec)
|
|
76
72
|
except ValueError as e:
|
|
77
|
-
|
|
78
|
-
raise typer.Exit(code=1) from e
|
|
73
|
+
raise click.ClickException(str(e)) from e
|
|
79
74
|
|
|
80
75
|
# Download FAB
|
|
81
76
|
typer.secho("Downloading FAB... ", fg=typer.colors.BLUE)
|
|
@@ -83,8 +78,7 @@ def review(
|
|
|
83
78
|
try:
|
|
84
79
|
presigned_url, _ = request_download_link(app_id, app_version, url, "fab_url")
|
|
85
80
|
except ValueError as e:
|
|
86
|
-
|
|
87
|
-
raise typer.Exit(code=1) from e
|
|
81
|
+
raise click.ClickException(str(e)) from e
|
|
88
82
|
|
|
89
83
|
fab_bytes = _download_fab(presigned_url)
|
|
90
84
|
|
|
@@ -170,12 +164,7 @@ def _download_fab(url: str) -> bytes:
|
|
|
170
164
|
r = requests.get(url, timeout=60)
|
|
171
165
|
r.raise_for_status()
|
|
172
166
|
except requests.RequestException as e:
|
|
173
|
-
|
|
174
|
-
f"❌ FAB download failed: {e}",
|
|
175
|
-
fg=typer.colors.RED,
|
|
176
|
-
err=True,
|
|
177
|
-
)
|
|
178
|
-
raise typer.Exit(code=1) from e
|
|
167
|
+
raise click.ClickException(f"FAB download failed: {e}") from e
|
|
179
168
|
return r.content
|
|
180
169
|
|
|
181
170
|
|
|
@@ -209,20 +198,14 @@ def _submit_review(
|
|
|
209
198
|
try:
|
|
210
199
|
resp = requests.post(url, headers=headers, json=payload, timeout=120)
|
|
211
200
|
except requests.RequestException as e:
|
|
212
|
-
|
|
213
|
-
f"❌ Network error while submitting review: {e}",
|
|
214
|
-
fg=typer.colors.RED,
|
|
215
|
-
err=True,
|
|
216
|
-
)
|
|
217
|
-
raise typer.Exit(code=1) from e
|
|
201
|
+
raise click.ClickException(f"Network error while submitting review: {e}") from e
|
|
218
202
|
|
|
219
203
|
if resp.ok:
|
|
220
204
|
typer.secho("🎊 Review submitted", fg=typer.colors.GREEN, bold=True)
|
|
221
205
|
return
|
|
222
206
|
|
|
223
207
|
# Error path:
|
|
224
|
-
msg = f"
|
|
208
|
+
msg = f"Review submission failed (HTTP {resp.status_code})"
|
|
225
209
|
if resp.text:
|
|
226
210
|
msg += f": {resp.text}"
|
|
227
|
-
|
|
228
|
-
raise typer.Exit(code=1)
|
|
211
|
+
raise click.ClickException(msg)
|
|
@@ -19,6 +19,7 @@ import time
|
|
|
19
19
|
import webbrowser
|
|
20
20
|
from collections.abc import Sequence
|
|
21
21
|
|
|
22
|
+
import click
|
|
22
23
|
import typer
|
|
23
24
|
|
|
24
25
|
from flwr.cli.constant import (
|
|
@@ -137,13 +138,9 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
|
137
138
|
) -> Sequence[tuple[str, str | bytes]]:
|
|
138
139
|
"""Write authentication tokens to the provided metadata."""
|
|
139
140
|
if self.access_token is None or self.refresh_token is None:
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
fg=typer.colors.RED,
|
|
143
|
-
bold=True,
|
|
144
|
-
err=True,
|
|
141
|
+
raise click.ClickException(
|
|
142
|
+
"Missing authentication tokens. Please login first."
|
|
145
143
|
)
|
|
146
|
-
raise typer.Exit(code=1)
|
|
147
144
|
|
|
148
145
|
return list(metadata) + [
|
|
149
146
|
(ACCESS_TOKEN_KEY, self.access_token),
|
flwr/cli/build.py
CHANGED
|
@@ -21,6 +21,7 @@ from io import BytesIO
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Annotated, Any
|
|
23
23
|
|
|
24
|
+
import click
|
|
24
25
|
import pathspec
|
|
25
26
|
import tomli
|
|
26
27
|
import tomli_w
|
|
@@ -107,35 +108,23 @@ def build(
|
|
|
107
108
|
|
|
108
109
|
app = app.resolve()
|
|
109
110
|
if not app.is_dir():
|
|
110
|
-
|
|
111
|
-
f"
|
|
112
|
-
fg=typer.colors.RED,
|
|
113
|
-
bold=True,
|
|
114
|
-
err=True,
|
|
111
|
+
raise click.ClickException(
|
|
112
|
+
f"The path {app} is not a valid path to a Flower app."
|
|
115
113
|
)
|
|
116
|
-
raise typer.Exit(code=1)
|
|
117
114
|
|
|
118
115
|
if not is_valid_project_name(app.name):
|
|
119
|
-
|
|
120
|
-
f"
|
|
116
|
+
raise click.ClickException(
|
|
117
|
+
f"The project name {app.name} is invalid, "
|
|
121
118
|
"a valid project name must start with a letter, "
|
|
122
|
-
"and can only contain letters, digits, and hyphens."
|
|
123
|
-
fg=typer.colors.RED,
|
|
124
|
-
bold=True,
|
|
125
|
-
err=True,
|
|
119
|
+
"and can only contain letters, digits, and hyphens."
|
|
126
120
|
)
|
|
127
|
-
raise typer.Exit(code=1)
|
|
128
121
|
|
|
129
122
|
config, errors, warnings = load_and_validate(app / "pyproject.toml")
|
|
130
123
|
if config is None:
|
|
131
|
-
|
|
124
|
+
raise click.ClickException(
|
|
132
125
|
"Project configuration could not be loaded.\npyproject.toml is invalid:\n"
|
|
133
|
-
+ "\n".join([f"- {line}" for line in errors])
|
|
134
|
-
fg=typer.colors.RED,
|
|
135
|
-
bold=True,
|
|
136
|
-
err=True,
|
|
126
|
+
+ "\n".join([f"- {line}" for line in errors])
|
|
137
127
|
)
|
|
138
|
-
raise typer.Exit(code=1)
|
|
139
128
|
|
|
140
129
|
if warnings:
|
|
141
130
|
typer.secho(
|
flwr/cli/config/ls.py
CHANGED
|
@@ -19,6 +19,7 @@ import io
|
|
|
19
19
|
import json
|
|
20
20
|
from typing import Annotated
|
|
21
21
|
|
|
22
|
+
import click
|
|
22
23
|
import typer
|
|
23
24
|
from rich.console import Console
|
|
24
25
|
|
|
@@ -66,6 +67,8 @@ def ls(
|
|
|
66
67
|
}
|
|
67
68
|
Console().print_json(json.dumps(conn))
|
|
68
69
|
else:
|
|
70
|
+
typer.secho("Flower Config file: ", fg=typer.colors.BLUE, nl=False)
|
|
71
|
+
typer.secho(f"{config_path}", fg=typer.colors.GREEN)
|
|
69
72
|
typer.secho("SuperLink connections:", fg=typer.colors.BLUE)
|
|
70
73
|
# List SuperLink connections and highlight default
|
|
71
74
|
for k in connection_names:
|
|
@@ -77,26 +80,18 @@ def ls(
|
|
|
77
80
|
nl=False,
|
|
78
81
|
)
|
|
79
82
|
typer.echo()
|
|
80
|
-
except typer.Exit as err:
|
|
81
|
-
# log the error if json format requested
|
|
82
|
-
# else do nothing since it will be logged by typer
|
|
83
|
-
if suppress_output:
|
|
84
|
-
restore_output()
|
|
85
|
-
e_message = captured_output.getvalue()
|
|
86
|
-
print_json_error(e_message, err)
|
|
87
83
|
|
|
88
84
|
except Exception as err: # pylint: disable=broad-except
|
|
85
|
+
# log the error if json format requested
|
|
89
86
|
if suppress_output:
|
|
90
87
|
restore_output()
|
|
91
88
|
e_message = captured_output.getvalue()
|
|
92
89
|
print_json_error(e_message, err)
|
|
93
90
|
else:
|
|
94
|
-
|
|
95
|
-
f"
|
|
96
|
-
f"connections in the Flower configuration file ({config_path}): {err}"
|
|
97
|
-
|
|
98
|
-
err=True,
|
|
99
|
-
)
|
|
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
|
|
100
95
|
|
|
101
96
|
finally:
|
|
102
97
|
if suppress_output:
|
flwr/cli/config_migration.py
CHANGED
|
@@ -31,7 +31,7 @@ from .flower_config import (
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
CONFIG_MIGRATION_NOTICE = """
|
|
34
|
-
|
|
34
|
+
#######################################################################
|
|
35
35
|
# CONFIGURATION MIGRATION NOTICE:
|
|
36
36
|
#
|
|
37
37
|
# What was previously called "federation config" for SuperLink
|
|
@@ -43,8 +43,8 @@ CONFIG_MIGRATION_NOTICE = """
|
|
|
43
43
|
# The entries below are commented out intentionally and are kept
|
|
44
44
|
# only as a migration reference.
|
|
45
45
|
#
|
|
46
|
-
# Docs:
|
|
47
|
-
|
|
46
|
+
# Docs: https://flower.ai/docs/framework/ref-flower-configuration.html
|
|
47
|
+
#######################################################################
|
|
48
48
|
|
|
49
49
|
"""
|
|
50
50
|
|
|
@@ -53,7 +53,7 @@ CLI_NOTICE = (
|
|
|
53
53
|
+ "We detected legacy usage of this command that relies on connection\n"
|
|
54
54
|
+ "settings from your pyproject.toml.\n\n"
|
|
55
55
|
+ "Flower will migrate any relevant settings to the new Flower config.\n\n"
|
|
56
|
-
+ "Learn more: https://flower.ai/docs\n"
|
|
56
|
+
+ "Learn more: https://flower.ai/docs/framework/ref-flower-configuration.html\n"
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
flwr/cli/config_utils.py
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
+
import click
|
|
21
22
|
import tomli
|
|
22
|
-
import typer
|
|
23
23
|
|
|
24
24
|
from flwr.cli.typing import SuperLinkConnection
|
|
25
25
|
from flwr.common.config import (
|
|
@@ -137,22 +137,18 @@ def validate_federation_in_project_config(
|
|
|
137
137
|
|
|
138
138
|
Raises
|
|
139
139
|
------
|
|
140
|
-
|
|
140
|
+
click.ClickException
|
|
141
141
|
If no federation name provided and no default found, or if federation
|
|
142
142
|
doesn't exist in config.
|
|
143
143
|
"""
|
|
144
144
|
federation = federation or config["tool"]["flwr"]["federations"].get("default")
|
|
145
145
|
|
|
146
146
|
if federation is None:
|
|
147
|
-
|
|
148
|
-
"
|
|
147
|
+
raise click.ClickException(
|
|
148
|
+
"No federation name was provided and the project's `pyproject.toml` "
|
|
149
149
|
"doesn't declare a default federation (with an Control API address or an "
|
|
150
|
-
"`options.num-supernodes` value)."
|
|
151
|
-
fg=typer.colors.RED,
|
|
152
|
-
bold=True,
|
|
153
|
-
err=True,
|
|
150
|
+
"`options.num-supernodes` value)."
|
|
154
151
|
)
|
|
155
|
-
raise typer.Exit(code=1)
|
|
156
152
|
|
|
157
153
|
# Validate the federation exists in the configuration
|
|
158
154
|
federation_config = config["tool"]["flwr"]["federations"].get(federation)
|
|
@@ -160,15 +156,11 @@ def validate_federation_in_project_config(
|
|
|
160
156
|
available_feds = {
|
|
161
157
|
fed for fed in config["tool"]["flwr"]["federations"] if fed != "default"
|
|
162
158
|
}
|
|
163
|
-
|
|
164
|
-
f"
|
|
159
|
+
raise click.ClickException(
|
|
160
|
+
f"There is no `{federation}` federation declared in the "
|
|
165
161
|
"`pyproject.toml`.\n The following federations were found:\n\n"
|
|
166
|
-
+ "\n".join(available_feds)
|
|
167
|
-
fg=typer.colors.RED,
|
|
168
|
-
bold=True,
|
|
169
|
-
err=True,
|
|
162
|
+
+ "\n".join(available_feds)
|
|
170
163
|
)
|
|
171
|
-
raise typer.Exit(code=1)
|
|
172
164
|
|
|
173
165
|
# Override the federation configuration if provided
|
|
174
166
|
if overrides:
|
|
@@ -198,32 +190,24 @@ def load_certificate_in_connection(
|
|
|
198
190
|
------
|
|
199
191
|
ValueError
|
|
200
192
|
If required TLS settings are missing.
|
|
201
|
-
|
|
193
|
+
click.ClickException
|
|
202
194
|
If the configuration is invalid or the certificate file cannot be read.
|
|
203
195
|
"""
|
|
204
196
|
# Process root certificates
|
|
205
197
|
if root_certificates := connection.root_certificates:
|
|
206
198
|
if connection.insecure:
|
|
207
|
-
|
|
208
|
-
"
|
|
209
|
-
"is set to `True`."
|
|
210
|
-
fg=typer.colors.RED,
|
|
211
|
-
bold=True,
|
|
212
|
-
err=True,
|
|
199
|
+
raise click.ClickException(
|
|
200
|
+
"`root-certificates` were provided but the `insecure` parameter "
|
|
201
|
+
"is set to `True`."
|
|
213
202
|
)
|
|
214
|
-
raise typer.Exit(code=1)
|
|
215
203
|
|
|
216
204
|
# TLS is enabled with self-signed certificates: attempt to read the file
|
|
217
205
|
try:
|
|
218
206
|
root_certificates_bytes = Path(root_certificates).read_bytes()
|
|
219
207
|
except Exception as e:
|
|
220
|
-
|
|
221
|
-
f"
|
|
222
|
-
|
|
223
|
-
bold=True,
|
|
224
|
-
err=True,
|
|
225
|
-
)
|
|
226
|
-
raise typer.Exit(code=1) from e
|
|
208
|
+
raise click.ClickException(
|
|
209
|
+
f"Failed to read certificate file `{root_certificates}`: {e}"
|
|
210
|
+
) from e
|
|
227
211
|
else:
|
|
228
212
|
root_certificates_bytes = None
|
|
229
213
|
|
|
@@ -245,7 +229,7 @@ def get_insecure_flag(federation_config: dict[str, Any]) -> bool:
|
|
|
245
229
|
|
|
246
230
|
Raises
|
|
247
231
|
------
|
|
248
|
-
|
|
232
|
+
click.ClickException
|
|
249
233
|
If insecure value is not a boolean type.
|
|
250
234
|
"""
|
|
251
235
|
insecure_value = federation_config.get("insecure")
|
|
@@ -255,11 +239,7 @@ def get_insecure_flag(federation_config: dict[str, Any]) -> bool:
|
|
|
255
239
|
return False
|
|
256
240
|
if isinstance(insecure_value, bool):
|
|
257
241
|
return insecure_value
|
|
258
|
-
|
|
259
|
-
"
|
|
260
|
-
"(`insecure = true` or `insecure = false`)"
|
|
261
|
-
fg=typer.colors.RED,
|
|
262
|
-
bold=True,
|
|
263
|
-
err=True,
|
|
242
|
+
raise click.ClickException(
|
|
243
|
+
"Invalid type for `insecure`: expected a boolean if provided. "
|
|
244
|
+
"(`insecure = true` or `insecure = false`)"
|
|
264
245
|
)
|
|
265
|
-
raise typer.Exit(code=1)
|
flwr/cli/constant.py
CHANGED
|
@@ -57,7 +57,6 @@ class SuperLinkConnectionTomlKey:
|
|
|
57
57
|
ADDRESS = "address"
|
|
58
58
|
ROOT_CERTIFICATES = "root-certificates"
|
|
59
59
|
INSECURE = "insecure"
|
|
60
|
-
ENABLE_ACCOUNT_AUTH = "enable-account-auth"
|
|
61
60
|
FEDERATION = "federation"
|
|
62
61
|
OPTIONS = "options"
|
|
63
62
|
|
|
@@ -102,7 +101,6 @@ default = "local"
|
|
|
102
101
|
|
|
103
102
|
[superlink.supergrid]
|
|
104
103
|
address = "{SUPERGRID_ADDRESS}"
|
|
105
|
-
enable-account-auth = true
|
|
106
104
|
federation = "YOUR-FEDERATION-HERE"
|
|
107
105
|
|
|
108
106
|
[superlink.local]
|
flwr/cli/federation/ls.py
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import io
|
|
19
19
|
from typing import Annotated, Any
|
|
20
20
|
|
|
21
|
+
import click
|
|
21
22
|
import typer
|
|
22
23
|
from rich.console import Console
|
|
23
24
|
from rich.table import Table
|
|
@@ -112,18 +113,13 @@ def ls( # pylint: disable=R0914, R0913, R0917, R0912
|
|
|
112
113
|
finally:
|
|
113
114
|
if channel:
|
|
114
115
|
channel.close()
|
|
115
|
-
except
|
|
116
|
+
except Exception as err: # pylint: disable=broad-except
|
|
116
117
|
if suppress_output:
|
|
117
118
|
restore_output()
|
|
118
119
|
e_message = captured_output.getvalue()
|
|
119
120
|
print_json_error(e_message, err)
|
|
120
121
|
else:
|
|
121
|
-
|
|
122
|
-
f"{err}",
|
|
123
|
-
fg=typer.colors.RED,
|
|
124
|
-
bold=True,
|
|
125
|
-
err=True,
|
|
126
|
-
)
|
|
122
|
+
raise click.ClickException(str(err)) from None
|
|
127
123
|
finally:
|
|
128
124
|
if suppress_output:
|
|
129
125
|
restore_output()
|