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.
Files changed (60) hide show
  1. flwr/cli/cli_user_auth_interceptor.py +6 -2
  2. flwr/cli/log.py +8 -6
  3. flwr/cli/login/login.py +11 -4
  4. flwr/cli/ls.py +7 -4
  5. flwr/cli/new/templates/app/.gitignore.tpl +3 -0
  6. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  7. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  8. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  9. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  12. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  13. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  14. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  15. flwr/cli/run/run.py +7 -2
  16. flwr/cli/stop.py +3 -2
  17. flwr/cli/utils.py +83 -14
  18. flwr/client/app.py +17 -9
  19. flwr/client/client.py +0 -32
  20. flwr/client/grpc_rere_client/client_interceptor.py +6 -0
  21. flwr/client/grpc_rere_client/grpc_adapter.py +16 -0
  22. flwr/client/message_handler/message_handler.py +0 -2
  23. flwr/client/numpy_client.py +0 -44
  24. flwr/client/supernode/app.py +1 -2
  25. flwr/common/auth_plugin/auth_plugin.py +33 -23
  26. flwr/common/constant.py +2 -0
  27. flwr/common/grpc.py +154 -3
  28. flwr/common/record/recordset.py +1 -1
  29. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
  30. flwr/common/telemetry.py +13 -3
  31. flwr/common/typing.py +20 -0
  32. flwr/proto/exec_pb2.py +12 -24
  33. flwr/proto/exec_pb2.pyi +27 -54
  34. flwr/proto/fleet_pb2.py +40 -27
  35. flwr/proto/fleet_pb2.pyi +84 -0
  36. flwr/proto/fleet_pb2_grpc.py +66 -0
  37. flwr/proto/fleet_pb2_grpc.pyi +20 -0
  38. flwr/server/app.py +54 -33
  39. flwr/server/run_serverapp.py +8 -9
  40. flwr/server/serverapp/app.py +17 -2
  41. flwr/server/superlink/driver/serverappio_grpc.py +1 -1
  42. flwr/server/superlink/driver/serverappio_servicer.py +29 -6
  43. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
  44. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +16 -0
  45. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -1
  46. flwr/server/superlink/fleet/vce/vce_api.py +2 -2
  47. flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -24
  48. flwr/server/superlink/linkstate/linkstate.py +14 -4
  49. flwr/server/superlink/linkstate/sqlite_linkstate.py +56 -31
  50. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  51. flwr/server/superlink/simulation/simulationio_servicer.py +13 -0
  52. flwr/simulation/app.py +15 -4
  53. flwr/simulation/run_simulation.py +35 -7
  54. flwr/superexec/exec_grpc.py +1 -1
  55. flwr/superexec/exec_servicer.py +23 -2
  56. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/METADATA +5 -5
  57. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/RECORD +60 -60
  58. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/LICENSE +0 -0
  59. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/WHEEL +0 -0
  60. {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
- retrieved_metadata = dict(response.initial_metadata())
58
- self.auth_plugin.store_tokens(retrieved_metadata)
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
- for res in stub.StreamLogs(req, timeout=duration):
92
- print(res.log_output, end="")
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
- # Enforce timeout for graceful exit
113
- for res in stub.StreamLogs(req, timeout=timeout):
114
- print(res.log_output)
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.constant import AUTH_TYPE
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.login_details.get(AUTH_TYPE)
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
- auth_config = auth_plugin.login(dict(login_response.login_details), stub)
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(auth_config)
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
- res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
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
- res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
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
 
@@ -3,6 +3,9 @@ __pycache__/
3
3
  *.py[cod]
4
4
  *$py.class
5
5
 
6
+ # Flower directory
7
+ .flwr
8
+
6
9
  # C extensions
7
10
  *.so
8
11
 
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "torch==2.2.1",
14
14
  "torchvision==0.17.1",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets>=0.3.0",
13
13
  "torch==2.3.1",
14
14
  "trl==0.8.1",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets>=0.3.0",
13
13
  "torch==2.2.1",
14
14
  "transformers>=4.30.0,<5.0",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "jax==0.4.30",
13
13
  "jaxlib==0.4.30",
14
14
  "scikit-learn==1.3.2",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "mlx==0.21.1",
14
14
  ]
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "numpy>=1.21.0",
13
13
  ]
14
14
 
@@ -8,10 +8,10 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
- "torch==2.2.1",
14
- "torchvision==0.17.1",
13
+ "torch==2.5.1",
14
+ "torchvision==0.20.1",
15
15
  ]
16
16
 
17
17
  [tool.hatch.build.targets.wheel]
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "scikit-learn>=1.1.1",
14
14
  ]
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.13.1",
11
+ "flwr[simulation]>=1.14.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "tensorflow>=2.11.1,<2.18.0",
14
14
  ]
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 init_channel, try_obtain_cli_auth_plugin
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
- res = stub.StartRun(req)
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
- response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
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(file_path: Path) -> str:
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
- with open(file_path, "rb") as f:
153
- while True:
154
- data = f.read(65536) # Read in 64kB blocks
155
- if not data:
156
- break
157
- sha256.update(data)
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
- credentials_dir = root_dir.absolute() / FLWR_DIR / CREDENTIALS_DIR
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
- config: dict[str, Any] = {}
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
- config = json.load(file)
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 config:
236
+ if AUTH_TYPE not in json_file:
189
237
  return None
190
- auth_type = config[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: int = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
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
- subprocess.run(
553
- command,
554
- stdout=None,
555
- stderr=None,
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:
@@ -105,8 +105,6 @@ def handle_legacy_message_from_msgtype(
105
105
  "Please use `NumPyClient.to_client()` method to convert it to `Client`.",
106
106
  )
107
107
 
108
- client.set_context(context)
109
-
110
108
  message_type = message.metadata.message_type
111
109
 
112
110
  # Handle GetPropertiesIns