flwr 1.23.0__py3-none-any.whl → 1.25.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. flwr/__init__.py +16 -5
  2. flwr/app/error.py +2 -2
  3. flwr/app/exception.py +3 -3
  4. flwr/cli/app.py +19 -0
  5. flwr/cli/{new/templates → app_cmd}/__init__.py +9 -1
  6. flwr/cli/app_cmd/publish.py +285 -0
  7. flwr/cli/app_cmd/review.py +262 -0
  8. flwr/cli/auth_plugin/auth_plugin.py +4 -5
  9. flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
  10. flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
  11. flwr/cli/build.py +60 -18
  12. flwr/cli/cli_account_auth_interceptor.py +24 -7
  13. flwr/cli/config_utils.py +101 -13
  14. flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +10 -1
  15. flwr/cli/federation/ls.py +140 -0
  16. flwr/cli/federation/show.py +318 -0
  17. flwr/cli/install.py +91 -13
  18. flwr/cli/log.py +52 -9
  19. flwr/cli/login/login.py +7 -4
  20. flwr/cli/ls.py +211 -130
  21. flwr/cli/new/new.py +123 -331
  22. flwr/cli/pull.py +10 -5
  23. flwr/cli/run/run.py +71 -29
  24. flwr/cli/run_utils.py +148 -0
  25. flwr/cli/stop.py +26 -8
  26. flwr/cli/supernode/ls.py +25 -12
  27. flwr/cli/supernode/register.py +9 -4
  28. flwr/cli/supernode/unregister.py +5 -3
  29. flwr/cli/utils.py +239 -16
  30. flwr/client/__init__.py +1 -1
  31. flwr/client/dpfedavg_numpy_client.py +4 -1
  32. flwr/client/grpc_adapter_client/connection.py +8 -9
  33. flwr/client/grpc_rere_client/connection.py +16 -14
  34. flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
  35. flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
  36. flwr/client/message_handler/message_handler.py +2 -2
  37. flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
  38. flwr/client/numpy_client.py +1 -1
  39. flwr/client/rest_client/connection.py +18 -18
  40. flwr/client/run_info_store.py +4 -5
  41. flwr/client/typing.py +1 -1
  42. flwr/clientapp/client_app.py +9 -10
  43. flwr/clientapp/mod/centraldp_mods.py +16 -17
  44. flwr/clientapp/mod/localdp_mod.py +8 -9
  45. flwr/clientapp/typing.py +1 -1
  46. flwr/clientapp/utils.py +3 -3
  47. flwr/common/address.py +1 -2
  48. flwr/common/args.py +3 -4
  49. flwr/common/config.py +13 -16
  50. flwr/common/constant.py +5 -2
  51. flwr/common/differential_privacy.py +3 -4
  52. flwr/common/event_log_plugin/event_log_plugin.py +3 -4
  53. flwr/common/exit/exit.py +15 -2
  54. flwr/common/exit/exit_code.py +19 -0
  55. flwr/common/exit/exit_handler.py +6 -2
  56. flwr/common/exit/signal_handler.py +5 -5
  57. flwr/common/grpc.py +6 -6
  58. flwr/common/inflatable_protobuf_utils.py +1 -1
  59. flwr/common/inflatable_utils.py +38 -21
  60. flwr/common/logger.py +19 -19
  61. flwr/common/message.py +4 -4
  62. flwr/common/object_ref.py +7 -7
  63. flwr/common/record/array.py +3 -3
  64. flwr/common/record/arrayrecord.py +18 -30
  65. flwr/common/record/configrecord.py +3 -3
  66. flwr/common/record/recorddict.py +5 -5
  67. flwr/common/record/typeddict.py +9 -2
  68. flwr/common/recorddict_compat.py +7 -10
  69. flwr/common/retry_invoker.py +20 -20
  70. flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
  71. flwr/common/serde.py +11 -4
  72. flwr/common/serde_utils.py +2 -2
  73. flwr/common/telemetry.py +9 -5
  74. flwr/common/typing.py +58 -37
  75. flwr/compat/client/app.py +38 -37
  76. flwr/compat/client/grpc_client/connection.py +11 -11
  77. flwr/compat/server/app.py +5 -6
  78. flwr/proto/appio_pb2.py +13 -3
  79. flwr/proto/appio_pb2.pyi +134 -65
  80. flwr/proto/appio_pb2_grpc.py +20 -0
  81. flwr/proto/appio_pb2_grpc.pyi +27 -0
  82. flwr/proto/clientappio_pb2.py +17 -7
  83. flwr/proto/clientappio_pb2.pyi +15 -0
  84. flwr/proto/clientappio_pb2_grpc.py +206 -40
  85. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  86. flwr/proto/control_pb2.py +71 -52
  87. flwr/proto/control_pb2.pyi +277 -111
  88. flwr/proto/control_pb2_grpc.py +249 -40
  89. flwr/proto/control_pb2_grpc.pyi +185 -52
  90. flwr/proto/error_pb2.py +13 -3
  91. flwr/proto/error_pb2.pyi +24 -6
  92. flwr/proto/error_pb2_grpc.py +20 -0
  93. flwr/proto/error_pb2_grpc.pyi +27 -0
  94. flwr/proto/fab_pb2.py +14 -4
  95. flwr/proto/fab_pb2.pyi +59 -31
  96. flwr/proto/fab_pb2_grpc.py +20 -0
  97. flwr/proto/fab_pb2_grpc.pyi +27 -0
  98. flwr/proto/federation_pb2.py +38 -0
  99. flwr/proto/federation_pb2.pyi +56 -0
  100. flwr/proto/federation_pb2_grpc.py +24 -0
  101. flwr/proto/federation_pb2_grpc.pyi +31 -0
  102. flwr/proto/fleet_pb2.py +24 -14
  103. flwr/proto/fleet_pb2.pyi +141 -61
  104. flwr/proto/fleet_pb2_grpc.py +189 -48
  105. flwr/proto/fleet_pb2_grpc.pyi +175 -61
  106. flwr/proto/grpcadapter_pb2.py +14 -4
  107. flwr/proto/grpcadapter_pb2.pyi +38 -16
  108. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  109. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  110. flwr/proto/heartbeat_pb2.py +17 -7
  111. flwr/proto/heartbeat_pb2.pyi +51 -22
  112. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  113. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  114. flwr/proto/log_pb2.py +13 -3
  115. flwr/proto/log_pb2.pyi +34 -11
  116. flwr/proto/log_pb2_grpc.py +20 -0
  117. flwr/proto/log_pb2_grpc.pyi +27 -0
  118. flwr/proto/message_pb2.py +15 -5
  119. flwr/proto/message_pb2.pyi +154 -86
  120. flwr/proto/message_pb2_grpc.py +20 -0
  121. flwr/proto/message_pb2_grpc.pyi +27 -0
  122. flwr/proto/node_pb2.py +15 -5
  123. flwr/proto/node_pb2.pyi +50 -25
  124. flwr/proto/node_pb2_grpc.py +20 -0
  125. flwr/proto/node_pb2_grpc.pyi +27 -0
  126. flwr/proto/recorddict_pb2.py +13 -3
  127. flwr/proto/recorddict_pb2.pyi +184 -107
  128. flwr/proto/recorddict_pb2_grpc.py +20 -0
  129. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  130. flwr/proto/run_pb2.py +40 -31
  131. flwr/proto/run_pb2.pyi +158 -84
  132. flwr/proto/run_pb2_grpc.py +20 -0
  133. flwr/proto/run_pb2_grpc.pyi +27 -0
  134. flwr/proto/serverappio_pb2.py +13 -3
  135. flwr/proto/serverappio_pb2.pyi +32 -8
  136. flwr/proto/serverappio_pb2_grpc.py +246 -65
  137. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  138. flwr/proto/simulationio_pb2.py +16 -8
  139. flwr/proto/simulationio_pb2.pyi +15 -0
  140. flwr/proto/simulationio_pb2_grpc.py +162 -41
  141. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  142. flwr/proto/transport_pb2.py +20 -10
  143. flwr/proto/transport_pb2.pyi +249 -160
  144. flwr/proto/transport_pb2_grpc.py +35 -4
  145. flwr/proto/transport_pb2_grpc.pyi +38 -8
  146. flwr/server/app.py +39 -17
  147. flwr/server/client_manager.py +4 -5
  148. flwr/server/client_proxy.py +10 -11
  149. flwr/server/compat/app.py +4 -5
  150. flwr/server/compat/app_utils.py +2 -1
  151. flwr/server/compat/grid_client_proxy.py +10 -12
  152. flwr/server/compat/legacy_context.py +3 -4
  153. flwr/server/fleet_event_log_interceptor.py +2 -1
  154. flwr/server/grid/grid.py +2 -3
  155. flwr/server/grid/grpc_grid.py +10 -8
  156. flwr/server/grid/inmemory_grid.py +4 -4
  157. flwr/server/run_serverapp.py +2 -3
  158. flwr/server/server.py +34 -39
  159. flwr/server/server_app.py +7 -8
  160. flwr/server/server_config.py +1 -2
  161. flwr/server/serverapp/app.py +34 -28
  162. flwr/server/serverapp_components.py +4 -5
  163. flwr/server/strategy/aggregate.py +9 -8
  164. flwr/server/strategy/bulyan.py +13 -11
  165. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  166. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  167. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  168. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  169. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  170. flwr/server/strategy/fedadagrad.py +18 -14
  171. flwr/server/strategy/fedadam.py +16 -14
  172. flwr/server/strategy/fedavg.py +16 -17
  173. flwr/server/strategy/fedavg_android.py +15 -15
  174. flwr/server/strategy/fedavgm.py +21 -18
  175. flwr/server/strategy/fedmedian.py +2 -3
  176. flwr/server/strategy/fedopt.py +11 -10
  177. flwr/server/strategy/fedprox.py +10 -9
  178. flwr/server/strategy/fedtrimmedavg.py +12 -11
  179. flwr/server/strategy/fedxgb_bagging.py +13 -11
  180. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  181. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  182. flwr/server/strategy/fedyogi.py +16 -14
  183. flwr/server/strategy/krum.py +12 -11
  184. flwr/server/strategy/qfedavg.py +16 -15
  185. flwr/server/strategy/strategy.py +6 -9
  186. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
  187. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  188. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  189. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  190. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  191. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
  192. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
  193. flwr/server/superlink/fleet/message_handler/message_handler.py +75 -30
  194. flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
  195. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  196. flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
  197. flwr/server/superlink/fleet/vce/vce_api.py +15 -9
  198. flwr/server/superlink/linkstate/in_memory_linkstate.py +148 -149
  199. flwr/server/superlink/linkstate/linkstate.py +91 -43
  200. flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
  201. flwr/server/superlink/linkstate/sqlite_linkstate.py +502 -436
  202. flwr/server/superlink/linkstate/utils.py +6 -6
  203. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  204. flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
  205. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  206. flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
  207. flwr/server/superlink/utils.py +4 -6
  208. flwr/server/typing.py +1 -1
  209. flwr/server/utils/tensorboard.py +15 -8
  210. flwr/server/workflow/default_workflows.py +5 -5
  211. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  212. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
  213. flwr/serverapp/strategy/bulyan.py +16 -15
  214. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  215. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  216. flwr/serverapp/strategy/fedadagrad.py +10 -11
  217. flwr/serverapp/strategy/fedadam.py +10 -11
  218. flwr/serverapp/strategy/fedavg.py +9 -10
  219. flwr/serverapp/strategy/fedavgm.py +17 -16
  220. flwr/serverapp/strategy/fedmedian.py +2 -2
  221. flwr/serverapp/strategy/fedopt.py +10 -11
  222. flwr/serverapp/strategy/fedprox.py +7 -8
  223. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  224. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  225. flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
  226. flwr/serverapp/strategy/fedyogi.py +9 -11
  227. flwr/serverapp/strategy/krum.py +7 -7
  228. flwr/serverapp/strategy/multikrum.py +9 -9
  229. flwr/serverapp/strategy/qfedavg.py +17 -16
  230. flwr/serverapp/strategy/strategy.py +6 -9
  231. flwr/serverapp/strategy/strategy_utils.py +7 -8
  232. flwr/simulation/app.py +46 -42
  233. flwr/simulation/legacy_app.py +12 -12
  234. flwr/simulation/ray_transport/ray_actor.py +10 -11
  235. flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
  236. flwr/simulation/run_simulation.py +43 -43
  237. flwr/simulation/simulationio_connection.py +4 -4
  238. flwr/supercore/cli/flower_superexec.py +3 -4
  239. flwr/supercore/constant.py +34 -1
  240. flwr/supercore/corestate/corestate.py +24 -3
  241. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  242. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  243. flwr/supercore/ffs/disk_ffs.py +1 -2
  244. flwr/supercore/ffs/ffs.py +1 -2
  245. flwr/supercore/ffs/ffs_factory.py +1 -2
  246. flwr/{common → supercore}/heartbeat.py +20 -25
  247. flwr/supercore/object_store/in_memory_object_store.py +1 -2
  248. flwr/supercore/object_store/object_store.py +1 -2
  249. flwr/supercore/object_store/object_store_factory.py +1 -2
  250. flwr/supercore/object_store/sqlite_object_store.py +8 -7
  251. flwr/supercore/primitives/asymmetric.py +1 -1
  252. flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
  253. flwr/supercore/sqlite_mixin.py +37 -34
  254. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  255. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  256. flwr/supercore/superexec/run_superexec.py +9 -13
  257. flwr/supercore/utils.py +190 -0
  258. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  259. flwr/superlink/auth_plugin/auth_plugin.py +6 -9
  260. flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
  261. flwr/{cli/new/templates/app → superlink/federation}/__init__.py +10 -1
  262. flwr/superlink/federation/federation_manager.py +64 -0
  263. flwr/superlink/federation/noop_federation_manager.py +71 -0
  264. flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
  265. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  266. flwr/superlink/servicer/control/control_grpc.py +7 -6
  267. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  268. flwr/superlink/servicer/control/control_servicer.py +190 -23
  269. flwr/supernode/cli/flower_supernode.py +58 -3
  270. flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
  271. flwr/supernode/nodestate/nodestate.py +52 -8
  272. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  273. flwr/supernode/runtime/run_clientapp.py +41 -22
  274. flwr/supernode/servicer/clientappio/clientappio_servicer.py +46 -10
  275. flwr/supernode/start_client_internal.py +165 -46
  276. {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/METADATA +9 -11
  277. flwr-1.25.0.dist-info/RECORD +393 -0
  278. flwr/cli/new/templates/app/.gitignore.tpl +0 -163
  279. flwr/cli/new/templates/app/LICENSE.tpl +0 -202
  280. flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
  281. flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
  282. flwr/cli/new/templates/app/README.md.tpl +0 -37
  283. flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
  284. flwr/cli/new/templates/app/code/__init__.py +0 -15
  285. flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
  286. flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
  287. flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
  288. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
  289. flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
  290. flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
  291. flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
  292. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
  293. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
  294. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
  295. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
  296. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
  297. flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
  298. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
  299. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
  300. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
  301. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
  302. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
  303. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
  304. flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
  305. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
  306. flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
  307. flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
  308. flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
  309. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
  310. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
  311. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
  312. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
  313. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
  314. flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
  315. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
  316. flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
  317. flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
  318. flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
  319. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
  320. flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
  321. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
  322. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
  323. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
  324. flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
  325. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
  326. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
  327. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
  328. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
  329. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
  330. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
  331. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
  332. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
  333. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
  334. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
  335. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
  336. flwr/supercore/object_store/utils.py +0 -43
  337. flwr-1.23.0.dist-info/RECORD +0 -439
  338. {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/WHEEL +0 -0
  339. {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/entry_points.txt +0 -0
@@ -15,23 +15,26 @@
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
 
32
35
  from flwr.client.grpc_adapter_client.connection import grpc_adapter
33
36
  from flwr.client.grpc_rere_client.connection import grpc_request_response
34
- from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
37
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Error, Message, RecordDict
35
38
  from flwr.common.address import parse_address
36
39
  from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
37
40
  from flwr.common.constant import (
@@ -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,
@@ -60,11 +69,18 @@ from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
60
69
  from flwr.supercore.ffs import Ffs, FfsFactory
61
70
  from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
62
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
+ )
63
77
  from flwr.supernode.nodestate import NodeState, NodeStateFactory
64
78
  from flwr.supernode.servicer.clientappio import ClientAppIoServicer
65
79
 
66
80
  DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
67
81
 
82
+ FAB_VERIFICATION_ERROR = Error(ErrorCode.INVALID_FAB, "The FAB could not be verified.")
83
+
68
84
 
69
85
  # pylint: disable=import-outside-toplevel
70
86
  # pylint: disable=too-many-branches
@@ -75,18 +91,19 @@ def start_client_internal(
75
91
  *,
76
92
  server_address: str,
77
93
  node_config: UserConfig,
78
- root_certificates: Optional[Union[bytes, str]] = None,
79
- insecure: Optional[bool] = None,
94
+ root_certificates: bytes | str | None = None,
95
+ insecure: bool | None = None,
80
96
  transport: str,
81
- authentication_keys: Optional[
82
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
83
- ] = None,
84
- max_retries: Optional[int] = None,
85
- max_wait_time: Optional[float] = None,
86
- flwr_path: Optional[Path] = None,
97
+ authentication_keys: (
98
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
99
+ ) = None,
100
+ max_retries: int | None = None,
101
+ max_wait_time: float | None = None,
102
+ flwr_path: Path | None = None,
87
103
  isolation: str = ISOLATION_MODE_SUBPROCESS,
88
104
  clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
89
- health_server_address: Optional[str] = None,
105
+ health_server_address: str | None = None,
106
+ trusted_entities: dict[str, str] | None = None,
90
107
  ) -> None:
91
108
  """Start a Flower client node which connects to a Flower server.
92
109
 
@@ -138,6 +155,10 @@ def start_client_internal(
138
155
  health_server_address : Optional[str] (default: None)
139
156
  The address of the health server. If `None` is provided, the health server will
140
157
  NOT be started.
158
+ trusted_entities : Optional[dict[str, str]] (default: None)
159
+ A dictionary mapping public key IDs to public keys.
160
+ Only apps verified by at least one of these
161
+ entities can run on a supernode.
141
162
  """
142
163
  if insecure is None:
143
164
  insecure = root_certificates is None
@@ -156,9 +177,9 @@ def start_client_internal(
156
177
  )
157
178
 
158
179
  # Initialize factories
159
- state_factory = NodeStateFactory()
160
- ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
161
180
  object_store_factory = ObjectStoreFactory()
181
+ state_factory = NodeStateFactory(objectstore_factory=object_store_factory)
182
+ ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
162
183
 
163
184
  # Launch ClientAppIo API server
164
185
  grpc_servers = []
@@ -218,6 +239,7 @@ def start_client_internal(
218
239
  ) = conn
219
240
  # Store node_id in state
220
241
  state.set_node_id(node_id)
242
+ log(INFO, "SuperNode ID: %s", node_id)
221
243
 
222
244
  # pylint: disable=too-many-nested-blocks
223
245
  while True:
@@ -233,6 +255,7 @@ def start_client_internal(
233
255
  get_fab=get_fab,
234
256
  pull_object=pull_object,
235
257
  confirm_message_received=confirm_message_received,
258
+ trusted_entities=trusted_entities,
236
259
  )
237
260
 
238
261
  # No message has been pulled therefore we can skip the push stage.
@@ -249,17 +272,34 @@ def start_client_internal(
249
272
  )
250
273
 
251
274
 
275
+ def _insert_message(msg: Message, state: NodeState, store: ObjectStore) -> None:
276
+ """Insert a message into the NodeState and ObjectStore."""
277
+ with no_object_id_recompute():
278
+ # Store message in state
279
+ msg.metadata.__dict__["_message_id"] = msg.object_id # Set message_id
280
+ state.store_message(msg)
281
+
282
+ # Preregister objects in ObjectStore
283
+ store.preregister(msg.metadata.run_id, get_object_tree(msg))
284
+
285
+ # Store all objects in ObjectStore
286
+ all_objects = get_all_nested_objects(msg)
287
+ for obj_id, obj in all_objects.items():
288
+ store.put(obj_id, obj.deflate())
289
+
290
+
252
291
  def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
253
292
  state: NodeState,
254
293
  ffs: Ffs,
255
294
  object_store: ObjectStore,
256
295
  node_config: UserConfig,
257
- receive: Callable[[], Optional[tuple[Message, ObjectTree]]],
296
+ receive: Callable[[], tuple[Message, ObjectTree] | None],
258
297
  get_run: Callable[[int], Run],
259
298
  get_fab: Callable[[str, int], Fab],
260
299
  pull_object: Callable[[int, str], bytes],
261
300
  confirm_message_received: Callable[[int, str], None],
262
- ) -> Optional[int]:
301
+ trusted_entities: dict[str, str] | None,
302
+ ) -> int | None:
263
303
  """Pull a message from the SuperLink and store it in the state.
264
304
 
265
305
  This function current returns None if no message is received,
@@ -267,6 +307,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
267
307
  This behavior will change in the future to return None after
268
308
  completing transition to the `NodeState`-based SuperNode.
269
309
  """
310
+ # pylint: disable=too-many-nested-blocks
270
311
  message = None
271
312
  try:
272
313
  # Pull message
@@ -287,7 +328,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
287
328
  log(INFO, "[RUN %s]", message.metadata.run_id)
288
329
  log(
289
330
  INFO,
290
- "Received: %s message %s",
331
+ "Receiving: %s message (ID: %s)",
291
332
  message.metadata.message_type,
292
333
  message.metadata.message_id,
293
334
  )
@@ -299,11 +340,32 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
299
340
  if (run_info := state.get_run(run_id)) is None:
300
341
  # Pull run info from SuperLink
301
342
  run_info = get_run(run_id)
302
- state.store_run(run_info)
303
343
 
304
344
  # Pull and store the FAB
305
345
  fab = get_fab(run_info.fab_hash, run_id)
306
- ffs.put(fab.content, {})
346
+
347
+ # Verify the received FAB
348
+ # FAB must be signed if trust entities provided
349
+ if trusted_entities:
350
+ if not fab.verifications.get("valid_license", ""):
351
+ log(
352
+ WARN,
353
+ "App verification is not supported by the connected SuperLink.",
354
+ )
355
+ else:
356
+ fab_verified = _verify_fab(fab, trusted_entities)
357
+ if not fab_verified:
358
+ # Insert an error message in the state
359
+ # when FAB verification fails
360
+ log(
361
+ ERROR,
362
+ "FAB verification failed: the provided trusted entities "
363
+ "could not verify the FAB. An error reply "
364
+ "has been generated.",
365
+ )
366
+ reply = Message(FAB_VERIFICATION_ERROR, reply_to=message)
367
+ _insert_message(reply, state, object_store)
368
+ return run_id
307
369
 
308
370
  # Initialize the context
309
371
  run_cfg = get_fused_config_from_fab(fab.content, run_info)
@@ -314,7 +376,11 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
314
376
  state=RecordDict(),
315
377
  run_config=run_cfg,
316
378
  )
