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/login/login.py
CHANGED
|
@@ -15,20 +15,14 @@
|
|
|
15
15
|
"""Flower command line interface `login` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from
|
|
19
|
-
from typing import Annotated
|
|
18
|
+
from typing import Annotated, cast
|
|
20
19
|
|
|
20
|
+
import click
|
|
21
21
|
import typer
|
|
22
22
|
|
|
23
23
|
from flwr.cli.auth_plugin import LoginError, NoOpCliAuthPlugin
|
|
24
|
-
from flwr.cli.config_utils import (
|
|
25
|
-
exit_if_no_address,
|
|
26
|
-
get_insecure_flag,
|
|
27
|
-
load_and_validate,
|
|
28
|
-
process_loaded_project_config,
|
|
29
|
-
validate_federation_in_project_config,
|
|
30
|
-
)
|
|
31
24
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
25
|
+
from flwr.cli.utils import init_channel_from_connection
|
|
32
26
|
from flwr.common.typing import AccountAuthLoginDetails
|
|
33
27
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
34
28
|
GetLoginDetailsRequest,
|
|
@@ -36,68 +30,45 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
36
30
|
)
|
|
37
31
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
38
32
|
|
|
39
|
-
from ..
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
init_channel,
|
|
43
|
-
load_cli_auth_plugin,
|
|
44
|
-
)
|
|
33
|
+
from ..config_migration import migrate, warn_if_federation_config_overrides
|
|
34
|
+
from ..flower_config import read_superlink_connection
|
|
35
|
+
from ..utils import flwr_cli_grpc_exc_handler, load_cli_auth_plugin_from_connection
|
|
45
36
|
|
|
46
37
|
|
|
47
|
-
def login(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
typer.Argument(help="Path of the Flower App to run."),
|
|
51
|
-
] = Path("."),
|
|
52
|
-
federation: Annotated[
|
|
38
|
+
def login(
|
|
39
|
+
ctx: typer.Context,
|
|
40
|
+
superlink: Annotated[
|
|
53
41
|
str | None,
|
|
54
|
-
typer.Argument(help="Name of the
|
|
42
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
55
43
|
] = None,
|
|
56
44
|
federation_config_overrides: Annotated[
|
|
57
45
|
list[str] | None,
|
|
58
46
|
typer.Option(
|
|
59
47
|
"--federation-config",
|
|
60
48
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
49
|
+
hidden=True,
|
|
61
50
|
),
|
|
62
51
|
] = None,
|
|
63
52
|
) -> None:
|
|
64
53
|
"""Login to Flower SuperLink."""
|
|
65
|
-
|
|
54
|
+
# Warn `--federation-config` is ignored
|
|
55
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
# Migrate legacy usage if any
|
|
58
|
+
migrate(superlink, args=ctx.args)
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
exit_if_no_address(federation_config, "login")
|
|
60
|
+
# Read superlink connection configuration
|
|
61
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
62
|
+
superlink = superlink_connection.name
|
|
75
63
|
|
|
76
|
-
# Check if `enable-account-auth` is set to `true`
|
|
77
|
-
|
|
78
|
-
if not account_auth_enabled(federation_config):
|
|
79
|
-
typer.secho(
|
|
80
|
-
"❌ Account authentication is not enabled for the federation "
|
|
81
|
-
f"'{federation}'. To enable it, set `enable-account-auth = true` "
|
|
82
|
-
"in the federation configuration.",
|
|
83
|
-
fg=typer.colors.RED,
|
|
84
|
-
bold=True,
|
|
85
|
-
err=True,
|
|
86
|
-
)
|
|
87
|
-
raise typer.Exit(code=1)
|
|
88
64
|
# Check if insecure flag is set to `True`
|
|
89
|
-
insecure
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"
|
|
93
|
-
"`true` in the federation configuration.",
|
|
94
|
-
fg=typer.colors.RED,
|
|
95
|
-
bold=True,
|
|
96
|
-
err=True,
|
|
65
|
+
if superlink_connection.insecure:
|
|
66
|
+
raise click.ClickException(
|
|
67
|
+
"`flwr login` requires TLS to be enabled. `insecure` must NOT be set to "
|
|
68
|
+
"`true` in the federation configuration."
|
|
97
69
|
)
|
|
98
|
-
raise typer.Exit(code=1)
|
|
99
70
|
|
|
100
|
-
channel =
|
|
71
|
+
channel = init_channel_from_connection(superlink_connection, NoOpCliAuthPlugin())
|
|
101
72
|
stub = ControlStub(channel)
|
|
102
73
|
|
|
103
74
|
login_request = GetLoginDetailsRequest()
|
|
@@ -105,8 +76,9 @@ def login( # pylint: disable=R0914
|
|
|
105
76
|
login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
|
|
106
77
|
|
|
107
78
|
# Get the auth plugin
|
|
108
|
-
|
|
109
|
-
|
|
79
|
+
authn_plugin = load_cli_auth_plugin_from_connection(
|
|
80
|
+
cast(str, superlink_connection.address), login_response.authn_type
|
|
81
|
+
)
|
|
110
82
|
|
|
111
83
|
# Login
|
|
112
84
|
details = AccountAuthLoginDetails(
|
|
@@ -118,20 +90,14 @@ def login( # pylint: disable=R0914
|
|
|
118
90
|
)
|
|
119
91
|
try:
|
|
120
92
|
with flwr_cli_grpc_exc_handler():
|
|
121
|
-
credentials =
|
|
93
|
+
credentials = authn_plugin.login(details, stub)
|
|
122
94
|
typer.secho(
|
|
123
95
|
"✅ Login successful.",
|
|
124
96
|
fg=typer.colors.GREEN,
|
|
125
97
|
bold=False,
|
|
126
98
|
)
|
|
127
99
|
except LoginError as e:
|
|
128
|
-
|
|
129
|
-
f"❌ Login failed: {e.message}",
|
|
130
|
-
fg=typer.colors.RED,
|
|
131
|
-
bold=True,
|
|
132
|
-
err=True,
|
|
133
|
-
)
|
|
134
|
-
raise typer.Exit(code=1) from None
|
|
100
|
+
raise click.ClickException(f"Login failed: {e.message}") from None
|
|
135
101
|
|
|
136
102
|
# Store the tokens
|
|
137
|
-
|
|
103
|
+
authn_plugin.store_tokens(credentials)
|
flwr/cli/ls.py
CHANGED
|
@@ -15,25 +15,18 @@
|
|
|
15
15
|
"""Flower command line interface `ls` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import io
|
|
19
18
|
import json
|
|
20
|
-
from
|
|
21
|
-
from typing import Annotated, cast
|
|
19
|
+
from typing import Annotated
|
|
22
20
|
|
|
23
21
|
import typer
|
|
24
22
|
from rich.console import Console
|
|
25
23
|
from rich.table import Table
|
|
26
24
|
from rich.text import Text
|
|
27
25
|
|
|
28
|
-
from flwr.cli.
|
|
29
|
-
exit_if_no_address,
|
|
30
|
-
load_and_validate,
|
|
31
|
-
process_loaded_project_config,
|
|
32
|
-
validate_federation_in_project_config,
|
|
33
|
-
)
|
|
26
|
+
from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
|
|
34
27
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
35
|
-
from flwr.
|
|
36
|
-
from flwr.common.
|
|
28
|
+
from flwr.cli.flower_config import read_superlink_connection
|
|
29
|
+
from flwr.common.constant import CliOutputFormat, Status, SubStatus
|
|
37
30
|
from flwr.common.serde import run_from_proto
|
|
38
31
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
39
32
|
ListRunsRequest,
|
|
@@ -43,24 +36,26 @@ from flwr.proto.control_pb2_grpc import ControlStub
|
|
|
43
36
|
from flwr.supercore.utils import humanize_bytes, humanize_duration
|
|
44
37
|
|
|
45
38
|
from .run_utils import RunRow, format_runs
|
|
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
|
|
|
49
47
|
def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
50
48
|
ctx: typer.Context,
|
|
51
|
-
|
|
52
|
-
Path,
|
|
53
|
-
typer.Argument(help="Path of the Flower project"),
|
|
54
|
-
] = Path("."),
|
|
55
|
-
federation: Annotated[
|
|
49
|
+
superlink: Annotated[
|
|
56
50
|
str | None,
|
|
57
|
-
typer.Argument(help="Name of the
|
|
51
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
58
52
|
] = None,
|
|
59
53
|
federation_config_overrides: Annotated[
|
|
60
54
|
list[str] | None,
|
|
61
55
|
typer.Option(
|
|
62
56
|
"--federation-config",
|
|
63
57
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
58
|
+
hidden=True,
|
|
64
59
|
),
|
|
65
60
|
] = None,
|
|
66
61
|
runs: Annotated[
|
|
@@ -86,7 +81,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
86
81
|
),
|
|
87
82
|
] = CliOutputFormat.DEFAULT,
|
|
88
83
|
) -> None:
|
|
89
|
-
"""List the details of one provided run ID or all runs
|
|
84
|
+
"""List the details of one provided run ID or all runs (alias: ls).
|
|
90
85
|
|
|
91
86
|
The following details are displayed:
|
|
92
87
|
|
|
@@ -99,32 +94,23 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
99
94
|
|
|
100
95
|
All timestamps follow ISO 8601, UTC and are formatted as ``YYYY-MM-DD HH:MM:SSZ``.
|
|
101
96
|
"""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
112
|
-
|
|
113
|
-
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
114
|
-
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
115
|
-
config = process_loaded_project_config(config, errors, warnings)
|
|
116
|
-
federation, federation_config = validate_federation_in_project_config(
|
|
117
|
-
federation, config, federation_config_overrides
|
|
118
|
-
)
|
|
119
|
-
exit_if_no_address(federation_config, command_name)
|
|
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)
|
|
100
|
+
|
|
101
|
+
# Migrate legacy usage if any
|
|
102
|
+
migrate(superlink, args=ctx.args)
|
|
103
|
+
|
|
104
|
+
# Read superlink connection configuration
|
|
105
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
120
106
|
channel = None
|
|
107
|
+
|
|
121
108
|
try:
|
|
122
109
|
if runs and run_id is not None:
|
|
123
110
|
raise ValueError(
|
|
124
111
|
"The options '--runs' and '--run-id' are mutually exclusive."
|
|
125
112
|
)
|
|
126
|
-
|
|
127
|
-
channel = init_channel(app, federation_config, auth_plugin)
|
|
113
|
+
channel = init_channel_from_connection(superlink_connection)
|
|
128
114
|
stub = ControlStub(channel)
|
|
129
115
|
|
|
130
116
|
# Display information about a specific run ID
|
|
@@ -135,9 +121,9 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
135
121
|
else:
|
|
136
122
|
typer.echo("📄 Listing all runs...")
|
|
137
123
|
formatted_runs = _list_runs(stub)
|
|
138
|
-
|
|
139
|
-
if
|
|
140
|
-
|
|
124
|
+
|
|
125
|
+
if is_json:
|
|
126
|
+
print_json_to_stdout(_to_json(formatted_runs))
|
|
141
127
|
else:
|
|
142
128
|
if run_id is not None:
|
|
143
129
|
Console().print(_to_detail_table(formatted_runs[0]))
|
|
@@ -146,22 +132,6 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
|
|
|
146
132
|
finally:
|
|
147
133
|
if channel:
|
|
148
134
|
channel.close()
|
|
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
135
|
|
|
166
136
|
|
|
167
137
|
def _get_status_style(status_text: str) -> str:
|
flwr/cli/new/new.py
CHANGED
|
@@ -20,6 +20,7 @@ import zipfile
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Annotated, cast
|
|
22
22
|
|
|
23
|
+
import click
|
|
23
24
|
import requests
|
|
24
25
|
import typer
|
|
25
26
|
|
|
@@ -52,16 +53,12 @@ def new(
|
|
|
52
53
|
) -> None:
|
|
53
54
|
"""Create new Flower App."""
|
|
54
55
|
if framework is not None or username is not None:
|
|
55
|
-
|
|
56
|
-
"
|
|
56
|
+
raise click.ClickException(
|
|
57
|
+
"The --framework and --username options are deprecated and will be "
|
|
57
58
|
"removed in future versions of Flower. Please provide an app specifier "
|
|
58
59
|
"after `flwr new` instead, e.g., '@account_name/app_name' or "
|
|
59
|
-
"'@account_name/app_name==x.y.z'."
|
|
60
|
-
fg=typer.colors.RED,
|
|
61
|
-
bold=True,
|
|
62
|
-
err=True,
|
|
60
|
+
"'@account_name/app_name==x.y.z'."
|
|
63
61
|
)
|
|
64
|
-
raise typer.Exit(code=1)
|
|
65
62
|
|
|
66
63
|
if app_spec is None:
|
|
67
64
|
# Fetch recommended apps
|
|
@@ -141,12 +138,7 @@ def fetch_recommended_apps() -> list[dict[str, str]]:
|
|
|
141
138
|
return cast(list[dict[str, str]], apps)
|
|
142
139
|
|
|
143
140
|
except requests.RequestException as e:
|
|
144
|
-
|
|
145
|
-
f"❌ Failed to fetch recommended apps: {e}",
|
|
146
|
-
fg=typer.colors.RED,
|
|
147
|
-
err=True,
|
|
148
|
-
)
|
|
149
|
-
raise typer.Exit(code=1) from e
|
|
141
|
+
raise click.ClickException(f"Failed to fetch recommended apps: {e}") from e
|
|
150
142
|
|
|
151
143
|
|
|
152
144
|
# Security: prevent zip-slip
|
|
@@ -190,22 +182,12 @@ def _download_zip_to_memory(presigned_url: str) -> io.BytesIO:
|
|
|
190
182
|
r = requests.get(presigned_url, timeout=60)
|
|
191
183
|
r.raise_for_status()
|
|
192
184
|
except requests.RequestException as e:
|
|
193
|
-
|
|
194
|
-
f"ZIP download failed: {e}",
|
|
195
|
-
fg=typer.colors.RED,
|
|
196
|
-
err=True,
|
|
197
|
-
)
|
|
198
|
-
raise typer.Exit(code=1) from e
|
|
185
|
+
raise click.ClickException(f"ZIP download failed: {e}") from e
|
|
199
186
|
|
|
200
187
|
buf = io.BytesIO(r.content)
|
|
201
188
|
# Validate it's a zip
|
|
202
189
|
if not zipfile.is_zipfile(buf):
|
|
203
|
-
|
|
204
|
-
"Downloaded file is not a valid ZIP",
|
|
205
|
-
fg=typer.colors.RED,
|
|
206
|
-
err=True,
|
|
207
|
-
)
|
|
208
|
-
raise typer.Exit(code=1)
|
|
190
|
+
raise click.ClickException("Downloaded file is not a valid ZIP")
|
|
209
191
|
buf.seek(0)
|
|
210
192
|
return buf
|
|
211
193
|
|
|
@@ -216,8 +198,7 @@ def download_remote_app_via_api(app_spec: str) -> None:
|
|
|
216
198
|
try:
|
|
217
199
|
app_id, app_version = parse_app_spec(app_spec)
|
|
218
200
|
except ValueError as e:
|
|
219
|
-
|
|
220
|
-
raise typer.Exit(code=1) from e
|
|
201
|
+
raise click.ClickException(str(e)) from e
|
|
221
202
|
|
|
222
203
|
app_name = app_id.split("/")[1]
|
|
223
204
|
|
|
@@ -242,8 +223,7 @@ def download_remote_app_via_api(app_spec: str) -> None:
|
|
|
242
223
|
try:
|
|
243
224
|
presigned_url, _ = request_download_link(app_id, app_version, url, "zip_url")
|
|
244
225
|
except ValueError as e:
|
|
245
|
-
|
|
246
|
-
raise typer.Exit(code=1) from e
|
|
226
|
+
raise click.ClickException(str(e)) from e
|
|
247
227
|
|
|
248
228
|
typer.secho(
|
|
249
229
|
"🔽 Downloading ZIP into memory...",
|
flwr/cli/pull.py
CHANGED
|
@@ -15,49 +15,39 @@
|
|
|
15
15
|
"""Flower command line interface `pull` command."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from pathlib import Path
|
|
19
18
|
from typing import Annotated
|
|
20
19
|
|
|
20
|
+
import click
|
|
21
21
|
import typer
|
|
22
22
|
|
|
23
|
-
from flwr.cli.
|
|
24
|
-
exit_if_no_address,
|
|
25
|
-
load_and_validate,
|
|
26
|
-
process_loaded_project_config,
|
|
27
|
-
validate_federation_in_project_config,
|
|
28
|
-
)
|
|
23
|
+
from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
|
|
29
24
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
30
|
-
from flwr.
|
|
25
|
+
from flwr.cli.flower_config import read_superlink_connection
|
|
31
26
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
32
27
|
PullArtifactsRequest,
|
|
33
28
|
PullArtifactsResponse,
|
|
34
29
|
)
|
|
35
30
|
from flwr.proto.control_pb2_grpc import ControlStub
|
|
36
31
|
|
|
37
|
-
from .utils import flwr_cli_grpc_exc_handler,
|
|
32
|
+
from .utils import flwr_cli_grpc_exc_handler, init_channel_from_connection
|
|
38
33
|
|
|
39
34
|
|
|
40
35
|
def pull( # pylint: disable=R0914
|
|
36
|
+
ctx: typer.Context,
|
|
41
37
|
run_id: Annotated[
|
|
42
38
|
int,
|
|
43
|
-
typer.
|
|
44
|
-
"--run-id",
|
|
45
|
-
help="Run ID to pull artifacts from.",
|
|
46
|
-
),
|
|
39
|
+
typer.Argument(help="Run ID to pull artifacts from."),
|
|
47
40
|
],
|
|
48
|
-
|
|
49
|
-
Path,
|
|
50
|
-
typer.Argument(help="Path of the Flower App to run."),
|
|
51
|
-
] = Path("."),
|
|
52
|
-
federation: Annotated[
|
|
41
|
+
superlink: Annotated[
|
|
53
42
|
str | None,
|
|
54
|
-
typer.Argument(help="Name of the
|
|
43
|
+
typer.Argument(help="Name of the SuperLink connection."),
|
|
55
44
|
] = None,
|
|
56
45
|
federation_config_overrides: Annotated[
|
|
57
46
|
list[str] | None,
|
|
58
47
|
typer.Option(
|
|
59
48
|
"--federation-config",
|
|
60
49
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
50
|
+
hidden=True,
|
|
61
51
|
),
|
|
62
52
|
] = None,
|
|
63
53
|
) -> None:
|
|
@@ -66,20 +56,17 @@ def pull( # pylint: disable=R0914
|
|
|
66
56
|
Retrieve a download URL for artifacts generated during a completed Flower run. The
|
|
67
57
|
artifacts can then be downloaded from the provided URL.
|
|
68
58
|
"""
|
|
69
|
-
|
|
59
|
+
# Warn `--federation-config` is ignored
|
|
60
|
+
warn_if_federation_config_overrides(federation_config_overrides)
|
|
70
61
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
exit_if_no_address(federation_config, "pull")
|
|
62
|
+
# Migrate legacy usage if any
|
|
63
|
+
migrate(superlink, args=ctx.args)
|
|
64
|
+
|
|
65
|
+
# Read superlink connection configuration
|
|
66
|
+
superlink_connection = read_superlink_connection(superlink)
|
|
78
67
|
channel = None
|
|
79
68
|
try:
|
|
80
|
-
|
|
81
|
-
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
|
82
|
-
channel = init_channel(app, federation_config, auth_plugin)
|
|
69
|
+
channel = init_channel_from_connection(superlink_connection)
|
|
83
70
|
stub = ControlStub(channel)
|
|
84
71
|
with flwr_cli_grpc_exc_handler():
|
|
85
72
|
res: PullArtifactsResponse = stub.PullArtifacts(
|
|
@@ -87,14 +74,9 @@ def pull( # pylint: disable=R0914
|
|
|
87
74
|
)
|
|
88
75
|
|
|
89
76
|
if not res.url:
|
|
90
|
-
|
|
91
|
-
f"
|
|
92
|
-
"obtained.",
|
|
93
|
-
fg=typer.colors.RED,
|
|
94
|
-
bold=True,
|
|
95
|
-
err=True,
|
|
77
|
+
raise click.ClickException(
|
|
78
|
+
f"A download URL for artifacts from run {run_id} couldn't be obtained."
|
|
96
79
|
)
|
|
97
|
-
raise typer.Exit(code=1)
|
|
98
80
|
|
|
99
81
|
typer.secho(
|
|
100
82
|
f"✅ Artifacts for run {run_id} can be downloaded from: {res.url}",
|