flwr 1.20.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 (132) 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/cli_user_auth_interceptor.py +1 -1
  6. flwr/cli/config_utils.py +3 -3
  7. flwr/cli/constant.py +25 -8
  8. flwr/cli/log.py +9 -9
  9. flwr/cli/login/login.py +3 -3
  10. flwr/cli/ls.py +5 -5
  11. flwr/cli/new/new.py +11 -0
  12. flwr/cli/new/templates/app/code/__init__.pytorch_msg_api.py.tpl +1 -0
  13. flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +80 -0
  14. flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +41 -0
  15. flwr/cli/new/templates/app/code/task.pytorch_msg_api.py.tpl +98 -0
  16. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  17. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  18. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  19. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  20. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  21. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  22. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  23. flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
  24. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  26. flwr/cli/run/run.py +9 -13
  27. flwr/cli/stop.py +7 -4
  28. flwr/cli/utils.py +19 -8
  29. flwr/client/grpc_rere_client/connection.py +1 -12
  30. flwr/client/rest_client/connection.py +3 -0
  31. flwr/clientapp/__init__.py +10 -0
  32. flwr/clientapp/mod/__init__.py +26 -0
  33. flwr/clientapp/mod/centraldp_mods.py +132 -0
  34. flwr/common/args.py +20 -6
  35. flwr/common/auth_plugin/__init__.py +4 -4
  36. flwr/common/auth_plugin/auth_plugin.py +7 -7
  37. flwr/common/constant.py +23 -4
  38. flwr/common/event_log_plugin/event_log_plugin.py +1 -1
  39. flwr/common/exit/__init__.py +4 -0
  40. flwr/common/exit/exit.py +8 -1
  41. flwr/common/exit/exit_code.py +26 -7
  42. flwr/common/exit/exit_handler.py +62 -0
  43. flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
  44. flwr/common/grpc.py +0 -11
  45. flwr/common/inflatable_utils.py +1 -1
  46. flwr/common/logger.py +1 -1
  47. flwr/common/retry_invoker.py +30 -11
  48. flwr/common/telemetry.py +4 -0
  49. flwr/compat/server/app.py +2 -2
  50. flwr/proto/appio_pb2.py +25 -17
  51. flwr/proto/appio_pb2.pyi +46 -2
  52. flwr/proto/clientappio_pb2.py +3 -11
  53. flwr/proto/clientappio_pb2.pyi +0 -47
  54. flwr/proto/clientappio_pb2_grpc.py +19 -20
  55. flwr/proto/clientappio_pb2_grpc.pyi +10 -11
  56. flwr/proto/control_pb2.py +62 -0
  57. flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +54 -54
  58. flwr/proto/{exec_pb2_grpc.pyi → control_pb2_grpc.pyi} +28 -28
  59. flwr/proto/serverappio_pb2.py +2 -2
  60. flwr/proto/serverappio_pb2_grpc.py +68 -0
  61. flwr/proto/serverappio_pb2_grpc.pyi +26 -0
  62. flwr/proto/simulationio_pb2.py +4 -11
  63. flwr/proto/simulationio_pb2.pyi +0 -58
  64. flwr/proto/simulationio_pb2_grpc.py +129 -27
  65. flwr/proto/simulationio_pb2_grpc.pyi +52 -13
  66. flwr/server/app.py +129 -152
  67. flwr/server/grid/grpc_grid.py +3 -0
  68. flwr/server/grid/inmemory_grid.py +1 -0
  69. flwr/server/serverapp/app.py +157 -146
  70. flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -1
  71. flwr/server/superlink/fleet/vce/vce_api.py +6 -6
  72. flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -0
  73. flwr/server/superlink/linkstate/linkstate.py +2 -1
  74. flwr/server/superlink/linkstate/sqlite_linkstate.py +45 -0
  75. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -1
  76. flwr/server/superlink/serverappio/serverappio_servicer.py +61 -6
  77. flwr/server/superlink/simulation/simulationio_servicer.py +97 -21
  78. flwr/serverapp/__init__.py +12 -0
  79. flwr/serverapp/dp_fixed_clipping.py +352 -0
  80. flwr/serverapp/exception.py +38 -0
  81. flwr/serverapp/strategy/__init__.py +38 -0
  82. flwr/serverapp/strategy/dp_fixed_clipping.py +352 -0
  83. flwr/serverapp/strategy/fedadagrad.py +162 -0
  84. flwr/serverapp/strategy/fedadam.py +181 -0
  85. flwr/serverapp/strategy/fedavg.py +295 -0
  86. flwr/serverapp/strategy/fedopt.py +218 -0
  87. flwr/serverapp/strategy/fedyogi.py +173 -0
  88. flwr/serverapp/strategy/result.py +105 -0
  89. flwr/serverapp/strategy/strategy.py +285 -0
  90. flwr/serverapp/strategy/strategy_utils.py +251 -0
  91. flwr/serverapp/strategy/strategy_utils_tests.py +304 -0
  92. flwr/simulation/app.py +161 -164
  93. flwr/supercore/app_utils.py +58 -0
  94. flwr/{supernode/scheduler → supercore/cli}/__init__.py +3 -3
  95. flwr/supercore/cli/flower_superexec.py +141 -0
  96. flwr/supercore/{scheduler → corestate}/__init__.py +3 -3
  97. flwr/supercore/corestate/corestate.py +81 -0
  98. flwr/supercore/grpc_health/__init__.py +3 -0
  99. flwr/supercore/grpc_health/health_server.py +53 -0
  100. flwr/supercore/grpc_health/simple_health_servicer.py +2 -2
  101. flwr/{superexec → supercore/superexec}/__init__.py +1 -1
  102. flwr/supercore/superexec/plugin/__init__.py +28 -0
  103. flwr/{supernode/scheduler/simple_clientapp_scheduler_plugin.py → supercore/superexec/plugin/base_exec_plugin.py} +10 -6
  104. flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
  105. flwr/supercore/{scheduler/plugin.py → superexec/plugin/exec_plugin.py} +4 -4
  106. flwr/supercore/superexec/plugin/serverapp_exec_plugin.py +28 -0
  107. flwr/supercore/superexec/plugin/simulation_exec_plugin.py +28 -0
  108. flwr/supercore/superexec/run_superexec.py +185 -0
  109. flwr/superlink/servicer/__init__.py +15 -0
  110. flwr/superlink/servicer/control/__init__.py +22 -0
  111. flwr/{superexec/exec_event_log_interceptor.py → superlink/servicer/control/control_event_log_interceptor.py} +7 -7
  112. flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +24 -29
  113. flwr/{superexec/exec_license_interceptor.py → superlink/servicer/control/control_license_interceptor.py} +6 -6
  114. flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +69 -30
  115. flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +10 -10
  116. flwr/supernode/cli/flower_supernode.py +3 -0
  117. flwr/supernode/cli/flwr_clientapp.py +18 -21
  118. flwr/supernode/nodestate/in_memory_nodestate.py +2 -2
  119. flwr/supernode/nodestate/nodestate.py +3 -59
  120. flwr/supernode/runtime/run_clientapp.py +39 -102
  121. flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -17
  122. flwr/supernode/start_client_internal.py +35 -76
  123. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/METADATA +4 -3
  124. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/RECORD +127 -98
  125. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
  126. flwr/proto/exec_pb2.py +0 -62
  127. flwr/superexec/app.py +0 -45
  128. flwr/superexec/deployment.py +0 -191
  129. flwr/superexec/executor.py +0 -100
  130. flwr/superexec/simulation.py +0 -129
  131. /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
  132. {flwr-1.20.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,81 @@
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 CoreState."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import Optional
20
+
21
+
22
+ class CoreState(ABC):
23
+ """Abstract base class for core state."""
24
+
25
+ @abstractmethod
26
+ def create_token(self, run_id: int) -> Optional[str]:
27
+ """Create a token for the given run ID.
28
+
29
+ Parameters
30
+ ----------
31
+ run_id : int
32
+ The ID of the run for which to create a token.
33
+
34
+ Returns
35
+ -------
36
+ str
37
+ The newly generated token if one does not already exist
38
+ for the given run ID, otherwise None.
39
+ """
40
+
41
+ @abstractmethod
42
+ def verify_token(self, run_id: int, token: str) -> bool:
43
+ """Verify a token for the given run ID.
44
+
45
+ Parameters
46
+ ----------
47
+ run_id : int
48
+ The ID of the run for which to verify the token.
49
+ token : str
50
+ The token to verify.
51
+
52
+ Returns
53
+ -------
54
+ bool
55
+ True if the token is valid for the run ID, False otherwise.
56
+ """
57
+
58
+ @abstractmethod
59
+ def delete_token(self, run_id: int) -> None:
60
+ """Delete the token for the given run ID.
61
+
62
+ Parameters
63
+ ----------
64
+ run_id : int
65
+ The ID of the run for which to delete the token.
66
+ """
67
+
68
+ @abstractmethod
69
+ def get_run_id_by_token(self, token: str) -> Optional[int]:
70
+ """Get the run ID associated with a given token.
71
+
72
+ Parameters
73
+ ----------
74
+ token : str
75
+ The token to look up.
76
+
77
+ Returns
78
+ -------
79
+ Optional[int]
80
+ The run ID if the token is valid, otherwise None.
81
+ """
@@ -15,8 +15,11 @@
15
15
  """GRPC health servicers."""
16
16
 
17
17
 
18
+ from .health_server import add_args_health, run_health_server_grpc_no_tls
18
19
  from .simple_health_servicer import SimpleHealthServicer
19
20
 
20
21
  __all__ = [
21
22
  "SimpleHealthServicer",
23
+ "add_args_health",
24
+ "run_health_server_grpc_no_tls",
22
25
  ]
@@ -0,0 +1,53 @@
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
+ """Health servers."""
16
+
17
+
18
+ import argparse
19
+ from logging import INFO
20
+
21
+ import grpc
22
+ from grpc_health.v1.health_pb2_grpc import add_HealthServicer_to_server
23
+
24
+ from flwr.common.grpc import generic_create_grpc_server
25
+ from flwr.common.logger import log
26
+
27
+ from .simple_health_servicer import SimpleHealthServicer
28
+
29
+
30
+ def run_health_server_grpc_no_tls(address: str) -> grpc.Server:
31
+ """Run gRPC health server with no TLS."""
32
+ health_server = generic_create_grpc_server(
33
+ servicer_and_add_fn=(
34
+ SimpleHealthServicer(),
35
+ add_HealthServicer_to_server,
36
+ ),
37
+ server_address=address,
38
+ certificates=None,
39
+ )
40
+ log(INFO, "Starting gRPC health server on %s", address)
41
+ health_server.start()
42
+ return health_server
43
+
44
+
45
+ def add_args_health(parser: argparse.ArgumentParser) -> None:
46
+ """Add arguments for health server."""
47
+ parser.add_argument(
48
+ "--health-server-address",
49
+ type=str,
50
+ default=None,
51
+ help="Health service gRPC server address (IPv4, IPv6, or a domain name) "
52
+ "with no TLS. If not set, the health server will not be started.",
53
+ )
@@ -28,11 +28,11 @@ class SimpleHealthServicer(HealthServicer): # type: ignore
28
28
  """A simple gRPC health servicer that always returns SERVING."""
29
29
 
30
30
  def Check(
31
- self, request: HealthCheckRequest, context: grpc.RpcContext
31
+ self, request: HealthCheckRequest, context: grpc.ServicerContext
32
32
  ) -> HealthCheckResponse:
33
33
  """Return a HealthCheckResponse with SERVING status."""
34
34
  return HealthCheckResponse(status=HealthCheckResponse.SERVING)
35
35
 
36
- def Watch(self, request: HealthCheckRequest, context: grpc.RpcContext) -> None:
36
+ def Watch(self, request: HealthCheckRequest, context: grpc.ServicerContext) -> None:
37
37
  """Watch the health status (not implemented)."""
38
38
  context.abort(grpc.StatusCode.UNIMPLEMENTED, "Watch is not implemented")
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower SuperExec service."""
15
+ """Flower SuperExec."""
@@ -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
+ """Flower SuperExec plugins."""
16
+
17
+
18
+ from .clientapp_exec_plugin import ClientAppExecPlugin
19
+ from .exec_plugin import ExecPlugin
20
+ from .serverapp_exec_plugin import ServerAppExecPlugin
21
+ from .simulation_exec_plugin import SimulationExecPlugin
22
+
23
+ __all__ = [
24
+ "ClientAppExecPlugin",
25
+ "ExecPlugin",
26
+ "ServerAppExecPlugin",
27
+ "SimulationExecPlugin",
28
+ ]
@@ -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
- """Simple Flower ClientApp Scheduler plugin."""
15
+ """Simple base Flower SuperExec plugin for app processes."""
16
16
 
17
17
 
18
18
  import os
@@ -20,15 +20,19 @@ import subprocess
20
20
  from collections.abc import Sequence
21
21
  from typing import Optional
22
22
 
23
- from flwr.supercore.scheduler import SchedulerPlugin
23
+ from .exec_plugin import ExecPlugin
24
24
 
25
25
 
26
- class SimpleClientAppSchedulerPlugin(SchedulerPlugin):
27
- """Simple Flower ClientApp Scheduler plugin.
26
+ class BaseExecPlugin(ExecPlugin):
27
+ """Simple Flower SuperExec plugin for app processes.
28
28
 
29
29
  The plugin always selects the first candidate run ID.
30
30
  """
31
31
 
32
+ # Placeholders to be defined in subclasses
33
+ command = ""
34
+ appio_api_address_arg = ""
35
+
32
36
  def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
33
37
  """Select a run ID to execute from a sequence of candidates."""
34
38
  if not candidate_run_ids:
@@ -37,8 +41,8 @@ class SimpleClientAppSchedulerPlugin(SchedulerPlugin):
37
41
 
38
42
  def launch_app(self, token: str, run_id: int) -> None:
39
43
  """Launch the application associated with a given run ID and token."""
40
- cmds = ["flwr-clientapp", "--insecure"]
41
- cmds += ["--clientappio-api-address", self.appio_api_address]
44
+ cmds = [self.command, "--insecure"]
45
+ cmds += [self.appio_api_address_arg, self.appio_api_address]
42
46
  cmds += ["--token", token]
43
47
  cmds += ["--parent-pid", str(os.getpid())]
44
48
  cmds += ["--flwr-dir", self.flwr_dir]
@@ -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 ClientApp."""
16
+
17
+
18
+ from .base_exec_plugin import BaseExecPlugin
19
+
20
+
21
+ class ClientAppExecPlugin(BaseExecPlugin):
22
+ """Simple Flower SuperExec plugin for ClientApp.
23
+
24
+ The plugin always selects the first candidate run ID.
25
+ """
26
+
27
+ command = "flwr-clientapp"
28
+ appio_api_address_arg = "--clientappio-api-address"
@@ -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
- """Abstract base class SchedulerPlugin."""
15
+ """Abstract base class ExecPlugin."""
16
16
 
17
17
 
18
18
  from abc import ABC, abstractmethod
@@ -22,8 +22,8 @@ from typing import Callable, Optional
22
22
  from flwr.common.typing import Run
23
23
 
24
24
 
25
- class SchedulerPlugin(ABC):
26
- """Abstract base class for Scheduler plugins."""
25
+ class ExecPlugin(ABC):
26
+ """Abstract base class for SuperExec plugins."""
27
27
 
28
28
  def __init__(
29
29
  self,
@@ -59,7 +59,7 @@ class SchedulerPlugin(ABC):
59
59
 
60
60
  This method starts the application process using the given `token`.
61
61
  The `run_id` is used solely for bookkeeping purposes, allowing any
62
- scheduler implementation to associate this launch with a specific run.
62
+ plugin implementation to associate this launch with a specific run.
63
63
 
64
64
  Parameters
65
65
  ----------
@@ -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,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,12 +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 Exec service
48
- if not handler_call_details.method.startswith("/flwr.proto.Exec/"):
47
+ # Only apply to Control service
48
+ if not handler_call_details.method.startswith("/flwr.proto.Control/"):
49
49
  return continuation(handler_call_details)
50
50
 
51
51
  # One of the method handlers in
52
- # `flwr.superexec.exec_servicer.ExecServicer`
52
+ # `flwr.superlink.servicer.control.ControlServicer`
53
53
  method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
54
54
  method_name: str = handler_call_details.method
55
55
  return self._generic_event_log_unary_method_handler(method_handler, method_name)