379
+
380
+ # Store in the state
317
381
  state.store_context(run_ctx)
382
+ state.store_run(run_info)
383
+ ffs.put(fab.content, fab.verifications)
318
384
 
319
385
  # Preregister the object tree of the message
320
386
  obj_ids_to_pull = object_store.preregister(run_id, object_tree)
@@ -322,16 +388,27 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
322
388
  # Store the message in the state (note this message has no content)
323
389
  state.store_message(message)
324
390
 
325
- # Pull and store objects of the message in the ObjectStore
326
- obj_contents = pull_objects(
327
- obj_ids_to_pull,
328
- pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
329
- )
330
- for obj_id in list(obj_contents.keys()):
331
- object_store.put(obj_id, obj_contents.pop(obj_id))
391
+ try:
392
+ # Pull and store objects of the message in the ObjectStore
393
+ obj_contents = pull_objects(
394
+ obj_ids_to_pull,
395
+ pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
396
+ )
397
+ for obj_id in list(obj_contents.keys()):
398
+ object_store.put(obj_id, obj_contents.pop(obj_id))
332
399
 
333
- # Confirm that the message was received
334
- confirm_message_received(run_id, message.metadata.message_id)
400
+ # Confirm that the message was received
401
+ confirm_message_received(run_id, message.metadata.message_id)
402
+ log(INFO, "Received successfully")
403
+ except Exception as err: # pylint: disable=broad-except
404
+ log(
405
+ ERROR,
406
+ "Failed to receive message %s: %s",
407
+ message.metadata.message_id,
408
+ err,
409
+ )
410
+ state.delete_messages(message_ids=[message.metadata.message_id])
411
+ object_store.delete(message.metadata.message_id)
335
412
 
