flwr-nightly 1.14.0.dev20241204__py3-none-any.whl → 1.14.0.dev20241216__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/app.py +5 -0
- flwr/cli/build.py +1 -0
- flwr/cli/cli_user_auth_interceptor.py +86 -0
- flwr/cli/config_utils.py +19 -2
- flwr/cli/example.py +1 -0
- flwr/cli/install.py +1 -0
- flwr/cli/log.py +11 -31
- flwr/cli/login/__init__.py +22 -0
- flwr/cli/login/login.py +81 -0
- flwr/cli/ls.py +25 -55
- flwr/cli/new/__init__.py +1 -0
- flwr/cli/new/new.py +2 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -2
- flwr/cli/run/__init__.py +1 -0
- flwr/cli/run/run.py +17 -39
- flwr/cli/stop.py +129 -0
- flwr/cli/utils.py +96 -1
- flwr/client/app.py +14 -3
- flwr/client/client.py +1 -0
- flwr/client/clientapp/app.py +4 -1
- flwr/client/clientapp/utils.py +1 -0
- flwr/client/grpc_adapter_client/connection.py +1 -1
- flwr/client/grpc_client/connection.py +1 -1
- flwr/client/grpc_rere_client/connection.py +13 -7
- flwr/client/message_handler/message_handler.py +1 -0
- flwr/client/mod/comms_mods.py +1 -0
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/nodestate/__init__.py +1 -0
- flwr/client/nodestate/nodestate.py +1 -0
- flwr/client/nodestate/nodestate_factory.py +1 -0
- flwr/client/rest_client/connection.py +3 -3
- flwr/client/supernode/app.py +1 -0
- flwr/common/address.py +1 -0
- flwr/common/args.py +1 -0
- flwr/common/auth_plugin/__init__.py +24 -0
- flwr/common/auth_plugin/auth_plugin.py +111 -0
- flwr/common/config.py +3 -1
- flwr/common/constant.py +6 -1
- flwr/common/logger.py +17 -1
- flwr/common/message.py +1 -0
- flwr/common/object_ref.py +57 -54
- flwr/common/pyproject.py +1 -0
- flwr/common/record/__init__.py +1 -0
- flwr/common/record/parametersrecord.py +1 -0
- flwr/common/retry_invoker.py +77 -0
- flwr/common/secure_aggregation/secaggplus_utils.py +2 -2
- flwr/common/telemetry.py +2 -1
- flwr/common/typing.py +12 -0
- flwr/common/version.py +1 -0
- flwr/proto/exec_pb2.py +27 -3
- flwr/proto/exec_pb2.pyi +103 -0
- flwr/proto/exec_pb2_grpc.py +102 -0
- flwr/proto/exec_pb2_grpc.pyi +39 -0
- flwr/proto/fab_pb2.py +4 -4
- flwr/proto/fab_pb2.pyi +4 -1
- flwr/proto/serverappio_pb2.py +18 -18
- flwr/proto/serverappio_pb2.pyi +8 -2
- flwr/proto/serverappio_pb2_grpc.py +34 -0
- flwr/proto/serverappio_pb2_grpc.pyi +13 -0
- flwr/proto/simulationio_pb2.py +2 -2
- flwr/proto/simulationio_pb2_grpc.py +34 -0
- flwr/proto/simulationio_pb2_grpc.pyi +13 -0
- flwr/server/app.py +52 -1
- flwr/server/compat/app_utils.py +7 -1
- flwr/server/driver/grpc_driver.py +11 -63
- flwr/server/driver/inmemory_driver.py +5 -1
- flwr/server/serverapp/app.py +9 -2
- flwr/server/strategy/dpfedavg_fixed.py +1 -0
- flwr/server/superlink/driver/serverappio_grpc.py +1 -0
- flwr/server/superlink/driver/serverappio_servicer.py +72 -22
- flwr/server/superlink/ffs/disk_ffs.py +1 -0
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -0
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +32 -12
- flwr/server/superlink/fleet/message_handler/message_handler.py +32 -5
- flwr/server/superlink/fleet/rest_rere/rest_api.py +4 -1
- flwr/server/superlink/fleet/vce/__init__.py +1 -0
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -0
- flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -0
- flwr/server/superlink/linkstate/in_memory_linkstate.py +14 -30
- flwr/server/superlink/linkstate/linkstate.py +13 -2
- flwr/server/superlink/linkstate/sqlite_linkstate.py +24 -44
- flwr/server/superlink/simulation/simulationio_servicer.py +20 -0
- flwr/server/superlink/utils.py +65 -0
- flwr/simulation/app.py +1 -0
- flwr/simulation/ray_transport/ray_actor.py +1 -0
- flwr/simulation/ray_transport/utils.py +1 -0
- flwr/simulation/run_simulation.py +1 -15
- flwr/simulation/simulationio_connection.py +3 -0
- flwr/superexec/app.py +1 -0
- flwr/superexec/deployment.py +1 -0
- flwr/superexec/exec_grpc.py +19 -1
- flwr/superexec/exec_servicer.py +76 -2
- flwr/superexec/exec_user_auth_interceptor.py +101 -0
- flwr/superexec/executor.py +1 -0
- {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/METADATA +8 -7
- {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/RECORD +101 -93
- {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower command line interface `run` command."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
import io
|
|
18
19
|
import json
|
|
19
20
|
import subprocess
|
|
20
|
-
from logging import DEBUG
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import Annotated, Any, Optional
|
|
22
|
+
from typing import Annotated, Any, Optional
|
|
23
23
|
|
|
24
24
|
import typer
|
|
25
25
|
from rich.console import Console
|
|
@@ -28,9 +28,8 @@ from flwr.cli.build import build
|
|
|
28
28
|
from flwr.cli.config_utils import (
|
|
29
29
|
get_fab_metadata,
|
|
30
30
|
load_and_validate,
|
|
31
|
-
|
|
31
|
+
process_loaded_project_config,
|
|
32
32
|
validate_federation_in_project_config,
|
|
33
|
-
validate_project_config,
|
|
34
33
|
)
|
|
35
34
|
from flwr.common.config import (
|
|
36
35
|
flatten_dict,
|
|
@@ -38,8 +37,7 @@ from flwr.common.config import (
|
|
|
38
37
|
user_config_to_configsrecord,
|
|
39
38
|
)
|
|
40
39
|
from flwr.common.constant import CliOutputFormat
|
|
41
|
-
from flwr.common.
|
|
42
|
-
from flwr.common.logger import log, redirect_output, remove_emojis, restore_output
|
|
40
|
+
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
43
41
|
from flwr.common.serde import (
|
|
44
42
|
configs_record_to_proto,
|
|
45
43
|
fab_to_proto,
|
|
@@ -50,15 +48,11 @@ from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
|
|
|
50
48
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
|
51
49
|
|
|
52
50
|
from ..log import start_stream
|
|
51
|
+
from ..utils import init_channel, try_obtain_cli_auth_plugin
|
|
53
52
|
|
|
54
53
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
|
55
54
|
|
|
56
55
|
|
|
57
|
-
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
58
|
-
"""Log channel connectivity."""
|
|
59
|
-
log(DEBUG, channel_connectivity)
|
|
60
|
-
|
|
61
|
-
|
|
62
56
|
# pylint: disable-next=too-many-locals
|
|
63
57
|
def run(
|
|
64
58
|
app: Annotated[
|
|
@@ -108,14 +102,19 @@ def run(
|
|
|
108
102
|
|
|
109
103
|
pyproject_path = app / "pyproject.toml" if app else None
|
|
110
104
|
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
111
|
-
config =
|
|
105
|
+
config = process_loaded_project_config(config, errors, warnings)
|
|
112
106
|
federation, federation_config = validate_federation_in_project_config(
|
|
113
107
|
federation, config
|
|
114
108
|
)
|
|
115
109
|
|
|
116
110
|
if "address" in federation_config:
|
|
117
111
|
_run_with_exec_api(
|
|
118
|
-
app,
|
|
112
|
+
app,
|
|
113
|
+
federation,
|
|
114
|
+
federation_config,
|
|
115
|
+
config_overrides,
|
|
116
|
+
stream,
|
|
117
|
+
output_format,
|
|
119
118
|
)
|
|
120
119
|
else:
|
|
121
120
|
_run_without_exec_api(app, federation_config, config_overrides, federation)
|
|
@@ -123,7 +122,7 @@ def run(
|
|
|
123
122
|
if suppress_output:
|
|
124
123
|
restore_output()
|
|
125
124
|
e_message = captured_output.getvalue()
|
|
126
|
-
|
|
125
|
+
print_json_error(e_message, err)
|
|
127
126
|
else:
|
|
128
127
|
typer.secho(
|
|
129
128
|
f"{err}",
|
|
@@ -136,26 +135,17 @@ def run(
|
|
|
136
135
|
captured_output.close()
|
|
137
136
|
|
|
138
137
|
|
|
139
|
-
# pylint: disable-next=
|
|
138
|
+
# pylint: disable-next=R0913, R0914, R0917
|
|
140
139
|
def _run_with_exec_api(
|
|
141
140
|
app: Path,
|
|
141
|
+
federation: str,
|
|
142
142
|
federation_config: dict[str, Any],
|
|
143
143
|
config_overrides: Optional[list[str]],
|
|
144
144
|
stream: bool,
|
|
145
145
|
output_format: str,
|
|
146
146
|
) -> None:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
app, federation_config
|
|
150
|
-
)
|
|
151
|
-
channel = create_channel(
|
|
152
|
-
server_address=federation_config["address"],
|
|
153
|
-
insecure=insecure,
|
|
154
|
-
root_certificates=root_certificates_bytes,
|
|
155
|
-
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
156
|
-
interceptors=None,
|
|
157
|
-
)
|
|
158
|
-
channel.subscribe(on_channel_state_change)
|
|
147
|
+
auth_plugin = try_obtain_cli_auth_plugin(app, federation)
|
|
148
|
+
channel = init_channel(app, federation_config, auth_plugin)
|
|
159
149
|
stub = ExecStub(channel)
|
|
160
150
|
|
|
161
151
|
fab_path, fab_hash = build(app)
|
|
@@ -249,15 +239,3 @@ def _run_without_exec_api(
|
|
|
249
239
|
check=True,
|
|
250
240
|
text=True,
|
|
251
241
|
)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def _print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
|
|
255
|
-
"""Print error message as JSON."""
|
|
256
|
-
Console().print_json(
|
|
257
|
-
json.dumps(
|
|
258
|
-
{
|
|
259
|
-
"success": False,
|
|
260
|
-
"error-message": remove_emojis(str(msg) + "\n" + str(e)),
|
|
261
|
-
}
|
|
262
|
-
)
|
|
263
|
-
)
|
flwr/cli/stop.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Flower command line interface `stop` command."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import io
|
|
19
|
+
import json
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Annotated, Optional
|
|
22
|
+
|
|
23
|
+
import typer
|
|
24
|
+
from rich.console import Console
|
|
25
|
+
|
|
26
|
+
from flwr.cli.config_utils import (
|
|
27
|
+
exit_if_no_address,
|
|
28
|
+
load_and_validate,
|
|
29
|
+
process_loaded_project_config,
|
|
30
|
+
validate_federation_in_project_config,
|
|
31
|
+
)
|
|
32
|
+
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
|
33
|
+
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
34
|
+
from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
|
|
35
|
+
from flwr.proto.exec_pb2_grpc import ExecStub
|
|
36
|
+
|
|
37
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def stop( # pylint: disable=R0914
|
|
41
|
+
run_id: Annotated[ # pylint: disable=unused-argument
|
|
42
|
+
int,
|
|
43
|
+
typer.Argument(help="The Flower run ID to stop"),
|
|
44
|
+
],
|
|
45
|
+
app: Annotated[
|
|
46
|
+
Path,
|
|
47
|
+
typer.Argument(help="Path of the Flower project"),
|
|
48
|
+
] = Path("."),
|
|
49
|
+
federation: Annotated[
|
|
50
|
+
Optional[str],
|
|
51
|
+
typer.Argument(help="Name of the federation"),
|
|
52
|
+
] = None,
|
|
53
|
+
output_format: Annotated[
|
|
54
|
+
str,
|
|
55
|
+
typer.Option(
|
|
56
|
+
"--format",
|
|
57
|
+
case_sensitive=False,
|
|
58
|
+
help="Format output using 'default' view or 'json'",
|
|
59
|
+
),
|
|
60
|
+
] = CliOutputFormat.DEFAULT,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Stop a run."""
|
|
63
|
+
suppress_output = output_format == CliOutputFormat.JSON
|
|
64
|
+
captured_output = io.StringIO()
|
|
65
|
+
try:
|
|
66
|
+
if suppress_output:
|
|
67
|
+
redirect_output(captured_output)
|
|
68
|
+
|
|
69
|
+
# Load and validate federation config
|
|
70
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
71
|
+
|
|
72
|
+
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
73
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
74
|
+
config = process_loaded_project_config(config, errors, warnings)
|
|
75
|
+
federation, federation_config = validate_federation_in_project_config(
|
|
76
|
+
federation, config
|
|
77
|
+
)
|
|
78
|
+
exit_if_no_address(federation_config, "stop")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
auth_plugin = try_obtain_cli_auth_plugin(app, federation)
|
|
82
|
+
channel = init_channel(app, federation_config, auth_plugin)
|
|
83
|
+
stub = ExecStub(channel) # pylint: disable=unused-variable # noqa: F841
|
|
84
|
+
|
|
85
|
+
typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
|
|
86
|
+
_stop_run(stub=stub, run_id=run_id, output_format=output_format)
|
|
87
|
+
|
|
88
|
+
except ValueError as err:
|
|
89
|
+
typer.secho(
|
|
90
|
+
f"❌ {err}",
|
|
91
|
+
fg=typer.colors.RED,
|
|
92
|
+
bold=True,
|
|
93
|
+
)
|
|
94
|
+
raise typer.Exit(code=1) from err
|
|
95
|
+
finally:
|
|
96
|
+
channel.close()
|
|
97
|
+
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
|
98
|
+
if suppress_output:
|
|
99
|
+
restore_output()
|
|
100
|
+
e_message = captured_output.getvalue()
|
|
101
|
+
print_json_error(e_message, err)
|
|
102
|
+
else:
|
|
103
|
+
typer.secho(
|
|
104
|
+
f"{err}",
|
|
105
|
+
fg=typer.colors.RED,
|
|
106
|
+
bold=True,
|
|
107
|
+
)
|
|
108
|
+
finally:
|
|
109
|
+
if suppress_output:
|
|
110
|
+
restore_output()
|
|
111
|
+
captured_output.close()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _stop_run(stub: ExecStub, run_id: int, output_format: str) -> None:
|
|
115
|
+
"""Stop a run."""
|
|
116
|
+
response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
|
|
117
|
+
if response.success:
|
|
118
|
+
typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
|
|
119
|
+
if output_format == CliOutputFormat.JSON:
|
|
120
|
+
run_output = json.dumps(
|
|
121
|
+
{
|
|
122
|
+
"success": True,
|
|
123
|
+
"run-id": run_id,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
restore_output()
|
|
127
|
+
Console().print_json(run_output)
|
|
128
|
+
else:
|
|
129
|
+
typer.secho(f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED)
|
flwr/cli/utils.py
CHANGED
|
@@ -14,13 +14,33 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower command line interface utils."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
import hashlib
|
|
19
|
+
import json
|
|
18
20
|
import re
|
|
21
|
+
from logging import DEBUG
|
|
19
22
|
from pathlib import Path
|
|
20
|
-
from typing import Callable, Optional, cast
|
|
23
|
+
from typing import Any, Callable, Optional, cast
|
|
21
24
|
|
|
25
|
+
import grpc
|
|
22
26
|
import typer
|
|
23
27
|
|
|
28
|
+
from flwr.cli.cli_user_auth_interceptor import CliUserAuthInterceptor
|
|
29
|
+
from flwr.common.auth_plugin import CliAuthPlugin
|
|
30
|
+
from flwr.common.constant import AUTH_TYPE, CREDENTIALS_DIR, FLWR_DIR
|
|
31
|
+
from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
|
|
32
|
+
from flwr.common.logger import log
|
|
33
|
+
|
|
34
|
+
from .config_utils import validate_certificate_in_federation_config
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from flwr.ee import get_cli_auth_plugins
|
|
38
|
+
except ImportError:
|
|
39
|
+
|
|
40
|
+
def get_cli_auth_plugins() -> dict[str, type[CliAuthPlugin]]:
|
|
41
|
+
"""Return all CLI authentication plugins."""
|
|
42
|
+
raise NotImplementedError("No authentication plugins are currently supported.")
|
|
43
|
+
|
|
24
44
|
|
|
25
45
|
def prompt_text(
|
|
26
46
|
text: str,
|
|
@@ -136,3 +156,78 @@ def get_sha256_hash(file_path: Path) -> str:
|
|
|
136
156
|
break
|
|
137
157
|
sha256.update(data)
|
|
138
158
|
return sha256.hexdigest()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
162
|
+
"""Return the path to the user auth config file."""
|
|
163
|
+
# Locate the credentials directory
|
|
164
|
+
credentials_dir = root_dir.absolute() / FLWR_DIR / CREDENTIALS_DIR
|
|
165
|
+
credentials_dir.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
return credentials_dir / f"{federation}.json"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def try_obtain_cli_auth_plugin(
|
|
170
|
+
root_dir: Path,
|
|
171
|
+
federation: str,
|
|
172
|
+
auth_type: Optional[str] = None,
|
|
173
|
+
) -> Optional[CliAuthPlugin]:
|
|
174
|
+
"""Load the CLI-side user auth plugin for the given auth type."""
|
|
175
|
+
config_path = get_user_auth_config_path(root_dir, federation)
|
|
176
|
+
|
|
177
|
+
# Load the config file if it exists
|
|
178
|
+
config: dict[str, Any] = {}
|
|
179
|
+
if config_path.exists():
|
|
180
|
+
with config_path.open("r", encoding="utf-8") as file:
|
|
181
|
+
config = json.load(file)
|
|
182
|
+
# This is the case when the user auth is not enabled
|
|
183
|
+
elif auth_type is None:
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
# Get the auth type from the config if not provided
|
|
187
|
+
if auth_type is None:
|
|
188
|
+
if AUTH_TYPE not in config:
|
|
189
|
+
return None
|
|
190
|
+
auth_type = config[AUTH_TYPE]
|
|
191
|
+
|
|
192
|
+
# Retrieve auth plugin class and instantiate it
|
|
193
|
+
try:
|
|
194
|
+
all_plugins: dict[str, type[CliAuthPlugin]] = get_cli_auth_plugins()
|
|
195
|
+
auth_plugin_class = all_plugins[auth_type]
|
|
196
|
+
return auth_plugin_class(config_path)
|
|
197
|
+
except KeyError:
|
|
198
|
+
typer.echo(f"❌ Unknown user authentication type: {auth_type}")
|
|
199
|
+
raise typer.Exit(code=1) from None
|
|
200
|
+
except ImportError:
|
|
201
|
+
typer.echo("❌ No authentication plugins are currently supported.")
|
|
202
|
+
raise typer.Exit(code=1) from None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def init_channel(
|
|
206
|
+
app: Path, federation_config: dict[str, Any], auth_plugin: Optional[CliAuthPlugin]
|
|
207
|
+
) -> grpc.Channel:
|
|
208
|
+
"""Initialize gRPC channel to the Exec API."""
|
|
209
|
+
|
|
210
|
+
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
211
|
+
"""Log channel connectivity."""
|
|
212
|
+
log(DEBUG, channel_connectivity)
|
|
213
|
+
|
|
214
|
+
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
215
|
+
app, federation_config
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Initialize the CLI-side user auth interceptor
|
|
219
|
+
interceptors: list[grpc.UnaryUnaryClientInterceptor] = []
|
|
220
|
+
if auth_plugin is not None:
|
|
221
|
+
auth_plugin.load_tokens()
|
|
222
|
+
interceptors = CliUserAuthInterceptor(auth_plugin)
|
|
223
|
+
|
|
224
|
+
# Create the gRPC channel
|
|
225
|
+
channel = create_channel(
|
|
226
|
+
server_address=federation_config["address"],
|
|
227
|
+
insecure=insecure,
|
|
228
|
+
root_certificates=root_certificates_bytes,
|
|
229
|
+
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
230
|
+
interceptors=interceptors or None,
|
|
231
|
+
)
|
|
232
|
+
channel.subscribe(on_channel_state_change)
|
|
233
|
+
return channel
|
flwr/client/app.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower client app."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
import signal
|
|
18
19
|
import subprocess
|
|
19
20
|
import sys
|
|
@@ -55,7 +56,7 @@ from flwr.common.constant import (
|
|
|
55
56
|
from flwr.common.logger import log, warn_deprecated_feature
|
|
56
57
|
from flwr.common.message import Error
|
|
57
58
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
58
|
-
from flwr.common.typing import Fab, Run, UserConfig
|
|
59
|
+
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
|
59
60
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
|
60
61
|
from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
|
61
62
|
from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
|
|
@@ -474,7 +475,7 @@ def start_client_internal(
|
|
|
474
475
|
|
|
475
476
|
run: Run = runs[run_id]
|
|
476
477
|
if get_fab is not None and run.fab_hash:
|
|
477
|
-
fab = get_fab(run.fab_hash)
|
|
478
|
+
fab = get_fab(run.fab_hash, run_id)
|
|
478
479
|
if not isolation:
|
|
479
480
|
# If `ClientApp` runs in the same process, install the FAB
|
|
480
481
|
install_from_fab(fab.content, flwr_path, True)
|
|
@@ -611,6 +612,16 @@ def start_client_internal(
|
|
|
611
612
|
send(reply_message)
|
|
612
613
|
log(INFO, "Sent reply")
|
|
613
614
|
|
|
615
|
+
except RunNotRunningException:
|
|
616
|
+
log(INFO, "")
|
|
617
|
+
log(
|
|
618
|
+
INFO,
|
|
619
|
+
"SuperNode aborted sending the reply message. "
|
|
620
|
+
"Run ID %s is not in `RUNNING` status.",
|
|
621
|
+
run_id,
|
|
622
|
+
)
|
|
623
|
+
log(INFO, "")
|
|
624
|
+
|
|
614
625
|
except StopIteration:
|
|
615
626
|
sleep_duration = 0
|
|
616
627
|
break
|
|
@@ -752,7 +763,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
|
|
752
763
|
Optional[Callable[[], Optional[int]]],
|
|
753
764
|
Optional[Callable[[], None]],
|
|
754
765
|
Optional[Callable[[int], Run]],
|
|
755
|
-
Optional[Callable[[str], Fab]],
|
|
766
|
+
Optional[Callable[[str, int], Fab]],
|
|
756
767
|
]
|
|
757
768
|
],
|
|
758
769
|
],
|
flwr/client/client.py
CHANGED
flwr/client/clientapp/app.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower ClientApp process."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
import argparse
|
|
18
19
|
import sys
|
|
19
20
|
import time
|
|
@@ -31,6 +32,7 @@ from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ErrorCo
|
|
|
31
32
|
from flwr.common.grpc import create_channel
|
|
32
33
|
from flwr.common.logger import log
|
|
33
34
|
from flwr.common.message import Error
|
|
35
|
+
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
|
34
36
|
from flwr.common.serde import (
|
|
35
37
|
context_from_proto,
|
|
36
38
|
context_to_proto,
|
|
@@ -105,9 +107,9 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
105
107
|
|
|
106
108
|
# Resolve directory where FABs are installed
|
|
107
109
|
flwr_dir_ = get_flwr_dir(flwr_dir)
|
|
108
|
-
|
|
109
110
|
try:
|
|
110
111
|
stub = ClientAppIoStub(channel)
|
|
112
|
+
_wrap_stub(stub, _make_simple_grpc_retry_invoker())
|
|
111
113
|
|
|
112
114
|
while True:
|
|
113
115
|
# If token is not set, loop until token is received from SuperNode
|
|
@@ -138,6 +140,7 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
138
140
|
|
|
139
141
|
# Execute ClientApp
|
|
140
142
|
reply_message = client_app(message=message, context=context)
|
|
143
|
+
|
|
141
144
|
except Exception as ex: # pylint: disable=broad-exception-caught
|
|
142
145
|
# Don't update/change NodeState
|
|
143
146
|
|
flwr/client/clientapp/utils.py
CHANGED
|
@@ -48,7 +48,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
|
48
48
|
Optional[Callable[[], Optional[int]]],
|
|
49
49
|
Optional[Callable[[], None]],
|
|
50
50
|
Optional[Callable[[int], Run]],
|
|
51
|
-
Optional[Callable[[str], Fab]],
|
|
51
|
+
Optional[Callable[[str, int], Fab]],
|
|
52
52
|
]
|
|
53
53
|
]:
|
|
54
54
|
"""Primitives for request/response-based interaction with a server via GrpcAdapter.
|
|
@@ -76,7 +76,7 @@ def grpc_connection( # pylint: disable=R0913,R0915,too-many-positional-argument
|
|
|
76
76
|
Optional[Callable[[], Optional[int]]],
|
|
77
77
|
Optional[Callable[[], None]],
|
|
78
78
|
Optional[Callable[[int], Run]],
|
|
79
|
-
Optional[Callable[[str], Fab]],
|
|
79
|
+
Optional[Callable[[str, int], Fab]],
|
|
80
80
|
]
|
|
81
81
|
]:
|
|
82
82
|
"""Establish a gRPC connection to a gRPC server.
|
|
@@ -42,7 +42,7 @@ from flwr.common.logger import log
|
|
|
42
42
|
from flwr.common.message import Message, Metadata
|
|
43
43
|
from flwr.common.retry_invoker import RetryInvoker
|
|
44
44
|
from flwr.common.serde import message_from_taskins, message_to_taskres, run_from_proto
|
|
45
|
-
from flwr.common.typing import Fab, Run
|
|
45
|
+
from flwr.common.typing import Fab, Run, RunNotRunningException
|
|
46
46
|
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
47
47
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
48
48
|
CreateNodeRequest,
|
|
@@ -84,7 +84,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
84
84
|
Optional[Callable[[], Optional[int]]],
|
|
85
85
|
Optional[Callable[[], None]],
|
|
86
86
|
Optional[Callable[[int], Run]],
|
|
87
|
-
Optional[Callable[[str], Fab]],
|
|
87
|
+
Optional[Callable[[str, int], Fab]],
|
|
88
88
|
]
|
|
89
89
|
]:
|
|
90
90
|
"""Primitives for request/response-based interaction with a server.
|
|
@@ -155,10 +155,16 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
155
155
|
ping_thread: Optional[threading.Thread] = None
|
|
156
156
|
ping_stop_event = threading.Event()
|
|
157
157
|
|
|
158
|
+
def _should_giveup_fn(e: Exception) -> bool:
|
|
159
|
+
if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
|
|
160
|
+
raise RunNotRunningException
|
|
161
|
+
if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
|
|
162
|
+
return False
|
|
163
|
+
return True
|
|
164
|
+
|
|
158
165
|
# Restrict retries to cases where the status code is UNAVAILABLE
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
)
|
|
166
|
+
# If the status code is PERMISSION_DENIED, additionally raise RunNotRunningException
|
|
167
|
+
retry_invoker.should_giveup = _should_giveup_fn
|
|
162
168
|
|
|
163
169
|
###########################################################################
|
|
164
170
|
# ping/create_node/delete_node/receive/send/get_run functions
|
|
@@ -290,9 +296,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
290
296
|
# Return fab_id and fab_version
|
|
291
297
|
return run_from_proto(get_run_response.run)
|
|
292
298
|
|
|
293
|
-
def get_fab(fab_hash: str) -> Fab:
|
|
299
|
+
def get_fab(fab_hash: str, run_id: int) -> Fab:
|
|
294
300
|
# Call FleetAPI
|
|
295
|
-
get_fab_request = GetFabRequest(node=node, hash_str=fab_hash)
|
|
301
|
+
get_fab_request = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
|
|
296
302
|
get_fab_response: GetFabResponse = retry_invoker.invoke(
|
|
297
303
|
stub.GetFab,
|
|
298
304
|
request=get_fab_request,
|
flwr/client/mod/comms_mods.py
CHANGED
flwr/client/mod/localdp_mod.py
CHANGED
|
@@ -136,7 +136,7 @@ class LocalDpMod:
|
|
|
136
136
|
fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
|
|
137
137
|
|
|
138
138
|
# Add noise to model params
|
|
139
|
-
add_localdp_gaussian_noise_to_params(
|
|
139
|
+
fit_res.parameters = add_localdp_gaussian_noise_to_params(
|
|
140
140
|
fit_res.parameters, self.sensitivity, self.epsilon, self.delta
|
|
141
141
|
)
|
|
142
142
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower NodeState."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
from .in_memory_nodestate import InMemoryNodeState as InMemoryNodeState
|
|
18
19
|
from .nodestate import NodeState as NodeState
|
|
19
20
|
from .nodestate_factory import NodeStateFactory as NodeStateFactory
|
|
@@ -96,7 +96,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
96
96
|
Optional[Callable[[], Optional[int]]],
|
|
97
97
|
Optional[Callable[[], None]],
|
|
98
98
|
Optional[Callable[[int], Run]],
|
|
99
|
-
Optional[Callable[[str], Fab]],
|
|
99
|
+
Optional[Callable[[str, int], Fab]],
|
|
100
100
|
]
|
|
101
101
|
]:
|
|
102
102
|
"""Primitives for request/response-based interaction with a server.
|
|
@@ -361,9 +361,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
361
361
|
|
|
362
362
|
return run_from_proto(res.run)
|
|
363
363
|
|
|
364
|
-
def get_fab(fab_hash: str) -> Fab:
|
|
364
|
+
def get_fab(fab_hash: str, run_id: int) -> Fab:
|
|
365
365
|
# Construct the request
|
|
366
|
-
req = GetFabRequest(node=node, hash_str=fab_hash)
|
|
366
|
+
req = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
|
|
367
367
|
|
|
368
368
|
# Send the request
|
|
369
369
|
res = _request(req, GetFabResponse, PATH_GET_FAB)
|
flwr/client/supernode/app.py
CHANGED
flwr/common/address.py
CHANGED
flwr/common/args.py
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Auth plugin components."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .auth_plugin import CliAuthPlugin as CliAuthPlugin
|
|
19
|
+
from .auth_plugin import ExecAuthPlugin as ExecAuthPlugin
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"CliAuthPlugin",
|
|
23
|
+
"ExecAuthPlugin",
|
|
24
|
+
]
|