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/run/run.py CHANGED
@@ -15,53 +15,57 @@
15
15
  """Flower command line interface `run` command."""
16
16
 
17
17
 
18
+ import hashlib
18
19
  import io
19
20
  import json
20
21
  import subprocess
21
22
  from pathlib import Path
22
- from typing import Annotated, Any, Optional
23
+ from typing import Annotated, Any, cast
23
24
 
24
25
  import typer
25
26
  from rich.console import Console
26
27
 
27
- from flwr.cli.build import build_fab, get_fab_filename
28
- from flwr.cli.config_utils import (
29
- load_and_validate,
30
- process_loaded_project_config,
31
- validate_federation_in_project_config,
32
- )
28
+ from flwr.cli.build import build_fab_from_disk, get_fab_filename
29
+ from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
30
+ from flwr.cli.config_utils import load as load_toml
33
31
  from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE, RUN_CONFIG_HELP_MESSAGE
32
+ from flwr.cli.flower_config import (
33
+ _serialize_simulation_options,
34
+ read_superlink_connection,
35
+ )
36
+ from flwr.cli.typing import SuperLinkConnection, SuperLinkSimulationOptions
34
37
  from flwr.common.config import (
35
- flatten_dict,
36
38
  get_metadata_from_config,
37
39
  parse_config_args,
38
40
  user_config_to_configrecord,
39
41
  )
40
- from flwr.common.constant import CliOutputFormat
42
+ from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
41
43
  from flwr.common.logger import print_json_error, redirect_output, restore_output
42
44
  from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_to_proto
43
45
  from flwr.common.typing import Fab
44
46
  from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
45
47
  from flwr.proto.control_pb2_grpc import ControlStub
48
+ from flwr.supercore.constant import NOOP_FEDERATION
49
+ from flwr.supercore.utils import parse_app_spec
46
50
 
47
51
  from ..log import start_stream
48
- from ..utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
52
+ from ..utils import flwr_cli_grpc_exc_handler, init_channel_from_connection
49
53
 
50
54
  CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
51
55
 
52
56
 
53
- # pylint: disable-next=too-many-locals, R0913, R0917
57
+ # pylint: disable-next=too-many-locals, too-many-branches, R0913, R0917
54
58
  def run(
55
59
  app: Annotated[
56
60
  Path,
57
61
  typer.Argument(help="Path of the Flower App to run."),
58
62
  ] = Path("."),
59
- federation: Annotated[
60
- Optional[str],
61
- typer.Argument(help="Name of the federation to run the app on."),
63
+ superlink: Annotated[
64
+ str | None,
65
+ typer.Argument(help="Name of the SuperLink connection."),
62
66
  ] = None,
63
67
  run_config_overrides: Annotated[
64
- Optional[list[str]],
68
+ list[str] | None,
65
69
  typer.Option(
66
70
  "--run-config",
67
71
  "-c",
@@ -69,10 +73,11 @@ def run(
69
73
  ),
70
74
  ] = None,
71
75
  federation_config_overrides: Annotated[
72
- Optional[list[str]],
76
+ list[str] | None,
73
77
  typer.Option(
74
78
  "--federation-config",
75
79
  help=FEDERATION_CONFIG_HELP_MESSAGE,
80
+ hidden=True,
76
81
  ),
77
82
  ] = None,
78
83
  stream: Annotated[
@@ -95,30 +100,49 @@ def run(
95
100
  """Run Flower App."""
96
101
  suppress_output = output_format == CliOutputFormat.JSON
97
102
  captured_output = io.StringIO()
103
+
104
+ if suppress_output:
105
+ redirect_output(captured_output)
106
+
107
+ # Warn `--federation-config` is ignored
108
+ warn_if_federation_config_overrides(federation_config_overrides)
109
+
110
+ # Migrate legacy usage if any
111
+ migrate(str(app), [], ignore_legacy_usage=True)
112
+
113
+ # Read superlink connection configuration
114
+ superlink_connection = read_superlink_connection(superlink)
115
+
98
116
  try:
99
- if suppress_output:
100
- redirect_output(captured_output)
101
- typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
102
-
103
- pyproject_path = app / "pyproject.toml" if app else None
104
- config, errors, warnings = load_and_validate(path=pyproject_path)
105
- config = process_loaded_project_config(config, errors, warnings)
106
- federation, federation_config = validate_federation_in_project_config(
107
- federation, config, federation_config_overrides
108
- )
109
117
 
110
- if "address" in federation_config:
118
+ # Determine if app is remote
119
+ app_spec = None
120
+ if (app_str := str(app)).startswith("@"):
121
+ # Validate app version and ID format
122
+ try:
123
+ _ = parse_app_spec(app_str)
124
+ except ValueError as e:
125
+ typer.secho(f"❌ {e}", fg=typer.colors.RED, err=True)
126
+ raise typer.Exit(code=1) from e
127
+
128
+ app_spec = app_str
129
+ # Set `app` to current directory for credential storage
130
+ app = Path(".")
131
+
132
+ if superlink_connection.address:
111
133
  _run_with_control_api(
112
134
  app,
113
- federation,
114
- federation_config,
135
+ superlink_connection,
115
136
  run_config_overrides,
116
137
  stream,
117
138
  output_format,
139
+ app_spec,
118
140
  )
119
141
  else:
120
142
  _run_without_control_api(
121
- app, federation_config, run_config_overrides, federation
143
+ app=app,
144
+ simulation_options=superlink_connection.options, # type: ignore
145
+ config_overrides=run_config_overrides,
122
146
  )
123
147
  except (typer.Exit, Exception) as err: # pylint: disable=broad-except
124
148
  if suppress_output:
@@ -130,6 +154,7 @@ def run(
130
154
  f"{err}",
131
155
  fg=typer.colors.RED,
132
156
  bold=True,
157
+ err=True,
133
158
  )
134
159
  finally:
135
160
  if suppress_output:
@@ -140,31 +165,46 @@ def run(
140
165
  # pylint: disable-next=R0913, R0914, R0917
141
166
  def _run_with_control_api(
142
167
  app: Path,
143
- federation: str,
144
- federation_config: dict[str, Any],
145
- config_overrides: Optional[list[str]],
168
+ superlink_connection: SuperLinkConnection,
169
+ config_overrides: list[str] | None,
146
170
  stream: bool,
147
171
  output_format: str,
172
+ app_spec: str | None,
148
173
  ) -> None:
149
174
  channel = None
175
+ is_remote_app = app_spec is not None
150
176
  try:
151
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
152
- channel = init_channel(app, federation_config, auth_plugin)
177
+ channel = init_channel_from_connection(superlink_connection)
153
178
  stub = ControlStub(channel)
154
179
 
155
- fab_bytes, fab_hash, config = build_fab(app)
156
- fab_id, fab_version = get_metadata_from_config(config)
180
+ # Build FAB if local app
181
+ if not is_remote_app:
182
+ fab_bytes = build_fab_from_disk(app)
183
+ fab_hash = hashlib.sha256(fab_bytes).hexdigest()
184
+ config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
185
+ fab_id, fab_version = get_metadata_from_config(config)
186
+ fab = Fab(fab_hash, fab_bytes, {})
187
+ # Skip FAB build if remote app
188
+ else:
189
+ # Use empty values for FAB
190
+ fab_id = fab_version = fab_hash = ""
191
+ fab = Fab(fab_hash, b"", {})
157
192
 
158
- fab = Fab(fab_hash, fab_bytes)
193
+ federation: str = superlink_connection.federation or NOOP_FEDERATION
159
194
 
160
195
  # Construct a `ConfigRecord` out of a flattened `UserConfig`
161
- fed_config = flatten_dict(federation_config.get("options", {}))
162
- c_record = user_config_to_configrecord(fed_config)
196
+ options = {}
197
+ if superlink_connection.options:
198
+ options = _serialize_simulation_options(superlink_connection.options)
199
+
200
+ c_record = user_config_to_configrecord(options)
163
201
 
164
202
  req = StartRunRequest(
165
203
  fab=fab_to_proto(fab),
166
204
  override_config=user_config_to_proto(parse_config_args(config_overrides)),
205
+ federation=federation,
167
206
  federation_options=config_record_to_proto(c_record),
207
+ app_spec=app_spec or "",
168
208
  )
169
209
  with flwr_cli_grpc_exc_handler():
170
210
  res = stub.StartRun(req)
@@ -174,23 +214,27 @@ def _run_with_control_api(
174
214
  f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN
175
215
  )
176
216
  else:
177
- typer.secho("❌ Failed to start run", fg=typer.colors.RED)
217
+ typer.secho("❌ Failed to start run", fg=typer.colors.RED, err=True)
178
218
  raise typer.Exit(code=1)
179
219
 
180
220
  if output_format == CliOutputFormat.JSON:
181
- run_output = json.dumps(
182
- {
183
- "success": res.HasField("run_id"),
184
- "run-id": res.run_id if res.HasField("run_id") else None,
185
- "fab-id": fab_id,
186
- "fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
187
- "fab-version": fab_version,
188
- "fab-hash": fab_hash[:8],
189
- "fab-filename": get_fab_filename(config, fab_hash),
190
- }
191
- )
221
+ # Only include FAB metadata if we actually built a local FAB
222
+ payload: dict[str, Any] = {
223
+ "success": res.HasField("run_id"),
224
+ "run-id": f"{res.run_id}" if res.HasField("run_id") else None,
225
+ }
226
+ if not is_remote_app:
227
+ payload.update(
228
+ {
229
+ "fab-id": fab_id,
230
+ "fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
231
+ "fab-version": fab_version,
232
+ "fab-hash": fab_hash[:8],
233
+ "fab-filename": get_fab_filename(config, fab_hash),
234
+ }
235
+ )
192
236
  restore_output()
193
- Console().print_json(run_output)
237
+ Console().print_json(json.dumps(payload))
194
238
 
195
239
  if stream:
196
240
  start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
@@ -200,26 +244,13 @@ def _run_with_control_api(
200
244
 
201
245
 
202
246
  def _run_without_control_api(
203
- app: Optional[Path],
204
- federation_config: dict[str, Any],
205
- config_overrides: Optional[list[str]],
206
- federation: str,
247
+ app: Path | None,
248
+ simulation_options: SuperLinkSimulationOptions,
249
+ config_overrides: list[str] | None,
207
250
  ) -> None:
208
- try:
209
- num_supernodes = federation_config["options"]["num-supernodes"]
210
- verbose: Optional[bool] = federation_config["options"].get("verbose")
211
- backend_cfg = federation_config["options"].get("backend", {})
212
- except KeyError as err:
213
- typer.secho(
214
- "❌ The project's `pyproject.toml` needs to declare the number of"
215
- " SuperNodes in the simulation. To simulate 10 SuperNodes,"
216
- " use the following notation:\n\n"
217
- f"[tool.flwr.federations.{federation}]\n"
218
- "options.num-supernodes = 10\n",
219
- fg=typer.colors.RED,
220
- bold=True,
221
- )
222
- raise typer.Exit(code=1) from err
251
+
252
+ num_supernodes = simulation_options.num_supernodes
253
+ verbose = False # bool | None = superlink_connection.options.verbose
223
254
 
224
255
  command = [
225
256
  "flower-simulation",
@@ -229,9 +260,10 @@ def _run_without_control_api(
229
260
  f"{num_supernodes}",
230
261
  ]
231
262
 
232
- if backend_cfg:
263
+ if simulation_options.backend:
233
264
  # Stringify as JSON
234
- command.extend(["--backend-config", json.dumps(backend_cfg)])
265
+ backend_serial = _serialize_simulation_options(simulation_options)
266
+ command.extend(["--backend-config", json.dumps(backend_serial)])
235
267
 
236
268
  if verbose:
237
269
  command.extend(["--verbose"])
flwr/cli/run_utils.py ADDED
@@ -0,0 +1,148 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower command line interface utils."""
16
+
17
+
18
+ from dataclasses import dataclass
19
+ from datetime import datetime, timedelta
20
+
21
+ from flwr.common.typing import Run
22
+ from flwr.supercore.date import isoformat8601_utc
23
+
24
+
25
+ @dataclass
26
+ class RunRow: # pylint: disable=too-many-instance-attributes
27
+ """Represents a single run's data for display.
28
+
29
+ Attributes
30
+ ----------
31
+ run_id : int
32
+ The unique identifier for the run.
33
+ federation : str
34
+ The federation name.
35
+ fab_id : str
36
+ The Flower App Bundle identifier.
37
+ fab_version : str
38
+ The FAB version string.
39
+ fab_hash : str
40
+ The SHA-256 hash of the FAB.
41
+ status_text : str
42
+ The formatted status text.
43
+ elapsed : float
44
+ The elapsed time in seconds.
45
+ pending_at : str
46
+ Timestamp when run entered pending state.
47
+ starting_at : str
48
+ Timestamp when run entered starting state.
49
+ running_at : str
50
+ Timestamp when run entered running state.
51
+ finished_at : str
52
+ Timestamp when run finished.
53
+ network_traffic_inbound : int
54
+ The total inbound network traffic (in bytes) used during the run.
55
+ It includes the traffic from SuperNodes to SuperLink.
56
+ network_traffic_outbound : int
57
+ The total outbound network traffic (in bytes) used during the run.
58
+ It includes the traffic from SuperLink to SuperNodes.
59
+ compute_time_serverapp : float
60
+ The total compute time (in seconds) of the ServerApp during the run.
61
+ compute_time_clientapp : float
62
+ The total compute time (in seconds) of all ClientApps during the run.
63
+ """
64
+
65
+ run_id: int
66
+ federation: str
67
+ fab_id: str
68
+ fab_version: str
69
+ fab_hash: str
70
+ status_text: str
71
+ elapsed: float
72
+ pending_at: str
73
+ starting_at: str
74
+ running_at: str
75
+ finished_at: str
76
+ network_traffic_inbound: int
77
+ network_traffic_outbound: int
78
+ compute_time_serverapp: float
79
+ compute_time_clientapp: float
80
+
81
+
82
+ def format_runs(runs: list[Run], now_isoformat: str) -> list[RunRow]:
83
+ """Format runs to a list of RunRow objects.
84
+
85
+ Parameters
86
+ ----------
87
+ runs : list[Run]
88
+ List of Run objects to format.
89
+ now_isoformat : str
90
+ Current timestamp in ISO format for calculating elapsed time.
91
+
92
+ Returns
93
+ -------
94
+ list[RunRow]
95
+ List of formatted RunRow objects sorted by pending_at timestamp.
96
+ """
97
+
98
+ def _format_datetime(dt: datetime | None) -> str:
99
+ return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
100
+
101
+ run_list: list[RunRow] = []
102
+
103
+ # Add rows
104
+ for run in sorted(runs, key=lambda x: datetime.fromisoformat(x.pending_at)):
105
+ # Combine status and sub-status into a single string
106
+ if run.status.sub_status == "":
107
+ status_text = run.status.status
108
+ else:
109
+ status_text = f"{run.status.status}:{run.status.sub_status}"
110
+
111
+ # Convert isoformat to datetime
112
+ pending_at = datetime.fromisoformat(run.pending_at) if run.pending_at else None
113
+ starting_at = (
114
+ datetime.fromisoformat(run.starting_at) if run.starting_at else None
115
+ )
116
+ running_at = datetime.fromisoformat(run.running_at) if run.running_at else None
117
+ finished_at = (
118
+ datetime.fromisoformat(run.finished_at) if run.finished_at else None
119
+ )
120
+
121
+ # Calculate elapsed time
122
+ elapsed_time = timedelta()
123
+ if running_at:
124
+ if finished_at:
125
+ end_time = finished_at
126
+ else:
127
+ end_time = datetime.fromisoformat(now_isoformat)
128
+ elapsed_time = end_time - running_at
129
+
130
+ row = RunRow(
131
+ run_id=run.run_id,
132
+ federation=run.federation,
133
+ fab_id=run.fab_id,
134
+ fab_version=run.fab_version,
135
+ fab_hash=run.fab_hash,
136
+ status_text=status_text,
137
+ elapsed=elapsed_time.total_seconds(),
138
+ pending_at=_format_datetime(pending_at),
139
+ starting_at=_format_datetime(starting_at),
140
+ running_at=_format_datetime(running_at),
141
+ finished_at=_format_datetime(finished_at),
142
+ network_traffic_inbound=run.bytes_recv,
143
+ network_traffic_outbound=run.bytes_sent,
144
+ compute_time_serverapp=elapsed_time.total_seconds(),
145
+ compute_time_clientapp=run.clientapp_runtime,
146
+ )
147
+ run_list.append(row)
148
+ return run_list
flwr/cli/stop.py CHANGED
@@ -17,20 +17,15 @@
17
17
 
18
18
  import io
19
19
  import json
20
- from pathlib import Path
21
- from typing import Annotated, Optional
20
+ from typing import Annotated
22
21
 
23
22
  import typer
24
23
  from rich.console import Console
25
24
 
26
- from flwr.cli.config_utils import (
27
- exit_if_no_address,
28
- load_and_validate,
29
- process_loaded_project_config,
30
- validate_federation_in_project_config,
31
- )
25
+ from flwr.cli.config_migration import migrate, warn_if_federation_config_overrides
32
26
  from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
33
- from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
27
+ from flwr.cli.flower_config import read_superlink_connection
28
+ from flwr.common.constant import CliOutputFormat
34
29
  from flwr.common.logger import print_json_error, redirect_output, restore_output
35
30
  from flwr.proto.control_pb2 import ( # pylint: disable=E0611
36
31
  StopRunRequest,
@@ -38,27 +33,25 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
38
33
  )
39
34
  from flwr.proto.control_pb2_grpc import ControlStub
40
35
 
41
- from .utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
36
+ from .utils import flwr_cli_grpc_exc_handler, init_channel_from_connection
42
37
 
43
38
 
44
39
  def stop( # pylint: disable=R0914
40
+ ctx: typer.Context,
45
41
  run_id: Annotated[ # pylint: disable=unused-argument
46
42
  int,
47
43
  typer.Argument(help="The Flower run ID to stop"),
48
44
  ],
49
- app: Annotated[
50
- Path,
51
- typer.Argument(help="Path of the Flower project"),
52
- ] = Path("."),
53
- federation: Annotated[
54
- Optional[str],
55
- typer.Argument(help="Name of the federation"),
45
+ superlink: Annotated[
46
+ str | None,
47
+ typer.Argument(help="Name of the SuperLink connection."),
56
48
  ] = None,
57
49
  federation_config_overrides: Annotated[
58
- Optional[list[str]],
50
+ list[str] | None,
59
51
  typer.Option(
60
52
  "--federation-config",
61
53
  help=FEDERATION_CONFIG_HELP_MESSAGE,
54
+ hidden=True,
62
55
  ),
63
56
  ] = None,
64
57
  output_format: Annotated[
@@ -70,27 +63,29 @@ def stop( # pylint: disable=R0914
70
63
  ),
71
64
  ] = CliOutputFormat.DEFAULT,
72
65
  ) -> None:
73
- """Stop a run."""
66
+ """Stop a Flower run.
67
+
68
+ This command stops a running Flower App execution by sending a stop request to the
69
+ SuperLink via the Control API.
70
+ """
74
71
  suppress_output = output_format == CliOutputFormat.JSON
75
72
  captured_output = io.StringIO()
76
- try:
77
- if suppress_output:
78
- redirect_output(captured_output)
79
73
 
80
- # Load and validate federation config
81
- typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
74
+ if suppress_output:
75
+ redirect_output(captured_output)
82
76
 
83
- pyproject_path = app / FAB_CONFIG_FILE if app else None
84
- config, errors, warnings = load_and_validate(path=pyproject_path)
85
- config = process_loaded_project_config(config, errors, warnings)
86
- federation, federation_config = validate_federation_in_project_config(
87
- federation, config, federation_config_overrides
88
- )
89
- exit_if_no_address(federation_config, "stop")
90
- channel = None
77
+ # Warn `--federation-config` is ignored
78
+ warn_if_federation_config_overrides(federation_config_overrides)
79
+
80
+ migrate(superlink, args=ctx.args)
81
+
82
+ # Read superlink connection configuration
83
+ superlink_connection = read_superlink_connection(superlink)
84
+ channel = None
85
+
86
+ try:
91
87
  try:
92
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
93
- channel = init_channel(app, federation_config, auth_plugin)
88
+ channel = init_channel_from_connection(superlink_connection)
94
89
  stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
95
90
 
96
91
  typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
@@ -101,6 +96,7 @@ def stop( # pylint: disable=R0914
101
96
  f"❌ {err}",
102
97
  fg=typer.colors.RED,
103
98
  bold=True,
99
+ err=True,
104
100
  )
105
101
  raise typer.Exit(code=1) from err
106
102
  finally:
@@ -116,6 +112,7 @@ def stop( # pylint: disable=R0914
116
112
  f"{err}",
117
113
  fg=typer.colors.RED,
118
114
  bold=True,
115
+ err=True,
119
116
  )
120
117
  finally:
121
118
  if suppress_output:
@@ -124,7 +121,17 @@ def stop( # pylint: disable=R0914
124
121
 
125
122
 
126
123
  def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
127
- """Stop a run."""
124
+ """Stop a run and display the result.
125
+
126
+ Parameters
127
+ ----------
128
+ stub : ControlStub
129
+ The gRPC stub for Control API communication.
130
+ run_id : int
131
+ The unique identifier of the run to stop.
132
+ output_format : str
133
+ Output format ('default' or 'json').
134
+ """
128
135
  with flwr_cli_grpc_exc_handler():
129
136
  response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
130
137
  if response.success:
@@ -133,10 +140,12 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
133
140
  run_output = json.dumps(
134
141
  {
135
142
  "success": True,
136
- "run-id": run_id,
143
+ "run-id": f"{run_id}",
137
144
  }
138
145
  )
139
146
  restore_output()
140
147
  Console().print_json(run_output)
141
148
  else:
142
- typer.secho(f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED)
149
+ typer.secho(
150
+ f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED, err=True
151
+ )
@@ -0,0 +1,25 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower command line interface `supernode` command."""
16
+
17
+ from .ls import ls as ls
18
+ from .register import register as register
19
+ from .unregister import unregister as unregister
20
+
21
+ __all__ = [
22
+ "ls",
23
+ "register",
24
+ "unregister",
25
+ ]