flwr 1.22.0__py3-none-any.whl → 1.24.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 (301) 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 +34 -1
  5. flwr/cli/app_cmd/__init__.py +23 -0
  6. flwr/cli/app_cmd/publish.py +285 -0
  7. flwr/cli/app_cmd/review.py +252 -0
  8. flwr/cli/auth_plugin/__init__.py +15 -6
  9. flwr/cli/auth_plugin/auth_plugin.py +94 -0
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
  12. flwr/cli/build.py +166 -53
  13. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
  14. flwr/cli/config_utils.py +101 -13
  15. flwr/cli/federation/__init__.py +24 -0
  16. flwr/cli/federation/ls.py +140 -0
  17. flwr/cli/federation/show.py +317 -0
  18. flwr/cli/install.py +91 -13
  19. flwr/cli/log.py +54 -11
  20. flwr/cli/login/login.py +41 -27
  21. flwr/cli/ls.py +177 -133
  22. flwr/cli/new/new.py +175 -40
  23. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
  24. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  26. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  28. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  29. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  30. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  31. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  33. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
  34. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
  35. flwr/cli/pull.py +12 -7
  36. flwr/cli/run/run.py +82 -31
  37. flwr/cli/run_utils.py +130 -0
  38. flwr/cli/stop.py +27 -9
  39. flwr/cli/supernode/__init__.py +25 -0
  40. flwr/cli/supernode/ls.py +268 -0
  41. flwr/cli/supernode/register.py +190 -0
  42. flwr/cli/supernode/unregister.py +140 -0
  43. flwr/cli/utils.py +464 -81
  44. flwr/client/__init__.py +2 -1
  45. flwr/client/dpfedavg_numpy_client.py +4 -1
  46. flwr/client/grpc_adapter_client/connection.py +12 -15
  47. flwr/client/grpc_rere_client/connection.py +68 -41
  48. flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
  49. flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
  50. flwr/client/message_handler/message_handler.py +2 -2
  51. flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
  52. flwr/client/numpy_client.py +1 -1
  53. flwr/client/rest_client/connection.py +94 -51
  54. flwr/client/run_info_store.py +4 -5
  55. flwr/client/typing.py +1 -1
  56. flwr/clientapp/__init__.py +1 -2
  57. flwr/{client → clientapp}/client_app.py +9 -10
  58. flwr/clientapp/mod/centraldp_mods.py +16 -17
  59. flwr/clientapp/mod/localdp_mod.py +8 -9
  60. flwr/clientapp/typing.py +1 -1
  61. flwr/{client/clientapp → clientapp}/utils.py +4 -4
  62. flwr/common/address.py +1 -2
  63. flwr/common/args.py +3 -4
  64. flwr/common/config.py +13 -16
  65. flwr/common/constant.py +56 -13
  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 +15 -2
  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 +6 -6
  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 +4 -4
  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 +3 -3
  87. flwr/common/serde.py +9 -6
  88. flwr/common/serde_utils.py +2 -2
  89. flwr/common/telemetry.py +9 -5
  90. flwr/common/typing.py +59 -43
  91. flwr/compat/client/app.py +39 -38
  92. flwr/compat/client/grpc_client/connection.py +13 -13
  93. flwr/compat/server/app.py +5 -6
  94. flwr/proto/appio_pb2.py +13 -3
  95. flwr/proto/appio_pb2.pyi +134 -65
  96. flwr/proto/appio_pb2_grpc.py +20 -0
  97. flwr/proto/appio_pb2_grpc.pyi +27 -0
  98. flwr/proto/clientappio_pb2.py +17 -7
  99. flwr/proto/clientappio_pb2.pyi +15 -0
  100. flwr/proto/clientappio_pb2_grpc.py +206 -40
  101. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  102. flwr/proto/control_pb2.py +72 -40
  103. flwr/proto/control_pb2.pyi +319 -87
  104. flwr/proto/control_pb2_grpc.py +339 -28
  105. flwr/proto/control_pb2_grpc.pyi +209 -37
  106. flwr/proto/error_pb2.py +13 -3
  107. flwr/proto/error_pb2.pyi +24 -6
  108. flwr/proto/error_pb2_grpc.py +20 -0
  109. flwr/proto/error_pb2_grpc.pyi +27 -0
  110. flwr/proto/fab_pb2.py +24 -10
  111. flwr/proto/fab_pb2.pyi +68 -20
  112. flwr/proto/fab_pb2_grpc.py +20 -0
  113. flwr/proto/fab_pb2_grpc.pyi +27 -0
  114. flwr/proto/federation_pb2.py +38 -0
  115. flwr/proto/federation_pb2.pyi +56 -0
  116. flwr/proto/federation_pb2_grpc.py +24 -0
  117. flwr/proto/federation_pb2_grpc.pyi +31 -0
  118. flwr/proto/fleet_pb2.py +45 -27
  119. flwr/proto/fleet_pb2.pyi +186 -70
  120. flwr/proto/fleet_pb2_grpc.py +277 -66
  121. flwr/proto/fleet_pb2_grpc.pyi +201 -55
  122. flwr/proto/grpcadapter_pb2.py +14 -4
  123. flwr/proto/grpcadapter_pb2.pyi +38 -16
  124. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  125. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  126. flwr/proto/heartbeat_pb2.py +17 -7
  127. flwr/proto/heartbeat_pb2.pyi +51 -22
  128. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  129. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  130. flwr/proto/log_pb2.py +13 -3
  131. flwr/proto/log_pb2.pyi +34 -11
  132. flwr/proto/log_pb2_grpc.py +20 -0
  133. flwr/proto/log_pb2_grpc.pyi +27 -0
  134. flwr/proto/message_pb2.py +15 -5
  135. flwr/proto/message_pb2.pyi +154 -86
  136. flwr/proto/message_pb2_grpc.py +20 -0
  137. flwr/proto/message_pb2_grpc.pyi +27 -0
  138. flwr/proto/node_pb2.py +16 -4
  139. flwr/proto/node_pb2.pyi +77 -4
  140. flwr/proto/node_pb2_grpc.py +20 -0
  141. flwr/proto/node_pb2_grpc.pyi +27 -0
  142. flwr/proto/recorddict_pb2.py +13 -3
  143. flwr/proto/recorddict_pb2.pyi +184 -107
  144. flwr/proto/recorddict_pb2_grpc.py +20 -0
  145. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  146. flwr/proto/run_pb2.py +40 -31
  147. flwr/proto/run_pb2.pyi +149 -84
  148. flwr/proto/run_pb2_grpc.py +20 -0
  149. flwr/proto/run_pb2_grpc.pyi +27 -0
  150. flwr/proto/serverappio_pb2.py +13 -3
  151. flwr/proto/serverappio_pb2.pyi +32 -8
  152. flwr/proto/serverappio_pb2_grpc.py +246 -65
  153. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  154. flwr/proto/simulationio_pb2.py +16 -8
  155. flwr/proto/simulationio_pb2.pyi +15 -0
  156. flwr/proto/simulationio_pb2_grpc.py +162 -41
  157. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  158. flwr/proto/transport_pb2.py +20 -10
  159. flwr/proto/transport_pb2.pyi +249 -160
  160. flwr/proto/transport_pb2_grpc.py +35 -4
  161. flwr/proto/transport_pb2_grpc.pyi +38 -8
  162. flwr/server/app.py +173 -127
  163. flwr/server/client_manager.py +4 -5
  164. flwr/server/client_proxy.py +10 -11
  165. flwr/server/compat/app.py +4 -5
  166. flwr/server/compat/app_utils.py +2 -1
  167. flwr/server/compat/grid_client_proxy.py +10 -12
  168. flwr/server/compat/legacy_context.py +3 -4
  169. flwr/server/fleet_event_log_interceptor.py +2 -1
  170. flwr/server/grid/grid.py +2 -3
  171. flwr/server/grid/grpc_grid.py +10 -8
  172. flwr/server/grid/inmemory_grid.py +4 -4
  173. flwr/server/run_serverapp.py +2 -3
  174. flwr/server/server.py +34 -39
  175. flwr/server/server_app.py +7 -8
  176. flwr/server/server_config.py +1 -2
  177. flwr/server/serverapp/app.py +34 -28
  178. flwr/server/serverapp_components.py +4 -5
  179. flwr/server/strategy/aggregate.py +9 -8
  180. flwr/server/strategy/bulyan.py +13 -11
  181. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  182. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  183. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  184. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  185. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  186. flwr/server/strategy/fedadagrad.py +18 -14
  187. flwr/server/strategy/fedadam.py +16 -14
  188. flwr/server/strategy/fedavg.py +16 -17
  189. flwr/server/strategy/fedavg_android.py +15 -15
  190. flwr/server/strategy/fedavgm.py +21 -18
  191. flwr/server/strategy/fedmedian.py +2 -3
  192. flwr/server/strategy/fedopt.py +11 -10
  193. flwr/server/strategy/fedprox.py +10 -9
  194. flwr/server/strategy/fedtrimmedavg.py +12 -11
  195. flwr/server/strategy/fedxgb_bagging.py +13 -11
  196. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  197. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  198. flwr/server/strategy/fedyogi.py +16 -14
  199. flwr/server/strategy/krum.py +12 -11
  200. flwr/server/strategy/qfedavg.py +16 -15
  201. flwr/server/strategy/strategy.py +6 -9
  202. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
  203. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  204. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  206. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  207. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
  208. flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
  209. flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
  210. flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
  211. flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
  212. flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
  213. flwr/server/superlink/fleet/vce/vce_api.py +32 -13
  214. flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
  215. flwr/server/superlink/linkstate/linkstate.py +161 -62
  216. flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
  217. flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
  218. flwr/server/superlink/linkstate/utils.py +9 -60
  219. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  220. flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
  221. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  222. flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
  223. flwr/server/superlink/utils.py +4 -6
  224. flwr/server/typing.py +1 -1
  225. flwr/server/utils/tensorboard.py +15 -8
  226. flwr/server/utils/validator.py +2 -3
  227. flwr/server/workflow/default_workflows.py +5 -5
  228. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  229. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
  230. flwr/serverapp/strategy/bulyan.py +16 -15
  231. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  232. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  233. flwr/serverapp/strategy/fedadagrad.py +10 -11
  234. flwr/serverapp/strategy/fedadam.py +10 -11
  235. flwr/serverapp/strategy/fedavg.py +9 -10
  236. flwr/serverapp/strategy/fedavgm.py +17 -16
  237. flwr/serverapp/strategy/fedmedian.py +2 -2
  238. flwr/serverapp/strategy/fedopt.py +10 -11
  239. flwr/serverapp/strategy/fedprox.py +7 -8
  240. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  241. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  242. flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
  243. flwr/serverapp/strategy/fedyogi.py +9 -11
  244. flwr/serverapp/strategy/krum.py +7 -7
  245. flwr/serverapp/strategy/multikrum.py +9 -9
  246. flwr/serverapp/strategy/qfedavg.py +17 -16
  247. flwr/serverapp/strategy/strategy.py +6 -9
  248. flwr/serverapp/strategy/strategy_utils.py +7 -8
  249. flwr/simulation/app.py +46 -42
  250. flwr/simulation/legacy_app.py +12 -12
  251. flwr/simulation/ray_transport/ray_actor.py +11 -12
  252. flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
  253. flwr/simulation/run_simulation.py +44 -43
  254. flwr/simulation/simulationio_connection.py +4 -4
  255. flwr/supercore/cli/flower_superexec.py +3 -4
  256. flwr/supercore/constant.py +52 -0
  257. flwr/supercore/corestate/corestate.py +24 -3
  258. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  259. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  260. flwr/supercore/ffs/disk_ffs.py +1 -2
  261. flwr/supercore/ffs/ffs.py +1 -2
  262. flwr/supercore/ffs/ffs_factory.py +1 -2
  263. flwr/{common → supercore}/heartbeat.py +20 -25
  264. flwr/supercore/object_store/in_memory_object_store.py +1 -6
  265. flwr/supercore/object_store/object_store.py +1 -2
  266. flwr/supercore/object_store/object_store_factory.py +27 -8
  267. flwr/supercore/object_store/sqlite_object_store.py +253 -0
  268. flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
  269. flwr/supercore/primitives/asymmetric.py +117 -0
  270. flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
  271. flwr/supercore/sqlite_mixin.py +159 -0
  272. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  273. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  274. flwr/supercore/superexec/run_superexec.py +9 -13
  275. flwr/supercore/utils.py +20 -0
  276. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  277. flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
  278. flwr/superlink/auth_plugin/auth_plugin.py +88 -0
  279. flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
  280. flwr/superlink/federation/__init__.py +24 -0
  281. flwr/superlink/federation/federation_manager.py +64 -0
  282. flwr/superlink/federation/noop_federation_manager.py +71 -0
  283. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
  284. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  285. flwr/superlink/servicer/control/control_grpc.py +18 -17
  286. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  287. flwr/superlink/servicer/control/control_servicer.py +239 -63
  288. flwr/supernode/cli/flower_supernode.py +74 -26
  289. flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
  290. flwr/supernode/nodestate/nodestate.py +7 -8
  291. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  292. flwr/supernode/runtime/run_clientapp.py +43 -24
  293. flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
  294. flwr/supernode/start_client_internal.py +175 -51
  295. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
  296. flwr-1.24.0.dist-info/RECORD +454 -0
  297. flwr/common/auth_plugin/auth_plugin.py +0 -149
  298. flwr/supercore/object_store/utils.py +0 -43
  299. flwr-1.22.0.dist-info/RECORD +0 -428
  300. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
  301. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
