flwr 1.18.0__tar.gz → 1.19.0__tar.gz

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 (373) hide show
  1. {flwr-1.18.0 → flwr-1.19.0}/PKG-INFO +5 -4
  2. flwr-1.19.0/py/flwr/app/__init__.py +15 -0
  3. flwr-1.19.0/py/flwr/app/error.py +68 -0
  4. flwr-1.19.0/py/flwr/app/metadata.py +223 -0
  5. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/build.py +82 -57
  6. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/log.py +3 -3
  7. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/login/login.py +3 -7
  8. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/ls.py +15 -36
  9. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
  10. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
  11. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -17
  12. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  13. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  14. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  15. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  16. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  17. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  18. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  19. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  20. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/run/run.py +10 -18
  21. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/stop.py +2 -2
  22. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/utils.py +31 -5
  23. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/__init__.py +2 -2
  24. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/client_app.py +1 -1
  25. flwr-1.19.0/py/flwr/client/clientapp/__init__.py +15 -0
  26. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/grpc_adapter_client/connection.py +4 -4
  27. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/grpc_rere_client/connection.py +130 -60
  28. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/grpc_rere_client/grpc_adapter.py +34 -6
  29. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/message_handler/message_handler.py +1 -1
  30. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/comms_mods.py +36 -17
  31. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/rest_client/connection.py +173 -67
  32. flwr-1.19.0/py/flwr/clientapp/__init__.py +15 -0
  33. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/__init__.py +2 -2
  34. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/auth_plugin/__init__.py +2 -0
  35. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/auth_plugin/auth_plugin.py +29 -3
  36. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/constant.py +36 -7
  37. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  38. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/exit_handlers.py +30 -0
  39. flwr-1.19.0/py/flwr/common/heartbeat.py +165 -0
  40. flwr-1.19.0/py/flwr/common/inflatable.py +290 -0
  41. flwr-1.19.0/py/flwr/common/inflatable_grpc_utils.py +99 -0
  42. flwr-1.19.0/py/flwr/common/inflatable_rest_utils.py +99 -0
  43. flwr-1.19.0/py/flwr/common/inflatable_utils.py +341 -0
  44. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/message.py +110 -242
  45. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/__init__.py +2 -1
  46. flwr-1.19.0/py/flwr/common/record/array.py +323 -0
  47. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/arrayrecord.py +103 -225
  48. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/configrecord.py +59 -4
  49. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/conversion_utils.py +1 -1
  50. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/metricrecord.py +55 -4
  51. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/recorddict.py +69 -1
  52. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/recorddict_compat.py +2 -2
  53. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/retry_invoker.py +5 -1
  54. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/serde.py +59 -183
  55. flwr-1.19.0/py/flwr/common/serde_utils.py +175 -0
  56. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/typing.py +5 -3
  57. flwr-1.19.0/py/flwr/compat/__init__.py +15 -0
  58. flwr-1.19.0/py/flwr/compat/client/__init__.py +15 -0
  59. {flwr-1.18.0/py/flwr → flwr-1.19.0/py/flwr/compat}/client/app.py +19 -159
  60. flwr-1.19.0/py/flwr/compat/common/__init__.py +15 -0
  61. flwr-1.19.0/py/flwr/compat/server/__init__.py +15 -0
  62. flwr-1.19.0/py/flwr/compat/server/app.py +174 -0
  63. flwr-1.19.0/py/flwr/compat/simulation/__init__.py +15 -0
  64. flwr-1.19.0/py/flwr/proto/fleet_pb2.py +61 -0
  65. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fleet_pb2.pyi +49 -35
  66. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fleet_pb2_grpc.py +117 -13
  67. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fleet_pb2_grpc.pyi +47 -6
  68. flwr-1.19.0/py/flwr/proto/heartbeat_pb2.py +33 -0
  69. flwr-1.19.0/py/flwr/proto/heartbeat_pb2.pyi +66 -0
  70. flwr-1.19.0/py/flwr/proto/message_pb2.py +58 -0
  71. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/message_pb2.pyi +125 -0
  72. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/recorddict_pb2.py +16 -28
  73. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/recorddict_pb2.pyi +46 -64
  74. flwr-1.19.0/py/flwr/proto/run_pb2.py +56 -0
  75. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/run_pb2.pyi +4 -52
  76. flwr-1.19.0/py/flwr/proto/run_pb2_grpc.py +4 -0
  77. flwr-1.19.0/py/flwr/proto/run_pb2_grpc.pyi +4 -0
  78. flwr-1.19.0/py/flwr/proto/serverappio_pb2.py +60 -0
  79. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/serverappio_pb2.pyi +45 -3
  80. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/serverappio_pb2_grpc.py +138 -34
  81. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/serverappio_pb2_grpc.pyi +54 -13
  82. flwr-1.19.0/py/flwr/proto/simulationio_pb2.py +39 -0
  83. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/simulationio_pb2_grpc.py +35 -0
  84. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/simulationio_pb2_grpc.pyi +14 -0
  85. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/__init__.py +1 -1
  86. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/app.py +68 -186
  87. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/compat/app_utils.py +50 -28
  88. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/fleet_event_log_interceptor.py +2 -2
  89. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/grid/grpc_grid.py +104 -34
  90. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/grid/inmemory_grid.py +5 -4
  91. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/serverapp/app.py +18 -0
  92. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/ffs/__init__.py +2 -0
  93. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +13 -3
  94. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +101 -7
  95. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/message_handler/message_handler.py +135 -18
  96. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/rest_rere/rest_api.py +72 -11
  97. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/vce/vce_api.py +6 -3
  98. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/linkstate/in_memory_linkstate.py +138 -43
  99. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/linkstate/linkstate.py +53 -20
  100. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/linkstate/sqlite_linkstate.py +149 -55
  101. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/linkstate/utils.py +33 -29
  102. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/serverappio/serverappio_grpc.py +3 -0
  103. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/serverappio/serverappio_servicer.py +211 -57
  104. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/simulation/simulationio_servicer.py +25 -1
  105. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/utils.py +44 -2
  106. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/utils/validator.py +2 -2
  107. flwr-1.19.0/py/flwr/serverapp/__init__.py +15 -0
  108. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/app.py +17 -0
  109. flwr-1.19.0/py/flwr/supercore/__init__.py +15 -0
  110. flwr-1.19.0/py/flwr/supercore/object_store/__init__.py +24 -0
  111. flwr-1.19.0/py/flwr/supercore/object_store/in_memory_object_store.py +229 -0
  112. flwr-1.19.0/py/flwr/supercore/object_store/object_store.py +192 -0
  113. flwr-1.19.0/py/flwr/supercore/object_store/object_store_factory.py +44 -0
  114. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/deployment.py +6 -2
  115. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/exec_event_log_interceptor.py +4 -4
  116. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/exec_grpc.py +7 -3
  117. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/exec_servicer.py +125 -23
  118. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/exec_user_auth_interceptor.py +37 -8
  119. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/executor.py +4 -0
  120. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/simulation.py +7 -1
  121. flwr-1.19.0/py/flwr/superlink/__init__.py +15 -0
  122. flwr-1.19.0/py/flwr/supernode/__init__.py +15 -0
  123. {flwr-1.18.0/py/flwr/client/clientapp → flwr-1.19.0/py/flwr/supernode/cli}/__init__.py +4 -2
  124. flwr-1.18.0/py/flwr/client/supernode/app.py → flwr-1.19.0/py/flwr/supernode/cli/flower_supernode.py +3 -12
  125. flwr-1.19.0/py/flwr/supernode/cli/flwr_clientapp.py +81 -0
  126. flwr-1.19.0/py/flwr/supernode/nodestate/in_memory_nodestate.py +190 -0
  127. flwr-1.19.0/py/flwr/supernode/nodestate/nodestate.py +212 -0
  128. flwr-1.19.0/py/flwr/supernode/runtime/__init__.py +15 -0
  129. flwr-1.18.0/py/flwr/client/clientapp/app.py → flwr-1.19.0/py/flwr/supernode/runtime/run_clientapp.py +25 -56
  130. flwr-1.19.0/py/flwr/supernode/servicer/__init__.py +15 -0
  131. flwr-1.19.0/py/flwr/supernode/servicer/clientappio/__init__.py +24 -0
  132. flwr-1.19.0/py/flwr/supernode/start_client_internal.py +491 -0
  133. {flwr-1.18.0 → flwr-1.19.0}/pyproject.toml +5 -4
  134. flwr-1.18.0/py/flwr/client/heartbeat.py +0 -74
  135. flwr-1.18.0/py/flwr/client/nodestate/in_memory_nodestate.py +0 -38
  136. flwr-1.18.0/py/flwr/client/nodestate/nodestate.py +0 -31
  137. flwr-1.18.0/py/flwr/client/supernode/__init__.py +0 -22
  138. flwr-1.18.0/py/flwr/proto/fleet_pb2.py +0 -56
  139. flwr-1.18.0/py/flwr/proto/message_pb2.py +0 -41
  140. flwr-1.18.0/py/flwr/proto/run_pb2.py +0 -64
  141. flwr-1.18.0/py/flwr/proto/serverappio_pb2.py +0 -51
  142. flwr-1.18.0/py/flwr/proto/simulationio_pb2.py +0 -38
  143. {flwr-1.18.0 → flwr-1.19.0}/README.md +0 -0
  144. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/__init__.py +0 -0
  145. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/__init__.py +0 -0
  146. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/app.py +0 -0
  147. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/auth_plugin/__init__.py +0 -0
  148. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/auth_plugin/oidc_cli_plugin.py +0 -0
  149. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/cli_user_auth_interceptor.py +0 -0
  150. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/config_utils.py +0 -0
  151. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/constant.py +0 -0
  152. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/example.py +0 -0
  153. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/install.py +0 -0
  154. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/login/__init__.py +0 -0
  155. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/__init__.py +0 -0
  156. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/new.py +0 -0
  157. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/__init__.py +0 -0
  158. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/.gitignore.tpl +0 -0
  159. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/LICENSE.tpl +0 -0
  160. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/README.baseline.md.tpl +0 -0
  161. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -0
  162. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/README.md.tpl +0 -0
  163. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/__init__.py +0 -0
  164. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -0
  165. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/__init__.py +0 -0
  166. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/__init__.py.tpl +0 -0
  167. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
  168. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -0
  169. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -0
  170. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -0
  171. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -0
  172. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -0
  173. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -0
  174. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -0
  175. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -0
  176. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/flwr_tune/__init__.py +0 -0
  177. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -0
  178. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -0
  179. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -0
  180. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -0
  181. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -0
  182. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -0
  183. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -0
  184. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -0
  185. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -0
  186. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -0
  187. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -0
  188. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -0
  189. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -0
  190. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -0
  191. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -0
  192. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -0
  193. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -0
  194. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -0
  195. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -0
  196. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -0
  197. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -0
  198. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/cli/run/__init__.py +0 -0
  199. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/client.py +0 -0
  200. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/clientapp/utils.py +0 -0
  201. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/dpfedavg_numpy_client.py +0 -0
  202. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/grpc_adapter_client/__init__.py +0 -0
  203. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/grpc_rere_client/__init__.py +0 -0
  204. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/grpc_rere_client/client_interceptor.py +0 -0
  205. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/message_handler/__init__.py +0 -0
  206. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/__init__.py +0 -0
  207. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/centraldp_mods.py +0 -0
  208. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/localdp_mod.py +0 -0
  209. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/secure_aggregation/__init__.py +0 -0
  210. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/secure_aggregation/secagg_mod.py +0 -0
  211. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/secure_aggregation/secaggplus_mod.py +0 -0
  212. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/mod/utils.py +0 -0
  213. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/numpy_client.py +0 -0
  214. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/rest_client/__init__.py +0 -0
  215. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/run_info_store.py +0 -0
  216. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/client/typing.py +0 -0
  217. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/address.py +0 -0
  218. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/args.py +0 -0
  219. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/config.py +0 -0
  220. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/context.py +0 -0
  221. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/date.py +0 -0
  222. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/differential_privacy.py +0 -0
  223. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/differential_privacy_constants.py +0 -0
  224. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/dp.py +0 -0
  225. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/event_log_plugin/__init__.py +0 -0
  226. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/exit/__init__.py +0 -0
  227. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/exit/exit.py +0 -0
  228. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/exit/exit_code.py +0 -0
  229. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/grpc.py +0 -0
  230. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/logger.py +0 -0
  231. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/object_ref.py +0 -0
  232. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/parameter.py +0 -0
  233. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/pyproject.py +0 -0
  234. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/record/typeddict.py +0 -0
  235. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/__init__.py +0 -0
  236. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/crypto/__init__.py +0 -0
  237. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/crypto/shamir.py +0 -0
  238. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/crypto/symmetric_encryption.py +0 -0
  239. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/ndarrays_arithmetic.py +0 -0
  240. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/quantization.py +0 -0
  241. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/secaggplus_constants.py +0 -0
  242. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/secure_aggregation/secaggplus_utils.py +0 -0
  243. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/telemetry.py +0 -0
  244. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/common/version.py +0 -0
  245. {flwr-1.18.0/py/flwr → flwr-1.19.0/py/flwr/compat}/client/grpc_client/__init__.py +0 -0
  246. {flwr-1.18.0/py/flwr → flwr-1.19.0/py/flwr/compat}/client/grpc_client/connection.py +0 -0
  247. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/__init__.py +0 -0
  248. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/clientappio_pb2.py +0 -0
  249. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/clientappio_pb2.pyi +0 -0
  250. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/clientappio_pb2_grpc.py +0 -0
  251. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/clientappio_pb2_grpc.pyi +0 -0
  252. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/error_pb2.py +0 -0
  253. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/error_pb2.pyi +0 -0
  254. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/error_pb2_grpc.py +0 -0
  255. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/error_pb2_grpc.pyi +0 -0
  256. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/exec_pb2.py +0 -0
  257. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/exec_pb2.pyi +0 -0
  258. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/exec_pb2_grpc.py +0 -0
  259. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/exec_pb2_grpc.pyi +0 -0
  260. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fab_pb2.py +0 -0
  261. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fab_pb2.pyi +0 -0
  262. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fab_pb2_grpc.py +0 -0
  263. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/fab_pb2_grpc.pyi +0 -0
  264. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/grpcadapter_pb2.py +0 -0
  265. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/grpcadapter_pb2.pyi +0 -0
  266. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/grpcadapter_pb2_grpc.py +0 -0
  267. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/grpcadapter_pb2_grpc.pyi +0 -0
  268. /flwr-1.18.0/py/flwr/proto/log_pb2_grpc.py → /flwr-1.19.0/py/flwr/proto/heartbeat_pb2_grpc.py +0 -0
  269. /flwr-1.18.0/py/flwr/proto/log_pb2_grpc.pyi → /flwr-1.19.0/py/flwr/proto/heartbeat_pb2_grpc.pyi +0 -0
  270. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/log_pb2.py +0 -0
  271. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/log_pb2.pyi +0 -0
  272. /flwr-1.18.0/py/flwr/proto/message_pb2_grpc.py → /flwr-1.19.0/py/flwr/proto/log_pb2_grpc.py +0 -0
  273. /flwr-1.18.0/py/flwr/proto/message_pb2_grpc.pyi → /flwr-1.19.0/py/flwr/proto/log_pb2_grpc.pyi +0 -0
  274. /flwr-1.18.0/py/flwr/proto/node_pb2_grpc.py → /flwr-1.19.0/py/flwr/proto/message_pb2_grpc.py +0 -0
  275. /flwr-1.18.0/py/flwr/proto/node_pb2_grpc.pyi → /flwr-1.19.0/py/flwr/proto/message_pb2_grpc.pyi +0 -0
  276. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/node_pb2.py +0 -0
  277. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/node_pb2.pyi +0 -0
  278. /flwr-1.18.0/py/flwr/proto/recorddict_pb2_grpc.py → /flwr-1.19.0/py/flwr/proto/node_pb2_grpc.py +0 -0
  279. /flwr-1.18.0/py/flwr/proto/recorddict_pb2_grpc.pyi → /flwr-1.19.0/py/flwr/proto/node_pb2_grpc.pyi +0 -0
  280. /flwr-1.18.0/py/flwr/proto/run_pb2_grpc.py → /flwr-1.19.0/py/flwr/proto/recorddict_pb2_grpc.py +0 -0
  281. /flwr-1.18.0/py/flwr/proto/run_pb2_grpc.pyi → /flwr-1.19.0/py/flwr/proto/recorddict_pb2_grpc.pyi +0 -0
  282. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/simulationio_pb2.pyi +0 -0
  283. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/transport_pb2.py +0 -0
  284. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/transport_pb2.pyi +0 -0
  285. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/transport_pb2_grpc.py +0 -0
  286. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/proto/transport_pb2_grpc.pyi +0 -0
  287. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/py.typed +0 -0
  288. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/client_manager.py +0 -0
  289. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/client_proxy.py +0 -0
  290. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/compat/__init__.py +0 -0
  291. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/compat/app.py +0 -0
  292. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/compat/grid_client_proxy.py +0 -0
  293. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/compat/legacy_context.py +0 -0
  294. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/criterion.py +0 -0
  295. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/grid/__init__.py +0 -0
  296. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/grid/grid.py +0 -0
  297. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/history.py +0 -0
  298. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/run_serverapp.py +0 -0
  299. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/server.py +0 -0
  300. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/server_app.py +0 -0
  301. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/server_config.py +0 -0
  302. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/serverapp/__init__.py +0 -0
  303. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/serverapp_components.py +0 -0
  304. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/__init__.py +0 -0
  305. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/aggregate.py +0 -0
  306. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/bulyan.py +0 -0
  307. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/dp_adaptive_clipping.py +0 -0
  308. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/dp_fixed_clipping.py +0 -0
  309. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/dpfedavg_adaptive.py +0 -0
  310. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/dpfedavg_fixed.py +0 -0
  311. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fault_tolerant_fedavg.py +0 -0
  312. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedadagrad.py +0 -0
  313. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedadam.py +0 -0
  314. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedavg.py +0 -0
  315. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedavg_android.py +0 -0
  316. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedavgm.py +0 -0
  317. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedmedian.py +0 -0
  318. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedopt.py +0 -0
  319. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedprox.py +0 -0
  320. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedtrimmedavg.py +0 -0
  321. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedxgb_bagging.py +0 -0
  322. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedxgb_cyclic.py +0 -0
  323. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedxgb_nn_avg.py +0 -0
  324. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/fedyogi.py +0 -0
  325. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/krum.py +0 -0
  326. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/qfedavg.py +0 -0
  327. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/strategy/strategy.py +0 -0
  328. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/__init__.py +0 -0
  329. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/ffs/disk_ffs.py +0 -0
  330. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/ffs/ffs.py +0 -0
  331. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/ffs/ffs_factory.py +0 -0
  332. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/__init__.py +0 -0
  333. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_adapter/__init__.py +0 -0
  334. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_bidi/__init__.py +0 -0
  335. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +0 -0
  336. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +0 -0
  337. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +0 -0
  338. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +0 -0
  339. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_rere/__init__.py +0 -0
  340. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +0 -0
  341. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/message_handler/__init__.py +0 -0
  342. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/rest_rere/__init__.py +0 -0
  343. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/vce/__init__.py +0 -0
  344. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/vce/backend/__init__.py +0 -0
  345. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/vce/backend/backend.py +0 -0
  346. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/fleet/vce/backend/raybackend.py +0 -0
  347. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/linkstate/__init__.py +0 -0
  348. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/linkstate/linkstate_factory.py +0 -0
  349. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/serverappio/__init__.py +0 -0
  350. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/simulation/__init__.py +0 -0
  351. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/superlink/simulation/simulationio_grpc.py +0 -0
  352. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/typing.py +0 -0
  353. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/utils/__init__.py +0 -0
  354. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/utils/tensorboard.py +0 -0
  355. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/workflow/__init__.py +0 -0
  356. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/workflow/constant.py +0 -0
  357. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/workflow/default_workflows.py +0 -0
  358. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/workflow/secure_aggregation/__init__.py +0 -0
  359. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/workflow/secure_aggregation/secagg_workflow.py +0 -0
  360. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +0 -0
  361. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/__init__.py +0 -0
  362. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/legacy_app.py +0 -0
  363. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/ray_transport/__init__.py +0 -0
  364. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/ray_transport/ray_actor.py +0 -0
  365. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/ray_transport/ray_client_proxy.py +0 -0
  366. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/ray_transport/utils.py +0 -0
  367. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/run_simulation.py +0 -0
  368. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/simulation/simulationio_connection.py +0 -0
  369. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/__init__.py +0 -0
  370. {flwr-1.18.0 → flwr-1.19.0}/py/flwr/superexec/app.py +0 -0
  371. {flwr-1.18.0/py/flwr/client → flwr-1.19.0/py/flwr/supernode}/nodestate/__init__.py +0 -0
  372. {flwr-1.18.0/py/flwr/client → flwr-1.19.0/py/flwr/supernode}/nodestate/nodestate_factory.py +0 -0
  373. {flwr-1.18.0/py/flwr/client/clientapp → flwr-1.19.0/py/flwr/supernode/servicer/clientappio}/clientappio_servicer.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: flwr
