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
flwr/cli/utils.py CHANGED
@@ -18,40 +18,68 @@
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
+ import click
26
27
  import grpc
28
+ import pathspec
27
29
  import typer
28
30
 
31
+ from flwr.cli.typing import SuperLinkConnection
29
32
  from flwr.common.constant import (
30
- AUTH_TYPE_JSON_KEY,
33
+ ACCESS_TOKEN_KEY,
34
+ AUTHN_TYPE_JSON_KEY,
31
35
  CREDENTIALS_DIR,
32
36
  FLWR_DIR,
37
+ NO_ACCOUNT_AUTH_MESSAGE,
33
38
  NO_ARTIFACT_PROVIDER_MESSAGE,
34
- NO_USER_AUTH_MESSAGE,
39
+ NODE_NOT_FOUND_MESSAGE,
40
+ PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
41
+ PUBLIC_KEY_NOT_VALID,
35
42
  PULL_UNFINISHED_RUN_MESSAGE,
43
+ REFRESH_TOKEN_KEY,
36
44
  RUN_ID_NOT_FOUND_MESSAGE,
45
+ AuthnType,
37
46
  )
38
47
  from flwr.common.grpc import (
39
48
  GRPC_MAX_MESSAGE_LENGTH,
40
49
  create_channel,
41
50
  on_channel_state_change,
42
51
  )
52
+ from flwr.supercore.utils import get_flwr_home
43
53
 
44
- from .auth_plugin import CliAuthPlugin, get_cli_auth_plugins
45
- from .cli_user_auth_interceptor import CliUserAuthInterceptor
46
- from .config_utils import validate_certificate_in_federation_config
54
+ from .auth_plugin import CliAuthPlugin, get_cli_plugin_class
55
+ from .cli_account_auth_interceptor import CliAccountAuthInterceptor
56
+ from .config_utils import (
57
+ load_certificate_in_connection,
58
+ validate_certificate_in_federation_config,
59
+ )
47
60
 
48
61
 
49
62
  def prompt_text(
50
63
  text: str,
51
64
  predicate: Callable[[str], bool] = lambda _: True,
52
- default: Optional[str] = None,
65
+ default: str | None = None,
53
66
  ) -> str:
54
- """Ask user to enter text input."""
67
+ """Ask user to enter text input.
68
+
69
+ Parameters
70
+ ----------
71
+ text : str
72
+ The prompt text to display to the user.
73
+ predicate : Callable[[str], bool] (default: lambda _: True)
74
+ A function to validate the user input. Default accepts all non-empty strings.
75
+ default : str | None (default: None)
76
+ Default value to use if user presses enter without input.
77
+
78
+ Returns
79
+ -------
80
+ str
81
+ The validated user input.
82
+ """
55
83
  while True:
56
84
  result = typer.prompt(
57
85
  typer.style(f"\n💬 {text}", fg=typer.colors.MAGENTA, bold=True),
@@ -65,7 +93,20 @@ def prompt_text(
65
93
 
66
94
 
67
95
  def prompt_options(text: str, options: list[str]) -> str:
68
- """Ask user to select one of the given options and return the selected item."""
96
+ """Ask user to select one of the given options and return the selected item.
97
+
98
+ Parameters
99
+ ----------
100
+ text : str
101
+ The prompt text to display to the user.
102
+ options : list[str]
103
+ List of options to present to the user.
104
+
105
+ Returns
106
+ -------
107
+ str
108
+ The selected option from the list.
109
+ """
69
110
  # Turn options into a list with index as in " [ 0] quickstart-pytorch"
70
111
  options_formatted = [
71
112
  " [ "
@@ -123,9 +164,19 @@ def is_valid_project_name(name: str) -> bool:
123
164
  def sanitize_project_name(name: str) -> str:
124
165
  """Sanitize the given string to make it a valid Python project name.
125
166
 
126
- This version replaces spaces, dots, slashes, and underscores with dashes, removes
167
+ This function replaces spaces, dots, slashes, and underscores with dashes, removes
127
168
  any characters not allowed in Python project names, makes the string lowercase, and
128
169
  ensures it starts with a valid character.
170
+
171
+ Parameters
172
+ ----------
173
+ name : str
174
+ The project name to sanitize.
175
+
176
+ Returns
177
+ -------
178
+ str
179
+ The sanitized project name that is valid for Python projects.
129
180
  """
130
181
  # Replace whitespace with '_'
131
182
  name_with_hyphens = re.sub(r"[ ./_]", "-", name)
@@ -150,8 +201,19 @@ def sanitize_project_name(name: str) -> str:
150
201
  return sanitized_name
151
202
 
152
203
 
153
- def get_sha256_hash(file_path_or_int: Union[Path, int]) -> str:
154
- """Calculate the SHA-256 hash of a file."""
204
+ def get_sha256_hash(file_path_or_int: Path | int) -> str:
205
+ """Calculate the SHA-256 hash of a file or integer.
206
+
207
+ Parameters
208
+ ----------
209
+ file_path_or_int : Path | int
210
+ Either a path to a file to hash, or an integer to convert to string and hash.
211
+
212
+ Returns
213
+ -------
214
+ str
215
+ The SHA-256 hash as a hexadecimal string.
216
+ """
155
217
  sha256 = hashlib.sha256()
156
218
  if isinstance(file_path_or_int, Path):
157
219
  with open(file_path_or_int, "rb") as f:
@@ -165,8 +227,8 @@ def get_sha256_hash(file_path_or_int: Union[Path, int]) -> str:
165
227
  return sha256.hexdigest()
166
228
 
167
229
 
168
- def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
169
- """Return the path to the user auth config file.
230
+ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
231
+ """Return the path to the account auth config file.
170
232
 
171
233
  Additionally, a `.gitignore` file will be created in the Flower directory to
172
234
  include the `.credentials` folder to be excluded from git. If the `.gitignore`
@@ -210,77 +272,180 @@ def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
210
272
  f"Please check the permissions of `{gitignore_path}` and try again.",
211
273
  fg=typer.colors.RED,
212
274
  bold=True,
275
+ err=True,
213
276
  )
214
277
  raise typer.Exit(code=1) from err
215
278
 
216
279
  return credentials_dir / f"{federation}.json"
217
280
 
218
281
 
219
- def try_obtain_cli_auth_plugin(
282
+ def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
283
+ """Check if account authentication is enabled in the federation config.
284
+
285
+ Parameters
286
+ ----------
287
+ federation_config : dict[str, Any]
288
+ The federation configuration dictionary.
289
+
290
+ Returns
291
+ -------
292
+ bool
293
+ True if account authentication is enabled, False otherwise.
294
+ """
295
+ enabled: bool = federation_config.get("enable-user-auth", False)
296
+ enabled |= federation_config.get("enable-account-auth", False)
297
+ if "enable-user-auth" in federation_config:
298
+ typer.secho(
299
+ "`enable-user-auth` is deprecated and will be removed in a future "
300
+ "release. Please use `enable-account-auth` instead.",
301
+ fg=typer.colors.YELLOW,
302
+ bold=True,
303
+ )
304
+ return enabled
305
+
306
+
307
+ def retrieve_authn_type(config_path: Path) -> str:
308
+ """Retrieve the auth type from the config file or return NOOP if not found.
309
+
310
+ Parameters
311
+ ----------
312
+ config_path : Path
313
+ Path to the authentication configuration file.
314
+
315
+ Returns
316
+ -------
317
+ str
318
+ The authentication type string, or AuthnType.NOOP if not found.
319
+ """
320
+ try:
321
+ with config_path.open("r", encoding="utf-8") as file:
322
+ json_file = json.load(file)
323
+ authn_type: str = json_file[AUTHN_TYPE_JSON_KEY]
324
+ return authn_type
325
+ except (FileNotFoundError, KeyError):
326
+ return AuthnType.NOOP
327
+
328
+
329
+ def load_cli_auth_plugin(
220
330
  root_dir: Path,
221
331
  federation: str,
222
332
  federation_config: dict[str, Any],
223
- auth_type: Optional[str] = None,
224
- ) -> Optional[CliAuthPlugin]:
225
- """Load the CLI-side user auth plugin for the given auth type."""
226
- # Check if user auth is enabled
227
- if not federation_config.get("enable-user-auth", False):
228
- return None
229
-
230
- config_path = get_user_auth_config_path(root_dir, federation)
231
-
232
- # Get the auth type from the config if not provided
233
- # auth_type will be None for all CLI commands except login
234
- if auth_type is None:
235
- try:
236
- with config_path.open("r", encoding="utf-8") as file:
237
- json_file = json.load(file)
238
- auth_type = json_file[AUTH_TYPE_JSON_KEY]
239
- except (FileNotFoundError, KeyError):
240
- typer.secho(
241
- "❌ Missing or invalid credentials for user authentication. "
242
- "Please run `flwr login` to authenticate.",
243
- fg=typer.colors.RED,
244
- bold=True,
245
- )
246
- raise typer.Exit(code=1) from None
333
+ authn_type: str | None = None,
334
+ ) -> CliAuthPlugin:
335
+ """Load the CLI-side account auth plugin for the given authn type.
336
+
337
+ Parameters
338
+ ----------
339
+ root_dir : Path
340
+ Root directory of the Flower project.
341
+ federation : str
342
+ Name of the federation.
343
+ federation_config : dict[str, Any]
344
+ Federation configuration dictionary.
345
+ authn_type : str | None
346
+ Authentication type. If None, will be determined from config.
347
+
348
+ Returns
349
+ -------
350
+ CliAuthPlugin
351
+ The loaded authentication plugin instance.
352
+
353
+ Raises
354
+ ------
355
+ typer.Exit
356
+ If the authentication type is unknown.
357
+ """
358
+ # Find the path to the account auth config file
359
+ config_path = get_account_auth_config_path(root_dir, federation)
360
+
361
+ # Determine the auth type if not provided
362
+ # Only `flwr login` command can provide `authn_type` explicitly, as it can query the
363
+ # SuperLink for the auth type.
364
+ if authn_type is None:
365
+ authn_type = AuthnType.NOOP
366
+ if account_auth_enabled(federation_config):
367
+ authn_type = retrieve_authn_type(config_path)
247
368
 
248
369
  # Retrieve auth plugin class and instantiate it
249
370
  try:
250
- all_plugins: dict[str, type[CliAuthPlugin]] = get_cli_auth_plugins()
251
- auth_plugin_class = all_plugins[auth_type]
371
+ auth_plugin_class = get_cli_plugin_class(authn_type)
252
372
  return auth_plugin_class(config_path)
253
- except KeyError:
254
- typer.echo(f"❌ Unknown user authentication type: {auth_type}")
373
+ except ValueError:
374
+ typer.echo(f"❌ Unknown account authentication type: {authn_type}")
255
375
  raise typer.Exit(code=1) from None
256
- except ImportError:
257
- typer.echo("❌ No authentication plugins are currently supported.")
376
+
377
+
378
+ def load_cli_auth_plugin_from_connection(
379
+ connection: SuperLinkConnection,
380
+ authn_type: str | None = None,
381
+ ) -> CliAuthPlugin:
382
+ """Load the CLI-side account auth plugin for the given connection.
383
+
384
+ Parameters
385
+ ----------
386
+ connection : SuperLinkConnection
387
+ The SuperLink connection configuration.
388
+ authn_type : str | None
389
+ Authentication type. If None, will be determined from config.
390
+
391
+ Returns
392
+ -------
393
+ CliAuthPlugin
394
+ The loaded authentication plugin instance.
395
+
396
+ Raises
397
+ ------
398
+ typer.Exit
399
+ If the authentication type is unknown.
400
+ """
401
+ # Locate the credentials directory
402
+ flwr_dir = get_flwr_home()
403
+ credentials_dir = flwr_dir / CREDENTIALS_DIR
404
+ credentials_dir.mkdir(parents=True, exist_ok=True)
405
+
406
+ # Find the path to the account auth config file
407
+ config_path = get_account_auth_config_path(flwr_dir, connection.name)
408
+
409
+ # Determine the auth type if not provided
410
+ if authn_type is None:
411
+ authn_type = AuthnType.NOOP
412
+ if connection.enable_account_auth:
413
+ authn_type = retrieve_authn_type(config_path)
414
+
415
+ # Retrieve auth plugin class and instantiate it
416
+ try:
417
+ auth_plugin_class = get_cli_plugin_class(authn_type)
418
+ return auth_plugin_class(config_path)
419
+ except ValueError:
420
+ typer.echo(f"❌ Unknown account authentication type: {authn_type}")
258
421
  raise typer.Exit(code=1) from None
259
422
 
260
423
 
261
424
  def init_channel(
262
- app: Path, federation_config: dict[str, Any], auth_plugin: Optional[CliAuthPlugin]
425
+ app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
263
426
  ) -> grpc.Channel:
264
- """Initialize gRPC channel to the Control API."""
427
+ """Initialize gRPC channel to the Control API.
428
+
429
+ Parameters
430
+ ----------
431
+ app : Path
432
+ Path to the Flower app directory.
433
+ federation_config : dict[str, Any]
434
+ Federation configuration dictionary containing address and TLS settings.
435
+ auth_plugin : CliAuthPlugin
436
+ Authentication plugin instance for handling credentials.
437
+
438
+ Returns
439
+ -------
440
+ grpc.Channel
441
+ Configured gRPC channel with authentication interceptors.
442
+ """
265
443
  insecure, root_certificates_bytes = validate_certificate_in_federation_config(
266
444
  app, federation_config
267
445
  )
268
446
 
269
- # Initialize the CLI-side user auth interceptor
270
- interceptors: list[grpc.UnaryUnaryClientInterceptor] = []
271
- if auth_plugin is not None:
272
- # Check if TLS is enabled. If not, raise an error
273
- if insecure:
274
- typer.secho(
275
- "❌ User authentication requires TLS to be enabled. "
276
- "Remove `insecure = true` from the federation configuration.",
277
- fg=typer.colors.RED,
278
- bold=True,
279
- )
280
- raise typer.Exit(code=1)
281
-
282
- auth_plugin.load_tokens()
283
- interceptors.append(CliUserAuthInterceptor(auth_plugin))
447
+ # Load tokens
448
+ auth_plugin.load_tokens()
284
449
 
285
450
  # Create the gRPC channel
286
451
  channel = create_channel(
@@ -288,19 +453,81 @@ def init_channel(
288
453
  insecure=insecure,
289
454
  root_certificates=root_certificates_bytes,
290
455
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
291
- interceptors=interceptors or None,
456
+ interceptors=[CliAccountAuthInterceptor(auth_plugin)],
457
+ )
458
+ channel.subscribe(on_channel_state_change)
459
+ return channel
460
+
461
+
462
+ def init_channel_from_connection(
463
+ connection: SuperLinkConnection, auth_plugin: CliAuthPlugin | None = None
464
+ ) -> grpc.Channel:
465
+ """Initialize gRPC channel to the Control API.
466
+
467
+ Parameters
468
+ ----------
469
+ connection : SuperLinkConnection
470
+ SuperLink connection configuration.
471
+ auth_plugin : CliAuthPlugin | None (default: None)
472
+ Authentication plugin instance for handling credentials.
473
+
474
+ Returns
475
+ -------
476
+ grpc.Channel
477
+ Configured gRPC channel with authentication interceptors.
478
+ """
479
+ if connection.address is None:
480
+ cmd = click.get_current_context().command.name
481
+ typer.secho(
482
+ f"❌ `flwr {cmd}` currently works with a SuperLink. Ensure that the "
483
+ "correct SuperLink (Control API) address is provided in `pyproject.toml`.",
484
+ fg=typer.colors.RED,
485
+ bold=True,
486
+ err=True,
487
+ )
488
+ raise typer.Exit(code=1)
489
+
490
+ root_certificates_bytes = load_certificate_in_connection(connection)
491
+
492
+ # Load authentication plugin
493
+ if auth_plugin is None:
494
+ auth_plugin = load_cli_auth_plugin_from_connection(connection)
495
+
496
+ # Load tokens
497
+ auth_plugin.load_tokens()
498
+
499
+ # Create the gRPC channel
500
+ channel = create_channel(
501
+ server_address=connection.address,
502
+ insecure=connection.insecure,
503
+ root_certificates=root_certificates_bytes,
504
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
505
+ interceptors=[CliAccountAuthInterceptor(auth_plugin)],
292
506
  )
293
507
  channel.subscribe(on_channel_state_change)
294
508
  return channel
295
509
 
296
510
 
297
511
  @contextmanager
298
- def flwr_cli_grpc_exc_handler() -> Iterator[None]:
512
+ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
299
513
  """Context manager to handle specific gRPC errors.
300
514
 
301
- It catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
302
- UNAVAILABLE, and PERMISSION_DENIED statuses, informs the user, and exits the
303
- application. All other exceptions will be allowed to escape.
515
+ Catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
516
+ UNAVAILABLE, PERMISSION_DENIED, NOT_FOUND, and FAILED_PRECONDITION statuses,
517
+ informs the user, and exits the application. All other exceptions will be
518
+ allowed to escape.
519
+
520
+ Yields
521
+ ------
522
+ None
523
+ Context manager yields nothing.
524
+
525
+ Raises
526
+ ------
527
+ typer.Exit
528
+ On handled gRPC error statuses with appropriate exit code.
529
+ grpc.RpcError
530
+ For unhandled gRPC error statuses.
304
531
  """
305
532
  try:
306
533
  yield
@@ -311,20 +538,23 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
311
538
  " to authenticate and try again.",
312
539
  fg=typer.colors.RED,
313
540
  bold=True,
541
+ err=True,
314
542
  )
315
543
  raise typer.Exit(code=1) from None
316
544
  if e.code() == grpc.StatusCode.UNIMPLEMENTED:
317
- if e.details() == NO_USER_AUTH_MESSAGE: # pylint: disable=E1101
545
+ if e.details() == NO_ACCOUNT_AUTH_MESSAGE: # pylint: disable=E1101
318
546
  typer.secho(
319
- "❌ User authentication is not enabled on this SuperLink.",
547
+ "❌ Account authentication is not enabled on this SuperLink.",
320
548
  fg=typer.colors.RED,
321
549
  bold=True,
550
+ err=True,
322
551
  )
323
552
  elif e.details() == NO_ARTIFACT_PROVIDER_MESSAGE: # pylint: disable=E1101
324
553
  typer.secho(
325
554
  "❌ The SuperLink does not support `flwr pull` command.",
326
555
  fg=typer.colors.RED,
327
556
  bold=True,
557
+ err=True,
328
558
  )
329
559
  else:
330
560
  typer.secho(
@@ -334,6 +564,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
334
564
  "the CLI and SuperLink are compatible.",
335
565
  fg=typer.colors.RED,
336
566
  bold=True,
567
+ err=True,
337
568
  )
338
569
  raise typer.Exit(code=1) from None
339
570
  if e.code() == grpc.StatusCode.PERMISSION_DENIED:
@@ -341,6 +572,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
341
572
  "❌ Permission denied.",
342
573
  fg=typer.colors.RED,
343
574
  bold=True,
575
+ err=True,
344
576
  )
345
577
  # pylint: disable-next=E1101
346
578
  typer.secho(e.details(), fg=typer.colors.RED, bold=True)
@@ -351,18 +583,26 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
351
583
  "connection and 'address' in the federation configuration.",
352
584
  fg=typer.colors.RED,
353
585
  bold=True,
586
+ err=True,
354
587
  )
