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
@@ -35,7 +35,7 @@ from flwr.common.typing import Fab, Run
35
35
 
36
36
  # pylint: disable=E0611
37
37
  from flwr.proto import clientappio_pb2_grpc
38
- from flwr.proto.appio_pb2 import ( # pylint: disable=E0401
38
+ from flwr.proto.appio_pb2 import (
39
39
  ListAppsToLaunchRequest,
40
40
  ListAppsToLaunchResponse,
41
41
  PullAppInputsRequest,
@@ -49,6 +49,7 @@ from flwr.proto.appio_pb2 import ( # pylint: disable=E0401
49
49
  RequestTokenRequest,
50
50
  RequestTokenResponse,
51
51
  )
52
+ from flwr.proto.heartbeat_pb2 import SendAppHeartbeatRequest, SendAppHeartbeatResponse
52
53
  from flwr.proto.message_pb2 import (
53
54
  ConfirmMessageReceivedRequest,
54
55
  ConfirmMessageReceivedResponse,
@@ -57,12 +58,11 @@ from flwr.proto.message_pb2 import (
57
58
  PushObjectRequest,
58
59
  PushObjectResponse,
59
60
  )
60
- from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
61
+ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse
61
62
 
62
63
  # pylint: disable=E0601
63
64
  from flwr.supercore.ffs import FfsFactory
64
65
  from flwr.supercore.object_store import NoObjectInStoreError, ObjectStoreFactory
65
- from flwr.supercore.object_store.utils import store_mapping_and_register_objects
66
66
  from flwr.supernode.nodestate import NodeStateFactory
67
67
 
68
68
 
@@ -151,7 +151,24 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
151
151
  # Retrieve context, run and fab for this run
152
152
  context = cast(Context, state.get_context(run_id))
153
153
  run = cast(Run, state.get_run(run_id))
154
- fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
154
+
155
+ # Retrieve FAB from FFS
156
+ if result := ffs.get(run.fab_hash):
157
+ content, verifications = result
158
+ log(
159
+ DEBUG,
160
+ "Retrieved FAB: hash=%s, content_len=%d, verifications=%s",
161
+ run.fab_hash,
162
+ len(content),
163
+ verifications,
164
+ )
165
+ fab = Fab(run.fab_hash, content, verifications)
166
+ else:
167
+ context.abort(
168
+ grpc.StatusCode.NOT_FOUND,
169
+ f"FAB with hash {run.fab_hash} not found in FFS.",
170
+ )
171
+ raise RuntimeError("This line should never be reached.")
155
172
 
156
173
  return PullAppInputsResponse(
157
174
  context=context_to_proto(context),
@@ -206,6 +223,9 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
206
223
  # Retrieve message for this run
207
224
  message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
208
225
 
226
+ # Record message processing start time
227
+ state.record_message_processing_start(message_id=message.metadata.message_id)
228
+
209
229
  # Retrieve the object tree for the message
210
230
  object_tree = store.get_object_tree(message.metadata.message_id)
211
231
 
@@ -231,19 +251,37 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
231
251
  )
232
252
  raise RuntimeError("This line should never be reached.")
233
253
 
234
- # Save the message to the state
235
- state.store_message(message_from_proto(request.messages_list[0]))
254
+ # Record message processing end time
255
+ state.record_message_processing_end(
256
+ message_id=request.messages_list[0].metadata.reply_to_message_id
257
+ )
236
258
 
237
259
  # Store Message object to descendants mapping and preregister objects
238
- objects_to_push = store_mapping_and_register_objects(store, request=request)
260
+ objects_to_push: set[str] = set()
261
+ for object_tree in request.message_object_trees:
262
+ objects_to_push |= set(store.preregister(run_id, object_tree))
239
263
 
264
+ # Save the message to the state
265
+ state.store_message(message_from_proto(request.messages_list[0]))
240
266
  return PushAppMessagesResponse(objects_to_push=objects_to_push)
241
267
 
268
+ def SendAppHeartbeat(
269
+ self, request: SendAppHeartbeatRequest, context: grpc.ServicerContext
270
+ ) -> SendAppHeartbeatResponse:
271
+ """Handle a heartbeat from an app process."""
272
+ log(DEBUG, "ClientAppIoServicer.SendAppHeartbeat")
273
+ # Initialize state
274
+ state = self.state_factory.state()
275
+
276
+ # Acknowledge the heartbeat
277
+ success = state.acknowledge_app_heartbeat(request.token)
278
+ return SendAppHeartbeatResponse(success=success)
279
+
242
280
  def PushObject(
243
281
  self, request: PushObjectRequest, context: grpc.ServicerContext
244
282
  ) -> PushObjectResponse:
245
283
  """Push an object to the ObjectStore."""
246
- log(DEBUG, "ServerAppIoServicer.PushObject")
284
+ log(DEBUG, "ClientAppIoServicer.PushObject")
247
285
 
248
286
  # Init state and store
249
287
  store = self.objectstore_factory.store()
@@ -265,7 +303,7 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
265
303
  self, request: PullObjectRequest, context: grpc.ServicerContext
266
304
  ) -> PullObjectResponse:
267
305
  """Pull an object from the ObjectStore."""
268
- log(DEBUG, "ServerAppIoServicer.PullObject")
306
+ log(DEBUG, "ClientAppIoServicer.PullObject")
269
307
 
270
308
  # Init state and store
271
309
  store = self.objectstore_factory.store()
@@ -285,7 +323,7 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
285
323
  self, request: ConfirmMessageReceivedRequest, context: grpc.ServicerContext
286
324
  ) -> ConfirmMessageReceivedResponse:
287
325
  """Confirm message received."""
288
- log(DEBUG, "ServerAppIoServicer.ConfirmMessageReceived")
326
+ log(DEBUG, "ClientAppIoServicer.ConfirmMessageReceived")
289
327
 
290
328
  # Init state and store
291
329
  store = self.objectstore_factory.store()
@@ -15,24 +15,27 @@
15
15
  """Main loop for Flower SuperNode."""
16
16
 
17
17
 
18
+ import hashlib
19
+ import json
18
20
  import os
19
21
  import subprocess
20
22
  import time
21
- from collections.abc import Iterator
23
+ from collections.abc import Callable, Iterator
22
24
  from contextlib import contextmanager
23
25
  from functools import partial
24
- from logging import INFO
26
+ from logging import ERROR, INFO, WARN
25
27
  from pathlib import Path
26
- from typing import Callable, Optional, Union, cast
28
+ from typing import cast
27
29
 
28
30
  import grpc
29
- from cryptography.hazmat.primitives.asymmetric import ec
31
+ from cryptography.hazmat.primitives.asymmetric import ec, ed25519
32
+ from cryptography.hazmat.primitives.serialization.ssh import load_ssh_public_key
30
33
  from grpc import RpcError
31
34
 
35
+ from flwr.app.user_config import UserConfig
32
36
  from flwr.client.grpc_adapter_client.connection import grpc_adapter
33
37
  from flwr.client.grpc_rere_client.connection import grpc_request_response
34
- from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
35
- from flwr.common.address import parse_address
38
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Error, Message, RecordDict
36
39
  from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
37
40
  from flwr.common.constant import (
38
41
  CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
@@ -41,11 +44,17 @@ from flwr.common.constant import (
41
44
  TRANSPORT_TYPE_GRPC_RERE,
42
45
  TRANSPORT_TYPE_REST,
43
46
  TRANSPORT_TYPES,
47
+ ErrorCode,
44
48
  ExecPluginType,
45
49
  )
46
50
  from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
47
51
  from flwr.common.grpc import generic_create_grpc_server
48
- from flwr.common.inflatable import iterate_object_tree
52
+ from flwr.common.inflatable import (
53
+ get_all_nested_objects,
54
+ get_object_tree,
55
+ iterate_object_tree,
56
+ no_object_id_recompute,
57
+ )
49
58
  from flwr.common.inflatable_utils import (
50
59
  pull_objects,
51
60
  push_object_contents_from_iterable,
@@ -53,17 +62,26 @@ from flwr.common.inflatable_utils import (
53
62
  from flwr.common.logger import log
54
63
  from flwr.common.retry_invoker import RetryInvoker, _make_simple_grpc_retry_invoker
55
64
  from flwr.common.telemetry import EventType
56
- from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
65
+ from flwr.common.typing import Fab, Run, RunNotRunningException
57
66
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
58
67
  from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
68
+ from flwr.supercore.address import parse_address
59
69
  from flwr.supercore.ffs import Ffs, FfsFactory
60
70
  from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
61
71
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
72
+ from flwr.supercore.primitives.asymmetric_ed25519 import (
73
+ create_message_to_sign,
74
+ decode_base64url,
75
+ verify_signature,
76
+ )
77
+ from flwr.supercore.version import package_version
62
78
  from flwr.supernode.nodestate import NodeState, NodeStateFactory
63
79
  from flwr.supernode.servicer.clientappio import ClientAppIoServicer
64
80
 
65
81
  DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
66
82
 
83
+ FAB_VERIFICATION_ERROR = Error(ErrorCode.INVALID_FAB, "The FAB could not be verified.")
84
+
67
85
 
68
86
  # pylint: disable=import-outside-toplevel
69
87
  # pylint: disable=too-many-branches
@@ -74,18 +92,19 @@ def start_client_internal(
74
92
  *,
75
93
  server_address: str,
76
94
  node_config: UserConfig,
77
- root_certificates: Optional[Union[bytes, str]] = None,
78
- insecure: Optional[bool] = None,
95
+ root_certificates: bytes | str | None = None,
96
+ insecure: bool | None = None,
79
97
  transport: str,
80
- authentication_keys: Optional[
81
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
82
- ] = None,
83
- max_retries: Optional[int] = None,
84
- max_wait_time: Optional[float] = None,
85
- flwr_path: Optional[Path] = None,
98
+ authentication_keys: (
99
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
100
+ ) = None,
101
+ max_retries: int | None = None,
102
+ max_wait_time: float | None = None,
103
+ flwr_path: Path | None = None,
86
104
  isolation: str = ISOLATION_MODE_SUBPROCESS,
87
105
  clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
88
- health_server_address: Optional[str] = None,
106
+ health_server_address: str | None = None,
107
+ trusted_entities: dict[str, str] | None = None,
89
108
  ) -> None:
90
109
  """Start a Flower client node which connects to a Flower server.
91
110
 
@@ -137,14 +156,31 @@ def start_client_internal(
137
156
  health_server_address : Optional[str] (default: None)
138
157
  The address of the health server. If `None` is provided, the health server will
139
158
  NOT be started.
159
+ trusted_entities : Optional[dict[str, str]] (default: None)
160
+ A dictionary mapping public key IDs to public keys.
161
+ Only apps verified by at least one of these
162
+ entities can run on a supernode.
140
163
  """
141
164
  if insecure is None:
142
165
  insecure = root_certificates is None
143
166
 
167
+ # Insecure HTTP is incompatible with authentication
168
+ if insecure and authentication_keys is not None:
169
+ url_v = f"https://flower.ai/docs/framework/v{package_version}/en/"
170
+ page = "how-to-authenticate-supernodes.html"
171
+ flwr_exit(
172
+ ExitCode.SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED,
173
+ "Insecure connection is enabled, but the SuperNode's private key is "
174
+ "provided for authentication. SuperNode authentication requires a "
175
+ "secure TLS connection with the SuperLink. Please enable TLS by "
176
+ "providing the certificate via `--root-certificates`. Please refer "
177
+ f"to the Flower documentation for more information: {url_v}{page}",
178
+ )
179
+
144
180
  # Initialize factories
145
- state_factory = NodeStateFactory()
146
- ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
147
181
  object_store_factory = ObjectStoreFactory()
182
+ state_factory = NodeStateFactory(objectstore_factory=object_store_factory)
183
+ ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
148
184
 
149
185
  # Launch ClientAppIo API server
150
186
  grpc_servers = []
@@ -193,22 +229,18 @@ def start_client_internal(
193
229
  max_wait_time=max_wait_time,
194
230
  ) as conn:
195
231
  (
232
+ node_id,
196
233
  receive,
197
234
  send,
198
- create_node,
199
- _,
200
235
  get_run,
201
236
  get_fab,
202
237
  pull_object,
203
238
  push_object,
204
239
  confirm_message_received,
205
240
  ) = conn
206
-
207
- # Call create_node fn to register node
208
- # and store node_id in state
209
- if (node_id := create_node()) is None:
210
- raise ValueError("Failed to register SuperNode with the SuperLink")
241
+ # Store node_id in state
211
242
  state.set_node_id(node_id)
243
+ log(INFO, "SuperNode ID: %s", node_id)
212
244
 
213
245
  # pylint: disable=too-many-nested-blocks
214
246
  while True:
@@ -224,6 +256,7 @@ def start_client_internal(
224
256
  get_fab=get_fab,
225
257
  pull_object=pull_object,
226
258
  confirm_message_received=confirm_message_received,
259
+ trusted_entities=trusted_entities,
227
260
  )
228
261
 
229
262
  # No message has been pulled therefore we can skip the push stage.
@@ -240,17 +273,34 @@ def start_client_internal(
240
273
  )
241
274
 
242
275
 
276
+ def _insert_message(msg: Message, state: NodeState, store: ObjectStore) -> None:
277
+ """Insert a message into the NodeState and ObjectStore."""
278
+ with no_object_id_recompute():
279
+ # Store message in state
280
+ msg.metadata.__dict__["_message_id"] = msg.object_id # Set message_id
281
+ state.store_message(msg)
282
+
283
+ # Preregister objects in ObjectStore
284
+ store.preregister(msg.metadata.run_id, get_object_tree(msg))
285
+
286
+ # Store all objects in ObjectStore
287
+ all_objects = get_all_nested_objects(msg)
288
+ for obj_id, obj in all_objects.items():
289
+ store.put(obj_id, obj.deflate())
290
+
291
+
243
292
  def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
244
293
  state: NodeState,
245
294
  ffs: Ffs,
246
295
  object_store: ObjectStore,
247
296
  node_config: UserConfig,
248
- receive: Callable[[], Optional[tuple[Message, ObjectTree]]],
297
+ receive: Callable[[], tuple[Message, ObjectTree] | None],
249
298
  get_run: Callable[[int], Run],
250
299
  get_fab: Callable[[str, int], Fab],
251
300
  pull_object: Callable[[int, str], bytes],
252
301
  confirm_message_received: Callable[[int, str], None],
253
- ) -> Optional[int]:
302
+ trusted_entities: dict[str, str] | None,
303
+ ) -> int | None:
254
304
  """Pull a message from the SuperLink and store it in the state.
255
305
 
256
306
  This function current returns None if no message is received,
@@ -258,6 +308,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
258
308
  This behavior will change in the future to return None after
259
309
  completing transition to the `NodeState`-based SuperNode.
260
310
  """
311
+ # pylint: disable=too-many-nested-blocks
261
312
  message = None
262
313
  try:
263
314
  # Pull message
@@ -278,7 +329,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
278
329
  log(INFO, "[RUN %s]", message.metadata.run_id)
279
330
  log(
280
331
  INFO,
281
- "Received: %s message %s",
332
+ "Receiving: %s message (ID: %s)",
282
333
  message.metadata.message_type,
283
334
  message.metadata.message_id,
284
335
  )
@@ -290,11 +341,32 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
290
341
  if (run_info := state.get_run(run_id)) is None:
291
342
  # Pull run info from SuperLink
292
343
  run_info = get_run(run_id)
293
- state.store_run(run_info)
294
344
 
295
345
  # Pull and store the FAB
296
346
  fab = get_fab(run_info.fab_hash, run_id)
297
- ffs.put(fab.content, {})
347
+
348
+ # Verify the received FAB
349
+ # FAB must be signed if trust entities provided
350
+ if trusted_entities:
351
+ if not fab.verifications.get("valid_license", ""):
352
+ log(
353
+ WARN,
354
+ "App verification is not supported by the connected SuperLink.",
355
+ )
356
+ else:
357
+ fab_verified = _verify_fab(fab, trusted_entities)
358
+ if not fab_verified:
359
+ # Insert an error message in the state
360
+ # when FAB verification fails
361
+ log(
362
+ ERROR,
363
+ "FAB verification failed: the provided trusted entities "
364
+ "could not verify the FAB. An error reply "
365
+ "has been generated.",
366
+ )
367
+ reply = Message(FAB_VERIFICATION_ERROR, reply_to=message)
368
+ _insert_message(reply, state, object_store)
369
+ return run_id
298
370
 
299
371
  # Initialize the context
300
372
  run_cfg = get_fused_config_from_fab(fab.content, run_info)
@@ -305,7 +377,11 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
305
377
  state=RecordDict(),
306
378
  run_config=run_cfg,
307
379
  )
380
+
381
+ # Store in the state
308
382
  state.store_context(run_ctx)
383
+ state.store_run(run_info)
384
+ ffs.put(fab.content, fab.verifications)
309
385
 
310
386
  # Preregister the object tree of the message
311
387
  obj_ids_to_pull = object_store.preregister(run_id, object_tree)
@@ -313,16 +389,27 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
313
389
  # Store the message in the state (note this message has no content)
314
390
  state.store_message(message)
315
391
 
316
- # Pull and store objects of the message in the ObjectStore
317
- obj_contents = pull_objects(
318
- obj_ids_to_pull,
319
- pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
320
- )
321
- for obj_id in list(obj_contents.keys()):
322
- object_store.put(obj_id, obj_contents.pop(obj_id))
392
+ try:
393
+ # Pull and store objects of the message in the ObjectStore
394
+ obj_contents = pull_objects(
395
+ obj_ids_to_pull,
396
+ pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
397
+ )
398
+ for obj_id in list(obj_contents.keys()):
399
+ object_store.put(obj_id, obj_contents.pop(obj_id))
323
400
 
324
- # Confirm that the message was received
325
- confirm_message_received(run_id, message.metadata.message_id)
401
+ # Confirm that the message was received
402
+ confirm_message_received(run_id, message.metadata.message_id)
403
+ log(INFO, "Received successfully")
404
+ except Exception as err: # pylint: disable=broad-except
405
+ log(
406
+ ERROR,
407
+ "Failed to receive message %s: %s",
408
+ message.metadata.message_id,
409
+ err,
410
+ )
411
+ state.delete_messages(message_ids=[message.metadata.message_id])
412
+ object_store.delete(message.metadata.message_id)
326
413
 
327
414
  except RunNotRunningException:
328
415
  if message is None:
@@ -346,7 +433,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
346
433
  def _push_messages(
347
434
  state: NodeState,
348
435
  object_store: ObjectStore,
349
- send: Callable[[Message, ObjectTree], set[str]],
436
+ send: Callable[[Message, ObjectTree, float], set[str]],
350
437
  push_object: Callable[[int, str, bytes], None],
351
438
  ) -> None:
352
439
  """Push reply messages to the SuperLink."""
@@ -369,8 +456,9 @@ def _push_messages(
369
456
  log(INFO, "[RUN %s]", message.metadata.run_id)
370
457
  log(
371
458
  INFO,
372
- "Sending: %s message",
459
+ "Sending: %s message (ID: %s)",
373
460
  message.metadata.message_type,
461
+ message.metadata.message_id,
374
462
  )
375
463
 
376
464
  # Get the object tree for the message
@@ -393,9 +481,12 @@ def _push_messages(
393
481
 
394
482
  # Send the message
395
483
  try:
396
- # Send the reply message with its ObjectTree
484
+ clientapp_runtime = state.get_message_processing_duration(
485
+ message_id=message.metadata.reply_to_message_id,
486
+ )
487
+ # Send the reply message with its ObjectTree and ClientApp runtime
397
488
  # Get the IDs of objects to send
398
- ids_obj_to_send = send(message, object_tree)
489
+ ids_obj_to_send = send(message, object_tree, clientapp_runtime)
399
490
 
400
491
  # Push object contents from the ObjectStore
401
492
  run_id = message.metadata.run_id
@@ -415,6 +506,13 @@ def _push_messages(
415
506
  message.metadata.run_id,
416
507
  message.metadata.message_id,
417
508
  )
509
+ except Exception as err: # pylint: disable=broad-except
510
+ log(
511
+ ERROR,
512
+ "Failed to send message %s: %s",
513
+ message.metadata.message_id,
514
+ err,
515
+ )
418
516
  finally:
419
517
  # Delete the message from the state
420
518
  state.delete_messages(
@@ -435,18 +533,17 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
435
533
  transport: str,
436
534
  server_address: str,
437
535
  insecure: bool,
438
- root_certificates: Optional[Union[bytes, str]] = None,
439
- authentication_keys: Optional[
440
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
441
- ] = None,
442
- max_retries: Optional[int] = None,
443
- max_wait_time: Optional[float] = None,
536
+ root_certificates: bytes | str | None = None,
537
+ authentication_keys: (
538
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
539
+ ) = None,
540
+ max_retries: int | None = None,
541
+ max_wait_time: float | None = None,
444
542
  ) -> Iterator[
445
543
  tuple[
446
- Callable[[], Optional[tuple[Message, ObjectTree]]],
447
- Callable[[Message, ObjectTree], set[str]],
448
- Callable[[], Optional[int]],
449
- Callable[[], None],
544
+ int,
545
+ Callable[[], tuple[Message, ObjectTree] | None],
546
+ Callable[[Message, ObjectTree, float], set[str]],
450
547
  Callable[[int], Run],
451
548
  Callable[[str, int], Fab],
452
549
  Callable[[int, str], bytes],
@@ -505,8 +602,8 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
505
602
 
506
603
 
507
604
  def _make_fleet_connection_retry_invoker(
508
- max_retries: Optional[int] = None,
509
- max_wait_time: Optional[float] = None,
605
+ max_retries: int | None = None,
606
+ max_wait_time: float | None = None,
510
607
  connection_error_type: type[Exception] = RpcError,
511
608
  ) -> RetryInvoker:
512
609
  """Create a retry invoker for fleet connection."""
@@ -525,7 +622,7 @@ def run_clientappio_api_grpc(
525
622
  state_factory: NodeStateFactory,
526
623
  ffs_factory: FfsFactory,
527
624
  objectstore_factory: ObjectStoreFactory,
528
- certificates: Optional[tuple[bytes, bytes, bytes]],
625
+ certificates: tuple[bytes, bytes, bytes] | None,
529
626
  ) -> grpc.Server:
530
627
  """Run ClientAppIo API gRPC server."""
531
628
  clientappio_servicer: grpc.Server = ClientAppIoServicer(
@@ -546,3 +643,34 @@ def run_clientappio_api_grpc(
546
643
  log(INFO, "Flower Deployment Runtime: Starting ClientAppIo API on %s", address)
547
644
  clientappio_grpc_server.start()
548
645
  return clientappio_grpc_server
646
+
647
+
648
+ def _verify_fab(fab: Fab, trusted_entities: dict[str, str]) -> bool:
649
+ """Verify a FAB using its verification data and the provided trusted entities.
650
+
651
+ The FAB is considered verified if at least one trusted entity matches the
652
+ information contained in its verification records.
653
+ """
654
+ verifications = fab.verifications
655
+ verif_full = {
656
+ k: json.loads(v) for k, v in verifications.items() if k != "valid_license"
657
+ }
658
+ fab_verified = False
659
+ for public_key_id, verif in verif_full.items():
660
+ if public_key_id in trusted_entities:
661
+ verifier_public_key = load_ssh_public_key(
662
+ trusted_entities[public_key_id].encode("utf-8")
663
+ )
664
+ message_to_verify = create_message_to_sign(
665
+ hashlib.sha256(fab.content).digest(),
666
+ verif["signed_at"],
667
+ )
668
+ assert isinstance(verifier_public_key, ed25519.Ed25519PublicKey)
669
+ if verify_signature(
670
+ verifier_public_key,
671
+ message_to_verify,
672
+ decode_base64url(verif["signature"]),
673
+ ):
674
+ fab_verified = True
675
+ break
676
+ return fab_verified
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.23.0.dev20250930
3
+ Version: 1.26.0.dev20260121
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  License: Apache-2.0
6
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
7
7
  Author: The Flower Authors
8
8
  Author-email: hello@flower.ai
9
- Requires-Python: >=3.9.2,<4.0.0
9
+ Requires-Python: >=3.10,<4.0
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Intended Audience :: Science/Research
@@ -20,7 +20,6 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3 :: Only
23
- Classifier: Programming Language :: Python :: 3.9
24
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
24
  Classifier: Topic :: Scientific/Engineering
26
25
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -31,23 +30,25 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
30
  Classifier: Typing :: Typed
32
31
  Provides-Extra: rest
33
32
  Provides-Extra: simulation
33
+ Requires-Dist: SQLAlchemy (>=2.0.45,<3.0.0)
34
34
  Requires-Dist: click (<8.2.0)
35
35
  Requires-Dist: cryptography (>=44.0.1,<45.0.0)
36
- Requires-Dist: grpcio (>=1.62.3,<2.0.0,!=1.65.0)
37
- Requires-Dist: grpcio-health-checking (>=1.62.3,<2.0.0)
36
+ Requires-Dist: grpcio (>=1.70.0,<2.0.0)
37
+ Requires-Dist: grpcio-health-checking (>=1.70.0,<2.0.0)
38
38
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
39
39
  Requires-Dist: numpy (>=1.26.0,<3.0.0)
40
40
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
41
- Requires-Dist: protobuf (>=4.21.6,<5.0.0)
41
+ Requires-Dist: protobuf (>=5.28.0,<7.0.0)
42
42
  Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
43
43
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
44
- Requires-Dist: ray (==2.31.0) ; (python_version >= "3.9" and python_version < "3.13") and (extra == "simulation")
44
+ Requires-Dist: ray (==2.51.1) ; (python_version >= "3.10" and python_version < "3.13") and (extra == "simulation")
45
+ Requires-Dist: ray (==2.51.1) ; (sys_platform != "win32" and python_version == "3.13") and (extra == "simulation")
45
46
  Requires-Dist: requests (>=2.31.0,<3.0.0)
46
47
  Requires-Dist: rich (>=13.5.0,<14.0.0)
47
48
  Requires-Dist: starlette (>=0.45.2,<0.46.0) ; extra == "rest"
48
49
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
49
50
  Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
50
- Requires-Dist: typer (>=0.12.5,<0.13.0)
51
+ Requires-Dist: typer (>=0.12.5,<0.21.0)
51
52
  Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
52
53
  Project-URL: Documentation, https://flower.ai
53
54
  Project-URL: Homepage, https://flower.ai
@@ -180,7 +181,7 @@ Quickstart examples:
180
181
  - [Quickstart (Pandas)](https://github.com/adap/flower/tree/main/examples/quickstart-pandas)
181
182
  - [Quickstart (JAX)](https://github.com/adap/flower/tree/main/examples/quickstart-jax)
182
183
  - [Quickstart (MONAI)](https://github.com/adap/flower/tree/main/examples/quickstart-monai)
183
- - [Quickstart (scikit-learn)](https://github.com/adap/flower/tree/main/examples/sklearn-logreg-mnist)
184
+ - [Quickstart (scikit-learn)](https://github.com/adap/flower/tree/main/examples/quickstart-sklearn)
184
185
  - [Quickstart (Android [TFLite])](https://github.com/adap/flower/tree/main/examples/android)
185
186
  - [Quickstart (iOS [CoreML])](https://github.com/adap/flower/tree/main/examples/ios)
186
187
  - [Quickstart (MLX)](https://github.com/adap/flower/tree/main/examples/quickstart-mlx)
@@ -197,10 +198,8 @@ Other [examples](https://github.com/adap/flower/tree/main/examples):
197
198
  - [Advanced Flower with TensorFlow/Keras](https://github.com/adap/flower/tree/main/examples/advanced-tensorflow)
198
199
  - [Advanced Flower with PyTorch](https://github.com/adap/flower/tree/main/examples/advanced-pytorch)
199
200
  - [Comprehensive Flower+XGBoost](https://github.com/adap/flower/tree/main/examples/xgboost-comprehensive)
200
- - [Flower through Docker Compose and with Grafana dashboard](https://github.com/adap/flower/tree/main/examples/flower-via-docker-compose)
201
201
  - [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-kaplan-meier-fitter)
202
202
  - [Sample Level Privacy with Opacus](https://github.com/adap/flower/tree/main/examples/opacus)
203
- - [Sample Level Privacy with TensorFlow-Privacy](https://github.com/adap/flower/tree/main/examples/tensorflow-privacy)
204
203
  - [Flower with a Tabular Dataset](https://github.com/adap/flower/tree/main/examples/fl-tabular)
205
204
 
206
205
  ## Community