flwr 1.17.0__py3-none-any.whl → 1.19.0__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 (286) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +15 -0
  3. flwr/app/error.py +68 -0
  4. flwr/app/metadata.py +223 -0
  5. flwr/cli/__init__.py +1 -1
  6. flwr/cli/app.py +21 -2
  7. flwr/cli/build.py +83 -58
  8. flwr/cli/cli_user_auth_interceptor.py +1 -1
  9. flwr/cli/config_utils.py +53 -17
  10. flwr/cli/example.py +1 -1
  11. flwr/cli/install.py +1 -1
  12. flwr/cli/log.py +4 -4
  13. flwr/cli/login/__init__.py +1 -1
  14. flwr/cli/login/login.py +15 -8
  15. flwr/cli/ls.py +16 -37
  16. flwr/cli/new/__init__.py +1 -1
  17. flwr/cli/new/new.py +4 -4
  18. flwr/cli/new/templates/__init__.py +1 -1
  19. flwr/cli/new/templates/app/__init__.py +1 -1
  20. flwr/cli/new/templates/app/code/__init__.py +1 -1
  21. flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
  22. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
  23. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +4 -4
  24. flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
  25. flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
  26. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -17
  28. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
  29. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  30. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  31. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  33. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  34. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  35. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  36. flwr/cli/run/__init__.py +1 -1
  37. flwr/cli/run/run.py +11 -19
  38. flwr/cli/stop.py +3 -3
  39. flwr/cli/utils.py +42 -17
  40. flwr/client/__init__.py +3 -3
  41. flwr/client/client.py +1 -1
  42. flwr/client/client_app.py +140 -138
  43. flwr/client/clientapp/__init__.py +1 -8
  44. flwr/client/clientapp/utils.py +1 -1
  45. flwr/client/dpfedavg_numpy_client.py +1 -1
  46. flwr/client/grpc_adapter_client/__init__.py +1 -1
  47. flwr/client/grpc_adapter_client/connection.py +5 -5
  48. flwr/client/grpc_rere_client/__init__.py +1 -1
  49. flwr/client/grpc_rere_client/client_interceptor.py +1 -1
  50. flwr/client/grpc_rere_client/connection.py +131 -61
  51. flwr/client/grpc_rere_client/grpc_adapter.py +35 -7
  52. flwr/client/message_handler/__init__.py +1 -1
  53. flwr/client/message_handler/message_handler.py +2 -2
  54. flwr/client/mod/__init__.py +1 -1
  55. flwr/client/mod/centraldp_mods.py +1 -1
  56. flwr/client/mod/comms_mods.py +39 -20
  57. flwr/client/mod/localdp_mod.py +6 -6
  58. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  59. flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
  60. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  61. flwr/client/mod/utils.py +1 -1
  62. flwr/client/numpy_client.py +1 -1
  63. flwr/client/rest_client/__init__.py +1 -1
  64. flwr/client/rest_client/connection.py +174 -68
  65. flwr/client/run_info_store.py +1 -1
  66. flwr/client/typing.py +1 -1
  67. flwr/clientapp/__init__.py +15 -0
  68. flwr/common/__init__.py +3 -3
  69. flwr/common/address.py +1 -1
  70. flwr/common/args.py +1 -1
  71. flwr/common/auth_plugin/__init__.py +3 -1
  72. flwr/common/auth_plugin/auth_plugin.py +30 -4
  73. flwr/common/config.py +1 -1
  74. flwr/common/constant.py +37 -8
  75. flwr/common/context.py +1 -1
  76. flwr/common/date.py +1 -1
  77. flwr/common/differential_privacy.py +1 -1
  78. flwr/common/differential_privacy_constants.py +1 -1
  79. flwr/common/dp.py +1 -1
  80. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  81. flwr/common/exit/exit.py +6 -6
  82. flwr/common/exit_handlers.py +31 -1
  83. flwr/common/grpc.py +1 -1
  84. flwr/common/heartbeat.py +165 -0
  85. flwr/common/inflatable.py +290 -0
  86. flwr/common/inflatable_grpc_utils.py +99 -0
  87. flwr/common/inflatable_rest_utils.py +99 -0
  88. flwr/common/inflatable_utils.py +341 -0
  89. flwr/common/logger.py +1 -1
  90. flwr/common/message.py +137 -252
  91. flwr/common/object_ref.py +1 -1
  92. flwr/common/parameter.py +1 -1
  93. flwr/common/pyproject.py +1 -1
  94. flwr/common/record/__init__.py +3 -2
  95. flwr/common/record/array.py +323 -0
  96. flwr/common/record/arrayrecord.py +121 -243
  97. flwr/common/record/configrecord.py +71 -16
  98. flwr/common/record/conversion_utils.py +2 -2
  99. flwr/common/record/metricrecord.py +71 -20
  100. flwr/common/record/recorddict.py +207 -90
  101. flwr/common/record/typeddict.py +1 -1
  102. flwr/common/recorddict_compat.py +2 -2
  103. flwr/common/retry_invoker.py +15 -11
  104. flwr/common/secure_aggregation/__init__.py +1 -1
  105. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  106. flwr/common/secure_aggregation/crypto/shamir.py +52 -30
  107. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
  108. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  109. flwr/common/secure_aggregation/quantization.py +1 -1
  110. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  111. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  112. flwr/common/serde.py +60 -184
  113. flwr/common/serde_utils.py +175 -0
  114. flwr/common/telemetry.py +2 -2
  115. flwr/common/typing.py +6 -4
  116. flwr/common/version.py +1 -1
  117. flwr/compat/__init__.py +15 -0
  118. flwr/compat/client/__init__.py +15 -0
  119. flwr/{client → compat/client}/app.py +71 -211
  120. flwr/{client → compat/client}/grpc_client/__init__.py +1 -1
  121. flwr/{client → compat/client}/grpc_client/connection.py +13 -13
  122. flwr/compat/common/__init__.py +15 -0
  123. flwr/compat/server/__init__.py +15 -0
  124. flwr/compat/server/app.py +174 -0
  125. flwr/compat/simulation/__init__.py +15 -0
  126. flwr/proto/__init__.py +1 -1
  127. flwr/proto/fleet_pb2.py +32 -27
  128. flwr/proto/fleet_pb2.pyi +49 -35
  129. flwr/proto/fleet_pb2_grpc.py +117 -13
  130. flwr/proto/fleet_pb2_grpc.pyi +47 -6
  131. flwr/proto/heartbeat_pb2.py +33 -0
  132. flwr/proto/heartbeat_pb2.pyi +66 -0
  133. flwr/proto/heartbeat_pb2_grpc.py +4 -0
  134. flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
  135. flwr/proto/message_pb2.py +28 -11
  136. flwr/proto/message_pb2.pyi +125 -0
  137. flwr/proto/recorddict_pb2.py +16 -28
  138. flwr/proto/recorddict_pb2.pyi +46 -64
  139. flwr/proto/run_pb2.py +24 -32
  140. flwr/proto/run_pb2.pyi +4 -52
  141. flwr/proto/serverappio_pb2.py +32 -23
  142. flwr/proto/serverappio_pb2.pyi +45 -3
  143. flwr/proto/serverappio_pb2_grpc.py +138 -34
  144. flwr/proto/serverappio_pb2_grpc.pyi +54 -13
  145. flwr/proto/simulationio_pb2.py +12 -11
  146. flwr/proto/simulationio_pb2_grpc.py +35 -0
  147. flwr/proto/simulationio_pb2_grpc.pyi +14 -0
  148. flwr/server/__init__.py +2 -2
  149. flwr/server/app.py +69 -187
  150. flwr/server/client_manager.py +1 -1
  151. flwr/server/client_proxy.py +1 -1
  152. flwr/server/compat/__init__.py +1 -1
  153. flwr/server/compat/app.py +1 -1
  154. flwr/server/compat/app_utils.py +51 -29
  155. flwr/server/compat/legacy_context.py +1 -1
  156. flwr/server/criterion.py +1 -1
  157. flwr/server/fleet_event_log_interceptor.py +2 -2
  158. flwr/server/grid/grid.py +3 -3
  159. flwr/server/grid/grpc_grid.py +104 -34
  160. flwr/server/grid/inmemory_grid.py +5 -4
  161. flwr/server/history.py +1 -1
  162. flwr/server/run_serverapp.py +1 -1
  163. flwr/server/server.py +1 -1
  164. flwr/server/server_app.py +65 -58
  165. flwr/server/server_config.py +1 -1
  166. flwr/server/serverapp/__init__.py +1 -1
  167. flwr/server/serverapp/app.py +19 -1
  168. flwr/server/serverapp_components.py +1 -1
  169. flwr/server/strategy/__init__.py +1 -1
  170. flwr/server/strategy/aggregate.py +1 -1
  171. flwr/server/strategy/bulyan.py +2 -2
  172. flwr/server/strategy/dp_adaptive_clipping.py +17 -17
  173. flwr/server/strategy/dp_fixed_clipping.py +17 -17
  174. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  175. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  176. flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
  177. flwr/server/strategy/fedadagrad.py +1 -1
  178. flwr/server/strategy/fedadam.py +1 -1
  179. flwr/server/strategy/fedavg.py +1 -1
  180. flwr/server/strategy/fedavg_android.py +1 -1
  181. flwr/server/strategy/fedavgm.py +1 -1
  182. flwr/server/strategy/fedmedian.py +1 -1
  183. flwr/server/strategy/fedopt.py +1 -1
  184. flwr/server/strategy/fedprox.py +1 -1
  185. flwr/server/strategy/fedtrimmedavg.py +1 -1
  186. flwr/server/strategy/fedxgb_bagging.py +1 -1
  187. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  188. flwr/server/strategy/fedxgb_nn_avg.py +3 -2
  189. flwr/server/strategy/fedyogi.py +1 -1
  190. flwr/server/strategy/krum.py +1 -1
  191. flwr/server/strategy/qfedavg.py +1 -1
  192. flwr/server/strategy/strategy.py +1 -1
  193. flwr/server/superlink/__init__.py +1 -1
  194. flwr/server/superlink/ffs/__init__.py +3 -1
  195. flwr/server/superlink/ffs/disk_ffs.py +1 -1
  196. flwr/server/superlink/ffs/ffs.py +1 -1
  197. flwr/server/superlink/ffs/ffs_factory.py +1 -1
  198. flwr/server/superlink/fleet/__init__.py +1 -1
  199. flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
  200. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +14 -4
  201. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  202. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  203. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  204. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
  206. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  207. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
  208. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
  209. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  210. flwr/server/superlink/fleet/message_handler/message_handler.py +136 -19
  211. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  212. flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -12
  213. flwr/server/superlink/fleet/vce/__init__.py +1 -1
  214. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
  215. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  216. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
  217. flwr/server/superlink/fleet/vce/vce_api.py +7 -4
  218. flwr/server/superlink/linkstate/__init__.py +1 -1
  219. flwr/server/superlink/linkstate/in_memory_linkstate.py +139 -44
  220. flwr/server/superlink/linkstate/linkstate.py +54 -21
  221. flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
  222. flwr/server/superlink/linkstate/sqlite_linkstate.py +150 -56
  223. flwr/server/superlink/linkstate/utils.py +34 -30
  224. flwr/server/superlink/serverappio/serverappio_grpc.py +3 -0
  225. flwr/server/superlink/serverappio/serverappio_servicer.py +211 -57
  226. flwr/server/superlink/simulation/__init__.py +1 -1
  227. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  228. flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
  229. flwr/server/superlink/utils.py +45 -3
  230. flwr/server/typing.py +1 -1
  231. flwr/server/utils/__init__.py +1 -1
  232. flwr/server/utils/tensorboard.py +1 -1
  233. flwr/server/utils/validator.py +3 -3
  234. flwr/server/workflow/__init__.py +1 -1
  235. flwr/server/workflow/constant.py +1 -1
  236. flwr/server/workflow/default_workflows.py +1 -1
  237. flwr/server/workflow/secure_aggregation/__init__.py +1 -1
  238. flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
  239. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  240. flwr/serverapp/__init__.py +15 -0
  241. flwr/simulation/__init__.py +1 -1
  242. flwr/simulation/app.py +18 -1
  243. flwr/simulation/legacy_app.py +1 -1
  244. flwr/simulation/ray_transport/__init__.py +1 -1
  245. flwr/simulation/ray_transport/ray_actor.py +1 -1
  246. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  247. flwr/simulation/ray_transport/utils.py +1 -1
  248. flwr/simulation/run_simulation.py +2 -2
  249. flwr/simulation/simulationio_connection.py +1 -1
  250. flwr/supercore/__init__.py +15 -0
  251. flwr/supercore/object_store/__init__.py +24 -0
  252. flwr/supercore/object_store/in_memory_object_store.py +229 -0
  253. flwr/supercore/object_store/object_store.py +192 -0
  254. flwr/supercore/object_store/object_store_factory.py +44 -0
  255. flwr/superexec/__init__.py +1 -1
  256. flwr/superexec/app.py +1 -1
  257. flwr/superexec/deployment.py +7 -3
  258. flwr/superexec/exec_event_log_interceptor.py +4 -4
  259. flwr/superexec/exec_grpc.py +8 -4
  260. flwr/superexec/exec_servicer.py +126 -24
  261. flwr/superexec/exec_user_auth_interceptor.py +38 -9
  262. flwr/superexec/executor.py +5 -1
  263. flwr/superexec/simulation.py +8 -2
  264. flwr/superlink/__init__.py +15 -0
  265. flwr/{client/supernode → supernode}/__init__.py +1 -8
  266. flwr/{client/nodestate/nodestate.py → supernode/cli/__init__.py} +8 -15
  267. flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +4 -13
  268. flwr/supernode/cli/flwr_clientapp.py +81 -0
  269. flwr/{client → supernode}/nodestate/__init__.py +1 -1
  270. flwr/supernode/nodestate/in_memory_nodestate.py +190 -0
  271. flwr/supernode/nodestate/nodestate.py +212 -0
  272. flwr/{client → supernode}/nodestate/nodestate_factory.py +1 -1
  273. flwr/supernode/runtime/__init__.py +15 -0
  274. flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +26 -57
  275. flwr/supernode/servicer/__init__.py +15 -0
  276. flwr/supernode/servicer/clientappio/__init__.py +24 -0
  277. flwr/{client/clientapp → supernode/servicer/clientappio}/clientappio_servicer.py +1 -1
  278. flwr/supernode/start_client_internal.py +491 -0
  279. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/METADATA +6 -5
  280. flwr-1.19.0.dist-info/RECORD +365 -0
  281. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/WHEEL +1 -1
  282. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/entry_points.txt +2 -2
  283. flwr/client/heartbeat.py +0 -74
  284. flwr/client/nodestate/in_memory_nodestate.py +0 -38
  285. flwr-1.17.0.dist-info/LICENSE +0 -202
  286. flwr-1.17.0.dist-info/RECORD +0 -333
