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
flwr/cli/utils.py CHANGED
@@ -18,15 +18,17 @@
18
18
  import hashlib
19
19
  import json
20
20
  import re
21
- from collections.abc import Iterator
21
+ from collections.abc import Callable, Iterable, Iterator
22
22
  from contextlib import contextmanager
23
23
  from pathlib import Path
24
- from typing import Any, Callable, Optional, Union, cast
24
+ from typing import Any, cast
25
25
 
26
26
  import grpc
27
+ import pathspec
27
28
  import typer
28
29
 
29
30
  from flwr.common.constant import (
31
+ ACCESS_TOKEN_KEY,
30
32
  AUTHN_TYPE_JSON_KEY,
31
33
  CREDENTIALS_DIR,
32
34
  FLWR_DIR,
@@ -36,6 +38,7 @@ from flwr.common.constant import (
36
38
  PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
37
39
  PUBLIC_KEY_NOT_VALID,
38
40
  PULL_UNFINISHED_RUN_MESSAGE,
41
+ REFRESH_TOKEN_KEY,
39
42
  RUN_ID_NOT_FOUND_MESSAGE,
40
43
  AuthnType,
41
44
  )
@@ -53,9 +56,24 @@ from .config_utils import validate_certificate_in_federation_config
53
56
  def prompt_text(
54
57
  text: str,
55
58
  predicate: Callable[[str], bool] = lambda _: True,
56
- default: Optional[str] = None,
59
+ default: str | None = None,
57
60
  ) -> str:
58
- """Ask user to enter text input."""
61
+ """Ask user to enter text input.
62
+
63
+ Parameters
64
+ ----------
65
+ text : str
66
+ The prompt text to display to the user.
67
+ predicate : Callable[[str], bool] (default: lambda _: True)
68
+ A function to validate the user input. Default accepts all non-empty strings.
69
+ default : str | None (default: None)
70
+ Default value to use if user presses enter without input.
71
+
72
+ Returns
73
+ -------
74
+ str
75
+ The validated user input.
76
+ """
59
77
  while True:
60
78
  result = typer.prompt(
61
79
  typer.style(f"\n💬 {text}", fg=typer.colors.MAGENTA, bold=True),
@@ -69,7 +87,20 @@ def prompt_text(
69
87
 
70
88
 
71
89
  def prompt_options(text: str, options: list[str]) -> str:
72
- """Ask user to select one of the given options and return the selected item."""
90
+ """Ask user to select one of the given options and return the selected item.
91
+
92
+ Parameters
93
+ ----------
94
+ text : str
95
+ The prompt text to display to the user.
96
+ options : list[str]
97
+ List of options to present to the user.
98
+
99
+ Returns
100
+ -------
101
+ str
102
+ The selected option from the list.
103
+ """
73
104
  # Turn options into a list with index as in " [ 0] quickstart-pytorch"
74
105
  options_formatted = [
75
106
  " [ "
@@ -127,9 +158,19 @@ def is_valid_project_name(name: str) -> bool:
127
158
  def sanitize_project_name(name: str) -> str:
128
159
  """Sanitize the given string to make it a valid Python project name.
129
160
 
130
- This version replaces spaces, dots, slashes, and underscores with dashes, removes
161
+ This function replaces spaces, dots, slashes, and underscores with dashes, removes
131
162
  any characters not allowed in Python project names, makes the string lowercase, and
132
163
  ensures it starts with a valid character.
164
+
165
+ Parameters
166
+ ----------
167
+ name : str
168
+ The project name to sanitize.
169
+
170
+ Returns
171
+ -------
172
+ str
173
+ The sanitized project name that is valid for Python projects.
133
174
  """
134
175
  # Replace whitespace with '_'
135
176
  name_with_hyphens = re.sub(r"[ ./_]", "-", name)
@@ -154,8 +195,19 @@ def sanitize_project_name(name: str) -> str:
154
195
  return sanitized_name
155
196
 
156
197
 
157
- def get_sha256_hash(file_path_or_int: Union[Path, int]) -> str:
158
- """Calculate the SHA-256 hash of a file."""
198
+ def get_sha256_hash(file_path_or_int: Path | int) -> str:
199
+ """Calculate the SHA-256 hash of a file or integer.
200
+
201
+ Parameters
202
+ ----------
203
+ file_path_or_int : Path | int
204
+ Either a path to a file to hash, or an integer to convert to string and hash.
205
+
206
+ Returns
207
+ -------
208
+ str
209
+ The SHA-256 hash as a hexadecimal string.
210
+ """
159
211
  sha256 = hashlib.sha256()
160
212
  if isinstance(file_path_or_int, Path):
161
213
  with open(file_path_or_int, "rb") as f:
@@ -214,6 +266,7 @@ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
214
266
  f"Please check the permissions of `{gitignore_path}` and try again.",
215
267
  fg=typer.colors.RED,
216
268
  bold=True,
269
+ err=True,
217
270
  )
218
271
  raise typer.Exit(code=1) from err
219
272
 
@@ -221,7 +274,18 @@ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
221
274
 
222
275
 
223
276
  def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
224
- """Check if account authentication is enabled in the federation config."""
277
+ """Check if account authentication is enabled in the federation config.
278
+
279
+ Parameters
280
+ ----------
281
+ federation_config : dict[str, Any]
282
+ The federation configuration dictionary.
283
+
284
+ Returns
285
+ -------
286
+ bool
287
+ True if account authentication is enabled, False otherwise.
288
+ """
225
289
  enabled: bool = federation_config.get("enable-user-auth", False)
226
290
  enabled |= federation_config.get("enable-account-auth", False)
227
291
  if "enable-user-auth" in federation_config:
@@ -235,7 +299,18 @@ def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
235
299
 
236
300
 
237
301
  def retrieve_authn_type(config_path: Path) -> str:
238
- """Retrieve the auth type from the config file or return NOOP if not found."""
302
+ """Retrieve the auth type from the config file or return NOOP if not found.
303
+
304
+ Parameters
305
+ ----------
306
+ config_path : Path
307
+ Path to the authentication configuration file.
308
+
309
+ Returns
310
+ -------
311
+ str
312
+ The authentication type string, or AuthnType.NOOP if not found.
313
+ """
239
314
  try:
240
315
  with config_path.open("r", encoding="utf-8") as file:
241
316
  json_file = json.load(file)
@@ -249,9 +324,31 @@ def load_cli_auth_plugin(
249
324
  root_dir: Path,
250
325
  federation: str,
251
326
  federation_config: dict[str, Any],
252
- authn_type: Optional[str] = None,
327
+ authn_type: str | None = None,
253
328
  ) -> CliAuthPlugin:
254
- """Load the CLI-side account auth plugin for the given authn type."""
329
+ """Load the CLI-side account auth plugin for the given authn type.
330
+
331
+ Parameters
332
+ ----------
333
+ root_dir : Path
334
+ Root directory of the Flower project.
335
+ federation : str
336
+ Name of the federation.
337
+ federation_config : dict[str, Any]
338
+ Federation configuration dictionary.
339
+ authn_type : str | None
340
+ Authentication type. If None, will be determined from config.
341
+
342
+ Returns
343
+ -------
344
+ CliAuthPlugin
345
+ The loaded authentication plugin instance.
346
+
347
+ Raises
348
+ ------
349
+ typer.Exit
350
+ If the authentication type is unknown.
351
+ """
255
352
  # Find the path to the account auth config file
256
353
  config_path = get_account_auth_config_path(root_dir, federation)
257
354
 
@@ -275,7 +372,22 @@ def load_cli_auth_plugin(
275
372
  def init_channel(
276
373
  app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
277
374
  ) -> grpc.Channel:
278
- """Initialize gRPC channel to the Control API."""
375
+ """Initialize gRPC channel to the Control API.
376
+
377
+ Parameters
378
+ ----------
379
+ app : Path
380
+ Path to the Flower app directory.
381
+ federation_config : dict[str, Any]
382
+ Federation configuration dictionary containing address and TLS settings.
383
+ auth_plugin : CliAuthPlugin
384
+ Authentication plugin instance for handling credentials.
385
+
386
+ Returns
387
+ -------
388
+ grpc.Channel
389
+ Configured gRPC channel with authentication interceptors.
390
+ """
279
391
  insecure, root_certificates_bytes = validate_certificate_in_federation_config(
280
392
  app, federation_config
281
393
  )
@@ -299,9 +411,22 @@ def init_channel(
299
411
  def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
300
412
  """Context manager to handle specific gRPC errors.
301
413
 
302
- It catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
303
- UNAVAILABLE, and PERMISSION_DENIED statuses, informs the user, and exits the
304
- application. All other exceptions will be allowed to escape.
414
+ Catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
415
+ UNAVAILABLE, PERMISSION_DENIED, NOT_FOUND, and FAILED_PRECONDITION statuses,
416
+ informs the user, and exits the application. All other exceptions will be
417
+ allowed to escape.
418
+
419
+ Yields
420
+ ------
421
+ None
422
+ Context manager yields nothing.
423
+
424
+ Raises
425
+ ------
426
+ typer.Exit
427
+ On handled gRPC error statuses with appropriate exit code.
428
+ grpc.RpcError
429
+ For unhandled gRPC error statuses.
305
430
  """
306
431
  try:
307
432
  yield
@@ -312,6 +437,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
312
437
  " to authenticate and try again.",
313
438
  fg=typer.colors.RED,
314
439
  bold=True,
440
+ err=True,
315
441
  )
316
442
  raise typer.Exit(code=1) from None
317
443
  if e.code() == grpc.StatusCode.UNIMPLEMENTED:
@@ -320,12 +446,14 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
320
446
  "❌ Account authentication is not enabled on this SuperLink.",
321
447
  fg=typer.colors.RED,
322
448
  bold=True,
449
+ err=True,
323
450
  )
324
451
  elif e.details() == NO_ARTIFACT_PROVIDER_MESSAGE: # pylint: disable=E1101
325
452
  typer.secho(
326
453
  "❌ The SuperLink does not support `flwr pull` command.",
327
454
  fg=typer.colors.RED,
328
455
  bold=True,
456
+ err=True,
329
457
  )
330
458
  else:
331
459
  typer.secho(
@@ -335,6 +463,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
335
463
  "the CLI and SuperLink are compatible.",
336
464
  fg=typer.colors.RED,
337
465
  bold=True,
466
+ err=True,
338
467
  )
339
468
  raise typer.Exit(code=1) from None
340
469
  if e.code() == grpc.StatusCode.PERMISSION_DENIED:
@@ -342,6 +471,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
342
471
  "❌ Permission denied.",
343
472
  fg=typer.colors.RED,
344
473
  bold=True,
474
+ err=True,
345
475
  )
346
476
  # pylint: disable-next=E1101
347
477
  typer.secho(e.details(), fg=typer.colors.RED, bold=True)
@@ -352,6 +482,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
352
482
  "connection and 'address' in the federation configuration.",
353
483
  fg=typer.colors.RED,
354
484
  bold=True,
485
+ err=True,
355
486
  )
356
487
  raise typer.Exit(code=1) from None
357
488
  if e.code() == grpc.StatusCode.NOT_FOUND:
@@ -360,6 +491,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
360
491
  "❌ Run ID not found.",
361
492
  fg=typer.colors.RED,
362
493
  bold=True,
494
+ err=True,
363
495
  )
364
496
  raise typer.Exit(code=1) from None
365
497
  if e.details() == NODE_NOT_FOUND_MESSAGE: # pylint: disable=E1101
@@ -367,6 +499,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
367
499
  "❌ Node ID not found for this account.",
368
500
  fg=typer.colors.RED,
369
501
  bold=True,
502
+ err=True,
370
503
  )
371
504
  raise typer.Exit(code=1) from None
372
505
  if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
@@ -376,6 +509,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
376
509
  "the run is finished. You can check the run status with `flwr ls`.",
377
510
  fg=typer.colors.RED,
378
511
  bold=True,
512
+ err=True,
379
513
  )
380
514
  raise typer.Exit(code=1) from None
381
515
  if (
@@ -386,6 +520,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
386
520
  "SuperNode.",
387
521
  fg=typer.colors.RED,
388
522
  bold=True,
523
+ err=True,
389
524
  )
390
525
  raise typer.Exit(code=1) from None
391
526
  if e.details() == PUBLIC_KEY_NOT_VALID: # pylint: disable=E1101
@@ -394,6 +529,94 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
394
529
  "NIST EC public key.",
395
530
  fg=typer.colors.RED,
396
531
  bold=True,
532
+ err=True,
397
533
  )
398
534
  raise typer.Exit(code=1) from None
535
+
536
+ # Log details from grpc error directly
537
+ typer.secho(
538
+ f"❌ {e.details()}",
539
+ fg=typer.colors.RED,
540
+ bold=True,
541
+ err=True,
542
+ )
543
+ raise typer.Exit(code=1) from None
399
544
  raise
545
+
546
+
547
+ def build_pathspec(patterns: Iterable[str]) -> pathspec.PathSpec:
548
+ """Build a PathSpec from a list of GitIgnore-style patterns.
549
+
550
+ Parameters
551
+ ----------
552
+ patterns : Iterable[str]
553
+ Iterable of GitIgnore-style pattern strings.
554
+
555
+ Returns
556
+ -------
557
+ pathspec.PathSpec
558
+ Compiled PathSpec object for pattern matching.
559
+ """
560
+ return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
561
+
562
+
563
+ def load_gitignore_patterns(file: Path | bytes) -> list[str]:
564
+ """Load gitignore patterns from .gitignore file bytes.
565
+
566
+ Parameters
567
+ ----------
568
+ file : Path | bytes
569
+ The path to a .gitignore file or its bytes content.
570
+
571
+ Returns
572
+ -------
573
+ list[str]
574
+ List of gitignore patterns.
575
+ Returns empty list if content can't be decoded or the file does not exist.
576
+ """
577
+ try:
578
+ if isinstance(file, Path):
579
+ content = file.read_text(encoding="utf-8")
580
+ else:
581
+ content = file.decode("utf-8")
582
+ patterns = [
583
+ line.strip()
584
+ for line in content.splitlines()
585
+ if line.strip() and not line.strip().startswith("#")
586
+ ]
587
+ return patterns
588
+ except (UnicodeDecodeError, OSError):
589
+ return []
590
+
591
+
592
+ def validate_credentials_content(creds_path: Path) -> str:
593
+ """Load and validate the credentials file content.
594
+
595
+ Ensures required keys exist:
596
+ - AUTHN_TYPE_JSON_KEY
597
+ - ACCESS_TOKEN_KEY
598
+ - REFRESH_TOKEN_KEY
599
+ """
600
+ try:
601
+ creds: dict[str, str] = json.loads(creds_path.read_text(encoding="utf-8"))
602
+ except (OSError, json.JSONDecodeError) as err:
603
+ typer.secho(
604
+ f"Invalid credentials file at '{creds_path}': {err}",
605
+ fg=typer.colors.RED,
606
+ err=True,
607
+ )
608
+ raise typer.Exit(code=1) from err
609
+
610
+ required_keys = [AUTHN_TYPE_JSON_KEY, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY]
611
+ missing = [key for key in required_keys if key not in creds]
612
+
613
+ if missing:
614
+ typer.secho(
615
+ f"Credentials file '{creds_path}' is missing "
616
+ f"required key(s): {', '.join(missing)}. Please log in again.",
617
+ fg=typer.colors.RED,
618
+ err=True,
619
+ )
620
+ raise typer.Exit(code=1)
621
+
622
+ return creds[ACCESS_TOKEN_KEY]
flwr/client/__init__.py CHANGED
@@ -15,7 +15,7 @@
15
15
  """Flower client."""
16
16
 
17
17
 
18
- from flwr.clientapp import ClientApp
18
+ from flwr.clientapp.client_app import ClientApp
19
19
 
20
20
  from ..compat.client.app import start_client as start_client # Deprecated
21
21
  from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
@@ -120,7 +120,10 @@ class DPFedAvgNumPyClient(NumPyClient):
120
120
  updated_params, num_examples, metrics = self.client.fit(parameters, config)
121
121
 
122
122
  # Update = updated model - original model
123
- update = [np.subtract(x, y) for (x, y) in zip(updated_params, original_params)]
123
+ update = [
124
+ np.subtract(x, y)
125
+ for (x, y) in zip(updated_params, original_params, strict=True)
126
+ ]
124
127
 
125
128
  if "dpfedavg_clip_norm" not in config:
126
129
  raise KeyError("Clipping threshold not supplied by the server.")
@@ -15,10 +15,9 @@
15
15
  """Contextmanager for a GrpcAdapter channel to the Flower server."""
16
16
 
17
17
 
18
- from collections.abc import Iterator
18
+ from collections.abc import Callable, Iterator
19
19
  from contextlib import contextmanager
20
20
  from logging import ERROR
21
- from typing import Callable, Optional, Union
22
21
 
23
22
  from cryptography.hazmat.primitives.asymmetric import ec
24
23
 
@@ -38,15 +37,15 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
38
37
  insecure: bool,
39
38
  retry_invoker: RetryInvoker,
40
39
  max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
41
- root_certificates: Optional[Union[bytes, str]] = None,
42
- authentication_keys: Optional[ # pylint: disable=unused-argument
43
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
44
- ] = None,
40
+ root_certificates: bytes | str | None = None,
41
+ authentication_keys: (
42
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
43
+ ) = None,
45
44
  ) -> Iterator[
46
45
  tuple[
47
46
  int,
48
- Callable[[], Optional[tuple[Message, ObjectTree]]],
49
- Callable[[Message, ObjectTree], set[str]],
47
+ Callable[[], tuple[Message, ObjectTree] | None],
48
+ Callable[[Message, ObjectTree, float], set[str]],
50
49
  Callable[[int], Run],
51
50
  Callable[[str, int], Fab],
52
51
  Callable[[int, str], bytes],
@@ -82,7 +81,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
82
81
  -------
83
82
  node_id : int
84
83
  receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
85
- send : Callable[[Message, ObjectTree], set[str]]
84
+ send : Callable[[Message, ObjectTree, float], set[str]]
86
85
  get_run : Callable[[int], Run]
87
86
  get_fab : Callable[[str, int], Fab]
88
87
  pull_object : Callable[[str], bytes]
@@ -15,11 +15,10 @@
15
15
  """Contextmanager for a gRPC request-response channel to the Flower server."""
16
16
 
17
17
 
18
- from collections.abc import Iterator, Sequence
18
+ from collections.abc import Callable, Iterator, Sequence
19
19
  from contextlib import contextmanager
20
20
  from logging import ERROR
21
21
  from pathlib import Path
22
- from typing import Callable, Optional, Union
23
22
 
24
23
  import grpc
25
24
  from cryptography.hazmat.primitives.asymmetric import ec
@@ -27,7 +26,6 @@ from cryptography.hazmat.primitives.asymmetric import ec
27
26
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
28
27
  from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
29
28
  from flwr.common.grpc import create_channel, on_channel_state_change
30
- from flwr.common.heartbeat import HeartbeatSender
31
29
  from flwr.common.inflatable_protobuf_utils import (
32
30
  make_confirm_message_received_fn_protobuf,
33
31
  make_pull_object_fn_protobuf,
@@ -63,6 +61,7 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
63
61
  from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
64
62
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
65
63
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
64
+ from flwr.supercore.heartbeat import HeartbeatSender
66
65
  from flwr.supercore.primitives.asymmetric import generate_key_pairs, public_key_to_bytes
67
66
 
68
67
  from .grpc_adapter import GrpcAdapter
@@ -75,16 +74,16 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
75
74
  insecure: bool,
76
75
  retry_invoker: RetryInvoker,
77
76
  max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
78
- root_certificates: Optional[Union[bytes, str]] = None,
79
- authentication_keys: Optional[
80
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
81
- ] = None,
82
- adapter_cls: Optional[Union[type[FleetStub], type[GrpcAdapter]]] = None,
77
+ root_certificates: bytes | str | None = None,
78
+ authentication_keys: (
79
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
80
+ ) = None,
81
+ adapter_cls: type[FleetStub] | type[GrpcAdapter] | None = None,
83
82
  ) -> Iterator[
84
83
  tuple[
85
84
  int,
86
- Callable[[], Optional[tuple[Message, ObjectTree]]],
87
- Callable[[Message, ObjectTree], set[str]],
85
+ Callable[[], tuple[Message, ObjectTree] | None],
86
+ Callable[[Message, ObjectTree, float], set[str]],
88
87
  Callable[[int], Run],
89
88
  Callable[[str, int], Fab],
90
89
  Callable[[int, str], bytes],
@@ -129,7 +128,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
129
128
  -------
130
129
  node_id : int
131
130
  receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
132
- send : Callable[[Message, ObjectTree], set[str]]
131
+ send : Callable[[Message, ObjectTree, float], set[str]]
133
132
  get_run : Callable[[int], Run]
134
133
  get_fab : Callable[[str, int], Fab]
135
134
  pull_object : Callable[[str], bytes]
@@ -163,7 +162,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
163
162
  if adapter_cls is None:
164
163
  adapter_cls = FleetStub
165
164
  stub = adapter_cls(channel)
166
- node: Optional[Node] = None
165
+ node: Node | None = None
167
166
 
168
167
  # Wrap stub
169
168
  _wrap_stub(stub, retry_invoker)
@@ -253,7 +252,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
253
252
  # Cleanup
254
253
  node = None
255
254
 
256
- def receive() -> Optional[tuple[Message, ObjectTree]]:
255
+ def receive() -> tuple[Message, ObjectTree] | None:
257
256
  """Pull a message with its ObjectTree from SuperLink."""
258
257
  # Get Node
259
258
  if node is None:
@@ -278,7 +277,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
278
277
  # Return the Message and its object tree
279
278
  return in_message, object_tree
280
279
 
281
- def send(message: Message, object_tree: ObjectTree) -> set[str]:
280
+ def send(
281
+ message: Message, object_tree: ObjectTree, clientapp_runtime: float
282
+ ) -> set[str]:
282
283
  """Send the message with its ObjectTree to SuperLink."""
283
284
  # Get Node
284
285
  if node is None:
@@ -294,6 +295,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
294
295
  node=node,
295
296
  messages_list=[message_to_proto(message)],
296
297
  message_object_trees=[object_tree],
298
+ clientapp_runtime_list=[clientapp_runtime],
297
299
  )
298
300
  response: PushMessagesResponse = stub.PushMessages(request=request)
299
301
 
@@ -15,7 +15,8 @@
15
15
  """GrpcAdapter implementation."""
16
16
 
17
17
 
18
- import sys
18
+ import signal
19
+ import time
19
20
  from logging import DEBUG
20
21
  from typing import Any, TypeVar, cast
21
22
 
@@ -62,6 +63,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
62
63
  PushObjectResponse,
63
64
  )
64
65
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
66
+ from flwr.supercore.constant import FORCE_EXIT_TIMEOUT_SECONDS
65
67
 
66
68
  T = TypeVar("T", bound=GrpcMessage)
67
69
 
@@ -108,7 +110,9 @@ class GrpcAdapter:
108
110
  DEBUG,
109
111
  'Received shutdown signal: exit flag is set to ``"true"``. Exiting...',
110
112
  )
111
- sys.exit(0)
113
+ signal.raise_signal(signal.SIGTERM)
114
+ # Give some time to handle the signal
115
+ time.sleep(FORCE_EXIT_TIMEOUT_SECONDS + 1)
112
116
 
113
117
  # Check the grpc_message_name of the response
114
118
  if container_res.grpc_message_name != response_type.__qualname__:
@@ -15,7 +15,8 @@
15
15
  """Flower client interceptor."""
16
16
 
17
17
 
18
- from typing import Any, Callable
18
+ from collections.abc import Callable
19
+ from typing import Any
19
20
 
20
21
  import grpc
21
22
  from cryptography.hazmat.primitives.asymmetric import ec
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from logging import WARN
19
- from typing import Optional, cast
19
+ from typing import cast
20
20
 
21
21
  from flwr.client.client import (
22
22
  maybe_call_evaluate,
@@ -53,7 +53,7 @@ class UnknownServerMessage(Exception):
53
53
  """Exception indicating that the received message is unknown."""
54
54
 
55
55
 
56
- def handle_control_message(message: Message) -> tuple[Optional[Message], int]:
56
+ def handle_control_message(message: Message) -> tuple[Message | None, int]:
57
57
  """Handle control part of the incoming message.
58
58
 
59
59
  Parameters
@@ -112,9 +112,9 @@ class SecAggPlusState:
112
112
  updated_values = [
113
113
  tuple(values[i : i + 2]) for i in range(0, len(values), 2)
114
114
  ]
115
- new_v = dict(zip(keys, updated_values))
115
+ new_v = dict(zip(keys, updated_values, strict=True))
116
116
  else:
117
- new_v = dict(zip(keys, values))
117
+ new_v = dict(zip(keys, values, strict=True))
118
118
  self.__setattr__(k, new_v)
119
119
 
120
120
  def to_dict(self) -> dict[str, ConfigRecordValues]:
@@ -426,7 +426,7 @@ def _collect_masked_vectors(
426
426
  raise ValueError("Not enough available neighbour clients.")
427
427
 
428
428
  # Decrypt ciphertexts, verify their sources, and store shares.
429
- for src, ciphertext in zip(srcs, ciphertexts):
429
+ for src, ciphertext in zip(srcs, ciphertexts, strict=True):
430
430
  shared_key = state.ss2_dict[src]
431
431
  plaintext = decrypt(shared_key, ciphertext)
432
432
  actual_src, dst, rd_seed_share, sk1_share = share_keys_plaintext_separate(
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from abc import ABC
19
- from typing import Callable
19
+ from collections.abc import Callable
20
20
 
21
21
  from flwr.client.client import Client
22
22
  from flwr.common import (