flwr-nightly 1.23.0.dev20250930__py3-none-any.whl → 1.26.0.dev20260121__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 (375) hide show
  1. flwr/__init__.py +17 -6
  2. flwr/app/__init__.py +4 -1
  3. flwr/app/error.py +2 -2
  4. flwr/app/exception.py +3 -3
  5. flwr/app/message_type.py +29 -0
  6. flwr/app/metadata.py +5 -2
  7. flwr/app/user_config.py +19 -0
  8. flwr/cli/app.py +62 -9
  9. flwr/cli/{new/templates/app/code → app_cmd}/__init__.py +9 -1
  10. flwr/cli/app_cmd/publish.py +285 -0
  11. flwr/cli/app_cmd/review.py +262 -0
  12. flwr/cli/auth_plugin/__init__.py +13 -6
  13. flwr/cli/auth_plugin/auth_plugin.py +26 -15
  14. flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
  15. flwr/cli/auth_plugin/oidc_cli_plugin.py +52 -32
  16. flwr/cli/build.py +166 -53
  17. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +27 -10
  18. flwr/cli/config/__init__.py +21 -0
  19. flwr/cli/config/ls.py +104 -0
  20. flwr/cli/config_migration.py +300 -0
  21. flwr/cli/config_utils.py +154 -13
  22. flwr/cli/constant.py +67 -0
  23. flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +8 -1
  24. flwr/cli/federation/ls.py +361 -0
  25. flwr/cli/flower_config.py +447 -0
  26. flwr/cli/install.py +91 -13
  27. flwr/cli/log.py +65 -36
  28. flwr/cli/login/login.py +41 -27
  29. flwr/cli/ls.py +232 -158
  30. flwr/cli/new/new.py +188 -244
  31. flwr/cli/pull.py +25 -34
  32. flwr/cli/run/run.py +106 -74
  33. flwr/cli/run_utils.py +148 -0
  34. flwr/cli/stop.py +46 -37
  35. flwr/cli/supernode/__init__.py +25 -0
  36. flwr/cli/supernode/ls.py +273 -0
  37. flwr/cli/supernode/register.py +190 -0
  38. flwr/cli/supernode/unregister.py +140 -0
  39. flwr/cli/typing.py +211 -0
  40. flwr/cli/utils.py +428 -80
  41. flwr/client/__init__.py +2 -1
  42. flwr/client/dpfedavg_numpy_client.py +4 -1
  43. flwr/client/grpc_adapter_client/connection.py +14 -17
  44. flwr/client/grpc_rere_client/connection.py +73 -43
  45. flwr/client/grpc_rere_client/grpc_adapter.py +35 -15
  46. flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
  47. flwr/client/message_handler/message_handler.py +4 -3
  48. flwr/client/mod/centraldp_mods.py +1 -1
  49. flwr/client/mod/localdp_mod.py +1 -1
  50. flwr/client/mod/secure_aggregation/secaggplus_mod.py +11 -9
  51. flwr/client/numpy_client.py +1 -1
  52. flwr/client/rest_client/connection.py +99 -54
  53. flwr/client/run_info_store.py +6 -6
  54. flwr/client/typing.py +1 -1
  55. flwr/clientapp/__init__.py +1 -2
  56. flwr/{client → clientapp}/client_app.py +11 -11
  57. flwr/clientapp/mod/centraldp_mods.py +16 -17
  58. flwr/clientapp/mod/localdp_mod.py +8 -9
  59. flwr/clientapp/typing.py +1 -1
  60. flwr/{client/clientapp → clientapp}/utils.py +4 -4
  61. flwr/common/__init__.py +3 -2
  62. flwr/common/args.py +3 -4
  63. flwr/common/config.py +15 -17
  64. flwr/common/constant.py +56 -28
  65. flwr/common/context.py +2 -1
  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 +16 -3
  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 +8 -7
  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 +5 -5
  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 +8 -5
  87. flwr/common/serde.py +22 -11
  88. flwr/common/serde_utils.py +2 -2
  89. flwr/common/telemetry.py +10 -6
  90. flwr/common/typing.py +65 -44
  91. flwr/compat/client/app.py +45 -47
  92. flwr/compat/client/grpc_client/connection.py +15 -14
  93. flwr/compat/common/constant.py +29 -0
  94. flwr/compat/server/app.py +6 -7
  95. flwr/proto/appio_pb2.py +13 -3
  96. flwr/proto/appio_pb2.pyi +134 -65
  97. flwr/proto/appio_pb2_grpc.py +20 -0
  98. flwr/proto/appio_pb2_grpc.pyi +27 -0
  99. flwr/proto/clientappio_pb2.py +17 -7
  100. flwr/proto/clientappio_pb2.pyi +15 -0
  101. flwr/proto/clientappio_pb2_grpc.py +206 -40
  102. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  103. flwr/proto/control_pb2.py +72 -40
  104. flwr/proto/control_pb2.pyi +319 -87
  105. flwr/proto/control_pb2_grpc.py +339 -28
  106. flwr/proto/control_pb2_grpc.pyi +209 -37
  107. flwr/proto/error_pb2.py +13 -3
  108. flwr/proto/error_pb2.pyi +24 -6
  109. flwr/proto/error_pb2_grpc.py +20 -0
  110. flwr/proto/error_pb2_grpc.pyi +27 -0
  111. flwr/proto/fab_pb2.py +24 -10
  112. flwr/proto/fab_pb2.pyi +68 -20
  113. flwr/proto/fab_pb2_grpc.py +20 -0
  114. flwr/proto/fab_pb2_grpc.pyi +27 -0
  115. flwr/proto/federation_pb2.py +38 -0
  116. flwr/proto/federation_pb2.pyi +56 -0
  117. flwr/proto/federation_pb2_grpc.py +24 -0
  118. flwr/proto/federation_pb2_grpc.pyi +31 -0
  119. flwr/proto/fleet_pb2.py +45 -27
  120. flwr/proto/fleet_pb2.pyi +190 -70
  121. flwr/proto/fleet_pb2_grpc.py +277 -66
  122. flwr/proto/fleet_pb2_grpc.pyi +201 -55
  123. flwr/proto/grpcadapter_pb2.py +14 -4
  124. flwr/proto/grpcadapter_pb2.pyi +38 -16
  125. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  126. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  127. flwr/proto/heartbeat_pb2.py +17 -7
  128. flwr/proto/heartbeat_pb2.pyi +51 -22
  129. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  130. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  131. flwr/proto/log_pb2.py +13 -3
  132. flwr/proto/log_pb2.pyi +34 -11
  133. flwr/proto/log_pb2_grpc.py +20 -0
  134. flwr/proto/log_pb2_grpc.pyi +27 -0
  135. flwr/proto/message_pb2.py +15 -5
  136. flwr/proto/message_pb2.pyi +154 -86
  137. flwr/proto/message_pb2_grpc.py +20 -0
  138. flwr/proto/message_pb2_grpc.pyi +27 -0
  139. flwr/proto/node_pb2.py +16 -4
  140. flwr/proto/node_pb2.pyi +77 -4
  141. flwr/proto/node_pb2_grpc.py +20 -0
  142. flwr/proto/node_pb2_grpc.pyi +27 -0
  143. flwr/proto/recorddict_pb2.py +13 -3
  144. flwr/proto/recorddict_pb2.pyi +184 -107
  145. flwr/proto/recorddict_pb2_grpc.py +20 -0
  146. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  147. flwr/proto/run_pb2.py +40 -31
  148. flwr/proto/run_pb2.pyi +158 -84
  149. flwr/proto/run_pb2_grpc.py +20 -0
  150. flwr/proto/run_pb2_grpc.pyi +27 -0
  151. flwr/proto/serverappio_pb2.py +13 -3
  152. flwr/proto/serverappio_pb2.pyi +32 -8
  153. flwr/proto/serverappio_pb2_grpc.py +246 -65
  154. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  155. flwr/proto/simulationio_pb2.py +16 -8
  156. flwr/proto/simulationio_pb2.pyi +15 -0
  157. flwr/proto/simulationio_pb2_grpc.py +162 -41
  158. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  159. flwr/proto/transport_pb2.py +20 -10
  160. flwr/proto/transport_pb2.pyi +249 -160
  161. flwr/proto/transport_pb2_grpc.py +35 -4
  162. flwr/proto/transport_pb2_grpc.pyi +38 -8
  163. flwr/server/app.py +175 -128
  164. flwr/server/client_manager.py +4 -5
  165. flwr/server/client_proxy.py +10 -11
  166. flwr/server/compat/app.py +4 -5
  167. flwr/server/compat/app_utils.py +2 -1
  168. flwr/server/compat/grid_client_proxy.py +12 -13
  169. flwr/server/compat/legacy_context.py +3 -4
  170. flwr/server/fleet_event_log_interceptor.py +2 -1
  171. flwr/server/grid/grid.py +2 -3
  172. flwr/server/grid/grpc_grid.py +12 -10
  173. flwr/server/grid/inmemory_grid.py +4 -4
  174. flwr/server/run_serverapp.py +2 -3
  175. flwr/server/server.py +34 -39
  176. flwr/server/server_app.py +7 -8
  177. flwr/server/server_config.py +1 -2
  178. flwr/server/serverapp/app.py +34 -28
  179. flwr/server/serverapp_components.py +4 -5
  180. flwr/server/strategy/aggregate.py +9 -8
  181. flwr/server/strategy/bulyan.py +13 -11
  182. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  183. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  184. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  185. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  186. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  187. flwr/server/strategy/fedadagrad.py +18 -14
  188. flwr/server/strategy/fedadam.py +16 -14
  189. flwr/server/strategy/fedavg.py +16 -17
  190. flwr/server/strategy/fedavg_android.py +15 -15
  191. flwr/server/strategy/fedavgm.py +21 -18
  192. flwr/server/strategy/fedmedian.py +2 -3
  193. flwr/server/strategy/fedopt.py +11 -10
  194. flwr/server/strategy/fedprox.py +10 -9
  195. flwr/server/strategy/fedtrimmedavg.py +12 -11
  196. flwr/server/strategy/fedxgb_bagging.py +13 -11
  197. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  198. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  199. flwr/server/strategy/fedyogi.py +16 -14
  200. flwr/server/strategy/krum.py +12 -11
  201. flwr/server/strategy/qfedavg.py +16 -15
  202. flwr/server/strategy/strategy.py +6 -9
  203. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +20 -9
  204. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  206. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  207. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  208. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
  209. flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -50
  210. flwr/server/superlink/fleet/message_handler/message_handler.py +141 -51
  211. flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
  212. flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
  213. flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
  214. flwr/server/superlink/fleet/vce/vce_api.py +32 -13
  215. flwr/server/superlink/linkstate/__init__.py +2 -0
  216. flwr/server/superlink/linkstate/in_memory_linkstate.py +293 -208
  217. flwr/server/superlink/linkstate/linkstate.py +176 -64
  218. flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
  219. flwr/server/superlink/linkstate/sql_linkstate.py +221 -0
  220. flwr/server/superlink/linkstate/sqlite_linkstate.py +743 -648
  221. flwr/server/superlink/linkstate/utils.py +11 -62
  222. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  223. flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
  224. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  225. flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
  226. flwr/server/superlink/utils.py +4 -6
  227. flwr/server/typing.py +1 -1
  228. flwr/server/utils/tensorboard.py +15 -8
  229. flwr/server/utils/validator.py +2 -3
  230. flwr/server/workflow/default_workflows.py +7 -6
  231. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  232. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +13 -11
  233. flwr/serverapp/strategy/bulyan.py +16 -15
  234. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  235. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  236. flwr/serverapp/strategy/fedadagrad.py +10 -11
  237. flwr/serverapp/strategy/fedadam.py +10 -11
  238. flwr/serverapp/strategy/fedavg.py +10 -11
  239. flwr/serverapp/strategy/fedavgm.py +17 -16
  240. flwr/serverapp/strategy/fedmedian.py +2 -2
  241. flwr/serverapp/strategy/fedopt.py +10 -11
  242. flwr/serverapp/strategy/fedprox.py +7 -8
  243. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  244. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  245. flwr/serverapp/strategy/fedxgb_cyclic.py +10 -10
  246. flwr/serverapp/strategy/fedyogi.py +9 -11
  247. flwr/serverapp/strategy/krum.py +7 -7
  248. flwr/serverapp/strategy/multikrum.py +9 -9
  249. flwr/serverapp/strategy/qfedavg.py +17 -16
  250. flwr/serverapp/strategy/strategy.py +6 -9
  251. flwr/serverapp/strategy/strategy_utils.py +7 -8
  252. flwr/simulation/app.py +46 -42
  253. flwr/simulation/legacy_app.py +12 -12
  254. flwr/simulation/ray_transport/ray_actor.py +11 -12
  255. flwr/simulation/ray_transport/ray_client_proxy.py +14 -19
  256. flwr/simulation/run_simulation.py +46 -44
  257. flwr/simulation/simulationio_connection.py +4 -4
  258. flwr/{common → supercore}/address.py +1 -37
  259. flwr/supercore/cli/flower_superexec.py +3 -4
  260. flwr/supercore/constant.py +69 -0
  261. flwr/supercore/corestate/corestate.py +24 -3
  262. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  263. flwr/supercore/corestate/sql_corestate.py +153 -0
  264. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  265. flwr/supercore/credential_store/__init__.py +33 -0
  266. flwr/supercore/credential_store/credential_store.py +34 -0
  267. flwr/supercore/credential_store/file_credential_store.py +76 -0
  268. flwr/{common → supercore}/date.py +0 -11
  269. flwr/supercore/ffs/disk_ffs.py +1 -2
  270. flwr/supercore/ffs/ffs.py +1 -2
  271. flwr/supercore/ffs/ffs_factory.py +1 -2
  272. flwr/{common → supercore}/heartbeat.py +20 -25
  273. flwr/supercore/object_store/in_memory_object_store.py +1 -6
  274. flwr/supercore/object_store/object_store.py +1 -2
  275. flwr/supercore/object_store/object_store_factory.py +27 -8
  276. flwr/supercore/object_store/sqlite_object_store.py +253 -0
  277. flwr/{cli/new/templates/app → supercore/primitives}/__init__.py +1 -1
  278. flwr/supercore/primitives/asymmetric.py +117 -0
  279. flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
  280. flwr/supercore/sql_mixin.py +292 -0
  281. flwr/supercore/sqlite_mixin.py +156 -0
  282. flwr/{client/clientapp → supercore/state}/__init__.py +2 -2
  283. flwr/supercore/state/schema/README.md +125 -0
  284. flwr/{cli/new/templates → supercore/state/schema}/__init__.py +2 -2
  285. flwr/supercore/state/schema/corestate_tables.py +36 -0
  286. flwr/supercore/state/schema/linkstate_tables.py +152 -0
  287. flwr/supercore/state/schema/objectstore_tables.py +90 -0
  288. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  289. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  290. flwr/supercore/superexec/run_superexec.py +9 -13
  291. flwr/supercore/utils.py +224 -0
  292. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  293. flwr/superlink/auth_plugin/__init__.py +5 -2
  294. flwr/superlink/auth_plugin/auth_plugin.py +20 -19
  295. flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
  296. flwr/superlink/federation/__init__.py +24 -0
  297. flwr/superlink/federation/federation_manager.py +64 -0
  298. flwr/superlink/federation/noop_federation_manager.py +71 -0
  299. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
  300. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  301. flwr/superlink/servicer/control/control_grpc.py +20 -17
  302. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  303. flwr/superlink/servicer/control/control_servicer.py +328 -68
  304. flwr/supernode/cli/flower_supernode.py +74 -26
  305. flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
  306. flwr/supernode/nodestate/nodestate.py +52 -8
  307. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  308. flwr/supernode/runtime/run_clientapp.py +43 -24
  309. flwr/supernode/servicer/clientappio/clientappio_servicer.py +48 -10
  310. flwr/supernode/start_client_internal.py +185 -57
  311. {flwr_nightly-1.23.0.dev20250930.dist-info → flwr_nightly-1.26.0.dev20260121.dist-info}/METADATA +10 -11
  312. flwr_nightly-1.26.0.dev20260121.dist-info/RECORD +411 -0
  313. flwr/cli/new/templates/app/.gitignore.tpl +0 -163
  314. flwr/cli/new/templates/app/LICENSE.tpl +0 -202
  315. flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
  316. flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
  317. flwr/cli/new/templates/app/README.md.tpl +0 -37
  318. flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
  319. flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
  320. flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
  321. flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
  322. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
  323. flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
  324. flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
  325. flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
  326. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
  327. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
  328. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
  329. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
  330. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
  331. flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
  332. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
  333. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
  334. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
  335. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
  336. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
  337. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
  338. flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
  339. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
  340. flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
  341. flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
  342. flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
  343. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
  344. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
  345. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
  346. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
  347. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
  348. flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
  349. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
  350. flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
  351. flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
  352. flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
  353. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
  354. flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
  355. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
  356. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
  357. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
  358. flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
  359. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
  360. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
  361. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
  362. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
  363. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
  364. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
  365. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
  366. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
  367. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
  368. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
  369. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
  370. flwr/common/pyproject.py +0 -42
  371. flwr/supercore/object_store/utils.py +0 -43
  372. flwr_nightly-1.23.0.dev20250930.dist-info/RECORD +0 -429
  373. /flwr/{common → supercore}/version.py +0 -0
  374. {flwr_nightly-1.23.0.dev20250930.dist-info → flwr_nightly-1.26.0.dev20260121.dist-info}/WHEEL +0 -0
  375. {flwr_nightly-1.23.0.dev20250930.dist-info → flwr_nightly-1.26.0.dev20260121.dist-info}/entry_points.txt +0 -0
@@ -17,19 +17,14 @@
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
22
+ from flwr.app.message_type import MessageType
23
23
  from flwr.client import ClientFnExt
24
- from flwr.client.client_app import ClientApp
25
24
  from flwr.client.run_info_store import DeprecatedRunInfoStore
25
+ from flwr.clientapp.client_app import ClientApp
26
26
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordDict, now
27
- from flwr.common.constant import (
28
- NUM_PARTITIONS_KEY,
29
- PARTITION_ID_KEY,
30
- MessageType,
31
- MessageTypeLegacy,
32
- )
27
+ from flwr.common.constant import NUM_PARTITIONS_KEY, PARTITION_ID_KEY, MessageTypeLegacy
33
28
  from flwr.common.logger import log
34
29
  from flwr.common.message import make_message
35
30
  from flwr.common.recorddict_compat import (
@@ -74,7 +69,7 @@ class RayActorClientProxy(ClientProxy):
74
69
  },
75
70
  )
76
71
 
77
- def _submit_job(self, message: Message, timeout: Optional[float]) -> Message:
72
+ def _submit_job(self, message: Message, timeout: float | None) -> Message:
78
73
  """Sumbit a message to the ActorPool."""
79
74
  run_id = message.metadata.run_id
80
75
 
@@ -114,8 +109,8 @@ class RayActorClientProxy(ClientProxy):
114
109
  self,
115
110
  recorddict: RecordDict,
116
111
  message_type: str,
117
- timeout: Optional[float],
118
- group_id: Optional[int],
112
+ timeout: float | None,
113
+ group_id: int | None,
119
114
  ) -> Message:
120
115
  """Wrap a RecordDict inside a Message."""
121
116
  return make_message(
@@ -136,8 +131,8 @@ class RayActorClientProxy(ClientProxy):
136
131
  def get_properties(
137
132
  self,
138
133
  ins: common.GetPropertiesIns,
139
- timeout: Optional[float],
140
- group_id: Optional[int],
134
+ timeout: float | None,
135
+ group_id: int | None,
141
136
  ) -> common.GetPropertiesRes:
142
137
  """Return client's properties."""
143
138
  recorddict = getpropertiesins_to_recorddict(ins)
@@ -155,8 +150,8 @@ class RayActorClientProxy(ClientProxy):
155
150
  def get_parameters(
156
151
  self,
157
152
  ins: common.GetParametersIns,
158
- timeout: Optional[float],
159
- group_id: Optional[int],
153
+ timeout: float | None,
154
+ group_id: int | None,
160
155
  ) -> common.GetParametersRes:
161
156
  """Return the current local model parameters."""
162
157
  recorddict = getparametersins_to_recorddict(ins)
@@ -172,7 +167,7 @@ class RayActorClientProxy(ClientProxy):
172
167
  return recorddict_to_getparametersres(message_out.content, keep_input=False)
173
168
 
174
169
  def fit(
175
- self, ins: common.FitIns, timeout: Optional[float], group_id: Optional[int]
170
+ self, ins: common.FitIns, timeout: float | None, group_id: int | None
176
171
  ) -> common.FitRes:
177
172
  """Train model parameters on the locally held dataset."""
178
173
  recorddict = fitins_to_recorddict(
@@ -190,7 +185,7 @@ class RayActorClientProxy(ClientProxy):
190
185
  return recorddict_to_fitres(message_out.content, keep_input=False)
191
186
 
192
187
  def evaluate(
193
- self, ins: common.EvaluateIns, timeout: Optional[float], group_id: Optional[int]
188
+ self, ins: common.EvaluateIns, timeout: float | None, group_id: int | None
194
189
  ) -> common.EvaluateRes:
195
190
  """Evaluate model parameters on the locally held dataset."""
196
191
  recorddict = evaluateins_to_recorddict(
@@ -210,8 +205,8 @@ class RayActorClientProxy(ClientProxy):
210
205
  def reconnect(
211
206
  self,
212
207
  ins: common.ReconnectIns,
213
- timeout: Optional[float],
214
- group_id: Optional[int],
208
+ timeout: float | None,
209
+ group_id: int | None,
215
210
  ) -> common.DisconnectRes:
216
211
  """Disconnect and (optionally) reconnect later."""
217
212
  return common.DisconnectRes(reason="") # Nothing to do here (yet)
@@ -26,11 +26,12 @@ 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
+ from flwr.app.user_config import UserConfig
31
32
  from flwr.cli.config_utils import load_and_validate
32
33
  from flwr.cli.utils import get_sha256_hash
33
- from flwr.client import ClientApp
34
+ from flwr.clientapp import ClientApp
34
35
  from flwr.common import Context, EventType, RecordDict, event, log, now
35
36
  from flwr.common.config import get_fused_config_from_dir, parse_config_args
36
37
  from flwr.common.constant import RUN_ID_NUM_BYTES, Status
@@ -39,7 +40,7 @@ from flwr.common.logger import (
39
40
  update_console_handler,
40
41
  warn_deprecated_feature_with_example,
41
42
  )
42
- from flwr.common.typing import Run, RunStatus, UserConfig
43
+ from flwr.common.typing import Run, RunStatus
43
44
  from flwr.server.grid import Grid, InMemoryGrid
44
45
  from flwr.server.run_serverapp import run as _run
45
46
  from flwr.server.server_app import ServerApp
@@ -51,6 +52,9 @@ from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
51
52
  from flwr.simulation.ray_transport.utils import (
52
53
  enable_tf_gpu_growth as enable_gpu_growth,
53
54
  )
55
+ from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME, NOOP_FEDERATION
56
+ from flwr.supercore.object_store import ObjectStoreFactory
57
+ from flwr.superlink.federation import NoOpFederationManager
54
58
 
55
59
 
56
60
  def _replace_keys(d: Any, match: str, target: str) -> Any:
@@ -98,12 +102,7 @@ def run_simulation_from_cli() -> None:
98
102
  _check_ray_support(args.backend)
99
103
 
100
104
  # 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)
105
+ backend_config = json.loads(args.backend_config)
107
106
 
108
107
  run_id = (
109
108
  generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
@@ -141,6 +140,7 @@ def run_simulation_from_cli() -> None:
141
140
 
142
141
  # Create run
143
142
  run = Run.create_empty(run_id)
143
+ run.federation = NOOP_FEDERATION
144
144
  run.override_config = override_config
145
145
 
146
146
  # Create Context
@@ -157,7 +157,7 @@ def run_simulation_from_cli() -> None:
157
157
  client_app_attr=client_app_attr,
158
158
  num_supernodes=args.num_supernodes,
159
159
  backend_name=args.backend,
160
- backend_config=backend_config_dict,
160
+ backend_config=backend_config,
161
161
  app_dir=args.app,
162
162
  run=run,
163
163
  enable_tf_gpu_growth=args.enable_tf_gpu_growth,
@@ -175,7 +175,7 @@ def run_simulation(
175
175
  client_app: ClientApp,
176
176
  num_supernodes: int,
177
177
  backend_name: str = "ray",
178
- backend_config: Optional[BackendConfig] = None,
178
+ backend_config: BackendConfig | None = None,
179
179
  enable_tf_gpu_growth: bool = False,
180
180
  verbose_logging: bool = False,
181
181
  ) -> None:
@@ -248,8 +248,8 @@ def run_simulation(
248
248
 
249
249
  # pylint: disable=too-many-arguments,too-many-positional-arguments
250
250
  def run_serverapp_th(
251
- server_app_attr: Optional[str],
252
- server_app: Optional[ServerApp],
251
+ server_app_attr: str | None,
252
+ server_app: ServerApp | None,
253
253
  server_app_context: Context,
254
254
  grid: Grid,
255
255
  app_dir: str,
@@ -266,8 +266,8 @@ def run_serverapp_th(
266
266
  exception_event: threading.Event,
267
267
  _grid: Grid,
268
268
  _server_app_dir: str,
269
- _server_app_attr: Optional[str],
270
- _server_app: Optional[ServerApp],
269
+ _server_app_attr: str | None,
270
+ _server_app: ServerApp | None,
271
271
  _ctx_queue: "Queue[Context]",
272
272
  ) -> None:
273
273
  """Run SeverApp, after check if GPU memory growth has to be set.
@@ -327,16 +327,18 @@ def _main_loop(
327
327
  enable_tf_gpu_growth: bool,
328
328
  run: Run,
329
329
  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,
330
+ flwr_dir: str | None = None,
331
+ client_app: ClientApp | None = None,
332
+ client_app_attr: str | None = None,
333
+ server_app: ServerApp | None = None,
334
+ server_app_attr: str | None = None,
335
+ server_app_context: Context | None = None,
336
336
  ) -> Context:
337
337
  """Start ServerApp on a separate thread, then launch Simulation Engine."""
338
338
  # Initialize StateFactory
339
- state_factory = LinkStateFactory(":flwr-in-memory-state:")
339
+ state_factory = LinkStateFactory(
340
+ FLWR_IN_MEMORY_DB_NAME, NoOpFederationManager(), ObjectStoreFactory()
341
+ )
340
342
 
341
343
  f_stop = threading.Event()
342
344
  # A Threading event to indicate if an exception was raised in the ServerApp thread
@@ -427,16 +429,16 @@ def _main_loop(
427
429
  def _run_simulation(
428
430
  num_supernodes: int,
429
431
  exit_event: EventType,
430
- client_app: Optional[ClientApp] = None,
431
- server_app: Optional[ServerApp] = None,
432
+ client_app: ClientApp | None = None,
433
+ server_app: ServerApp | None = None,
432
434
  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,
435
+ backend_config: BackendConfig | None = None,
436
+ client_app_attr: str | None = None,
437
+ server_app_attr: str | None = None,
438
+ server_app_context: Context | None = None,
437
439
  app_dir: str = "",
438
- flwr_dir: Optional[str] = None,
439
- run: Optional[Run] = None,
440
+ flwr_dir: str | None = None,
441
+ run: Run | None = None,
440
442
  enable_tf_gpu_growth: bool = False,
441
443
  verbose_logging: bool = False,
442
444
  is_app: bool = False,
@@ -444,29 +446,28 @@ def _run_simulation(
444
446
  """Launch the Simulation Engine."""
445
447
  if backend_config is None:
446
448
  backend_config = {}
449
+ elif backend_config:
450
+ # Backend config internally operates with `_` not with `-`
451
+ backend_config = cast(
452
+ BackendConfig, _replace_keys(backend_config, match="-", target="_")
453
+ )
454
+ log(DEBUG, "backend_config: %s", backend_config)
447
455
 
448
- if "init_args" not in backend_config:
449
- backend_config["init_args"] = {}
450
-
456
+ # Set default init_args if not passed
457
+ backend_config.setdefault("init_args", {})
451
458
  # 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
-
459
+ backend_config.setdefault("client_resources", {"num_cpus": 2, "num_gpus": 0})
455
460
  # Initialization of backend config to enable GPU growth globally when set
456
- if "actor" not in backend_config:
457
- backend_config["actor"] = {"tensorflow": 0}
461
+ backend_config.setdefault("actor", {"tensorflow": 0})
458
462
 
459
463
  # Set logging level
460
464
  logger = logging.getLogger("flwr")
461
465
  if verbose_logging:
462
466
  update_console_handler(level=DEBUG, timestamps=True, colored=True)
463
467
  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
- )
468
+ init_args = backend_config["init_args"]
469
+ init_args.setdefault("logging_level", WARNING)
470
+ init_args.setdefault("log_to_driver", True)
470
471
 
471
472
  if enable_tf_gpu_growth:
472
473
  # Check that Backend config has also enabled using GPU growth
@@ -482,6 +483,7 @@ def _run_simulation(
482
483
  if run is None:
483
484
  run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
484
485
  run = Run.create_empty(run_id=run_id)
486
+ run.federation = NOOP_FEDERATION
485
487
 
486
488
  args = (
487
489
  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
@@ -15,17 +15,13 @@
15
15
  """Flower IP address utils."""
16
16
 
17
17
 
18
- import re
19
18
  import socket
20
19
  from ipaddress import ip_address
21
- from typing import Optional
22
-
23
- import grpc
24
20
 
25
21
  IPV6: int = 6
26
22
 
27
23
 
28
- def parse_address(address: str) -> Optional[tuple[str, int, Optional[bool]]]:
24
+ def parse_address(address: str) -> tuple[str, int, bool | None] | None:
29
25
  """Parse an IP address into host, port, and version.
30
26
 
31
27
  Parameters
@@ -104,35 +100,3 @@ def is_port_in_use(address: str) -> bool:
104
100
  return True
105
101
 
106
102
  return False
107
-
108
-
109
- def get_ip_address_from_servicer_context(context: grpc.ServicerContext) -> str:
110
- """Extract the client's IPv4 or IPv6 address from the gRPC ServicerContext.
111
-
112
- Parameters
113
- ----------
114
- context : grpc.ServicerContext
115
- The gRPC ServicerContext object. The context.peer() returns a string like
116
- "ipv4:127.0.0.1:56789" for IPv4 and "ipv6:[2001:db8::1]:54321" for IPv6.
117
-
118
- Returns
119
- -------
120
- str
121
- If one of the format matches, the function will return the client's IP address,
122
- otherwise, it will raise a ValueError.
123
- """
124
- peer: str = context.peer()
125
- # Match IPv4: "ipv4:IP:port"
126
- ipv4_match = re.match(r"^ipv4:(?P<ip>[^:]+):", peer)
127
- if ipv4_match:
128
- return ipv4_match.group("ip")
129
-
130
- # Match IPv6: "ipv6:[IP]:port"
131
- ipv6_match = re.match(r"^ipv6:\[(?P<ip>[^\]]+)\]:", peer)
132
- if ipv6_match:
133
- return ipv6_match.group("ip")
134
-
135
- raise ValueError(
136
- f"Unsupported peer address format: {peer} for the transport protocol. "
137
- "The supported formats are ipv4:IP:port and ipv6:[IP]:port."
138
- )
@@ -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,74 @@
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
+ # Constants for message processing timing
61
+ MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS = 3600
62
+
63
+
64
+ # System message type
65
+ SYSTEM_MESSAGE_TYPE = "system"
66
+
67
+ # SQLite PRAGMA settings for optimal performance and correctness
68
+ SQLITE_PRAGMAS = (
69
+ ("journal_mode", "WAL"), # Enable Write-Ahead Logging for better concurrency
70
+ ("synchronous", "NORMAL"),
71
+ ("foreign_keys", "ON"),
72
+ ("cache_size", "-64000"), # 64MB cache
73
+ ("temp_store", "MEMORY"), # In-memory temp tables
74
+ ("mmap_size", "268435456"), # 256MB memory-mapped I/O
75
+ )
76
+
77
+
78
+ class NodeStatus:
79
+ """Event log writer types."""
80
+
81
+ REGISTERED = "registered"
82
+ ONLINE = "online"
83
+ OFFLINE = "offline"
84
+ UNREGISTERED = "unregistered"
85
+
86
+ def __new__(cls) -> NodeStatus:
87
+ """Prevent instantiation."""
88
+ 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
+ """