flwr-nightly 1.14.0.dev20241215__py3-none-any.whl → 1.14.0.dev20241217__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/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:
@@ -170,7 +172,7 @@ def _log_with_exec_api(
170
172
  run_id: int,
171
173
  stream: bool,
172
174
  ) -> None:
173
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
175
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation)
174
176
  channel = init_channel(app, federation_config, auth_plugin)
175
177
 
176
178
  if stream:
flwr/cli/login/login.py CHANGED
@@ -65,9 +65,7 @@ def login( # pylint: disable=R0914
65
65
 
66
66
  # Get the auth plugin
67
67
  auth_type = login_response.login_details.get(AUTH_TYPE)
68
- auth_plugin = try_obtain_cli_auth_plugin(
69
- app, federation, federation_config, auth_type
70
- )
68
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation, auth_type)
71
69
  if auth_plugin is None:
72
70
  typer.secho(
73
71
  f'❌ Authentication type "{auth_type}" not found',
flwr/cli/ls.py CHANGED
@@ -19,13 +19,12 @@ import io
19
19
  import json
20
20
  from datetime import datetime, timedelta
21
21
  from pathlib import Path
22
- from typing import Annotated, Optional, Union
22
+ from typing import Annotated, Optional
23
23
 
24
24
  import typer
25
25
  from rich.console import Console
26
26
  from rich.table import Table
27
27
  from rich.text import Text
28
- from typer import Exit
29
28
 
30
29
  from flwr.cli.config_utils import (
31
30
  exit_if_no_address,
@@ -35,7 +34,7 @@ from flwr.cli.config_utils import (
35
34
  )
36
35
  from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat, SubStatus
37
36
  from flwr.common.date import format_timedelta, isoformat8601_utc
38
- from flwr.common.logger import redirect_output, remove_emojis, restore_output
37
+ from flwr.common.logger import print_json_error, redirect_output, restore_output
39
38
  from flwr.common.serde import run_from_proto
40
39
  from flwr.common.typing import Run
41
40
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
@@ -44,7 +43,7 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
44
43
  )
45
44
  from flwr.proto.exec_pb2_grpc import ExecStub
46
45
 
47
- from .utils import init_channel, try_obtain_cli_auth_plugin
46
+ from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
48
47
 
49
48
  _RunListType = tuple[int, str, str, str, str, str, str, str, str]
50
49
 
@@ -81,13 +80,25 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
81
80
  ),
82
81
  ] = CliOutputFormat.DEFAULT,
83
82
  ) -> None:
84
- """List runs."""
83
+ """List the details of one provided run ID or all runs in a Flower federation.
84
+
85
+ The following details are displayed:
86
+
87
+ - **Run ID:** Unique identifier for the run.
88
+ - **FAB:** Name of the FAB associated with the run (``{FAB_ID} (v{FAB_VERSION})``).
89
+ - **Status:** Current status of the run (pending, starting, running, finished).
90
+ - **Elapsed:** Time elapsed since the run started (``HH:MM:SS``).
91
+ - **Created At:** Timestamp when the run was created.
92
+ - **Running At:** Timestamp when the run started running.
93
+ - **Finished At:** Timestamp when the run finished.
94
+
95
+ All timestamps follow ISO 8601, UTC and are formatted as ``YYYY-MM-DD HH:MM:SSZ``.
96
+ """
85
97
  suppress_output = output_format == CliOutputFormat.JSON
86
98
  captured_output = io.StringIO()
87
99
  try:
88
100
  if suppress_output:
89
101
  redirect_output(captured_output)
90
-
91
102
  # Load and validate federation config
92
103
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
93
104
 
@@ -104,7 +115,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
104
115
  raise ValueError(
105
116
  "The options '--runs' and '--run-id' are mutually exclusive."
106
117
  )
107
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
118
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation)
108
119
  channel = init_channel(app, federation_config, auth_plugin)
109
120
  stub = ExecStub(channel)
110
121
 
@@ -120,6 +131,8 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
120
131
  _list_runs(stub, output_format)
121
132
 
122
133
  except ValueError as err:
134
+ if suppress_output:
135
+ redirect_output(captured_output)
123
136
  typer.secho(
124
137
  f"❌ {err}",
125
138
  fg=typer.colors.RED,
@@ -132,7 +145,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
132
145
  if suppress_output:
133
146
  restore_output()
134
147
  e_message = captured_output.getvalue()
135
- _print_json_error(e_message, err)
148
+ print_json_error(e_message, err)
136
149
  else:
137
150
  typer.secho(
138
151
  f"{err}",
@@ -283,7 +296,8 @@ def _list_runs(
283
296
  output_format: str = CliOutputFormat.DEFAULT,
284
297
  ) -> None:
285
298
  """List all runs."""
286
- res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
299
+ with unauthenticated_exc_handler():
300
+ res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
287
301
  run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
288
302
 
289
303
  formatted_runs = _format_runs(run_dict, res.now)
@@ -299,7 +313,8 @@ def _display_one_run(
299
313
  output_format: str = CliOutputFormat.DEFAULT,
300
314
  ) -> None:
301
315
  """Display information about a specific run."""
302
- res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
316
+ with unauthenticated_exc_handler():
317
+ res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
303
318
  if not res.run_dict:
304
319
  raise ValueError(f"Run ID {run_id} not found")
305
320
 
@@ -310,15 +325,3 @@ def _display_one_run(
310
325
  Console().print_json(_to_json(formatted_runs))
311
326
  else:
312
327
  Console().print(_to_table(formatted_runs))
313
-
314
-
315
- def _print_json_error(msg: str, e: Union[Exit, Exception]) -> None:
316
- """Print error message as JSON."""
317
- Console().print_json(
318
- json.dumps(
319
- {
320
- "success": False,
321
- "error-message": remove_emojis(str(msg) + "\n" + str(e)),
322
- }
323
- )
324
- )
flwr/cli/run/run.py CHANGED
@@ -19,7 +19,7 @@ import io
19
19
  import json
20
20
  import subprocess
21
21
  from pathlib import Path
22
- from typing import Annotated, Any, Optional, Union
22
+ from typing import Annotated, Any, Optional
23
23
 
24
24
  import typer
25
25
  from rich.console import Console
@@ -37,7 +37,7 @@ from flwr.common.config import (
37
37
  user_config_to_configsrecord,
38
38
  )
39
39
  from flwr.common.constant import CliOutputFormat
40
- from flwr.common.logger import redirect_output, remove_emojis, restore_output
40
+ from flwr.common.logger import print_json_error, redirect_output, restore_output
41
41
  from flwr.common.serde import (
42
42
  configs_record_to_proto,
43
43
  fab_to_proto,
@@ -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
 
@@ -122,7 +126,7 @@ def run(
122
126
  if suppress_output:
123
127
  restore_output()
124
128
  e_message = captured_output.getvalue()
125
- _print_json_error(e_message, err)
129
+ print_json_error(e_message, err)
126
130
  else:
127
131
  typer.secho(
128
132
  f"{err}",
@@ -144,7 +148,7 @@ def _run_with_exec_api(
144
148
  stream: bool,
145
149
  output_format: str,
146
150
  ) -> None:
147
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
151
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation)
148
152
  channel = init_channel(app, federation_config, auth_plugin)
149
153
  stub = ExecStub(channel)
150
154
 
@@ -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)
@@ -239,15 +244,3 @@ def _run_without_exec_api(
239
244
  check=True,
240
245
  text=True,
241
246
  )
242
-
243
-
244
- def _print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
245
- """Print error message as JSON."""
246
- Console().print_json(
247
- json.dumps(
248
- {
249
- "success": False,
250
- "error-message": remove_emojis(str(msg) + "\n" + str(e)),
251
- }
252
- )
253
- )
flwr/cli/stop.py CHANGED
@@ -15,10 +15,13 @@
15
15
  """Flower command line interface `stop` command."""
16
16
 
17
17
 
18
+ import io
19
+ import json
18
20
  from pathlib import Path
19
21
  from typing import Annotated, Optional
20
22
 
21
23
  import typer
24
+ from rich.console import Console
22
25
 
23
26
  from flwr.cli.config_utils import (
24
27
  exit_if_no_address,
@@ -26,14 +29,15 @@ from flwr.cli.config_utils import (
26
29
  process_loaded_project_config,
27
30
  validate_federation_in_project_config,
28
31
  )
29
- from flwr.common.constant import FAB_CONFIG_FILE
32
+ from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
33
+ from flwr.common.logger import print_json_error, redirect_output, restore_output
30
34
  from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
31
35
  from flwr.proto.exec_pb2_grpc import ExecStub
32
36
 
33
- from .utils import init_channel, try_obtain_cli_auth_plugin
37
+ from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
34
38
 
35
39
 
36
- def stop(
40
+ def stop( # pylint: disable=R0914
37
41
  run_id: Annotated[ # pylint: disable=unused-argument
38
42
  int,
39
43
  typer.Argument(help="The Flower run ID to stop"),
@@ -46,46 +50,81 @@ def stop(
46
50
  Optional[str],
47
51
  typer.Argument(help="Name of the federation"),
48
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,
49
61
  ) -> None:
50
62
  """Stop a run."""
51
- # Load and validate federation config
52
- typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
53
-
54
- pyproject_path = app / FAB_CONFIG_FILE if app else None
55
- config, errors, warnings = load_and_validate(path=pyproject_path)
56
- config = process_loaded_project_config(config, errors, warnings)
57
- federation, federation_config = validate_federation_in_project_config(
58
- federation, config
59
- )
60
- exit_if_no_address(federation_config, "stop")
61
-
63
+ suppress_output = output_format == CliOutputFormat.JSON
64
+ captured_output = io.StringIO()
62
65
  try:
63
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
64
- channel = init_channel(app, federation_config, auth_plugin)
65
- stub = ExecStub(channel) # pylint: disable=unused-variable # noqa: F841
66
+ if suppress_output:
67
+ redirect_output(captured_output)
66
68
 
67
- typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
68
- _stop_run(stub, run_id=run_id)
69
+ # Load and validate federation config
70
+ typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
69
71
 
70
- except ValueError as err:
71
- typer.secho(
72
- f"❌ {err}",
73
- fg=typer.colors.RED,
74
- bold=True,
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
75
77
  )
76
- raise typer.Exit(code=1) from err
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
+ )
77
108
  finally:
78
- channel.close()
109
+ if suppress_output:
110
+ restore_output()
111
+ captured_output.close()
79
112
 
80
113
 
81
- def _stop_run(
82
- stub: ExecStub, # pylint: disable=unused-argument
83
- run_id: int, # pylint: disable=unused-argument
84
- ) -> None:
114
+ def _stop_run(stub: ExecStub, run_id: int, output_format: str) -> None:
85
115
  """Stop a run."""
86
- response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
87
-
116
+ with unauthenticated_exc_handler():
117
+ response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
88
118
  if response.success:
89
119
  typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
120
+ if output_format == CliOutputFormat.JSON:
121
+ run_output = json.dumps(
122
+ {
123
+ "success": True,
124
+ "run-id": run_id,
125
+ }
126
+ )
127
+ restore_output()
128
+ Console().print_json(run_output)
90
129
  else:
91
130
  typer.secho(f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED)
flwr/cli/utils.py CHANGED
@@ -18,6 +18,8 @@
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
25
  from typing import Any, Callable, Optional, cast
@@ -26,7 +28,6 @@ import grpc
26
28
  import typer
27
29
 
28
30
  from flwr.cli.cli_user_auth_interceptor import CliUserAuthInterceptor
29
- from flwr.common.address import parse_address
30
31
  from flwr.common.auth_plugin import CliAuthPlugin
31
32
  from flwr.common.constant import AUTH_TYPE, CREDENTIALS_DIR, FLWR_DIR
32
33
  from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
@@ -159,33 +160,21 @@ def get_sha256_hash(file_path: Path) -> str:
159
160
  return sha256.hexdigest()
160
161
 
161
162
 
162
- def get_user_auth_config_path(
163
- root_dir: Path, federation: str, server_address: str
164
- ) -> Path:
163
+ def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
165
164
  """Return the path to the user auth config file."""
166
- # Parse the server address
167
- parsed_addr = parse_address(server_address)
168
- if parsed_addr is None:
169
- raise ValueError(f"Invalid server address: {server_address}")
170
- host, port, is_v6 = parsed_addr
171
- formatted_addr = f"[{host}]_{port}" if is_v6 else f"{host}_{port}"
172
-
173
165
  # Locate the credentials directory
174
166
  credentials_dir = root_dir.absolute() / FLWR_DIR / CREDENTIALS_DIR
175
167
  credentials_dir.mkdir(parents=True, exist_ok=True)
176
- return credentials_dir / f"{federation}_{formatted_addr}.json"
168
+ return credentials_dir / f"{federation}.json"
177
169
 
178
170
 
179
171
  def try_obtain_cli_auth_plugin(
180
172
  root_dir: Path,
181
173
  federation: str,
182
- federation_config: dict[str, Any],
183
174
  auth_type: Optional[str] = None,
184
175
  ) -> Optional[CliAuthPlugin]:
185
176
  """Load the CLI-side user auth plugin for the given auth type."""
186
- config_path = get_user_auth_config_path(
187
- root_dir, federation, federation_config["address"]
188
- )
177
+ config_path = get_user_auth_config_path(root_dir, federation)
189
178
 
190
179
  # Load the config file if it exists
191
180
  config: dict[str, Any] = {}
@@ -244,3 +233,24 @@ def init_channel(
244
233
  )
245
234
  channel.subscribe(on_channel_state_change)
246
235
  return channel
236
+
237
+
238
+ @contextmanager
239
+ def unauthenticated_exc_handler() -> Iterator[None]:
240
+ """Context manager to handle gRPC UNAUTHENTICATED errors.
241
+
242
+ It catches grpc.RpcError exceptions with UNAUTHENTICATED status, informs the user,
243
+ and exits the application. All other exceptions will be allowed to escape.
244
+ """
245
+ try:
246
+ yield
247
+ except grpc.RpcError as e:
248
+ if e.code() != grpc.StatusCode.UNAUTHENTICATED:
249
+ raise
250
+ typer.secho(
251
+ "❌ Authentication failed. Please run `flwr login`"
252
+ " to authenticate and try again.",
253
+ fg=typer.colors.RED,
254
+ bold=True,
255
+ )
256
+ raise typer.Exit(code=1) from None
flwr/client/app.py CHANGED
@@ -56,7 +56,7 @@ from flwr.common.constant import (
56
56
  from flwr.common.logger import log, warn_deprecated_feature
57
57
  from flwr.common.message import Error
58
58
  from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
59
- from flwr.common.typing import Fab, Run, UserConfig
59
+ from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
60
60
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
61
61
  from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
62
62
  from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
@@ -612,6 +612,16 @@ def start_client_internal(
612
612
  send(reply_message)
613
613
  log(INFO, "Sent reply")
614
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
+
615
625
  except StopIteration:
616
626
  sleep_duration = 0
617
627
  break
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
@@ -32,6 +32,7 @@ from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ErrorCo
32
32
  from flwr.common.grpc import create_channel
33
33
  from flwr.common.logger import log
34
34
  from flwr.common.message import Error
35
+ from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
35
36
  from flwr.common.serde import (
36
37
  context_from_proto,
37
38
  context_to_proto,
@@ -106,9 +107,9 @@ def run_clientapp( # pylint: disable=R0914
106
107
 
107
108
  # Resolve directory where FABs are installed
108
109
  flwr_dir_ = get_flwr_dir(flwr_dir)
109
-
110
110
  try:
111
111
  stub = ClientAppIoStub(channel)
112
+ _wrap_stub(stub, _make_simple_grpc_retry_invoker())
112
113
 
113
114
  while True:
114
115
  # If token is not set, loop until token is received from SuperNode
@@ -139,6 +140,7 @@ def run_clientapp( # pylint: disable=R0914
139
140
 
140
141
  # Execute ClientApp
141
142
  reply_message = client_app(message=message, context=context)
143
+
142
144
  except Exception as ex: # pylint: disable=broad-exception-caught
143
145
  # Don't update/change NodeState
144
146
 
@@ -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,
@@ -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
- retry_invoker.should_giveup = (
160
- lambda e: e.code() != grpc.StatusCode.UNAVAILABLE # type: ignore
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
@@ -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
@@ -21,13 +21,11 @@ from typing import Callable
21
21
  from flwr.client.client import Client
22
22
  from flwr.common import (
23
23
  Config,
24
- Context,
25
24
  NDArrays,
26
25
  Scalar,
27
26
  ndarrays_to_parameters,
28
27
  parameters_to_ndarrays,
29
28
  )
30
- from flwr.common.logger import warn_deprecated_feature_with_example
31
29
  from flwr.common.typing import (
32
30
  Code,
33
31
  EvaluateIns,
@@ -71,8 +69,6 @@ Example
71
69
  class NumPyClient(ABC):
72
70
  """Abstract base class for Flower clients using NumPy."""
73
71
 
74
- _context: Context
75
-
76
72
  def get_properties(self, config: Config) -> dict[str, Scalar]:
77
73
  """Return a client's set of properties.
78
74
 
@@ -175,34 +171,6 @@ class NumPyClient(ABC):
175
171
  _ = (self, parameters, config)
176
172
  return 0.0, 0, {}
177
173
 
178
- @property
179
- def context(self) -> Context:
180
- """Getter for `Context` client attribute."""
181
- warn_deprecated_feature_with_example(
182
- "Accessing the context via the client's attribute is deprecated.",
183
- example_message="Instead, pass it to the client's "
184
- "constructor in your `client_fn()` which already "
185
- "receives a context object.",
186
- code_example="def client_fn(context: Context) -> Client:\n\n"
187
- "\t\t# Your existing client_fn\n\n"
188
- "\t\t# Pass `context` to the constructor\n"
189
- "\t\treturn FlowerClient(context).to_client()",
190
- )
191
- return self._context
192
-
193
- @context.setter
194
- def context(self, context: Context) -> None:
195
- """Setter for `Context` client attribute."""
196
- self._context = context
197
-
198
- def get_context(self) -> Context:
199
- """Get the run context from this client."""
200
- return self.context
201
-
202
- def set_context(self, context: Context) -> None:
203
- """Apply a run context to this client."""
204
- self.context = context
205
-
206
174
  def to_client(self) -> Client:
207
175
  """Convert to object to Client type and return it."""
208
176
  return _wrap_numpy_client(client=self)
@@ -299,21 +267,9 @@ def _evaluate(self: Client, ins: EvaluateIns) -> EvaluateRes:
299
267
  )
300
268
 
301
269
 
302
- def _get_context(self: Client) -> Context:
303
- """Return context of underlying NumPyClient."""
304
- return self.numpy_client.get_context() # type: ignore
305
-
306
-
307
- def _set_context(self: Client, context: Context) -> None:
308
- """Apply context to underlying NumPyClient."""
309
- self.numpy_client.set_context(context) # type: ignore
310
-
311
-
312
270
  def _wrap_numpy_client(client: NumPyClient) -> Client:
313
271
  member_dict: dict[str, Callable] = { # type: ignore
314
272
  "__init__": _constructor,
315
- "get_context": _get_context,
316
- "set_context": _set_context,
317
273
  }
318
274
 
319
275
  # Add wrapper type methods (if overridden)
flwr/common/logger.py CHANGED
@@ -15,6 +15,7 @@
15
15
  """Flower Logger."""
16
16
 
17
17
 
18
+ import json as _json
18
19
  import logging
19
20
  import re
20
21
  import sys
@@ -27,6 +28,8 @@ from queue import Empty, Queue
27
28
  from typing import TYPE_CHECKING, Any, Optional, TextIO, Union
28
29
 
29
30
  import grpc
31
+ import typer
32
+ from rich.console import Console
30
33
 
31
34
  from flwr.proto.log_pb2 import PushLogsRequest # pylint: disable=E0611
32
35
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
@@ -377,7 +380,7 @@ def stop_log_uploader(
377
380
  log_uploader.join()
378
381
 
379
382
 
380
- def remove_emojis(text: str) -> str:
383
+ def _remove_emojis(text: str) -> str:
381
384
  """Remove emojis from the provided text."""
382
385
  emoji_pattern = re.compile(
383
386
  "["
@@ -391,3 +394,15 @@ def remove_emojis(text: str) -> str:
391
394
  flags=re.UNICODE,
392
395
  )
393
396
  return emoji_pattern.sub(r"", text)
397
+
398
+
399
+ def print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
400
+ """Print error message as JSON."""
401
+ Console().print_json(
402
+ _json.dumps(
403
+ {
404
+ "success": False,
405
+ "error-message": _remove_emojis(str(msg) + "\n" + str(e)),
406
+ }
407
+ )
408
+ )
@@ -30,6 +30,7 @@ from flwr.common.logger import log
30
30
  from flwr.common.typing import RunNotRunningException
31
31
  from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
32
32
  from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
33
+ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
33
34
 
34
35
 
35
36
  def exponential(
@@ -365,7 +366,8 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
365
366
 
366
367
 
367
368
  def _wrap_stub(
368
- stub: Union[ServerAppIoStub, ClientAppIoStub], retry_invoker: RetryInvoker
369
+ stub: Union[ServerAppIoStub, ClientAppIoStub, SimulationIoStub],
370
+ retry_invoker: RetryInvoker,
369
371
  ) -> None:
370
372
  """Wrap a gRPC stub with a retry invoker."""
371
373
 
flwr/server/app.py CHANGED
@@ -93,9 +93,13 @@ BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
93
93
 
94
94
 
95
95
  try:
96
- from flwr.ee import get_exec_auth_plugins
96
+ from flwr.ee import add_ee_args_superlink, get_exec_auth_plugins
97
97
  except ImportError:
98
98
 
99
+ # pylint: disable-next=unused-argument
100
+ def add_ee_args_superlink(parser: argparse.ArgumentParser) -> None:
101
+ """Add EE-specific arguments to the parser."""
102
+
99
103
  def get_exec_auth_plugins() -> dict[str, type[ExecAuthPlugin]]:
100
104
  """Return all Exec API authentication plugins."""
101
105
  raise NotImplementedError("No authentication plugins are currently supported.")
@@ -580,7 +584,7 @@ def _try_setup_node_authentication(
580
584
 
581
585
 
582
586
  def _try_obtain_user_auth_config(args: argparse.Namespace) -> Optional[dict[str, Any]]:
583
- if args.user_auth_config is not None:
587
+ if getattr(args, "user_auth_config", None) is not None:
584
588
  with open(args.user_auth_config, encoding="utf-8") as file:
585
589
  config: dict[str, Any] = yaml.safe_load(file)
586
590
  return config
@@ -703,6 +707,7 @@ def _parse_args_run_superlink() -> argparse.ArgumentParser:
703
707
  )
704
708
 
705
709
  _add_args_common(parser=parser)
710
+ add_ee_args_superlink(parser=parser)
706
711
  _add_args_serverappio_api(parser=parser)
707
712
  _add_args_fleet_api(parser=parser)
708
713
  _add_args_exec_api(parser=parser)
@@ -792,12 +797,6 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
792
797
  type=str,
793
798
  help="The SuperLink's public key (as a path str) to enable authentication.",
794
799
  )
795
- parser.add_argument(
796
- "--user-auth-config",
797
- help="The path to the user authentication configuration YAML file.",
798
- type=str,
799
- default=None,
800
- )
801
800
 
802
801
 
803
802
  def _add_args_serverappio_api(parser: argparse.ArgumentParser) -> None:
@@ -159,6 +159,9 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
159
159
  for task_ins in request.task_ins_list:
160
160
  validation_errors = validate_task_ins_or_res(task_ins)
161
161
  _raise_if(bool(validation_errors), ", ".join(validation_errors))
162
+ _raise_if(
163
+ request.run_id != task_ins.run_id, "`task_ins` has mismatched `run_id`"
164
+ )
162
165
 
163
166
  # Store each TaskIns
164
167
  task_ids: list[Optional[UUID]] = []
@@ -193,6 +196,12 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
193
196
  # Read from state
194
197
  task_res_list: list[TaskRes] = state.get_task_res(task_ids=task_ids)
195
198
 
199
+ # Validate request
200
+ for task_res in task_res_list:
201
+ _raise_if(
202
+ request.run_id != task_res.run_id, "`task_res` has mismatched `run_id`"
203
+ )
204
+
196
205
  # Delete the TaskIns/TaskRes pairs if TaskRes is found
197
206
  task_ins_ids_to_delete = {
198
207
  UUID(task_res.task.ancestry[0]) for task_res in task_res_list
@@ -123,9 +123,7 @@ def push_task_res(request: PushTaskResRequest, state: LinkState) -> PushTaskResR
123
123
  return response
124
124
 
125
125
 
126
- def get_run(
127
- request: GetRunRequest, state: LinkState # pylint: disable=W0613
128
- ) -> GetRunResponse:
126
+ def get_run(request: GetRunRequest, state: LinkState) -> GetRunResponse:
129
127
  """Get run information."""
130
128
  run = state.get_run(request.run_id)
131
129
 
@@ -761,8 +761,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
761
761
  "federation_options, pending_at, starting_at, running_at, finished_at, "
762
762
  "sub_status, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
763
763
  )
764
- if fab_hash:
765
- fab_id, fab_version = "", ""
766
764
  override_config_json = json.dumps(override_config)
767
765
  data = [
768
766
  sint64_run_id,
@@ -54,6 +54,7 @@ from flwr.proto.simulationio_pb2 import ( # pylint: disable=E0611
54
54
  )
55
55
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
56
56
  from flwr.server.superlink.linkstate import LinkStateFactory
57
+ from flwr.server.superlink.utils import abort_if
57
58
 
58
59
 
59
60
  class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
@@ -110,6 +111,15 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
110
111
  """Push Simulation process outputs."""
111
112
  log(DEBUG, "SimultionIoServicer.PushSimulationOutputs")
112
113
  state = self.state_factory.state()
114
+
115
+ # Abort if the run is not running
116
+ abort_if(
117
+ request.run_id,
118
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
119
+ state,
120
+ context,
121
+ )
122
+
113
123
  state.set_serverapp_context(request.run_id, context_from_proto(request.context))
114
124
  return PushSimulationOutputsResponse()
115
125
 
@@ -120,6 +130,9 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
120
130
  log(DEBUG, "SimultionIoServicer.UpdateRunStatus")
121
131
  state = self.state_factory.state()
122
132
 
133
+ # Abort if the run is finished
134
+ abort_if(request.run_id, [Status.FINISHED], state, context)
135
+
123
136
  # Update the run status
124
137
  state.update_run_status(
125
138
  run_id=request.run_id, new_status=run_status_from_proto(request.run_status)
flwr/simulation/app.py CHANGED
@@ -48,6 +48,7 @@ from flwr.common.logger import (
48
48
  from flwr.common.serde import (
49
49
  configs_record_from_proto,
50
50
  context_from_proto,
51
+ context_to_proto,
51
52
  fab_from_proto,
52
53
  run_from_proto,
53
54
  run_status_to_proto,
@@ -202,7 +203,7 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
202
203
  enable_tf_gpu_growth: bool = fed_opt.get("enable_tf_gpu_growth", False)
203
204
 
204
205
  # Launch the simulation
205
- _run_simulation(
206
+ updated_context = _run_simulation(
206
207
  server_app_attr=server_app_attr,
207
208
  client_app_attr=client_app_attr,
208
209
  num_supernodes=num_supernodes,
@@ -217,7 +218,7 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
217
218
  )
218
219
 
219
220
  # Send resulting context
220
- context_proto = None # context_to_proto(updated_context)
221
+ context_proto = context_to_proto(updated_context)
221
222
  out_req = PushSimulationOutputsRequest(
222
223
  run_id=run.run_id, context=context_proto
223
224
  )
@@ -24,7 +24,7 @@ import threading
24
24
  import traceback
25
25
  from logging import DEBUG, ERROR, INFO, WARNING
26
26
  from pathlib import Path
27
- from time import sleep
27
+ from queue import Empty, Queue
28
28
  from typing import Any, Optional
29
29
 
30
30
  from flwr.cli.config_utils import load_and_validate
@@ -127,7 +127,7 @@ def run_simulation_from_cli() -> None:
127
127
  run = Run.create_empty(run_id)
128
128
  run.override_config = override_config
129
129
 
130
- _run_simulation(
130
+ _ = _run_simulation(
131
131
  server_app_attr=server_app_attr,
132
132
  client_app_attr=client_app_attr,
133
133
  num_supernodes=args.num_supernodes,
@@ -136,7 +136,6 @@ def run_simulation_from_cli() -> None:
136
136
  app_dir=args.app,
137
137
  run=run,
138
138
  enable_tf_gpu_growth=args.enable_tf_gpu_growth,
139
- delay_start=args.delay_start,
140
139
  verbose_logging=args.verbose,
141
140
  server_app_run_config=fused_config,
142
141
  is_app=True,
@@ -208,7 +207,7 @@ def run_simulation(
208
207
  "\n\tflwr.simulation.run_simulationt(...)",
209
208
  )
210
209
 
211
- _run_simulation(
210
+ _ = _run_simulation(
212
211
  num_supernodes=num_supernodes,
213
212
  client_app=client_app,
214
213
  server_app=server_app,
@@ -231,6 +230,7 @@ def run_serverapp_th(
231
230
  has_exception: threading.Event,
232
231
  enable_tf_gpu_growth: bool,
233
232
  run_id: int,
233
+ ctx_queue: "Queue[Context]",
234
234
  ) -> threading.Thread:
235
235
  """Run SeverApp in a thread."""
236
236
 
@@ -243,6 +243,7 @@ def run_serverapp_th(
243
243
  _server_app_run_config: UserConfig,
244
244
  _server_app_attr: Optional[str],
245
245
  _server_app: Optional[ServerApp],
246
+ _ctx_queue: "Queue[Context]",
246
247
  ) -> None:
247
248
  """Run SeverApp, after check if GPU memory growth has to be set.
248
249
 
@@ -263,13 +264,14 @@ def run_serverapp_th(
263
264
  )
264
265
 
265
266
  # Run ServerApp
266
- _run(
267
+ updated_context = _run(
267
268
  driver=_driver,
268
269
  context=context,
269
270
  server_app_dir=_server_app_dir,
270
271
  server_app_attr=_server_app_attr,
271
272
  loaded_server_app=_server_app,
272
273
  )
274
+ _ctx_queue.put(updated_context)
273
275
  except Exception as ex: # pylint: disable=broad-exception-caught
274
276
  log(ERROR, "ServerApp thread raised an exception: %s", ex)
275
277
  log(ERROR, traceback.format_exc())
@@ -293,6 +295,7 @@ def run_serverapp_th(
293
295
  server_app_run_config,
294
296
  server_app_attr,
295
297
  server_app,
298
+ ctx_queue,
296
299
  ),
297
300
  )
298
301
  serverapp_th.start()
@@ -309,14 +312,13 @@ def _main_loop(
309
312
  enable_tf_gpu_growth: bool,
310
313
  run: Run,
311
314
  exit_event: EventType,
312
- delay_start: int,
313
315
  flwr_dir: Optional[str] = None,
314
316
  client_app: Optional[ClientApp] = None,
315
317
  client_app_attr: Optional[str] = None,
316
318
  server_app: Optional[ServerApp] = None,
317
319
  server_app_attr: Optional[str] = None,
318
320
  server_app_run_config: Optional[UserConfig] = None,
319
- ) -> None:
321
+ ) -> Context:
320
322
  """Start ServerApp on a separate thread, then launch Simulation Engine."""
321
323
  # Initialize StateFactory
322
324
  state_factory = LinkStateFactory(":flwr-in-memory-state:")
@@ -326,6 +328,13 @@ def _main_loop(
326
328
  server_app_thread_has_exception = threading.Event()
327
329
  serverapp_th = None
328
330
  success = True
331
+ updated_context = Context(
332
+ run_id=run.run_id,
333
+ node_id=0,
334
+ node_config=UserConfig(),
335
+ state=RecordSet(),
336
+ run_config=UserConfig(),
337
+ )
329
338
  try:
330
339
  # Register run
331
340
  log(DEBUG, "Pre-registering run with id %s", run.run_id)
@@ -340,6 +349,7 @@ def _main_loop(
340
349
  # Initialize Driver
341
350
  driver = InMemoryDriver(state_factory=state_factory)
342
351
  driver.set_run(run_id=run.run_id)
352
+ output_context_queue: "Queue[Context]" = Queue()
343
353
 
344
354
  # Get and run ServerApp thread
345
355
  serverapp_th = run_serverapp_th(
@@ -352,11 +362,9 @@ def _main_loop(
352
362
  has_exception=server_app_thread_has_exception,
353
363
  enable_tf_gpu_growth=enable_tf_gpu_growth,
354
364
  run_id=run.run_id,
365
+ ctx_queue=output_context_queue,
355
366
  )
356
367
 
357
- # Buffer time so the `ServerApp` in separate thread is ready
358
- log(DEBUG, "Buffer time delay: %ds", delay_start)
359
- sleep(delay_start)
360
368
  # Start Simulation Engine
361
369
  vce.start_vce(
362
370
  num_supernodes=num_supernodes,
@@ -372,6 +380,11 @@ def _main_loop(
372
380
  flwr_dir=flwr_dir,
373
381
  )
374
382
 
383
+ updated_context = output_context_queue.get(timeout=3)
384
+
385
+ except Empty:
386
+ log(DEBUG, "Queue timeout. No context received.")
387
+
375
388
  except Exception as ex:
376
389
  log(ERROR, "An exception occurred !! %s", ex)
377
390
  log(ERROR, traceback.format_exc())
@@ -388,6 +401,7 @@ def _main_loop(
388
401
  raise RuntimeError("Exception in ServerApp thread")
389
402
 
390
403
  log(DEBUG, "Stopping Simulation Engine now.")
404
+ return updated_context
391
405
 
392
406
 
393
407
  # pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
@@ -405,10 +419,9 @@ def _run_simulation(
405
419
  flwr_dir: Optional[str] = None,
406
420
  run: Optional[Run] = None,
407
421
  enable_tf_gpu_growth: bool = False,
408
- delay_start: int = 5,
409
422
  verbose_logging: bool = False,
410
423
  is_app: bool = False,
411
- ) -> None:
424
+ ) -> Context:
412
425
  """Launch the Simulation Engine."""
413
426
  if backend_config is None:
414
427
  backend_config = {}
@@ -460,7 +473,6 @@ def _run_simulation(
460
473
  enable_tf_gpu_growth,
461
474
  run,
462
475
  exit_event,
463
- delay_start,
464
476
  flwr_dir,
465
477
  client_app,
466
478
  client_app_attr,
@@ -488,7 +500,8 @@ def _run_simulation(
488
500
  # Set logger propagation to False to prevent duplicated log output in Colab.
489
501
  logger = set_logger_propagation(logger, False)
490
502
 
491
- _main_loop(*args)
503
+ updated_context = _main_loop(*args)
504
+ return updated_context
492
505
 
493
506
 
494
507
  def _parse_args_run_simulation() -> argparse.ArgumentParser:
@@ -538,13 +551,6 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser:
538
551
  "Read more about how `tf.config.experimental.set_memory_growth()` works in "
539
552
  "the TensorFlow documentation: https://www.tensorflow.org/api/stable.",
540
553
  )
541
- parser.add_argument(
542
- "--delay-start",
543
- type=int,
544
- default=3,
545
- help="Buffer time (in seconds) to delay the start the simulation engine after "
546
- "the `ServerApp`, which runs in a separate thread, has been launched.",
547
- )
548
554
  parser.add_argument(
549
555
  "--verbose",
550
556
  action="store_true",
@@ -23,6 +23,7 @@ import grpc
23
23
  from flwr.common.constant import SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS
24
24
  from flwr.common.grpc import create_channel
25
25
  from flwr.common.logger import log
26
+ from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
26
27
  from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
27
28
 
28
29
 
@@ -48,6 +49,7 @@ class SimulationIoConnection:
48
49
  self._cert = root_certificates
49
50
  self._grpc_stub: Optional[SimulationIoStub] = None
50
51
  self._channel: Optional[grpc.Channel] = None
52
+ self._retry_invoker = _make_simple_grpc_retry_invoker()
51
53
 
52
54
  @property
53
55
  def _is_connected(self) -> bool:
@@ -72,6 +74,7 @@ class SimulationIoConnection:
72
74
  root_certificates=self._cert,
73
75
  )
74
76
  self._grpc_stub = SimulationIoStub(self._channel)
77
+ _wrap_stub(self._grpc_stub, self._retry_invoker)
75
78
  log(DEBUG, "[SimulationIO] Connected to %s", self._addr)
76
79
 
77
80
  def _disconnect(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.14.0.dev20241215
3
+ Version: 1.14.0.dev20241217
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -6,10 +6,10 @@ flwr/cli/cli_user_auth_interceptor.py,sha256=rEjgAZmzHO0GjwdyZib6bkTI2X59ErJAZlu
6
6
  flwr/cli/config_utils.py,sha256=I4_EMv2f68mfrL_QuOYoAG--yDfKisE7tGiIg09G2YQ,12079
7
7
  flwr/cli/example.py,sha256=uk5CoD0ZITgpY_ffsTbEKf8XOOCSUzByjHPcMSPqV18,2216
8
8
  flwr/cli/install.py,sha256=0AD0qJD79SKgBnWOQlphcubfr4zHk8jTpFgwZbJBI_g,8180
9
- flwr/cli/log.py,sha256=1HUCUnSMk1yXwjR5G4w12xGoIZmCDGA7HWBbOePsDlQ,5995
9
+ flwr/cli/log.py,sha256=O7MBpsJp114PIZb-7Cru-KM6fqyneFQkqoQbQsqQmZU,6121
10
10
  flwr/cli/login/__init__.py,sha256=6_9zOzbPOAH72K2wX3-9dXTAbS7Mjpa5sEn2lA6eHHI,800
11
- flwr/cli/login/login.py,sha256=_G16s07CJOT1l_jlqRgIDCch0PWIjg0WPpmxI1GC7y8,2797
12
- flwr/cli/ls.py,sha256=5uO0YG0XXn7paS4oUs1T7rwicApxMV3ac9ejBZfLN3k,10545
11
+ flwr/cli/login/login.py,sha256=bZZ3hVeGpF5805R0Eg_SBZUGwrLAWmyaoLhLw6vQFcg,2764
12
+ flwr/cli/ls.py,sha256=K_3Bt2RfETw4V7J4qgo8_Wx-Y_bWZqttuO879Ppxo5Y,11056
13
13
  flwr/cli/new/__init__.py,sha256=pOQtPT9W4kCIttcKne5m-FtJbvTqdjTVJxzQ9AUYK8I,790
14
14
  flwr/cli/new/new.py,sha256=scyyKt8mzkc3El1bypgkHjKwVQEc2-q4I50PxriPFdI,9922
15
15
  flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
@@ -65,15 +65,15 @@ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=UtH3Vslg2S8fIKIHC-d
65
65
  flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=01HArBqRrbZT3O7pXOM9MqduXMNm525wv7Sj6dvYMJE,686
66
66
  flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=KVCIOEYNWnq6j7XOboXqZshc9aQ2PyRDUu7bZtmfJ24,710
67
67
  flwr/cli/run/__init__.py,sha256=cCsKVB0SFzh2b3QmGba6BHckB85xlhjh3mh4pBpACtY,790
68
- flwr/cli/run/run.py,sha256=O-avAXq9uk27BBGyRlTlaX4a1xvLwwNKJDg0XozxZQc,8140
69
- flwr/cli/stop.py,sha256=pa3etMLCLxfGl9w2c2o6e5u46j6LimEmNp2zuQGxAIk,3143
70
- flwr/cli/utils.py,sha256=aVTtQ2RD5RM8dnmqHNEp1kIBVyjF4tJ7KupczmegWc8,8331
68
+ flwr/cli/run/run.py,sha256=BvpjYyUvDhVMvO5cG711ihtdeSbls9p8zVAuFGETLA8,7893
69
+ flwr/cli/stop.py,sha256=1T9RNRCH8dxjmBT38hFtKAWY9Gb7RMCMCML7kex9WzE,4613
70
+ flwr/cli/utils.py,sha256=kko38Sci_yy5V5f8ZXwKyAzG-7BRlkKi2l2vlFOx5ug,8626
71
71
  flwr/client/__init__.py,sha256=DGDoO0AEAfz-0CUFmLdyUUweAS64-07AOnmDfWUefK4,1192
72
- flwr/client/app.py,sha256=eznpoeYPRI-pjgtb3BCZVQGz9L91SSRrXbZOMzbIt54,34398
73
- flwr/client/client.py,sha256=lIIUgxHk4tofk2RljVBL5wFzPR_Ob9iURg0NpyKufPY,9081
72
+ flwr/client/app.py,sha256=XJWu-kPswM52oLYXaOLKr0gj87KPNRI7M0Na9oBsDK4,34784
73
+ flwr/client/client.py,sha256=8o58nd9o6ZFcMIaVYPGcV4MSjBG4H0oFgWiv8ZEO3oA,7895
74
74
  flwr/client/client_app.py,sha256=cTig-N00YzTucbo9zNi6I21J8PlbflU_8J_f5CI-Wpw,10390
75
75
  flwr/client/clientapp/__init__.py,sha256=kZqChGnTChQ1WGSUkIlW2S5bc0d0mzDubCAmZUGRpEY,800
76
- flwr/client/clientapp/app.py,sha256=w94GzkFJw89xMZ69znEz6vNs0tELhMSlLlqttFuJDe8,8807
76
+ flwr/client/clientapp/app.py,sha256=n3IbbQ__QBjd4n7hhP2oydYg66IvrnSXvwi3sZvnWeU,8949
77
77
  flwr/client/clientapp/clientappio_servicer.py,sha256=5L6bjw_j3Mnx9kRFwYwxDNABKurBO5q1jZOWE_X11wQ,8522
78
78
  flwr/client/clientapp/utils.py,sha256=TTihPRO_AUhA3ZCszPsLyLZ30D_tnhTfe1ndMNVOBPg,4344
79
79
  flwr/client/dpfedavg_numpy_client.py,sha256=4KsEvzavDKyVDU1V0kMqffTwu1lNdUCYQN-i0DTYVN8,7404
@@ -83,11 +83,11 @@ flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1
83
83
  flwr/client/grpc_client/connection.py,sha256=gMwB87mlmRBbvPOvUA1m3C-Ci6bjMEmTRI4bJpgbyic,9416
84
84
  flwr/client/grpc_rere_client/__init__.py,sha256=MK-oSoV3kwUEQnIwl0GN4OpiHR7eLOrMA8ikunET130,752
85
85
  flwr/client/grpc_rere_client/client_interceptor.py,sha256=q08lIEeJLvvonNOiejNXvmySbPObteGnbDHhEKDmWzE,5380
86
- flwr/client/grpc_rere_client/connection.py,sha256=fqqorPCieqfFHbFXPniAM-qga5Lf-6-kp6DHdbk8Rrg,11148
86
+ flwr/client/grpc_rere_client/connection.py,sha256=NqKSoYIJblB4lElZ7EKIgDjLb6KYEcI-7CBrTbyiKfg,11475
87
87
  flwr/client/grpc_rere_client/grpc_adapter.py,sha256=sQo0I9T65t97LFGoW_PrmgaTbd18GFgi2DoAI5wQJ4k,5589
88
88
  flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
89
89
  flwr/client/message_handler/__init__.py,sha256=QxxQuBNpFPTHx3KiUNvQSlqMKlEnbRR1kFfc1KVje08,719
90
- flwr/client/message_handler/message_handler.py,sha256=wEbSn60qjozUfNaJIPTsSG2X9OsAKBd8D4c7TU4oJc4,6525
90
+ flwr/client/message_handler/message_handler.py,sha256=s7FEfYJp5QB259Pj1L94_9AC24Kh5JyKC2U-E6eNkkY,6492
91
91
  flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5VmZiSp_6h9GHVU,1824
92
92
  flwr/client/mod/__init__.py,sha256=37XeXZLFq_tzFVKVtC9JaigM2bSAU7BrGQvMPCE3Q28,1159
93
93
  flwr/client/mod/centraldp_mods.py,sha256=UGwNuqpmOWfLdfJITFgdi1TG-nLjuSb-cbEyoyfDgxQ,5415
@@ -101,7 +101,7 @@ flwr/client/nodestate/__init__.py,sha256=6FTlzydo1j0n55Tb-Qo0XmuqTUyRxg3x7jHgo3g
101
101
  flwr/client/nodestate/in_memory_nodestate.py,sha256=MKI3jVPARPWJmNGw61k1-9LIXROkTx2PrhWjDM8cpHk,1291
102
102
  flwr/client/nodestate/nodestate.py,sha256=CmHZdR6kVO8tkffg42W0Yb9JdRmrUonZ9deXfUNK6Hg,1024
103
103
  flwr/client/nodestate/nodestate_factory.py,sha256=apUbcJG0a_FUVsc0TkNN3q9yovc9u_J34u9iuLFKTLQ,1430
104
- flwr/client/numpy_client.py,sha256=tqGyhIkeeZQGr65BR03B7TWgx4rW3FA7G2874D8z_VU,11167
104
+ flwr/client/numpy_client.py,sha256=chTkL9dOtK_wgUoYtzp5mfDOC1k8xPAd1qPIsB3hcjA,9581
105
105
  flwr/client/rest_client/__init__.py,sha256=5KGlp7pjc1dhNRkKlaNtUfQmg8wrRFh9lS3P3uRS-7Q,735
106
106
  flwr/client/rest_client/connection.py,sha256=NWBu7Cc8LUTlf7GjJl3rgdCAykrE5suul_xZUV21OgI,12659
107
107
  flwr/client/run_info_store.py,sha256=ZN2Phi4DSLbSyzg8RmzJcVYh1g6eurHOmWRCT7GMtw4,4040
@@ -122,7 +122,7 @@ flwr/common/differential_privacy_constants.py,sha256=c7b7tqgvT7yMK0XN9ndiTBs4mQf
122
122
  flwr/common/dp.py,sha256=vddkvyjV2FhRoN4VuU2LeAM1UBn7dQB8_W-Qdiveal8,1978
123
123
  flwr/common/exit_handlers.py,sha256=MracJaBeoCOC7TaXK9zCJQxhrMSx9ZtczK237qvhBpU,2806
124
124
  flwr/common/grpc.py,sha256=AIPMAHsvcTlduaYKCgnoBnst1A7RZEgGqh0Ulm7qfJ0,2621
125
- flwr/common/logger.py,sha256=mdo-jhSL5--dSzaLX0oPf_XluSBEYcF93Nnloz6orC4,11941
125
+ flwr/common/logger.py,sha256=qwiOc9N_Dqh-NlxtENcMa-dCPqint20ZLuWEvnAEwHU,12323
126
126
  flwr/common/message.py,sha256=Zv4ID2BLQsbff0F03DI_MeFoHbSqVZAdDD9NcKYv6Zo,13832
127
127
  flwr/common/object_ref.py,sha256=fIXf8aP5mG6Nuni7dvcKK5Di3zRfRWGs4ljvqIXplds,10115
128
128
  flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
@@ -135,7 +135,7 @@ flwr/common/record/parametersrecord.py,sha256=SasHn35JRHsj8G1UT76FgRjaP4ZJasejvg
135
135
  flwr/common/record/recordset.py,sha256=sSofrBycZSqiHR4TzfI4_QoIIN-5B1LnMG0C9CiByAo,8312
136
136
  flwr/common/record/typeddict.py,sha256=q5hL2xkXymuiCprHWb69mUmLpWQk_XXQq0hGQ69YPaw,3599
137
137
  flwr/common/recordset_compat.py,sha256=ViSwA26h6Q55ZmV1LLjSJpcKiipV-p_JpCj4wxdE-Ow,14230
138
- flwr/common/retry_invoker.py,sha256=nCA-dfBw6YoWkOgop71QfhTDmYj1JIgXsdpzlyqgZK4,14396
138
+ flwr/common/retry_invoker.py,sha256=UIDKsn0AitS3fOr43WTqZAdD-TaHkBeTj1QxD7SGba0,14481
139
139
  flwr/common/secure_aggregation/__init__.py,sha256=erPnTWdOfMH0K0HQTmj5foDJ6t3iYcExy2aACy8iZNQ,731
140
140
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=nlHesCWy8xxE5s6qHWnauCtyClcMQ2K0CEXAHakY5n0,738
141
141
  flwr/common/secure_aggregation/crypto/shamir.py,sha256=wCSfEfeaPgJ9Om580-YPUF2ljiyRhq33TRC4HtwxYl8,2779
@@ -211,7 +211,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
211
211
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
212
212
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
213
213
  flwr/server/__init__.py,sha256=cEg1oecBu4cKB69iJCqWEylC8b5XW47bl7rQiJsdTvM,1528
214
- flwr/server/app.py,sha256=JDF_dEnLvnZwcQgGz2E7ur8eBDC-xVU04hf2adirDvU,30869
214
+ flwr/server/app.py,sha256=t2N5Q2CEOptaCBxmos397Re9UDPDvNvgnXxNy7xqj-g,30944
215
215
  flwr/server/client_manager.py,sha256=7Ese0tgrH-i-ms363feYZJKwB8gWnXSmg_hYF2Bju4U,6227
216
216
  flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
217
217
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
@@ -259,7 +259,7 @@ flwr/server/strategy/strategy.py,sha256=cXapkD5uDrt5C-RbmWDn9FLoap3Q41i7GKvbmfbC
259
259
  flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
260
260
  flwr/server/superlink/driver/__init__.py,sha256=5soEK5QSvxNjmJQ-CGTWROc4alSAeU0e9Ad9RDhsd3E,717
261
261
  flwr/server/superlink/driver/serverappio_grpc.py,sha256=62371xIRzp3k-eQTaSpb9c4TiSfueSuI7Iw5X3IafOY,2186
262
- flwr/server/superlink/driver/serverappio_servicer.py,sha256=EjqseReChpd0b4ZPEVPbWiWlVMsqK-WhIbH7jNHi7IM,11880
262
+ flwr/server/superlink/driver/serverappio_servicer.py,sha256=mgyV0XTONO7Vqb7sGOLu6AkCXWpBSeJ2s7ksadK1vE4,12197
263
263
  flwr/server/superlink/ffs/__init__.py,sha256=FAY-zShcfPmOxosok2QyT6hTNMNctG8cH9s_nIl8jkI,840
264
264
  flwr/server/superlink/ffs/disk_ffs.py,sha256=n_Ah0sQwXGVQ9wj5965nLjdkQQbpoHCljjXKFnwftsU,3297
265
265
  flwr/server/superlink/ffs/ffs.py,sha256=qLI1UfosJugu2BKOJWqHIhafTm-YiuKqGf3OGWPH0NM,2395
@@ -276,7 +276,7 @@ flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=j2hyC342am-_Hgp1g80Y3fG
276
276
  flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=KBVsGt57G2_OWB_74N29TYVzD36G0xJg2l5m0ArPoEU,5389
277
277
  flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=8PHzqtW_rKBvqI5XVwYN-CBEpEonnj85iN0daSWliyI,8299
278
278
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=h8oLD7uo5lKICPy0rRdKRjTYe62u8PKkT_fA4xF5JPA,731
279
- flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=BVc30QUQBP8aMZ77jT4-VEbbqMI7b4kbw33ERzlbwWk,5329
279
+ flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=hzL8t6uUqO1lu5UHLF_NerdJuce4S5cK9fIWkKDzJfA,5298
280
280
  flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=5jbYbAn75sGv-gBwOPDySE0kz96F6dTYLeMrGqNi4lM,735
281
281
  flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=0b10l9zz381GXgCmTZGoz76Z_fdRaa8XcqMGwuLqJ38,6723
282
282
  flwr/server/superlink/fleet/vce/__init__.py,sha256=TZJsKTpYO_djv2EXx9Ji62I8TA0JiZF8jvRyJRZkAes,784
@@ -288,11 +288,11 @@ flwr/server/superlink/linkstate/__init__.py,sha256=v-2JyJlCB3qyhMNwMjmcNVOq4rkoo
288
288
  flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=haJiQ0TkinyVH4vOG-EUuEhhI78YESgjKYU6qVgXics,21638
289
289
  flwr/server/superlink/linkstate/linkstate.py,sha256=sbI7JLAZNMtVH1ZRjRjWDrws4mL0fjvrywxAKgCw9Mw,12936
290
290
  flwr/server/superlink/linkstate/linkstate_factory.py,sha256=ISSMjDlwuN7swxjOeYlTNpI_kuZ8PGkMcJnf1dbhUSE,2069
291
- flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=9xmHV15hAicEtViCv4X-lHQosaCUwT6-NPd2UnY0zhA,42662
291
+ flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=q_XyGRxL6vTT-fe0L6dKnPEZq0ARIT4D_ytGajU9Zzk,42592
292
292
  flwr/server/superlink/linkstate/utils.py,sha256=d5uqqIOCKfd54X8CFNfUr3AWqPLpgmzsC_RagRwFugM,13321
293
293
  flwr/server/superlink/simulation/__init__.py,sha256=mg-oapC9dkzEfjXPQFior5lpWj4g9kwbLovptyYM_g0,718
294
294
  flwr/server/superlink/simulation/simulationio_grpc.py,sha256=5wflYW_TS0mjmPG6OYuHMJwXD2_cYmUNhFkdOU0jMWQ,2237
295
- flwr/server/superlink/simulation/simulationio_servicer.py,sha256=UqOyc76IQ5iDELkyuET1J9F20K0mYib30HyVLwX_2Mg,6546
295
+ flwr/server/superlink/simulation/simulationio_servicer.py,sha256=J_TmdqM-Bxgp-iPEI3tvCuBpykw1UX0FouMQalEYAF4,6907
296
296
  flwr/server/superlink/utils.py,sha256=KVb3K_g2vYfu9TnftcN0ewmev133WZcjuEePMm8d7GE,2137
297
297
  flwr/server/typing.py,sha256=5kaRLZuxTEse9A0g7aVna2VhYxU3wTq1f3d3mtw7kXs,1019
298
298
  flwr/server/utils/__init__.py,sha256=pltsPHJoXmUIr3utjwwYxu7_ZAGy5u4MVHzv9iA5Un8,908
@@ -305,14 +305,14 @@ flwr/server/workflow/secure_aggregation/__init__.py,sha256=3XlgDOjD_hcukTGl6Bc1B
305
305
  flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=l2IdMdJjs1bgHs5vQgLSOVzar7v2oxUn46oCrnVE1rM,5839
306
306
  flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=rfn2etO1nb7u-1oRl-H9q3enJZz3shMINZaBB7rPsC4,29671
307
307
  flwr/simulation/__init__.py,sha256=5UcDVJNjFoSwWqHbGM1hKfTTUUNdwAtuoNvNrfvdkUY,1556
308
- flwr/simulation/app.py,sha256=qalNoJxT17bbU-kVQNGTPSFu6C7W9F1oZl4i4nzCJx0,9380
308
+ flwr/simulation/app.py,sha256=Q9vZFVUvy2_QNNUpyElCmAMfe4d90mSoX9rGpzgZjD4,9412
309
309
  flwr/simulation/legacy_app.py,sha256=ySggtKEtXe8L77n8qyGXDA7UPv840MXh-QoalzoGiLU,15780
310
310
  flwr/simulation/ray_transport/__init__.py,sha256=wzcEEwUUlulnXsg6raCA1nGpP3LlAQDtJ8zNkCXcVbA,734
311
311
  flwr/simulation/ray_transport/ray_actor.py,sha256=k11yoAPQzFGQU-KnCCP0ZrfPPdUPXXrBe-1DKM5VdW4,18997
312
312
  flwr/simulation/ray_transport/ray_client_proxy.py,sha256=2vjOKoom3B74C6XU-jC3N6DwYmsLdB-lmkHZ_Xrv96o,7367
313
313
  flwr/simulation/ray_transport/utils.py,sha256=wtbQhKQ4jGoiQDLJNQP17m1DSfL22ERhDBGuoeUFaAQ,2393
314
- flwr/simulation/run_simulation.py,sha256=9zIDWA0fL901cobVRfVuJlTdxAIYJJqjiPV4_a1_v3U,20023
315
- flwr/simulation/simulationio_connection.py,sha256=m31L9Iej-61va48E5x-wJypA6p5s82WM4PKIAmKizQA,3209
314
+ flwr/simulation/run_simulation.py,sha256=MSD2USh40j8vRrUyJeb3ngtghB8evtFLBMi6nUfpljY,20169
315
+ flwr/simulation/simulationio_connection.py,sha256=8aAh6MKQkQPMSnWEqA5vua_QzZtoMxG-_-AB23RPhS4,3412
316
316
  flwr/superexec/__init__.py,sha256=fcj366jh4RFby_vDwLroU4kepzqbnJgseZD_jUr_Mko,715
317
317
  flwr/superexec/app.py,sha256=Z6kYHWd62YL0Q4YKyCAbt_BcefNfbKH6V-jCC-1NkZM,1842
318
318
  flwr/superexec/deployment.py,sha256=wZ9G42gGS91knfplswh95MnQ83Fzu-rs6wcuNgDmmvY,6735
@@ -321,8 +321,8 @@ flwr/superexec/exec_servicer.py,sha256=8tFwj1fDBF6PzwLhByTlxM-KNZc83bG1UdE92-8DS
321
321
  flwr/superexec/exec_user_auth_interceptor.py,sha256=K06OU-l4LnYhTDg071hGJuOaQWEJbZsYi5qxUmmtiG0,3704
322
322
  flwr/superexec/executor.py,sha256=_B55WW2TD1fBINpabSSDRenVHXYmvlfhv-k8hJKU4lQ,3115
323
323
  flwr/superexec/simulation.py,sha256=WQDon15oqpMopAZnwRZoTICYCfHqtkvFSqiTQ2hLD_g,4088
324
- flwr_nightly-1.14.0.dev20241215.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
325
- flwr_nightly-1.14.0.dev20241215.dist-info/METADATA,sha256=csVHUef81pr3BfCXzf9Q-1SeSVielWD42cIhAG1KeC4,15799
326
- flwr_nightly-1.14.0.dev20241215.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
327
- flwr_nightly-1.14.0.dev20241215.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
328
- flwr_nightly-1.14.0.dev20241215.dist-info/RECORD,,
324
+ flwr_nightly-1.14.0.dev20241217.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
325
+ flwr_nightly-1.14.0.dev20241217.dist-info/METADATA,sha256=U7ytaeqIXkDuNUF0Dt5_nCAgh6ILjoWnyYiX1gpo2gM,15799
326
+ flwr_nightly-1.14.0.dev20241217.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
327
+ flwr_nightly-1.14.0.dev20241217.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
328
+ flwr_nightly-1.14.0.dev20241217.dist-info/RECORD,,