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
@@ -16,22 +16,30 @@
16
16
 
17
17
 
18
18
  import hashlib
19
+ import json
19
20
  import time
20
- from collections.abc import Generator
21
+ from collections.abc import Generator, Sequence
21
22
  from logging import ERROR, INFO
22
- from typing import Any, Optional, cast
23
+ from typing import Any, cast
23
24
 
24
25
  import grpc
26
+ import requests
25
27
 
26
28
  from flwr.cli.config_utils import get_fab_metadata
27
29
  from flwr.common import Context, RecordDict, now
28
30
  from flwr.common.constant import (
29
31
  FAB_MAX_SIZE,
32
+ HEARTBEAT_DEFAULT_INTERVAL,
30
33
  LOG_STREAM_INTERVAL,
34
+ NO_ACCOUNT_AUTH_MESSAGE,
31
35
  NO_ARTIFACT_PROVIDER_MESSAGE,
32
- NO_USER_AUTH_MESSAGE,
36
+ NODE_NOT_FOUND_MESSAGE,
37
+ PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
38
+ PUBLIC_KEY_NOT_VALID,
33
39
  PULL_UNFINISHED_RUN_MESSAGE,
34
40
  RUN_ID_NOT_FOUND_MESSAGE,
41
+ SUPERLINK_NODE_ID,
42
+ TRANSPORT_TYPE_GRPC_ADAPTER,
35
43
  Status,
36
44
  SubStatus,
37
45
  )
@@ -48,24 +56,39 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
48
56
  GetAuthTokensResponse,
49
57
  GetLoginDetailsRequest,
50
58
  GetLoginDetailsResponse,
59
+ ListFederationsRequest,
60
+ ListFederationsResponse,
61
+ ListNodesRequest,
62
+ ListNodesResponse,
51
63
  ListRunsRequest,
52
64
  ListRunsResponse,
53
65
  PullArtifactsRequest,
54
66
  PullArtifactsResponse,
67
+ RegisterNodeRequest,
68
+ RegisterNodeResponse,
69
+ ShowFederationRequest,
70
+ ShowFederationResponse,
55
71
  StartRunRequest,
56
72
  StartRunResponse,
57
73
  StopRunRequest,
58
74
  StopRunResponse,
59
75
  StreamLogsRequest,
60
76
  StreamLogsResponse,
77
+ UnregisterNodeRequest,
78
+ UnregisterNodeResponse,
61
79
  )
80
+ from flwr.proto.federation_pb2 import Federation # pylint: disable=E0611
81
+ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
62
82
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
83
+ from flwr.supercore.constant import PLATFORM_API_URL
63
84
  from flwr.supercore.ffs import FfsFactory
64
85
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
86
+ from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
87
+ from flwr.supercore.utils import parse_app_spec, request_download_link
65
88
  from flwr.superlink.artifact_provider import ArtifactProvider
66
- from flwr.superlink.auth_plugin import ControlAuthPlugin
89
+ from flwr.superlink.auth_plugin import ControlAuthnPlugin
67
90
 
68
- from .control_user_auth_interceptor import shared_account_info
91
+ from .control_account_auth_interceptor import get_current_account_info
69
92
 
70
93
 
71
94
  class ControlServicer(control_pb2_grpc.ControlServicer):
@@ -77,17 +100,19 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
77
100
  ffs_factory: FfsFactory,
78
101
  objectstore_factory: ObjectStoreFactory,
79
102
  is_simulation: bool,
80
- auth_plugin: Optional[ControlAuthPlugin] = None,
81
- artifact_provider: Optional[ArtifactProvider] = None,
103
+ authn_plugin: ControlAuthnPlugin,
104
+ artifact_provider: ArtifactProvider | None = None,
105
+ fleet_api_type: str | None = None,
82
106
  ) -> None:
83
107
  self.linkstate_factory = linkstate_factory
84
108
  self.ffs_factory = ffs_factory
85
109
  self.objectstore_factory = objectstore_factory
86
110
  self.is_simulation = is_simulation
87
- self.auth_plugin = auth_plugin
111
+ self.authn_plugin = authn_plugin
88
112
  self.artifact_provider = artifact_provider
113
+ self.fleet_api_type = fleet_api_type
89
114
 
90
- def StartRun( # pylint: disable=too-many-locals
115
+ def StartRun( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
91
116
  self, request: StartRunRequest, context: grpc.ServicerContext
92
117
  ) -> StartRunResponse:
93
118
  """Create run ID."""
@@ -95,7 +120,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
95
120
  state = self.linkstate_factory.state()
96
121
  ffs = self.ffs_factory.ffs()
97
122
 
98
- if len(request.fab.content) > FAB_MAX_SIZE:
123
+ verification_dict: dict[str, str] = {}
124
+ if request.app_spec:
125
+ fab_file, verification_dict = _get_remote_fab(
126
+ self.fleet_api_type, request.app_spec, context
127
+ )
128
+ else:
129
+ fab_file = request.fab.content
130
+
131
+ if len(fab_file) > FAB_MAX_SIZE:
99
132
  log(
100
133
  ERROR,
101
134
  "FAB size exceeds maximum allowed size of %d bytes.",
@@ -103,10 +136,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
103
136
  )
104
137
  return StartRunResponse()
105
138
 
106
- flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
139
+ flwr_aid = get_current_account_info().flwr_aid
140
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
107
141
  override_config = user_config_from_proto(request.override_config)
108
142
  federation_options = config_record_from_proto(request.federation_options)
109
- fab_file = request.fab.content
110
143
 
111
144
  try:
112
145
  # Check that num-supernodes is set
@@ -115,9 +148,27 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
115
148
  "Federation options doesn't contain key `num-supernodes`."
116
149
  )
117
150
 
151
+ # Check (1) federation exists and (2) the flwr_aid is a member
152
+ federation = request.federation
153
+
154
+ if not state.federation_manager.exists(federation):
155
+ raise ValueError(f"Federation '{federation}' does not exist.")
156
+
157
+ if not state.federation_manager.has_member(flwr_aid, federation):
158
+ raise ValueError(
159
+ f"Account with ID '{flwr_aid}' is not a member of the "
160
+ f"federation '{federation}'. Please log in with another account "
161
+ "or request access to this federation."
162
+ )
163
+
118
164
  # Create run
119
- fab = Fab(hashlib.sha256(fab_file).hexdigest(), fab_file)
120
- fab_hash = ffs.put(fab.content, {})
165
+ fab = Fab(
166
+ hashlib.sha256(fab_file).hexdigest(),
167
+ fab_file,
168
+ verification_dict,
169
+ )
170
+ fab_hash = ffs.put(fab.content, fab.verifications)
171
+
121
172
  if fab_hash != fab.hash_str:
122
173
  raise RuntimeError(
123
174
  f"FAB ({fab.hash_str}) hash from request doesn't match contents"
@@ -129,6 +180,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
129
180
  fab_version,
130
181
  fab_hash,
131
182
  override_config,
183
+ request.federation,
132
184
  federation_options,
133
185
  flwr_aid,
134
186
  )
@@ -144,7 +196,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
144
196
  # Create an empty context for the Run
145
197
  context = Context(
146
198
  run_id=run_id,
147
- node_id=0,
199
+ node_id=SUPERLINK_NODE_ID,
148
200
  # Dict is invariant in mypy
149
201
  node_config=node_config, # type: ignore[arg-type]
150
202
  state=RecordDict(),
@@ -157,7 +209,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
157
209
  # pylint: disable-next=broad-except
158
210
  except Exception as e:
159
211
  log(ERROR, "Could not start run: %s", str(e))
160
- return StartRunResponse()
212
+ context.abort(
213
+ grpc.StatusCode.FAILED_PRECONDITION,
214
+ str(e),
215
+ )
161
216
 
162
217
  log(INFO, "Created run %s", str(run_id))
163
218
  return StartRunResponse(run_id=run_id)
@@ -177,12 +232,9 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
177
232
  if not run:
178
233
  context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
179
234
 
180
- # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
181
- if self.auth_plugin:
182
- flwr_aid = shared_account_info.get().flwr_aid
183
- _check_flwr_aid_in_run(
184
- flwr_aid=flwr_aid, run=cast(Run, run), context=context
185
- )
235
+ # Check if `flwr_aid` matches the run's `flwr_aid`
236
+ flwr_aid = get_current_account_info().flwr_aid
237
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=cast(Run, run), context=context)
186
238
 
