flwr-nightly 1.21.0.dev20250812__py3-none-any.whl → 1.21.0.dev20250814__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/common/args.py CHANGED
@@ -28,6 +28,12 @@ from flwr.common.logger import log
28
28
 
29
29
  def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
30
30
  """Add common Flower arguments for flwr-*app to the provided parser."""
31
+ parser.add_argument(
32
+ "--token",
33
+ type=str,
34
+ required=False,
35
+ help="Unique token generated by AppIo API for each app execution",
36
+ )
31
37
  parser.add_argument(
32
38
  "--flwr-dir",
33
39
  default=None,
@@ -47,6 +53,13 @@ def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
47
53
  "is not encrypted. By default, the server runs with HTTPS enabled. "
48
54
  "Use this flag only if you understand the risks.",
49
55
  )
56
+ parser.add_argument(
57
+ "--parent-pid",
58
+ type=int,
59
+ default=None,
60
+ help="The PID of the parent process. When set, the process will terminate "
61
+ "when the parent process exits.",
62
+ )
50
63
 
51
64
 
52
65
  def try_obtain_root_certificates(
@@ -20,7 +20,7 @@ from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
20
20
  from flwr.proto import appio_pb2 as flwr_dot_proto_dot_appio__pb2
21
21
 
22
22
 
23
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x66lwr/proto/simulationio.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x16\x66lwr/proto/appio.proto2\xc1\x06\n\x0cSimulationIo\x12_\n\x10ListAppsToLaunch\x12#.flwr.proto.ListAppsToLaunchRequest\x1a$.flwr.proto.ListAppsToLaunchResponse\"\x00\x12S\n\x0cRequestToken\x12\x1f.flwr.proto.RequestTokenRequest\x1a .flwr.proto.RequestTokenResponse\"\x00\x12V\n\rPullAppInputs\x12 .flwr.proto.PullAppInputsRequest\x1a!.flwr.proto.PullAppInputsResponse\"\x00\x12Y\n\x0ePushAppOutputs\x12!.flwr.proto.PushAppOutputsRequest\x1a\".flwr.proto.PushAppOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x12k\n\x14GetFederationOptions\x12\'.flwr.proto.GetFederationOptionsRequest\x1a(.flwr.proto.GetFederationOptionsResponse\"\x00\x12S\n\x0cGetRunStatus\x12\x1f.flwr.proto.GetRunStatusRequest\x1a .flwr.proto.GetRunStatusResponse\"\x00\x12_\n\x10SendAppHeartbeat\x12#.flwr.proto.SendAppHeartbeatRequest\x1a$.flwr.proto.SendAppHeartbeatResponse\"\x00\x62\x06proto3')
23
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x66lwr/proto/simulationio.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x16\x66lwr/proto/appio.proto2\x84\x07\n\x0cSimulationIo\x12_\n\x10ListAppsToLaunch\x12#.flwr.proto.ListAppsToLaunchRequest\x1a$.flwr.proto.ListAppsToLaunchResponse\"\x00\x12S\n\x0cRequestToken\x12\x1f.flwr.proto.RequestTokenRequest\x1a .flwr.proto.RequestTokenResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12V\n\rPullAppInputs\x12 .flwr.proto.PullAppInputsRequest\x1a!.flwr.proto.PullAppInputsResponse\"\x00\x12Y\n\x0ePushAppOutputs\x12!.flwr.proto.PushAppOutputsRequest\x1a\".flwr.proto.PushAppOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x12k\n\x14GetFederationOptions\x12\'.flwr.proto.GetFederationOptionsRequest\x1a(.flwr.proto.GetFederationOptionsResponse\"\x00\x12S\n\x0cGetRunStatus\x12\x1f.flwr.proto.GetRunStatusRequest\x1a .flwr.proto.GetRunStatusResponse\"\x00\x12_\n\x10SendAppHeartbeat\x12#.flwr.proto.SendAppHeartbeatRequest\x1a$.flwr.proto.SendAppHeartbeatResponse\"\x00\x62\x06proto3')
24
24
 
25
25
  _globals = globals()
26
26
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -28,5 +28,5 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.simulationio_pb2
28
28
  if _descriptor._USE_C_DESCRIPTORS == False:
29
29
  DESCRIPTOR._options = None
30
30
  _globals['_SIMULATIONIO']._serialized_start=190
31
- _globals['_SIMULATIONIO']._serialized_end=1023
31
+ _globals['_SIMULATIONIO']._serialized_end=1090
32
32
  # @@protoc_insertion_point(module_scope)
@@ -27,6 +27,11 @@ class SimulationIoStub(object):
27
27
  request_serializer=flwr_dot_proto_dot_appio__pb2.RequestTokenRequest.SerializeToString,
28
28
  response_deserializer=flwr_dot_proto_dot_appio__pb2.RequestTokenResponse.FromString,
29
29
  )
30
+ self.GetRun = channel.unary_unary(
31
+ '/flwr.proto.SimulationIo/GetRun',
32
+ request_serializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString,
33
+ response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString,
34
+ )
30
35
  self.PullAppInputs = channel.unary_unary(
31
36
  '/flwr.proto.SimulationIo/PullAppInputs',
32
37
  request_serializer=flwr_dot_proto_dot_appio__pb2.PullAppInputsRequest.SerializeToString,
@@ -81,6 +86,13 @@ class SimulationIoServicer(object):
81
86
  context.set_details('Method not implemented!')
82
87
  raise NotImplementedError('Method not implemented!')
83
88
 
89
+ def GetRun(self, request, context):
90
+ """Get run details
91
+ """
92
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
93
+ context.set_details('Method not implemented!')
94
+ raise NotImplementedError('Method not implemented!')
95
+
84
96
  def PullAppInputs(self, request, context):
85
97
  """Pull Simulation inputs
86
98
  """
@@ -143,6 +155,11 @@ def add_SimulationIoServicer_to_server(servicer, server):
143
155
  request_deserializer=flwr_dot_proto_dot_appio__pb2.RequestTokenRequest.FromString,
144
156
  response_serializer=flwr_dot_proto_dot_appio__pb2.RequestTokenResponse.SerializeToString,
145
157
  ),
