flwr 1.22.0__py3-none-any.whl → 1.24.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 (301) hide show
  1. flwr/__init__.py +16 -5
  2. flwr/app/error.py +2 -2
  3. flwr/app/exception.py +3 -3
  4. flwr/cli/app.py +34 -1
  5. flwr/cli/app_cmd/__init__.py +23 -0
  6. flwr/cli/app_cmd/publish.py +285 -0
  7. flwr/cli/app_cmd/review.py +252 -0
  8. flwr/cli/auth_plugin/__init__.py +15 -6
  9. flwr/cli/auth_plugin/auth_plugin.py +94 -0
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
  12. flwr/cli/build.py +166 -53
  13. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
  14. flwr/cli/config_utils.py +101 -13
  15. flwr/cli/federation/__init__.py +24 -0
  16. flwr/cli/federation/ls.py +140 -0
  17. flwr/cli/federation/show.py +317 -0
  18. flwr/cli/install.py +91 -13
  19. flwr/cli/log.py +54 -11
  20. flwr/cli/login/login.py +41 -27
  21. flwr/cli/ls.py +177 -133
  22. flwr/cli/new/new.py +175 -40
  23. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
  24. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  26. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  28. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  29. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  30. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  31. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  33. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
  34. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
  35. flwr/cli/pull.py +12 -7
  36. flwr/cli/run/run.py +82 -31
  37. flwr/cli/run_utils.py +130 -0
  38. flwr/cli/stop.py +27 -9
  39. flwr/cli/supernode/__init__.py +25 -0
  40. flwr/cli/supernode/ls.py +268 -0
  41. flwr/cli/supernode/register.py +190 -0
  42. flwr/cli/supernode/unregister.py +140 -0
  43. flwr/cli/utils.py +464 -81
  44. flwr/client/__init__.py +2 -1
  45. flwr/client/dpfedavg_numpy_client.py +4 -1
  46. flwr/client/grpc_adapter_client/connection.py +12 -15
  47. flwr/client/grpc_rere_client/connection.py +68 -41
  48. flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
  49. flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
  50. flwr/client/message_handler/message_handler.py +2 -2
  51. flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
  52. flwr/client/numpy_client.py +1 -1
  53. flwr/client/rest_client/connection.py +94 -51
  54. flwr/client/run_info_store.py +4 -5
  55. flwr/client/typing.py +1 -1
  56. flwr/clientapp/__init__.py +1 -2
  57. flwr/{client → clientapp}/client_app.py +9 -10
  58. flwr/clientapp/mod/centraldp_mods.py +16 -17
  59. flwr/clientapp/mod/localdp_mod.py +8 -9
  60. flwr/clientapp/typing.py +1 -1
  61. flwr/{client/clientapp → clientapp}/utils.py +4 -4
  62. flwr/common/address.py +1 -2
  63. flwr/common/args.py +3 -4
  64. flwr/common/config.py +13 -16
  65. flwr/common/constant.py +56 -13
  66. flwr/common/differential_privacy.py +3 -4
  67. flwr/common/event_log_plugin/event_log_plugin.py +3 -4
  68. flwr/common/exit/exit.py +15 -2
  69. flwr/common/exit/exit_code.py +39 -10
  70. flwr/common/exit/exit_handler.py +6 -2
  71. flwr/common/exit/signal_handler.py +5 -5
  72. flwr/common/grpc.py +6 -6
  73. flwr/common/inflatable_protobuf_utils.py +1 -1
  74. flwr/common/inflatable_utils.py +48 -31
  75. flwr/common/logger.py +19 -19
  76. flwr/common/message.py +4 -4
  77. flwr/common/object_ref.py +7 -7
  78. flwr/common/record/array.py +6 -6
  79. flwr/common/record/arrayrecord.py +18 -21
  80. flwr/common/record/configrecord.py +3 -3
  81. flwr/common/record/recorddict.py +5 -5
  82. flwr/common/record/typeddict.py +9 -2
  83. flwr/common/recorddict_compat.py +7 -10
  84. flwr/common/retry_invoker.py +20 -20
  85. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
  86. flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
  87. flwr/common/serde.py +9 -6
  88. flwr/common/serde_utils.py +2 -2
  89. flwr/common/telemetry.py +9 -5
  90. flwr/common/typing.py +59 -43
  91. flwr/compat/client/app.py +39 -38
  92. flwr/compat/client/grpc_client/connection.py +13 -13
  93. flwr/compat/server/app.py +5 -6
  94. flwr/proto/appio_pb2.py +13 -3
  95. flwr/proto/appio_pb2.pyi +134 -65
  96. flwr/proto/appio_pb2_grpc.py +20 -0
  97. flwr/proto/appio_pb2_grpc.pyi +27 -0
  98. flwr/proto/clientappio_pb2.py +17 -7
  99. flwr/proto/clientappio_pb2.pyi +15 -0
  100. flwr/proto/clientappio_pb2_grpc.py +206 -40
  101. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  102. flwr/proto/control_pb2.py +72 -40
  103. flwr/proto/control_pb2.pyi +319 -87
  104. flwr/proto/control_pb2_grpc.py +339 -28
  105. flwr/proto/control_pb2_grpc.pyi +209 -37
  106. flwr/proto/error_pb2.py +13 -3
  107. flwr/proto/error_pb2.pyi +24 -6
  108. flwr/proto/error_pb2_grpc.py +20 -0
  109. flwr/proto/error_pb2_grpc.pyi +27 -0
  110. flwr/proto/fab_pb2.py +24 -10
  111. flwr/proto/fab_pb2.pyi +68 -20
  112. flwr/proto/fab_pb2_grpc.py +20 -0
  113. flwr/proto/fab_pb2_grpc.pyi +27 -0
  114. flwr/proto/federation_pb2.py +38 -0
  115. flwr/proto/federation_pb2.pyi +56 -0
  116. flwr/proto/federation_pb2_grpc.py +24 -0
  117. flwr/proto/federation_pb2_grpc.pyi +31 -0
  118. flwr/proto/fleet_pb2.py +45 -27
  119. flwr/proto/fleet_pb2.pyi +186 -70
  120. flwr/proto/fleet_pb2_grpc.py +277 -66
  121. flwr/proto/fleet_pb2_grpc.pyi +201 -55
  122. flwr/proto/grpcadapter_pb2.py +14 -4
  123. flwr/proto/grpcadapter_pb2.pyi +38 -16
  124. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  125. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  126. flwr/proto/heartbeat_pb2.py +17 -7
  127. flwr/proto/heartbeat_pb2.pyi +51 -22
  128. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  129. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  130. flwr/proto/log_pb2.py +13 -3
  131. flwr/proto/log_pb2.pyi +34 -11
  132. flwr/proto/log_pb2_grpc.py +20 -0
  133. flwr/proto/log_pb2_grpc.pyi +27 -0
  134. flwr/proto/message_pb2.py +15 -5
  135. flwr/proto/message_pb2.pyi +154 -86
  136. flwr/proto/message_pb2_grpc.py +20 -0
  137. flwr/proto/message_pb2_grpc.pyi +27 -0
  138. flwr/proto/node_pb2.py +16 -4
  139. flwr/proto/node_pb2.pyi +77 -4
  140. flwr/proto/node_pb2_grpc.py +20 -0
  141. flwr/proto/node_pb2_grpc.pyi +27 -0
  142. flwr/proto/recorddict_pb2.py +13 -3
  143. flwr/proto/recorddict_pb2.pyi +184 -107
  144. flwr/proto/recorddict_pb2_grpc.py +20 -0
  145. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  146. flwr/proto/run_pb2.py +40 -31
  147. flwr/proto/run_pb2.pyi +149 -84
  148. flwr/proto/run_pb2_grpc.py +20 -0
  149. flwr/proto/run_pb2_grpc.pyi +27 -0
  150. flwr/proto/serverappio_pb2.py +13 -3
  151. flwr/proto/serverappio_pb2.pyi +32 -8
  152. flwr/proto/serverappio_pb2_grpc.py +246 -65
  153. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  154. flwr/proto/simulationio_pb2.py +16 -8
  155. flwr/proto/simulationio_pb2.pyi +15 -0
  156. flwr/proto/simulationio_pb2_grpc.py +162 -41
  157. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  158. flwr/proto/transport_pb2.py +20 -10
  159. flwr/proto/transport_pb2.pyi +249 -160
  160. flwr/proto/transport_pb2_grpc.py +35 -4
  161. flwr/proto/transport_pb2_grpc.pyi +38 -8
  162. flwr/server/app.py +173 -127
  163. flwr/server/client_manager.py +4 -5
  164. flwr/server/client_proxy.py +10 -11
  165. flwr/server/compat/app.py +4 -5
  166. flwr/server/compat/app_utils.py +2 -1
  167. flwr/server/compat/grid_client_proxy.py +10 -12
  168. flwr/server/compat/legacy_context.py +3 -4
  169. flwr/server/fleet_event_log_interceptor.py +2 -1
  170. flwr/server/grid/grid.py +2 -3
  171. flwr/server/grid/grpc_grid.py +10 -8
  172. flwr/server/grid/inmemory_grid.py +4 -4
  173. flwr/server/run_serverapp.py +2 -3
  174. flwr/server/server.py +34 -39
  175. flwr/server/server_app.py +7 -8
  176. flwr/server/server_config.py +1 -2
  177. flwr/server/serverapp/app.py +34 -28
  178. flwr/server/serverapp_components.py +4 -5
  179. flwr/server/strategy/aggregate.py +9 -8
  180. flwr/server/strategy/bulyan.py +13 -11
  181. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  182. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  183. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  184. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  185. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  186. flwr/server/strategy/fedadagrad.py +18 -14
  187. flwr/server/strategy/fedadam.py +16 -14
  188. flwr/server/strategy/fedavg.py +16 -17
  189. flwr/server/strategy/fedavg_android.py +15 -15
  190. flwr/server/strategy/fedavgm.py +21 -18
  191. flwr/server/strategy/fedmedian.py +2 -3
  192. flwr/server/strategy/fedopt.py +11 -10
  193. flwr/server/strategy/fedprox.py +10 -9
  194. flwr/server/strategy/fedtrimmedavg.py +12 -11
  195. flwr/server/strategy/fedxgb_bagging.py +13 -11
  196. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  197. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  198. flwr/server/strategy/fedyogi.py +16 -14
  199. flwr/server/strategy/krum.py +12 -11
  200. flwr/server/strategy/qfedavg.py +16 -15
  201. flwr/server/strategy/strategy.py +6 -9
  202. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
  203. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  204. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  206. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  207. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
  208. flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
  209. flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
  210. flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
  211. flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
  212. flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
  213. flwr/server/superlink/fleet/vce/vce_api.py +32 -13
  214. flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
  215. flwr/server/superlink/linkstate/linkstate.py +161 -62
  216. flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
  217. flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
  218. flwr/server/superlink/linkstate/utils.py +9 -60
  219. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  220. flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
  221. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  222. flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
  223. flwr/server/superlink/utils.py +4 -6
  224. flwr/server/typing.py +1 -1
  225. flwr/server/utils/tensorboard.py +15 -8
  226. flwr/server/utils/validator.py +2 -3
  227. flwr/server/workflow/default_workflows.py +5 -5
  228. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  229. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
  230. flwr/serverapp/strategy/bulyan.py +16 -15
  231. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  232. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  233. flwr/serverapp/strategy/fedadagrad.py +10 -11
  234. flwr/serverapp/strategy/fedadam.py +10 -11
  235. flwr/serverapp/strategy/fedavg.py +9 -10
  236. flwr/serverapp/strategy/fedavgm.py +17 -16
  237. flwr/serverapp/strategy/fedmedian.py +2 -2
  238. flwr/serverapp/strategy/fedopt.py +10 -11
  239. flwr/serverapp/strategy/fedprox.py +7 -8
  240. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  241. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  242. flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
  243. flwr/serverapp/strategy/fedyogi.py +9 -11
  244. flwr/serverapp/strategy/krum.py +7 -7
  245. flwr/serverapp/strategy/multikrum.py +9 -9
  246. flwr/serverapp/strategy/qfedavg.py +17 -16
  247. flwr/serverapp/strategy/strategy.py +6 -9
  248. flwr/serverapp/strategy/strategy_utils.py +7 -8
  249. flwr/simulation/app.py +46 -42
  250. flwr/simulation/legacy_app.py +12 -12
  251. flwr/simulation/ray_transport/ray_actor.py +11 -12
  252. flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
  253. flwr/simulation/run_simulation.py +44 -43
  254. flwr/simulation/simulationio_connection.py +4 -4
  255. flwr/supercore/cli/flower_superexec.py +3 -4
  256. flwr/supercore/constant.py +52 -0
  257. flwr/supercore/corestate/corestate.py +24 -3
  258. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  259. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  260. flwr/supercore/ffs/disk_ffs.py +1 -2
  261. flwr/supercore/ffs/ffs.py +1 -2
  262. flwr/supercore/ffs/ffs_factory.py +1 -2
  263. flwr/{common → supercore}/heartbeat.py +20 -25
  264. flwr/supercore/object_store/in_memory_object_store.py +1 -6
  265. flwr/supercore/object_store/object_store.py +1 -2
  266. flwr/supercore/object_store/object_store_factory.py +27 -8
  267. flwr/supercore/object_store/sqlite_object_store.py +253 -0
  268. flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
  269. flwr/supercore/primitives/asymmetric.py +117 -0
  270. flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
  271. flwr/supercore/sqlite_mixin.py +159 -0
  272. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  273. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  274. flwr/supercore/superexec/run_superexec.py +9 -13
  275. flwr/supercore/utils.py +20 -0
  276. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  277. flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
  278. flwr/superlink/auth_plugin/auth_plugin.py +88 -0
  279. flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
  280. flwr/superlink/federation/__init__.py +24 -0
  281. flwr/superlink/federation/federation_manager.py +64 -0
  282. flwr/superlink/federation/noop_federation_manager.py +71 -0
  283. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
  284. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  285. flwr/superlink/servicer/control/control_grpc.py +18 -17
  286. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  287. flwr/superlink/servicer/control/control_servicer.py +239 -63
  288. flwr/supernode/cli/flower_supernode.py +74 -26
  289. flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
  290. flwr/supernode/nodestate/nodestate.py +7 -8
  291. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  292. flwr/supernode/runtime/run_clientapp.py +43 -24
  293. flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
  294. flwr/supernode/start_client_internal.py +175 -51
  295. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
  296. flwr-1.24.0.dist-info/RECORD +454 -0
  297. flwr/common/auth_plugin/auth_plugin.py +0 -149
  298. flwr/supercore/object_store/utils.py +0 -43
  299. flwr-1.22.0.dist-info/RECORD +0 -428
  300. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
  301. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
