flwr-nightly 1.14.0.dev20241216__py3-none-any.whl → 1.15.0.dev20250112__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. flwr/cli/cli_user_auth_interceptor.py +6 -2
  2. flwr/cli/log.py +8 -6
  3. flwr/cli/login/login.py +11 -4
  4. flwr/cli/ls.py +7 -4
  5. flwr/cli/new/templates/app/.gitignore.tpl +3 -0
  6. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  7. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  8. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  9. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  12. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  13. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  14. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  15. flwr/cli/run/run.py +7 -2
  16. flwr/cli/stop.py +3 -2
  17. flwr/cli/utils.py +83 -14
  18. flwr/client/app.py +17 -9
  19. flwr/client/client.py +0 -32
  20. flwr/client/grpc_rere_client/client_interceptor.py +6 -0
  21. flwr/client/grpc_rere_client/grpc_adapter.py +16 -0
  22. flwr/client/message_handler/message_handler.py +0 -2
  23. flwr/client/numpy_client.py +0 -44
  24. flwr/client/supernode/app.py +1 -2
  25. flwr/common/auth_plugin/auth_plugin.py +33 -23
  26. flwr/common/constant.py +2 -0
  27. flwr/common/grpc.py +154 -3
  28. flwr/common/record/recordset.py +1 -1
  29. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
  30. flwr/common/telemetry.py +13 -3
  31. flwr/common/typing.py +20 -0
  32. flwr/proto/exec_pb2.py +12 -24
  33. flwr/proto/exec_pb2.pyi +27 -54
  34. flwr/proto/fleet_pb2.py +40 -27
  35. flwr/proto/fleet_pb2.pyi +84 -0
  36. flwr/proto/fleet_pb2_grpc.py +66 -0
  37. flwr/proto/fleet_pb2_grpc.pyi +20 -0
  38. flwr/server/app.py +54 -33
  39. flwr/server/run_serverapp.py +8 -9
  40. flwr/server/serverapp/app.py +17 -2
  41. flwr/server/superlink/driver/serverappio_grpc.py +1 -1
  42. flwr/server/superlink/driver/serverappio_servicer.py +29 -6
  43. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
  44. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +16 -0
  45. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -1
  46. flwr/server/superlink/fleet/vce/vce_api.py +2 -2
  47. flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -24
  48. flwr/server/superlink/linkstate/linkstate.py +14 -4
  49. flwr/server/superlink/linkstate/sqlite_linkstate.py +56 -31
  50. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  51. flwr/server/superlink/simulation/simulationio_servicer.py +13 -0
  52. flwr/simulation/app.py +15 -4
  53. flwr/simulation/run_simulation.py +35 -7
  54. flwr/superexec/exec_grpc.py +1 -1
  55. flwr/superexec/exec_servicer.py +23 -2
  56. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/METADATA +5 -5
  57. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/RECORD +60 -60
  58. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/LICENSE +0 -0
  59. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/WHEEL +0 -0
  60. {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250112.dist-info}/entry_points.txt +0 -0
@@ -72,14 +72,14 @@ CREATE TABLE IF NOT EXISTS node(
72
72
 
73
73
  SQL_CREATE_TABLE_CREDENTIAL = """
74
74
  CREATE TABLE IF NOT EXISTS credential(
75
- private_key BLOB PRIMARY KEY,
76
- public_key BLOB
75
+ private_key BLOB PRIMARY KEY,
76
+ public_key BLOB
77
77
  );
78
78
  """
79
79
 
80
80
  SQL_CREATE_TABLE_PUBLIC_KEY = """
81
81
  CREATE TABLE IF NOT EXISTS public_key(
82
- public_key BLOB UNIQUE
82
+ public_key BLOB PRIMARY KEY
83
83
  );
84
84
  """
85
85
 
@@ -635,9 +635,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
635
635
 
636
636
  return {UUID(row["task_id"]) for row in rows}
637
637
 
638
- def create_node(
639
- self, ping_interval: float, public_key: Optional[bytes] = None
640
- ) -> int:
638
+ def create_node(self, ping_interval: float) -> int:
641
639
  """Create, store in the link state, and return `node_id`."""
642
640
  # Sample a random uint64 as node_id
643
641
  uint64_node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
@@ -645,13 +643,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
645
643
  # Convert the uint64 value to sint64 for SQLite
646
644
  sint64_node_id = convert_uint64_to_sint64(uint64_node_id)
647
645
 
648
- query = "SELECT node_id FROM node WHERE public_key = :public_key;"
649
- row = self.query(query, {"public_key": public_key})
650
-
651
- if len(row) > 0:
652
- log(ERROR, "Unexpected node registration failure.")
653
- return 0
654
-
655
646
  query = (
656
647
  "INSERT INTO node "
657
648
  "(node_id, online_until, ping_interval, public_key) "
@@ -665,7 +656,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
665
656
  sint64_node_id,
666
657
  time.time() + ping_interval,
667
658
  ping_interval,
668
- public_key,
659
+ b"", # Initialize with an empty public key
669
660
  ),
670
661
  )
671
662
  except sqlite3.IntegrityError:
@@ -675,7 +666,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
675
666
  # Note: we need to return the uint64 value of the node_id
676
667
  return uint64_node_id
677
668
 
678
- def delete_node(self, node_id: int, public_key: Optional[bytes] = None) -> None:
669
+ def delete_node(self, node_id: int) -> None:
679
670
  """Delete a node."""
680
671
  # Convert the uint64 value to sint64 for SQLite
681
672
  sint64_node_id = convert_uint64_to_sint64(node_id)
@@ -683,10 +674,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
683
674
  query = "DELETE FROM node WHERE node_id = ?"
684
675
  params = (sint64_node_id,)
685
676
 
686
- if public_key is not None:
687
- query += " AND public_key = ?"
688
- params += (public_key,) # type: ignore
689
-
690
677
  if self.conn is None:
691
678
  raise AttributeError("LinkState is not initialized.")
692
679
 
@@ -694,7 +681,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
694
681
  with self.conn:
695
682
  rows = self.conn.execute(query, params)
696
683
  if rows.rowcount < 1:
697
- raise ValueError("Public key or node_id not found")
684
+ raise ValueError(f"Node {node_id} not found")
698
685
  except KeyError as exc:
699
686
  log(ERROR, {"query": query, "data": params, "exception": exc})
700
687
 
@@ -722,6 +709,41 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
722
709
  result: set[int] = {convert_sint64_to_uint64(row["node_id"]) for row in rows}
723
710
  return result
724
711
 
712
+ def set_node_public_key(self, node_id: int, public_key: bytes) -> None:
713
+ """Set `public_key` for the specified `node_id`."""
714
+ # Convert the uint64 value to sint64 for SQLite
715
+ sint64_node_id = convert_uint64_to_sint64(node_id)
716
+
717
+ # Check if the node exists in the `node` table
718
+ query = "SELECT 1 FROM node WHERE node_id = ?"
719
+ if not self.query(query, (sint64_node_id,)):
720
+ raise ValueError(f"Node {node_id} not found")
721
+
722
+ # Check if the public key is already in use in the `node` table
723
+ query = "SELECT 1 FROM node WHERE public_key = ?"
724
+ if self.query(query, (public_key,)):
725
+ raise ValueError("Public key already in use")
726
+
727
+ # Update the `node` table to set the public key for the given node ID
728
+ query = "UPDATE node SET public_key = ? WHERE node_id = ?"
729
+ self.query(query, (public_key, sint64_node_id))
730
+
731
+ def get_node_public_key(self, node_id: int) -> Optional[bytes]:
732
+ """Get `public_key` for the specified `node_id`."""
733
+ # Convert the uint64 value to sint64 for SQLite
734
+ sint64_node_id = convert_uint64_to_sint64(node_id)
735
+
736
+ # Query the public key for the given node_id
737
+ query = "SELECT public_key FROM node WHERE node_id = ?"
738
+ rows = self.query(query, (sint64_node_id,))
739
+
740
+ # If no result is found, return None
741
+ if not rows:
742
+ raise ValueError(f"Node {node_id} not found")
743
+
744
+ # Return the public key if it is not empty, otherwise return None
745
+ return rows[0]["public_key"] or None
746
+
725
747
  def get_node_id(self, node_public_key: bytes) -> Optional[int]:
726
748
  """Retrieve stored `node_id` filtered by `node_public_keys`."""
727
749
  query = "SELECT node_id FROM node WHERE public_key = :public_key;"
@@ -761,8 +783,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
761
783
  "federation_options, pending_at, starting_at, running_at, finished_at, "
762
784
  "sub_status, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
763
785
  )
764
- if fab_hash:
765
- fab_id, fab_version = "", ""
766
786
  override_config_json = json.dumps(override_config)
767
787
  data = [
768
788
  sint64_run_id,
@@ -820,6 +840,12 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
820
840
  public_key = None
821
841
  return public_key
822
842
 
843
+ def clear_supernode_auth_keys_and_credentials(self) -> None:
844
+ """Clear stored `node_public_keys` and credentials in the link state if any."""
845
+ queries = ["DELETE FROM public_key;", "DELETE FROM credential;"]
846
+ for query in queries:
847
+ self.query(query)
848
+
823
849
  def store_node_public_keys(self, public_keys: set[bytes]) -> None:
824
850
  """Store a set of `node_public_keys` in the link state."""
825
851
  query = "INSERT INTO public_key (public_key) VALUES (?)"
@@ -978,17 +1004,16 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
978
1004
  """Acknowledge a ping received from a node, serving as a heartbeat."""
979
1005
  sint64_node_id = convert_uint64_to_sint64(node_id)
980
1006
 
981
- # Update `online_until` and `ping_interval` for the given `node_id`
982
- query = "UPDATE node SET online_until = ?, ping_interval = ? WHERE node_id = ?;"
983
- try:
984
- self.query(
985
- query, (time.time() + ping_interval, ping_interval, sint64_node_id)
986
- )
987
- return True
988
- except sqlite3.IntegrityError:
989
- log(ERROR, "`node_id` does not exist.")
1007
+ # Check if the node exists in the `node` table
1008
+ query = "SELECT 1 FROM node WHERE node_id = ?"
1009
+ if not self.query(query, (sint64_node_id,)):
990
1010
  return False
991
1011
 
1012
+ # Update `online_until` and `ping_interval` for the given `node_id`
1013
+ query = "UPDATE node SET online_until = ?, ping_interval = ? WHERE node_id = ?"
1014
+ self.query(query, (time.time() + ping_interval, ping_interval, sint64_node_id))
1015
+ return True
1016
+
992
1017
  def get_serverapp_context(self, run_id: int) -> Optional[Context]:
993
1018
  """Get the context for the specified `run_id`."""
994
1019
  # Retrieve context if any
@@ -21,6 +21,7 @@ from typing import Optional
21
21
  import grpc
22
22
 
23
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
+ from flwr.common.grpc import generic_create_grpc_server
24
25
  from flwr.common.logger import log
25
26
  from flwr.proto.simulationio_pb2_grpc import ( # pylint: disable=E0611
26
27
  add_SimulationIoServicer_to_server,
@@ -28,7 +29,6 @@ from flwr.proto.simulationio_pb2_grpc import ( # pylint: disable=E0611
28
29
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
29
30
  from flwr.server.superlink.linkstate import LinkStateFactory
30
31
 
31
- from ..fleet.grpc_bidi.grpc_server import generic_create_grpc_server
32
32
  from .simulationio_servicer import SimulationIoServicer
33
33
 
34
34
 
@@ -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
@@ -24,7 +24,8 @@ from typing import Optional
24
24
 
25
25
  from flwr.cli.config_utils import get_fab_metadata
26
26
  from flwr.cli.install import install_from_fab
27
- from flwr.common import EventType
27
+ from flwr.cli.utils import get_sha256_hash
28
+ from flwr.common import EventType, event
28
29
  from flwr.common.args import add_args_flwr_app_common
29
30
  from flwr.common.config import (
30
31
  get_flwr_dir,
@@ -48,6 +49,7 @@ from flwr.common.logger import (
48
49
  from flwr.common.serde import (
49
50
  configs_record_from_proto,
50
51
  context_from_proto,
52
+ context_to_proto,
51
53
  fab_from_proto,
52
54
  run_from_proto,
53
55
  run_status_to_proto,
@@ -201,8 +203,17 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
201
203
  verbose: bool = fed_opt.get("verbose", False)
202
204
  enable_tf_gpu_growth: bool = fed_opt.get("enable_tf_gpu_growth", False)
203
205
 
206
+ event(
207
+ EventType.FLWR_SIMULATION_RUN_ENTER,
208
+ event_details={
209
+ "backend": "ray",
210
+ "num-supernodes": num_supernodes,
211
+ "run-id-hash": get_sha256_hash(run.run_id),
212
+ },
213
+ )
214
+
204
215
  # Launch the simulation
205
- _run_simulation(
216
+ updated_context = _run_simulation(
206
217
  server_app_attr=server_app_attr,
207
218
  client_app_attr=client_app_attr,
208
219
  num_supernodes=num_supernodes,
@@ -213,11 +224,11 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
213
224
  verbose_logging=verbose,
214
225
  server_app_run_config=fused_config,
215
226
  is_app=True,
216
- exit_event=EventType.CLI_FLOWER_SIMULATION_LEAVE,
227
+ exit_event=EventType.FLWR_SIMULATION_RUN_LEAVE,
217
228
  )
218
229
 
219
230
  # Send resulting context
220
- context_proto = None # context_to_proto(updated_context)
231
+ context_proto = context_to_proto(updated_context)
221
232
  out_req = PushSimulationOutputsRequest(
222
233
  run_id=run.run_id, context=context_proto
223
234
  )
@@ -24,9 +24,11 @@ import threading
24
24
  import traceback
25
25
  from logging import DEBUG, ERROR, INFO, WARNING
26
26
  from pathlib import Path
27
+ from queue import Empty, Queue
27
28
  from typing import Any, Optional
28
29
 
29
30
  from flwr.cli.config_utils import load_and_validate
31
+ from flwr.cli.utils import get_sha256_hash
30
32
  from flwr.client import ClientApp
31
33
  from flwr.common import Context, EventType, RecordSet, event, log, now
32
34
  from flwr.common.config import get_fused_config_from_dir, parse_config_args
@@ -126,7 +128,7 @@ def run_simulation_from_cli() -> None:
126
128
  run = Run.create_empty(run_id)
127
129
  run.override_config = override_config
128
130
 
129
- _run_simulation(
131
+ _ = _run_simulation(
130
132
  server_app_attr=server_app_attr,
131
133
  client_app_attr=client_app_attr,
132
134
  num_supernodes=args.num_supernodes,
@@ -206,7 +208,7 @@ def run_simulation(
206
208
  "\n\tflwr.simulation.run_simulationt(...)",
207
209
  )
208
210
 
209
- _run_simulation(
211
+ _ = _run_simulation(
210
212
  num_supernodes=num_supernodes,
211
213
  client_app=client_app,
212
214
  server_app=server_app,
@@ -229,6 +231,7 @@ def run_serverapp_th(
229
231
  has_exception: threading.Event,
230
232
  enable_tf_gpu_growth: bool,
231
233
  run_id: int,
234
+ ctx_queue: "Queue[Context]",
232
235
  ) -> threading.Thread:
233
236
  """Run SeverApp in a thread."""
234
237
 
@@ -241,6 +244,7 @@ def run_serverapp_th(
241
244
  _server_app_run_config: UserConfig,
242
245
  _server_app_attr: Optional[str],
243
246
  _server_app: Optional[ServerApp],
247
+ _ctx_queue: "Queue[Context]",
244
248
  ) -> None:
245
249
  """Run SeverApp, after check if GPU memory growth has to be set.
246
250
 
@@ -261,13 +265,14 @@ def run_serverapp_th(
261
265
  )
262
266
 
263
267
  # Run ServerApp
264
- _run(
268
+ updated_context = _run(
265
269
  driver=_driver,
266
270
  context=context,
267
271
  server_app_dir=_server_app_dir,
268
272
  server_app_attr=_server_app_attr,
269
273
  loaded_server_app=_server_app,
270
274
  )
275
+ _ctx_queue.put(updated_context)
271
276
  except Exception as ex: # pylint: disable=broad-exception-caught
272
277
  log(ERROR, "ServerApp thread raised an exception: %s", ex)
273
278
  log(ERROR, traceback.format_exc())
@@ -291,6 +296,7 @@ def run_serverapp_th(
291
296
  server_app_run_config,
292
297
  server_app_attr,
293
298
  server_app,
299
+ ctx_queue,
294
300
  ),
295
301
  )
296
302
  serverapp_th.start()
@@ -313,7 +319,7 @@ def _main_loop(
313
319
  server_app: Optional[ServerApp] = None,
314
320
  server_app_attr: Optional[str] = None,
315
321
  server_app_run_config: Optional[UserConfig] = None,
316
- ) -> None:
322
+ ) -> Context:
317
323
  """Start ServerApp on a separate thread, then launch Simulation Engine."""
318
324
  # Initialize StateFactory
319
325
  state_factory = LinkStateFactory(":flwr-in-memory-state:")
@@ -323,6 +329,13 @@ def _main_loop(
323
329
  server_app_thread_has_exception = threading.Event()
324
330
  serverapp_th = None
325
331
  success = True
332
+ updated_context = Context(
333
+ run_id=run.run_id,
334
+ node_id=0,
335
+ node_config=UserConfig(),
336
+ state=RecordSet(),
337
+ run_config=UserConfig(),
338
+ )
326
339
  try:
327
340
  # Register run
328
341
  log(DEBUG, "Pre-registering run with id %s", run.run_id)
@@ -337,6 +350,7 @@ def _main_loop(
337
350
  # Initialize Driver
338
351
  driver = InMemoryDriver(state_factory=state_factory)
339
352
  driver.set_run(run_id=run.run_id)
353
+ output_context_queue: Queue[Context] = Queue()
340
354
 
341
355
  # Get and run ServerApp thread
342
356
  serverapp_th = run_serverapp_th(
@@ -349,6 +363,7 @@ def _main_loop(
349
363
  has_exception=server_app_thread_has_exception,
350
364
  enable_tf_gpu_growth=enable_tf_gpu_growth,
351
365
  run_id=run.run_id,
366
+ ctx_queue=output_context_queue,
352
367
  )
353
368
 
354
369
  # Start Simulation Engine
@@ -366,6 +381,11 @@ def _main_loop(
366
381
  flwr_dir=flwr_dir,
367
382
  )
368
383
 
384
+ updated_context = output_context_queue.get(timeout=3)
385
+
386
+ except Empty:
387
+ log(DEBUG, "Queue timeout. No context received.")
388
+
369
389
  except Exception as ex:
370
390
  log(ERROR, "An exception occurred !! %s", ex)
371
391
  log(ERROR, traceback.format_exc())
@@ -375,13 +395,20 @@ def _main_loop(
375
395
  finally:
376
396
  # Trigger stop event
377
397
  f_stop.set()
378
- event(exit_event, event_details={"success": success})
398
+ event(
399
+ exit_event,
400
+ event_details={
401
+ "run-id-hash": get_sha256_hash(run.run_id),
402
+ "success": success,
403
+ },
404
+ )
379
405
  if serverapp_th:
380
406
  serverapp_th.join()
381
407
  if server_app_thread_has_exception.is_set():
382
408
  raise RuntimeError("Exception in ServerApp thread")
383
409
 
384
410
  log(DEBUG, "Stopping Simulation Engine now.")
411
+ return updated_context
385
412
 
386
413
 
387
414
  # pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
@@ -401,7 +428,7 @@ def _run_simulation(
401
428
  enable_tf_gpu_growth: bool = False,
402
429
  verbose_logging: bool = False,
403
430
  is_app: bool = False,
404
- ) -> None:
431
+ ) -> Context:
405
432
  """Launch the Simulation Engine."""
406
433
  if backend_config is None:
407
434
  backend_config = {}
@@ -480,7 +507,8 @@ def _run_simulation(
480
507
  # Set logger propagation to False to prevent duplicated log output in Colab.
481
508
  logger = set_logger_propagation(logger, False)
482
509
 
483
- _main_loop(*args)
510
+ updated_context = _main_loop(*args)
511
+ return updated_context
484
512
 
485
513
 
486
514
  def _parse_args_run_simulation() -> argparse.ArgumentParser:
@@ -23,11 +23,11 @@ import grpc
23
23
 
24
24
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
25
25
  from flwr.common.auth_plugin import ExecAuthPlugin
26
+ from flwr.common.grpc import generic_create_grpc_server
26
27
  from flwr.common.logger import log
27
28
  from flwr.common.typing import UserConfig
28
29
  from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
29
30
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
30
- from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
31
31
  from flwr.server.superlink.linkstate import LinkStateFactory
32
32
  from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
33
33
 
@@ -181,8 +181,20 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
181
181
  "ExecServicer initialized without user authentication",
182
182
  )
183
183
  raise grpc.RpcError() # This line is unreachable
184
+
185
+ # Get login details
186
+ details = self.auth_plugin.get_login_details()
187
+
188
+ # Return empty response if details is None
189
+ if details is None:
190
+ return GetLoginDetailsResponse()
191
+
184
192
  return GetLoginDetailsResponse(
185
- login_details=self.auth_plugin.get_login_details()
193
+ auth_type=details.auth_type,
194
+ device_code=details.device_code,
195
+ verification_uri_complete=details.verification_uri_complete,
196
+ expires_in=details.expires_in,
197
+ interval=details.interval,
186
198
  )
187
199
 
188
200
  def GetAuthTokens(
@@ -196,8 +208,17 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
196
208
  "ExecServicer initialized without user authentication",
197
209
  )
198
210
  raise grpc.RpcError() # This line is unreachable
211
+
212
+ # Get auth tokens
213
+ credentials = self.auth_plugin.get_auth_tokens(request.device_code)
214
+
215
+ # Return empty response if credentials is None
216
+ if credentials is None:
217
+ return GetAuthTokensResponse()
218
+
199
219
  return GetAuthTokensResponse(
200
- auth_tokens=self.auth_plugin.get_auth_tokens(dict(request.auth_details))
220
+ access_token=credentials.access_token,
221
+ refresh_token=credentials.refresh_token,
201
222
  )
202
223
 
203
224
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.14.0.dev20241216
3
+ Version: 1.15.0.dev20250112
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -43,11 +43,11 @@ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
43
43
  Requires-Dist: ray (==2.10.0) ; (python_version >= "3.9" and python_version < "3.12") and (extra == "simulation")
44
44
  Requires-Dist: requests (>=2.31.0,<3.0.0)
45
45
  Requires-Dist: rich (>=13.5.0,<14.0.0)
46
- Requires-Dist: starlette (>=0.31.0,<0.32.0) ; extra == "rest"
46
+ Requires-Dist: starlette (>=0.45.2,<0.46.0) ; extra == "rest"
47
47
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
48
48
  Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
49
49
  Requires-Dist: typer (>=0.12.5,<0.13.0)
50
- Requires-Dist: uvicorn[standard] (>=0.23.0,<0.24.0) ; extra == "rest"
50
+ Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
51
51
  Project-URL: Documentation, https://flower.ai
52
52
  Project-URL: Repository, https://github.com/adap/flower
53
53
  Description-Content-Type: text/markdown
@@ -88,7 +88,7 @@ design of Flower is based on a few guiding principles:
88
88
 
89
89
  - **Framework-agnostic**: Different machine learning frameworks have different
90
90
  strengths. Flower can be used with any machine learning framework, for
91
- example, [PyTorch](https://pytorch.org), [TensorFlow](https://tensorflow.org), [Hugging Face Transformers](https://huggingface.co/), [PyTorch Lightning](https://pytorchlightning.ai/), [scikit-learn](https://scikit-learn.org/), [JAX](https://jax.readthedocs.io/), [TFLite](https://tensorflow.org/lite/), [MONAI](https://docs.monai.io/en/latest/index.html), [fastai](https://www.fast.ai/), [MLX](https://ml-explore.github.io/mlx/build/html/index.html), [XGBoost](https://xgboost.readthedocs.io/en/stable/), [Pandas](https://pandas.pydata.org/) for federated analytics, or even raw [NumPy](https://numpy.org/)
91
+ example, [PyTorch](https://pytorch.org), [TensorFlow](https://tensorflow.org), [Hugging Face Transformers](https://huggingface.co/), [PyTorch Lightning](https://pytorchlightning.ai/), [scikit-learn](https://scikit-learn.org/), [JAX](https://jax.readthedocs.io/), [TFLite](https://tensorflow.org/lite/), [MONAI](https://docs.monai.io/en/latest/index.html), [fastai](https://www.fast.ai/), [MLX](https://ml-explore.github.io/mlx/build/html/index.html), [XGBoost](https://xgboost.readthedocs.io/en/stable/), [LeRobot](https://github.com/huggingface/lerobot) for federated robots, [Pandas](https://pandas.pydata.org/) for federated analytics, or even raw [NumPy](https://numpy.org/)
92
92
  for users who enjoy computing gradients by hand.
93
93
 
94
94
  - **Understandable**: Flower is written with maintainability in mind. The
@@ -118,7 +118,7 @@ Flower's goal is to make federated learning accessible to everyone. This series
118
118
 
119
119
  4. **Custom Clients for Federated Learning**
120
120
 
121
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-customize-the-client-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb))
121
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb))
122
122
 
123
123
  Stay tuned, more tutorials are coming soon. Topics include **Privacy and Security in Federated Learning**, and **Scaling Federated Learning**.
124
124