336
413
  except RunNotRunningException:
337
414
  if message is None:
@@ -355,7 +432,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
355
432
  def _push_messages(
356
433
  state: NodeState,
357
434
  object_store: ObjectStore,
358
- send: Callable[[Message, ObjectTree], set[str]],
435
+ send: Callable[[Message, ObjectTree, float], set[str]],
359
436
  push_object: Callable[[int, str, bytes], None],
360
437
  ) -> None:
361
438
  """Push reply messages to the SuperLink."""
@@ -378,8 +455,9 @@ def _push_messages(
378
455
  log(INFO, "[RUN %s]", message.metadata.run_id)
379
456
  log(
380
457
  INFO,
381
- "Sending: %s message",
458
+ "Sending: %s message (ID: %s)",
382
459
  message.metadata.message_type,
460
+ message.metadata.message_id,
383
461
  )
384
462
 
385
463
  # Get the object tree for the message
@@ -402,9 +480,12 @@ def _push_messages(
402
480
 
403
481
  # Send the message
404
482
  try:
405
- # Send the reply message with its ObjectTree
483
+ clientapp_runtime = state.get_message_processing_duration(
484
+ message_id=message.metadata.reply_to_message_id,
485
+ )
486
+ # Send the reply message with its ObjectTree and ClientApp runtime
406
487
  # Get the IDs of objects to send
407
- ids_obj_to_send = send(message, object_tree)
488
+ ids_obj_to_send = send(message, object_tree, clientapp_runtime)
408
489
 
409
490
  # Push object contents from the ObjectStore
410
491
  run_id = message.metadata.run_id
@@ -424,6 +505,13 @@ def _push_messages(
424
505
  message.metadata.run_id,
425
506
  message.metadata.message_id,
426
507
  )
508
+ except Exception as err: # pylint: disable=broad-except
509
+ log(
510
+ ERROR,
511
+ "Failed to send message %s: %s",
512
+ message.metadata.message_id,
513
+ err,
514
+ )
427
515
  finally:
428
516
  # Delete the message from the state
429
517
  state.delete_messages(
@@ -444,17 +532,17 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
444
532
  transport: str,
445
533
  server_address: str,
446
534
  insecure: bool,
447
- root_certificates: Optional[Union[bytes, str]] = None,
448
- authentication_keys: Optional[
449
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
450
- ] = None,
451
- max_retries: Optional[int] = None,
452
- max_wait_time: Optional[float] = None,
535
+ root_certificates: bytes | str | None = None,
536
+ authentication_keys: (
537
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
538
+ ) = None,
539
+ max_retries: int | None = None,
540
+ max_wait_time: float | None = None,
453
541
  ) -> Iterator[
454
542
  tuple[
455
543
  int,
456
- Callable[[], Optional[tuple[Message, ObjectTree]]],
457
- Callable[[Message, ObjectTree], set[str]],
544
+ Callable[[], tuple[Message, ObjectTree] | None],
545
+ Callable[[Message, ObjectTree, float], set[str]],
458
546
  Callable[[int], Run],
459
547
  Callable[[str, int], Fab],
460
548
  Callable[[int, str], bytes],
@@ -513,8 +601,8 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
513
601
 
514
602
 
515
603
  def _make_fleet_connection_retry_invoker(
516
- max_retries: Optional[int] = None,
517
- max_wait_time: Optional[float] = None,
604
+ max_retries: int | None = None,
605
+ max_wait_time: float | None = None,
518
606
  connection_error_type: type[Exception] = RpcError,
519
607
  ) -> RetryInvoker:
520
608
  """Create a retry invoker for fleet connection."""
@@ -533,7 +621,7 @@ def run_clientappio_api_grpc(
533
621
  state_factory: NodeStateFactory,
534
622
  ffs_factory: FfsFactory,
535
623
  objectstore_factory: ObjectStoreFactory,
536
- certificates: Optional[tuple[bytes, bytes, bytes]],
624
+ certificates: tuple[bytes, bytes, bytes] | None,
537
625
  ) -> grpc.Server:
538
626
  """Run ClientAppIo API gRPC server."""
539
627
  clientappio_servicer: grpc.Server = ClientAppIoServicer(
@@ -554,3 +642,34 @@ def run_clientappio_api_grpc(
554
642
  log(INFO, "Flower Deployment Runtime: Starting ClientAppIo API on %s", address)
555
643
  clientappio_grpc_server.start()
556
644
  return clientappio_grpc_server
645
+
646
+
647
+ def _verify_fab(fab: Fab, trusted_entities: dict[str, str]) -> bool:
648
+ """Verify a FAB using its verification data and the provided trusted entities.
649
+
650
+ The FAB is considered verified if at least one trusted entity matches the
651
+ information contained in its verification records.
652
+ """
653
+ verifications = fab.verifications
654
+ verif_full = {
655
+ k: json.loads(v) for k, v in verifications.items() if k != "valid_license"
656
+ }
657
+ fab_verified = False
658
+ for public_key_id, verif in verif_full.items():
659
+ if public_key_id in trusted_entities:
660
+ verifier_public_key = load_ssh_public_key(
661
+ trusted_entities[public_key_id].encode("utf-8")
662
+ )
663
+ message_to_verify = create_message_to_sign(
664
+ hashlib.sha256(fab.content).digest(),
665
+ verif["signed_at"],
666
+ )
667
+ assert isinstance(verifier_public_key, ed25519.Ed25519PublicKey)
668
+ if verify_signature(
669
+ verifier_public_key,
670
+ message_to_verify,
671
+ decode_base64url(verif["signature"]),
672
+ ):
673
+ fab_verified = True
674
+ break
675
+ return fab_verified
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr
3
- Version: 1.23.0
3
+ Version: 1.25.0
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
@@ -33,21 +32,22 @@ Provides-Extra: rest
33
32
  Provides-Extra: simulation
34
33
  Requires-Dist: click (<8.2.0)
35
34
  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)
35
+ Requires-Dist: grpcio (>=1.70.0,<2.0.0)
36
+ Requires-Dist: grpcio-health-checking (>=1.70.0,<2.0.0)
38
37
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
39
38
  Requires-Dist: numpy (>=1.26.0,<3.0.0)
40
39
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
41
- Requires-Dist: protobuf (>=4.21.6,<5.0.0)
40
+ Requires-Dist: protobuf (>=5.28.0,<7.0.0)
42
41
  Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
43
42
  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")
43
+ Requires-Dist: ray (==2.51.1) ; (python_version >= "3.10" and python_version < "3.13") and (extra == "simulation")
44
+ Requires-Dist: ray (==2.51.1) ; (sys_platform != "win32" and python_version == "3.13") and (extra == "simulation")
45
45
  Requires-Dist: requests (>=2.31.0,<3.0.0)
46
46
  Requires-Dist: rich (>=13.5.0,<14.0.0)
47
47
  Requires-Dist: starlette (>=0.45.2,<0.46.0) ; extra == "rest"
48
48
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
49
49
  Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
50
- Requires-Dist: typer (>=0.12.5,<0.13.0)
50
+ Requires-Dist: typer (>=0.12.5,<0.21.0)
51
51
  Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
52
52
  Project-URL: Documentation, https://flower.ai
53
53
  Project-URL: Homepage, https://flower.ai
@@ -180,7 +180,7 @@ Quickstart examples:
180
180
  - [Quickstart (Pandas)](https://github.com/adap/flower/tree/main/examples/quickstart-pandas)
181
181
  - [Quickstart (JAX)](https://github.com/adap/flower/tree/main/examples/quickstart-jax)
182
182
  - [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)
183
+ - [Quickstart (scikit-learn)](https://github.com/adap/flower/tree/main/examples/quickstart-sklearn)
184
184
  - [Quickstart (Android [TFLite])](https://github.com/adap/flower/tree/main/examples/android)
185
185
  - [Quickstart (iOS [CoreML])](https://github.com/adap/flower/tree/main/examples/ios)
186
186
  - [Quickstart (MLX)](https://github.com/adap/flower/tree/main/examples/quickstart-mlx)
@@ -197,10 +197,8 @@ Other [examples](https://github.com/adap/flower/tree/main/examples):
197
197
  - [Advanced Flower with TensorFlow/Keras](https://github.com/adap/flower/tree/main/examples/advanced-tensorflow)
198
198
  - [Advanced Flower with PyTorch](https://github.com/adap/flower/tree/main/examples/advanced-pytorch)
199
199
  - [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
200
  - [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-kaplan-meier-fitter)
202
201
  - [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
202
  - [Flower with a Tabular Dataset](https://github.com/adap/flower/tree/main/examples/fl-tabular)
205
203
 
206
204
  ## Community