187
239
  after_timestamp = request.after_timestamp + 1e-6
188
240
  while context.is_active():
@@ -218,20 +270,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
218
270
 
219
271
  # Build a set of run IDs for `flwr ls --runs`
220
272
  if not request.HasField("run_id"):
221
- if self.auth_plugin:
222
- # If no `run_id` is specified and user auth is enabled,
223
- # return run IDs for the authenticated user
224
- flwr_aid = shared_account_info.get().flwr_aid
225
- if flwr_aid is None:
226
- context.abort(
227
- grpc.StatusCode.PERMISSION_DENIED,
228
- "️⛔️ User authentication is enabled, but `flwr_aid` is None",
229
- )
230
- run_ids = state.get_run_ids(flwr_aid=flwr_aid)
231
- else:
232
- # If no `run_id` is specified and no user auth is enabled,
233
- # return all run IDs
234
- run_ids = state.get_run_ids(None)
273
+ # If no `run_id` is specified and account auth is enabled,
274
+ # return run IDs for the authenticated account
275
+ flwr_aid = get_current_account_info().flwr_aid
276
+ _check_flwr_aid_exists(flwr_aid, context)
277
+ run_ids = state.get_run_ids(flwr_aid=flwr_aid)
235
278
  # Build a set of run IDs for `flwr ls --run-id <run_id>`
236
279
  else:
237
280
  # Retrieve run ID and run
@@ -241,13 +284,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
241
284
  # Exit if `run_id` not found
242
285
  if not run:
243
286
  context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
287
+ raise grpc.RpcError() # This line is unreachable
244
288
 
245
- # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
246
- if self.auth_plugin:
247
- flwr_aid = shared_account_info.get().flwr_aid
248
- _check_flwr_aid_in_run(
249
- flwr_aid=flwr_aid, run=cast(Run, run), context=context
250
- )
289
+ # Check if `flwr_aid` matches the run's `flwr_aid`
290
+ flwr_aid = get_current_account_info().flwr_aid
291
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
251
292
 
252
293
  run_ids = {run_id}
253
294
 
@@ -269,13 +310,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
269
310
  # Exit if `run_id` not found
270
311
  if not run:
271
312
  context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
313
+ raise grpc.RpcError() # This line is unreachable
272
314
 
273
- # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
274
- if self.auth_plugin:
275
- flwr_aid = shared_account_info.get().flwr_aid
276
- _check_flwr_aid_in_run(
277
- flwr_aid=flwr_aid, run=cast(Run, run), context=context
278
- )
315
+ # Check if `flwr_aid` matches the run's `flwr_aid`
316
+ flwr_aid = get_current_account_info().flwr_aid
317
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
279
318
 
280
319
  run_status = state.get_run_status({run_id})[run_id]
281
320
  if run_status.status == Status.FINISHED:
@@ -284,11 +323,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
284
323
  f"Run ID {run_id} is already finished",
285
324
  )
286
325
 
326
+ # Update run status to finished:stopped
287
327
  update_success = state.update_run_status(
288
328
  run_id=run_id,
289
329
  new_status=RunStatus(Status.FINISHED, SubStatus.STOPPED, ""),
290
330
  )
291
331
 
332
+ # Delete the token associated with the run to stop further operations
333
+ state.delete_token(run_id)
334
+
292
335
  if update_success:
293
336
  message_ids: set[str] = state.get_message_ids_from_run_id(run_id)
294
337
 
@@ -305,22 +348,22 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
305
348
  ) -> GetLoginDetailsResponse:
306
349
  """Start login."""
307
350
  log(INFO, "ControlServicer.GetLoginDetails")
308
- if self.auth_plugin is None:
351
+ if self.authn_plugin is None:
309
352
  context.abort(
310
353
  grpc.StatusCode.UNIMPLEMENTED,
311
- NO_USER_AUTH_MESSAGE,
354
+ NO_ACCOUNT_AUTH_MESSAGE,
312
355
  )
313
356
  raise grpc.RpcError() # This line is unreachable
314
357
 
315
358
  # Get login details
316
- details = self.auth_plugin.get_login_details()
359
+ details = self.authn_plugin.get_login_details()
317
360
 
318
361
  # Return empty response if details is None
319
362
  if details is None:
320
363
  return GetLoginDetailsResponse()
321
364
 
322
365
  return GetLoginDetailsResponse(
323
- auth_type=details.auth_type,
366
+ authn_type=details.authn_type,
324
367
  device_code=details.device_code,
325
368
  verification_uri_complete=details.verification_uri_complete,
326
369
  expires_in=details.expires_in,
@@ -332,15 +375,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
332
375
  ) -> GetAuthTokensResponse:
333
376
  """Get auth token."""
334
377
  log(INFO, "ControlServicer.GetAuthTokens")
335
- if self.auth_plugin is None:
378
+ if self.authn_plugin is None:
336
379
  context.abort(
337
380
  grpc.StatusCode.UNIMPLEMENTED,
338
- NO_USER_AUTH_MESSAGE,
381
+ NO_ACCOUNT_AUTH_MESSAGE,
339
382
  )
340
383
  raise grpc.RpcError() # This line is unreachable
341
384
 
342
385
  # Get auth tokens
343
- credentials = self.auth_plugin.get_auth_tokens(request.device_code)
386
+ credentials = self.authn_plugin.get_auth_tokens(request.device_code)
344
387
 
345
388
  # Return empty response if credentials is None
346
389
  if credentials is None:
@@ -383,15 +426,160 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
383
426
  grpc.StatusCode.FAILED_PRECONDITION, PULL_UNFINISHED_RUN_MESSAGE
384
427
  )
385
428
 
386
- # Check if `flwr_aid` matches the run's `flwr_aid` when user auth is enabled
387
- if self.auth_plugin:
388
- flwr_aid = shared_account_info.get().flwr_aid
389
- _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
429
+ # Check if `flwr_aid` matches the run's `flwr_aid`
430
+ flwr_aid = get_current_account_info().flwr_aid
431
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
390
432
 
391
433
  # Call artifact provider
392
434
  download_url = self.artifact_provider.get_url(run_id)
393
435
  return PullArtifactsResponse(url=download_url)
394
436
 