@@ -84,6 +84,7 @@ def train(net, trainloader, epochs, lr, device):
84
84
  def test(net, testloader, device):
85
85
  """Validate the model on the test set."""
86
86
  net.to(device)
87
+ net.eval()
87
88
  criterion = torch.nn.CrossEntropyLoss()
88
89
  correct, loss = 0, 0.0
89
90
  with torch.no_grad():
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
19
  "torch==2.8.0",
20
20
  "torchvision==0.23.0",
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets>=0.5.0",
19
19
  "torch==2.4.0",
20
20
  "trl==0.8.1",
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets>=0.5.0",
19
19
  "torch>=2.7.1",
20
20
  "transformers>=4.30.0,<5.0",
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "jax==0.4.30",
19
19
  "jaxlib==0.4.30",
20
20
  "scikit-learn==1.6.1",
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
19
  "mlx==0.29.0",
20
20
  ]
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "numpy>=2.0.2",
19
19
  ]
20
20
 
@@ -14,10 +14,10 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
- "torch==2.7.1",
20
- "torchvision==0.22.1",
19
+ "torch>=2.7.1",
20
+ "torchvision>=0.22.1",
21
21
  ]
22
22
 
23
23
  [tool.hatch.build.targets.wheel]
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
19
  "torch==2.7.1",
