flwr 1.24.0__py3-none-any.whl → 1.26.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 (204) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +4 -1
  3. flwr/app/message_type.py +29 -0
  4. flwr/app/metadata.py +5 -2
  5. flwr/app/user_config.py +19 -0
  6. flwr/cli/app.py +37 -19
  7. flwr/cli/app_cmd/publish.py +25 -75
  8. flwr/cli/app_cmd/review.py +25 -66
  9. flwr/cli/auth_plugin/auth_plugin.py +5 -10
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
  12. flwr/cli/build.py +15 -28
  13. flwr/cli/config/__init__.py +21 -0
  14. flwr/cli/config/ls.py +71 -0
  15. flwr/cli/config_migration.py +297 -0
  16. flwr/cli/config_utils.py +63 -156
  17. flwr/cli/constant.py +71 -0
  18. flwr/cli/federation/__init__.py +0 -2
  19. flwr/cli/federation/ls.py +256 -64
  20. flwr/cli/flower_config.py +429 -0
  21. flwr/cli/install.py +23 -62
  22. flwr/cli/log.py +23 -37
  23. flwr/cli/login/login.py +29 -63
  24. flwr/cli/ls.py +72 -61
  25. flwr/cli/new/new.py +98 -309
  26. flwr/cli/pull.py +19 -37
  27. flwr/cli/run/run.py +87 -100
  28. flwr/cli/run_utils.py +23 -5
  29. flwr/cli/stop.py +33 -74
  30. flwr/cli/supernode/ls.py +35 -62
  31. flwr/cli/supernode/register.py +31 -80
  32. flwr/cli/supernode/unregister.py +24 -70
  33. flwr/cli/typing.py +200 -0
  34. flwr/cli/utils.py +160 -412
  35. flwr/client/grpc_adapter_client/connection.py +2 -2
  36. flwr/client/grpc_rere_client/connection.py +9 -6
  37. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  38. flwr/client/message_handler/message_handler.py +2 -1
  39. flwr/client/mod/centraldp_mods.py +1 -1
  40. flwr/client/mod/localdp_mod.py +1 -1
  41. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  42. flwr/client/rest_client/connection.py +6 -4
  43. flwr/client/run_info_store.py +2 -1
  44. flwr/clientapp/client_app.py +2 -1
  45. flwr/common/__init__.py +3 -2
  46. flwr/common/args.py +5 -5
  47. flwr/common/config.py +12 -17
  48. flwr/common/constant.py +3 -16
  49. flwr/common/context.py +2 -1
  50. flwr/common/exit/exit.py +4 -4
  51. flwr/common/exit/exit_code.py +6 -0
  52. flwr/common/grpc.py +2 -1
  53. flwr/common/logger.py +1 -1
  54. flwr/common/message.py +1 -1
  55. flwr/common/retry_invoker.py +13 -5
  56. flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
  57. flwr/common/serde.py +13 -5
  58. flwr/common/telemetry.py +1 -1
  59. flwr/common/typing.py +10 -3
  60. flwr/compat/client/app.py +6 -9
  61. flwr/compat/client/grpc_client/connection.py +2 -1
  62. flwr/compat/common/constant.py +29 -0
  63. flwr/compat/server/app.py +1 -1
  64. flwr/proto/clientappio_pb2.py +2 -2
  65. flwr/proto/clientappio_pb2_grpc.py +104 -88
  66. flwr/proto/clientappio_pb2_grpc.pyi +140 -80
  67. flwr/proto/federation_pb2.py +5 -3
  68. flwr/proto/federation_pb2.pyi +32 -2
  69. flwr/proto/fleet_pb2.py +10 -10
  70. flwr/proto/fleet_pb2.pyi +5 -1
  71. flwr/proto/run_pb2.py +18 -26
  72. flwr/proto/run_pb2.pyi +10 -58
  73. flwr/proto/serverappio_pb2.py +2 -2
  74. flwr/proto/serverappio_pb2_grpc.py +138 -207
  75. flwr/proto/serverappio_pb2_grpc.pyi +189 -155
  76. flwr/proto/simulationio_pb2.py +2 -2
  77. flwr/proto/simulationio_pb2_grpc.py +62 -90
  78. flwr/proto/simulationio_pb2_grpc.pyi +95 -55
  79. flwr/server/app.py +7 -13
  80. flwr/server/compat/grid_client_proxy.py +2 -1
  81. flwr/server/grid/grpc_grid.py +5 -5
  82. flwr/server/serverapp/app.py +11 -4
  83. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  84. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
  85. flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
  86. flwr/server/superlink/linkstate/__init__.py +2 -2
  87. flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -10
  88. flwr/server/superlink/linkstate/linkstate.py +34 -21
  89. flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
  90. flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +471 -516
  91. flwr/server/superlink/linkstate/utils.py +49 -2
  92. flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
  93. flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
  94. flwr/server/utils/validator.py +1 -1
  95. flwr/server/workflow/default_workflows.py +2 -1
  96. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  97. flwr/serverapp/strategy/bulyan.py +7 -1
  98. flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
  99. flwr/serverapp/strategy/fedavg.py +1 -1
  100. flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
  101. flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
  102. flwr/simulation/run_simulation.py +3 -12
  103. flwr/simulation/simulationio_connection.py +3 -3
  104. flwr/{common → supercore}/address.py +7 -33
  105. flwr/supercore/app_utils.py +2 -1
  106. flwr/supercore/constant.py +27 -2
  107. flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
  108. flwr/supercore/credential_store/__init__.py +33 -0
  109. flwr/supercore/credential_store/credential_store.py +34 -0
  110. flwr/supercore/credential_store/file_credential_store.py +76 -0
  111. flwr/{common → supercore}/date.py +0 -11
  112. flwr/supercore/ffs/disk_ffs.py +1 -1
  113. flwr/supercore/object_store/object_store_factory.py +14 -6
  114. flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
  115. flwr/supercore/sql_mixin.py +315 -0
  116. flwr/{cli/new/templates → supercore/state}/__init__.py +2 -2
  117. flwr/{cli/new/templates/app/code/flwr_tune → supercore/state/alembic}/__init__.py +2 -2
  118. flwr/supercore/state/alembic/env.py +103 -0
  119. flwr/supercore/state/alembic/script.py.mako +43 -0
  120. flwr/supercore/state/alembic/utils.py +239 -0
  121. flwr/{cli/new/templates/app → supercore/state/alembic/versions}/__init__.py +2 -2
  122. flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
  123. flwr/supercore/state/schema/README.md +121 -0
  124. flwr/{cli/new/templates/app/code → supercore/state/schema}/__init__.py +2 -2
  125. flwr/supercore/state/schema/corestate_tables.py +36 -0
  126. flwr/supercore/state/schema/linkstate_tables.py +152 -0
  127. flwr/supercore/state/schema/objectstore_tables.py +90 -0
  128. flwr/supercore/superexec/run_superexec.py +2 -2
  129. flwr/supercore/utils.py +225 -0
  130. flwr/superlink/federation/federation_manager.py +2 -2
  131. flwr/superlink/federation/noop_federation_manager.py +8 -6
  132. flwr/superlink/servicer/control/control_grpc.py +2 -0
  133. flwr/superlink/servicer/control/control_servicer.py +106 -21
  134. flwr/supernode/cli/flower_supernode.py +2 -1
  135. flwr/supernode/nodestate/in_memory_nodestate.py +62 -1
  136. flwr/supernode/nodestate/nodestate.py +45 -0
  137. flwr/supernode/runtime/run_clientapp.py +14 -14
  138. flwr/supernode/servicer/clientappio/clientappio_servicer.py +13 -5
  139. flwr/supernode/start_client_internal.py +17 -10
  140. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/METADATA +8 -8
  141. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/RECORD +144 -184
  142. flwr/cli/federation/show.py +0 -317
  143. flwr/cli/new/templates/app/.gitignore.tpl +0 -163
  144. flwr/cli/new/templates/app/LICENSE.tpl +0 -202
  145. flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
  146. flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
  147. flwr/cli/new/templates/app/README.md.tpl +0 -37
  148. flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
  149. flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
  150. flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
  151. flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
  152. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
  153. flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
  154. flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
  155. flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
  156. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
  157. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
  158. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
  159. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
  160. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
  161. flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
  162. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
  163. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
  164. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
  165. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
  166. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
  167. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
  168. flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
  169. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
  170. flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
  171. flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
  172. flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
  173. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
  174. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
  175. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
  176. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
  177. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
  178. flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
  179. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
  180. flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
  181. flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
  182. flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
  183. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -99
  184. flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
  185. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
  186. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
  187. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
  188. flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
  189. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
  190. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
  191. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
  192. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
  193. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
  194. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
  195. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
  196. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
  197. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
  198. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
  199. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
  200. flwr/common/pyproject.py +0 -42
  201. flwr/supercore/sqlite_mixin.py +0 -159
  202. /flwr/{common → supercore}/version.py +0 -0
  203. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
  204. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