437
+ def RegisterNode(
438
+ self, request: RegisterNodeRequest, context: grpc.ServicerContext
439
+ ) -> RegisterNodeResponse:
440
+ """Add a SuperNode."""
441
+ log(INFO, "ControlServicer.RegisterNode")
442
+
443
+ # Verify public key
444
+ try:
445
+ # Attempt to deserialize public key
446
+ pub_key = bytes_to_public_key(request.public_key)
447
+ # Check if it's a NIST EC curve public key
448
+ if not uses_nist_ec_curve(pub_key):
449
+ err_msg = "The provided public key is not a NIST EC curve public key."
450
+ log(ERROR, "%s", err_msg)
451
+ raise ValueError(err_msg)
452
+ except (ValueError, AttributeError) as err:
453
+ log(ERROR, "%s", err)
454
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_NOT_VALID)
455
+
456
+ # Init link state
457
+ state = self.linkstate_factory.state()
458
+ node_id = 0
459
+
460
+ flwr_aid = get_current_account_info().flwr_aid
461
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
462
+ # Account name exists if `flwr_aid` exists
463
+ account_name = cast(str, get_current_account_info().account_name)
464
+ try:
465
+ node_id = state.create_node(
466
+ owner_aid=flwr_aid,
467
+ owner_name=account_name,
468
+ public_key=request.public_key,
469
+ heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL,
470
+ )
471
+
472
+ except ValueError:
473
+ # Public key already in use
474
+ log(ERROR, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE)
475
+ context.abort(
476
+ grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
477
+ )
478
+ log(INFO, "[ControlServicer.RegisterNode] Created node_id=%s", node_id)
479
+
480
+ return RegisterNodeResponse(node_id=node_id)
481
+
482
+ def UnregisterNode(
483
+ self, request: UnregisterNodeRequest, context: grpc.ServicerContext
484
+ ) -> UnregisterNodeResponse:
485
+ """Remove a SuperNode."""
486
+ log(INFO, "ControlServicer.UnregisterNode")
487
+
488
+ # Init link state
489
+ state = self.linkstate_factory.state()
490
+
491
+ flwr_aid = get_current_account_info().flwr_aid
492
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
493
+ try:
494
+ state.delete_node(owner_aid=flwr_aid, node_id=request.node_id)
495
+ except ValueError:
496
+ log(ERROR, NODE_NOT_FOUND_MESSAGE)
497
+ context.abort(grpc.StatusCode.NOT_FOUND, NODE_NOT_FOUND_MESSAGE)
498
+
499
+ return UnregisterNodeResponse()
500
+
501
+ def ListNodes(
502
+ self, request: ListNodesRequest, context: grpc.ServicerContext
503
+ ) -> ListNodesResponse:
504
+ """List all SuperNodes."""
505
+ log(INFO, "ControlServicer.ListNodes")
506
+
507
+ if self.is_simulation:
508
+ log(ERROR, "ListNodes is not available in simulation mode.")
509
+ context.abort(
510
+ grpc.StatusCode.UNIMPLEMENTED,
511
+ "ListNodes is not available in simulation mode.",
512
+ )
513
+ raise grpc.RpcError() # This line is unreachable
514
+
515
+ nodes_info: Sequence[NodeInfo] = []
516
+ # Init link state
517
+ state = self.linkstate_factory.state()
518
+
519
+ flwr_aid = get_current_account_info().flwr_aid
520
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
521
+ # Retrieve all nodes for the account
522
+ nodes_info = state.get_node_info(owner_aids=[flwr_aid])
523
+
524
+ return ListNodesResponse(nodes_info=nodes_info, now=now().isoformat())
525
+
526
+ def ListFederations(
527
+ self, request: ListFederationsRequest, context: grpc.ServicerContext
528
+ ) -> ListFederationsResponse:
529
+ """List all SuperNodes."""
530
+ log(INFO, "ControlServicer.ListFederations")
531
+
532
+ # Init link state
533
+ state = self.linkstate_factory.state()
534
+
535
+ flwr_aid = get_current_account_info().flwr_aid
536
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
537
+
538
+ # Get federations the account is a member of
539
+ federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
540
+
541
+ return ListFederationsResponse(
542
+ federations=[Federation(name=fed) for fed in federations]
543
+ )
544
+
545
+ def ShowFederation(
546
+ self, request: ShowFederationRequest, context: grpc.ServicerContext
547
+ ) -> ShowFederationResponse:
548
+ """Show details of a specific Federation."""
549
+ log(INFO, "ControlServicer.ShowFederation")
550
+
551
+ # Init link state
552
+ state = self.linkstate_factory.state()
553
+
554
+ flwr_aid = get_current_account_info().flwr_aid
555
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
556
+
557
+ # Get federations the account is a member of
558
+ federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
559
+
560
+ # Ensure flwr_aid is a member of the requested federation
561
+ federation = request.federation_name
562
+ if federation not in federations:
563
+ context.abort(
564
+ grpc.StatusCode.FAILED_PRECONDITION,
565
+ f"Federation '{federation}' does not exist or you are "
566
+ "not a member of it.",
567
+ )
568
+
569
+ # Fetch federation details
570
+ details = state.federation_manager.get_details(federation)
571
+
572
+ # Build Federation proto object
573
+ federation_proto = Federation(
574
+ name=federation,
575
+ member_aids=details.member_aids,
576
+ nodes=details.nodes,
577
+ runs=[run_to_proto(run) for run in details.runs],
578
+ )
579
+ return ShowFederationResponse(
580
+ federation=federation_proto, now=now().isoformat()
581
+ )
582
+
395
583
 
396
584
  def _create_list_runs_response(
397
585
  run_ids: set[int], state: LinkState, store: ObjectStore
@@ -410,29 +598,101 @@ def _create_list_runs_response(
410
598
  )
411
599
 
412
600
 
413
- def _check_flwr_aid_in_run(
414
- flwr_aid: Optional[str], run: Run, context: grpc.ServicerContext
415
- ) -> None:
416
- """Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
417
- # `flwr_aid` must not be None. Abort if it is None.
601
+ def _check_flwr_aid_exists(flwr_aid: str | None, context: grpc.ServicerContext) -> str:
602
+ """Guard clause to check if `flwr_aid` exists."""
418
603
  if flwr_aid is None:
419
604
  context.abort(
420
605
  grpc.StatusCode.PERMISSION_DENIED,
421
- "️⛔️ User authentication is enabled, but `flwr_aid` is None",
606
+ "️⛔️ Failed to fetch the account information.",
422
607
  )
608
+ raise RuntimeError # This line is unreachable
609
+ return flwr_aid
610
+
423
611
 
612
+ def _check_flwr_aid_in_run(
613
+ flwr_aid: str | None, run: Run, context: grpc.ServicerContext
614
+ ) -> None:
615
+ """Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
616
+ _check_flwr_aid_exists(flwr_aid, context)
424
617
  # `run.flwr_aid` must not be an empty string. Abort if it is empty.
425
618
  run_flwr_aid = run.flwr_aid
426
619
  if not run_flwr_aid:
427
620
  context.abort(
428
621
  grpc.StatusCode.PERMISSION_DENIED,
429
- "⛔️ User authentication is enabled, but the run is not associated "
430
- "with a `flwr_aid`.",
622
+ "⛔️ Run is not associated with a `flwr_aid`.",
431
623
  )
432
624
 
433
625
  # Exit if `flwr_aid` does not match the run's `flwr_aid`
434
626
  if run_flwr_aid != flwr_aid:
435
627
  context.abort(
436
628
  grpc.StatusCode.PERMISSION_DENIED,
437
- "⛔️ Run ID does not belong to the user",
629
+ "⛔️ Run ID does not belong to the account",
630
+ )
631
+
632
+
633
+ def _format_verification(verifications: list[dict[str, str]]) -> dict[str, str]:
634
+ """Format verification information for FAB."""
635
+ # Convert verifications to dict[str, str] type
636
+ verification_dict = {
637
+ item["public_key_id"]: json.dumps(
638
+ {k: v for k, v in item.items() if k != "public_key_id"}
639
+ )
640
+ for item in verifications
641
+ }
642
+ verification_dict.update({"valid_license": "Valid"})
643
+
644
+ return verification_dict
645
+
646
+
647
+ def _get_remote_fab(
648
+ fleet_api_type: str | None,
649
+ app_spec: str,
650
+ context: grpc.ServicerContext,
651
+ ) -> tuple[bytes, dict[str, str]]:
652
+ """Get remote FAB from Flower platform API."""
653
+ if fleet_api_type == TRANSPORT_TYPE_GRPC_ADAPTER:
654
+ context.abort(
655
+ grpc.StatusCode.FAILED_PRECONDITION,
656
+ "The selected SuperLink transport type is not "
657
+ "supported for connecting to Flower Platform.",
658
+ )
659
+
660
+ # Parse and validate app specification
661
+ try:
662
+ app_id, app_version = parse_app_spec(app_spec)
663
+ except ValueError as e:
664
+ context.abort(
665
+ grpc.StatusCode.FAILED_PRECONDITION,
666
+ f"{e}",
667
+ )
668
+
669
+ # Request download link and verification information
670
+ url = f"{PLATFORM_API_URL}/hub/fetch-fab"
671
+ try:
672
+ presigned_url, verifications = request_download_link(
673
+ app_id, app_version, url, "fab_url"
674
+ )
675
+ except ValueError as e:
676
+ context.abort(
677
+ grpc.StatusCode.FAILED_PRECONDITION,
678
+ f"{e}",
679
+ )
680
+
681
+ # Format verification information
682
+ verification_dict = (
683
+ _format_verification(verifications)
684
+ if verifications is not None
685
+ else {"valid_license": ""}
686
+ )
687
+
688
+ # Download FAB from Flower platform API
689
+ try:
690
+ r = requests.get(presigned_url, timeout=60)
691
+ r.raise_for_status()
692
+ except requests.RequestException as e:
693
+ context.abort(
694
+ grpc.StatusCode.FAILED_PRECONDITION,
695
+ f"FAB download failed: {str(e)}",
438
696
  )
697
+ fab_file = r.content
698
+ return fab_file, verification_dict