3
- Version: 1.18.0
3
+ Version: 1.19.0
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
- Home-page: https://flower.ai
6
5
  License: Apache-2.0
7
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
8
7
  Author: The Flower Authors
@@ -19,8 +18,8 @@ Classifier: Programming Language :: Python :: 3
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3 :: Only
23
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3 :: Only
24
23
  Classifier: Programming Language :: Python :: 3.9
25
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
25
  Classifier: Topic :: Scientific/Engineering
@@ -32,6 +31,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
31
  Classifier: Typing :: Typed
33
32
  Provides-Extra: rest
34
33
  Provides-Extra: simulation
34
+ Requires-Dist: click (<8.2.0)
35
35
  Requires-Dist: cryptography (>=44.0.1,<45.0.0)
36
36
  Requires-Dist: grpcio (>=1.62.3,<2.0.0,!=1.65.0)
37
37
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
@@ -49,6 +49,7 @@ Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
49
49
  Requires-Dist: typer (>=0.12.5,<0.13.0)
50
50
  Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
51
51
  Project-URL: Documentation, https://flower.ai
52
+ Project-URL: Homepage, https://flower.ai
52
53
  Project-URL: Repository, https://github.com/adap/flower
53
54
  Description-Content-Type: text/markdown
54
55
 
@@ -0,0 +1,15 @@
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
+ """Public Flower App APIs."""
@@ -0,0 +1,68 @@
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
+ """Error."""
16
+
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Optional, cast
21
+
22
+ DEFAULT_TTL = 43200 # This is 12 hours
23
+ MESSAGE_INIT_ERROR_MESSAGE = (
24
+ "Invalid arguments for Message. Expected one of the documented "
25
+ "signatures: Message(content: RecordDict, dst_node_id: int, message_type: str,"
26
+ " *, [ttl: float, group_id: str]) or Message(content: RecordDict | error: Error,"
27
+ " *, reply_to: Message, [ttl: float])."
28
+ )
29
+
30
+
31
+ class Error:
32
+ """The class storing information about an error that occurred.
33
+
34
+ Parameters
35
+ ----------
36
+ code : int
37
+ An identifier for the error.
38
+ reason : Optional[str]
39
+ A reason for why the error arose (e.g. an exception stack-trace)
40
+ """
41
+
42
+ def __init__(self, code: int, reason: str | None = None) -> None:
43
+ var_dict = {
44
+ "_code": code,
45
+ "_reason": reason,
46
+ }
47
+ self.__dict__.update(var_dict)
48
+
49
+ @property
50
+ def code(self) -> int:
51
+ """Error code."""
52
+ return cast(int, self.__dict__["_code"])
53
+
54
+ @property
55
+ def reason(self) -> str | None:
56
+ """Reason reported about the error."""
57
+ return cast(Optional[str], self.__dict__["_reason"])
58
+
59
+ def __repr__(self) -> str:
60
+ """Return a string representation of this instance."""
61
+ view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
62
+ return f"{self.__class__.__qualname__}({view})"
63
+
64
+ def __eq__(self, other: object) -> bool:
65
+ """Compare two instances of the class."""
66
+ if not isinstance(other, self.__class__):
67
+ raise NotImplementedError
68
+ return self.__dict__ == other.__dict__
@@ -0,0 +1,223 @@
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
+ """Metadata."""
16
+
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import cast
21
+
22
+ from ..common.constant import MessageType, MessageTypeLegacy
23
+
24
+
25
+ class Metadata: # pylint: disable=too-many-instance-attributes
26
+ """The class representing metadata associated with the current message.
27
+
28
+ Parameters
29
+ ----------
30
+ run_id : int
31
+ An identifier for the current run.
32
+ message_id : str
33
+ An identifier for the current message.
34
+ src_node_id : int
35
+ An identifier for the node sending this message.
36
+ dst_node_id : int
37
+ An identifier for the node receiving this message.
38
+ reply_to_message_id : str
39
+ An identifier for the message to which this message is a reply.
40
+ group_id : str
41
+ An identifier for grouping messages. In some settings,
42
+ this is used as the FL round.
43
+ created_at : float
44
+ Unix timestamp when the message was created.
45
+ ttl : float
46
+ Time-to-live for this message in seconds.
47
+ message_type : str
48
+ A string that encodes the action to be executed on
49
+ the receiving end.
50
+ """
51
+
52
+ def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
53
+ self,
54
+ run_id: int,
55
+ message_id: str,
56
+ src_node_id: int,
57
+ dst_node_id: int,
58
+ reply_to_message_id: str,
59
+ group_id: str,
60
+ created_at: float,
61
+ ttl: float,
62
+ message_type: str,
63
+ ) -> None:
64
+ var_dict = {
65
+ "_run_id": run_id,
66
+ "_message_id": message_id,
67
+ "_src_node_id": src_node_id,
68
+ "_dst_node_id": dst_node_id,
69
+ "_reply_to_message_id": reply_to_message_id,
70
+ "_group_id": group_id,
71
+ "_created_at": created_at,
72
+ "_ttl": ttl,
73
+ "_message_type": message_type,
74
+ }
75
+ self.__dict__.update(var_dict)
76
+ self.message_type = message_type # Trigger validation
77
+
78
+ @property
79
+ def run_id(self) -> int:
80
+ """An identifier for the current run."""
81
+ return cast(int, self.__dict__["_run_id"])
82
+
83
+ @property
84
+ def message_id(self) -> str:
85
+ """An identifier for the current message."""
86
+ return cast(str, self.__dict__["_message_id"])
87
+
88
+ @property
89
+ def src_node_id(self) -> int:
90
+ """An identifier for the node sending this message."""
91
+ return cast(int, self.__dict__["_src_node_id"])
92
+
93
+ @property
94
+ def reply_to_message_id(self) -> str:
95
+ """An identifier for the message to which this message is a reply."""
96
+ return cast(str, self.__dict__["_reply_to_message_id"])
97
+
98
+ @property
99
+ def dst_node_id(self) -> int:
100
+ """An identifier for the node receiving this message."""
101
+ return cast(int, self.__dict__["_dst_node_id"])
102
+
103
+ @dst_node_id.setter
104
+ def dst_node_id(self, value: int) -> None:
105
+ """Set dst_node_id."""
106
+ self.__dict__["_dst_node_id"] = value
107
+
108
+ @property
109
+ def group_id(self) -> str:
110
+ """An identifier for grouping messages."""
111
+ return cast(str, self.__dict__["_group_id"])
112
+
113
+ @group_id.setter
114
+ def group_id(self, value: str) -> None:
115
+ """Set group_id."""
116
+ self.__dict__["_group_id"] = value
117
+
118
+ @property
119
+ def created_at(self) -> float:
120
+ """Unix timestamp when the message was created."""
121
+ return cast(float, self.__dict__["_created_at"])
122
+
123
+ @created_at.setter
124
+ def created_at(self, value: float) -> None:
125
+ """Set creation timestamp of this message."""
126
+ self.__dict__["_created_at"] = value
127
+
128
+ @property
129
+ def delivered_at(self) -> str:
130
+ """Unix timestamp when the message was delivered."""
131
+ return cast(str, self.__dict__["_delivered_at"])
132
+
133
+ @delivered_at.setter
134
+ def delivered_at(self, value: str) -> None:
135
+ """Set delivery timestamp of this message."""
136
+ self.__dict__["_delivered_at"] = value
137
+
138
+ @property
139
+ def ttl(self) -> float:
140
+ """Time-to-live for this message."""
141
+ return cast(float, self.__dict__["_ttl"])
142
+
143
+ @ttl.setter
144
+ def ttl(self, value: float) -> None:
145
+ """Set ttl."""
146
+ self.__dict__["_ttl"] = value
147
+
148
+ @property
149
+ def message_type(self) -> str:
150
+ """A string that encodes the action to be executed on the receiving end."""
151
+ return cast(str, self.__dict__["_message_type"])
152
+
153
+ @message_type.setter
154
+ def message_type(self, value: str) -> None:
155
+ """Set message_type."""
156
+ # Validate message type
157
+ if validate_legacy_message_type(value):
158
+ pass # Backward compatibility for legacy message types
159
+ elif not validate_message_type(value):
160
+ raise ValueError(
161
+ f"Invalid message type: '{value}'. "
162
+ "Expected format: '<category>' or '<category>.<action>', "
163
+ "where <category> must be 'train', 'evaluate', or 'query', "
164
+ "and <action> must be a valid Python identifier."
165
+ )
166
+
167
+ self.__dict__["_message_type"] = value
168
+
169
+ def __repr__(self) -> str:
170
+ """Return a string representation of this instance."""
171
+ view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
172
+ return f"{self.__class__.__qualname__}({view})"
173
+
174
+ def __eq__(self, other: object) -> bool:
175
+ """Compare two instances of the class."""
176
+ if not isinstance(other, self.__class__):
177
+ raise NotImplementedError
178
+ return self.__dict__ == other.__dict__
179
+
180
+
181
+ def validate_message_type(message_type: str) -> bool:
182
+ """Validate if the message type is valid.
183
+
184
+ A valid message type format must be one of the following:
185
+
186
+ - "<category>"
187
+ - "<category>.<action>"
188
+
189
+ where `category` must be one of "train", "evaluate", or "query",
190
+ and `action` must be a valid Python identifier.
191
+ """
192
+ # Check if conforming to the format "<category>"
193
+ valid_types = {
194
+ MessageType.TRAIN,
195
+ MessageType.EVALUATE,
196
+ MessageType.QUERY,
197
+ MessageType.SYSTEM,
198
+ }
199
+ if message_type in valid_types:
200
+ return True
201
+
202
+ # Check if conforming to the format "<category>.<action>"
203
+ if message_type.count(".") != 1:
204
+ return False
205
+
206
+ category, action = message_type.split(".")
207
+ if category in valid_types and action.isidentifier():
208
+ return True
209
+
210
+ return False
211
+
212
+
213
+ def validate_legacy_message_type(message_type: str) -> bool:
214
+ """Validate if the legacy message type is valid."""
215
+ # Backward compatibility for legacy message types
216
+ if message_type in (
217
+ MessageTypeLegacy.GET_PARAMETERS,
218
+ MessageTypeLegacy.GET_PROPERTIES,
219
+ "reconnect",
220
+ ):
221
+ return True
222
+
223
+ return False
@@ -16,10 +16,8 @@
16
16
 
17
17
 
18
18
  import hashlib
19
- import os
20
- import shutil
21
- import tempfile
22
19
  import zipfile
20
+ from io import BytesIO
23
21
  from pathlib import Path
24
22
  from typing import Annotated, Any, Optional, Union
25
23
 
@@ -29,6 +27,7 @@ import typer
29
27
 
30
28
  from flwr.common.constant import FAB_ALLOWED_EXTENSIONS, FAB_DATE, FAB_HASH_TRUNCATION
31
29
 
30
+ from .config_utils import load as load_toml
32
31
  from .config_utils import load_and_validate
33
32
  from .utils import is_valid_project_name
34
33
 
@@ -43,11 +42,11 @@ def write_to_zip(
43
42
  return zipfile_obj
44
43
 
45
44
 
46
- def get_fab_filename(conf: dict[str, Any], fab_hash: str) -> str:
45
+ def get_fab_filename(config: dict[str, Any], fab_hash: str) -> str:
47
46
  """Get the FAB filename based on the given config and FAB hash."""
48
- publisher = conf["tool"]["flwr"]["app"]["publisher"]
49
- name = conf["project"]["name"]
50
- version = conf["project"]["version"].replace(".", "-")
47
+ publisher = config["tool"]["flwr"]["app"]["publisher"]
48
+ name = config["project"]["name"]
49
+ version = config["project"]["version"].replace(".", "-")
51
50
  fab_hash_truncated = fab_hash[:FAB_HASH_TRUNCATION]
52
51
  return f"{publisher}.{name}.{version}.{fab_hash_truncated}.fab"
53
52
 
@@ -89,8 +88,8 @@ def build(
89
88
  )
90
89
  raise typer.Exit(code=1)
91
90
 
92
- conf, errors, warnings = load_and_validate(app / "pyproject.toml")
93
- if conf is None:
91
+ config, errors, warnings = load_and_validate(app / "pyproject.toml")
92
+ if config is None:
94
93
  typer.secho(
95
94
  "Project configuration could not be loaded.\npyproject.toml is invalid:\n"
96
95
  + "\n".join([f"- {line}" for line in errors]),
@@ -107,70 +106,96 @@ def build(
107
106
  bold=True,
108
107
  )
109
108
 
110
- # Load .gitignore rules if present
111
- ignore_spec = _load_gitignore(app)
109
+ # Build FAB
110
+ fab_bytes, fab_hash, _ = build_fab(app)
112
111
 
113
- list_file_content = ""
112
+ # Get the name of the zip file
113
+ fab_filename = get_fab_filename(config, fab_hash)
114
+
115
+ # Write the FAB
116
+ Path(fab_filename).write_bytes(fab_bytes)
117
+
118
+ typer.secho(
119
+ f"🎊 Successfully built {fab_filename}", fg=typer.colors.GREEN, bold=True
120
+ )
121
+
122
+ return fab_filename, fab_hash
114
123
 
115
- # Remove the 'federations' field from 'tool.flwr' if it exists
116
- if (
117
- "tool" in conf
118
- and "flwr" in conf["tool"]
119
- and "federations" in conf["tool"]["flwr"]
120
- ):
121
- del conf["tool"]["flwr"]["federations"]
122
124
 
123
- toml_contents = tomli_w.dumps(conf)
125
+ def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
126
+ """Build a FAB in memory and return the bytes, hash, and config.
124
127
 
125
- with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_file:
126
- temp_filename = temp_file.name
128
+ This function assumes that the provided path points to a valid Flower app and
129
+ bundles it into a FAB without performing additional validation.
127
130
 
128
- with zipfile.ZipFile(temp_filename, "w", zipfile.ZIP_DEFLATED) as fab_file:
129
- write_to_zip(fab_file, "pyproject.toml", toml_contents)
131
+ Parameters
132
+ ----------
133
+ app : Path
134
+ Path to the Flower app to bundle into a FAB.
130
135
 
131
- # Continue with adding other files
132
- all_files = [
133
- f
134
- for f in app.rglob("*")
135
- if not ignore_spec.match_file(f)
136
- and f.name != temp_filename
137
- and f.suffix in FAB_ALLOWED_EXTENSIONS
138
- and f.name != "pyproject.toml" # Exclude the original pyproject.toml
139
- ]
136
+ Returns
137
+ -------
138
+ tuple[bytes, str, dict[str, Any]]
139
+ A tuple containing:
140
+ - the FAB as bytes
141
+ - the SHA256 hash of the FAB
142
+ - the project configuration (with the 'federations' field removed)
143
+ """
144
+ app = app.resolve()
145
+
146
+ # Load the pyproject.toml file
147
+ config = load_toml(app / "pyproject.toml")
148
+ if config is None:
149
+ raise ValueError("Project configuration could not be loaded.")
140
150
 
141
- all_files.sort()
151
+ # Remove the 'federations' field if it exists
152
+ if (
153
+ "tool" in config
154
+ and "flwr" in config["tool"]
155
+ and "federations" in config["tool"]["flwr"]
156
+ ):
157
+ del config["tool"]["flwr"]["federations"]
142
158
 
143
- for file_path in all_files:
144
- # Read the file content manually
145
- with open(file_path, "rb") as f:
146
- file_contents = f.read()
159
+ # Load .gitignore rules if present
160
+ ignore_spec = _load_gitignore(app)
147
161
 
148
- archive_path = file_path.relative_to(app)
149
- write_to_zip(fab_file, str(archive_path), file_contents)
162
+ # Search for all files in the app directory
163
+ all_files = [
164
+ f
165
+ for f in app.rglob("*")
166
+ if not ignore_spec.match_file(f)
167
+ and f.suffix in FAB_ALLOWED_EXTENSIONS
168
+ and f.name != "pyproject.toml" # Exclude the original pyproject.toml
169
+ ]
170
+ all_files.sort()
171
+
172
+ # Create a zip file in memory
173
+ list_file_content = ""
150
174
 
151
- # Calculate file info
152
- sha256_hash = hashlib.sha256(file_contents).hexdigest()
153
- file_size_bits = os.path.getsize(file_path) * 8 # size in bits
154
- list_file_content += f"{archive_path},{sha256_hash},{file_size_bits}\n"
175
+ fab_buffer = BytesIO()
176
+ with zipfile.ZipFile(fab_buffer, "w", zipfile.ZIP_DEFLATED) as fab_file:
177
+ # Add pyproject.toml
178
+ write_to_zip(fab_file, "pyproject.toml", tomli_w.dumps(config))
155
179
 
156
- # Add CONTENT and CONTENT.jwt to the zip file
157
- write_to_zip(fab_file, ".info/CONTENT", list_file_content)
180
+ for file_path in all_files:
181
+ # Read the file content manually
182
+ file_contents = file_path.read_bytes()
158
183
 
159
- # Get hash of FAB file
160
- content = Path(temp_filename).read_bytes()
161
- fab_hash = hashlib.sha256(content).hexdigest()
184
+ archive_path = str(file_path.relative_to(app))
185
+ write_to_zip(fab_file, archive_path, file_contents)
162
186
 
163
- # Set the name of the zip file
164
- fab_filename = get_fab_filename(conf, fab_hash)
187
+ # Calculate file info
188
+ sha256_hash = hashlib.sha256(file_contents).hexdigest()
189
+ file_size_bits = len(file_contents) * 8 # size in bits
190
+ list_file_content += f"{archive_path},{sha256_hash},{file_size_bits}\n"
165
191
 
166
- # Once the temporary zip file is created, rename it to the final filename
167
- shutil.move(temp_filename, fab_filename)
192
+ # Add CONTENT and CONTENT.jwt to the zip file
193
+ write_to_zip(fab_file, ".info/CONTENT", list_file_content)
168
194
 
169
- typer.secho(
170
- f"🎊 Successfully built {fab_filename}", fg=typer.colors.GREEN, bold=True
171
- )
195
+ fab_bytes = fab_buffer.getvalue()
196
+ fab_hash = hashlib.sha256(fab_bytes).hexdigest()
172
197
 
173
- return fab_filename, fab_hash
198
+ return fab_bytes, fab_hash, config
174
199
 
175
200
 
176
201
  def _load_gitignore(app: Path) -> pathspec.PathSpec:
@@ -35,7 +35,7 @@ from flwr.common.logger import log as logger
35
35
  from flwr.proto.exec_pb2 import StreamLogsRequest # pylint: disable=E0611
36
36
  from flwr.proto.exec_pb2_grpc import ExecStub
37
37
 
38
- from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
38
+ from .utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
39
39
 
40
40
 
41
41
  class AllLogsRetrieved(BaseException):
@@ -95,7 +95,7 @@ def stream_logs(
95
95
  latest_timestamp = 0.0
96
96
  res = None
97
97
  try:
98
- with unauthenticated_exc_handler():
98
+ with flwr_cli_grpc_exc_handler():
99
99
  for res in stub.StreamLogs(req, timeout=duration):
100
100
  print(res.log_output, end="")
101
101
  raise AllLogsRetrieved()
@@ -116,7 +116,7 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
116
116
  req = StreamLogsRequest(run_id=run_id, after_timestamp=0.0)
117
117
 
118
118
  try:
119
- with unauthenticated_exc_handler():
119
+ with flwr_cli_grpc_exc_handler():
120
120
  # Enforce timeout for graceful exit
121
121
  for res in stub.StreamLogs(req, timeout=timeout):
122
122
  print(res.log_output)
@@ -35,11 +35,7 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
35
35
  )
36
36
  from flwr.proto.exec_pb2_grpc import ExecStub
37
37
 
38
- from ..utils import (
39
- init_channel,
40
- try_obtain_cli_auth_plugin,
41
- unauthenticated_exc_handler,
42
- )
38
+ from ..utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
43
39
 
44
40
 
45
41
  def login( # pylint: disable=R0914
@@ -96,7 +92,7 @@ def login( # pylint: disable=R0914
96
92
  stub = ExecStub(channel)
97
93
 
98
94
  login_request = GetLoginDetailsRequest()
99
- with unauthenticated_exc_handler():
95
+ with flwr_cli_grpc_exc_handler():
100
96
  login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
101
97
 
102
98
  # Get the auth plugin
@@ -120,7 +116,7 @@ def login( # pylint: disable=R0914
120
116
  expires_in=login_response.expires_in,
121
117
  interval=login_response.interval,
122
118
  )
123
- with unauthenticated_exc_handler():
119
+ with flwr_cli_grpc_exc_handler():
124
120
  credentials = auth_plugin.login(details, stub)
125
121
 
126
122
  # Store the tokens
@@ -44,7 +44,7 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
44
44
  )
45
45
  from flwr.proto.exec_pb2_grpc import ExecStub
46
46
 
47
- from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
47
+ from .utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
48
48
 
49
49
  _RunListType = tuple[int, str, str, str, str, str, str, str, str]
50
50
 
@@ -130,23 +130,16 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
130
130
  # Display information about a specific run ID
131
131
  if run_id is not None:
132
132
  typer.echo(f"🔍 Displaying information for run ID {run_id}...")
133
- restore_output()
134
- _display_one_run(stub, run_id, output_format)
133
+ formatted_runs = _display_one_run(stub, run_id)
135
134
  # By default, list all runs
136
135
  else:
137
136
  typer.echo("📄 Listing all runs...")
138
- restore_output()
139
- _list_runs(stub, output_format)
140
-
141
- except ValueError as err:
142
- if suppress_output:
143
- redirect_output(captured_output)
144
- typer.secho(
145
- f"❌ {err}",
146
- fg=typer.colors.RED,
147
- bold=True,
148
- )
149
- raise typer.Exit(code=1) from err
137
+ formatted_runs = _list_runs(stub)
138
+ restore_output()
139
+ if output_format == CliOutputFormat.JSON:
140
+ Console().print_json(_to_json(formatted_runs))
141
+ else:
142
+ Console().print(_to_table(formatted_runs))
150
143
  finally:
151
144
  if channel:
152
145
  channel.close()
@@ -300,37 +293,23 @@ def _to_json(run_list: list[_RunListType]) -> str:
300
293
  return json.dumps({"success": True, "runs": runs_list})
301
294
 
302
295
 
303
- def _list_runs(
304
- stub: ExecStub,
305
- output_format: str = CliOutputFormat.DEFAULT,
306
- ) -> None:
296
+ def _list_runs(stub: ExecStub) -> list[_RunListType]:
307
297
  """List all runs."""
308
- with unauthenticated_exc_handler():
298
+ with flwr_cli_grpc_exc_handler():
309
299
  res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
310
300
  run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
311
301
 
312
- formatted_runs = _format_runs(run_dict, res.now)
313
- if output_format == CliOutputFormat.JSON:
314
- Console().print_json(_to_json(formatted_runs))
315
- else:
316
- Console().print(_to_table(formatted_runs))
302
+ return _format_runs(run_dict, res.now)
317
303
 
318
304
 
319
- def _display_one_run(
320
- stub: ExecStub,
321
- run_id: int,
322
- output_format: str = CliOutputFormat.DEFAULT,
323
- ) -> None:
305
+ def _display_one_run(stub: ExecStub, run_id: int) -> list[_RunListType]:
324
306
  """Display information about a specific run."""
325
- with unauthenticated_exc_handler():
307
+ with flwr_cli_grpc_exc_handler():
326
308
  res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
327
309
  if not res.run_dict:
310
+ # This won't be reached as an gRPC error is raised if run_id is invalid
328
311
  raise ValueError(f"Run ID {run_id} not found")
329
312
 
330
313
  run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
331
314
 
332
- formatted_runs = _format_runs(run_dict, res.now)
333
- if output_format == CliOutputFormat.JSON:
334
- Console().print_json(_to_json(formatted_runs))
335
- else:
336
- Console().print(_to_table(formatted_runs))
315
+ return _format_runs(run_dict, res.now)