@@ -28,11 +28,28 @@ from flwr.common.constant import (
28
28
  SUPERLINK_NODE_ID,
29
29
  )
30
30
  from flwr.common.grpc import create_channel, on_channel_state_change
31
+ from flwr.common.inflatable import (
32
+ get_all_nested_objects,
33
+ get_object_tree,
34
+ no_object_id_recompute,
35
+ )
36
+ from flwr.common.inflatable_grpc_utils import (
37
+ make_pull_object_fn_grpc,
38
+ make_push_object_fn_grpc,
39
+ )
40
+ from flwr.common.inflatable_utils import (
41
+ inflate_object_from_contents,
42
+ pull_objects,
43
+ push_objects,
44
+ )
31
45
  from flwr.common.logger import log, warn_deprecated_feature
46
+ from flwr.common.message import remove_content_from_message
32
47
  from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
33
- from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
48
+ from flwr.common.serde import message_to_proto, run_from_proto
34
49
  from flwr.common.typing import Run
35
- from flwr.proto.message_pb2 import Message as ProtoMessage # pylint: disable=E0611
50
+ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
51
+ ConfirmMessageReceivedRequest,
52
+ )
36
53
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
37
54
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
38
55
  from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
@@ -163,7 +180,7 @@ class GrpcGrid(Grid):
163
180
  def _check_message(self, message: Message) -> None:
164
181
  # Check if the message is valid
165
182
  if not (
166
- message.metadata.message_id == ""
183
+ message.metadata.message_id != ""
167
184
  and message.metadata.reply_to_message_id == ""
168
185
  and message.metadata.ttl > 0
169
186
  ):
@@ -198,6 +215,38 @@ class GrpcGrid(Grid):
198
215
  )
199
216
  return [node.node_id for node in res.nodes]
200
217
 
218
+ def _try_push_message(self, run_id: int, message: Message) -> str:
219
+ """Push one message and its associated objects."""
220
+ # Compute mapping of message descendants
221
+ all_objects = get_all_nested_objects(message)
222
+ msg_id = message.object_id
223
+ object_tree = get_object_tree(message)
224
+
225
+ # Call GrpcServerAppIoStub method
226
+ res: PushInsMessagesResponse = self._stub.PushMessages(
227
+ PushInsMessagesRequest(
228
+ messages_list=[message_to_proto(remove_content_from_message(message))],
229
+ run_id=run_id,
230
+ message_object_trees=[object_tree],
231
+ )
232
+ )
233
+
234
+ # Push objects
235
+ # If Message was added to the LinkState correctly
236
+ if msg_id is not None:
237
+ obj_ids_to_push = set(res.objects_to_push[msg_id].object_ids)
238
+ # Push only object that are not in the store
239
+ push_objects(
240
+ all_objects,
241
+ push_object_fn=make_push_object_fn_grpc(
242
+ push_object_grpc=self._stub.PushObject,
243
+ node=self.node,
244
+ run_id=run_id,
245
+ ),
246
+ object_ids_to_push=obj_ids_to_push,
247
+ )
248
+ return msg_id
249
+
201
250
  def push_messages(self, messages: Iterable[Message]) -> Iterable[str]:
202
251
  """Push messages to specified node IDs.
203
252
 
@@ -206,57 +255,78 @@ class GrpcGrid(Grid):
206
255
  """
207
256
  # Construct Messages
208
257
  run_id = cast(Run, self._run).run_id
209
- message_proto_list: list[ProtoMessage] = []
210
- for msg in messages:
211
- # Populate metadata
212
- msg.metadata.__dict__["_run_id"] = run_id
213
- msg.metadata.__dict__["_src_node_id"] = self.node.node_id
214
- # Check message
215
- self._check_message(msg)
216
- # Convert to proto
217
- msg_proto = message_to_proto(msg)
218
- # Add to list
219
- message_proto_list.append(msg_proto)
220
-
258
+ message_ids: list[str] = []
221
259
  try:
222
- # Call GrpcServerAppIoStub method
223
- res: PushInsMessagesResponse = self._stub.PushMessages(
224
- PushInsMessagesRequest(messages_list=message_proto_list, run_id=run_id)
225
- )
226
- if len([msg_id for msg_id in res.message_ids if msg_id]) != len(
227
- message_proto_list
228
- ):
229
- log(
230
- WARNING,
231
- "Not all messages could be pushed to the SuperLink. The returned "
232
- "list has `None` for those messages (the order is preserved as "
233
- "passed to `push_messages`). This could be due to a malformed "
234
- "message.",
235
- )
236
- return list(res.message_ids)
260
+ for msg in messages:
261
+ # Populate metadata
262
+ msg.metadata.__dict__["_run_id"] = run_id
263
+ msg.metadata.__dict__["_src_node_id"] = self.node.node_id
264
+ msg.metadata.__dict__["_message_id"] = msg.object_id
265
+ # Check message
266
+ self._check_message(msg)
267
+ # Try pushing message and its objects
268
+ with no_object_id_recompute():
269
+ message_ids.append(self._try_push_message(run_id, msg))
270
+
237
271
  except grpc.RpcError as e:
238
272
  if e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED: # pylint: disable=E1101
239
273
  log(ERROR, ERROR_MESSAGE_PUSH_MESSAGES_RESOURCE_EXHAUSTED)
240
274
  return []
241
275
  raise
242
276
 
277
+ if None in message_ids:
278
+ log(
279
+ WARNING,
280
+ "Not all messages could be pushed to the SuperLink. The returned "
281
+ "list has `None` for those messages (the order is preserved as "
282
+ "passed to `push_messages`). This could be due to a malformed "
283
+ "message.",
284
+ )
285
+
286
+ return message_ids
287
+
243
288
  def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]:
244
289
  """Pull messages based on message IDs.
245
290
 
246
291
  This method is used to collect messages from the SuperLink that correspond to a
247
292
  set of given message IDs.
248
293
  """
294
+ run_id = cast(Run, self._run).run_id
249
295
  try:
250
296
  # Pull Messages
251
297
  res: PullResMessagesResponse = self._stub.PullMessages(
252
298
  PullResMessagesRequest(
253
299
  message_ids=message_ids,
254
- run_id=cast(Run, self._run).run_id,
300
+ run_id=run_id,
255
301
  )
256
302
  )
257
- # Convert Message from Protobuf representation
258
- msgs = [message_from_proto(msg_proto) for msg_proto in res.messages_list]
259
- return msgs
303
+ # Pull Messages from store
304
+ inflated_msgs: list[Message] = []
305
+ for msg_proto in res.messages_list:
306
+ msg_id = msg_proto.metadata.message_id
307
+ all_object_contents = pull_objects(
308
+ list(res.objects_to_pull[msg_id].object_ids) + [msg_id],
309
+ pull_object_fn=make_pull_object_fn_grpc(
310
+ pull_object_grpc=self._stub.PullObject,
311
+ node=self.node,
312
+ run_id=run_id,
313
+ ),
314
+ )
315
+
316
+ # Confirm that the message has been received
317
+ self._stub.ConfirmMessageReceived(
318
+ ConfirmMessageReceivedRequest(
319
+ node=self.node, run_id=run_id, message_object_id=msg_id
320
+ )
321
+ )
322
+ message = cast(
323
+ Message, inflate_object_from_contents(msg_id, all_object_contents)
324
+ )
325
+ message.metadata.__dict__["_message_id"] = msg_id
326
+ inflated_msgs.append(message)
327
+
328
+ return inflated_msgs
329
+
260
330
  except grpc.RpcError as e:
261
331
  if e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED: # pylint: disable=E1101
262
332
  log(ERROR, ERROR_MESSAGE_PULL_MESSAGES_RESOURCE_EXHAUSTED)
@@ -18,7 +18,7 @@
18
18
  import time
19
19
  from collections.abc import Iterable
20
20
  from typing import Optional, cast
21
- from uuid import UUID
21
+ from uuid import uuid4
22
22
 
23
23
  from flwr.common import Message, RecordDict
24
24
  from flwr.common.constant import SUPERLINK_NODE_ID
@@ -56,7 +56,7 @@ class InMemoryGrid(Grid):
56
56
  def _check_message(self, message: Message) -> None:
57
57
  # Check if the message is valid
58
58
  if not (
59
- message.metadata.message_id == ""
59
+ message.metadata.message_id != ""
60
60
  and message.metadata.reply_to_message_id == ""
61
61
  and message.metadata.ttl > 0
62
62
  and message.metadata.delivered_at == ""
@@ -111,6 +111,7 @@ class InMemoryGrid(Grid):
111
111
  # Populate metadata
112
112
  msg.metadata.__dict__["_run_id"] = cast(Run, self._run).run_id
113
113
  msg.metadata.__dict__["_src_node_id"] = self.node.node_id
114
+ msg.metadata.__dict__["_message_id"] = str(uuid4())
114
115
  # Check message
115
116
  self._check_message(msg)
116
117
  # Store in state
@@ -126,12 +127,12 @@ class InMemoryGrid(Grid):
126
127
  This method is used to collect messages from the SuperLink that correspond to a
127
128
  set of given message IDs.
128
129
  """
129
- msg_ids = {UUID(msg_id) for msg_id in message_ids}
130
+ msg_ids = set(message_ids)
130
131
  # Pull Messages
131
132
  message_res_list = self.state.get_message_res(message_ids=msg_ids)
132
133
  # Get IDs of Messages these replies are for
133
134
  message_ins_ids_to_delete = {
134
- UUID(msg_res.metadata.reply_to_message_id) for msg_res in message_res_list
135
+ msg_res.metadata.reply_to_message_id for msg_res in message_res_list
135
136
  }
136
137
  # Delete
137
138
  self.state.delete_messages(message_ins_ids=message_ins_ids_to_delete)
flwr/server/history.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
flwr/server/server.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
flwr/server/server_app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -52,6 +52,30 @@ GRID_USAGE_EXAMPLE = """
52
52
  # Your existing ServerApp code ...
53
53
  """
54
54
 
55
+ BOTH_MAIN_FN_SERVER_FN_PROVIDED_ERROR_MSG = (
56
+ "Use either a custom main function or a `Strategy`, but not both."
57
+ """
58
+
59
+ Use the `ServerApp` with an existing `Strategy`:
60
+
61
+ server_config = ServerConfig(num_rounds=3)
62
+ strategy = FedAvg()
63
+
64
+ app = ServerApp(
65
+ server_config=server_config,
66
+ strategy=strategy,
67
+ )
68
+
69
+ Use the `ServerApp` with a custom main function:
70
+
71
+ app = ServerApp()
72
+
73
+ @app.main()
74
+ def main(grid: Grid, context: Context) -> None:
75
+ print("ServerApp running")
76
+ """
77
+ )
78
+
55
79
  DRIVER_DEPRECATION_MSG = """
56
80
  The `Driver` class is deprecated, it will be removed in a future release.
57
81
  """
@@ -70,25 +94,25 @@ class ServerApp: # pylint: disable=too-many-instance-attributes
70
94
 
71
95
  Examples
72
96
  --------
73
- Use the ``ServerApp`` with an existing ``Strategy``:
74
-
75
- >>> def server_fn(context: Context):
76
- >>> server_config = ServerConfig(num_rounds=3)
77
- >>> strategy = FedAvg()
78
- >>> return ServerAppComponents(
79
- >>> strategy=strategy,
80
- >>> server_config=server_config,
81
- >>> )
82
- >>>
83
- >>> app = ServerApp(server_fn=server_fn)
84
-
85
- Use the ``ServerApp`` with a custom main function:
86
-
87
- >>> app = ServerApp()
88
- >>>
89
- >>> @app.main()
90
- >>> def main(grid: Grid, context: Context) -> None:
91
- >>> print("ServerApp running")
97
+ Use the ``ServerApp`` with an existing ``Strategy``::
98
+
99
+ def server_fn(context: Context):
100
+ server_config = ServerConfig(num_rounds=3)
101
+ strategy = FedAvg()
102
+ return ServerAppComponents(
103
+ strategy=strategy,
104
+ server_config=server_config,
105
+ )
106
+
107
+ app = ServerApp(server_fn=server_fn)
108
+
109
+ Use the ``ServerApp`` with a custom main function::
110
+
111
+ app = ServerApp()
112
+
113
+ @app.main()
114
+ def main(grid: Grid, context: Context) -> None:
115
+ print("ServerApp running")
92
116
  """
93
117
 
94
118
  # pylint: disable=too-many-arguments,too-many-positional-arguments
@@ -156,38 +180,19 @@ class ServerApp: # pylint: disable=too-many-instance-attributes
156
180
 
157
181
  Examples
158
182
  --------
159
- >>> app = ServerApp()
160
- >>>
161
- >>> @app.main()
162
- >>> def main(grid: Grid, context: Context) -> None:
163
- >>> print("ServerApp running")
183
+ ::
184
+
185
+ app = ServerApp()
186
+
187
+ @app.main()
188
+ def main(grid: Grid, context: Context) -> None:
189
+ print("ServerApp running")
164
190
  """
165
191
 
166
192
  def main_decorator(main_fn: ServerAppCallable) -> ServerAppCallable:
167
193
  """Register the main fn with the ServerApp object."""
168
194
  if self._server or self._config or self._strategy or self._client_manager:
169
- raise ValueError(
170
- """Use either a custom main function or a `Strategy`, but not both.
171
-
172
- Use the `ServerApp` with an existing `Strategy`:
173
-
174
- >>> server_config = ServerConfig(num_rounds=3)
175
- >>> strategy = FedAvg()
176
- >>>
177
- >>> app = ServerApp(
178
- >>> server_config=server_config,
179
- >>> strategy=strategy,
180
- >>> )
181
-
182
- Use the `ServerApp` with a custom main function:
183
-
184
- >>> app = ServerApp()
185
- >>>
186
- >>> @app.main()
187
- >>> def main(grid: Grid, context: Context) -> None:
188
- >>> print("ServerApp running")
189
- """,
190
- )
195
+ raise ValueError(BOTH_MAIN_FN_SERVER_FN_PROVIDED_ERROR_MSG)
191
196
 
192
197
  sig = inspect.signature(main_fn)
193
198
  param = list(sig.parameters.values())[0]
@@ -219,17 +224,19 @@ class ServerApp: # pylint: disable=too-many-instance-attributes
219
224
 
220
225
  Examples
221
226
  --------
222
- >>> app = ServerApp()
223
- >>>
224
- >>> @app.lifespan()
225
- >>> def lifespan(context: Context) -> None:
226
- >>> # Perform initialization tasks before the app starts
227
- >>> print("Initializing ServerApp")
228
- >>>
229
- >>> yield # ServerApp is running
230
- >>>
231
- >>> # Perform cleanup tasks after the app stops
232
- >>> print("Cleaning up ServerApp")
227
+ ::
228
+
229
+ app = ServerApp()
230
+
231
+ @app.lifespan()
232
+ def lifespan(context: Context) -> None:
233
+ # Perform initialization tasks before the app starts
234
+ print("Initializing ServerApp")
235
+
236
+ yield # ServerApp is running
237
+
238
+ # Perform cleanup tasks after the app stops
239
+ print("Cleaning up ServerApp")
233
240
  """
234
241
 
235
242
  def lifespan_decorator(
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ from flwr.common.constant import (
38
38
  SubStatus,
39
39
  )
40
40
  from flwr.common.exit import ExitCode, flwr_exit
41
+ from flwr.common.heartbeat import HeartbeatSender, get_grpc_app_heartbeat_fn
41
42
  from flwr.common.logger import (
42
43
  log,
43
44
  mirror_output_to_queue,
@@ -117,6 +118,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
117
118
  success = True
118
119
  hash_run_id = None
119
120
  run_status = None
121
+ heartbeat_sender = None
120
122
  while True:
121
123
 
122
124
  try:
@@ -182,6 +184,16 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
182
184
  event_details={"run-id-hash": hash_run_id},
183
185
  )
184
186
 
187
+ # Set up heartbeat sender
188
+ heartbeat_fn = get_grpc_app_heartbeat_fn(
189
+ grid._stub,
190
+ run.run_id,
191
+ failure_message="Heartbeat failed unexpectedly. The SuperLink could "
192
+ "not find the provided run ID, or the run status is invalid.",
193
+ )
194
+ heartbeat_sender = HeartbeatSender(heartbeat_fn)
195
+ heartbeat_sender.start()
196
+
185
197
  # Load and run the ServerApp with the Grid
186
198
  updated_context = run_(
187
199
  grid=grid,
@@ -213,6 +225,11 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
213
225
  success = False
214
226
 
215
227
  finally:
228
+ # Stop heartbeat sender
229
+ if heartbeat_sender:
230
+ heartbeat_sender.stop()
231
+ heartbeat_sender = None
232
+
216
233
  # Stop log uploader for this run and upload final logs
217
234
  if log_uploader:
218
235
  stop_log_uploader(log_queue, log_uploader)
@@ -226,6 +243,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
226
243
  run_id=run.run_id, run_status=run_status_proto
227
244
  )
228
245
  )
246
+
229
247
  event(
230
248
  EventType.FLWR_SERVERAPP_RUN_LEAVE,
231
249
  event_details={"run-id-hash": hash_run_id, "success": success},
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ from .aggregate import aggregate_bulyan, aggregate_krum
37
37
  from .fedavg import FedAvg
38
38
 
39
39
 
40
- # flake8: noqa: E501
40
+ # noqa: E501
41
41
  # pylint: disable=line-too-long
42
42
  class Bulyan(FedAvg):
43
43
  """Bulyan strategy.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -77,15 +77,15 @@ class DifferentialPrivacyServerSideAdaptiveClipping(Strategy):
77
77
 
78
78
  Examples
79
79
  --------
80
- Create a strategy:
80
+ Create a strategy::
81
81
 
82
- >>> strategy = fl.server.strategy.FedAvg( ... )
82
+ strategy = fl.server.strategy.FedAvg( ... )
83
83
 
84
- Wrap the strategy with the DifferentialPrivacyServerSideAdaptiveClipping wrapper
84
+ Wrap the strategy with the DifferentialPrivacyServerSideAdaptiveClipping wrapper::
85
85
 
86
- >>> dp_strategy = DifferentialPrivacyServerSideAdaptiveClipping(
87
- >>> strategy, cfg.noise_multiplier, cfg.num_sampled_clients, ...
88
- >>> )
86
+ dp_strategy = DifferentialPrivacyServerSideAdaptiveClipping(
87
+ strategy, cfg.noise_multiplier, cfg.num_sampled_clients, ...
88
+ )
89
89
  """
90
90
 
91
91
  # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-positional-arguments
@@ -290,21 +290,21 @@ class DifferentialPrivacyClientSideAdaptiveClipping(Strategy):
290
290
 
291
291
  Examples
292
292
  --------
293
- Create a strategy:
293
+ Create a strategy::
294
294
 
295
- >>> strategy = fl.server.strategy.FedAvg(...)
295
+ strategy = fl.server.strategy.FedAvg(...)
296
296
 
297
- Wrap the strategy with the `DifferentialPrivacyClientSideAdaptiveClipping` wrapper:
297
+ Wrap the strategy with the `DifferentialPrivacyClientSideAdaptiveClipping` wrapper::
298
298
 
299
- >>> dp_strategy = DifferentialPrivacyClientSideAdaptiveClipping(
300
- >>> strategy, cfg.noise_multiplier, cfg.num_sampled_clients
301
- >>> )
299
+ dp_strategy = DifferentialPrivacyClientSideAdaptiveClipping(
300
+ strategy, cfg.noise_multiplier, cfg.num_sampled_clients
301
+ )
302
302
 
303
- On the client, add the `adaptiveclipping_mod` to the client-side mods:
303
+ On the client, add the `adaptiveclipping_mod` to the client-side mods::
304
304
 
305
- >>> app = fl.client.ClientApp(
306
- >>> client_fn=client_fn, mods=[adaptiveclipping_mod]
307
- >>> )
305
+ app = fl.client.ClientApp(
306
+ client_fn=client_fn, mods=[adaptiveclipping_mod]
307
+ )
308
308
  """
309
309
 
310
310
  # pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-positional-arguments
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -64,15 +64,15 @@ class DifferentialPrivacyServerSideFixedClipping(Strategy):
64
64
 
65
65
  Examples
66
66
  --------
67
- Create a strategy:
67
+ Create a strategy::
68
68
 
69
- >>> strategy = fl.server.strategy.FedAvg( ... )
69
+ strategy = fl.server.strategy.FedAvg( ... )
70
70
 
71
- Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping wrapper
71
+ Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping wrapper::
72
72
 
73
- >>> dp_strategy = DifferentialPrivacyServerSideFixedClipping(
74
- >>> strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients
75
- >>> )
73
+ dp_strategy = DifferentialPrivacyServerSideFixedClipping(
74
+ strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients
75
+ )
76
76
  """
77
77
 
78
78
  # pylint: disable=too-many-arguments,too-many-instance-attributes
@@ -228,21 +228,21 @@ class DifferentialPrivacyClientSideFixedClipping(Strategy):
228
228
 
229
229
  Examples
230
230
  --------
231
- Create a strategy:
231
+ Create a strategy::
232
232
 
233
- >>> strategy = fl.server.strategy.FedAvg(...)
233
+ strategy = fl.server.strategy.FedAvg(...)
234
234
 
235
- Wrap the strategy with the `DifferentialPrivacyClientSideFixedClipping` wrapper:
235
+ Wrap the strategy with the `DifferentialPrivacyClientSideFixedClipping` wrapper::
236
236
 
237
- >>> dp_strategy = DifferentialPrivacyClientSideFixedClipping(
238
- >>> strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients
239
- >>> )
237
+ dp_strategy = DifferentialPrivacyClientSideFixedClipping(
238
+ strategy, cfg.noise_multiplier, cfg.clipping_norm, cfg.num_sampled_clients
239
+ )
240
240
 
241
- On the client, add the `fixedclipping_mod` to the client-side mods:
241
+ On the client, add the `fixedclipping_mod` to the client-side mods::
242
242
 
243
- >>> app = fl.client.ClientApp(
244
- >>> client_fn=client_fn, mods=[fixedclipping_mod]
245
- >>> )
243
+ app = fl.client.ClientApp(
244
+ client_fn=client_fn, mods=[fixedclipping_mod]
245
+ )
246
246
  """
247
247
 
248
248
  # pylint: disable=too-many-arguments,too-many-instance-attributes
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.