355
588
  raise typer.Exit(code=1) from None
356
- if (
357
- e.code() == grpc.StatusCode.NOT_FOUND
358
- and e.details() == RUN_ID_NOT_FOUND_MESSAGE # pylint: disable=E1101
359
- ):
360
- typer.secho(
361
- "❌ Run ID not found.",
362
- fg=typer.colors.RED,
363
- bold=True,
364
- )
365
- raise typer.Exit(code=1) from None
589
+ if e.code() == grpc.StatusCode.NOT_FOUND:
590
+ if e.details() == RUN_ID_NOT_FOUND_MESSAGE: # pylint: disable=E1101
591
+ typer.secho(
592
+ "❌ Run ID not found.",
593
+ fg=typer.colors.RED,
594
+ bold=True,
595
+ err=True,
596
+ )
597
+ raise typer.Exit(code=1) from None
598
+ if e.details() == NODE_NOT_FOUND_MESSAGE: # pylint: disable=E1101
599
+ typer.secho(
600
+ "❌ Node ID not found for this account.",
601
+ fg=typer.colors.RED,
602
+ bold=True,
603
+ err=True,
604
+ )
605
+ raise typer.Exit(code=1) from None
366
606
  if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
367
607
  if e.details() == PULL_UNFINISHED_RUN_MESSAGE: # pylint: disable=E1101
