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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. flwr/__init__.py +16 -5
  2. flwr/app/error.py +2 -2
  3. flwr/app/exception.py +3 -3
  4. flwr/cli/app.py +19 -0
  5. flwr/cli/{new/templates → app_cmd}/__init__.py +9 -1
  6. flwr/cli/app_cmd/publish.py +285 -0
  7. flwr/cli/app_cmd/review.py +262 -0
  8. flwr/cli/auth_plugin/auth_plugin.py +4 -5
  9. flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
  10. flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
  11. flwr/cli/build.py +60 -18
  12. flwr/cli/cli_account_auth_interceptor.py +24 -7
  13. flwr/cli/config_utils.py +101 -13
  14. flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +10 -1
  15. flwr/cli/federation/ls.py +140 -0
  16. flwr/cli/federation/show.py +318 -0
  17. flwr/cli/install.py +91 -13
  18. flwr/cli/log.py +52 -9
  19. flwr/cli/login/login.py +7 -4
  20. flwr/cli/ls.py +211 -130
  21. flwr/cli/new/new.py +123 -331
  22. flwr/cli/pull.py +10 -5
  23. flwr/cli/run/run.py +71 -29
  24. flwr/cli/run_utils.py +148 -0
  25. flwr/cli/stop.py +26 -8
  26. flwr/cli/supernode/ls.py +25 -12
  27. flwr/cli/supernode/register.py +9 -4
  28. flwr/cli/supernode/unregister.py +5 -3
  29. flwr/cli/utils.py +239 -16
  30. flwr/client/__init__.py +1 -1
  31. flwr/client/dpfedavg_numpy_client.py +4 -1
  32. flwr/client/grpc_adapter_client/connection.py +8 -9
  33. flwr/client/grpc_rere_client/connection.py +16 -14
  34. flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
  35. flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
  36. flwr/client/message_handler/message_handler.py +2 -2
  37. flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
  38. flwr/client/numpy_client.py +1 -1
  39. flwr/client/rest_client/connection.py +18 -18
  40. flwr/client/run_info_store.py +4 -5
  41. flwr/client/typing.py +1 -1
  42. flwr/clientapp/client_app.py +9 -10
  43. flwr/clientapp/mod/centraldp_mods.py +16 -17
  44. flwr/clientapp/mod/localdp_mod.py +8 -9
  45. flwr/clientapp/typing.py +1 -1
  46. flwr/clientapp/utils.py +3 -3
  47. flwr/common/address.py +1 -2
  48. flwr/common/args.py +3 -4
  49. flwr/common/config.py +13 -16
  50. flwr/common/constant.py +5 -2
  51. flwr/common/differential_privacy.py +3 -4
  52. flwr/common/event_log_plugin/event_log_plugin.py +3 -4
  53. flwr/common/exit/exit.py +15 -2
  54. flwr/common/exit/exit_code.py +19 -0
  55. flwr/common/exit/exit_handler.py +6 -2
  56. flwr/common/exit/signal_handler.py +5 -5
  57. flwr/common/grpc.py +6 -6
  58. flwr/common/inflatable_protobuf_utils.py +1 -1
  59. flwr/common/inflatable_utils.py +38 -21
  60. flwr/common/logger.py +19 -19
  61. flwr/common/message.py +4 -4
  62. flwr/common/object_ref.py +7 -7
  63. flwr/common/record/array.py +3 -3
  64. flwr/common/record/arrayrecord.py +18 -30
  65. flwr/common/record/configrecord.py +3 -3
  66. flwr/common/record/recorddict.py +5 -5
  67. flwr/common/record/typeddict.py +9 -2
  68. flwr/common/recorddict_compat.py +7 -10
  69. flwr/common/retry_invoker.py +20 -20
  70. flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
  71. flwr/common/serde.py +11 -4
  72. flwr/common/serde_utils.py +2 -2
  73. flwr/common/telemetry.py +9 -5
  74. flwr/common/typing.py +58 -37
  75. flwr/compat/client/app.py +38 -37
  76. flwr/compat/client/grpc_client/connection.py +11 -11
  77. flwr/compat/server/app.py +5 -6
  78. flwr/proto/appio_pb2.py +13 -3
  79. flwr/proto/appio_pb2.pyi +134 -65
  80. flwr/proto/appio_pb2_grpc.py +20 -0
  81. flwr/proto/appio_pb2_grpc.pyi +27 -0
  82. flwr/proto/clientappio_pb2.py +17 -7
  83. flwr/proto/clientappio_pb2.pyi +15 -0
  84. flwr/proto/clientappio_pb2_grpc.py +206 -40
  85. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  86. flwr/proto/control_pb2.py +71 -52
  87. flwr/proto/control_pb2.pyi +277 -111
  88. flwr/proto/control_pb2_grpc.py +249 -40
  89. flwr/proto/control_pb2_grpc.pyi +185 -52
  90. flwr/proto/error_pb2.py +13 -3
  91. flwr/proto/error_pb2.pyi +24 -6
  92. flwr/proto/error_pb2_grpc.py +20 -0
  93. flwr/proto/error_pb2_grpc.pyi +27 -0
  94. flwr/proto/fab_pb2.py +14 -4
  95. flwr/proto/fab_pb2.pyi +59 -31
  96. flwr/proto/fab_pb2_grpc.py +20 -0
  97. flwr/proto/fab_pb2_grpc.pyi +27 -0
  98. flwr/proto/federation_pb2.py +38 -0
  99. flwr/proto/federation_pb2.pyi +56 -0
  100. flwr/proto/federation_pb2_grpc.py +24 -0
  101. flwr/proto/federation_pb2_grpc.pyi +31 -0
  102. flwr/proto/fleet_pb2.py +24 -14
  103. flwr/proto/fleet_pb2.pyi +141 -61
  104. flwr/proto/fleet_pb2_grpc.py +189 -48
  105. flwr/proto/fleet_pb2_grpc.pyi +175 -61
  106. flwr/proto/grpcadapter_pb2.py +14 -4
  107. flwr/proto/grpcadapter_pb2.pyi +38 -16
  108. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  109. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  110. flwr/proto/heartbeat_pb2.py +17 -7
  111. flwr/proto/heartbeat_pb2.pyi +51 -22
  112. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  113. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  114. flwr/proto/log_pb2.py +13 -3
  115. flwr/proto/log_pb2.pyi +34 -11
  116. flwr/proto/log_pb2_grpc.py +20 -0
  117. flwr/proto/log_pb2_grpc.pyi +27 -0
  118. flwr/proto/message_pb2.py +15 -5
  119. flwr/proto/message_pb2.pyi +154 -86
  120. flwr/proto/message_pb2_grpc.py +20 -0
  121. flwr/proto/message_pb2_grpc.pyi +27 -0
  122. flwr/proto/node_pb2.py +15 -5
  123. flwr/proto/node_pb2.pyi +50 -25
  124. flwr/proto/node_pb2_grpc.py +20 -0
  125. flwr/proto/node_pb2_grpc.pyi +27 -0
  126. flwr/proto/recorddict_pb2.py +13 -3
  127. flwr/proto/recorddict_pb2.pyi +184 -107
  128. flwr/proto/recorddict_pb2_grpc.py +20 -0
  129. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  130. flwr/proto/run_pb2.py +40 -31
  131. flwr/proto/run_pb2.pyi +158 -84
  132. flwr/proto/run_pb2_grpc.py +20 -0
  133. flwr/proto/run_pb2_grpc.pyi +27 -0
  134. flwr/proto/serverappio_pb2.py +13 -3
  135. flwr/proto/serverappio_pb2.pyi +32 -8
  136. flwr/proto/serverappio_pb2_grpc.py +246 -65
  137. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  138. flwr/proto/simulationio_pb2.py +16 -8
  139. flwr/proto/simulationio_pb2.pyi +15 -0
  140. flwr/proto/simulationio_pb2_grpc.py +162 -41
  141. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  142. flwr/proto/transport_pb2.py +20 -10
  143. flwr/proto/transport_pb2.pyi +249 -160
  144. flwr/proto/transport_pb2_grpc.py +35 -4
  145. flwr/proto/transport_pb2_grpc.pyi +38 -8
  146. flwr/server/app.py +39 -17
  147. flwr/server/client_manager.py +4 -5
  148. flwr/server/client_proxy.py +10 -11
  149. flwr/server/compat/app.py +4 -5
  150. flwr/server/compat/app_utils.py +2 -1
  151. flwr/server/compat/grid_client_proxy.py +10 -12
  152. flwr/server/compat/legacy_context.py +3 -4
  153. flwr/server/fleet_event_log_interceptor.py +2 -1
  154. flwr/server/grid/grid.py +2 -3
  155. flwr/server/grid/grpc_grid.py +10 -8
  156. flwr/server/grid/inmemory_grid.py +4 -4
  157. flwr/server/run_serverapp.py +2 -3
  158. flwr/server/server.py +34 -39
  159. flwr/server/server_app.py +7 -8
  160. flwr/server/server_config.py +1 -2
  161. flwr/server/serverapp/app.py +34 -28
  162. flwr/server/serverapp_components.py +4 -5
  163. flwr/server/strategy/aggregate.py +9 -8
  164. flwr/server/strategy/bulyan.py +13 -11
  165. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  166. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  167. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  168. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  169. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  170. flwr/server/strategy/fedadagrad.py +18 -14
  171. flwr/server/strategy/fedadam.py +16 -14
  172. flwr/server/strategy/fedavg.py +16 -17
  173. flwr/server/strategy/fedavg_android.py +15 -15
  174. flwr/server/strategy/fedavgm.py +21 -18
  175. flwr/server/strategy/fedmedian.py +2 -3
  176. flwr/server/strategy/fedopt.py +11 -10
  177. flwr/server/strategy/fedprox.py +10 -9
  178. flwr/server/strategy/fedtrimmedavg.py +12 -11
  179. flwr/server/strategy/fedxgb_bagging.py +13 -11
  180. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  181. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  182. flwr/server/strategy/fedyogi.py +16 -14
  183. flwr/server/strategy/krum.py +12 -11
  184. flwr/server/strategy/qfedavg.py +16 -15
  185. flwr/server/strategy/strategy.py +6 -9
  186. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
  187. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  188. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  189. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  190. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  191. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
  192. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
  193. flwr/server/superlink/fleet/message_handler/message_handler.py +75 -30
  194. flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
  195. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  196. flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
  197. flwr/server/superlink/fleet/vce/vce_api.py +15 -9
  198. flwr/server/superlink/linkstate/in_memory_linkstate.py +148 -149
  199. flwr/server/superlink/linkstate/linkstate.py +91 -43
  200. flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
  201. flwr/server/superlink/linkstate/sqlite_linkstate.py +502 -436
  202. flwr/server/superlink/linkstate/utils.py +6 -6
  203. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  204. flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
  205. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  206. flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
  207. flwr/server/superlink/utils.py +4 -6
  208. flwr/server/typing.py +1 -1
  209. flwr/server/utils/tensorboard.py +15 -8
  210. flwr/server/workflow/default_workflows.py +5 -5
  211. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  212. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
  213. flwr/serverapp/strategy/bulyan.py +16 -15
  214. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  215. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  216. flwr/serverapp/strategy/fedadagrad.py +10 -11
  217. flwr/serverapp/strategy/fedadam.py +10 -11
  218. flwr/serverapp/strategy/fedavg.py +9 -10
  219. flwr/serverapp/strategy/fedavgm.py +17 -16
  220. flwr/serverapp/strategy/fedmedian.py +2 -2
  221. flwr/serverapp/strategy/fedopt.py +10 -11
  222. flwr/serverapp/strategy/fedprox.py +7 -8
  223. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  224. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  225. flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
  226. flwr/serverapp/strategy/fedyogi.py +9 -11
  227. flwr/serverapp/strategy/krum.py +7 -7
  228. flwr/serverapp/strategy/multikrum.py +9 -9
  229. flwr/serverapp/strategy/qfedavg.py +17 -16
  230. flwr/serverapp/strategy/strategy.py +6 -9
  231. flwr/serverapp/strategy/strategy_utils.py +7 -8
  232. flwr/simulation/app.py +46 -42
  233. flwr/simulation/legacy_app.py +12 -12
  234. flwr/simulation/ray_transport/ray_actor.py +10 -11
  235. flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
  236. flwr/simulation/run_simulation.py +43 -43
  237. flwr/simulation/simulationio_connection.py +4 -4
  238. flwr/supercore/cli/flower_superexec.py +3 -4
  239. flwr/supercore/constant.py +34 -1
  240. flwr/supercore/corestate/corestate.py +24 -3
  241. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  242. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  243. flwr/supercore/ffs/disk_ffs.py +1 -2
  244. flwr/supercore/ffs/ffs.py +1 -2
  245. flwr/supercore/ffs/ffs_factory.py +1 -2
  246. flwr/{common → supercore}/heartbeat.py +20 -25
  247. flwr/supercore/object_store/in_memory_object_store.py +1 -2
  248. flwr/supercore/object_store/object_store.py +1 -2
  249. flwr/supercore/object_store/object_store_factory.py +1 -2
  250. flwr/supercore/object_store/sqlite_object_store.py +8 -7
  251. flwr/supercore/primitives/asymmetric.py +1 -1
  252. flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
  253. flwr/supercore/sqlite_mixin.py +37 -34
  254. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  255. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  256. flwr/supercore/superexec/run_superexec.py +9 -13
  257. flwr/supercore/utils.py +190 -0
  258. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  259. flwr/superlink/auth_plugin/auth_plugin.py +6 -9
  260. flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
  261. flwr/{cli/new/templates/app → superlink/federation}/__init__.py +10 -1
  262. flwr/superlink/federation/federation_manager.py +64 -0
  263. flwr/superlink/federation/noop_federation_manager.py +71 -0
  264. flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
  265. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  266. flwr/superlink/servicer/control/control_grpc.py +7 -6
  267. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  268. flwr/superlink/servicer/control/control_servicer.py +190 -23
  269. flwr/supernode/cli/flower_supernode.py +58 -3
  270. flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
  271. flwr/supernode/nodestate/nodestate.py +52 -8
  272. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  273. flwr/supernode/runtime/run_clientapp.py +41 -22
  274. flwr/supernode/servicer/clientappio/clientappio_servicer.py +46 -10
  275. flwr/supernode/start_client_internal.py +165 -46
  276. {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/METADATA +9 -11
  277. flwr-1.25.0.dist-info/RECORD +393 -0
  278. flwr/cli/new/templates/app/.gitignore.tpl +0 -163
  279. flwr/cli/new/templates/app/LICENSE.tpl +0 -202
  280. flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
  281. flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
  282. flwr/cli/new/templates/app/README.md.tpl +0 -37
  283. flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
  284. flwr/cli/new/templates/app/code/__init__.py +0 -15
  285. flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
  286. flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
  287. flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
  288. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
  289. flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
  290. flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
  291. flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
  292. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
  293. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
  294. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
  295. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
  296. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
  297. flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
  298. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
  299. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
  300. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
  301. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
  302. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
  303. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
  304. flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
  305. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
  306. flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
  307. flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
  308. flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
  309. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
  310. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
  311. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
  312. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
  313. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
  314. flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
  315. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
  316. flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
  317. flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
  318. flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
  319. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
  320. flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
  321. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
  322. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
  323. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
  324. flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
  325. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
  326. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
  327. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
  328. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
  329. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
  330. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
  331. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
  332. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
  333. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
  334. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
  335. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
  336. flwr/supercore/object_store/utils.py +0 -43
  337. flwr-1.23.0.dist-info/RECORD +0 -439
  338. {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/WHEEL +0 -0
  339. {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py CHANGED
@@ -20,7 +20,7 @@ import io
20
20
  import json
21
21
  import subprocess
22
22
  from pathlib import Path
23
- from typing import Annotated, Any, Optional, cast
23
+ from typing import Annotated, Any, cast
24
24
 
25
25
  import typer
26
26
  from rich.console import Console
@@ -45,6 +45,8 @@ from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_
45
45
  from flwr.common.typing import Fab
46
46
  from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
47
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
48
50
 
49
51
  from ..log import start_stream
50
52
  from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
@@ -52,18 +54,18 @@ from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugi
52
54
  CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
53
55
 
54
56
 
55
- # pylint: disable-next=too-many-locals, R0913, R0917
57
+ # pylint: disable-next=too-many-locals, too-many-branches, R0913, R0917
56
58
  def run(
57
59
  app: Annotated[
58
60
  Path,
59
61
  typer.Argument(help="Path of the Flower App to run."),
60
62
  ] = Path("."),
61
63
  federation: Annotated[
62
- Optional[str],
64
+ str | None,
63
65
  typer.Argument(help="Name of the federation to run the app on."),
64
66
  ] = None,
65
67
  run_config_overrides: Annotated[
66
- Optional[list[str]],
68
+ list[str] | None,
67
69
  typer.Option(
68
70
  "--run-config",
69
71
  "-c",
@@ -71,7 +73,7 @@ def run(
71
73
  ),
72
74
  ] = None,
73
75
  federation_config_overrides: Annotated[
74
- Optional[list[str]],
76
+ list[str] | None,
75
77
  typer.Option(
76
78
  "--federation-config",
77
79
  help=FEDERATION_CONFIG_HELP_MESSAGE,
@@ -100,11 +102,32 @@ def run(
100
102
  try:
101
103
  if suppress_output:
102
104
  redirect_output(captured_output)
105
+
106
+ # Determine if app is remote
107
+ app_spec = None
108
+ if (app_str := str(app)).startswith("@"):
109
+ # Validate app version and ID format
110
+ try:
111
+ _ = parse_app_spec(app_str)
112
+ except ValueError as e:
113
+ typer.secho(f"❌ {e}", fg=typer.colors.RED, err=True)
114
+ raise typer.Exit(code=1) from e
115
+
116
+ app_spec = app_str
117
+ # Set `app` to current directory for credential storage
118
+ app = Path(".")
119
+ is_remote_app = app_spec is not None
120
+
103
121
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
104
122
 
105
- pyproject_path = app / "pyproject.toml" if app else None
106
- config, errors, warnings = load_and_validate(path=pyproject_path)
123
+ # Disable the validation for remote apps
124
+ pyproject_path = app / "pyproject.toml" if not is_remote_app else None
125
+ # `./pyproject.toml` will be loaded when `pyproject_path` is None
126
+ config, errors, warnings = load_and_validate(
127
+ pyproject_path, check_module=not is_remote_app
128
+ )
107
129
  config = process_loaded_project_config(config, errors, warnings)
130
+
108
131
  federation, federation_config = validate_federation_in_project_config(
109
132
  federation, config, federation_config_overrides
110
133
  )
@@ -117,6 +140,7 @@ def run(
117
140
  run_config_overrides,
118
141
  stream,
119
142
  output_format,
143
+ app_spec,
120
144
  )
121
145
  else:
122
146
  _run_without_control_api(
@@ -132,6 +156,7 @@ def run(
132
156
  f"{err}",
133
157
  fg=typer.colors.RED,
134
158
  bold=True,
159
+ err=True,
135
160
  )
136
161
  finally:
137
162
  if suppress_output:
@@ -144,22 +169,32 @@ def _run_with_control_api(
144
169
  app: Path,
145
170
  federation: str,
146
171
  federation_config: dict[str, Any],
147
- config_overrides: Optional[list[str]],
172
+ config_overrides: list[str] | None,
148
173
  stream: bool,
149
174
  output_format: str,
175
+ app_spec: str | None,
150
176
  ) -> None:
151
177
  channel = None
178
+ is_remote_app = app_spec is not None
152
179
  try:
153
180
  auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
154
181
  channel = init_channel(app, federation_config, auth_plugin)
155
182
  stub = ControlStub(channel)
156
183
 
157
- fab_bytes = build_fab_from_disk(app)
158
- fab_hash = hashlib.sha256(fab_bytes).hexdigest()
159
- config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
160
- fab_id, fab_version = get_metadata_from_config(config)
184
+ # Build FAB if local app
185
+ if not is_remote_app:
186
+ fab_bytes = build_fab_from_disk(app)
187
+ fab_hash = hashlib.sha256(fab_bytes).hexdigest()
188
+ config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
189
+ fab_id, fab_version = get_metadata_from_config(config)
190
+ fab = Fab(fab_hash, fab_bytes, {})
191
+ # Skip FAB build if remote app
192
+ else:
193
+ # Use empty values for FAB
194
+ fab_id = fab_version = fab_hash = ""
195
+ fab = Fab(fab_hash, b"", {})
161
196
 
162
- fab = Fab(fab_hash, fab_bytes, {})
197
+ real_federation: str = federation_config.get("federation", NOOP_FEDERATION)
163
198
 
164
199
  # Construct a `ConfigRecord` out of a flattened `UserConfig`
165
200
  fed_config = flatten_dict(federation_config.get("options", {}))
@@ -168,7 +203,9 @@ def _run_with_control_api(
168
203
  req = StartRunRequest(
169
204
  fab=fab_to_proto(fab),
170
205
  override_config=user_config_to_proto(parse_config_args(config_overrides)),
206
+ federation=real_federation,
171
207
  federation_options=config_record_to_proto(c_record),
208
+ app_spec=app_spec or "",
172
209
  )
173
210
  with flwr_cli_grpc_exc_handler():
174
211
  res = stub.StartRun(req)
@@ -178,23 +215,27 @@ def _run_with_control_api(
178
215
  f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN
179
216
  )
180
217
  else:
181
- typer.secho("❌ Failed to start run", fg=typer.colors.RED)
218
+ typer.secho("❌ Failed to start run", fg=typer.colors.RED, err=True)
182
219
  raise typer.Exit(code=1)
183
220
 
184
221
  if output_format == CliOutputFormat.JSON:
185
- run_output = json.dumps(
186
- {
187
- "success": res.HasField("run_id"),
188
- "run-id": res.run_id if res.HasField("run_id") else None,
189
- "fab-id": fab_id,
190
- "fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
191
- "fab-version": fab_version,
192
- "fab-hash": fab_hash[:8],
193
- "fab-filename": get_fab_filename(config, fab_hash),
194
- }
195
- )
222
+ # Only include FAB metadata if we actually built a local FAB
223
+ payload: dict[str, Any] = {
224
+ "success": res.HasField("run_id"),
225
+ "run-id": f"{res.run_id}" if res.HasField("run_id") else None,
226
+ }
227
+ if not is_remote_app:
228
+ payload.update(
229
+ {
230
+ "fab-id": fab_id,
231
+ "fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
232
+ "fab-version": fab_version,
233
+ "fab-hash": fab_hash[:8],
234
+ "fab-filename": get_fab_filename(config, fab_hash),
235
+ }
236
+ )
196
237
  restore_output()
197
- Console().print_json(run_output)
238
+ Console().print_json(json.dumps(payload))
198
239
 
199
240
  if stream:
200
241
  start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
@@ -204,14 +245,14 @@ def _run_with_control_api(
204
245
 
205
246
 
206
247
  def _run_without_control_api(
207
- app: Optional[Path],
248
+ app: Path | None,
208
249
  federation_config: dict[str, Any],
209
- config_overrides: Optional[list[str]],
250
+ config_overrides: list[str] | None,
210
251
  federation: str,
211
252
  ) -> None:
212
253
  try:
213
254
  num_supernodes = federation_config["options"]["num-supernodes"]
214
- verbose: Optional[bool] = federation_config["options"].get("verbose")
255
+ verbose: bool | None = federation_config["options"].get("verbose")
215
256
  backend_cfg = federation_config["options"].get("backend", {})
216
257
  except KeyError as err:
217
258
  typer.secho(
@@ -222,6 +263,7 @@ def _run_without_control_api(
222
263
  "options.num-supernodes = 10\n",
223
264
  fg=typer.colors.RED,
224
265
  bold=True,
266
+ err=True,
225
267
  )
226
268
  raise typer.Exit(code=1) from err
227
269
 
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.date import isoformat8601_utc
22
+ from flwr.common.typing import Run
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
@@ -18,7 +18,7 @@
18
18
  import io
19
19
  import json
20
20
  from pathlib import Path
21
- from typing import Annotated, Optional
21
+ from typing import Annotated
22
22
 
23
23
  import typer
24
24
  from rich.console import Console
@@ -51,11 +51,11 @@ def stop( # pylint: disable=R0914
51
51
  typer.Argument(help="Path of the Flower project"),
52
52
  ] = Path("."),
53
53
  federation: Annotated[
54
- Optional[str],
54
+ str | None,
55
55
  typer.Argument(help="Name of the federation"),
56
56
  ] = None,
57
57
  federation_config_overrides: Annotated[
58
- Optional[list[str]],
58
+ list[str] | None,
59
59
  typer.Option(
60
60
  "--federation-config",
61
61
  help=FEDERATION_CONFIG_HELP_MESSAGE,
@@ -70,7 +70,11 @@ def stop( # pylint: disable=R0914
70
70
  ),
71
71
  ] = CliOutputFormat.DEFAULT,
72
72
  ) -> None:
73
- """Stop a run."""
73
+ """Stop a Flower run.
74
+
75
+ This command stops a running Flower App execution by sending a stop request to the
76
+ SuperLink via the Control API.
77
+ """
74
78
  suppress_output = output_format == CliOutputFormat.JSON
75
79
  captured_output = io.StringIO()
76
80
  try:
@@ -81,7 +85,7 @@ def stop( # pylint: disable=R0914
81
85
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
82
86
 
83
87
  pyproject_path = app / FAB_CONFIG_FILE if app else None
84
- config, errors, warnings = load_and_validate(path=pyproject_path)
88
+ config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
85
89
  config = process_loaded_project_config(config, errors, warnings)
86
90
  federation, federation_config = validate_federation_in_project_config(
87
91
  federation, config, federation_config_overrides
@@ -101,6 +105,7 @@ def stop( # pylint: disable=R0914
101
105
  f"❌ {err}",
102
106
  fg=typer.colors.RED,
103
107
  bold=True,
108
+ err=True,
104
109
  )
105
110
  raise typer.Exit(code=1) from err
106
111
  finally:
@@ -116,6 +121,7 @@ def stop( # pylint: disable=R0914
116
121
  f"{err}",
117
122
  fg=typer.colors.RED,
118
123
  bold=True,
124
+ err=True,
119
125
  )
120
126
  finally:
121
127
  if suppress_output:
@@ -124,7 +130,17 @@ def stop( # pylint: disable=R0914
124
130
 
125
131
 
126
132
  def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
127
- """Stop a run."""
133
+ """Stop a run and display the result.
134
+
135
+ Parameters
136
+ ----------
137
+ stub : ControlStub
138
+ The gRPC stub for Control API communication.
139
+ run_id : int
140
+ The unique identifier of the run to stop.
141
+ output_format : str
142
+ Output format ('default' or 'json').
143
+ """
128
144
  with flwr_cli_grpc_exc_handler():
129
145
  response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
130
146
  if response.success:
@@ -133,10 +149,12 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
133
149
  run_output = json.dumps(
134
150
  {
135
151
  "success": True,
136
- "run-id": run_id,
152
+ "run-id": f"{run_id}",
137
153
  }
138
154
  )
139
155
  restore_output()
140
156
  Console().print_json(run_output)
141
157
  else:
142
- typer.secho(f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED)
158
+ typer.secho(
159
+ f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED, err=True
160
+ )
flwr/cli/supernode/ls.py CHANGED
@@ -19,7 +19,7 @@ import io
19
19
  import json
20
20
  from datetime import datetime, timedelta
21
21
  from pathlib import Path
22
- from typing import Annotated, Optional, cast
22
+ from typing import Annotated, cast
23
23
 
24
24
  import typer
25
25
  from rich.console import Console
@@ -32,8 +32,8 @@ from flwr.cli.config_utils import (
32
32
  process_loaded_project_config,
33
33
  validate_federation_in_project_config,
34
34
  )
35
- from flwr.common.constant import FAB_CONFIG_FILE, NOOP_FLWR_AID, CliOutputFormat
36
- from flwr.common.date import format_timedelta, isoformat8601_utc
35
+ from flwr.common.constant import FAB_CONFIG_FILE, NOOP_ACCOUNT_NAME, CliOutputFormat
36
+ from flwr.common.date import isoformat8601_utc
37
37
  from flwr.common.logger import print_json_error, redirect_output, restore_output
38
38
  from flwr.proto.control_pb2 import ( # pylint: disable=E0611
39
39
  ListNodesRequest,
@@ -41,10 +41,11 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
41
41
  )
42
42
  from flwr.proto.control_pb2_grpc import ControlStub
43
43
  from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
44
+ from flwr.supercore.utils import humanize_duration
44
45
 
45
46
  from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
46
47
 
47
- _NodeListType = tuple[int, str, str, str, str, str, str, str]
48
+ _NodeListType = tuple[int, str, str, str, str, str, str, str, float]
48
49
 
49
50
 
50
51
  def ls( # pylint: disable=R0914, R0913, R0917
@@ -54,7 +55,7 @@ def ls( # pylint: disable=R0914, R0913, R0917
54
55
  typer.Argument(help="Path of the Flower project"),
55
56
  ] = Path("."),
56
57
  federation: Annotated[
57
- Optional[str],
58
+ str | None,
58
59
  typer.Argument(help="Name of the federation"),
59
60
  ] = None,
60
61
  output_format: Annotated[
@@ -86,7 +87,7 @@ def ls( # pylint: disable=R0914, R0913, R0917
86
87
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
87
88
 
88
89
  pyproject_path = app / FAB_CONFIG_FILE if app else None
89
- config, errors, warnings = load_and_validate(path=pyproject_path)
90
+ config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
90
91
  config = process_loaded_project_config(config, errors, warnings)
91
92
  federation, federation_config = validate_federation_in_project_config(
92
93
  federation, config
@@ -138,7 +139,7 @@ def _format_nodes(
138
139
  ) -> list[_NodeListType]:
139
140
  """Format node information for display."""
140
141
 
141
- def _format_datetime(dt_str: Optional[str]) -> str:
142
+ def _format_datetime(dt_str: str | None) -> str:
142
143
  dt = datetime.fromisoformat(dt_str) if dt_str else None
143
144
  return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
144
145
 
@@ -160,12 +161,13 @@ def _format_nodes(
160
161
  (
161
162
  node.node_id,
162
163
  node.owner_aid,
164
+ node.owner_name,
163
165
  node.status,
164
166
  _format_datetime(node.registered_at),
165
167
  _format_datetime(node.last_activated_at),
166
168
  _format_datetime(node.last_deactivated_at),
167
169
  _format_datetime(node.unregistered_at),
168
- format_timedelta(elapsed_time_activated),
170
+ elapsed_time_activated.total_seconds(),
169
171
  )
170
172
  )
171
173
 
@@ -188,7 +190,8 @@ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
188
190
  for row in nodes_info:
189
191
  (
190
192
  node_id,
191
- owner_aid,
193
+ _,
194
+ owner_name,
192
195
  status,
193
196
  _,
194
197
  last_activated_at,
@@ -216,9 +219,17 @@ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
216
219
 
217
220
  formatted_row = (
218
221
  f"[bold]{node_id}[/bold]",
219
- f"{owner_aid}" if owner_aid != NOOP_FLWR_AID else f"[dim]{owner_aid}[/dim]",
222
+ (
223
+ f"{owner_name}"
224
+ if owner_name != NOOP_ACCOUNT_NAME
225
+ else f"[dim]{owner_name}[/dim]"
226
+ ),
220
227
  f"[{status_style}]{status}",
221
- f"[cyan]{elapse_activated}[/cyan]" if status == "online" else "",
228
+ (
229
+ f"[cyan]{humanize_duration(elapse_activated)}[/cyan]"
230
+ if status == "online"
231
+ else ""
232
+ ),
222
233
  time_at,
223
234
  )
224
235
  table.add_row(*formatted_row)
@@ -233,6 +244,7 @@ def _to_json(nodes_info: list[_NodeListType], verbose: bool) -> str:
233
244
  (
234
245
  node_id,
235
246
  owner_aid,
247
+ owner_name,
236
248
  status,
237
249
  created_at,
238
250
  activated_at,
@@ -246,8 +258,9 @@ def _to_json(nodes_info: list[_NodeListType], verbose: bool) -> str:
246
258
 
247
259
  nodes_list.append(
248
260
  {
249
- "node-id": node_id,
261
+ "node-id": f"{node_id}",
250
262
  "owner-aid": owner_aid,
263
+ "owner-name": owner_name,
251
264
  "status": status,
252
265
  "created-at": created_at,
253
266
  "online-at": activated_at,
@@ -18,7 +18,7 @@
18
18
  import io
19
19
  import json
20
20
  from pathlib import Path
21
- from typing import Annotated, Optional
21
+ from typing import Annotated
22
22
 
23
23
  import typer
24
24
  from cryptography.exceptions import UnsupportedAlgorithm
@@ -57,7 +57,7 @@ def register( # pylint: disable=R0914
57
57
  typer.Argument(help="Path of the Flower project"),
58
58
  ] = Path("."),
59
59
  federation: Annotated[
60
- Optional[str],
60
+ str | None,
61
61
  typer.Argument(help="Name of the federation"),
62
62
  ] = None,
63
63
  output_format: Annotated[
@@ -85,7 +85,7 @@ def register( # pylint: disable=R0914
85
85
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
86
86
 
87
87
  pyproject_path = app / FAB_CONFIG_FILE if app else None
88
- config, errors, warnings = load_and_validate(path=pyproject_path)
88
+ config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
89
89
  config = process_loaded_project_config(config, errors, warnings)
90
90
  federation, federation_config = validate_federation_in_project_config(
91
91
  federation, config
@@ -107,6 +107,7 @@ def register( # pylint: disable=R0914
107
107
  f"❌ {err}",
108
108
  fg=typer.colors.RED,
109
109
  bold=True,
110
+ err=True,
110
111
  )
111
112
  raise typer.Exit(code=1) from err
112
113
  finally:
@@ -123,6 +124,7 @@ def register( # pylint: disable=R0914
123
124
  f"{err}",
124
125
  fg=typer.colors.RED,
125
126
  bold=True,
127
+ err=True,
126
128
  )
127
129
  finally:
128
130
  if suppress_output:
@@ -151,7 +153,9 @@ def _register_node(stub: ControlStub, public_key: bytes, output_format: str) ->
151
153
  restore_output()
152
154
  Console().print_json(run_output)
153
155
  else:
154
- typer.secho("❌ SuperNode couldn't be registered.", fg=typer.colors.RED)
156
+ typer.secho(
157
+ "❌ SuperNode couldn't be registered.", fg=typer.colors.RED, err=True
158
+ )
155
159
 
156
160
 
157
161
  def try_load_public_key(public_key_path: Path) -> bytes:
@@ -161,6 +165,7 @@ def try_load_public_key(public_key_path: Path) -> bytes:
161
165
  f"❌ Public key file '{public_key_path}' does not exist.",
162
166
  fg=typer.colors.RED,
163
167
  bold=True,
168
+ err=True,
164
169
  )
165
170
  raise typer.Exit(code=1)
166
171
 
@@ -18,7 +18,7 @@
18
18
  import io
19
19
  import json
20
20
  from pathlib import Path
21
- from typing import Annotated, Optional
21
+ from typing import Annotated
22
22
 
23
23
  import typer
24
24
  from rich.console import Console
@@ -49,7 +49,7 @@ def unregister( # pylint: disable=R0914
49
49
  typer.Argument(help="Path of the Flower project"),
50
50
  ] = Path("."),
51
51
  federation: Annotated[
52
- Optional[str],
52
+ str | None,
53
53
  typer.Argument(help="Name of the federation"),
54
54
  ] = None,
55
55
  output_format: Annotated[
@@ -73,7 +73,7 @@ def unregister( # pylint: disable=R0914
73
73
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
74
74
 
75
75
  pyproject_path = app / FAB_CONFIG_FILE if app else None
76
- config, errors, warnings = load_and_validate(path=pyproject_path)
76
+ config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
77
77
  config = process_loaded_project_config(config, errors, warnings)
78
78
  federation, federation_config = validate_federation_in_project_config(
79
79
  federation, config
@@ -93,6 +93,7 @@ def unregister( # pylint: disable=R0914
93
93
  f"❌ {err}",
94
94
  fg=typer.colors.RED,
95
95
  bold=True,
96
+ err=True,
96
97
  )
97
98
  raise typer.Exit(code=1) from err
98
99
  finally:
@@ -109,6 +110,7 @@ def unregister( # pylint: disable=R0914
109
110
  f"{err}",
110
111
  fg=typer.colors.RED,
111
112
  bold=True,
113
+ err=True,
112
114
  )
113
115
  finally:
114
116
  if suppress_output: