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
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Public Flower ServerApp APIs."""
@@ -1,4 +1,4 @@
1
- # Copyright 2021 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/simulation/app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2021 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.
@@ -39,6 +39,7 @@ from flwr.common.constant import (
39
39
  SubStatus,
40
40
  )
41
41
  from flwr.common.exit import ExitCode, flwr_exit
42
+ from flwr.common.heartbeat import HeartbeatSender, get_grpc_app_heartbeat_fn
42
43
  from flwr.common.logger import (
43
44
  log,
44
45
  mirror_output_to_queue,
@@ -120,6 +121,7 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
120
121
  # Resolve directory where FABs are installed
121
122
  flwr_dir = get_flwr_dir(flwr_dir_)
122
123
  log_uploader = None
124
+ heartbeat_sender = None
123
125
 
124
126
  while True:
125
127
 
@@ -210,6 +212,16 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
210
212
  },
211
213
  )
212
214
 
215
+ # Set up heartbeat sender
216
+ heartbeat_fn = get_grpc_app_heartbeat_fn(
217
+ conn._stub,
218
+ run.run_id,
219
+ failure_message="Heartbeat failed unexpectedly. The SuperLink could "
220
+ "not find the provided run ID, or the run status is invalid.",
221
+ )
222
+ heartbeat_sender = HeartbeatSender(heartbeat_fn)
223
+ heartbeat_sender.start()
224
+
213
225
  # Launch the simulation
214
226
  updated_context = _run_simulation(
215
227
  server_app_attr=server_app_attr,
@@ -240,6 +252,11 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
240
252
  run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(ex))
241
253
 
242
254
  finally:
255
+ # Stop heartbeat sender
256
+ if heartbeat_sender:
257
+ heartbeat_sender.stop()
258
+ heartbeat_sender = None
259
+
243
260
  # Stop log uploader for this run and upload final logs
244
261
  if log_uploader:
245
262
  stop_log_uploader(log_queue, log_uploader)
@@ -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 2021 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.
@@ -1,4 +1,4 @@
1
- # Copyright 2021 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.
@@ -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.
@@ -172,7 +172,7 @@ def run_simulation(
172
172
  ServerApp and receive a Message describing what the ClientApp should perform.
173
173
 
174
174
  backend_name : str (default: ray)
175
- A simulation backend that runs `ClientApp`s.
175
+ A simulation backend that runs `ClientApp` objects.
176
176
 
177
177
  backend_config : Optional[BackendConfig]
178
178
  'A dictionary to configure a backend. Separate dictionaries to configure
@@ -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.
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Infrastructure components shared between SuperLink and SuperNode."""
@@ -0,0 +1,24 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower ObjectStore."""
16
+
17
+ from .object_store import NoObjectInStoreError, ObjectStore
18
+ from .object_store_factory import ObjectStoreFactory
19
+
20
+ __all__ = [
21
+ "NoObjectInStoreError",
22
+ "ObjectStore",
23
+ "ObjectStoreFactory",
24
+ ]
@@ -0,0 +1,229 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower in-memory ObjectStore implementation."""
16
+
17
+
18
+ import threading
19
+ from dataclasses import dataclass
20
+ from typing import Optional
21
+
22
+ from flwr.common.inflatable import (
23
+ get_object_children_ids_from_object_content,
24
+ get_object_id,
25
+ is_valid_sha256_hash,
26
+ iterate_object_tree,
27
+ )
28
+ from flwr.common.inflatable_utils import validate_object_content
29
+ from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
30
+
31
+ from .object_store import NoObjectInStoreError, ObjectStore
32
+
33
+
34
+ @dataclass
35
+ class ObjectEntry:
36
+ """Data class representing an object entry in the store."""
37
+
38
+ content: bytes
39
+ is_available: bool
40
+ ref_count: int # Number of references (direct parents) to this object
41
+ runs: set[int] # Set of run IDs that used this object
42
+
43
+
44
+ class InMemoryObjectStore(ObjectStore):
45
+ """In-memory implementation of the ObjectStore interface."""
46
+
47
+ def __init__(self, verify: bool = True) -> None:
48
+ self.verify = verify
49
+ self.store: dict[str, ObjectEntry] = {}
50
+ self.lock_store = threading.RLock()
51
+ # Mapping the Object ID of a message to the list of descendant object IDs
52
+ self.msg_descendant_objects_mapping: dict[str, list[str]] = {}
53
+ self.lock_msg_mapping = threading.RLock()
54
+ # Mapping each run ID to a set of object IDs that are used in that run
55
+ self.run_objects_mapping: dict[int, set[str]] = {}
56
+
57
+ def preregister(self, run_id: int, object_tree: ObjectTree) -> list[str]:
58
+ """Identify and preregister missing objects."""
59
+ new_objects = []
60
+ if run_id not in self.run_objects_mapping:
61
+ self.run_objects_mapping[run_id] = set()
62
+
63
+ for tree_node in iterate_object_tree(object_tree):
64
+ obj_id = tree_node.object_id
65
+ # Verify object ID format (must be a valid sha256 hash)
66
+ if not is_valid_sha256_hash(obj_id):
67
+ raise ValueError(f"Invalid object ID format: {obj_id}")
68
+ with self.lock_store:
69
+ if obj_id not in self.store:
70
+ self.store[obj_id] = ObjectEntry(
71
+ content=b"", # Initially empty content
72
+ is_available=False, # Initially not available
73
+ ref_count=0, # Reference count starts at 0
74
+ runs={run_id}, # Start with the current run ID
75
+ )
76
+
77
+ # Increment the reference count for all its children
78
+ # Post-order traversal ensures that children are registered
79
+ # before parents
80
+ for child_node in tree_node.children:
81
+ child_id = child_node.object_id
82
+ self.store[child_id].ref_count += 1
83
+
84
+ # Add the object ID to the run's mapping
85
+ self.run_objects_mapping[run_id].add(obj_id)
86
+
87
+ # Add to the list of new objects
88
+ new_objects.append(obj_id)
89
+ else:
90
+ # Object is in store, retrieve it
91
+ obj_entry = self.store[obj_id]
92
+
93
+ # Add to the list of new objects if not available
94
+ if not obj_entry.is_available:
95
+ new_objects.append(obj_id)
96
+
97
+ # If the object is already registered but not in this run,
98
+ # add the run ID to its runs
99
+ if obj_id not in self.run_objects_mapping[run_id]:
100
+ obj_entry.runs.add(run_id)
101
+ self.run_objects_mapping[run_id].add(obj_id)
102
+
103
+ return new_objects
104
+
105
+ def put(self, object_id: str, object_content: bytes) -> None:
106
+ """Put an object into the store."""
107
+ if self.verify:
108
+ # Verify object_id and object_content match
109
+ object_id_from_content = get_object_id(object_content)
110
+ if object_id != object_id_from_content:
111
+ raise ValueError(f"Object ID {object_id} does not match content hash")
112
+
113
+ # Validate object content
114
+ validate_object_content(content=object_content)
115
+
116
+ with self.lock_store:
117
+ # Only allow adding the object if it has been preregistered
118
+ if object_id not in self.store:
119
+ raise NoObjectInStoreError(
120
+ f"Object with ID '{object_id}' was not pre-registered."
121
+ )
122
+
123
+ # Return if object is already present in the store
124
+ if self.store[object_id].is_available:
125
+ return
126
+
127
+ # Update the object entry in the store
128
+ self.store[object_id].content = object_content
129
+ self.store[object_id].is_available = True
130
+
131
+ def set_message_descendant_ids(
132
+ self, msg_object_id: str, descendant_ids: list[str]
133
+ ) -> None:
134
+ """Store the mapping from a ``Message`` object ID to the object IDs of its
135
+ descendants."""
136
+ with self.lock_msg_mapping:
137
+ self.msg_descendant_objects_mapping[msg_object_id] = descendant_ids
138
+
139
+ def get_message_descendant_ids(self, msg_object_id: str) -> list[str]:
140
+ """Retrieve the object IDs of all descendants of a given Message."""
141
+ with self.lock_msg_mapping:
142
+ if msg_object_id not in self.msg_descendant_objects_mapping:
143
+ raise NoObjectInStoreError(
144
+ f"No message registered in Object Store with ID '{msg_object_id}'. "
145
+ "Mapping to descendants could not be found."
146
+ )
147
+ return self.msg_descendant_objects_mapping[msg_object_id]
148
+
149
+ def delete_message_descendant_ids(self, msg_object_id: str) -> None:
150
+ """Delete the mapping from a ``Message`` object ID to its descendants."""
151
+ with self.lock_msg_mapping:
152
+ self.msg_descendant_objects_mapping.pop(msg_object_id, None)
153
+
154
+ def get(self, object_id: str) -> Optional[bytes]:
155
+ """Get an object from the store."""
156
+ with self.lock_store:
157
+ # Check if the object ID is pre-registered
158
+ if object_id not in self.store:
159
+ return None
160
+
161
+ # Return content (if not yet available, it will b"")
162
+ return self.store[object_id].content
163
+
164
+ def delete(self, object_id: str) -> None:
165
+ """Delete an object and its unreferenced descendants from the store."""
166
+ with self.lock_store:
167
+ # If the object is not in the store, nothing to delete
168
+ if (object_entry := self.store.get(object_id)) is None:
169
+ return
170
+
171
+ # Delete the object if it has no references left
172
+ if object_entry.ref_count == 0:
173
+ del self.store[object_id]
174
+
175
+ # Remove the object from the run's mapping
176
+ for run_id in object_entry.runs:
177
+ self.run_objects_mapping[run_id].discard(object_id)
178
+
179
+ # Decrease the reference count of its children
180
+ children_ids = get_object_children_ids_from_object_content(
181
+ object_entry.content
182
+ )
183
+ for child_id in children_ids:
184
+ self.store[child_id].ref_count -= 1
185
+
186
+ # Recursively try to delete the child object
187
+ self.delete(child_id)
188
+
189
+ def delete_objects_in_run(self, run_id: int) -> None:
190
+ """Delete all objects that were registered in a specific run."""
191
+ with self.lock_store:
192
+ if run_id not in self.run_objects_mapping:
193
+ return
194
+ for object_id in list(self.run_objects_mapping[run_id]):
195
+ # Check if the object is still in the store
196
+ if (object_entry := self.store.get(object_id)) is None:
197
+ continue
198
+
199
+ # Remove the run ID from the object's runs
200
+ object_entry.runs.discard(run_id)
201
+
202
+ # Only message objects are allowed to have a `ref_count` of 0,
203
+ # and every message object must have a `ref_count` of 0
204
+ if object_entry.ref_count == 0:
205
+ # Delete the message object and its unreferenced descendants
206
+ self.delete(object_id)
207
+
208
+ # Delete the message's descendants mapping
209
+ self.delete_message_descendant_ids(object_id)
210
+
211
+ # Remove the run from the mapping
212
+ del self.run_objects_mapping[run_id]
213
+
214
+ def clear(self) -> None:
215
+ """Clear the store."""
216
+ with self.lock_store:
217
+ self.store.clear()
218
+ self.msg_descendant_objects_mapping.clear()
219
+ self.run_objects_mapping.clear()
220
+
221
+ def __contains__(self, object_id: str) -> bool:
222
+ """Check if an object_id is in the store."""
223
+ with self.lock_store:
224
+ return object_id in self.store
225
+
226
+ def __len__(self) -> int:
227
+ """Get the number of objects in the store."""
228
+ with self.lock_store:
229
+ return len(self.store)
@@ -0,0 +1,192 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower abstract ObjectStore definition."""
16
+
17
+
18
+ import abc
19
+ from typing import Optional
20
+
21
+ from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
22
+
23
+
24
+ class NoObjectInStoreError(Exception):
25
+ """Error when trying to access an element in the ObjectStore that does not exist."""
26
+
27
+ def __init__(self, message: str):
28
+ super().__init__(message)
29
+ self.message = message
30
+
31
+ def __str__(self) -> str:
32
+ """Return formatted exception message string."""
33
+ return f"NoObjectInStoreError: {self.message}"
34
+
35
+
36
+ class ObjectStore(abc.ABC):
37
+ """Abstract base class for `ObjectStore` implementations.
38
+
39
+ This class defines the interface for an object store that can store, retrieve, and
40
+ delete objects identified by object IDs.
41
+ """
42
+
43
+ @abc.abstractmethod
44
+ def preregister(self, run_id: int, object_tree: ObjectTree) -> list[str]:
45
+ """Identify and preregister missing objects in the `ObjectStore`.
46
+
47
+ Parameters
48
+ ----------
49
+ run_id : int
50
+ The ID of the run for which to preregister objects.
51
+ object_tree : ObjectTree
52
+ The object tree containing the IDs of objects to preregister.
53
+ This tree should contain all objects that are expected to be
54
+ stored in the `ObjectStore`.
55
+
56
+ Returns
57
+ -------
58
+ list[str]
59
+ A list of object IDs that were either not previously preregistered
60
+ in the `ObjectStore`, or were preregistered but are not yet available.
61
+ """
62
+
63
+ @abc.abstractmethod
64
+ def put(self, object_id: str, object_content: bytes) -> None:
65
+ """Put an object into the store.
66
+
67
+ Parameters
68
+ ----------
69
+ object_id : str
70
+ The object_id under which to store the object. Must be preregistered.
71
+ object_content : bytes
72
+ The deflated object to store.
73
+ """
74
+
75
+ @abc.abstractmethod
76
+ def get(self, object_id: str) -> Optional[bytes]:
77
+ """Get an object from the store.
78
+
79
+ Parameters
80
+ ----------
81
+ object_id : str
82
+ The object_id under which the object is stored.
83
+
84
+ Returns
85
+ -------
86
+ bytes
87
+ The object stored under the given object_id.
88
+ """
89
+
90
+ @abc.abstractmethod
91
+ def delete(self, object_id: str) -> None:
92
+ """Delete an object and its unreferenced descendants from the store.
93
+
94
+ This method attempts to recursively delete the specified object and its
95
+ descendants, if they are not referenced by any other object.
96
+
97
+ Parameters
98
+ ----------
99
+ object_id : str
100
+ The object_id under which the object is stored.
101
+
102
+ Notes
103
+ -----
104
+ The object of the given object_id will NOT be deleted if it is still referenced
105
+ by any other object in the store.
106
+ """
107
+
108
+ @abc.abstractmethod
109
+ def delete_objects_in_run(self, run_id: int) -> None:
110
+ """Delete all objects that were registered in a specific run.
111
+
112
+ Parameters
113
+ ----------
114
+ run_id : int
115
+ The ID of the run for which to delete objects.
116
+
117
+ Notes
118
+ -----
119
+ Objects that are still registered in other runs will NOT be deleted.
120
+ """
121
+
122
+ @abc.abstractmethod
123
+ def clear(self) -> None:
124
+ """Clear the store.
125
+
126
+ This method should remove all objects from the store.
127
+ """
128
+
129
+ @abc.abstractmethod
130
+ def set_message_descendant_ids(
131
+ self, msg_object_id: str, descendant_ids: list[str]
132
+ ) -> None:
133
+ """Store the mapping from a ``Message`` object ID to the object IDs of its
134
+ descendants.
135
+
136
+ Parameters
137
+ ----------
138
+ msg_object_id : str
139
+ The object ID of the ``Message``.
140
+ descendant_ids : list[str]
141
+ A list of object IDs representing all descendant objects of the ``Message``.
142
+ """
143
+
144
+ @abc.abstractmethod
145
+ def get_message_descendant_ids(self, msg_object_id: str) -> list[str]:
146
+ """Retrieve the object IDs of all descendants of a given ``Message``.
147
+
148
+ Parameters
149
+ ----------
150
+ msg_object_id : str
151
+ The object ID of the ``Message``.
152
+
153
+ Returns
154
+ -------
155
+ list[str]
156
+ A list of object IDs of all descendant objects of the ``Message``.
157
+ """
158
+
159
+ @abc.abstractmethod
160
+ def delete_message_descendant_ids(self, msg_object_id: str) -> None:
161
+ """Delete the mapping from a ``Message`` object ID to its descendants.
162
+
163
+ Parameters
164
+ ----------
165
+ msg_object_id : str
166
+ The object ID of the ``Message``.
167
+ """
168
+
169
+ @abc.abstractmethod
170
+ def __contains__(self, object_id: str) -> bool:
171
+ """Check if an object_id is in the store.
172
+
173
+ Parameters
174
+ ----------
175
+ object_id : str
176
+ The object_id to check.
177
+
178
+ Returns
179
+ -------
180
+ bool
181
+ True if the object_id is in the store, False otherwise.
182
+ """
183
+
184
+ @abc.abstractmethod
185
+ def __len__(self) -> int:
186
+ """Return the number of objects in the store.
187
+
188
+ Returns
189
+ -------
190
+ int
191
+ The number of objects currently stored.
192
+ """
@@ -0,0 +1,44 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Factory class that creates ObjectStore instances."""
16
+
17
+
18
+ from logging import DEBUG
19
+ from typing import Optional
20
+
21
+ from flwr.common.logger import log
22
+
23
+ from .in_memory_object_store import InMemoryObjectStore
24
+ from .object_store import ObjectStore
25
+
26
+
27
+ class ObjectStoreFactory:
28
+ """Factory class that creates ObjectStore instances."""
29
+
30
+ def __init__(self) -> None:
31
+ self.store_instance: Optional[ObjectStore] = None
32
+
33
+ def store(self) -> ObjectStore:
34
+ """Return an ObjectStore instance and create it, if necessary.
35
+
36
+ Returns
37
+ -------
38
+ ObjectStore
39
+ An ObjectStore instance for storing objects by object_id.
40
+ """
41
+ if self.store_instance is None:
42
+ self.store_instance = InMemoryObjectStore()
43
+ log(DEBUG, "Using InMemoryObjectStore")
44
+ return self.store_instance
@@ -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/superexec/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.