368
608
  typer.secho(
@@ -370,6 +610,114 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
370
610
  "the run is finished. You can check the run status with `flwr ls`.",
371
611
  fg=typer.colors.RED,
372
612
  bold=True,
613
+ err=True,
614
+ )
615
+ raise typer.Exit(code=1) from None
616
+ if (
617
+ e.details() == PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
618
+ ): # pylint: disable=E1101
619
+ typer.secho(
620
+ "❌ The provided public key is already in use by another "
621
+ "SuperNode.",
622
+ fg=typer.colors.RED,
623
+ bold=True,
624
+ err=True,
625
+ )
626
+ raise typer.Exit(code=1) from None
627
+ if e.details() == PUBLIC_KEY_NOT_VALID: # pylint: disable=E1101
628
+ typer.secho(
629
+ "❌ The provided public key is invalid. Please provide a valid "
630
+ "NIST EC public key.",
631
+ fg=typer.colors.RED,
632
+ bold=True,
633
+ err=True,
373
634
  )
374
635
  raise typer.Exit(code=1) from None
636
+
637
+ # Log details from grpc error directly
638
+ typer.secho(
639
+ f"❌ {e.details()}",
640
+ fg=typer.colors.RED,
641
+ bold=True,
642
+ err=True,
643
+ )
644
+ raise typer.Exit(code=1) from None
375
645
  raise
646
+
647
+
648
+ def build_pathspec(patterns: Iterable[str]) -> pathspec.PathSpec:
649
+ """Build a PathSpec from a list of GitIgnore-style patterns.
650
+
651
+ Parameters
652
+ ----------
653
+ patterns : Iterable[str]
654
+ Iterable of GitIgnore-style pattern strings.
655
+
656
+ Returns
657
+ -------
658
+ pathspec.PathSpec
659
+ Compiled PathSpec object for pattern matching.
660
+ """
661
+ return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
662
+
663
+
664
+ def load_gitignore_patterns(file: Path | bytes) -> list[str]:
665
+ """Load gitignore patterns from .gitignore file bytes.
666
+
667
+ Parameters
668
+ ----------
669
+ file : Path | bytes
670
+ The path to a .gitignore file or its bytes content.
671
+
672
+ Returns
673
+ -------
674
+ list[str]
675
+ List of gitignore patterns.
676
+ Returns empty list if content can't be decoded or the file does not exist.
677
+ """
678
+ try:
679
+ if isinstance(file, Path):
680
+ content = file.read_text(encoding="utf-8")
681
+ else:
682
+ content = file.decode("utf-8")
683
+ patterns = [
684
+ line.strip()
685
+ for line in content.splitlines()
686
+ if line.strip() and not line.strip().startswith("#")
687
+ ]
688
+ return patterns
689
+ except (UnicodeDecodeError, OSError):
690
+ return []
691
+
692
+
693
+ def validate_credentials_content(creds_path: Path) -> str:
694
+ """Load and validate the credentials file content.
695
+
696
+ Ensures required keys exist:
697
+ - AUTHN_TYPE_JSON_KEY
698
+ - ACCESS_TOKEN_KEY
699
+ - REFRESH_TOKEN_KEY
700
+ """
701
+ try:
702
+ creds: dict[str, str] = json.loads(creds_path.read_text(encoding="utf-8"))
703
+ except (OSError, json.JSONDecodeError) as err:
704
+ typer.secho(
705
+ f"Invalid credentials file at '{creds_path}': {err}",
706
+ fg=typer.colors.RED,
707
+ err=True,
708
+ )
709
+ raise typer.Exit(code=1) from err
710
+
711
+ required_keys = [AUTHN_TYPE_JSON_KEY, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY]
712
+ missing = [key for key in required_keys if key not in creds]
713
+
714
+ if missing:
715
+ typer.secho(
716
+ f"Credentials file '{creds_path}' is missing "
717
+ f"required key(s): {', '.join(missing)}. Please log in again.",
718
+ fg=typer.colors.RED,
719
+ err=True,
720
+ )
721
+ raise typer.Exit(code=1)
722
+
723
+ return creds[ACCESS_TOKEN_KEY]
flwr/client/__init__.py CHANGED
@@ -15,10 +15,11 @@
15
15
  """Flower client."""
16
16
 
17
17
 
18
+ from flwr.clientapp.client_app import ClientApp
19
+
18
20
  from ..compat.client.app import start_client as start_client # Deprecated
19
21
  from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
20
22
  from .client import Client as Client
21
- from .client_app import ClientApp as ClientApp
22
23
  from .numpy_client import NumPyClient as NumPyClient
23
24
  from .typing import ClientFn as ClientFn
24
25
  from .typing import ClientFnExt as ClientFnExt
@@ -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.")