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,53 @@
1
+ # =====================================================================
2
+ # For a full TOML configuration guide, check the Flower docs:
3
+ # https://flower.ai/docs/framework/how-to-configure-pyproject-toml.html
4
+ # =====================================================================
5
+
6
+ [build-system]
7
+ requires = ["hatchling"]
8
+ build-backend = "hatchling.build"
9
+
10
+ [project]
11
+ name = "$package_name"
12
+ version = "1.0.0"
13
+ description = ""
14
+ license = "Apache-2.0"
15
+ # Dependencies for your Flower App
16
+ dependencies = [
17
+ "flwr[simulation]>=1.21.0",
18
+ "flwr-datasets[vision]>=0.5.0",
19
+ "torch==2.7.1",
20
+ "torchvision==0.22.1",
21
+ ]
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["."]
25
+
26
+ [tool.flwr.app]
27
+ publisher = "$username"
28
+
29
+ # Point to your ServerApp and ClientApp objects
30
+ [tool.flwr.app.components]
31
+ serverapp = "$import_name.server_app:app"
32
+ clientapp = "$import_name.client_app:app"
33
+
34
+ # Custom config values accessible via `context.run_config`
35
+ [tool.flwr.app.config]
36
+ num-server-rounds = 3
37
+ fraction-train = 0.5
38
+ local-epochs = 1
39
+ lr = 0.01
40
+
41
+ # Default federation to use when running the app
42
+ [tool.flwr.federations]
43
+ default = "local-simulation"
44
+
45
+ # Local simulation federation with 10 virtual SuperNodes
46
+ [tool.flwr.federations.local-simulation]
47
+ options.num-supernodes = 10
48
+
49
+ # Remote federation example for use with SuperLink
50
+ [tool.flwr.federations.remote-federation]
51
+ address = "<SUPERLINK-ADDRESS>:<PORT>"
52
+ insecure = true # Remove this line to enable TLS
53
+ # root-certificates = "<PATH/TO/ca.crt>" # For TLS setup
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.20.0",
17
+ "flwr[simulation]>=1.21.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
19
  "scikit-learn>=1.6.1",
20
20
  ]
@@ -14,7 +14,7 @@ description = ""
14
14
  license = "Apache-2.0"
15
15
  # Dependencies for your Flower App
16
16
  dependencies = [
17
- "flwr[simulation]>=1.20.0",
17
+ "flwr[simulation]>=1.21.0",
18
18
  "flwr-datasets[vision]>=0.5.0",
19
19
  "tensorflow>=2.11.1,<2.18.0",
20
20
  ]
flwr/cli/run/run.py CHANGED
@@ -30,7 +30,7 @@ from flwr.cli.config_utils import (
30
30
  process_loaded_project_config,
31
31
  validate_federation_in_project_config,
32
32
  )
33
- from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
33
+ from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE, RUN_CONFIG_HELP_MESSAGE
34
34
  from flwr.common.config import (
35
35
  flatten_dict,
36
36
  get_metadata_from_config,
@@ -41,8 +41,8 @@ from flwr.common.constant import CliOutputFormat
41
41
  from flwr.common.logger import print_json_error, redirect_output, restore_output
42
42
  from flwr.common.serde import config_record_to_proto, fab_to_proto, user_config_to_proto
43
43
  from flwr.common.typing import Fab
44
- from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
45
- from flwr.proto.exec_pb2_grpc import ExecStub
44
+ from flwr.proto.control_pb2 import StartRunRequest # pylint: disable=E0611
45
+ from flwr.proto.control_pb2_grpc import ControlStub
46
46
 
47
47
  from ..log import start_stream
48
48
  from ..utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
@@ -65,11 +65,7 @@ def run(
65
65
  typer.Option(
66
66
  "--run-config",
67
67
  "-c",
68
- help="Override run configuration values in the format:\n\n"
69
- "`--run-config 'key1=value1 key2=value2' --run-config 'key3=value3'`\n\n"
70
- "Values can be of any type supported in TOML, such as bool, int, "
71
- "float, or string. Ensure that the keys (`key1`, `key2`, `key3` "
72
- "in this example) exist in `pyproject.toml` for proper overriding.",
68
+ help=RUN_CONFIG_HELP_MESSAGE,
73
69
  ),
74
70
  ] = None,
75
71
  federation_config_overrides: Annotated[
@@ -112,7 +108,7 @@ def run(
112
108
  )
113
109
 
114
110
  if "address" in federation_config:
115
- _run_with_exec_api(
111
+ _run_with_control_api(
116
112
  app,
117
113
  federation,
118
114
  federation_config,
@@ -121,7 +117,7 @@ def run(
121
117
  output_format,
122
118
  )
123
119
  else:
124
- _run_without_exec_api(
120
+ _run_without_control_api(
125
121
  app, federation_config, run_config_overrides, federation
126
122
  )
127
123
  except (typer.Exit, Exception) as err: # pylint: disable=broad-except
@@ -142,7 +138,7 @@ def run(
142
138
 
143
139
 
144
140
  # pylint: disable-next=R0913, R0914, R0917
145
- def _run_with_exec_api(
141
+ def _run_with_control_api(
146
142
  app: Path,
147
143
  federation: str,
148
144
  federation_config: dict[str, Any],
@@ -154,7 +150,7 @@ def _run_with_exec_api(
154
150
  try:
155
151
  auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
156
152
  channel = init_channel(app, federation_config, auth_plugin)
157
- stub = ExecStub(channel)
153
+ stub = ControlStub(channel)
158
154
 
159
155
  fab_bytes, fab_hash, config = build_fab(app)
160
156
  fab_id, fab_version = get_metadata_from_config(config)
@@ -203,7 +199,7 @@ def _run_with_exec_api(
203
199
  channel.close()
204
200
 
205
201
 
206
- def _run_without_exec_api(
202
+ def _run_without_control_api(
207
203
  app: Optional[Path],
208
204
  federation_config: dict[str, Any],
209
205
  config_overrides: Optional[list[str]],
flwr/cli/stop.py CHANGED
@@ -32,8 +32,11 @@ from flwr.cli.config_utils import (
32
32
  from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
33
33
  from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
34
34
  from flwr.common.logger import print_json_error, redirect_output, restore_output
35
- from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
36
- from flwr.proto.exec_pb2_grpc import ExecStub
35
+ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
36
+ StopRunRequest,
37
+ StopRunResponse,
38
+ )
39
+ from flwr.proto.control_pb2_grpc import ControlStub
37
40
 
38
41
  from .utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
39
42
 
@@ -88,7 +91,7 @@ def stop( # pylint: disable=R0914
88
91
  try:
89
92
  auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
90
93
  channel = init_channel(app, federation_config, auth_plugin)
91
- stub = ExecStub(channel) # pylint: disable=unused-variable # noqa: F841
94
+ stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
92
95
 
93
96
  typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
94
97
  _stop_run(stub=stub, run_id=run_id, output_format=output_format)
@@ -120,7 +123,7 @@ def stop( # pylint: disable=R0914
120
123
  captured_output.close()
121
124
 
122
125
 
123
- def _stop_run(stub: ExecStub, run_id: int, output_format: str) -> None:
126
+ def _stop_run(stub: ControlStub, run_id: int, output_format: str) -> None:
124
127
  """Stop a run."""
125
128
  with flwr_cli_grpc_exc_handler():
126
129
  response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
flwr/cli/utils.py CHANGED
@@ -32,6 +32,7 @@ from flwr.common.constant import (
32
32
  AUTH_TYPE_JSON_KEY,
33
33
  CREDENTIALS_DIR,
34
34
  FLWR_DIR,
35
+ NO_USER_AUTH_MESSAGE,
35
36
  RUN_ID_NOT_FOUND_MESSAGE,
36
37
  )
37
38
  from flwr.common.grpc import (
@@ -259,7 +260,7 @@ def try_obtain_cli_auth_plugin(
259
260
  def init_channel(
260
261
  app: Path, federation_config: dict[str, Any], auth_plugin: Optional[CliAuthPlugin]
261
262
  ) -> grpc.Channel:
262
- """Initialize gRPC channel to the Exec API."""
263
+ """Initialize gRPC channel to the Control API."""
263
264
  insecure, root_certificates_bytes = validate_certificate_in_federation_config(
264
265
  app, federation_config
265
266
  )
@@ -312,11 +313,21 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
312
313
  )
313
314
  raise typer.Exit(code=1) from None
314
315
  if e.code() == grpc.StatusCode.UNIMPLEMENTED:
315
- typer.secho(
316
- "❌ User authentication is not enabled on this SuperLink.",
317
- fg=typer.colors.RED,
318
- bold=True,
319
- )
316
+ if e.details() == NO_USER_AUTH_MESSAGE: # pylint: disable=E1101
317
+ typer.secho(
318
+ "❌ User authentication is not enabled on this SuperLink.",
319
+ fg=typer.colors.RED,
320
+ bold=True,
321
+ )
322
+ else:
323
+ typer.secho(
324
+ "❌ The SuperLink cannot process this request. Please verify that "
325
+ "you set the address to its Control API endpoint correctly in your "
326
+ "`pyproject.toml`, and ensure that the Flower versions used by "
327
+ "the CLI and SuperLink are compatible.",
328
+ fg=typer.colors.RED,
329
+ bold=True,
330
+ )
320
331
  raise typer.Exit(code=1) from None
321
332
  if e.code() == grpc.StatusCode.PERMISSION_DENIED:
322
333
  typer.secho(
@@ -324,7 +335,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
324
335
  fg=typer.colors.RED,
325
336
  bold=True,
326
337
  )
327
- # pylint: disable=E1101
338
+ # pylint: disable-next=E1101
328
339
  typer.secho(e.details(), fg=typer.colors.RED, bold=True)
329
340
  raise typer.Exit(code=1) from None
330
341
  if e.code() == grpc.StatusCode.UNAVAILABLE:
@@ -337,7 +348,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
337
348
  raise typer.Exit(code=1) from None
338
349
  if (
339
350
  e.code() == grpc.StatusCode.NOT_FOUND
340
- and e.details() == RUN_ID_NOT_FOUND_MESSAGE
351
+ and e.details() == RUN_ID_NOT_FOUND_MESSAGE # pylint: disable=E1101
341
352
  ):
342
353
  typer.secho(
343
354
  "❌ Run ID not found.",
@@ -40,7 +40,7 @@ from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
40
40
  generate_key_pairs,
41
41
  )
42
42
  from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
43
- from flwr.common.typing import Fab, Run, RunNotRunningException
43
+ from flwr.common.typing import Fab, Run
44
44
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
45
45
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
46
46
  CreateNodeRequest,
@@ -157,17 +157,6 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
157
157
  stub = adapter_cls(channel)
158
158
  node: Optional[Node] = None
159
159
 
160
- def _should_giveup_fn(e: Exception) -> bool:
161
- if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
162
- raise RunNotRunningException
163
- if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
164
- return False
165
- return True
166
-
167
- # Restrict retries to cases where the status code is UNAVAILABLE
168
- # If the status code is PERMISSION_DENIED, additionally raise RunNotRunningException
169
- retry_invoker.should_giveup = _should_giveup_fn
170
-
171
160
  # Wrap stub
172
161
  _wrap_stub(stub, retry_invoker)
173
162
  ###########################################################################
@@ -176,6 +176,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
176
176
  # Shared variables for inner functions
177
177
  node: Optional[Node] = None
178
178
 
179
+ # Remove should_giveup from RetryInvoker as REST does not support gRPC status codes
180
+ retry_invoker.should_giveup = None
181
+
179
182
  ###########################################################################
180
183
  # heartbeat/create_node/delete_node/receive/send/get_run functions
181
184
  ###########################################################################
@@ -13,3 +13,13 @@
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
15
  """Public Flower ClientApp APIs."""
16
+
17
+
18
+ from flwr.client.client_app import ClientApp
19
+
20
+ from . import mod
21
+
22
+ __all__ = [
23
+ "ClientApp",
24
+ "mod",
25
+ ]
@@ -0,0 +1,26 @@
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 Built-in Mods."""
16
+
17
+
18
+ from flwr.client.mod.comms_mods import arrays_size_mod, message_size_mod
19
+
20
+ from .centraldp_mods import fixedclipping_mod
21
+
22
+ __all__ = [
23
+ "arrays_size_mod",
24
+ "fixedclipping_mod",
25
+ "message_size_mod",
26
+ ]
@@ -0,0 +1,132 @@
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
+ """Clipping modifiers for central DP with client-side clipping."""
16
+
17
+
18
+ from collections import OrderedDict
19
+ from logging import INFO, WARN
20
+ from typing import cast
21
+
22
+ from flwr.client.typing import ClientAppCallable
23
+ from flwr.common import Array, ArrayRecord, Context, Message, MessageType, log
24
+ from flwr.common.differential_privacy import compute_clip_model_update
25
+ from flwr.common.differential_privacy_constants import KEY_CLIPPING_NORM
26
+
27
+
28
+ # pylint: disable=too-many-return-statements
29
+ def fixedclipping_mod(
30
+ msg: Message, ctxt: Context, call_next: ClientAppCallable
31
+ ) -> Message:
32
+ """Client-side fixed clipping modifier.
33
+
34
+ This mod needs to be used with the `DifferentialPrivacyClientSideFixedClipping`
35
+ server-side strategy wrapper.
36
+
37
+ The wrapper sends the clipping_norm value to the client.
38
+
39
+ This mod clips the client model updates before sending them to the server.
40
+
41
+ It operates on messages of type `MessageType.TRAIN`.
42
+
43
+ Notes
44
+ -----
45
+ Consider the order of mods when using multiple.
46
+
47
+ Typically, fixedclipping_mod should be the last to operate on params.
48
+ """
49
+ if msg.metadata.message_type != MessageType.TRAIN:
50
+ return call_next(msg, ctxt)
51
+
52
+ if len(msg.content.array_records) != 1:
53
+ log(
54
+ WARN,
55
+ "fixedclipping_mod is designed to work with a single ArrayRecord. "
56
+ "Skipping.",
57
+ )
58
+ return call_next(msg, ctxt)
59
+
60
+ if len(msg.content.config_records) != 1:
61
+ log(
62
+ WARN,
63
+ "fixedclipping_mod is designed to work with a single ConfigRecord. "
64
+ "Skipping.",
65
+ )
66
+ return call_next(msg, ctxt)
67
+
68
+ # Get keys in the single ConfigRecord
69
+ keys_in_config = set(next(iter(msg.content.config_records.values())).keys())
70
+ if KEY_CLIPPING_NORM not in keys_in_config:
71
+ raise KeyError(
72
+ f"The {KEY_CLIPPING_NORM} value is not supplied by the "
73
+ f"`DifferentialPrivacyClientSideFixedClipping` wrapper at"
74
+ f" the server side."
75
+ )
76
+ # Record array record communicated to client and clipping norm
77
+ original_array_record = next(iter(msg.content.array_records.values()))
78
+ clipping_norm = cast(
79
+ float, next(iter(msg.content.config_records.values()))[KEY_CLIPPING_NORM]
80
+ )
81
+
82
+ # Call inner app
83
+ out_msg = call_next(msg, ctxt)
84
+
85
+ # Check if the msg has error
86
+ if out_msg.has_error():
87
+ return out_msg
88
+
89
+ # Ensure there is a single ArrayRecord
90
+ if len(out_msg.content.array_records) != 1:
91
+ log(
92
+ WARN,
93
+ "fixedclipping_mod is designed to work with a single ArrayRecord. "
94
+ "Skipping.",
95
+ )
96
+ return out_msg
97
+
98
+ new_array_record_key, client_to_server_arrecord = next(
99
+ iter(out_msg.content.array_records.items())
100
+ )
101
+ # Ensure keys in returned ArrayRecord match those in the one sent from server
102
+ if set(original_array_record.keys()) != set(client_to_server_arrecord.keys()):
103
+ log(
104
+ WARN,
105
+ "fixedclipping_mod: Keys in ArrayRecord must match those from the model "
106
+ "that the ClientApp received. Skipping.",
107
+ )
108
+ return out_msg
109
+
110
+ client_to_server_ndarrays = client_to_server_arrecord.to_numpy_ndarrays()
111
+ # Clip the client update
112
+ compute_clip_model_update(
113
+ param1=client_to_server_ndarrays,
114
+ param2=original_array_record.to_numpy_ndarrays(),
115
+ clipping_norm=clipping_norm,
116
+ )
117
+
118
+ log(
119
+ INFO, "fixedclipping_mod: parameters are clipped by value: %.4f.", clipping_norm
120
+ )
121
+ # Replace outgoing ArrayRecord's Array while preserving their keys
122
+ out_msg.content.array_records[new_array_record_key] = ArrayRecord(
123
+ OrderedDict(
124
+ {
125
+ k: Array(v)
126
+ for k, v in zip(
127
+ client_to_server_arrecord.keys(), client_to_server_ndarrays
128
+ )
129
+ }
130
+ )
131
+ )
132
+ return out_msg
flwr/common/args.py CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  import argparse
19
19
  import sys
20
- from logging import DEBUG, ERROR, WARN
20
+ from logging import DEBUG, ERROR, INFO, WARN
21
21
  from os.path import isfile
22
22
  from pathlib import Path
23
23
  from typing import Optional, Union
@@ -28,6 +28,12 @@ from flwr.common.logger import log
28
28
 
29
29
  def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
30
30
  """Add common Flower arguments for flwr-*app to the provided parser."""
31
+ parser.add_argument(
32
+ "--token",
33
+ type=str,
34
+ required=False,
35
+ help="Unique token generated by AppIo API for each app execution",
36
+ )
31
37
  parser.add_argument(
32
38
  "--flwr-dir",
33
39
  default=None,
@@ -47,6 +53,18 @@ def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
47
53
  "is not encrypted. By default, the server runs with HTTPS enabled. "
48
54
  "Use this flag only if you understand the risks.",
49
55
  )
56
+ parser.add_argument(
57
+ "--parent-pid",
58
+ type=int,
59
+ default=None,
60
+ help="The PID of the parent process. When set, the process will terminate "
61
+ "when the parent process exits.",
62
+ )
63
+ parser.add_argument(
64
+ "--run-once",
65
+ action="store_true",
66
+ help="This flag is deprecated and will be removed in a future release.",
67
+ )
50
68
 
51
69
 
52
70
  def try_obtain_root_certificates(
@@ -72,11 +90,7 @@ def try_obtain_root_certificates(
72
90
  else:
73
91
  # Load the certificates if provided, or load the system certificates
74
92
  if root_cert_path is None:
75
- log(
76
- WARN,
77
- "Both `--insecure` and `--root-certificates` were not set. "
78
- "Using system certificates.",
79
- )
93
+ log(INFO, "Using system certificates")
80
94
  root_certificates = None
81
95
  elif not isfile(root_cert_path):
82
96
  log(ERROR, "Path argument `--root-certificates` does not point to a file.")
@@ -16,11 +16,11 @@
16
16
 
17
17
 
18
18
  from .auth_plugin import CliAuthPlugin as CliAuthPlugin
19
- from .auth_plugin import ExecAuthPlugin as ExecAuthPlugin
20
- from .auth_plugin import ExecAuthzPlugin as ExecAuthzPlugin
19
+ from .auth_plugin import ControlAuthPlugin as ControlAuthPlugin
20
+ from .auth_plugin import ControlAuthzPlugin as ControlAuthzPlugin
21
21
 
22
22
  __all__ = [
23
23
  "CliAuthPlugin",
24
- "ExecAuthPlugin",
25
- "ExecAuthzPlugin",
24
+ "ControlAuthPlugin",
25
+ "ControlAuthzPlugin",
26
26
  ]
@@ -21,13 +21,13 @@ from pathlib import Path
21
21
  from typing import Optional, Union
22
22
 
23
23
  from flwr.common.typing import AccountInfo
24
- from flwr.proto.exec_pb2_grpc import ExecStub
24
+ from flwr.proto.control_pb2_grpc import ControlStub
25
25
 
26
26
  from ..typing import UserAuthCredentials, UserAuthLoginDetails
27
27
 
28
28
 
29
- class ExecAuthPlugin(ABC):
30
- """Abstract Flower Auth Plugin class for ExecServicer.
29
+ class ControlAuthPlugin(ABC):
30
+ """Abstract Flower Auth Plugin class for ControlServicer.
31
31
 
32
32
  Parameters
33
33
  ----------
@@ -69,8 +69,8 @@ class ExecAuthPlugin(ABC):
69
69
  """Refresh authentication tokens in the provided metadata."""
70
70
 
71
71
 
72
- class ExecAuthzPlugin(ABC): # pylint: disable=too-few-public-methods
73
- """Abstract Flower Authorization Plugin class for ExecServicer.
72
+ class ControlAuthzPlugin(ABC): # pylint: disable=too-few-public-methods
73
+ """Abstract Flower Authorization Plugin class for ControlServicer.
74
74
 
75
75
  Parameters
76
76
  ----------
@@ -103,7 +103,7 @@ class CliAuthPlugin(ABC):
103
103
  @abstractmethod
104
104
  def login(
105
105
  login_details: UserAuthLoginDetails,
106
- exec_stub: ExecStub,
106
+ control_stub: ControlStub,
107
107
  ) -> UserAuthCredentials:
108
108
  """Authenticate the user and retrieve authentication credentials.
109
109
 
@@ -111,7 +111,7 @@ class CliAuthPlugin(ABC):
111
111
  ----------
112
112
  login_details : UserAuthLoginDetails
113
113
  An object containing the user's login details.
114
- exec_stub : ExecStub
114
+ control_stub : ControlStub
115
115
  A stub for executing RPC calls to the server.
116
116
 
117
117
  Returns
flwr/common/constant.py CHANGED
@@ -35,7 +35,7 @@ CLIENTAPPIO_PORT = "9094"
35
35
  SERVERAPPIO_PORT = "9091"
36
36
  FLEETAPI_GRPC_RERE_PORT = "9092"
37
37
  FLEETAPI_PORT = "9095"
38
- EXEC_API_PORT = "9093"
38
+ CONTROL_API_PORT = "9093"
39
39
  SIMULATIONIO_PORT = "9096"
40
40
  # Octets
41
41
  SERVER_OCTET = "0.0.0.0"
@@ -51,7 +51,7 @@ FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS = (
51
51
  "[::]:8080" # IPv6 to keep start_server compatible
52
52
  )
53
53
  FLEET_API_REST_DEFAULT_ADDRESS = f"{SERVER_OCTET}:{FLEETAPI_PORT}"
54
- EXEC_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{EXEC_API_PORT}"
54
+ CONTROL_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{CONTROL_API_PORT}"
55
55
  SIMULATIONIO_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{SIMULATIONIO_PORT}"
56
56
  SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS = f"{CLIENT_OCTET}:{SIMULATIONIO_PORT}"
57
57
 
@@ -103,7 +103,7 @@ ISOLATION_MODE_PROCESS = "process"
103
103
  # Log streaming configurations
104
104
  CONN_REFRESH_PERIOD = 60 # Stream connection refresh period
105
105
  CONN_RECONNECT_INTERVAL = 0.5 # Reconnect interval between two stream connections
106
- LOG_STREAM_INTERVAL = 0.5 # Log stream interval for `ExecServicer.StreamLogs`
106
+ LOG_STREAM_INTERVAL = 0.5 # Log stream interval for `ControlServicer.StreamLogs`
107
107
  LOG_UPLOAD_INTERVAL = 0.2 # Minimum interval between two log uploads
108
108
 
109
109
  # Retry configurations
@@ -152,8 +152,9 @@ PULL_INITIAL_BACKOFF = 1 # Initial backoff time for pulling objects
152
152
  PULL_BACKOFF_CAP = 10 # Maximum backoff time for pulling objects
153
153
 
154
154
 
155
- # ExecServicer constants
155
+ # ControlServicer constants
156
156
  RUN_ID_NOT_FOUND_MESSAGE = "Run ID not found"
157
+ NO_USER_AUTH_MESSAGE = "ControlServicer initialized without user authentication"
157
158
 
158
159
 
159
160
  class MessageType:
@@ -259,3 +260,21 @@ class EventLogWriterType:
259
260
  def __new__(cls) -> EventLogWriterType:
260
261
  """Prevent instantiation."""
261
262
  raise TypeError(f"{cls.__name__} cannot be instantiated.")
263
+
264
+
265
+ class ExecPluginType:
266
+ """SuperExec plugin types."""
267
+
268
+ CLIENT_APP = "clientapp"
269
+ SERVER_APP = "serverapp"
270
+ SIMULATION = "simulation"
271
+
272
+ def __new__(cls) -> ExecPluginType:
273
+ """Prevent instantiation."""
274
+ raise TypeError(f"{cls.__name__} cannot be instantiated.")
275
+
276
+ @staticmethod
277
+ def all() -> list[str]:
278
+ """Return all SuperExec plugin types."""
279
+ # Filter all constants (uppercase) of the class
280
+ return [v for k, v in vars(ExecPluginType).items() if k.isupper()]
@@ -25,7 +25,7 @@ from flwr.common.typing import AccountInfo, LogEntry
25
25
 
26
26
 
27
27
  class EventLogWriterPlugin(ABC):
28
- """Abstract Flower Event Log Writer Plugin class for ExecServicer."""
28
+ """Abstract Flower Event Log Writer Plugin class for ControlServicer."""
29
29
 
30
30
  @abstractmethod
31
31
  def __init__(self) -> None:
@@ -17,8 +17,12 @@
17
17
 
18
18
  from .exit import flwr_exit
19
19
  from .exit_code import ExitCode
20
+ from .exit_handler import add_exit_handler
21
+ from .signal_handler import register_signal_handlers
20
22
 
21
23
  __all__ = [
22
24
  "ExitCode",
25
+ "add_exit_handler",
23
26
  "flwr_exit",
27
+ "register_signal_handlers",
24
28
  ]