20
20
  "torchvision==0.22.1",
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
19
  "scikit-learn>=1.6.1",
20
20
  ]
@@ -14,9 +14,9 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
- "tensorflow>=2.11.1,<2.18.0",
19
+ "tensorflow>=2.18.0",
20
20
  ]
21
21
 
22
22
  [tool.hatch.build.targets.wheel]
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.22.0",
17
+ "flwr[simulation]>=1.24.0",
18
18
  "flwr-datasets>=0.5.0",
19
19
  "xgboost>=2.0.0",
20
20
  ]
flwr/cli/pull.py CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from pathlib import Path
19
- from typing import Annotated, Optional
19
+ from typing import Annotated
20
20
 
21
21
  import typer
22
22
 
@@ -34,7 +34,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
34
34
  )
35
35
  from flwr.proto.control_pb2_grpc import ControlStub
36
36
 
37
- from .utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
37
+ from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
38
38
 
39
39
 
40
40
  def pull( # pylint: disable=R0914
@@ -50,22 +50,26 @@ def pull( # pylint: disable=R0914
50
50
  typer.Argument(help="Path of the Flower App to run."),
51
51
  ] = Path("."),
52
52
  federation: Annotated[
53
- Optional[str],
53
+ str | None,
54
54
  typer.Argument(help="Name of the federation."),
55
55
  ] = None,
56
56
  federation_config_overrides: Annotated[
57
- Optional[list[str]],
57
+ list[str] | None,
58
58
  typer.Option(
59
59
  "--federation-config",
60
60
  help=FEDERATION_CONFIG_HELP_MESSAGE,
61
61
  ),
62
62
  ] = None,
63
63
  ) -> None:
64
- """Pull artifacts from a Flower run."""
64
+ """Pull artifacts from a Flower run.
65
+
66
+ Retrieve a download URL for artifacts generated during a completed Flower run. The
67
+ artifacts can then be downloaded from the provided URL.
68
+ """
65
69
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
66
70
 
67
71
  pyproject_path = app / FAB_CONFIG_FILE if app else None
68
- config, errors, warnings = load_and_validate(path=pyproject_path)
72
+ config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
69
73
  config = process_loaded_project_config(config, errors, warnings)
70
74
  federation, federation_config = validate_federation_in_project_config(
71
75
  federation, config, federation_config_overrides
@@ -74,7 +78,7 @@ def pull( # pylint: disable=R0914
74
78
  channel = None
75
79
  try:
76
80
 
77
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
81
+ auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
78
82
  channel = init_channel(app, federation_config, auth_plugin)
79
83
  stub = ControlStub(channel)
80
84
  with flwr_cli_grpc_exc_handler():
@@ -88,6 +92,7 @@ def pull( # pylint: disable=R0914
88
92
  "obtained.",
89
93
  fg=typer.colors.RED,
90
94
  bold=True,
95
+ err=True,
91
96
  )
92
97
  raise typer.Exit(code=1)
93
98
 
flwr/cli/run/run.py CHANGED
@@ -15,16 +15,18 @@
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.build import build_fab_from_disk, get_fab_filename
29
+ from flwr.cli.config_utils import load as load_toml
28
30
  from flwr.cli.config_utils import (
29
31
  load_and_validate,
30
32
  process_loaded_project_config,
@@ -37,31 +39,37 @@ from flwr.common.config import (
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
46
49
 
47
50
  from ..log import start_stream
48
- from ..utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
51
+ from ..utils import (
52
+ flwr_cli_grpc_exc_handler,
53
+ init_channel,
54
+ load_cli_auth_plugin,
55
+ parse_app_spec,
56
+ )
49
57
 
50
58
  CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
51
59
 
52
60
 
53
- # pylint: disable-next=too-many-locals, R0913, R0917
61
+ # pylint: disable-next=too-many-locals, too-many-branches, R0913, R0917
54
62
  def run(
55
63
  app: Annotated[
56
64
  Path,
57
65
  typer.Argument(help="Path of the Flower App to run."),
58
66
  ] = Path("."),
59
67
  federation: Annotated[
60
- Optional[str],
68
+ str | None,
61
69
  typer.Argument(help="Name of the federation to run the app on."),
62
70
  ] = None,
63
71
  run_config_overrides: Annotated[
64
- Optional[list[str]],
72
+ list[str] | None,
65
73
  typer.Option(
66
74
  "--run-config",
67
75
  "-c",
@@ -69,7 +77,7 @@ def run(
69
77
  ),
70
78
  ] = None,
71
79
  federation_config_overrides: Annotated[
72
- Optional[list[str]],
80
+ list[str] | None,
73
81
  typer.Option(
74
82
  "--federation-config",
75
83
  help=FEDERATION_CONFIG_HELP_MESSAGE,
@@ -98,11 +106,25 @@ def run(
98
106
  try:
99
107
  if suppress_output:
100
108
  redirect_output(captured_output)
109
+
110
+ # Determine if app is remote
111
+ app_spec = None
112
+ if (app_str := str(app)).startswith("@"):
113
+ # Validate app version and ID format
114
+ _ = parse_app_spec(app_str)
115
+ app_spec = app_str
116
+ is_remote_app = app_spec is not None
117
+
101
118
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
102
119
 
103
- pyproject_path = app / "pyproject.toml" if app else None
104
- config, errors, warnings = load_and_validate(path=pyproject_path)
120
+ # Disable the validation for remote apps
121
+ pyproject_path = app / "pyproject.toml" if not is_remote_app else None
122
+ # `./pyproject.toml` will be loaded when `pyproject_path` is None
123
+ config, errors, warnings = load_and_validate(
124
+ pyproject_path, check_module=not is_remote_app
125
+ )
105
126
  config = process_loaded_project_config(config, errors, warnings)
127
+
106
128
  federation, federation_config = validate_federation_in_project_config(
107
129
  federation, config, federation_config_overrides
108
130
  )
@@ -115,6 +137,7 @@ def run(
115
137
  run_config_overrides,
116
138
  stream,
117
139
  output_format,
140
+ app_spec,
118
141
  )
119
142
  else:
120
143
  _run_without_control_api(
@@ -130,6 +153,7 @@ def run(
130
153
  f"{err}",
131
154
  fg=typer.colors.RED,
132
155
  bold=True,
156
+ err=True,
133
157
  )
134
158
  finally:
135
159
  if suppress_output:
@@ -142,20 +166,32 @@ def _run_with_control_api(
142
166
  app: Path,
143
167
  federation: str,
144
168
  federation_config: dict[str, Any],
145
- config_overrides: Optional[list[str]],
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)
177
+ auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
152
178
  channel = init_channel(app, federation_config, auth_plugin)
153
179
  stub = ControlStub(channel)
154
180
 
155
- fab_bytes, fab_hash, config = build_fab(app)
156
- fab_id, fab_version = get_metadata_from_config(config)
181
+ # Build FAB if local app
182
+ if not is_remote_app:
183
+ fab_bytes = build_fab_from_disk(app)
184
+ fab_hash = hashlib.sha256(fab_bytes).hexdigest()
185
+ config = cast(dict[str, Any], load_toml(app / FAB_CONFIG_FILE))
186
+ fab_id, fab_version = get_metadata_from_config(config)
187
+ fab = Fab(fab_hash, fab_bytes, {})
188
+ # Skip FAB build if remote app
189
+ else:
190
+ # Use empty values for FAB
191
+ fab_id = fab_version = fab_hash = ""
192
+ fab = Fab(fab_hash, b"", {})
157
193
 
158
- fab = Fab(fab_hash, fab_bytes)
194
+ real_federation: str = federation_config.get("federation", NOOP_FEDERATION)
159
195
 
160
196
  # Construct a `ConfigRecord` out of a flattened `UserConfig`
161
197
  fed_config = flatten_dict(federation_config.get("options", {}))
@@ -164,7 +200,9 @@ def _run_with_control_api(
164
200
  req = StartRunRequest(
165
201
  fab=fab_to_proto(fab),
166
202
  override_config=user_config_to_proto(parse_config_args(config_overrides)),
203
+ federation=real_federation,
167
204
  federation_options=config_record_to_proto(c_record),
205
+ app_spec=app_spec or "",
168
206
  )
169
207
  with flwr_cli_grpc_exc_handler():
170
208
  res = stub.StartRun(req)
@@ -174,23 +212,35 @@ def _run_with_control_api(
174
212
  f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN
175
213
  )
176
214
  else:
177
- typer.secho("❌ Failed to start run", fg=typer.colors.RED)
215
+ if is_remote_app:
216
+ typer.secho(
217
+ "❌ Failed to start run. Please check that the provided "
218
+ "app identifier (@account_name/app_name) is correct.",
219
+ fg=typer.colors.RED,
220
+ err=True,
221
+ )
222
+ else:
223
+ typer.secho("❌ Failed to start run", fg=typer.colors.RED, err=True)
178
224
  raise typer.Exit(code=1)
179
225
 
180
226
  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
- )
227
+ # Only include FAB metadata if we actually built a local FAB
228
+ payload: dict[str, Any] = {
229
+ "success": res.HasField("run_id"),
230
+ "run-id": res.run_id if res.HasField("run_id") else None,
231
+ }
232
+ if not is_remote_app:
233
+ payload.update(
234
+ {
235
+ "fab-id": fab_id,
236
+ "fab-name": fab_id.rsplit("/", maxsplit=1)[-1],
237
+ "fab-version": fab_version,
238
+ "fab-hash": fab_hash[:8],
239
+ "fab-filename": get_fab_filename(config, fab_hash),
240
+ }
241
+ )
192
242
  restore_output()
193
- Console().print_json(run_output)
243
+ Console().print_json(json.dumps(payload))
194
244
 
195
245
  if stream:
196
246
  start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
@@ -200,14 +250,14 @@ def _run_with_control_api(
200
250
 
201
251
 
202
252
  def _run_without_control_api(
203
- app: Optional[Path],
253
+ app: Path | None,
204
254
  federation_config: dict[str, Any],
205
- config_overrides: Optional[list[str]],
255
+ config_overrides: list[str] | None,
206
256
  federation: str,
207
257
  ) -> None:
208
258
  try:
209
259
  num_supernodes = federation_config["options"]["num-supernodes"]
210
- verbose: Optional[bool] = federation_config["options"].get("verbose")
260
+ verbose: bool | None = federation_config["options"].get("verbose")
211
261
  backend_cfg = federation_config["options"].get("backend", {})
212
262
  except KeyError as err:
213
263
  typer.secho(
@@ -218,6 +268,7 @@ def _run_without_control_api(
218
268
  "options.num-supernodes = 10\n",
219
269
  fg=typer.colors.RED,
220
270
  bold=True,
271
+ err=True,
221
272
  )
222
273
  raise typer.Exit(code=1) from err
223
274
 
flwr/cli/run_utils.py ADDED
@@ -0,0 +1,130 @@
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 format_timedelta, 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 : str
44
+ The formatted elapsed time.
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
+ """
54
+
55
+ run_id: int
56
+ federation: str
57
+ fab_id: str
58
+ fab_version: str
59
+ fab_hash: str
60
+ status_text: str
61
+ elapsed: str
62
+ pending_at: str
63
+ starting_at: str
64
+ running_at: str
65
+ finished_at: str
66
+
67
+
68
+ def format_runs(runs: list[Run], now_isoformat: str) -> list[RunRow]:
69
+ """Format runs to a list of RunRow objects.
70
+
71
+ Parameters
72
+ ----------
73
+ runs : list[Run]
74
+ List of Run objects to format.
75
+ now_isoformat : str
76
+ Current timestamp in ISO format for calculating elapsed time.
77
+
78
+ Returns
79
+ -------
80
+ list[RunRow]
81
+ List of formatted RunRow objects sorted by pending_at timestamp.
82
+ """
83
+
84
+ def _format_datetime(dt: datetime | None) -> str:
85
+ return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
86
+
87
+ run_list: list[RunRow] = []
88
+
89
+ # Add rows
90
+ for run in sorted(runs, key=lambda x: datetime.fromisoformat(x.pending_at)):
91
+ # Combine status and sub-status into a single string
92
+ if run.status.sub_status == "":
93
+ status_text = run.status.status
94
+ else:
95
+ status_text = f"{run.status.status}:{run.status.sub_status}"
96
+
97
+ # Convert isoformat to datetime
98
+ pending_at = datetime.fromisoformat(run.pending_at) if run.pending_at else None
99
+ starting_at = (
100
+ datetime.fromisoformat(run.starting_at) if run.starting_at else None
101
+ )
102
+ running_at = datetime.fromisoformat(run.running_at) if run.running_at else None
103
+ finished_at = (
104
+ datetime.fromisoformat(run.finished_at) if run.finished_at else None
105
+ )
106
+
107
+ # Calculate elapsed time
108
+ elapsed_time = timedelta()
109
+ if running_at:
110
+ if finished_at:
111
+ end_time = finished_at
112
+ else:
113
+ end_time = datetime.fromisoformat(now_isoformat)
114
+ elapsed_time = end_time - running_at
115
+
116
+ row = RunRow(
117
+ run_id=run.run_id,
118
+ federation=run.federation,
119
+ fab_id=run.fab_id,
120
+ fab_version=run.fab_version,
121
+ fab_hash=run.fab_hash,
122
+ status_text=status_text,
123
+ elapsed=format_timedelta(elapsed_time),
124
+ pending_at=_format_datetime(pending_at),
125
+ starting_at=_format_datetime(starting_at),
126
+ running_at=_format_datetime(running_at),
127
+ finished_at=_format_datetime(finished_at),
128
+ )
129
+ run_list.append(row)
130
+ 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
@@ -38,7 +38,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
38
38
  )
39
39
  from flwr.proto.control_pb2_grpc import ControlStub
40
40
 
41
- from .utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
41
+ from .utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
42
42
 
43
43
 
44
44
  def stop( # pylint: disable=R0914
@@ -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
@@ -89,7 +93,7 @@ def stop( # pylint: disable=R0914
89
93
  exit_if_no_address(federation_config, "stop")
90
94
  channel = None
91
95
  try:
92
- auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
96
+ auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
93
97
  channel = init_channel(app, federation_config, auth_plugin)
94
98
  stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
95
99
 
@@ -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:
@@ -139,4 +155,6 @@ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
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
+ )