@@ -17,12 +17,11 @@
17
17
 
18
18
  import traceback
19
19
  from logging import ERROR
20
- from typing import Optional
21
20
 
22
21
  from flwr import common
23
22
  from flwr.client import ClientFnExt
24
- from flwr.client.client_app import ClientApp
25
23
  from flwr.client.run_info_store import DeprecatedRunInfoStore
24
+ from flwr.clientapp.client_app import ClientApp
26
25
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordDict, now
27
26
  from flwr.common.constant import (
28
27
  NUM_PARTITIONS_KEY,
@@ -74,7 +73,7 @@ class RayActorClientProxy(ClientProxy):
74
73
  },
75
74
  )
76
75
 
77
- def _submit_job(self, message: Message, timeout: Optional[float]) -> Message:
76
+ def _submit_job(self, message: Message, timeout: float | None) -> Message:
78
77
  """Sumbit a message to the ActorPool."""
79
78
  run_id = message.metadata.run_id
80
79
 
@@ -114,8 +113,8 @@ class RayActorClientProxy(ClientProxy):
114
113
  self,
115
114
  recorddict: RecordDict,
116
115
  message_type: str,
117
- timeout: Optional[float],
118
- group_id: Optional[int],
116
+ timeout: float | None,
117
+ group_id: int | None,
119
118
  ) -> Message:
120
119
  """Wrap a RecordDict inside a Message."""
121
120
  return make_message(
@@ -136,8 +135,8 @@ class RayActorClientProxy(ClientProxy):
136
135
  def get_properties(
137
136
  self,
138
137
  ins: common.GetPropertiesIns,
139
- timeout: Optional[float],
140
- group_id: Optional[int],
138
+ timeout: float | None,
139
+ group_id: int | None,
141
140
  ) -> common.GetPropertiesRes:
142
141
  """Return client's properties."""
143
142
  recorddict = getpropertiesins_to_recorddict(ins)
@@ -155,8 +154,8 @@ class RayActorClientProxy(ClientProxy):
155
154
  def get_parameters(
156
155
  self,
157
156
  ins: common.GetParametersIns,
158
- timeout: Optional[float],
159
- group_id: Optional[int],
157
+ timeout: float | None,
158
+ group_id: int | None,
160
159
  ) -> common.GetParametersRes:
161
160
  """Return the current local model parameters."""
162
161
  recorddict = getparametersins_to_recorddict(ins)
@@ -172,7 +171,7 @@ class RayActorClientProxy(ClientProxy):
172
171
  return recorddict_to_getparametersres(message_out.content, keep_input=False)
173
172
 
174
173
  def fit(
175
- self, ins: common.FitIns, timeout: Optional[float], group_id: Optional[int]
174
+ self, ins: common.FitIns, timeout: float | None, group_id: int | None
176
175
  ) -> common.FitRes:
177
176
  """Train model parameters on the locally held dataset."""
178
177
  recorddict = fitins_to_recorddict(
@@ -190,7 +189,7 @@ class RayActorClientProxy(ClientProxy):
190
189
  return recorddict_to_fitres(message_out.content, keep_input=False)
191
190
 
192
191
  def evaluate(
193
- self, ins: common.EvaluateIns, timeout: Optional[float], group_id: Optional[int]
192
+ self, ins: common.EvaluateIns, timeout: float | None, group_id: int | None
194
193
  ) -> common.EvaluateRes:
195
194
  """Evaluate model parameters on the locally held dataset."""
196
195
  recorddict = evaluateins_to_recorddict(
@@ -210,8 +209,8 @@ class RayActorClientProxy(ClientProxy):
210
209
  def reconnect(
211
210
  self,
212
211
  ins: common.ReconnectIns,
213
- timeout: Optional[float],
214
- group_id: Optional[int],
212
+ timeout: float | None,
213
+ group_id: int | None,
215
214
  ) -> common.DisconnectRes:
216
215
  """Disconnect and (optionally) reconnect later."""
217
216
  return common.DisconnectRes(reason="") # Nothing to do here (yet)
@@ -26,11 +26,11 @@ import traceback
26
26
  from logging import DEBUG, ERROR, INFO, WARNING
27
27
  from pathlib import Path
28
28
  from queue import Empty, Queue
29
- from typing import Any, Optional
29
+ from typing import Any, cast
30
30
 
31
31
  from flwr.cli.config_utils import load_and_validate
32
32
  from flwr.cli.utils import get_sha256_hash
33
- from flwr.client import ClientApp
33
+ from flwr.clientapp import ClientApp
34
34
  from flwr.common import Context, EventType, RecordDict, event, log, now
35
35
  from flwr.common.config import get_fused_config_from_dir, parse_config_args
36
36
  from flwr.common.constant import RUN_ID_NUM_BYTES, Status
@@ -51,6 +51,9 @@ from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
51
51
  from flwr.simulation.ray_transport.utils import (
52
52
  enable_tf_gpu_growth as enable_gpu_growth,
53
53
  )
54
+ from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME, NOOP_FEDERATION
55
+ from flwr.supercore.object_store import ObjectStoreFactory
56
+ from flwr.superlink.federation import NoOpFederationManager
54
57
 
55
58
 
56
59
  def _replace_keys(d: Any, match: str, target: str) -> Any:
@@ -98,12 +101,7 @@ def run_simulation_from_cli() -> None:
98
101
  _check_ray_support(args.backend)
99
102
 
100
103
  # Load JSON config
101
- backend_config_dict = json.loads(args.backend_config)
102
-
103
- if backend_config_dict:
104
- # Backend config internally operates with `_` not with `-`
105
- backend_config_dict = _replace_keys(backend_config_dict, match="-", target="_")
106
- log(DEBUG, "backend_config_dict: %s", backend_config_dict)
104
+ backend_config = json.loads(args.backend_config)
107
105
 
108
106
  run_id = (
109
107
  generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
@@ -141,6 +139,7 @@ def run_simulation_from_cli() -> None:
141
139
 
142
140
  # Create run
143
141
  run = Run.create_empty(run_id)
142
+ run.federation = NOOP_FEDERATION
144
143
  run.override_config = override_config
145
144
 
146
145
  # Create Context
@@ -157,7 +156,7 @@ def run_simulation_from_cli() -> None:
157
156
  client_app_attr=client_app_attr,
158
157
  num_supernodes=args.num_supernodes,
159
158
  backend_name=args.backend,
160
- backend_config=backend_config_dict,
159
+ backend_config=backend_config,
161
160
  app_dir=args.app,
162
161
  run=run,
163
162
  enable_tf_gpu_growth=args.enable_tf_gpu_growth,
@@ -175,7 +174,7 @@ def run_simulation(
175
174
  client_app: ClientApp,
176
175
  num_supernodes: int,
177
176
  backend_name: str = "ray",
178
- backend_config: Optional[BackendConfig] = None,
177
+ backend_config: BackendConfig | None = None,
179
178
  enable_tf_gpu_growth: bool = False,
180
179
  verbose_logging: bool = False,
181
180
  ) -> None:
@@ -248,8 +247,8 @@ def run_simulation(
248
247
 
249
248
  # pylint: disable=too-many-arguments,too-many-positional-arguments
250
249
  def run_serverapp_th(
251
- server_app_attr: Optional[str],
252
- server_app: Optional[ServerApp],
250
+ server_app_attr: str | None,
251
+ server_app: ServerApp | None,
253
252
  server_app_context: Context,
254
253
  grid: Grid,
255
254
  app_dir: str,
@@ -266,8 +265,8 @@ def run_serverapp_th(
266
265
  exception_event: threading.Event,
267
266
  _grid: Grid,
268
267
  _server_app_dir: str,
269
- _server_app_attr: Optional[str],
270
- _server_app: Optional[ServerApp],
268
+ _server_app_attr: str | None,
269
+ _server_app: ServerApp | None,
271
270
  _ctx_queue: "Queue[Context]",
272
271
  ) -> None:
273
272
  """Run SeverApp, after check if GPU memory growth has to be set.
@@ -327,16 +326,18 @@ def _main_loop(
327
326
  enable_tf_gpu_growth: bool,
328
327
  run: Run,
329
328
  exit_event: EventType,
330
- flwr_dir: Optional[str] = None,
331
- client_app: Optional[ClientApp] = None,
332
- client_app_attr: Optional[str] = None,
333
- server_app: Optional[ServerApp] = None,
334
- server_app_attr: Optional[str] = None,
335
- server_app_context: Optional[Context] = None,
329
+ flwr_dir: str | None = None,
330
+ client_app: ClientApp | None = None,
331
+ client_app_attr: str | None = None,
332
+ server_app: ServerApp | None = None,
333
+ server_app_attr: str | None = None,
334
+ server_app_context: Context | None = None,
336
335
  ) -> Context:
337
336
  """Start ServerApp on a separate thread, then launch Simulation Engine."""
338
337
  # Initialize StateFactory
339
- state_factory = LinkStateFactory(":flwr-in-memory-state:")
338
+ state_factory = LinkStateFactory(
339
+ FLWR_IN_MEMORY_DB_NAME, NoOpFederationManager(), ObjectStoreFactory()
340
+ )
340
341
 
341
342
  f_stop = threading.Event()
342
343
  # A Threading event to indicate if an exception was raised in the ServerApp thread
@@ -427,16 +428,16 @@ def _main_loop(
427
428
  def _run_simulation(
428
429
  num_supernodes: int,
429
430
  exit_event: EventType,
430
- client_app: Optional[ClientApp] = None,
431
- server_app: Optional[ServerApp] = None,
431
+ client_app: ClientApp | None = None,
432
+ server_app: ServerApp | None = None,
432
433
  backend_name: str = "ray",
433
- backend_config: Optional[BackendConfig] = None,
434
- client_app_attr: Optional[str] = None,
435
- server_app_attr: Optional[str] = None,
436
- server_app_context: Optional[Context] = None,
434
+ backend_config: BackendConfig | None = None,
435
+ client_app_attr: str | None = None,
436
+ server_app_attr: str | None = None,
437
+ server_app_context: Context | None = None,
437
438
  app_dir: str = "",
438
- flwr_dir: Optional[str] = None,
439
- run: Optional[Run] = None,
439
+ flwr_dir: str | None = None,
440
+ run: Run | None = None,
440
441
  enable_tf_gpu_growth: bool = False,
441
442
  verbose_logging: bool = False,
442
443
  is_app: bool = False,
@@ -444,29 +445,28 @@ def _run_simulation(
444
445
  """Launch the Simulation Engine."""
445
446
  if backend_config is None:
446
447
  backend_config = {}
448
+ elif backend_config:
449
+ # Backend config internally operates with `_` not with `-`
450
+ backend_config = cast(
451
+ BackendConfig, _replace_keys(backend_config, match="-", target="_")
452
+ )
453
+ log(DEBUG, "backend_config: %s", backend_config)
447
454
 
448
- if "init_args" not in backend_config:
449
- backend_config["init_args"] = {}
450
-
455
+ # Set default init_args if not passed
456
+ backend_config.setdefault("init_args", {})
451
457
  # Set default client_resources if not passed
452
- if "client_resources" not in backend_config:
453
- backend_config["client_resources"] = {"num_cpus": 2, "num_gpus": 0}
454
-
458
+ backend_config.setdefault("client_resources", {"num_cpus": 2, "num_gpus": 0})
455
459
  # Initialization of backend config to enable GPU growth globally when set
456
- if "actor" not in backend_config:
457
- backend_config["actor"] = {"tensorflow": 0}
460
+ backend_config.setdefault("actor", {"tensorflow": 0})
458
461
 
459
462
  # Set logging level
460
463
  logger = logging.getLogger("flwr")
461
464
  if verbose_logging:
462
465
  update_console_handler(level=DEBUG, timestamps=True, colored=True)
463
466
  else:
464
- backend_config["init_args"]["logging_level"] = backend_config["init_args"].get(
465
- "logging_level", WARNING
466
- )
467
- backend_config["init_args"]["log_to_driver"] = backend_config["init_args"].get(
468
- "log_to_driver", True
469
- )
467
+ init_args = backend_config["init_args"]
468
+ init_args.setdefault("logging_level", WARNING)
469
+ init_args.setdefault("log_to_driver", True)
470
470
 
471
471
  if enable_tf_gpu_growth:
472
472
  # Check that Backend config has also enabled using GPU growth
@@ -482,6 +482,7 @@ def _run_simulation(
482
482
  if run is None:
483
483
  run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
484
484
  run = Run.create_empty(run_id=run_id)
485
+ run.federation = NOOP_FEDERATION
485
486
 
486
487
  args = (
487
488
  num_supernodes,
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from logging import DEBUG, WARNING
19
- from typing import Optional, cast
19
+ from typing import cast
20
20
 
21
21
  import grpc
22
22
 
@@ -43,12 +43,12 @@ class SimulationIoConnection:
43
43
  def __init__( # pylint: disable=too-many-arguments
44
44
  self,
45
45
  simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS,
46
- root_certificates: Optional[bytes] = None,
46
+ root_certificates: bytes | None = None,
47
47
  ) -> None:
48
48
  self._addr = simulationio_service_address
49
49
  self._cert = root_certificates
50
- self._grpc_stub: Optional[SimulationIoStub] = None
51
- self._channel: Optional[grpc.Channel] = None
50
+ self._grpc_stub: SimulationIoStub | None = None
51
+ self._channel: grpc.Channel | None = None
52
52
  self._retry_invoker = _make_simple_grpc_retry_invoker()
53
53
 
54
54
  @property
@@ -17,7 +17,7 @@
17
17
 
18
18
  import argparse
19
19
  from logging import INFO
20
- from typing import Any, Optional
20
+ from typing import Any
21
21
 
22
22
  import yaml
23
23
 
@@ -54,7 +54,7 @@ except ImportError:
54
54
 
55
55
  def get_ee_plugin_and_stub_class( # pylint: disable=unused-argument
56
56
  plugin_type: str,
57
- ) -> Optional[tuple[type[ExecPlugin], type[object]]]:
57
+ ) -> tuple[type[ExecPlugin], type[object]] | None:
58
58
  """Get the EE plugin class and stub class based on the plugin type."""
59
59
  return None
60
60
 
@@ -75,7 +75,6 @@ def flower_superexec() -> None:
75
75
  # Log the first message after parsing arguments in case of `--help`
76
76
  log(INFO, "Starting Flower SuperExec")
77
77
 
78
- # Trigger telemetry event
79
78
  event(EventType.RUN_SUPEREXEC_ENTER, {"plugin_type": args.plugin_type})
80
79
 
81
80
  # Load plugin config from YAML file if provided
@@ -83,7 +82,7 @@ def flower_superexec() -> None:
83
82
  if plugin_config_path := getattr(args, "plugin_config", None):
84
83
  try:
85
84
  with open(plugin_config_path, encoding="utf-8") as file:
86
- yaml_config: Optional[dict[str, Any]] = yaml.safe_load(file)
85
+ yaml_config: dict[str, Any] | None = yaml.safe_load(file)
87
86
  if yaml_config is None or EXEC_PLUGIN_SECTION not in yaml_config:
88
87
  raise ValueError(f"Missing '{EXEC_PLUGIN_SECTION}' section.")
89
88
  plugin_config = yaml_config[EXEC_PLUGIN_SECTION]
@@ -15,5 +15,57 @@
15
15
  """Constants for Flower infrastructure."""
16
16
 
17
17
 
18
+ from __future__ import annotations
19
+
20
+ from flwr.common.constant import FLWR_DIR
21
+
18
22
  # Top-level key in YAML config for exec plugin settings
19
23
  EXEC_PLUGIN_SECTION = "exec_plugin"
24
+
25
+ # Flower in-memory Python-based database name
26
+ FLWR_IN_MEMORY_DB_NAME = ":flwr-in-memory:"
27
+
28
+ # Constants for Hub
29
+ APP_ID_PATTERN = r"^@[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
30
+ APP_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
31
+ PLATFORM_API_URL = "https://api.flower.ai/v1"
32
+
33
+ # Specification for app publishing
34
+ APP_PUBLISH_INCLUDE_PATTERNS = (
35
+ "**/*.py",
36
+ "**/*.toml",
37
+ "**/*.md",
38
+ )
39
+ APP_PUBLISH_EXCLUDE_PATTERNS = FAB_EXCLUDE_PATTERNS = (
40
+ f"{FLWR_DIR}/**", # Exclude the .flwr directory
41
+ "**/__pycache__/**",
42
+ )
43
+ MAX_TOTAL_BYTES = 10 * 1024 * 1024 # 10 MB
44
+ MAX_FILE_BYTES = 1 * 1024 * 1024 # 1 MB
45
+ MAX_FILE_COUNT = 1000
46
+ MAX_DIR_DEPTH = 10 # relative depth (number of parts in relpath)
47
+ UTF8 = "utf-8"
48
+ MIME_MAP = {
49
+ ".py": "text/x-python; charset=utf-8",
50
+ ".md": "text/markdown; charset=utf-8",
51
+ ".toml": "application/toml; charset=utf-8",
52
+ }
53
+
54
+ # Constants for federations
55
+ NOOP_FEDERATION = "default"
56
+
57
+ # Constants for exit handling
58
+ FORCE_EXIT_TIMEOUT_SECONDS = 5 # Used in `flwr_exit` function
59
+
60
+
61
+ class NodeStatus:
62
+ """Event log writer types."""
63
+
64
+ REGISTERED = "registered"
65
+ ONLINE = "online"
66
+ OFFLINE = "offline"
67
+ UNREGISTERED = "unregistered"
68
+
69
+ def __new__(cls) -> NodeStatus:
70
+ """Prevent instantiation."""
71
+ raise TypeError(f"{cls.__name__} cannot be instantiated.")
@@ -16,14 +16,20 @@
16
16
 
17
17
 
18
18
  from abc import ABC, abstractmethod
19
- from typing import Optional
19
+
20
+ from ..object_store import ObjectStore
20
21
 
21
22
 
22
23
  class CoreState(ABC):
23
24
  """Abstract base class for core state."""
24
25
 
26
+ @property
27
+ @abstractmethod
28
+ def object_store(self) -> ObjectStore:
29
+ """Return the ObjectStore instance used by this CoreState."""
30
+
25
31
  @abstractmethod
26
- def create_token(self, run_id: int) -> Optional[str]:
32
+ def create_token(self, run_id: int) -> str | None:
27
33
  """Create a token for the given run ID.
28
34
 
29
35
  Parameters
@@ -66,7 +72,7 @@ class CoreState(ABC):
66
72
  """
67
73
 
68
74
  @abstractmethod
69
- def get_run_id_by_token(self, token: str) -> Optional[int]:
75
+ def get_run_id_by_token(self, token: str) -> int | None:
70
76
  """Get the run ID associated with a given token.
71
77
 
72
78
  Parameters
@@ -79,3 +85,18 @@ class CoreState(ABC):
79
85
  Optional[int]
80
86
  The run ID if the token is valid, otherwise None.
81
87
  """
88
+
89
+ @abstractmethod
90
+ def acknowledge_app_heartbeat(self, token: str) -> bool:
91
+ """Acknowledge an app heartbeat with the provided token.
92
+
93
+ Parameters
94
+ ----------
95
+ token : str
96
+ The token associated with the app.
97
+
98
+ Returns
99
+ -------
100
+ bool
101
+ True if the heartbeat is acknowledged successfully, False otherwise.
102
+ """
@@ -0,0 +1,138 @@
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
+ """In-memory CoreState implementation."""
16
+
17
+
18
+ import secrets
19
+ from dataclasses import dataclass
20
+ from threading import Lock
21
+
22
+ from flwr.common import now
23
+ from flwr.common.constant import (
24
+ FLWR_APP_TOKEN_LENGTH,
25
+ HEARTBEAT_DEFAULT_INTERVAL,
26
+ HEARTBEAT_PATIENCE,
27
+ )
28
+
29
+ from ..object_store import ObjectStore
30
+ from .corestate import CoreState
31
+
32
+
33
+ @dataclass
34
+ class TokenRecord:
35
+ """Record containing token and heartbeat information."""
36
+
37
+ token: str
38
+ active_until: float
39
+
40
+
41
+ class InMemoryCoreState(CoreState):
42
+ """In-memory CoreState implementation."""
43
+
44
+ def __init__(self, object_store: ObjectStore) -> None:
45
+ self._object_store = object_store
46
+ # Store run ID to token mapping and token to run ID mapping
47
+ self.token_store: dict[int, TokenRecord] = {}
48
+ self.token_to_run_id: dict[str, int] = {}
49
+ self.lock_token_store = Lock()
50
+
51
+ @property
52
+ def object_store(self) -> ObjectStore:
53
+ """Return the ObjectStore instance used by this CoreState."""
54
+ return self._object_store
55
+
56
+ def create_token(self, run_id: int) -> str | None:
57
+ """Create a token for the given run ID."""
58
+ token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
59
+ with self.lock_token_store:
60
+ if run_id in self.token_store:
61
+ return None # Token already created for this run ID
62
+
63
+ self.token_store[run_id] = TokenRecord(
64
+ token=token, active_until=now().timestamp() + HEARTBEAT_DEFAULT_INTERVAL
65
+ )
66
+ self.token_to_run_id[token] = run_id
67
+ return token
68
+
69
+ def verify_token(self, run_id: int, token: str) -> bool:
70
+ """Verify a token for the given run ID."""
71
+ self._cleanup_expired_tokens()
72
+ with self.lock_token_store:
73
+ record = self.token_store.get(run_id)
74
+ return record is not None and record.token == token
75
+
76
+ def delete_token(self, run_id: int) -> None:
77
+ """Delete the token for the given run ID."""
78
+ with self.lock_token_store:
79
+ record = self.token_store.pop(run_id, None)
80
+ if record is not None:
81
+ self.token_to_run_id.pop(record.token, None)
82
+
83
+ def get_run_id_by_token(self, token: str) -> int | None:
84
+ """Get the run ID associated with a given token."""
85
+ self._cleanup_expired_tokens()
86
+ with self.lock_token_store:
87
+ return self.token_to_run_id.get(token)
88
+
89
+ def acknowledge_app_heartbeat(self, token: str) -> bool:
90
+ """Acknowledge an app heartbeat with the provided token."""
91
+ # Clean up expired tokens
92
+ self._cleanup_expired_tokens()
93
+
94
+ with self.lock_token_store:
95
+ # Return False if token is not found
96
+ if token not in self.token_to_run_id:
97
+ return False
98
+
99
+ # Get the run_id and update heartbeat info
100
+ run_id = self.token_to_run_id[token]
101
+ record = self.token_store[run_id]
102
+ current = now().timestamp()
103
+ record.active_until = (
104
+ current + HEARTBEAT_PATIENCE * HEARTBEAT_DEFAULT_INTERVAL
105
+ )
106
+ return True
107
+
108
+ def _cleanup_expired_tokens(self) -> None:
109
+ """Remove expired tokens and perform additional cleanup.
110
+
111
+ This method is called before token operations to ensure integrity.
112
+ Subclasses can override `_on_tokens_expired` to add custom cleanup logic.
113
+ """
114
+ with self.lock_token_store:
115
+ current = now().timestamp()
116
+ expired_records: list[tuple[int, float]] = []
117
+ for run_id, record in list(self.token_store.items()):
118
+ if record.active_until < current:
119
+ expired_records.append((run_id, record.active_until))
120
+ # Remove from both stores
121
+ del self.token_store[run_id]
122
+ self.token_to_run_id.pop(record.token, None)
123
+
124
+ # Hook for subclasses
125
+ if expired_records:
126
+ self._on_tokens_expired(expired_records)
127
+
128
+ def _on_tokens_expired(self, expired_records: list[tuple[int, float]]) -> None:
129
+ """Handle cleanup of expired tokens.
130
+
131
+ Override in subclasses to add custom cleanup logic.
132
+
133
+ Parameters
134
+ ----------
135
+ expired_records : list[tuple[int, float]]
136
+ List of tuples containing (run_id, active_until timestamp)
137
+ for expired tokens.
138
+ """