flwr/__init__.py CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  import importlib
19
19
 
20
- from flwr.common.version import package_version as _package_version
20
+ from flwr.supercore.version import package_version as _package_version
21
21
 
22
22
  from . import app, clientapp, serverapp
23
23
 
flwr/app/__init__.py CHANGED
@@ -15,7 +15,6 @@
15
15
  """Public Flower App APIs."""
16
16
 
17
17
 
18
- from flwr.common.constant import MessageType
19
18
  from flwr.common.context import Context
20
19
  from flwr.common.message import Message
21
20
  from flwr.common.record import (
@@ -27,7 +26,9 @@ from flwr.common.record import (
27
26
  )
28
27
 
29
28
  from .error import Error
29
+ from .message_type import MessageType
30
30
  from .metadata import Metadata
31
+ from .user_config import UserConfig, UserConfigValue
31
32
 
32
33
  __all__ = [
33
34
  "Array",
@@ -40,4 +41,6 @@ __all__ = [
40
41
  "Metadata",
41
42
  "MetricRecord",
42
43
  "RecordDict",
44
+ "UserConfig",
45
+ "UserConfigValue",
43
46
  ]
@@ -0,0 +1,29 @@
1
+ # Copyright 2026 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
+ """MessageType constants."""
16
+
17
+ from __future__ import annotations
18
+
19
+
20
+ class MessageType:
21
+ """Message type."""
22
+
23
+ TRAIN = "train"
24
+ EVALUATE = "evaluate"
25
+ QUERY = "query"
26
+
27
+ def __new__(cls) -> MessageType:
28
+ """Prevent instantiation."""
29
+ raise TypeError(f"{cls.__name__} cannot be instantiated.")
flwr/app/metadata.py CHANGED
@@ -19,7 +19,10 @@ from __future__ import annotations
19
19
 
20
20
  from typing import cast
21
21
 
22
- from ..common.constant import MessageType, MessageTypeLegacy
22
+ from flwr.app.message_type import MessageType
23
+ from flwr.supercore.constant import SYSTEM_MESSAGE_TYPE
24
+
25
+ from ..common.constant import MessageTypeLegacy
23
26
 
24
27
 
25
28
  class Metadata: # pylint: disable=too-many-instance-attributes
@@ -194,7 +197,7 @@ def validate_message_type(message_type: str) -> bool:
194
197
  MessageType.TRAIN,
195
198
  MessageType.EVALUATE,
196
199
  MessageType.QUERY,
197
- MessageType.SYSTEM,
200
+ SYSTEM_MESSAGE_TYPE,
198
201
  }
199
202
  if message_type in valid_types:
200
203
  return True
@@ -0,0 +1,19 @@
1
+ # Copyright 2026 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
+ """UserConfig type definition."""
16
+
17
+
18
+ UserConfigValue = bool | float | int | str
19
+ UserConfig = dict[str, UserConfigValue]
flwr/cli/app.py CHANGED
@@ -14,16 +14,19 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface."""
16
16
 
17
+
18
+ from typing import Any, TypedDict
19
+
17
20
  import typer
18
21
  from typer.main import get_command
19
22
 
20
- from flwr.common.version import package_version
23
+ from flwr.supercore.version import package_version
21
24
 
22
25
  from .app_cmd import publish as app_publish
23
26
  from .app_cmd import review as app_review
24
27
  from .build import build
28
+ from .config import ls as config_list
25
29
  from .federation import ls as federation_list
26
- from .federation import show as federation_show
27
30
  from .install import install
28
31
  from .log import log
29
32
  from .login import login
@@ -36,6 +39,15 @@ from .supernode import ls as supernode_list
36
39
  from .supernode import register as supernode_register
37
40
  from .supernode import unregister as supernode_unregister
38
41
 
42
+
43
+ class CommandKwargs(TypedDict):
44
+ """Keywords for typer command to make mypy happy."""
45
+
46
+ context_settings: dict[str, Any]
47
+
48
+
49
+ ALLOW_EXTRAS: CommandKwargs = {"context_settings": {"allow_extra_args": True}}
50
+
39
51
  app = typer.Typer(
40
52
  help=typer.style(
41
53
  "flwr is the Flower command line interface.",
@@ -50,21 +62,21 @@ app.command()(new)
50
62
  app.command()(run)
51
63
  app.command()(build)
52
64
  app.command()(install)
53
- app.command()(log)
54
- app.command("list")(ls)
55
- app.command(hidden=True)(ls)
56
- app.command()(stop)
57
- app.command()(login)
58
- app.command()(pull)
65
+ app.command(**ALLOW_EXTRAS)(log)
66
+ app.command("list", **ALLOW_EXTRAS)(ls)
67
+ app.command(hidden=True, **ALLOW_EXTRAS)(ls)
68
+ app.command(**ALLOW_EXTRAS)(stop)
69
+ app.command(**ALLOW_EXTRAS)(login)
70
+ app.command(**ALLOW_EXTRAS)(pull)
59
71
 
60
72
  # Create supernode command group
61
73
  supernode_app = typer.Typer(help="Manage SuperNodes")
62
- supernode_app.command()(supernode_register)
63
- supernode_app.command()(supernode_unregister)
74
+ supernode_app.command(**ALLOW_EXTRAS)(supernode_register)
75
+ supernode_app.command(**ALLOW_EXTRAS)(supernode_unregister)
64
76
  # Make it appear as "list"
65
- supernode_app.command("list")(supernode_list)
77
+ supernode_app.command("list", **ALLOW_EXTRAS)(supernode_list)
66
78
  # Hide "ls" command (left as alias)
67
- supernode_app.command(hidden=True)(supernode_list)
79
+ supernode_app.command(hidden=True, **ALLOW_EXTRAS)(supernode_list)
68
80
  app.add_typer(supernode_app, name="supernode")
69
81
 
70
82
  # Create app command group
@@ -76,18 +88,24 @@ app.add_typer(app_app, name="app")
76
88
  # Create federation command group
77
89
  federation_app = typer.Typer(help="Manage Federations")
78
90
  # Make it appear as "list"
79
- federation_app.command("list")(federation_list)
91
+ federation_app.command("list", **ALLOW_EXTRAS)(federation_list)
80
92
  # Hide "ls" command (left as alias)
81
- federation_app.command(hidden=True)(federation_list)
93
+ federation_app.command(hidden=True, **ALLOW_EXTRAS)(federation_list)
82
94
  app.add_typer(federation_app, name="federation")
83
- federation_app.command()(federation_show)
95
+
96
+ # Create config command group
97
+ config_app = typer.Typer(help="Manage Configuration")
98
+ config_app.command("list")(config_list)
99
+ # Hide "ls" command (left as alias)
100
+ config_app.command(hidden=True)(config_list)
101
+ app.add_typer(config_app, name="config")
84
102
 
85
103
  typer_click_object = get_command(app)
86
104
 
87
105
 
88
106
  @app.callback(invoke_without_command=True)
89
- def version_callback(
90
- ver: bool = typer.Option(
107
+ def main(
108
+ version: bool = typer.Option(
91
109
  None,
92
110
  "-V",
93
111
  "--version",
@@ -95,8 +113,8 @@ def version_callback(
95
113
  help="Show the version and exit.",
96
114
  ),
97
115
  ) -> None:
98
- """Print version."""
99
- if ver:
116
+ """Flower CLI."""
117
+ if version:
100
118
  typer.secho(f"Flower version: {package_version}", fg="blue")
101
119
  raise typer.Exit()
102
120
 
@@ -19,12 +19,11 @@ from contextlib import ExitStack
19
19
  from pathlib import Path
20
20
  from typing import IO, Annotated
21
21
 
22
+ import click
22
23
  import requests
23
24
  import typer
24
25
  from requests import Response
25
26
 
26
- from flwr.common.constant import FAB_CONFIG_FILE
27
- from flwr.common.version import package_version as flwr_version
28
27
  from flwr.supercore.constant import (
29
28
  APP_PUBLISH_EXCLUDE_PATTERNS,
30
29
  APP_PUBLISH_INCLUDE_PATTERNS,
@@ -34,17 +33,17 @@ from flwr.supercore.constant import (
34
33
  MAX_TOTAL_BYTES,
35
34
  MIME_MAP,
36
35
  PLATFORM_API_URL,
36
+ SUPERGRID_ADDRESS,
37
37
  UTF8,
38
38
  )
39
+ from flwr.supercore.version import package_version as flwr_version
39
40
 
40
41
  from ..auth_plugin.oidc_cli_plugin import OidcCliPlugin
41
- from ..config_utils import (
42
- load_and_validate,
43
- process_loaded_project_config,
44
- validate_federation_in_project_config,
42
+ from ..utils import (
43
+ build_pathspec,
44
+ load_cli_auth_plugin_from_connection,
45
+ load_gitignore_patterns,
45
46
  )
46
- from ..constant import FEDERATION_CONFIG_HELP_MESSAGE
47
- from ..utils import build_pathspec, load_cli_auth_plugin, load_gitignore_patterns
48
47
 
49
48
 
50
49
  # pylint: disable=too-many-locals
@@ -55,43 +54,16 @@ def publish(
55
54
  help="Project directory to upload (defaults to current directory)."
56
55
  ),
57
56
  ] = Path("."),
58
- federation: Annotated[
59
- str | None,
60
- typer.Argument(
61
- help="Name of the federation used for login before publishing app."
62
- ),
63
- ] = None,
64
- federation_config_overrides: Annotated[
65
- list[str] | None,
66
- typer.Option(
67
- "--federation-config",
68
- help=FEDERATION_CONFIG_HELP_MESSAGE,
69
- ),
70
- ] = None,
71
57
  ) -> None:
72
58
  """Publish a Flower App to the Flower Platform.
73
59
 
74
60
  This command uploads your app project to the Flower Platform. Files are filtered
75
61
  based on .gitignore patterns and allowed file extensions.
76
62
  """
77
- # Load configs
78
- pyproject_path = app / FAB_CONFIG_FILE if app else None
79
- config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
80
- config = process_loaded_project_config(config, errors, warnings)
81
- federation, federation_config = validate_federation_in_project_config(
82
- federation, config, federation_config_overrides
83
- )
84
-
85
- # Load the authentication plugin
86
- auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
63
+ auth_plugin = load_cli_auth_plugin_from_connection(SUPERGRID_ADDRESS)
87
64
  auth_plugin.load_tokens()
88
65
  if not isinstance(auth_plugin, OidcCliPlugin) or not auth_plugin.access_token:
89
- typer.secho(
90
- "❌ Please log in before publishing app.",
91
- fg=typer.colors.RED,
92
- err=True,
93
- )
94
- raise typer.Exit(code=1)
66
+ raise click.ClickException("Please log in before publishing app.")
95
67
 
96
68
  # Load token from the plugin
97
69
  token = auth_plugin.access_token
@@ -106,19 +78,17 @@ def publish(
106
78
  try:
107
79
  resp = _post_files(files_param, token)
108
80
  except requests.RequestException as err:
109
- typer.secho(f"Network error: {err}", fg=typer.colors.RED, err=True)
110
- raise typer.Exit(code=1) from err
81
+ raise click.ClickException(f"Network error: {err}") from err
111
82
 
112
83
  if resp.ok:
113
84
  typer.secho("🎊 Upload successful", fg=typer.colors.GREEN, bold=True)
114
85
  return # success
115
86
 
116
87
  # Error path:
117
- msg = f"Upload failed with status {resp.status_code}"
88
+ msg = f"Upload failed with status {resp.status_code}"
118
89
  if resp.text:
119
90
  msg += f": {resp.text}"
120
- typer.secho(msg, fg=typer.colors.RED, err=True)
121
- raise typer.Exit(code=1)
91
+ raise click.ClickException(msg)
122
92
 
123
93
 
124
94
  def _depth_of(relative_path_to_root: Path) -> int:
@@ -162,14 +132,9 @@ def _collect_file_paths(root: Path) -> list[Path]:
162
132
 
163
133
  # Check max depth
164
134
  if _depth_of(relative_path) > MAX_DIR_DEPTH:
165
- typer.secho(
166
- f"Error: '{path}' "
167
- f"exceeds the maximum directory depth "
168
- f"of {MAX_DIR_DEPTH}.",
169
- fg=typer.colors.RED,
170
- err=True,
135
+ raise click.ClickException(
136
+ f"'{path}' exceeds the maximum directory depth of {MAX_DIR_DEPTH}."
171
137
  )
172
- raise typer.Exit(code=2)
173
138
 
174
139
  file_paths.append(path)
175
140
 
@@ -184,21 +149,15 @@ def _validate_files(file_paths: list[Path]) -> None:
184
149
  Checks file count, individual file size, total size, and UTF-8 encoding.
185
150
  """
186
151
  if len(file_paths) == 0:
187
- typer.secho(
152
+ raise click.ClickException(
188
153
  "Nothing to upload: no files matched after applying .gitignore and "
189
- "allowed extensions.",
190
- fg=typer.colors.RED,
191
- err=True,
154
+ "allowed extensions."
192
155
  )
193
- raise typer.Exit(code=2)
194
156
 
195
157
  if len(file_paths) > MAX_FILE_COUNT:
196
- typer.secho(
197
- f"Too many files: {len(file_paths)} > allowed maximum of {MAX_FILE_COUNT}.",
198
- fg=typer.colors.RED,
199
- err=True,
158
+ raise click.ClickException(
159
+ f"Too many files: {len(file_paths)} > allowed maximum of {MAX_FILE_COUNT}."
200
160
  )
201
- raise typer.Exit(code=2)
202
161
 
203
162
  # Calculate files size
204
163
  total_size = 0
@@ -208,34 +167,25 @@ def _validate_files(file_paths: list[Path]) -> None:
208
167
 
209
168
  # Check single file size
210
169
  if file_size > MAX_FILE_BYTES:
211
- typer.secho(
170
+ raise click.ClickException(
212
171
  f"File too large: '{path.as_posix()}' is {file_size:,} bytes, "
213
- f"exceeding the per-file limit of {MAX_FILE_BYTES:,} bytes.",
214
- fg=typer.colors.RED,
215
- err=True,
172
+ f"exceeding the per-file limit of {MAX_FILE_BYTES:,} bytes."
216
173
  )
217
- raise typer.Exit(code=2)
218
174
 
219
175
  # Ensure we can decode as UTF-8.
220
176
  try:
221
177
  path.read_text(encoding=UTF8)
222
178
  except UnicodeDecodeError as err:
223
- typer.secho(
224
- f"Encoding error: '{path}' is not UTF-8 encoded.",
225
- fg=typer.colors.RED,
226
- err=True,
227
- )
228
- raise typer.Exit(code=2) from err
179
+ raise click.ClickException(
180
+ f"Encoding error: '{path}' is not UTF-8 encoded."
181
+ ) from err
229
182
 
230
183
  # Check total files size
231
184
  if total_size > MAX_TOTAL_BYTES:
232
- typer.secho(
185
+ raise click.ClickException(
233
186
  "Total size of all files is too large: "
234
- f"{total_size:,} bytes > {MAX_TOTAL_BYTES:,} bytes.",
235
- fg=typer.colors.RED,
236
- err=True,
187
+ f"{total_size:,} bytes > {MAX_TOTAL_BYTES:,} bytes."
237
188
  )
238
- raise typer.Exit(code=2)
239
189
 
240
190
  # Print validation passed prompt
241
191
  typer.echo(typer.style("✅ Validation passed", fg=typer.colors.GREEN, bold=True))
@@ -21,6 +21,7 @@ import re
21
21
  from pathlib import Path
22
22
  from typing import Annotated
23
23
 
24
+ import click
24
25
  import requests
25
26
  import typer
26
27
  from cryptography.exceptions import UnsupportedAlgorithm
@@ -28,24 +29,18 @@ from cryptography.hazmat.primitives.asymmetric import ed25519
28
29
 
29
30
  from flwr.common import now
30
31
  from flwr.common.config import get_flwr_dir
31
- from flwr.common.constant import FAB_CONFIG_FILE
32
- from flwr.common.version import package_version as flwr_version
33
- from flwr.supercore.constant import PLATFORM_API_URL
32
+ from flwr.supercore.constant import PLATFORM_API_URL, SUPERGRID_ADDRESS
34
33
  from flwr.supercore.primitives.asymmetric_ed25519 import (
35
34
  create_message_to_sign,
36
35
  load_private_key,
37
36
  sign_message,
38
37
  )
38
+ from flwr.supercore.utils import parse_app_spec, request_download_link
39
+ from flwr.supercore.version import package_version as flwr_version
39
40
 
40
41
  from ..auth_plugin.oidc_cli_plugin import OidcCliPlugin
41
- from ..config_utils import (
42
- load_and_validate,
43
- process_loaded_project_config,
44
- validate_federation_in_project_config,
45
- )
46
- from ..constant import FEDERATION_CONFIG_HELP_MESSAGE
47
42
  from ..install import install_from_fab
48
- from ..utils import load_cli_auth_plugin, parse_app_spec, request_download_link
43
+ from ..utils import load_cli_auth_plugin_from_connection
49
44
 
50
45
  TRY_AGAIN_MESSAGE = "Please try again or press CTRL+C to abort.\n"
51
46
 
@@ -59,63 +54,38 @@ def review(
59
54
  "Version is optional; defaults to the latest."
60
55
  ),
61
56
  ],
62
- app_dir_login: Annotated[
63
- Path,
64
- typer.Argument(
65
- help="Project directory to used for login before reviewing app."
66
- ),
67
- ] = Path("."),
68
- federation: Annotated[
69
- str | None,
70
- typer.Argument(
71
- help="Name of the federation used for login before reviewing app."
72
- ),
73
- ] = None,
74
- federation_config_overrides: Annotated[
75
- list[str] | None,
76
- typer.Option(
77
- "--federation-config",
78
- help=FEDERATION_CONFIG_HELP_MESSAGE,
79
- ),
80
- ] = None,
81
57
  ) -> None:
82
58
  """Download a FAB for <APP-ID>, unpack it for manual review, and upon confirmation
83
59
  sign & submit the review to the Platform."""
84
- # Load configs
85
- pyproject_path = app_dir_login / FAB_CONFIG_FILE if app_dir_login else None
86
- config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
87
- config = process_loaded_project_config(config, errors, warnings)
88
- federation, federation_config = validate_federation_in_project_config(
89
- federation, config, federation_config_overrides
90
- )
60
+ auth_plugin = load_cli_auth_plugin_from_connection(SUPERGRID_ADDRESS)
91
61
 
92
- # Load the authentication plugin
93
- auth_plugin = load_cli_auth_plugin(app_dir_login, federation, federation_config)
94
62
  auth_plugin.load_tokens()
95
63
  if not isinstance(auth_plugin, OidcCliPlugin) or not auth_plugin.access_token:
96
- typer.secho(
97
- "❌ Please log in before reviewing app.",
98
- fg=typer.colors.RED,
99
- err=True,
100
- )
101
- raise typer.Exit(code=1)
64
+ raise click.ClickException("Please log in before reviewing app.")
102
65
 
103
66
  # Load token from the plugin
104
67
  token = auth_plugin.access_token
105
68
 
106
69
  # Validate app version and ID format
107
- app_id, app_version = parse_app_spec(app_spec)
70
+ try:
71
+ app_id, app_version = parse_app_spec(app_spec)
72
+ except ValueError as e:
73
+ raise click.ClickException(str(e)) from e
108
74
 
109
75
  # Download FAB
110
76
  typer.secho("Downloading FAB... ", fg=typer.colors.BLUE)
111
77
  url = f"{PLATFORM_API_URL}/hub/fetch-fab"
112
- presigned_url = request_download_link(app_id, app_version, url, "fab_url")
113
- fab_bytes = _download_fab(presigned_url)
78
+ try:
79
+ presigned_url, _ = request_download_link(app_id, app_version, url, "fab_url")
80
+
81
+ fab_bytes = _download_fab(presigned_url)
114
82
 
115
- # Unpack FAB
116
- typer.secho("Unpacking FAB... ", fg=typer.colors.BLUE)
117
- review_dir = _create_review_dir()
118
- review_app_path = install_from_fab(fab_bytes, review_dir)
83
+ # Unpack FAB
84
+ typer.secho("Unpacking FAB... ", fg=typer.colors.BLUE)
85
+ review_dir = _create_review_dir()
86
+ review_app_path = install_from_fab(fab_bytes, review_dir)
87
+ except ValueError as e:
88
+ raise click.ClickException(str(e)) from e
119
89
 
120
90
  # Extract app version
121
91
  version_pattern = re.compile(r"\b(\d+\.\d+\.\d+)\b")
@@ -194,12 +164,7 @@ def _download_fab(url: str) -> bytes:
194
164
  r = requests.get(url, timeout=60)
195
165
  r.raise_for_status()
196
166
  except requests.RequestException as e:
197
- typer.secho(
198
- f"❌ FAB download failed: {e}",
199
- fg=typer.colors.RED,
200
- err=True,
201
- )
202
- raise typer.Exit(code=1) from e
167
+ raise click.ClickException(f"FAB download failed: {e}") from e
203
168
  return r.content
204
169
 
205
170
 
@@ -233,20 +198,14 @@ def _submit_review(
233
198
  try:
234
199
  resp = requests.post(url, headers=headers, json=payload, timeout=120)
235
200
  except requests.RequestException as e:
236
- typer.secho(
237
- f"❌ Network error while submitting review: {e}",
238
- fg=typer.colors.RED,
239
- err=True,
240
- )
241
- raise typer.Exit(code=1) from e
201
+ raise click.ClickException(f"Network error while submitting review: {e}") from e
242
202
 
243
203
  if resp.ok:
244
204
  typer.secho("🎊 Review submitted", fg=typer.colors.GREEN, bold=True)
245
205
  return
246
206
 
247
207
  # Error path:
248
- msg = f"Review submission failed (HTTP {resp.status_code})"
208
+ msg = f"Review submission failed (HTTP {resp.status_code})"
249
209
  if resp.text:
250
210
  msg += f": {resp.text}"
251
- typer.secho(msg, fg=typer.colors.RED, err=True)
252
- raise typer.Exit(code=1)
211
+ raise click.ClickException(msg)
@@ -17,7 +17,6 @@
17
17
 
18
18
  from abc import ABC, abstractmethod
19
19
  from collections.abc import Sequence
20
- from pathlib import Path
21
20
 
22
21
  from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
23
22
  from flwr.proto.control_pb2_grpc import ControlStub
@@ -35,8 +34,8 @@ class CliAuthPlugin(ABC):
35
34
 
36
35
  Parameters
37
36
  ----------
38
- credentials_path : Path
39
- Path to the Flower account's authentication credentials file.
37
+ host : str
38
+ The address of the SuperLink Control API server.
40
39
  """
41
40
 
42
41
  @staticmethod
@@ -66,20 +65,16 @@ class CliAuthPlugin(ABC):
66
65
  """
67
66
 
68
67
  @abstractmethod
69
- def __init__(self, credentials_path: Path):
68
+ def __init__(self, host: str):
70
69
  """Abstract constructor."""
71
70
 
72
71
  @abstractmethod
73
72
  def store_tokens(self, credentials: AccountAuthCredentials) -> None:
74
- """Store authentication tokens to the `credentials_path`.
75
-
76
- The credentials, including tokens, will be saved as a JSON file
77
- at `credentials_path`.
78
- """
73
+ """Store authentication tokens to the credential store."""
79
74
 
80
75
  @abstractmethod
81
76
  def load_tokens(self) -> None:
82
- """Load authentication tokens from the `credentials_path`."""
77
+ """Load authentication tokens from the credential store."""
83
78
 
84
79
  @abstractmethod
85
80
  def write_tokens_to_metadata(
@@ -16,7 +16,6 @@
16
16
 
17
17
 
18
18
  from collections.abc import Sequence
19
- from pathlib import Path
20
19
 
21
20
  from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
22
21
  from flwr.proto.control_pb2_grpc import ControlStub
@@ -57,7 +56,7 @@ class NoOpCliAuthPlugin(CliAuthPlugin):
57
56
  """
58
57
  raise LoginError("Account authentication is not enabled on this SuperLink.")
59
58
 
60
- def __init__(self, credentials_path: Path) -> None:
59
+ def __init__(self, host: str = "") -> None:
61
60
  pass
62
61
 
63
62
  def store_tokens(self, credentials: AccountAuthCredentials) -> None: