flwr 1.19.0__py3-none-any.whl → 1.21.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 (167) hide show
  1. flwr/__init__.py +4 -1
  2. flwr/app/__init__.py +28 -0
  3. flwr/app/exception.py +31 -0
  4. flwr/cli/auth_plugin/oidc_cli_plugin.py +4 -4
  5. flwr/cli/build.py +15 -5
  6. flwr/cli/cli_user_auth_interceptor.py +1 -1
  7. flwr/cli/config_utils.py +3 -3
  8. flwr/cli/constant.py +25 -8
  9. flwr/cli/log.py +9 -9
  10. flwr/cli/login/login.py +3 -3
  11. flwr/cli/ls.py +5 -5
  12. flwr/cli/new/new.py +23 -4
  13. flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
  14. flwr/cli/new/templates/app/README.md.tpl +5 -0
  15. flwr/cli/new/templates/app/code/__init__.pytorch_msg_api.py.tpl +1 -0
  16. flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +80 -0
  17. flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +41 -0
  18. flwr/cli/new/templates/app/code/task.pytorch_msg_api.py.tpl +98 -0
  19. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -3
  20. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
  21. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
  22. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
  23. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
  24. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
  25. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
  26. flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
  27. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
  28. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
  29. flwr/cli/run/run.py +53 -50
  30. flwr/cli/stop.py +7 -4
  31. flwr/cli/utils.py +29 -11
  32. flwr/client/grpc_adapter_client/connection.py +11 -4
  33. flwr/client/grpc_rere_client/connection.py +93 -129
  34. flwr/client/rest_client/connection.py +134 -164
  35. flwr/clientapp/__init__.py +10 -0
  36. flwr/clientapp/mod/__init__.py +26 -0
  37. flwr/clientapp/mod/centraldp_mods.py +132 -0
  38. flwr/common/args.py +20 -6
  39. flwr/common/auth_plugin/__init__.py +4 -4
  40. flwr/common/auth_plugin/auth_plugin.py +7 -7
  41. flwr/common/constant.py +26 -5
  42. flwr/common/event_log_plugin/event_log_plugin.py +1 -1
  43. flwr/common/exit/__init__.py +4 -0
  44. flwr/common/exit/exit.py +8 -1
  45. flwr/common/exit/exit_code.py +42 -8
  46. flwr/common/exit/exit_handler.py +62 -0
  47. flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
  48. flwr/common/grpc.py +1 -1
  49. flwr/common/{inflatable_grpc_utils.py → inflatable_protobuf_utils.py} +52 -10
  50. flwr/common/inflatable_utils.py +191 -24
  51. flwr/common/logger.py +1 -1
  52. flwr/common/record/array.py +101 -22
  53. flwr/common/record/arraychunk.py +59 -0
  54. flwr/common/retry_invoker.py +30 -11
  55. flwr/common/serde.py +0 -28
  56. flwr/common/telemetry.py +4 -0
  57. flwr/compat/client/app.py +14 -31
  58. flwr/compat/server/app.py +2 -2
  59. flwr/proto/appio_pb2.py +51 -0
  60. flwr/proto/appio_pb2.pyi +195 -0
  61. flwr/proto/appio_pb2_grpc.py +4 -0
  62. flwr/proto/appio_pb2_grpc.pyi +4 -0
  63. flwr/proto/clientappio_pb2.py +4 -19
  64. flwr/proto/clientappio_pb2.pyi +0 -125
  65. flwr/proto/clientappio_pb2_grpc.py +269 -29
  66. flwr/proto/clientappio_pb2_grpc.pyi +114 -21
  67. flwr/proto/control_pb2.py +62 -0
  68. flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +54 -54
  69. flwr/proto/{exec_pb2_grpc.pyi → control_pb2_grpc.pyi} +28 -28
  70. flwr/proto/fleet_pb2.py +12 -20
  71. flwr/proto/fleet_pb2.pyi +6 -36
  72. flwr/proto/serverappio_pb2.py +8 -31
  73. flwr/proto/serverappio_pb2.pyi +0 -152
  74. flwr/proto/serverappio_pb2_grpc.py +107 -38
  75. flwr/proto/serverappio_pb2_grpc.pyi +47 -20
  76. flwr/proto/simulationio_pb2.py +4 -11
  77. flwr/proto/simulationio_pb2.pyi +0 -58
  78. flwr/proto/simulationio_pb2_grpc.py +129 -27
  79. flwr/proto/simulationio_pb2_grpc.pyi +52 -13
  80. flwr/server/app.py +130 -153
  81. flwr/server/fleet_event_log_interceptor.py +4 -0
  82. flwr/server/grid/grpc_grid.py +94 -54
  83. flwr/server/grid/inmemory_grid.py +1 -0
  84. flwr/server/serverapp/app.py +165 -144
  85. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +8 -0
  86. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
  87. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
  88. flwr/server/superlink/fleet/message_handler/message_handler.py +10 -16
  89. flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -2
  90. flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -1
  91. flwr/server/superlink/fleet/vce/vce_api.py +6 -6
  92. flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -0
  93. flwr/server/superlink/linkstate/linkstate.py +2 -1
  94. flwr/server/superlink/linkstate/sqlite_linkstate.py +45 -0
  95. flwr/server/superlink/serverappio/serverappio_grpc.py +2 -2
  96. flwr/server/superlink/serverappio/serverappio_servicer.py +95 -48
  97. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  98. flwr/server/superlink/simulation/simulationio_servicer.py +98 -22
  99. flwr/server/superlink/utils.py +0 -35
  100. flwr/serverapp/__init__.py +12 -0
  101. flwr/serverapp/dp_fixed_clipping.py +352 -0
  102. flwr/serverapp/exception.py +38 -0
  103. flwr/serverapp/strategy/__init__.py +38 -0
  104. flwr/serverapp/strategy/dp_fixed_clipping.py +352 -0
  105. flwr/serverapp/strategy/fedadagrad.py +162 -0
  106. flwr/serverapp/strategy/fedadam.py +181 -0
  107. flwr/serverapp/strategy/fedavg.py +295 -0
  108. flwr/serverapp/strategy/fedopt.py +218 -0
  109. flwr/serverapp/strategy/fedyogi.py +173 -0
  110. flwr/serverapp/strategy/result.py +105 -0
  111. flwr/serverapp/strategy/strategy.py +285 -0
  112. flwr/serverapp/strategy/strategy_utils.py +251 -0
  113. flwr/serverapp/strategy/strategy_utils_tests.py +304 -0
  114. flwr/simulation/app.py +159 -154
  115. flwr/simulation/run_simulation.py +17 -0
  116. flwr/supercore/app_utils.py +58 -0
  117. flwr/supercore/cli/__init__.py +22 -0
  118. flwr/supercore/cli/flower_superexec.py +141 -0
  119. flwr/supercore/corestate/__init__.py +22 -0
  120. flwr/supercore/corestate/corestate.py +81 -0
  121. flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
  122. flwr/supercore/grpc_health/__init__.py +25 -0
  123. flwr/supercore/grpc_health/health_server.py +53 -0
  124. flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
  125. flwr/supercore/license_plugin/__init__.py +22 -0
  126. flwr/supercore/license_plugin/license_plugin.py +26 -0
  127. flwr/supercore/object_store/in_memory_object_store.py +31 -31
  128. flwr/supercore/object_store/object_store.py +20 -42
  129. flwr/supercore/object_store/utils.py +43 -0
  130. flwr/{superexec → supercore/superexec}/__init__.py +1 -1
  131. flwr/supercore/superexec/plugin/__init__.py +28 -0
  132. flwr/supercore/superexec/plugin/base_exec_plugin.py +53 -0
  133. flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
  134. flwr/supercore/superexec/plugin/exec_plugin.py +71 -0
  135. flwr/supercore/superexec/plugin/serverapp_exec_plugin.py +28 -0
  136. flwr/supercore/superexec/plugin/simulation_exec_plugin.py +28 -0
  137. flwr/supercore/superexec/run_superexec.py +185 -0
  138. flwr/supercore/utils.py +32 -0
  139. flwr/superlink/servicer/__init__.py +15 -0
  140. flwr/superlink/servicer/control/__init__.py +22 -0
  141. flwr/{superexec/exec_event_log_interceptor.py → superlink/servicer/control/control_event_log_interceptor.py} +9 -5
  142. flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +39 -28
  143. flwr/superlink/servicer/control/control_license_interceptor.py +82 -0
  144. flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +79 -31
  145. flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +18 -10
  146. flwr/supernode/cli/flower_supernode.py +3 -7
  147. flwr/supernode/cli/flwr_clientapp.py +20 -16
  148. flwr/supernode/nodestate/in_memory_nodestate.py +13 -4
  149. flwr/supernode/nodestate/nodestate.py +3 -44
  150. flwr/supernode/runtime/run_clientapp.py +129 -115
  151. flwr/supernode/servicer/clientappio/__init__.py +1 -3
  152. flwr/supernode/servicer/clientappio/clientappio_servicer.py +217 -165
  153. flwr/supernode/start_client_internal.py +205 -148
  154. {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/METADATA +5 -3
  155. {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/RECORD +161 -117
  156. {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
  157. flwr/common/inflatable_rest_utils.py +0 -99
  158. flwr/proto/exec_pb2.py +0 -62
  159. flwr/superexec/app.py +0 -45
  160. flwr/superexec/deployment.py +0 -192
  161. flwr/superexec/executor.py +0 -100
  162. flwr/superexec/simulation.py +0 -130
  163. /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
  164. /flwr/{server/superlink → supercore}/ffs/__init__.py +0 -0
  165. /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
  166. /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
  167. {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,71 @@
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
+ """Abstract base class ExecPlugin."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from collections.abc import Sequence
20
+ from typing import Callable, Optional
21
+
22
+ from flwr.common.typing import Run
23
+
24
+
25
+ class ExecPlugin(ABC):
26
+ """Abstract base class for SuperExec plugins."""
27
+
28
+ def __init__(
29
+ self,
30
+ appio_api_address: str,
31
+ flwr_dir: str,
32
+ get_run: Callable[[int], Run],
33
+ ) -> None:
34
+ self.appio_api_address = appio_api_address
35
+ self.flwr_dir = flwr_dir
36
+ self.get_run = get_run
37
+
38
+ @abstractmethod
39
+ def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
40
+ """Select a run ID to execute from a sequence of candidates.
41
+
42
+ A candidate run ID is one that has at least one pending message and is
43
+ not currently in progress (i.e., not associated with a token).
44
+
45
+ Parameters
46
+ ----------
47
+ candidate_run_ids : Sequence[int]
48
+ A sequence of candidate run IDs to choose from.
49
+
50
+ Returns
51
+ -------
52
+ Optional[int]
53
+ The selected run ID, or None if no suitable candidate is found.
54
+ """
55
+
56
+ @abstractmethod
57
+ def launch_app(self, token: str, run_id: int) -> None:
58
+ """Launch the application associated with a given run ID and token.
59
+
60
+ This method starts the application process using the given `token`.
61
+ The `run_id` is used solely for bookkeeping purposes, allowing any
62
+ plugin implementation to associate this launch with a specific run.
63
+
64
+ Parameters
65
+ ----------
66
+ token : str
67
+ The token required to run the application.
68
+ run_id : int
69
+ The ID of the run associated with the token, used for tracking or
70
+ logging purposes.
71
+ """
@@ -0,0 +1,28 @@
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
+ """Simple Flower SuperExec plugin for ServerApp."""
16
+
17
+
18
+ from .base_exec_plugin import BaseExecPlugin
19
+
20
+
21
+ class ServerAppExecPlugin(BaseExecPlugin):
22
+ """Simple Flower SuperExec plugin for ServerApp.
23
+
24
+ The plugin always selects the first candidate run ID.
25
+ """
26
+
27
+ command = "flwr-serverapp"
28
+ appio_api_address_arg = "--serverappio-api-address"
@@ -0,0 +1,28 @@
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
+ """Simple Flower SuperExec plugin for simulation processes."""
16
+
17
+
18
+ from .base_exec_plugin import BaseExecPlugin
19
+
20
+
21
+ class SimulationExecPlugin(BaseExecPlugin):
22
+ """Simple Flower SuperExec plugin for simulation processes.
23
+
24
+ The plugin always selects the first candidate run ID.
25
+ """
26
+
27
+ command = "flwr-simulation"
28
+ appio_api_address_arg = "--simulationio-api-address"
@@ -0,0 +1,185 @@
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 SuperExec."""
16
+
17
+
18
+ import time
19
+ from logging import WARN
20
+ from typing import Optional, Union
21
+
22
+ from flwr.common.config import get_flwr_dir
23
+ from flwr.common.exit import register_signal_handlers
24
+ from flwr.common.grpc import create_channel, on_channel_state_change
25
+ from flwr.common.logger import log
26
+ from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
27
+ from flwr.common.serde import run_from_proto
28
+ from flwr.common.telemetry import EventType
29
+ from flwr.common.typing import Run
30
+ from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
31
+ ListAppsToLaunchRequest,
32
+ RequestTokenRequest,
33
+ )
34
+ from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
35
+ from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611
36
+ from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
37
+ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
38
+ from flwr.supercore.app_utils import start_parent_process_monitor
39
+ from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
40
+
41
+ from .plugin import ExecPlugin
42
+
43
+
44
+ def run_superexec( # pylint: disable=R0913,R0914,R0917
45
+ plugin_class: type[ExecPlugin],
46
+ stub_class: Union[
47
+ type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
48
+ ],
49
+ appio_api_address: str,
50
+ flwr_dir: Optional[str] = None,
51
+ parent_pid: Optional[int] = None,
52
+ health_server_address: Optional[str] = None,
53
+ ) -> None:
54
+ """Run Flower SuperExec.
55
+
56
+ Parameters
57
+ ----------
58
+ plugin_class : type[ExecPlugin]
59
+ The class of the SuperExec plugin to use.
60
+ stub_class : type[ClientAppIoStub]
61
+ The gRPC stub class for the AppIO API.
62
+ appio_api_address : str
63
+ The address of the AppIO API.
64
+ flwr_dir : Optional[str] (default: None)
65
+ The Flower directory.
66
+ parent_pid : Optional[int] (default: None)
67
+ The PID of the parent process. If provided, the SuperExec will terminate
68
+ when the parent process exits.
69
+ health_server_address : Optional[str] (default: None)
70
+ The address of the health server. If `None` is provided, the health server will
71
+ NOT be started.
72
+ """
73
+ # Start monitoring the parent process if a PID is provided
74
+ if parent_pid is not None:
75
+ start_parent_process_monitor(parent_pid)
76
+
77
+ # Launch gRPC health server
78
+ grpc_servers = []
79
+ if health_server_address is not None:
80
+ health_server = run_health_server_grpc_no_tls(health_server_address)
81
+ grpc_servers.append(health_server)
82
+
83
+ # Create the channel to the AppIO API
84
+ # No TLS support for now, so insecure connection
85
+ channel = create_channel(
86
+ server_address=appio_api_address,
87
+ insecure=True,
88
+ root_certificates=None,
89
+ )
90
+ channel.subscribe(on_channel_state_change)
91
+
92
+ # Register exit handlers to close the channel on exit
93
+ register_signal_handlers(
94
+ event_type=EventType.RUN_SUPEREXEC_LEAVE,
95
+ exit_message="SuperExec terminated gracefully.",
96
+ grpc_servers=grpc_servers,
97
+ exit_handlers=[lambda: channel.close()], # pylint: disable=W0108
98
+ )
99
+
100
+ # Create the gRPC stub for the AppIO API
101
+ stub = stub_class(channel)
102
+ _wrap_stub(stub, _make_simple_grpc_retry_invoker())
103
+
104
+ def get_run(run_id: int) -> Run:
105
+ _req = GetRunRequest(run_id=run_id)
106
+ _res = stub.GetRun(_req)
107
+ return run_from_proto(_res.run)
108
+
109
+ # Create the SuperExec plugin instance
110
+ plugin = plugin_class(
111
+ appio_api_address=appio_api_address,
112
+ flwr_dir=str(get_flwr_dir(flwr_dir)),
113
+ get_run=get_run,
114
+ )
115
+
116
+ # Start the main loop
117
+ try:
118
+ while True:
119
+ # Fetch suitable run IDs
120
+ ls_req = ListAppsToLaunchRequest()
121
+ ls_res = stub.ListAppsToLaunch(ls_req)
122
+
123
+ # Allow the plugin to select a run ID
124
+ run_id = None
125
+ if ls_res.run_ids:
126
+ run_id = plugin.select_run_id(candidate_run_ids=ls_res.run_ids)
127
+
128
+ # Apply for a token if a run ID was selected
129
+ if run_id is not None:
130
+ tk_req = RequestTokenRequest(run_id=run_id)
131
+ tk_res = stub.RequestToken(tk_req)
132
+
133
+ # Launch the app if a token was granted; do nothing if not
134
+ if tk_res.token:
135
+ plugin.launch_app(token=tk_res.token, run_id=run_id)
136
+
137
+ # Sleep for a while before checking again
138
+ time.sleep(1)
139
+ finally:
140
+ channel.close()
141
+
142
+
143
+ def run_with_deprecation_warning( # pylint: disable=R0913, R0917
144
+ cmd: str,
145
+ plugin_type: str,
146
+ plugin_class: type[ExecPlugin],
147
+ stub_class: Union[
148
+ type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
149
+ ],
150
+ appio_api_address: str,
151
+ flwr_dir: Optional[str],
152
+ parent_pid: Optional[int],
153
+ warn_run_once: bool,
154
+ ) -> None:
155
+ """Log a deprecation warning and run the equivalent `flower-superexec` command.
156
+
157
+ Used for legacy long-running `flwr-*` commands (i.e., without `--token`) that will
158
+ be removed in favor of `flower-superexec`.
159
+ """
160
+ log(
161
+ WARN,
162
+ "Directly executing `%s` is DEPRECATED and will be prohibited "
163
+ "in a future release. Please use `flower-superexec` instead.",
164
+ cmd,
165
+ )
166
+ log(WARN, "For now, the following command is being run automatically:")
167
+ new_cmd = f"flower-superexec --insecure --plugin-type {plugin_type} "
168
+ new_cmd += f"--appio-api-address {appio_api_address} "
169
+ if flwr_dir is not None:
170
+ new_cmd += f"--flwr-dir {flwr_dir} "
171
+ if parent_pid is not None:
172
+ new_cmd += f"--parent-pid {parent_pid}"
173
+ log(WARN, new_cmd)
174
+
175
+ # Warn about unsupported `--run-once` flag
176
+ if warn_run_once:
177
+ log(WARN, "`flower-superexec` does not support the `--run-once` flag.")
178
+
179
+ run_superexec(
180
+ plugin_class=plugin_class,
181
+ stub_class=stub_class,
182
+ appio_api_address=appio_api_address,
183
+ flwr_dir=flwr_dir,
184
+ parent_pid=parent_pid,
185
+ )
@@ -0,0 +1,32 @@
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
+ """Utility functions for the infrastructure."""
16
+
17
+
18
+ def mask_string(value: str, head: int = 4, tail: int = 4) -> str:
19
+ """Mask a string by preserving only the head and tail characters.
20
+
21
+ Mask a string for safe display by preserving the head and tail characters,
22
+ and replacing the middle with '...'. Useful for logging tokens, secrets,
23
+ or IDs without exposing sensitive data.
24
+
25
+ Notes
26
+ -----
27
+ If the string is shorter than the combined length of `head` and `tail`,
28
+ the original string is returned unchanged.
29
+ """
30
+ if len(value) <= head + tail:
31
+ return value
32
+ return f"{value[:head]}...{value[-tail:]}"
@@ -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
+ """Flower SuperLink servicers."""
@@ -0,0 +1,22 @@
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
+ """Control API Servicer."""
16
+
17
+
18
+ from .control_grpc import run_control_api_grpc
19
+
20
+ __all__ = [
21
+ "run_control_api_grpc",
22
+ ]
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower Exec API event log interceptor."""
15
+ """Flower Control API event log interceptor."""
16
16
 
17
17
 
18
18
  from collections.abc import Iterator
@@ -24,11 +24,11 @@ from google.protobuf.message import Message as GrpcMessage
24
24
  from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
25
25
  from flwr.common.typing import LogEntry
26
26
 
27
- from .exec_user_auth_interceptor import shared_account_info
27
+ from .control_user_auth_interceptor import shared_account_info
28
28
 
29
29
 
30
- class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
31
- """Exec API interceptor for logging events."""
30
+ class ControlEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
31
+ """Control API interceptor for logging events."""
32
32
 
33
33
  def __init__(self, log_plugin: EventLogWriterPlugin) -> None:
34
34
  self.log_plugin = log_plugin
@@ -44,8 +44,12 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
44
44
  Continue RPC call if event logger is enabled on the SuperLink, else, terminate
45
45
  RPC call by setting context to abort.
46
46
  """
47
+ # Only apply to Control service
48
+ if not handler_call_details.method.startswith("/flwr.proto.Control/"):
49
+ return continuation(handler_call_details)
50
+
47
51
  # One of the method handlers in
48
- # `flwr.superexec.exec_servicer.ExecServicer`
52
+ # `flwr.superlink.servicer.control.ControlServicer`
49
53
  method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
50
54
  method_name: str = handler_call_details.method
51
55
  return self._generic_event_log_unary_method_handler(method_handler, method_name)
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """SuperExec gRPC API."""
15
+ """Control API server."""
16
16
 
17
17
 
18
18
  from logging import INFO
@@ -21,55 +21,66 @@ from typing import Optional
21
21
  import grpc
22
22
 
23
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
- from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
24
+ from flwr.common.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
25
25
  from flwr.common.event_log_plugin import EventLogWriterPlugin
26
+ from flwr.common.exit import ExitCode, flwr_exit
26
27
  from flwr.common.grpc import generic_create_grpc_server
27
28
  from flwr.common.logger import log
28
- from flwr.common.typing import UserConfig
29
- from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
30
- from flwr.server.superlink.ffs.ffs_factory import FfsFactory
29
+ from flwr.proto.control_pb2_grpc import add_ControlServicer_to_server
31
30
  from flwr.server.superlink.linkstate import LinkStateFactory
31
+ from flwr.supercore.ffs import FfsFactory
32
+ from flwr.supercore.license_plugin import LicensePlugin
32
33
  from flwr.supercore.object_store import ObjectStoreFactory
33
- from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
34
- from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
35
34
 
36
- from .exec_servicer import ExecServicer
37
- from .executor import Executor
35
+ from .control_event_log_interceptor import ControlEventLogInterceptor
36
+ from .control_license_interceptor import ControlLicenseInterceptor
37
+ from .control_servicer import ControlServicer
38
+ from .control_user_auth_interceptor import ControlUserAuthInterceptor
38
39
 
40
+ try:
41
+ from flwr.ee import get_license_plugin
42
+ except ImportError:
39
43
 
40
- # pylint: disable-next=too-many-arguments, too-many-positional-arguments
41
- def run_exec_api_grpc(
44
+ def get_license_plugin() -> Optional[LicensePlugin]:
45
+ """Return the license plugin."""
46
+
47
+
48
+ # pylint: disable-next=too-many-arguments,too-many-positional-arguments,too-many-locals
49
+ def run_control_api_grpc(
42
50
  address: str,
43
- executor: Executor,
44
51
  state_factory: LinkStateFactory,
45
52
  ffs_factory: FfsFactory,
46
53
  objectstore_factory: ObjectStoreFactory,
47
54
  certificates: Optional[tuple[bytes, bytes, bytes]],
48
- config: UserConfig,
49
- auth_plugin: Optional[ExecAuthPlugin] = None,
50
- authz_plugin: Optional[ExecAuthzPlugin] = None,
55
+ is_simulation: bool,
56
+ auth_plugin: Optional[ControlAuthPlugin] = None,
57
+ authz_plugin: Optional[ControlAuthzPlugin] = None,
51
58
  event_log_plugin: Optional[EventLogWriterPlugin] = None,
52
59
  ) -> grpc.Server:
53
- """Run Exec API (gRPC, request-response)."""
54
- executor.set_config(config)
60
+ """Run Control API (gRPC, request-response)."""
61
+ license_plugin: Optional[LicensePlugin] = get_license_plugin()
62
+ if license_plugin and not license_plugin.check_license():
63
+ flwr_exit(ExitCode.SUPERLINK_LICENSE_INVALID)
55
64
 
56
- exec_servicer: grpc.Server = ExecServicer(
65
+ control_servicer: grpc.Server = ControlServicer(
57
66
  linkstate_factory=state_factory,
58
67
  ffs_factory=ffs_factory,
59
68
  objectstore_factory=objectstore_factory,
60
- executor=executor,
69
+ is_simulation=is_simulation,
61
70
  auth_plugin=auth_plugin,
62
71
  )
63
72
  interceptors: list[grpc.ServerInterceptor] = []
73
+ if license_plugin is not None:
74
+ interceptors.append(ControlLicenseInterceptor(license_plugin))
64
75
  if auth_plugin is not None and authz_plugin is not None:
65
- interceptors.append(ExecUserAuthInterceptor(auth_plugin, authz_plugin))
76
+ interceptors.append(ControlUserAuthInterceptor(auth_plugin, authz_plugin))
66
77
  # Event log interceptor must be added after user auth interceptor
67
78
  if event_log_plugin is not None:
68
- interceptors.append(ExecEventLogInterceptor(event_log_plugin))
79
+ interceptors.append(ControlEventLogInterceptor(event_log_plugin))
69
80
  log(INFO, "Flower event logging enabled")
70
- exec_add_servicer_to_server_fn = add_ExecServicer_to_server
71
- exec_grpc_server = generic_create_grpc_server(
72
- servicer_and_add_fn=(exec_servicer, exec_add_servicer_to_server_fn),
81
+ control_add_servicer_to_server_fn = add_ControlServicer_to_server
82
+ control_grpc_server = generic_create_grpc_server(
83
+ servicer_and_add_fn=(control_servicer, control_add_servicer_to_server_fn),
73
84
  server_address=address,
74
85
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
75
86
  certificates=certificates,
@@ -77,14 +88,14 @@ def run_exec_api_grpc(
77
88
  )
78
89
 
79
90
  if auth_plugin is None:
80
- log(INFO, "Flower Deployment Engine: Starting Exec API on %s", address)
91
+ log(INFO, "Flower Deployment Runtime: Starting Control API on %s", address)
81
92
  else:
82
93
  log(
83
94
  INFO,
84
- "Flower Deployment Engine: Starting Exec API with user "
95
+ "Flower Deployment Runtime: Starting Control API with user "
85
96
  "authentication on %s",
86
97
  address,
87
98
  )
88
- exec_grpc_server.start()
99
+ control_grpc_server.start()
89
100
 
90
- return exec_grpc_server
101
+ return control_grpc_server
@@ -0,0 +1,82 @@
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 Control API license interceptor."""
16
+
17
+
18
+ from collections.abc import Iterator
19
+ from typing import Any, Callable, Union
20
+
21
+ import grpc
22
+ from google.protobuf.message import Message as GrpcMessage
23
+
24
+ from flwr.supercore.license_plugin import LicensePlugin
25
+
26
+
27
+ class ControlLicenseInterceptor(grpc.ServerInterceptor): # type: ignore
28
+ """Control API interceptor for license checking."""
29
+
30
+ def __init__(self, license_plugin: LicensePlugin) -> None:
31
+ """Initialize the interceptor with a license plugin."""
32
+ self.license_plugin = license_plugin
33
+
34
+ def intercept_service(
35
+ self,
36
+ continuation: Callable[[Any], Any],
37
+ handler_call_details: grpc.HandlerCallDetails,
38
+ ) -> grpc.RpcMethodHandler:
39
+ """Flower server interceptor license logic.
40
+
41
+ Intercept all unary-unary/unary-stream calls from users and check the license.
42
+ Continue RPC call if license check is enabled and passes, else, terminate RPC
43
+ call by setting context to abort.
44
+ """
45
+ # Only apply to Control service
46
+ if not handler_call_details.method.startswith("/flwr.proto.Control/"):
47
+ return continuation(handler_call_details)
48
+
49
+ # One of the method handlers in
50
+ # `flwr.superlink.servicer.control.ControlServicer`
51
+ method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
52
+ return self._generic_license_unary_method_handler(method_handler)
53
+
54
+ def _generic_license_unary_method_handler(
55
+ self, method_handler: grpc.RpcMethodHandler
56
+ ) -> grpc.RpcMethodHandler:
57
+ def _generic_method_handler(
58
+ request: GrpcMessage,
59
+ context: grpc.ServicerContext,
60
+ ) -> Union[GrpcMessage, Iterator[GrpcMessage]]:
61
+ """Handle the method call with license checking."""
62
+ call = method_handler.unary_unary or method_handler.unary_stream
63
+
64
+ if not self.license_plugin.check_license():
65
+ context.abort(
66
+ grpc.StatusCode.PERMISSION_DENIED,
67
+ "❗️ License check failed. Please contact the SuperLink "
68
+ "administrator.",
69
+ )
70
+ raise grpc.RpcError()
71
+
72
+ return call(request, context) # type: ignore
73
+
74
+ if method_handler.unary_unary:
75
+ message_handler = grpc.unary_unary_rpc_method_handler
76
+ else:
77
+ message_handler = grpc.unary_stream_rpc_method_handler
78
+ return message_handler(
79
+ _generic_method_handler,
80
+ request_deserializer=method_handler.request_deserializer,
81
+ response_serializer=method_handler.response_serializer,
82
+ )