flwr-nightly 1.14.0.dev20241204__py3-none-any.whl → 1.14.0.dev20241216__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.

Potentially problematic release.


This version of flwr-nightly might be problematic. Click here for more details.

Files changed (101) hide show
  1. flwr/cli/app.py +5 -0
  2. flwr/cli/build.py +1 -0
  3. flwr/cli/cli_user_auth_interceptor.py +86 -0
  4. flwr/cli/config_utils.py +19 -2
  5. flwr/cli/example.py +1 -0
  6. flwr/cli/install.py +1 -0
  7. flwr/cli/log.py +11 -31
  8. flwr/cli/login/__init__.py +22 -0
  9. flwr/cli/login/login.py +81 -0
  10. flwr/cli/ls.py +25 -55
  11. flwr/cli/new/__init__.py +1 -0
  12. flwr/cli/new/new.py +2 -1
  13. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -2
  14. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -2
  15. flwr/cli/run/__init__.py +1 -0
  16. flwr/cli/run/run.py +17 -39
  17. flwr/cli/stop.py +129 -0
  18. flwr/cli/utils.py +96 -1
  19. flwr/client/app.py +14 -3
  20. flwr/client/client.py +1 -0
  21. flwr/client/clientapp/app.py +4 -1
  22. flwr/client/clientapp/utils.py +1 -0
  23. flwr/client/grpc_adapter_client/connection.py +1 -1
  24. flwr/client/grpc_client/connection.py +1 -1
  25. flwr/client/grpc_rere_client/connection.py +13 -7
  26. flwr/client/message_handler/message_handler.py +1 -0
  27. flwr/client/mod/comms_mods.py +1 -0
  28. flwr/client/mod/localdp_mod.py +1 -1
  29. flwr/client/nodestate/__init__.py +1 -0
  30. flwr/client/nodestate/nodestate.py +1 -0
  31. flwr/client/nodestate/nodestate_factory.py +1 -0
  32. flwr/client/rest_client/connection.py +3 -3
  33. flwr/client/supernode/app.py +1 -0
  34. flwr/common/address.py +1 -0
  35. flwr/common/args.py +1 -0
  36. flwr/common/auth_plugin/__init__.py +24 -0
  37. flwr/common/auth_plugin/auth_plugin.py +111 -0
  38. flwr/common/config.py +3 -1
  39. flwr/common/constant.py +6 -1
  40. flwr/common/logger.py +17 -1
  41. flwr/common/message.py +1 -0
  42. flwr/common/object_ref.py +57 -54
  43. flwr/common/pyproject.py +1 -0
  44. flwr/common/record/__init__.py +1 -0
  45. flwr/common/record/parametersrecord.py +1 -0
  46. flwr/common/retry_invoker.py +77 -0
  47. flwr/common/secure_aggregation/secaggplus_utils.py +2 -2
  48. flwr/common/telemetry.py +2 -1
  49. flwr/common/typing.py +12 -0
  50. flwr/common/version.py +1 -0
  51. flwr/proto/exec_pb2.py +27 -3
  52. flwr/proto/exec_pb2.pyi +103 -0
  53. flwr/proto/exec_pb2_grpc.py +102 -0
  54. flwr/proto/exec_pb2_grpc.pyi +39 -0
  55. flwr/proto/fab_pb2.py +4 -4
  56. flwr/proto/fab_pb2.pyi +4 -1
  57. flwr/proto/serverappio_pb2.py +18 -18
  58. flwr/proto/serverappio_pb2.pyi +8 -2
  59. flwr/proto/serverappio_pb2_grpc.py +34 -0
  60. flwr/proto/serverappio_pb2_grpc.pyi +13 -0
  61. flwr/proto/simulationio_pb2.py +2 -2
  62. flwr/proto/simulationio_pb2_grpc.py +34 -0
  63. flwr/proto/simulationio_pb2_grpc.pyi +13 -0
  64. flwr/server/app.py +52 -1
  65. flwr/server/compat/app_utils.py +7 -1
  66. flwr/server/driver/grpc_driver.py +11 -63
  67. flwr/server/driver/inmemory_driver.py +5 -1
  68. flwr/server/serverapp/app.py +9 -2
  69. flwr/server/strategy/dpfedavg_fixed.py +1 -0
  70. flwr/server/superlink/driver/serverappio_grpc.py +1 -0
  71. flwr/server/superlink/driver/serverappio_servicer.py +72 -22
  72. flwr/server/superlink/ffs/disk_ffs.py +1 -0
  73. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -0
  74. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -0
  75. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +32 -12
  76. flwr/server/superlink/fleet/message_handler/message_handler.py +32 -5
  77. flwr/server/superlink/fleet/rest_rere/rest_api.py +4 -1
  78. flwr/server/superlink/fleet/vce/__init__.py +1 -0
  79. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -0
  80. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -0
  81. flwr/server/superlink/linkstate/in_memory_linkstate.py +14 -30
  82. flwr/server/superlink/linkstate/linkstate.py +13 -2
  83. flwr/server/superlink/linkstate/sqlite_linkstate.py +24 -44
  84. flwr/server/superlink/simulation/simulationio_servicer.py +20 -0
  85. flwr/server/superlink/utils.py +65 -0
  86. flwr/simulation/app.py +1 -0
  87. flwr/simulation/ray_transport/ray_actor.py +1 -0
  88. flwr/simulation/ray_transport/utils.py +1 -0
  89. flwr/simulation/run_simulation.py +1 -15
  90. flwr/simulation/simulationio_connection.py +3 -0
  91. flwr/superexec/app.py +1 -0
  92. flwr/superexec/deployment.py +1 -0
  93. flwr/superexec/exec_grpc.py +19 -1
  94. flwr/superexec/exec_servicer.py +76 -2
  95. flwr/superexec/exec_user_auth_interceptor.py +101 -0
  96. flwr/superexec/executor.py +1 -0
  97. {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/METADATA +8 -7
  98. {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/RECORD +101 -93
  99. {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/LICENSE +0 -0
  100. {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/WHEEL +0 -0
  101. {flwr_nightly-1.14.0.dev20241204.dist-info → flwr_nightly-1.14.0.dev20241216.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py CHANGED
@@ -14,12 +14,12 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `run` command."""
16
16
 
17
+
17
18
  import io
18
19
  import json
19
20
  import subprocess
20
- from logging import DEBUG
21
21
  from pathlib import Path
22
- from typing import Annotated, Any, Optional, Union
22
+ from typing import Annotated, Any, Optional
23
23
 
24
24
  import typer
25
25
  from rich.console import Console
@@ -28,9 +28,8 @@ from flwr.cli.build import build
28
28
  from flwr.cli.config_utils import (
29
29
  get_fab_metadata,
30
30
  load_and_validate,
31
- validate_certificate_in_federation_config,
31
+ process_loaded_project_config,
32
32
  validate_federation_in_project_config,
33
- validate_project_config,
34
33
  )
35
34
  from flwr.common.config import (
36
35
  flatten_dict,
@@ -38,8 +37,7 @@ from flwr.common.config import (
38
37
  user_config_to_configsrecord,
39
38
  )
40
39
  from flwr.common.constant import CliOutputFormat
41
- from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
42
- from flwr.common.logger import log, redirect_output, remove_emojis, restore_output
40
+ from flwr.common.logger import print_json_error, redirect_output, restore_output
43
41
  from flwr.common.serde import (
44
42
  configs_record_to_proto,
45
43
  fab_to_proto,
@@ -50,15 +48,11 @@ from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
50
48
  from flwr.proto.exec_pb2_grpc import ExecStub
51
49
 
52
50
  from ..log import start_stream
51
+ from ..utils import init_channel, try_obtain_cli_auth_plugin
53
52
 
54
53
  CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
55
54
 
56
55
 
57
- def on_channel_state_change(channel_connectivity: str) -> None:
58
- """Log channel connectivity."""
59
- log(DEBUG, channel_connectivity)
60
-
61
-
62
56
  # pylint: disable-next=too-many-locals
63
57
  def run(
64
58
  app: Annotated[
@@ -108,14 +102,19 @@ def run(
108
102
 
109
103
  pyproject_path = app / "pyproject.toml" if app else None
110
104
  config, errors, warnings = load_and_validate(path=pyproject_path)
111
- config = validate_project_config(config, errors, warnings)
105
+ config = process_loaded_project_config(config, errors, warnings)
112
106
  federation, federation_config = validate_federation_in_project_config(
113
107
  federation, config
114
108
  )
115
109
 
116
110
  if "address" in federation_config:
117
111
  _run_with_exec_api(
118
- app, federation_config, config_overrides, stream, output_format
112
+ app,
113
+ federation,
114
+ federation_config,
115
+ config_overrides,
116
+ stream,
117
+ output_format,
119
118
  )
120
119
  else:
121
120
  _run_without_exec_api(app, federation_config, config_overrides, federation)
@@ -123,7 +122,7 @@ def run(
123
122
  if suppress_output:
124
123
  restore_output()
125
124
  e_message = captured_output.getvalue()
126
- _print_json_error(e_message, err)
125
+ print_json_error(e_message, err)
127
126
  else:
128
127
  typer.secho(
129
128
  f"{err}",
@@ -136,26 +135,17 @@ def run(
136
135
  captured_output.close()
137
136
 
138
137
 
139
- # pylint: disable-next=too-many-locals
138
+ # pylint: disable-next=R0913, R0914, R0917
140
139
  def _run_with_exec_api(
141
140
  app: Path,
141
+ federation: str,
142
142
  federation_config: dict[str, Any],
143
143
  config_overrides: Optional[list[str]],
144
144
  stream: bool,
145
145
  output_format: str,
146
146
  ) -> None:
147
-
148
- insecure, root_certificates_bytes = validate_certificate_in_federation_config(
149
- app, federation_config
150
- )
151
- channel = create_channel(
152
- server_address=federation_config["address"],
153
- insecure=insecure,
154
- root_certificates=root_certificates_bytes,
155
- max_message_length=GRPC_MAX_MESSAGE_LENGTH,
156
- interceptors=None,
157
- )
158
- channel.subscribe(on_channel_state_change)
147
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation)
148
+ channel = init_channel(app, federation_config, auth_plugin)
159
149
  stub = ExecStub(channel)
160
150
 
161
151
  fab_path, fab_hash = build(app)
@@ -249,15 +239,3 @@ def _run_without_exec_api(
249
239
  check=True,
250
240
  text=True,
251
241
  )
252
-
253
-
254
- def _print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
255
- """Print error message as JSON."""
256
- Console().print_json(
257
- json.dumps(
258
- {
259
- "success": False,
260
- "error-message": remove_emojis(str(msg) + "\n" + str(e)),
261
- }
262
- )
263
- )
flwr/cli/stop.py ADDED
@@ -0,0 +1,129 @@
1
+ # Copyright 2024 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 command line interface `stop` command."""
16
+
17
+
18
+ import io
19
+ import json
20
+ from pathlib import Path
21
+ from typing import Annotated, Optional
22
+
23
+ import typer
24
+ from rich.console import Console
25
+
26
+ from flwr.cli.config_utils import (
27
+ exit_if_no_address,
28
+ load_and_validate,
29
+ process_loaded_project_config,
30
+ validate_federation_in_project_config,
31
+ )
32
+ from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
33
+ from flwr.common.logger import print_json_error, redirect_output, restore_output
34
+ from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
35
+ from flwr.proto.exec_pb2_grpc import ExecStub
36
+
37
+ from .utils import init_channel, try_obtain_cli_auth_plugin
38
+
39
+
40
+ def stop( # pylint: disable=R0914
41
+ run_id: Annotated[ # pylint: disable=unused-argument
42
+ int,
43
+ typer.Argument(help="The Flower run ID to stop"),
44
+ ],
45
+ app: Annotated[
46
+ Path,
47
+ typer.Argument(help="Path of the Flower project"),
48
+ ] = Path("."),
49
+ federation: Annotated[
50
+ Optional[str],
51
+ typer.Argument(help="Name of the federation"),
52
+ ] = None,
53
+ output_format: Annotated[
54
+ str,
55
+ typer.Option(
56
+ "--format",
57
+ case_sensitive=False,
58
+ help="Format output using 'default' view or 'json'",
59
+ ),
60
+ ] = CliOutputFormat.DEFAULT,
61
+ ) -> None:
62
+ """Stop a run."""
63
+ suppress_output = output_format == CliOutputFormat.JSON
64
+ captured_output = io.StringIO()
65
+ try:
66
+ if suppress_output:
67
+ redirect_output(captured_output)
68
+
69
+ # Load and validate federation config
70
+ typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
71
+
72
+ pyproject_path = app / FAB_CONFIG_FILE if app else None
73
+ config, errors, warnings = load_and_validate(path=pyproject_path)
74
+ config = process_loaded_project_config(config, errors, warnings)
75
+ federation, federation_config = validate_federation_in_project_config(
76
+ federation, config
77
+ )
78
+ exit_if_no_address(federation_config, "stop")
79
+
80
+ try:
81
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation)
82
+ channel = init_channel(app, federation_config, auth_plugin)
83
+ stub = ExecStub(channel) # pylint: disable=unused-variable # noqa: F841
84
+
85
+ typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
86
+ _stop_run(stub=stub, run_id=run_id, output_format=output_format)
87
+
88
+ except ValueError as err:
89
+ typer.secho(
90
+ f"❌ {err}",
91
+ fg=typer.colors.RED,
92
+ bold=True,
93
+ )
94
+ raise typer.Exit(code=1) from err
95
+ finally:
96
+ channel.close()
97
+ except (typer.Exit, Exception) as err: # pylint: disable=broad-except
98
+ if suppress_output:
99
+ restore_output()
100
+ e_message = captured_output.getvalue()
101
+ print_json_error(e_message, err)
102
+ else:
103
+ typer.secho(
104
+ f"{err}",
105
+ fg=typer.colors.RED,
106
+ bold=True,
107
+ )
108
+ finally:
109
+ if suppress_output:
110
+ restore_output()
111
+ captured_output.close()
112
+
113
+
114
+ def _stop_run(stub: ExecStub, run_id: int, output_format: str) -> None:
115
+ """Stop a run."""
116
+ response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
117
+ if response.success:
118
+ typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
119
+ if output_format == CliOutputFormat.JSON:
120
+ run_output = json.dumps(
121
+ {
122
+ "success": True,
123
+ "run-id": run_id,
124
+ }
125
+ )
126
+ restore_output()
127
+ Console().print_json(run_output)
128
+ else:
129
+ typer.secho(f"❌ Run {run_id} couldn't be stopped.", fg=typer.colors.RED)
flwr/cli/utils.py CHANGED
@@ -14,13 +14,33 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface utils."""
16
16
 
17
+
17
18
  import hashlib
19
+ import json
18
20
  import re
21
+ from logging import DEBUG
19
22
  from pathlib import Path
20
- from typing import Callable, Optional, cast
23
+ from typing import Any, Callable, Optional, cast
21
24
 
25
+ import grpc
22
26
  import typer
23
27
 
28
+ from flwr.cli.cli_user_auth_interceptor import CliUserAuthInterceptor
29
+ from flwr.common.auth_plugin import CliAuthPlugin
30
+ from flwr.common.constant import AUTH_TYPE, CREDENTIALS_DIR, FLWR_DIR
31
+ from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
32
+ from flwr.common.logger import log
33
+
34
+ from .config_utils import validate_certificate_in_federation_config
35
+
36
+ try:
37
+ from flwr.ee import get_cli_auth_plugins
38
+ except ImportError:
39
+
40
+ def get_cli_auth_plugins() -> dict[str, type[CliAuthPlugin]]:
41
+ """Return all CLI authentication plugins."""
42
+ raise NotImplementedError("No authentication plugins are currently supported.")
43
+
24
44
 
25
45
  def prompt_text(
26
46
  text: str,
@@ -136,3 +156,78 @@ def get_sha256_hash(file_path: Path) -> str:
136
156
  break
137
157
  sha256.update(data)
138
158
  return sha256.hexdigest()
159
+
160
+
161
+ def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
162
+ """Return the path to the user auth config file."""
163
+ # Locate the credentials directory
164
+ credentials_dir = root_dir.absolute() / FLWR_DIR / CREDENTIALS_DIR
165
+ credentials_dir.mkdir(parents=True, exist_ok=True)
166
+ return credentials_dir / f"{federation}.json"
167
+
168
+
169
+ def try_obtain_cli_auth_plugin(
170
+ root_dir: Path,
171
+ federation: str,
172
+ auth_type: Optional[str] = None,
173
+ ) -> Optional[CliAuthPlugin]:
174
+ """Load the CLI-side user auth plugin for the given auth type."""
175
+ config_path = get_user_auth_config_path(root_dir, federation)
176
+
177
+ # Load the config file if it exists
178
+ config: dict[str, Any] = {}
179
+ if config_path.exists():
180
+ with config_path.open("r", encoding="utf-8") as file:
181
+ config = json.load(file)
182
+ # This is the case when the user auth is not enabled
183
+ elif auth_type is None:
184
+ return None
185
+
186
+ # Get the auth type from the config if not provided
187
+ if auth_type is None:
188
+ if AUTH_TYPE not in config:
189
+ return None
190
+ auth_type = config[AUTH_TYPE]
191
+
192
+ # Retrieve auth plugin class and instantiate it
193
+ try:
194
+ all_plugins: dict[str, type[CliAuthPlugin]] = get_cli_auth_plugins()
195
+ auth_plugin_class = all_plugins[auth_type]
196
+ return auth_plugin_class(config_path)
197
+ except KeyError:
198
+ typer.echo(f"❌ Unknown user authentication type: {auth_type}")
199
+ raise typer.Exit(code=1) from None
200
+ except ImportError:
201
+ typer.echo("❌ No authentication plugins are currently supported.")
202
+ raise typer.Exit(code=1) from None
203
+
204
+
205
+ def init_channel(
206
+ app: Path, federation_config: dict[str, Any], auth_plugin: Optional[CliAuthPlugin]
207
+ ) -> grpc.Channel:
208
+ """Initialize gRPC channel to the Exec API."""
209
+
210
+ def on_channel_state_change(channel_connectivity: str) -> None:
211
+ """Log channel connectivity."""
212
+ log(DEBUG, channel_connectivity)
213
+
214
+ insecure, root_certificates_bytes = validate_certificate_in_federation_config(
215
+ app, federation_config
216
+ )
217
+
218
+ # Initialize the CLI-side user auth interceptor
219
+ interceptors: list[grpc.UnaryUnaryClientInterceptor] = []
220
+ if auth_plugin is not None:
221
+ auth_plugin.load_tokens()
222
+ interceptors = CliUserAuthInterceptor(auth_plugin)
223
+
224
+ # Create the gRPC channel
225
+ channel = create_channel(
226
+ server_address=federation_config["address"],
227
+ insecure=insecure,
228
+ root_certificates=root_certificates_bytes,
229
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
230
+ interceptors=interceptors or None,
231
+ )
232
+ channel.subscribe(on_channel_state_change)
233
+ return channel
flwr/client/app.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower client app."""
16
16
 
17
+
17
18
  import signal
18
19
  import subprocess
19
20
  import sys
@@ -55,7 +56,7 @@ from flwr.common.constant import (
55
56
  from flwr.common.logger import log, warn_deprecated_feature
56
57
  from flwr.common.message import Error
57
58
  from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
58
- from flwr.common.typing import Fab, Run, UserConfig
59
+ from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
59
60
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
60
61
  from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
61
62
  from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
@@ -474,7 +475,7 @@ def start_client_internal(
474
475
 
475
476
  run: Run = runs[run_id]
476
477
  if get_fab is not None and run.fab_hash:
477
- fab = get_fab(run.fab_hash)
478
+ fab = get_fab(run.fab_hash, run_id)
478
479
  if not isolation:
479
480
  # If `ClientApp` runs in the same process, install the FAB
480
481
  install_from_fab(fab.content, flwr_path, True)
@@ -611,6 +612,16 @@ def start_client_internal(
611
612
  send(reply_message)
612
613
  log(INFO, "Sent reply")
613
614
 
615
+ except RunNotRunningException:
616
+ log(INFO, "")
617
+ log(
618
+ INFO,
619
+ "SuperNode aborted sending the reply message. "
620
+ "Run ID %s is not in `RUNNING` status.",
621
+ run_id,
622
+ )
623
+ log(INFO, "")
624
+
614
625
  except StopIteration:
615
626
  sleep_duration = 0
616
627
  break
@@ -752,7 +763,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
752
763
  Optional[Callable[[], Optional[int]]],
753
764
  Optional[Callable[[], None]],
754
765
  Optional[Callable[[int], Run]],
755
- Optional[Callable[[str], Fab]],
766
+ Optional[Callable[[str, int], Fab]],
756
767
  ]
757
768
  ],
758
769
  ],
flwr/client/client.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower client (abstract base class)."""
16
16
 
17
+
17
18
  # Needed to `Client` class can return a type of `Client` (not needed in py3.11+)
18
19
  from __future__ import annotations
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower ClientApp process."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  import time
@@ -31,6 +32,7 @@ from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ErrorCo
31
32
  from flwr.common.grpc import create_channel
32
33
  from flwr.common.logger import log
33
34
  from flwr.common.message import Error
35
+ from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
34
36
  from flwr.common.serde import (
35
37
  context_from_proto,
36
38
  context_to_proto,
@@ -105,9 +107,9 @@ def run_clientapp( # pylint: disable=R0914
105
107
 
106
108
  # Resolve directory where FABs are installed
107
109
  flwr_dir_ = get_flwr_dir(flwr_dir)
108
-
109
110
  try:
110
111
  stub = ClientAppIoStub(channel)
112
+ _wrap_stub(stub, _make_simple_grpc_retry_invoker())
111
113
 
112
114
  while True:
113
115
  # If token is not set, loop until token is received from SuperNode
@@ -138,6 +140,7 @@ def run_clientapp( # pylint: disable=R0914
138
140
 
139
141
  # Execute ClientApp
140
142
  reply_message = client_app(message=message, context=context)
143
+
141
144
  except Exception as ex: # pylint: disable=broad-exception-caught
142
145
  # Don't update/change NodeState
143
146
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower ClientApp loading utils."""
16
16
 
17
+
17
18
  from logging import DEBUG
18
19
  from pathlib import Path
19
20
  from typing import Callable, Optional
@@ -48,7 +48,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
48
48
  Optional[Callable[[], Optional[int]]],
49
49
  Optional[Callable[[], None]],
50
50
  Optional[Callable[[int], Run]],
51
- Optional[Callable[[str], Fab]],
51
+ Optional[Callable[[str, int], Fab]],
52
52
  ]
53
53
  ]:
54
54
  """Primitives for request/response-based interaction with a server via GrpcAdapter.
@@ -76,7 +76,7 @@ def grpc_connection( # pylint: disable=R0913,R0915,too-many-positional-argument
76
76
  Optional[Callable[[], Optional[int]]],
77
77
  Optional[Callable[[], None]],
78
78
  Optional[Callable[[int], Run]],
79
- Optional[Callable[[str], Fab]],
79
+ Optional[Callable[[str, int], Fab]],
80
80
  ]
81
81
  ]:
82
82
  """Establish a gRPC connection to a gRPC server.
@@ -42,7 +42,7 @@ from flwr.common.logger import log
42
42
  from flwr.common.message import Message, Metadata
43
43
  from flwr.common.retry_invoker import RetryInvoker
44
44
  from flwr.common.serde import message_from_taskins, message_to_taskres, run_from_proto
45
- from flwr.common.typing import Fab, Run
45
+ from flwr.common.typing import Fab, Run, RunNotRunningException
46
46
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
47
47
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
48
48
  CreateNodeRequest,
@@ -84,7 +84,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
84
84
  Optional[Callable[[], Optional[int]]],
85
85
  Optional[Callable[[], None]],
86
86
  Optional[Callable[[int], Run]],
87
- Optional[Callable[[str], Fab]],
87
+ Optional[Callable[[str, int], Fab]],
88
88
  ]
89
89
  ]:
90
90
  """Primitives for request/response-based interaction with a server.
@@ -155,10 +155,16 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
155
155
  ping_thread: Optional[threading.Thread] = None
156
156
  ping_stop_event = threading.Event()
157
157
 
158
+ def _should_giveup_fn(e: Exception) -> bool:
159
+ if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
160
+ raise RunNotRunningException
161
+ if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
162
+ return False
163
+ return True
164
+
158
165
  # Restrict retries to cases where the status code is UNAVAILABLE
159
- retry_invoker.should_giveup = (
160
- lambda e: e.code() != grpc.StatusCode.UNAVAILABLE # type: ignore
161
- )
166
+ # If the status code is PERMISSION_DENIED, additionally raise RunNotRunningException
167
+ retry_invoker.should_giveup = _should_giveup_fn
162
168
 
163
169
  ###########################################################################
164
170
  # ping/create_node/delete_node/receive/send/get_run functions
@@ -290,9 +296,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
290
296
  # Return fab_id and fab_version
291
297
  return run_from_proto(get_run_response.run)
292
298
 
293
- def get_fab(fab_hash: str) -> Fab:
299
+ def get_fab(fab_hash: str, run_id: int) -> Fab:
294
300
  # Call FleetAPI
295
- get_fab_request = GetFabRequest(node=node, hash_str=fab_hash)
301
+ get_fab_request = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
296
302
  get_fab_response: GetFabResponse = retry_invoker.invoke(
297
303
  stub.GetFab,
298
304
  request=get_fab_request,
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Client-side message handler."""
16
16
 
17
+
17
18
  from logging import WARN
18
19
  from typing import Optional, cast
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Mods that report statistics about message communication."""
16
16
 
17
+
17
18
  from logging import INFO
18
19
 
19
20
  import numpy as np
@@ -136,7 +136,7 @@ class LocalDpMod:
136
136
  fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
137
137
 
138
138
  # Add noise to model params
139
- add_localdp_gaussian_noise_to_params(
139
+ fit_res.parameters = add_localdp_gaussian_noise_to_params(
140
140
  fit_res.parameters, self.sensitivity, self.epsilon, self.delta
141
141
  )
142
142
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower NodeState."""
16
16
 
17
+
17
18
  from .in_memory_nodestate import InMemoryNodeState as InMemoryNodeState
18
19
  from .nodestate import NodeState as NodeState
19
20
  from .nodestate_factory import NodeStateFactory as NodeStateFactory
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Abstract base class NodeState."""
16
16
 
17
+
17
18
  import abc
18
19
  from typing import Optional
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Factory class that creates NodeState instances."""
16
16
 
17
+
17
18
  import threading
18
19
  from typing import Optional
19
20
 
@@ -96,7 +96,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
96
96
  Optional[Callable[[], Optional[int]]],
97
97
  Optional[Callable[[], None]],
98
98
  Optional[Callable[[int], Run]],
99
- Optional[Callable[[str], Fab]],
99
+ Optional[Callable[[str, int], Fab]],
100
100
  ]
101
101
  ]:
102
102
  """Primitives for request/response-based interaction with a server.
@@ -361,9 +361,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
361
361
 
362
362
  return run_from_proto(res.run)
363
363
 
364
- def get_fab(fab_hash: str) -> Fab:
364
+ def get_fab(fab_hash: str, run_id: int) -> Fab:
365
365
  # Construct the request
366
- req = GetFabRequest(node=node, hash_str=fab_hash)
366
+ req = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
367
367
 
368
368
  # Send the request
369
369
  res = _request(req, GetFabResponse, PATH_GET_FAB)
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower SuperNode."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  from logging import DEBUG, ERROR, INFO, WARN
flwr/common/address.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower IP address utils."""
16
16
 
17
+
17
18
  import socket
18
19
  from ipaddress import ip_address
19
20
  from typing import Optional
flwr/common/args.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Common Flower arguments."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  from logging import DEBUG, ERROR, WARN
@@ -0,0 +1,24 @@
1
+ # Copyright 2024 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
+ """Auth plugin components."""
16
+
17
+
18
+ from .auth_plugin import CliAuthPlugin as CliAuthPlugin
19
+ from .auth_plugin import ExecAuthPlugin as ExecAuthPlugin
20
+
21
+ __all__ = [
22
+ "CliAuthPlugin",
23
+ "ExecAuthPlugin",
24
+ ]