158
+ 'GetRun': grpc.unary_unary_rpc_method_handler(
159
+ servicer.GetRun,
160
+ request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.FromString,
161
+ response_serializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.SerializeToString,
162
+ ),
146
163
  'PullAppInputs': grpc.unary_unary_rpc_method_handler(
147
164
  servicer.PullAppInputs,
148
165
  request_deserializer=flwr_dot_proto_dot_appio__pb2.PullAppInputsRequest.FromString,
@@ -222,6 +239,23 @@ class SimulationIo(object):
222
239
  options, channel_credentials,
223
240
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
224
241
 
242
+ @staticmethod
243
+ def GetRun(request,
244
+ target,
245
+ options=(),
246
+ channel_credentials=None,
247
+ call_credentials=None,
248
+ insecure=False,
249
+ compression=None,
250
+ wait_for_ready=None,
251
+ timeout=None,
252
+ metadata=None):
253
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.SimulationIo/GetRun',
254
+ flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString,
255
+ flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString,
256
+ options, channel_credentials,
257
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
258
+
225
259
  @staticmethod
226
260
  def PullAppInputs(request,
227
261
  target,
@@ -21,6 +21,11 @@ class SimulationIoStub:
21
21
  flwr.proto.appio_pb2.RequestTokenResponse]
22
22
  """Request token for a run"""
23
23
 
24
+ GetRun: grpc.UnaryUnaryMultiCallable[
25
+ flwr.proto.run_pb2.GetRunRequest,
26
+ flwr.proto.run_pb2.GetRunResponse]
27
+ """Get run details"""
28
+
24
29
  PullAppInputs: grpc.UnaryUnaryMultiCallable[
25
30
  flwr.proto.appio_pb2.PullAppInputsRequest,
26
31
  flwr.proto.appio_pb2.PullAppInputsResponse]
@@ -74,6 +79,14 @@ class SimulationIoServicer(metaclass=abc.ABCMeta):
74
79
  """Request token for a run"""
75
80
  pass
76
81
 
82
+ @abc.abstractmethod
83
+ def GetRun(self,
84
+ request: flwr.proto.run_pb2.GetRunRequest,
85
+ context: grpc.ServicerContext,
86
+ ) -> flwr.proto.run_pb2.GetRunResponse:
87
+ """Get run details"""
88
+ pass
89
+
77
90
  @abc.abstractmethod
78
91
  def PullAppInputs(self,
79
92
  request: flwr.proto.appio_pb2.PullAppInputsRequest,
flwr/server/app.py CHANGED
@@ -18,9 +18,8 @@
18
18
  import argparse
19
19
  import csv
20
20
  import importlib.util
21
- import multiprocessing
22
- import multiprocessing.context
23
21
  import os
22
+ import subprocess
24
23
  import sys
25
24
  import threading
26
25
  from collections.abc import Sequence
@@ -55,6 +54,7 @@ from flwr.common.constant import (
55
54
  TRANSPORT_TYPE_GRPC_RERE,
56
55
  TRANSPORT_TYPE_REST,
57
56
  EventLogWriterType,
57
+ ExecPluginType,
58
58
  )
59
59
  from flwr.common.event_log_plugin import EventLogWriterPlugin
60
60
  from flwr.common.exit import ExitCode, flwr_exit
@@ -69,8 +69,6 @@ from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
69
69
  )
70
70
  from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server
71
71
  from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
72
- from flwr.server.serverapp.app import flwr_serverapp
73
- from flwr.simulation.app import flwr_simulation
74
72
  from flwr.supercore.ffs import FfsFactory
75
73
  from flwr.supercore.object_store import ObjectStoreFactory
76
74
  from flwr.superlink.executor import load_executor
@@ -219,10 +217,10 @@ def run_superlink() -> None:
219
217
 
220
218
  # Determine Exec plugin
221
219
  # If simulation is used, don't start ServerAppIo and Fleet APIs
222
- sim_exec = executor.__class__.__qualname__ == "SimulationEngine"
220
+ is_simulation = executor.__class__.__qualname__ == "SimulationEngine"
223
221
  bckg_threads: list[threading.Thread] = []
224
222
 
225
- if sim_exec:
223
+ if is_simulation:
226
224
  simulationio_server: grpc.Server = run_simulationio_api_grpc(
227
225
  address=simulationio_address,
228
226
  state_factory=state_factory,
@@ -340,25 +338,18 @@ def run_superlink() -> None:
340
338
  io_address = (
341
339
  f"{CLIENT_OCTET}:{_port}" if _octet == SERVER_OCTET else serverappio_address
342
340
  )
343
- address_arg = (
344
- "--simulationio-api-address" if sim_exec else "--serverappio-api-address"
345
- )
346
- address = simulationio_address if sim_exec else io_address
347
- cmd = "flwr-simulation" if sim_exec else "flwr-serverapp"
348
-
349
- # Scheduler thread
350
- scheduler_th = threading.Thread(
351
- target=_flwr_scheduler,
352
- args=(
353
- state_factory,
354
- address_arg,
355
- address,
356
- cmd,
357
- ),
358
- daemon=True,
359
- )
360
- scheduler_th.start()
361
- bckg_threads.append(scheduler_th)
341
+ command = ["flower-superexec", "--insecure"]
342
+ command += [
343
+ "--appio-api-address",
344
+ simulationio_address if is_simulation else io_address,
345
+ ]
346
+ command += [
347
+ "--plugin-type",
348
+ ExecPluginType.SIMULATION if is_simulation else ExecPluginType.SERVER_APP,
349
+ ]
350
+ command += ["--parent-pid", str(os.getpid())]
351
+ # pylint: disable-next=consider-using-with
352
+ subprocess.Popen(command)
362
353
 
363
354
  # Graceful shutdown
364
355
  register_exit_handlers(
@@ -376,75 +367,6 @@ def run_superlink() -> None:
376
367
  flwr_exit(ExitCode.SUPERLINK_THREAD_CRASH)
377
368
 
378
369
 
379
- def _run_flwr_command(args: list[str], main_pid: int) -> None:
380
- # Monitor the main process in case of SIGKILL
381
- def main_process_monitor() -> None:
382
- while True:
383
- sleep(1)
384
- if os.getppid() != main_pid:
385
- os.kill(os.getpid(), 9)
386
-
387
- threading.Thread(target=main_process_monitor, daemon=True).start()
388
-
389
- # Run the command
390
- sys.argv = args
391
- if args[0] == "flwr-serverapp":
392
- flwr_serverapp()
393
- elif args[0] == "flwr-simulation":
394
- flwr_simulation()
395
- else:
396
- raise ValueError(f"Unknown command: {args[0]}")
397
-
398
-
399
- def _flwr_scheduler(
400
- state_factory: LinkStateFactory,
401
- io_api_arg: str,
402
- io_api_address: str,
403
- cmd: str,
404
- ) -> None:
405
- log(DEBUG, "Started %s scheduler thread.", cmd)
406
- state = state_factory.state()
407
- run_id_to_proc: dict[int, multiprocessing.context.SpawnProcess] = {}
408
-
409
- # Use the "spawn" start method for multiprocessing.
410
- mp_spawn_context = multiprocessing.get_context("spawn")
411
-
412
- # Periodically check for a pending run in the LinkState
413
- while True:
414
- sleep(0.1)
415
- pending_run_id = state.get_pending_run_id()
416
-
417
- if pending_run_id and pending_run_id not in run_id_to_proc:
418
-
419
- log(
420
- INFO,
421
- "Launching %s subprocess. Connects to SuperLink on %s",
422
- cmd,
423
- io_api_address,
424
- )
425
- # Start subprocess
426
- command = [
427
- cmd,
428
- "--run-once",
429
- io_api_arg,
430
- io_api_address,
431
- "--insecure",
432
- ]
433
-
434
- proc = mp_spawn_context.Process(
435
- target=_run_flwr_command, args=(command, os.getpid()), daemon=True
436
- )
437
- proc.start()
438
-
439
- # Store the process
440
- run_id_to_proc[pending_run_id] = proc
441
-
442
- # Clean up finished processes
443
- for run_id, proc in list(run_id_to_proc.items()):
444
- if not proc.is_alive():
445
- del run_id_to_proc[run_id]
446
-
447
-
448
370
  def _format_address(address: str) -> tuple[str, str, int]:
449
371
  parsed_address = parse_address(address)
450
372
  if not parsed_address:
@@ -92,7 +92,7 @@ def flwr_serverapp() -> None:
92
92
  serverappio_api_address=args.serverappio_api_address,
93
93
  log_queue=log_queue,
94
94
  token=args.token,
95
- run_once=args.run_once,
95
+ run_once=(args.token is not None) or args.run_once,
96
96
  flwr_dir=args.flwr_dir,
97
97
  certificates=None,
98
98
  parent_pid=args.parent_pid,
@@ -287,19 +287,6 @@ def _parse_args_run_flwr_serverapp() -> argparse.ArgumentParser:
287
287
  help="Address of SuperLink's ServerAppIo API (IPv4, IPv6, or a domain name)."
288
288
  f"By default, it is set to {SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
289
289
  )
290
- parser.add_argument(
291
- "--token",
292
- type=str,
293
- required=False,
294
- help="Unique token generated by SuperNode for each ServerApp execution",
295
- )
296
- parser.add_argument(
297
- "--parent-pid",
298
- type=int,
299
- default=None,
300
- help="The PID of the parent process. When set, the process will terminate "
301
- "when the parent process exits.",
302
- )
303
290
  parser.add_argument(
304
291
  "--run-once",
305
292
  action="store_true",
@@ -161,6 +161,7 @@ class RayBackend(Backend):
161
161
  "Call the backend's `build()` method before processing messages."
162
162
  )
163
163
 
164
+ future = None
164
165
  try:
165
166
  # Submit a task to the pool
166
167
  future = self.pool.submit(
@@ -183,7 +184,8 @@ class RayBackend(Backend):
183
184
  self.__class__.__name__,
184
185
  )
185
186
  # add actor back into pool
186
- self.pool.add_actor_back_to_pool(future)
187
+ if future is not None:
188
+ self.pool.add_actor_back_to_pool(future)
187
189
  raise ex
188
190
 
189
191
  def terminate(self) -> None:
@@ -23,7 +23,6 @@ from concurrent.futures import ThreadPoolExecutor
23
23
  from logging import DEBUG, ERROR, INFO, WARN
24
24
  from pathlib import Path
25
25
  from queue import Empty, Queue
26
- from time import sleep
27
26
  from typing import Callable, Optional
28
27
  from uuid import uuid4
29
28
 
@@ -153,7 +152,7 @@ def add_messages_to_queue(
153
152
  message_ins_list = state.get_message_ins(node_id=node_id, limit=1)
154
153
  for msg in message_ins_list:
155
154
  queue.put(msg)
156
- sleep(0.1)
155
+ f_stop.wait(0.1)
157
156
 
158
157
 
159
158
  def put_message_into_state(
@@ -182,6 +181,7 @@ def run_api(
182
181
  messageins_queue: Queue[Message] = Queue()
183
182
  messageres_queue: Queue[Message] = Queue()
184
183
 
184
+ backend = None
185
185
  try:
186
186
 
187
187
  # Instantiate backend
@@ -236,16 +236,16 @@ def run_api(
236
236
  log(ERROR, traceback.format_exc())
237
237
  log(WARN, "Stopping Simulation Engine.")
238
238
 
239
- # Manually trigger stopping event
240
- f_stop.set()
241
-
242
239
  # Raise exception
243
240
  raise RuntimeError("Simulation Engine crashed.") from ex
244
241
 
245
242
  finally:
243
+ # Manually trigger stopping event
244
+ f_stop.set()
246
245
 
247
246
  # Terminate backend
248
- backend.terminate()
247
+ if backend is not None:
248
+ backend.terminate()
249
249
 
250
250
 
251
251
  # pylint: disable=too-many-arguments,unused-argument,too-many-locals,too-many-branches
@@ -55,6 +55,8 @@ from flwr.proto.log_pb2 import ( # pylint: disable=E0611
55
55
  from flwr.proto.run_pb2 import ( # pylint: disable=E0611
56
56
  GetFederationOptionsRequest,
57
57
  GetFederationOptionsResponse,
58
+ GetRunRequest,
59
+ GetRunResponse,
58
60
  GetRunStatusRequest,
59
61
  GetRunStatusResponse,
60
62
  UpdateRunStatusRequest,
@@ -111,6 +113,23 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
111
113
  # Return the token
112
114
  return RequestTokenResponse(token=token or "")
113
115
 
116
+ def GetRun(
117
+ self, request: GetRunRequest, context: grpc.ServicerContext
118
+ ) -> GetRunResponse:
119
+ """Get run information."""
120
+ log(DEBUG, "SimulationIoServicer.GetRun")
121
+
122
+ # Init state
123
+ state = self.state_factory.state()
124
+
125
+ # Retrieve run information
126
+ run = state.get_run(request.run_id)
127
+
128
+ if run is None:
129
+ return GetRunResponse()
130
+
131
+ return GetRunResponse(run=run_to_proto(run))
132
+
114
133
  def PullAppInputs(
115
134
  self, request: PullAppInputsRequest, context: ServicerContext
116
135
  ) -> PullAppInputsResponse:
@@ -120,14 +139,11 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
120
139
  state = self.state_factory.state()
121
140
  ffs = self.ffs_factory.ffs()
122
141
 
142
+ # Validate the token
143
+ run_id = self._verify_token(request.token, context)
144
+
123
145
  # Lock access to LinkState, preventing obtaining the same pending run_id
124
146
  with self.lock:
125
- # Attempt getting the run_id of a pending run
126
- run_id = state.get_pending_run_id()
127
- # If there's no pending run, return an empty response
128
- if run_id is None:
129
- return PullAppInputsResponse()
130
-
131
147
  # Retrieve Context, Run and Fab for the run_id
132
148
  serverapp_ctxt = state.get_serverapp_context(run_id)
133
149
  run = state.get_run(run_id)
@@ -154,6 +170,11 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
154
170
  ) -> PushAppOutputsResponse:
155
171
  """Push Simulation process outputs."""
156
172
  log(DEBUG, "SimultionIoServicer.PushAppOutputs")
173
+
174
+ # Validate the token
175
+ run_id = self._verify_token(request.token, context)
176
+
177
+ # Init access to LinkState
157
178
  state = self.state_factory.state()
158
179
 
159
180
  # Abort if the run is not running
@@ -166,6 +187,9 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
166
187
  )
167
188
 
168
189
  state.set_serverapp_context(request.run_id, context_from_proto(request.context))
190
+
191
+ # Remove the token
192
+ state.delete_token(run_id)
169
193
  return PushAppOutputsResponse()
170
194
 
171
195
  def UpdateRunStatus(
@@ -248,3 +272,15 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
248
272
  )
249
273
 
250
274
  return SendAppHeartbeatResponse(success=success)
275
+
276
+ def _verify_token(self, token: str, context: grpc.ServicerContext) -> int:
277
+ """Verify the token and return the associated run ID."""
278
+ state = self.state_factory.state()
279
+ run_id = state.get_run_id_by_token(token)
280
+ if run_id is None or not state.verify_token(run_id, token):
281
+ context.abort(
282
+ grpc.StatusCode.PERMISSION_DENIED,
283
+ "Invalid token.",
284
+ )
285
+ raise RuntimeError("This line should never be reached.")
286
+ return run_id
flwr/simulation/app.py CHANGED
@@ -19,7 +19,6 @@ import argparse
19
19
  import gc
20
20
  from logging import DEBUG, ERROR, INFO
21
21
  from queue import Queue
22
- from time import sleep
23
22
  from typing import Optional
24
23
 
25
24
  from flwr.cli.config_utils import get_fab_metadata
@@ -70,6 +69,7 @@ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
70
69
  from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig
71
70
  from flwr.simulation.run_simulation import _run_simulation
72
71
  from flwr.simulation.simulationio_connection import SimulationIoConnection
72
+ from flwr.supercore.app_utils import simple_get_token, start_parent_process_monitor
73
73
 
74
74
 
75
75
  def flwr_simulation() -> None:
@@ -97,23 +97,31 @@ def flwr_simulation() -> None:
97
97
  run_simulation_process(
98
98
  simulationio_api_address=args.simulationio_api_address,
99
99
  log_queue=log_queue,
100
- run_once=args.run_once,
100
+ run_once=(args.token is not None) or args.run_once,
101
+ token=args.token,
101
102
  flwr_dir_=args.flwr_dir,
102
103
  certificates=None,
104
+ parent_pid=args.parent_pid,
103
105
  )
104
106
 
105
107
  # Restore stdout/stderr
106
108
  restore_output()
107
109
 
108
110
 
109
- def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R0915
111
+ def run_simulation_process( # pylint: disable=R0913, R0914, R0915, R0917, W0212
110
112
  simulationio_api_address: str,
111
113
  log_queue: Queue[Optional[str]],
112
114
  run_once: bool,
115
+ token: Optional[str] = None,
113
116
  flwr_dir_: Optional[str] = None,
114
117
  certificates: Optional[bytes] = None,
118
+ parent_pid: Optional[int] = None,
115
119
  ) -> None:
116
120
  """Run Flower Simulation process."""
121
+ # Start monitoring the parent process if a PID is provided
122
+ if parent_pid is not None:
123
+ start_parent_process_monitor(parent_pid)
124
+
117
125
  conn = SimulationIoConnection(
118
126
  simulationio_service_address=simulationio_api_address,
119
127
  root_certificates=certificates,
@@ -123,18 +131,19 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
123
131
  flwr_dir = get_flwr_dir(flwr_dir_)
124
132
  log_uploader = None
125
133
  heartbeat_sender = None
134
+ run_status = None
126
135
 
127
136
  while True:
128
137
 
129
138
  try:
139
+ # If token is not set, loop until token is received from SuperNode
140
+ if token is None:
141
+ log(DEBUG, "[flwr-simulation] Request token")
142
+ token = simple_get_token(conn._stub)
143
+
130
144
  # Pull SimulationInputs from LinkState
131
- req = PullAppInputsRequest()
145
+ req = PullAppInputsRequest(token=token)
132
146
  res: PullAppInputsResponse = conn._stub.PullAppInputs(req)
133
- if not res.HasField("run"):
134
- sleep(3)
135
- run_status = None
136
- continue
137
-
138
147
  context = context_from_proto(res.context)
139
148
  run = run_from_proto(res.run)
140
149
  fab = fab_from_proto(res.fab)
@@ -240,7 +249,9 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
240
249
 
241
250
  # Send resulting context
242
251
  context_proto = context_to_proto(updated_context)
243
- out_req = PushAppOutputsRequest(run_id=run.run_id, context=context_proto)
252
+ out_req = PushAppOutputsRequest(
253
+ token=token, run_id=run.run_id, context=context_proto
254
+ )
244
255
  _ = conn._stub.PushAppOutputs(out_req)
245
256
 
246
257
  run_status = RunStatus(Status.FINISHED, SubStatus.COMPLETED, "")
@@ -269,12 +280,16 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
269
280
  run_id=run.run_id, run_status=run_status_proto
270
281
  )
271
282
  )
283
+ run_status = None
272
284
 
273
285
  # Clean up the Context if it exists
274
286
  try:
275
287
  del updated_context
276
288
  except NameError:
277
289
  pass
290
+
291
+ # Remove the token
292
+ token = None
278
293
  gc.collect()
279
294
 
280
295
  # Stop the loop if `flwr-simulation` is expected to process a single run
@@ -29,6 +29,7 @@ from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
29
29
  )
30
30
  from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
31
31
  from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
32
+ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
32
33
 
33
34
  if os.name == "nt":
34
35
  from ctypes import windll # type: ignore
@@ -70,7 +71,9 @@ def start_parent_process_monitor(
70
71
  threading.Thread(target=monitor, daemon=True).start()
71
72
 
72
73
 
73
- def simple_get_token(stub: Union[ClientAppIoStub, ServerAppIoStub]) -> str:
74
+ def simple_get_token(
75
+ stub: Union[ClientAppIoStub, ServerAppIoStub, SimulationIoStub]
76
+ ) -> str:
74
77
  """Get a token from SuperLink/SuperNode.
75
78
 
76
79
  This shall be removed once the SuperExec is fully implemented.
@@ -65,19 +65,6 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
65
65
  help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
66
66
  f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
67
67
  )
68
- parser.add_argument(
69
- "--token",
70
- type=str,
71
- required=False,
72
- help="Unique token generated by SuperNode for each ClientApp execution",
73
- )
74
- parser.add_argument(
75
- "--parent-pid",
76
- type=int,
77
- default=None,
78
- help="The PID of the parent process. When set, the process will terminate "
79
- "when the parent process exits.",
80
- )
81
68
  parser.add_argument(
82
69
  "--run-once",
83
70
  action="store_true",
@@ -35,14 +35,13 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
35
35
  from flwr.common.address import parse_address
36
36
  from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
37
37
  from flwr.common.constant import (
38
- CLIENT_OCTET,
39
38
  CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
40
39
  ISOLATION_MODE_SUBPROCESS,
41
- SERVER_OCTET,
42
40
  TRANSPORT_TYPE_GRPC_ADAPTER,
43
41
  TRANSPORT_TYPE_GRPC_RERE,
44
42
  TRANSPORT_TYPE_REST,
45
43
  TRANSPORT_TYPES,
44
+ ExecPluginType,
46
45
  )
47
46
  from flwr.common.exit import ExitCode, flwr_exit
48
47
  from flwr.common.exit_handlers import register_exit_handlers
@@ -164,6 +163,15 @@ def start_client_internal(
164
163
  ffs = ffs_factory.ffs()
165
164
  store = object_store_factory.store()
166
165
 
166
+ # Launch the SuperExec if the isolation mode is `subprocess`
167
+ if isolation == ISOLATION_MODE_SUBPROCESS:
168
+ command = ["flower-superexec", "--insecure"]
169
+ command += ["--appio-api-address", clientappio_api_address]
170
+ command += ["--plugin-type", ExecPluginType.CLIENT_APP]
171
+ command += ["--parent-pid", str(os.getpid())]
172
+ # pylint: disable-next=consider-using-with
173
+ subprocess.Popen(command)
174
+
167
175
  with _init_connection(
168
176
  transport=transport,
169
177
  server_address=server_address,
@@ -207,35 +215,6 @@ def start_client_internal(
207
215
  confirm_message_received=confirm_message_received,
208
216
  )
209
217
 
210
- # Two isolation modes:
211
- # 1. `subprocess`: SuperNode is starting the ClientApp
212
- # process as a subprocess.
213
- # 2. `process`: ClientApp process gets started separately
214
- # (via `flwr-clientapp`), for example, in a separate
215
- # Docker container.
216
-
217
- # Mode 1: SuperNode starts ClientApp as subprocess
218
- start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
219
-
220
- if start_subprocess and run_id is not None:
221
- _octet, _colon, _port = clientappio_api_address.rpartition(":")
222
- io_address = (
223
- f"{CLIENT_OCTET}:{_port}"
224
- if _octet == SERVER_OCTET
225
- else clientappio_api_address
226
- )
227
- # Start ClientApp subprocess
228
- command = [
229
- "flwr-clientapp",
230
- "--clientappio-api-address",
231
- io_address,
232
- "--parent-pid",
233
- str(os.getpid()),
234
- "--insecure",
235
- "--run-once",
236
- ]
237
- subprocess.run(command, check=False)
238
-
239
218
  # No message has been pulled therefore we can skip the push stage.
240
219
  if run_id is None:
241
220
  # If no message was received, wait for a while
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.21.0.dev20250812
3
+ Version: 1.21.0.dev20250814
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  License: Apache-2.0
6
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
@@ -172,8 +172,9 @@ Flower Baselines is a collection of community-contributed projects that reproduc
172
172
  - [FedOpt](https://github.com/adap/flower/tree/main/baselines/flwr_baselines/flwr_baselines/publications/adaptive_federated_optimization)
173
173
 
174
174
  Please refer to the [Flower Baselines Documentation](https://flower.ai/docs/baselines/) for a detailed categorization of baselines and for additional info including:
175
- * [How to use Flower Baselines](https://flower.ai/docs/baselines/how-to-use-baselines.html)
176
- * [How to contribute a new Flower Baseline](https://flower.ai/docs/baselines/how-to-contribute-baselines.html)
175
+
176
+ - [How to use Flower Baselines](https://flower.ai/docs/baselines/how-to-use-baselines.html)
177
+ - [How to contribute a new Flower Baseline](https://flower.ai/docs/baselines/how-to-contribute-baselines.html)
177
178
 
178
179
  ## Flower Usage Examples
179
180
 
@@ -104,7 +104,7 @@ flwr/client/typing.py,sha256=Jw3rawDzI_-ZDcRmEQcs5gZModY7oeQlEeltYsdOhlU,1048
104
104
  flwr/clientapp/__init__.py,sha256=zGW4z49Ojzoi1hDiRC7kyhLjijUilc6fqHhtM_ATRVA,719
105
105
  flwr/common/__init__.py,sha256=5GCLVk399Az_rTJHNticRlL0Sl_oPw_j5_LuFKfX7-M,4171
106
106
  flwr/common/address.py,sha256=9JucdTwlc-jpeJkRKeUboZoacUtErwSVtnDR9kAtLqE,4119
107
- flwr/common/args.py,sha256=XFQ5PU0lU7NS1QCiKhhESHVeL8KSjcD3x8h4P3e5qlM,5298
107
+ flwr/common/args.py,sha256=WVx-NI3d9kc1vCxfQ_JAHqoAulJHbvMXDqoGjz21hF0,5686
108
108
  flwr/common/auth_plugin/__init__.py,sha256=DktrRcGZrRarLf7Jb_UlHtOyLp9_-kEplyq6PS5-vOA,988
109
109
  flwr/common/auth_plugin/auth_plugin.py,sha256=mM7SuphO4OsVAVJR1GErYVgYT83ZjxDzS_gha12bT9E,4855
110
110
  flwr/common/config.py,sha256=glcZDjco-amw1YfQcYTFJ4S1pt9APoexT-mf1QscuHs,13960
@@ -220,17 +220,17 @@ flwr/proto/serverappio_pb2.py,sha256=CRbRIJk9r4RvLng1a_2M32LdNM3PoZmBRlNLO7fKVFs
220
220
  flwr/proto/serverappio_pb2.pyi,sha256=MDY9CEUnev_oi7Y-gZIXx8divcc0BufLAE2d7nmbo_Y,1319
221
221
  flwr/proto/serverappio_pb2_grpc.py,sha256=hfRrdawakgu1uV6vf7lHSqB7IZNlxmsYmep3KJQXjjs,27446
222
222
  flwr/proto/serverappio_pb2_grpc.pyi,sha256=65o7dZaEbqaYJKnJG84umeHGKGdJJQKK1FYMIUnvYwQ,7461
223
- flwr/proto/simulationio_pb2.py,sha256=H-4-31qjJICyUFayoSwba1-nBAAKICGJ-PMboyhiyA4,2640
223
+ flwr/proto/simulationio_pb2.py,sha256=9XIMVuYUP5GsRh2ppp6mWw-IF50TY1xt6MWGR0xveMs,2733
224
224
  flwr/proto/simulationio_pb2.pyi,sha256=XbFvpZvvrS7QcH5AFXfpRGl4hQvhd3QdKO6x0oTlCCU,165
225
- flwr/proto/simulationio_pb2_grpc.py,sha256=sDPftQM16sdfL_GQTs4M2yP5Te4dcxApqwIcCosvznM,16029
226
- flwr/proto/simulationio_pb2_grpc.pyi,sha256=XtGJVEd-4SIUxtGxZ5Uu9rFwmYFaeeQcceNwyd401dM,4430
225
+ flwr/proto/simulationio_pb2_grpc.py,sha256=QJqP8njZpWbcG_vpbDI9wYEuULHPRcaEMiOxmshzgXU,17562
226
+ flwr/proto/simulationio_pb2_grpc.pyi,sha256=l9GDFkqrrHIEt3I5Eg1ZeMvEP3faN8Qcyh1pQe91DJA,4807
227
227
  flwr/proto/transport_pb2.py,sha256=P-jX_tUyk_8xFe-vIUUSfZlHGtk2Ou3A8eXdBKkp5AY,9824
228
228
  flwr/proto/transport_pb2.pyi,sha256=ipHQ03eFBqsxtAuAVefZ2lVr04BZ4YifJCS2eauNmy8,21627
229
229
  flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPcosk,2598
230
230
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
231
231
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
232
  flwr/server/__init__.py,sha256=LQQHiuL2jy7TpNaKastRdGsexlxSt5ZWAQNVqitDnrY,1598
233
- flwr/server/app.py,sha256=_STQz3zw9EBb5oMBuGUQ7bK-euoHRt2NJ6FwYUb8-Kk,30902
233
+ flwr/server/app.py,sha256=E7HFs_IB0y1haNFcuGy6dOqQ00zmW63O7pDbl8hfyLU,28639
234
234
  flwr/server/client_manager.py,sha256=5jCGavVli7XdupvWWo7ru3PdFTlRU8IGvHFSSoUVLRs,6227
235
235
  flwr/server/client_proxy.py,sha256=sv0E9AldBYOvc3pusqFh-GnyreeMfsXQ1cuTtxTq_wY,2399
236
236
  flwr/server/compat/__init__.py,sha256=0IsttWvY15qO98_1GyzVC-vR1e_ZPXOdu2qUlOkYMPE,886
@@ -250,7 +250,7 @@ flwr/server/server.py,sha256=39m4FSN2T-uVA-no9nstN0eWW0co-IUUAIMmpd3V7Jc,17893
250
250
  flwr/server/server_app.py,sha256=8uagoZX-3CY3tazPqkIV9jY-cN0YrRRrDmVe23o0AV0,9515
251
251
  flwr/server/server_config.py,sha256=e_6ddh0riwOJsdNn2BFev344uMWfDk9n7dyjNpPgm1w,1349
252
252
  flwr/server/serverapp/__init__.py,sha256=xcC0T_MQSMS9cicUzUUpMNCOsF2d8Oh_8jvnoBLuZvo,800
253
- flwr/server/serverapp/app.py,sha256=-IZY02Ym6AREMPaLld6WCD9mjDz7wsqfYgLLJxpy2pg,10433
253
+ flwr/server/serverapp/app.py,sha256=9S0B4yEuL1QFbPR7RQvn1N7BVu9t7jFhgNpIfXuRvGg,10067
254
254
  flwr/server/serverapp_components.py,sha256=dfSqmrsVy3arKXpl3ZIBQWdV8rehfIms8aJooyzdmEM,2118
255
255
  flwr/server/strategy/__init__.py,sha256=HhsSWMWaC7oCb2g7Kqn1MBKdrfvgi8VxACy9ZL706Q0,2836
256
256
  flwr/server/strategy/aggregate.py,sha256=smlKKy-uFUuuFR12vlclucnwSQWRz78R79-Km4RWqbw,13978
@@ -295,8 +295,8 @@ flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=mxWKwGpgHPqd7cGFqd2ASnR
295
295
  flwr/server/superlink/fleet/vce/__init__.py,sha256=XOKbAWOzlCqEOQ3M2cBYkH7HKA7PxlbCJMunt-ty-DY,784
296
296
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=PPH89Yqd1XKm-sRJN6R0WQlKT_b4v54Kzl2yzHAFzM8,1437
297
297
  flwr/server/superlink/fleet/vce/backend/backend.py,sha256=-wDHjgAy5mrfEgXj0GxkJI7lhEbgSUyPwmNAf9ZcDzc,2193
298
- flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=Hx9hxL7lju1_VJoAwkhBOGerZ3628u0P1zgkPhGWRPY,7154
299
- flwr/server/superlink/fleet/vce/vce_api.py,sha256=xSjQbBYHmUTinw7Q_-UxqR7qt07kqj9FCSpPYRsUKf8,12909
298
+ flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=K0QRDshgKHv7BSMpalLB4FDyLmNFis8Xqo7GSAziOXY,7215
299
+ flwr/server/superlink/fleet/vce/vce_api.py,sha256=DYaXOfVUHxjKraxlfBlB7vxMmQZ_Swx2MVvBGnwu0I0,12946
300
300
  flwr/server/superlink/linkstate/__init__.py,sha256=OtsgvDTnZLU3k0sUbkHbqoVwW6ql2FDmb6uT6DbNkZo,1064
301
301
  flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=eNjjQp6dJ_Qz54fYeOZ9XFebbB_k9syhBB6N2rXRTsA,27891
302
302
  flwr/server/superlink/linkstate/linkstate.py,sha256=TCLM9wZa2XGHs55B3LP9j5-WtUPhBjOUdMKQJELG2oY,13287
@@ -308,7 +308,7 @@ flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=zcvzDhCAnlFxAwCiJUH
308
308
  flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=3C_0boRbYuY1Vlf0DRGzBvTUX-D5UUzxYkFihSMZf-A,20094
309
309
  flwr/server/superlink/simulation/__init__.py,sha256=Ry8DrNaZCMcQXvUc4FoCN2m3dvUQgWjasfp015o3Ec4,718
310
310
  flwr/server/superlink/simulation/simulationio_grpc.py,sha256=VqWKxjpd4bCgPFKsgtIZPk9YcG0kc1EEmr5k20EKty4,2205
311
- flwr/server/superlink/simulation/simulationio_servicer.py,sha256=vuOH3upIgzQi8_vE6zE_6knnmPENhc_9dtfFNVvkQrc,8947
311
+ flwr/server/superlink/simulation/simulationio_servicer.py,sha256=aZp67AeNCGs1zI4mvj_WUOL8noxNcsYu_QIpYKhnHXg,9992
312
312
  flwr/server/superlink/utils.py,sha256=zXmyU2o535b9dgz-TvFklzfuQk4irNnMtiK8vT4Dm1c,2454
313
313
  flwr/server/typing.py,sha256=LvO6gq7H6TAWhA9JFx0WyqHxU7FycyvhSsLjBLPgpts,1011
314
314
  flwr/server/utils/__init__.py,sha256=U4gM84-uUFddarODDQkO6SjNUuGhFcsHJZMjSEbezkU,884
@@ -322,7 +322,7 @@ flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=b_pKk7gmbahwyj
322
322
  flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=DkayCsnlAya6Y2PZsueLgoUCMRtV-GbnW08RfWx_SXM,29460
323
323
  flwr/serverapp/__init__.py,sha256=HPvC_ZvMS7GCM7ALVrG_Wwm4bSDr4DZETeC561v3T9w,719
324
324
  flwr/simulation/__init__.py,sha256=Gg6OsP1Z-ixc3-xxzvl7j7rz2Fijy9rzyEPpxgAQCeM,1556
325
- flwr/simulation/app.py,sha256=z3LVQfBOrB1UUKE5tu4j3fsRQ4FrQzKLQRUavwfC0NM,10532
325
+ flwr/simulation/app.py,sha256=ZDDHNiObHVJ1tNatQHwX-c3icCl_-Bvn7q1kn37l0Pg,11163
326
326
  flwr/simulation/legacy_app.py,sha256=nMISQqW0otJL1-2Kfd94O6BLlGS2IEmEPKTM2WGKrIs,15861
327
327
  flwr/simulation/ray_transport/__init__.py,sha256=ogd-0AMv2U-wBZ1r3sHWaDIOIrVqr88Xi6C8o4Dviy0,734
328
328
  flwr/simulation/ray_transport/ray_actor.py,sha256=JN3xTqFIr5Z750k92CcA_uavzOHhSWDwE2WCaecvpks,19147
@@ -331,7 +331,7 @@ flwr/simulation/ray_transport/utils.py,sha256=KrexpWYCF-dAF3UHc9yDbPQWO-ahMT-BbD
331
331
  flwr/simulation/run_simulation.py,sha256=-sp3dNZcp7MCAH0BlmZpVcFAGvozRdYXRdDYcH_2Zxk,20838
332
332
  flwr/simulation/simulationio_connection.py,sha256=mzS1C6EEREwQDPceDo30anAasmTDLb9qqV2tXlBhOUA,3494
333
333
  flwr/supercore/__init__.py,sha256=pqkFoow_E6UhbBlhmoD1gmTH-33yJRhBsIZqxRPFZ7U,755
334
- flwr/supercore/app_utils.py,sha256=WS3tly_QIWE-NRogbtFVC5l6arxP3Md1XItI9idmt0M,2771
334
+ flwr/supercore/app_utils.py,sha256=ogT14HdSIxToJoOgVngdKJ3nf7eY4b2wSjzkPT--kX0,2857
335
335
  flwr/supercore/cli/__init__.py,sha256=EDl2aO-fuQfxSbL-T1W9RAfA2N0hpWHmqX_GSwblJbQ,845
336
336
  flwr/supercore/cli/flower_superexec.py,sha256=J_rf7SCVW9L9wsBScOYa-oJOpyb_e1WOtwTGSyUFu1k,3882
337
337
  flwr/supercore/corestate/__init__.py,sha256=Vau6-L_JG5QzNqtCTa9xCKGGljc09wY8avZmIjSJemg,774
@@ -377,7 +377,7 @@ flwr/superlink/servicer/control/control_user_auth_interceptor.py,sha256=9Aqhrt_U
377
377
  flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
378
378
  flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
379
379
  flwr/supernode/cli/flower_supernode.py,sha256=fAkk9zGhoP8Sv05EkdXRiCtirTAzWkSZBqRoaDdgflk,8529
380
- flwr/supernode/cli/flwr_clientapp.py,sha256=zaro6BoUEmfKIPQYuyJ9oR4rrHSS3bufhEqxcTo5VZU,3153
380
+ flwr/supernode/cli/flwr_clientapp.py,sha256=jxjR6etQRCHzG3zL04kyTZzicMMYdZ9dMiKdrW1uXs4,2759
381
381
  flwr/supernode/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUDfuSHsbU,976
382
382
  flwr/supernode/nodestate/in_memory_nodestate.py,sha256=rr_tg7YXhf_seYFipSB59TAfheKPratx3rrvHUOJ80g,7343
383
383
  flwr/supernode/nodestate/nodestate.py,sha256=jCOewZyctecMxsM0-_-pQwef9P3O5QjnKCgCGyx2PK4,5047
@@ -387,8 +387,8 @@ flwr/supernode/runtime/run_clientapp.py,sha256=vAeBTgIi4SmV4IRq1dSjXaxrFUPEeHg-n
387
387
  flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca8gxdEo,717
388
388
  flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
389
389
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=nIHRu38EWK-rpNOkcgBRAAKwYQQWFeCwu0lkO7OPZGQ,10239
390
- flwr/supernode/start_client_internal.py,sha256=iqJR8WbCW-8RQIRNwARZYoxhnlaAo5KnluCOEfRoLWM,21020
391
- flwr_nightly-1.21.0.dev20250812.dist-info/METADATA,sha256=Vwe0ftnR9LLzTf_h1ZFMnvBVShT7_2lKrg8BxzcxHAQ,15966
392
- flwr_nightly-1.21.0.dev20250812.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
393
- flwr_nightly-1.21.0.dev20250812.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
394
- flwr_nightly-1.21.0.dev20250812.dist-info/RECORD,,
390
+ flwr/supernode/start_client_internal.py,sha256=z2o92MQKzTRB-AZTELROueZ2ZQYouu947hiU-WJ_oq4,20257
391
+ flwr_nightly-1.21.0.dev20250814.dist-info/METADATA,sha256=KK0K1YI5Crr4QpQ8bkTH7saJIbCO5VxJABBYCMtdVIg,15967
392
+ flwr_nightly-1.21.0.dev20250814.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
393
+ flwr_nightly-1.21.0.dev20250814.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
394
+ flwr_nightly-1.21.0.dev20250814.dist-info/RECORD,,