flwr-nightly 1.14.0.dev20241216__py3-none-any.whl → 1.15.0.dev20250112__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/cli_user_auth_interceptor.py +6 -2
- flwr/cli/log.py +8 -6
- flwr/cli/login/login.py +11 -4
- flwr/cli/ls.py +7 -4
- flwr/cli/new/templates/app/.gitignore.tpl +3 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/run.py +7 -2
- flwr/cli/stop.py +3 -2
- flwr/cli/utils.py +83 -14
- flwr/client/app.py +17 -9
- flwr/client/client.py +0 -32
- flwr/client/grpc_rere_client/client_interceptor.py +6 -0
- flwr/client/grpc_rere_client/grpc_adapter.py +16 -0
- flwr/client/message_handler/message_handler.py +0 -2
- flwr/client/numpy_client.py +0 -44
- flwr/client/supernode/app.py +1 -2
- flwr/common/auth_plugin/auth_plugin.py +33 -23
- flwr/common/constant.py +2 -0
- flwr/common/grpc.py +154 -3
- flwr/common/record/recordset.py +1 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
- flwr/common/telemetry.py +13 -3
- flwr/common/typing.py +20 -0
- flwr/proto/exec_pb2.py +12 -24
- flwr/proto/exec_pb2.pyi +27 -54
- flwr/proto/fleet_pb2.py +40 -27
- flwr/proto/fleet_pb2.pyi +84 -0
- flwr/proto/fleet_pb2_grpc.py +66 -0
- flwr/proto/fleet_pb2_grpc.pyi +20 -0
- flwr/server/app.py +54 -33
- flwr/server/run_serverapp.py +8 -9
- flwr/server/serverapp/app.py +17 -2
- flwr/server/superlink/driver/serverappio_grpc.py +1 -1
- flwr/server/superlink/driver/serverappio_servicer.py +29 -6
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +16 -0
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -1
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -24
- flwr/server/superlink/linkstate/linkstate.py +14 -4
- flwr/server/superlink/linkstate/sqlite_linkstate.py +56 -31
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +13 -0
- flwr/simulation/app.py +15 -4
- flwr/simulation/run_simulation.py +35 -7
- flwr/superexec/exec_grpc.py +1 -1
- flwr/superexec/exec_servicer.py +23 -2
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/METADATA +5 -5
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/RECORD +60 -60
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/entry_points.txt +0 -0
@@ -54,8 +54,12 @@ class CliUserAuthInterceptor(
|
|
54
54
|
|
55
55
|
response = continuation(details, request)
|
56
56
|
if response.initial_metadata():
|
57
|
-
|
58
|
-
|
57
|
+
credentials = self.auth_plugin.read_tokens_from_metadata(
|
58
|
+
response.initial_metadata()
|
59
|
+
)
|
60
|
+
# The metadata contains tokens only if they have been refreshed
|
61
|
+
if credentials is not None:
|
62
|
+
self.auth_plugin.store_tokens(credentials)
|
59
63
|
|
60
64
|
return response
|
61
65
|
|
flwr/cli/log.py
CHANGED
@@ -34,7 +34,7 @@ from flwr.common.logger import log as logger
|
|
34
34
|
from flwr.proto.exec_pb2 import StreamLogsRequest # pylint: disable=E0611
|
35
35
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
36
36
|
|
37
|
-
from .utils import init_channel, try_obtain_cli_auth_plugin
|
37
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
38
38
|
|
39
39
|
|
40
40
|
def start_stream(
|
@@ -88,8 +88,9 @@ def stream_logs(
|
|
88
88
|
latest_timestamp = 0.0
|
89
89
|
res = None
|
90
90
|
try:
|
91
|
-
|
92
|
-
|
91
|
+
with unauthenticated_exc_handler():
|
92
|
+
for res in stub.StreamLogs(req, timeout=duration):
|
93
|
+
print(res.log_output, end="")
|
93
94
|
except grpc.RpcError as e:
|
94
95
|
# pylint: disable=E1101
|
95
96
|
if e.code() != grpc.StatusCode.DEADLINE_EXCEEDED:
|
@@ -109,9 +110,10 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
|
109
110
|
try:
|
110
111
|
while True:
|
111
112
|
try:
|
112
|
-
|
113
|
-
|
114
|
-
|
113
|
+
with unauthenticated_exc_handler():
|
114
|
+
# Enforce timeout for graceful exit
|
115
|
+
for res in stub.StreamLogs(req, timeout=timeout):
|
116
|
+
print(res.log_output)
|
115
117
|
except grpc.RpcError as e:
|
116
118
|
# pylint: disable=E1101
|
117
119
|
if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
|
flwr/cli/login/login.py
CHANGED
@@ -26,7 +26,7 @@ from flwr.cli.config_utils import (
|
|
26
26
|
process_loaded_project_config,
|
27
27
|
validate_federation_in_project_config,
|
28
28
|
)
|
29
|
-
from flwr.common.
|
29
|
+
from flwr.common.typing import UserAuthLoginDetails
|
30
30
|
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
31
31
|
GetLoginDetailsRequest,
|
32
32
|
GetLoginDetailsResponse,
|
@@ -64,7 +64,7 @@ def login( # pylint: disable=R0914
|
|
64
64
|
login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
|
65
65
|
|
66
66
|
# Get the auth plugin
|
67
|
-
auth_type = login_response.
|
67
|
+
auth_type = login_response.auth_type
|
68
68
|
auth_plugin = try_obtain_cli_auth_plugin(app, federation, auth_type)
|
69
69
|
if auth_plugin is None:
|
70
70
|
typer.secho(
|
@@ -75,7 +75,14 @@ def login( # pylint: disable=R0914
|
|
75
75
|
raise typer.Exit(code=1)
|
76
76
|
|
77
77
|
# Login
|
78
|
-
|
78
|
+
details = UserAuthLoginDetails(
|
79
|
+
auth_type=login_response.auth_type,
|
80
|
+
device_code=login_response.device_code,
|
81
|
+
verification_uri_complete=login_response.verification_uri_complete,
|
82
|
+
expires_in=login_response.expires_in,
|
83
|
+
interval=login_response.interval,
|
84
|
+
)
|
85
|
+
credentials = auth_plugin.login(details, stub)
|
79
86
|
|
80
87
|
# Store the tokens
|
81
|
-
auth_plugin.store_tokens(
|
88
|
+
auth_plugin.store_tokens(credentials)
|
flwr/cli/ls.py
CHANGED
@@ -43,7 +43,7 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
43
43
|
)
|
44
44
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
45
45
|
|
46
|
-
from .utils import init_channel, try_obtain_cli_auth_plugin
|
46
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
47
47
|
|
48
48
|
_RunListType = tuple[int, str, str, str, str, str, str, str, str]
|
49
49
|
|
@@ -99,7 +99,6 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
|
|
99
99
|
try:
|
100
100
|
if suppress_output:
|
101
101
|
redirect_output(captured_output)
|
102
|
-
|
103
102
|
# Load and validate federation config
|
104
103
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
105
104
|
|
@@ -132,6 +131,8 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
|
|
132
131
|
_list_runs(stub, output_format)
|
133
132
|
|
134
133
|
except ValueError as err:
|
134
|
+
if suppress_output:
|
135
|
+
redirect_output(captured_output)
|
135
136
|
typer.secho(
|
136
137
|
f"❌ {err}",
|
137
138
|
fg=typer.colors.RED,
|
@@ -295,7 +296,8 @@ def _list_runs(
|
|
295
296
|
output_format: str = CliOutputFormat.DEFAULT,
|
296
297
|
) -> None:
|
297
298
|
"""List all runs."""
|
298
|
-
|
299
|
+
with unauthenticated_exc_handler():
|
300
|
+
res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
|
299
301
|
run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
|
300
302
|
|
301
303
|
formatted_runs = _format_runs(run_dict, res.now)
|
@@ -311,7 +313,8 @@ def _display_one_run(
|
|
311
313
|
output_format: str = CliOutputFormat.DEFAULT,
|
312
314
|
) -> None:
|
313
315
|
"""Display information about a specific run."""
|
314
|
-
|
316
|
+
with unauthenticated_exc_handler():
|
317
|
+
res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
|
315
318
|
if not res.run_dict:
|
316
319
|
raise ValueError(f"Run ID {run_id} not found")
|
317
320
|
|
@@ -8,10 +8,10 @@ version = "1.0.0"
|
|
8
8
|
description = ""
|
9
9
|
license = "Apache-2.0"
|
10
10
|
dependencies = [
|
11
|
-
"flwr[simulation]>=1.
|
11
|
+
"flwr[simulation]>=1.14.0",
|
12
12
|
"flwr-datasets[vision]>=0.3.0",
|
13
|
-
"torch==2.
|
14
|
-
"torchvision==0.
|
13
|
+
"torch==2.5.1",
|
14
|
+
"torchvision==0.20.1",
|
15
15
|
]
|
16
16
|
|
17
17
|
[tool.hatch.build.targets.wheel]
|
flwr/cli/run/run.py
CHANGED
@@ -48,7 +48,11 @@ from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
|
|
48
48
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
49
49
|
|
50
50
|
from ..log import start_stream
|
51
|
-
from ..utils import
|
51
|
+
from ..utils import (
|
52
|
+
init_channel,
|
53
|
+
try_obtain_cli_auth_plugin,
|
54
|
+
unauthenticated_exc_handler,
|
55
|
+
)
|
52
56
|
|
53
57
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
54
58
|
|
@@ -166,7 +170,8 @@ def _run_with_exec_api(
|
|
166
170
|
override_config=user_config_to_proto(parse_config_args(config_overrides)),
|
167
171
|
federation_options=configs_record_to_proto(c_record),
|
168
172
|
)
|
169
|
-
|
173
|
+
with unauthenticated_exc_handler():
|
174
|
+
res = stub.StartRun(req)
|
170
175
|
|
171
176
|
if res.HasField("run_id"):
|
172
177
|
typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
|
flwr/cli/stop.py
CHANGED
@@ -34,7 +34,7 @@ from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
34
34
|
from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
|
35
35
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
36
36
|
|
37
|
-
from .utils import init_channel, try_obtain_cli_auth_plugin
|
37
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
38
38
|
|
39
39
|
|
40
40
|
def stop( # pylint: disable=R0914
|
@@ -113,7 +113,8 @@ def stop( # pylint: disable=R0914
|
|
113
113
|
|
114
114
|
def _stop_run(stub: ExecStub, run_id: int, output_format: str) -> None:
|
115
115
|
"""Stop a run."""
|
116
|
-
|
116
|
+
with unauthenticated_exc_handler():
|
117
|
+
response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
|
117
118
|
if response.success:
|
118
119
|
typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
|
119
120
|
if output_format == CliOutputFormat.JSON:
|
flwr/cli/utils.py
CHANGED
@@ -18,9 +18,11 @@
|
|
18
18
|
import hashlib
|
19
19
|
import json
|
20
20
|
import re
|
21
|
+
from collections.abc import Iterator
|
22
|
+
from contextlib import contextmanager
|
21
23
|
from logging import DEBUG
|
22
24
|
from pathlib import Path
|
23
|
-
from typing import Any, Callable, Optional, cast
|
25
|
+
from typing import Any, Callable, Optional, Union, cast
|
24
26
|
|
25
27
|
import grpc
|
26
28
|
import typer
|
@@ -146,23 +148,69 @@ def sanitize_project_name(name: str) -> str:
|
|
146
148
|
return sanitized_name
|
147
149
|
|
148
150
|
|
149
|
-
def get_sha256_hash(
|
151
|
+
def get_sha256_hash(file_path_or_int: Union[Path, int]) -> str:
|
150
152
|
"""Calculate the SHA-256 hash of a file."""
|
151
153
|
sha256 = hashlib.sha256()
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
154
|
+
if isinstance(file_path_or_int, Path):
|
155
|
+
with open(file_path_or_int, "rb") as f:
|
156
|
+
while True:
|
157
|
+
data = f.read(65536) # Read in 64kB blocks
|
158
|
+
if not data:
|
159
|
+
break
|
160
|
+
sha256.update(data)
|
161
|
+
elif isinstance(file_path_or_int, int):
|
162
|
+
sha256.update(str(file_path_or_int).encode())
|
158
163
|
return sha256.hexdigest()
|
159
164
|
|
160
165
|
|
161
166
|
def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
|
162
|
-
"""Return the path to the user auth config file.
|
167
|
+
"""Return the path to the user auth config file.
|
168
|
+
|
169
|
+
Additionally, a `.gitignore` file will be created in the Flower directory to
|
170
|
+
include the `.credentials` folder to be excluded from git. If the `.gitignore`
|
171
|
+
file already exists, a warning will be displayed if the `.credentials` entry is
|
172
|
+
not found.
|
173
|
+
"""
|
163
174
|
# Locate the credentials directory
|
164
|
-
|
175
|
+
abs_flwr_dir = root_dir.absolute() / FLWR_DIR
|
176
|
+
credentials_dir = abs_flwr_dir / CREDENTIALS_DIR
|
165
177
|
credentials_dir.mkdir(parents=True, exist_ok=True)
|
178
|
+
|
179
|
+
# Determine the absolute path of the Flower directory for .gitignore
|
180
|
+
gitignore_path = abs_flwr_dir / ".gitignore"
|
181
|
+
credential_entry = CREDENTIALS_DIR
|
182
|
+
|
183
|
+
try:
|
184
|
+
if gitignore_path.exists():
|
185
|
+
with open(gitignore_path, encoding="utf-8") as gitignore_file:
|
186
|
+
lines = gitignore_file.read().splitlines()
|
187
|
+
|
188
|
+
# Warn if .credentials is not already in .gitignore
|
189
|
+
if credential_entry not in lines:
|
190
|
+
typer.secho(
|
191
|
+
f"`.gitignore` exists, but `{credential_entry}` entry not found. "
|
192
|
+
"Consider adding it to your `.gitignore` to exclude Flower "
|
193
|
+
"credentials from git.",
|
194
|
+
fg=typer.colors.YELLOW,
|
195
|
+
bold=True,
|
196
|
+
)
|
197
|
+
else:
|
198
|
+
typer.secho(
|
199
|
+
f"Creating a new `.gitignore` with `{credential_entry}` entry...",
|
200
|
+
fg=typer.colors.BLUE,
|
201
|
+
)
|
202
|
+
# Create a new .gitignore with .credentials
|
203
|
+
with open(gitignore_path, "w", encoding="utf-8") as gitignore_file:
|
204
|
+
gitignore_file.write(f"{credential_entry}\n")
|
205
|
+
except Exception as err:
|
206
|
+
typer.secho(
|
207
|
+
"❌ An error occurred while handling `.gitignore.` "
|
208
|
+
f"Please check the permissions of `{gitignore_path}` and try again.",
|
209
|
+
fg=typer.colors.RED,
|
210
|
+
bold=True,
|
211
|
+
)
|
212
|
+
raise typer.Exit(code=1) from err
|
213
|
+
|
166
214
|
return credentials_dir / f"{federation}.json"
|
167
215
|
|
168
216
|
|
@@ -175,19 +223,19 @@ def try_obtain_cli_auth_plugin(
|
|
175
223
|
config_path = get_user_auth_config_path(root_dir, federation)
|
176
224
|
|
177
225
|
# Load the config file if it exists
|
178
|
-
|
226
|
+
json_file: dict[str, Any] = {}
|
179
227
|
if config_path.exists():
|
180
228
|
with config_path.open("r", encoding="utf-8") as file:
|
181
|
-
|
229
|
+
json_file = json.load(file)
|
182
230
|
# This is the case when the user auth is not enabled
|
183
231
|
elif auth_type is None:
|
184
232
|
return None
|
185
233
|
|
186
234
|
# Get the auth type from the config if not provided
|
187
235
|
if auth_type is None:
|
188
|
-
if AUTH_TYPE not in
|
236
|
+
if AUTH_TYPE not in json_file:
|
189
237
|
return None
|
190
|
-
auth_type =
|
238
|
+
auth_type = json_file[AUTH_TYPE]
|
191
239
|
|
192
240
|
# Retrieve auth plugin class and instantiate it
|
193
241
|
try:
|
@@ -231,3 +279,24 @@ def init_channel(
|
|
231
279
|
)
|
232
280
|
channel.subscribe(on_channel_state_change)
|
233
281
|
return channel
|
282
|
+
|
283
|
+
|
284
|
+
@contextmanager
|
285
|
+
def unauthenticated_exc_handler() -> Iterator[None]:
|
286
|
+
"""Context manager to handle gRPC UNAUTHENTICATED errors.
|
287
|
+
|
288
|
+
It catches grpc.RpcError exceptions with UNAUTHENTICATED status, informs the user,
|
289
|
+
and exits the application. All other exceptions will be allowed to escape.
|
290
|
+
"""
|
291
|
+
try:
|
292
|
+
yield
|
293
|
+
except grpc.RpcError as e:
|
294
|
+
if e.code() != grpc.StatusCode.UNAUTHENTICATED:
|
295
|
+
raise
|
296
|
+
typer.secho(
|
297
|
+
"❌ Authentication failed. Please run `flwr login`"
|
298
|
+
" to authenticate and try again.",
|
299
|
+
fg=typer.colors.RED,
|
300
|
+
bold=True,
|
301
|
+
)
|
302
|
+
raise typer.Exit(code=1) from None
|
flwr/client/app.py
CHANGED
@@ -15,13 +15,14 @@
|
|
15
15
|
"""Flower client app."""
|
16
16
|
|
17
17
|
|
18
|
+
import multiprocessing
|
18
19
|
import signal
|
19
|
-
import subprocess
|
20
20
|
import sys
|
21
21
|
import time
|
22
22
|
from contextlib import AbstractContextManager
|
23
23
|
from dataclasses import dataclass
|
24
24
|
from logging import ERROR, INFO, WARN
|
25
|
+
from os import urandom
|
25
26
|
from pathlib import Path
|
26
27
|
from typing import Callable, Optional, Union, cast
|
27
28
|
|
@@ -33,6 +34,7 @@ from flwr.cli.config_utils import get_fab_metadata
|
|
33
34
|
from flwr.cli.install import install_from_fab
|
34
35
|
from flwr.client.client import Client
|
35
36
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
37
|
+
from flwr.client.clientapp.app import flwr_clientapp
|
36
38
|
from flwr.client.nodestate.nodestate_factory import NodeStateFactory
|
37
39
|
from flwr.client.typing import ClientFnExt
|
38
40
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
|
@@ -53,13 +55,12 @@ from flwr.common.constant import (
|
|
53
55
|
TRANSPORT_TYPES,
|
54
56
|
ErrorCode,
|
55
57
|
)
|
58
|
+
from flwr.common.grpc import generic_create_grpc_server
|
56
59
|
from flwr.common.logger import log, warn_deprecated_feature
|
57
60
|
from flwr.common.message import Error
|
58
61
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
59
62
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
60
63
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
61
|
-
from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
62
|
-
from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
|
63
64
|
|
64
65
|
from .clientapp.clientappio_servicer import ClientAppInputs, ClientAppIoServicer
|
65
66
|
from .grpc_adapter_client.connection import grpc_adapter
|
@@ -391,6 +392,7 @@ def start_client_internal(
|
|
391
392
|
run_info_store: Optional[DeprecatedRunInfoStore] = None
|
392
393
|
state_factory = NodeStateFactory()
|
393
394
|
state = state_factory.state()
|
395
|
+
mp_spawn_context = multiprocessing.get_context("spawn")
|
394
396
|
|
395
397
|
runs: dict[int, Run] = {}
|
396
398
|
|
@@ -513,7 +515,7 @@ def start_client_internal(
|
|
513
515
|
# Docker container.
|
514
516
|
|
515
517
|
# Generate SuperNode token
|
516
|
-
token
|
518
|
+
token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
|
517
519
|
|
518
520
|
# Mode 1: SuperNode starts ClientApp as subprocess
|
519
521
|
start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
|
@@ -549,12 +551,13 @@ def start_client_internal(
|
|
549
551
|
]
|
550
552
|
command.append("--insecure")
|
551
553
|
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
check=True,
|
554
|
+
proc = mp_spawn_context.Process(
|
555
|
+
target=_run_flwr_clientapp,
|
556
|
+
args=(command,),
|
557
|
+
daemon=True,
|
557
558
|
)
|
559
|
+
proc.start()
|
560
|
+
proc.join()
|
558
561
|
else:
|
559
562
|
# Wait for output to become available
|
560
563
|
while not clientappio_servicer.has_outputs():
|
@@ -826,6 +829,11 @@ class _AppStateTracker:
|
|
826
829
|
signal.signal(signal.SIGTERM, signal_handler)
|
827
830
|
|
828
831
|
|
832
|
+
def _run_flwr_clientapp(args: list[str]) -> None:
|
833
|
+
sys.argv = args
|
834
|
+
flwr_clientapp()
|
835
|
+
|
836
|
+
|
829
837
|
def run_clientappio_api_grpc(
|
830
838
|
address: str,
|
831
839
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
flwr/client/client.py
CHANGED
@@ -22,7 +22,6 @@ from abc import ABC
|
|
22
22
|
|
23
23
|
from flwr.common import (
|
24
24
|
Code,
|
25
|
-
Context,
|
26
25
|
EvaluateIns,
|
27
26
|
EvaluateRes,
|
28
27
|
FitIns,
|
@@ -34,14 +33,11 @@ from flwr.common import (
|
|
34
33
|
Parameters,
|
35
34
|
Status,
|
36
35
|
)
|
37
|
-
from flwr.common.logger import warn_deprecated_feature_with_example
|
38
36
|
|
39
37
|
|
40
38
|
class Client(ABC):
|
41
39
|
"""Abstract base class for Flower clients."""
|
42
40
|
|
43
|
-
_context: Context
|
44
|
-
|
45
41
|
def get_properties(self, ins: GetPropertiesIns) -> GetPropertiesRes:
|
46
42
|
"""Return set of client's properties.
|
47
43
|
|
@@ -143,34 +139,6 @@ class Client(ABC):
|
|
143
139
|
metrics={},
|
144
140
|
)
|
145
141
|
|
146
|
-
@property
|
147
|
-
def context(self) -> Context:
|
148
|
-
"""Getter for `Context` client attribute."""
|
149
|
-
warn_deprecated_feature_with_example(
|
150
|
-
"Accessing the context via the client's attribute is deprecated.",
|
151
|
-
example_message="Instead, pass it to the client's "
|
152
|
-
"constructor in your `client_fn()` which already "
|
153
|
-
"receives a context object.",
|
154
|
-
code_example="def client_fn(context: Context) -> Client:\n\n"
|
155
|
-
"\t\t# Your existing client_fn\n\n"
|
156
|
-
"\t\t# Pass `context` to the constructor\n"
|
157
|
-
"\t\treturn FlowerClient(context).to_client()",
|
158
|
-
)
|
159
|
-
return self._context
|
160
|
-
|
161
|
-
@context.setter
|
162
|
-
def context(self, context: Context) -> None:
|
163
|
-
"""Setter for `Context` client attribute."""
|
164
|
-
self._context = context
|
165
|
-
|
166
|
-
def get_context(self) -> Context:
|
167
|
-
"""Get the run context from this client."""
|
168
|
-
return self.context
|
169
|
-
|
170
|
-
def set_context(self, context: Context) -> None:
|
171
|
-
"""Apply a run context to this client."""
|
172
|
-
self.context = context
|
173
|
-
|
174
142
|
def to_client(self) -> Client:
|
175
143
|
"""Return client (itself)."""
|
176
144
|
return self
|
@@ -36,7 +36,9 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
36
36
|
CreateNodeRequest,
|
37
37
|
DeleteNodeRequest,
|
38
38
|
PingRequest,
|
39
|
+
PullMessagesRequest,
|
39
40
|
PullTaskInsRequest,
|
41
|
+
PushMessagesRequest,
|
40
42
|
PushTaskResRequest,
|
41
43
|
)
|
42
44
|
from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611
|
@@ -52,6 +54,8 @@ Request = Union[
|
|
52
54
|
GetRunRequest,
|
53
55
|
PingRequest,
|
54
56
|
GetFabRequest,
|
57
|
+
PullMessagesRequest,
|
58
|
+
PushMessagesRequest,
|
55
59
|
]
|
56
60
|
|
57
61
|
|
@@ -129,6 +133,8 @@ class AuthenticateClientInterceptor(grpc.UnaryUnaryClientInterceptor): # type:
|
|
129
133
|
GetRunRequest,
|
130
134
|
PingRequest,
|
131
135
|
GetFabRequest,
|
136
|
+
PullMessagesRequest,
|
137
|
+
PushMessagesRequest,
|
132
138
|
),
|
133
139
|
):
|
134
140
|
if self.shared_secret is None:
|
@@ -40,8 +40,12 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
40
40
|
DeleteNodeResponse,
|
41
41
|
PingRequest,
|
42
42
|
PingResponse,
|
43
|
+
PullMessagesRequest,
|
44
|
+
PullMessagesResponse,
|
43
45
|
PullTaskInsRequest,
|
44
46
|
PullTaskInsResponse,
|
47
|
+
PushMessagesRequest,
|
48
|
+
PushMessagesResponse,
|
45
49
|
PushTaskResRequest,
|
46
50
|
PushTaskResResponse,
|
47
51
|
)
|
@@ -132,12 +136,24 @@ class GrpcAdapter:
|
|
132
136
|
"""."""
|
133
137
|
return self._send_and_receive(request, PullTaskInsResponse, **kwargs)
|
134
138
|
|
139
|
+
def PullMessages( # pylint: disable=C0103
|
140
|
+
self, request: PullMessagesRequest, **kwargs: Any
|
141
|
+
) -> PullMessagesResponse:
|
142
|
+
"""."""
|
143
|
+
return self._send_and_receive(request, PullMessagesResponse, **kwargs)
|
144
|
+
|
135
145
|
def PushTaskRes( # pylint: disable=C0103
|
136
146
|
self, request: PushTaskResRequest, **kwargs: Any
|
137
147
|
) -> PushTaskResResponse:
|
138
148
|
"""."""
|
139
149
|
return self._send_and_receive(request, PushTaskResResponse, **kwargs)
|
140
150
|
|
151
|
+
def PushMessages( # pylint: disable=C0103
|
152
|
+
self, request: PushMessagesRequest, **kwargs: Any
|
153
|
+
) -> PushMessagesResponse:
|
154
|
+
"""."""
|
155
|
+
return self._send_and_receive(request, PushMessagesResponse, **kwargs)
|
156
|
+
|
141
157
|
def GetRun( # pylint: disable=C0103
|
142
158
|
self, request: GetRunRequest, **kwargs: Any
|
143
159
|
) -> GetRunResponse:
|