flwr-nightly 1.13.0.dev20241111__py3-none-any.whl → 1.14.0.dev20241126__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 (53) hide show
  1. flwr/cli/app.py +2 -0
  2. flwr/cli/install.py +0 -16
  3. flwr/cli/ls.py +228 -0
  4. flwr/cli/new/new.py +23 -13
  5. flwr/cli/new/templates/app/README.md.tpl +11 -0
  6. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  7. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  8. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  9. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  12. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  13. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  14. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  15. flwr/cli/run/run.py +4 -2
  16. flwr/client/app.py +50 -14
  17. flwr/client/clientapp/app.py +40 -23
  18. flwr/client/grpc_rere_client/connection.py +7 -12
  19. flwr/client/rest_client/connection.py +4 -14
  20. flwr/client/supernode/app.py +31 -53
  21. flwr/common/args.py +85 -16
  22. flwr/common/constant.py +24 -6
  23. flwr/common/date.py +18 -0
  24. flwr/common/grpc.py +4 -1
  25. flwr/common/serde.py +10 -0
  26. flwr/common/typing.py +31 -10
  27. flwr/proto/exec_pb2.py +22 -13
  28. flwr/proto/exec_pb2.pyi +44 -0
  29. flwr/proto/exec_pb2_grpc.py +34 -0
  30. flwr/proto/exec_pb2_grpc.pyi +13 -0
  31. flwr/proto/run_pb2.py +30 -30
  32. flwr/proto/run_pb2.pyi +18 -1
  33. flwr/server/app.py +47 -77
  34. flwr/server/driver/grpc_driver.py +66 -16
  35. flwr/server/run_serverapp.py +8 -238
  36. flwr/server/serverapp/app.py +49 -29
  37. flwr/server/superlink/fleet/rest_rere/rest_api.py +10 -9
  38. flwr/server/superlink/linkstate/in_memory_linkstate.py +71 -46
  39. flwr/server/superlink/linkstate/linkstate.py +19 -5
  40. flwr/server/superlink/linkstate/sqlite_linkstate.py +81 -113
  41. flwr/server/superlink/linkstate/utils.py +193 -3
  42. flwr/simulation/app.py +52 -91
  43. flwr/simulation/legacy_app.py +21 -1
  44. flwr/simulation/run_simulation.py +7 -18
  45. flwr/simulation/simulationio_connection.py +2 -2
  46. flwr/superexec/deployment.py +12 -6
  47. flwr/superexec/exec_servicer.py +31 -2
  48. flwr/superexec/simulation.py +11 -46
  49. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/METADATA +5 -4
  50. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/RECORD +53 -52
  51. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/LICENSE +0 -0
  52. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/WHEEL +0 -0
  53. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.14.0.dev20241126.dist-info}/entry_points.txt +0 -0
flwr/cli/app.py CHANGED
@@ -20,6 +20,7 @@ from typer.main import get_command
20
20
  from .build import build
21
21
  from .install import install
22
22
  from .log import log
23
+ from .ls import ls
23
24
  from .new import new
24
25
  from .run import run
25
26
 
@@ -37,6 +38,7 @@ app.command()(run)
37
38
  app.command()(build)
38
39
  app.command()(install)
39
40
  app.command()(log)
41
+ app.command()(ls)
40
42
 
41
43
  typer_click_object = get_command(app)
42
44
 
flwr/cli/install.py CHANGED
@@ -16,7 +16,6 @@
16
16
 
17
17
  import hashlib
18
18
  import shutil
19
- import subprocess
20
19
  import tempfile
21
20
  import zipfile
22
21
  from io import BytesIO
@@ -188,21 +187,6 @@ def validate_and_install(
188
187
  else:
189
188
  shutil.copy2(item, install_dir / item.name)
190
189
 
191
- try:
192
- subprocess.run(
193
- ["pip", "install", "-e", install_dir, "--no-deps"],
194
- capture_output=True,
195
- text=True,
196
- check=True,
197
- )
198
- except subprocess.CalledProcessError as e:
199
- typer.secho(
200
- f"❌ Failed to `pip install` package(s) from {install_dir}:\n{e.stderr}",
201
- fg=typer.colors.RED,
202
- bold=True,
203
- )
204
- raise typer.Exit(code=1) from e
205
-
206
190
  typer.secho(
207
191
  f"🎊 Successfully installed {project_name} to {install_dir}.",
208
192
  fg=typer.colors.GREEN,
flwr/cli/ls.py ADDED
@@ -0,0 +1,228 @@
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 `ls` command."""
16
+
17
+
18
+ from datetime import datetime, timedelta
19
+ from logging import DEBUG
20
+ from pathlib import Path
21
+ from typing import Annotated, Any, Optional
22
+
23
+ import grpc
24
+ import typer
25
+ from rich.console import Console
26
+ from rich.table import Table
27
+ from rich.text import Text
28
+
29
+ from flwr.cli.config_utils import (
30
+ load_and_validate,
31
+ validate_certificate_in_federation_config,
32
+ validate_federation_in_project_config,
33
+ validate_project_config,
34
+ )
35
+ from flwr.common.constant import FAB_CONFIG_FILE, SubStatus
36
+ from flwr.common.date import format_timedelta, isoformat8601_utc
37
+ from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
38
+ from flwr.common.logger import log
39
+ from flwr.common.serde import run_from_proto
40
+ from flwr.common.typing import Run
41
+ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
42
+ ListRunsRequest,
43
+ ListRunsResponse,
44
+ )
45
+ from flwr.proto.exec_pb2_grpc import ExecStub
46
+
47
+
48
+ def ls(
49
+ app: Annotated[
50
+ Path,
51
+ typer.Argument(help="Path of the Flower project"),
52
+ ] = Path("."),
53
+ federation: Annotated[
54
+ Optional[str],
55
+ typer.Argument(help="Name of the federation"),
56
+ ] = None,
57
+ runs: Annotated[
58
+ bool,
59
+ typer.Option(
60
+ "--runs",
61
+ help="List all runs",
62
+ ),
63
+ ] = False,
64
+ run_id: Annotated[
65
+ Optional[int],
66
+ typer.Option(
67
+ "--run-id",
68
+ help="Specific run ID to display",
69
+ ),
70
+ ] = None,
71
+ ) -> None:
72
+ """List runs."""
73
+ # Load and validate federation config
74
+ typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
75
+
76
+ pyproject_path = app / FAB_CONFIG_FILE if app else None
77
+ config, errors, warnings = load_and_validate(path=pyproject_path)
78
+ config = validate_project_config(config, errors, warnings)
79
+ federation, federation_config = validate_federation_in_project_config(
80
+ federation, config
81
+ )
82
+
83
+ if "address" not in federation_config:
84
+ typer.secho(
85
+ "❌ `flwr ls` currently works with Exec API. Ensure that the correct"
86
+ "Exec API address is provided in the `pyproject.toml`.",
87
+ fg=typer.colors.RED,
88
+ bold=True,
89
+ )
90
+ raise typer.Exit(code=1)
91
+
92
+ try:
93
+ if runs and run_id is not None:
94
+ raise ValueError(
95
+ "The options '--runs' and '--run-id' are mutually exclusive."
96
+ )
97
+
98
+ channel = _init_channel(app, federation_config)
99
+ stub = ExecStub(channel)
100
+
101
+ # Display information about a specific run ID
102
+ if run_id is not None:
103
+ typer.echo(f"🔍 Displaying information for run ID {run_id}...")
104
+ _display_one_run(stub, run_id)
105
+ # By default, list all runs
106
+ else:
107
+ typer.echo("📄 Listing all runs...")
108
+ _list_runs(stub)
109
+
110
+ except ValueError as err:
111
+ typer.secho(
112
+ f"❌ {err}",
113
+ fg=typer.colors.RED,
114
+ bold=True,
115
+ )
116
+ raise typer.Exit(code=1) from err
117
+ finally:
118
+ channel.close()
119
+
120
+
121
+ def on_channel_state_change(channel_connectivity: str) -> None:
122
+ """Log channel connectivity."""
123
+ log(DEBUG, channel_connectivity)
124
+
125
+
126
+ def _init_channel(app: Path, federation_config: dict[str, Any]) -> grpc.Channel:
127
+ """Initialize gRPC channel to the Exec API."""
128
+ insecure, root_certificates_bytes = validate_certificate_in_federation_config(
129
+ app, federation_config
130
+ )
131
+ channel = create_channel(
132
+ server_address=federation_config["address"],
133
+ insecure=insecure,
134
+ root_certificates=root_certificates_bytes,
135
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
136
+ interceptors=None,
137
+ )
138
+ channel.subscribe(on_channel_state_change)
139
+ return channel
140
+
141
+
142
+ def _format_run_table(run_dict: dict[int, Run], now_isoformat: str) -> Table:
143
+ """Format run status as a rich Table."""
144
+ table = Table(header_style="bold cyan", show_lines=True)
145
+
146
+ def _format_datetime(dt: Optional[datetime]) -> str:
147
+ return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
148
+
149
+ # Add columns
150
+ table.add_column(
151
+ Text("Run ID", justify="center"), style="bright_white", overflow="fold"
152
+ )
153
+ table.add_column(Text("FAB", justify="center"), style="dim white")
154
+ table.add_column(Text("Status", justify="center"))
155
+ table.add_column(Text("Elapsed", justify="center"), style="blue")
156
+ table.add_column(Text("Created At", justify="center"), style="dim white")
157
+ table.add_column(Text("Running At", justify="center"), style="dim white")
158
+ table.add_column(Text("Finished At", justify="center"), style="dim white")
159
+
160
+ # Add rows
161
+ for run in sorted(
162
+ run_dict.values(), key=lambda x: datetime.fromisoformat(x.pending_at)
163
+ ):
164
+ # Combine status and sub-status into a single string
165
+ if run.status.sub_status == "":
166
+ status_text = run.status.status
167
+ else:
168
+ status_text = f"{run.status.status}:{run.status.sub_status}"
169
+
170
+ # Style the status based on its value
171
+ sub_status = run.status.sub_status
172
+ if sub_status == SubStatus.COMPLETED:
173
+ status_style = "green"
174
+ elif sub_status == SubStatus.FAILED:
175
+ status_style = "red"
176
+ else:
177
+ status_style = "yellow"
178
+
179
+ # Convert isoformat to datetime
180
+ pending_at = datetime.fromisoformat(run.pending_at) if run.pending_at else None
181
+ running_at = datetime.fromisoformat(run.running_at) if run.running_at else None
182
+ finished_at = (
183
+ datetime.fromisoformat(run.finished_at) if run.finished_at else None
184
+ )
185
+
186
+ # Calculate elapsed time
187
+ elapsed_time = timedelta()
188
+ if running_at:
189
+ if finished_at:
190
+ end_time = finished_at
191
+ else:
192
+ end_time = datetime.fromisoformat(now_isoformat)
193
+ elapsed_time = end_time - running_at
194
+
195
+ table.add_row(
196
+ f"[bold]{run.run_id}[/bold]",
197
+ f"{run.fab_id} (v{run.fab_version})",
198
+ f"[{status_style}]{status_text}[/{status_style}]",
199
+ format_timedelta(elapsed_time),
200
+ _format_datetime(pending_at),
201
+ _format_datetime(running_at),
202
+ _format_datetime(finished_at),
203
+ )
204
+ return table
205
+
206
+
207
+ def _list_runs(
208
+ stub: ExecStub,
209
+ ) -> None:
210
+ """List all runs."""
211
+ res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
212
+ run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
213
+
214
+ Console().print(_format_run_table(run_dict, res.now))
215
+
216
+
217
+ def _display_one_run(
218
+ stub: ExecStub,
219
+ run_id: int,
220
+ ) -> None:
221
+ """Display information about a specific run."""
222
+ res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
223
+ if not res.run_dict:
224
+ raise ValueError(f"Run ID {run_id} not found")
225
+
226
+ run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
227
+
228
+ Console().print(_format_run_table(run_dict, res.now))
flwr/cli/new/new.py CHANGED
@@ -268,20 +268,30 @@ def new(
268
268
  context=context,
269
269
  )
270
270
 
271
- print(
272
- typer.style(
273
- "🎊 Flower App creation successful.\n\n"
274
- "Use the following command to run your Flower App:\n",
275
- fg=typer.colors.GREEN,
276
- bold=True,
277
- )
271
+ prompt = typer.style(
272
+ "🎊 Flower App creation successful.\n\n"
273
+ "To run your Flower App, use the following command:\n\n",
274
+ fg=typer.colors.GREEN,
275
+ bold=True,
278
276
  )
279
277
 
280
278
  _add = " huggingface-cli login\n" if llm_challenge_str else ""
281
- print(
282
- typer.style(
283
- f" cd {package_name}\n" + " pip install -e .\n" + _add + " flwr run\n",
284
- fg=typer.colors.BRIGHT_CYAN,
285
- bold=True,
286
- )
279
+ prompt += typer.style(
280
+ _add + f" flwr run {package_name}\n\n",
281
+ fg=typer.colors.BRIGHT_CYAN,
282
+ bold=True,
283
+ )
284
+
285
+ prompt += typer.style(
286
+ "If you haven't installed all dependencies yet, follow these steps:\n\n",
287
+ fg=typer.colors.GREEN,
288
+ bold=True,
287
289
  )
290
+
291
+ prompt += typer.style(
292
+ f" cd {package_name}\n" + " pip install -e .\n" + _add + " flwr run .\n",
293
+ fg=typer.colors.BRIGHT_CYAN,
294
+ bold=True,
295
+ )
296
+
297
+ print(prompt)
@@ -14,7 +14,18 @@ In the `$project_name` directory, use `flwr run` to run a local simulation:
14
14
  flwr run .
15
15
  ```
16
16
 
17
+ Refer to the [How to Run Simulations](https://flower.ai/docs/framework/how-to-run-simulations.html) guide in the documentation for advice on how to optimize your simulations.
18
+
17
19
  ## Run with the Deployment Engine
18
20
 
19
21
  > \[!NOTE\]
20
22
  > An update to this example will show how to run this Flower application with the Deployment Engine and TLS certificates, or with Docker.
23
+
24
+ ## Resources
25
+
26
+ - Flower website: [flower.ai](https://flower.ai/)
27
+ - Check the documentation: [flower.ai/docs](https://flower.ai/docs/)
28
+ - Give Flower a ⭐️ on GitHub: [GitHub](https://github.com/adap/flower)
29
+ - Join the Flower community!
30
+ - [Flower Slack](https://flower.ai/join-slack/)
31
+ - [Flower Discuss](https://discuss.flower.ai/)
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "torch==2.2.1",
14
14
  "torchvision==0.17.1",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets>=0.3.0",
13
13
  "torch==2.3.1",
14
14
  "trl==0.8.1",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets>=0.3.0",
13
13
  "torch==2.2.1",
14
14
  "transformers>=4.30.0,<5.0",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "jax==0.4.30",
13
13
  "jaxlib==0.4.30",
14
14
  "scikit-learn==1.3.2",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "mlx==0.16.1",
14
14
  "numpy==1.24.4",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "numpy>=1.21.0",
13
13
  ]
14
14
 
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "torch==2.2.1",
14
14
  "torchvision==0.17.1",
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "scikit-learn>=1.1.1",
14
14
  ]
@@ -8,7 +8,7 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.12.0",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "tensorflow>=2.11.1,<2.18.0",
14
14
  ]
flwr/cli/run/run.py CHANGED
@@ -125,6 +125,10 @@ def _run_with_exec_api(
125
125
 
126
126
  fab_path, fab_hash = build(app)
127
127
  content = Path(fab_path).read_bytes()
128
+
129
+ # Delete FAB file once the bytes is computed
130
+ Path(fab_path).unlink()
131
+
128
132
  fab = Fab(fab_hash, content)
129
133
 
130
134
  # Construct a `ConfigsRecord` out of a flattened `UserConfig`
@@ -138,8 +142,6 @@ def _run_with_exec_api(
138
142
  )
139
143
  res = stub.StartRun(req)
140
144
 
141
- # Delete FAB file once it has been sent to the Exec API
142
- Path(fab_path).unlink()
143
145
  typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
144
146
 
145
147
  if stream:
flwr/client/app.py CHANGED
@@ -37,11 +37,14 @@ from flwr.client.typing import ClientFnExt
37
37
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
38
38
  from flwr.common.address import parse_address
39
39
  from flwr.common.constant import (
40
- CLIENTAPPIO_API_DEFAULT_ADDRESS,
40
+ CLIENT_OCTET,
41
+ CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
41
42
  ISOLATION_MODE_PROCESS,
42
43
  ISOLATION_MODE_SUBPROCESS,
44
+ MAX_RETRY_DELAY,
43
45
  MISSING_EXTRA_REST,
44
46
  RUN_ID_NUM_BYTES,
47
+ SERVER_OCTET,
45
48
  TRANSPORT_TYPE_GRPC_ADAPTER,
46
49
  TRANSPORT_TYPE_GRPC_BIDI,
47
50
  TRANSPORT_TYPE_GRPC_RERE,
@@ -102,6 +105,11 @@ def start_client(
102
105
  ) -> None:
103
106
  """Start a Flower client node which connects to a Flower server.
104
107
 
108
+ Warning
109
+ -------
110
+ This function is deprecated since 1.13.0. Use :code:`flower-supernode` command
111
+ instead to start a SuperNode.
112
+
105
113
  Parameters
106
114
  ----------
107
115
  server_address : str
@@ -176,6 +184,17 @@ def start_client(
176
184
  >>> root_certificates=Path("/crts/root.pem").read_bytes(),
177
185
  >>> )
178
186
  """
187
+ msg = (
188
+ "flwr.client.start_client() is deprecated."
189
+ "\n\tInstead, use the `flower-supernode` CLI command to start a SuperNode "
190
+ "as shown below:"
191
+ "\n\n\t\t$ flower-supernode --insecure --superlink='<IP>:<PORT>'"
192
+ "\n\n\tTo view all available options, run:"
193
+ "\n\n\t\t$ flower-supernode --help"
194
+ "\n\n\tUsing `start_client()` is deprecated."
195
+ )
196
+ warn_deprecated_feature(name=msg)
197
+
179
198
  event(EventType.START_CLIENT_ENTER)
180
199
  start_client_internal(
181
200
  server_address=server_address,
@@ -216,7 +235,7 @@ def start_client_internal(
216
235
  max_wait_time: Optional[float] = None,
217
236
  flwr_path: Optional[Path] = None,
218
237
  isolation: Optional[str] = None,
219
- supernode_address: Optional[str] = CLIENTAPPIO_API_DEFAULT_ADDRESS,
238
+ clientappio_api_address: Optional[str] = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
220
239
  ) -> None:
221
240
  """Start a Flower client node which connects to a Flower server.
222
241
 
@@ -274,9 +293,11 @@ def start_client_internal(
274
293
  `process`. Defaults to `None`, which runs the `ClientApp` in the same process
275
294
  as the SuperNode. If `subprocess`, the `ClientApp` runs in a subprocess started
276
295
  by the SueprNode and communicates using gRPC at the address
277
- `supernode_address`. If `process`, the `ClientApp` runs in a separate isolated
278
- process and communicates using gRPC at the address `supernode_address`.
279
- supernode_address : Optional[str] (default: `CLIENTAPPIO_API_DEFAULT_ADDRESS`)
296
+ `clientappio_api_address`. If `process`, the `ClientApp` runs in a separate
297
+ isolated process and communicates using gRPC at the address
298
+ `clientappio_api_address`.
299
+ clientappio_api_address : Optional[str]
300
+ (default: `CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS`)
280
301
  The SuperNode gRPC server address.
281
302
  """
282
303
  if insecure is None:
@@ -304,15 +325,16 @@ def start_client_internal(
304
325
  load_client_app_fn = _load_client_app
305
326
 
306
327
  if isolation:
307
- if supernode_address is None:
328
+ if clientappio_api_address is None:
308
329
  raise ValueError(
309
- f"`supernode_address` required when `isolation` is "
330
+ f"`clientappio_api_address` required when `isolation` is "
310
331
  f"{ISOLATION_MODE_SUBPROCESS} or {ISOLATION_MODE_PROCESS}",
311
332
  )
312
333
  _clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
313
- address=supernode_address
334
+ address=clientappio_api_address,
335
+ certificates=None,
314
336
  )
315
- supernode_address = cast(str, supernode_address)
337
+ clientappio_api_address = cast(str, clientappio_api_address)
316
338
 
317
339
  # At this point, only `load_client_app_fn` should be used
318
340
  # Both `client` and `client_fn` must not be used directly
@@ -346,7 +368,7 @@ def start_client_internal(
346
368
  )
347
369
 
348
370
  retry_invoker = RetryInvoker(
349
- wait_gen_factory=exponential,
371
+ wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
350
372
  recoverable_exceptions=connection_error_type,
351
373
  max_tries=max_retries + 1 if max_retries is not None else None,
352
374
  max_time=max_wait_time,
@@ -448,7 +470,7 @@ def start_client_internal(
448
470
  runs[run_id] = get_run(run_id)
449
471
  # If get_run is None, i.e., in grpc-bidi mode
450
472
  else:
451
- runs[run_id] = Run(run_id, "", "", "", {})
473
+ runs[run_id] = Run.create_empty(run_id=run_id)
452
474
 
453
475
  run: Run = runs[run_id]
454
476
  if get_fab is not None and run.fab_hash:
@@ -508,14 +530,24 @@ def start_client_internal(
508
530
  )
509
531
 
510
532
  if start_subprocess:
533
+ _octet, _colon, _port = (
534
+ clientappio_api_address.rpartition(":")
535
+ )
536
+ io_address = (
537
+ f"{CLIENT_OCTET}:{_port}"
538
+ if _octet == SERVER_OCTET
539
+ else clientappio_api_address
540
+ )
511
541
  # Start ClientApp subprocess
512
542
  command = [
513
543
  "flwr-clientapp",
514
- "--supernode",
515
- supernode_address,
544
+ "--clientappio-api-address",
545
+ io_address,
516
546
  "--token",
517
547
  str(token),
518
548
  ]
549
+ command.append("--insecure")
550
+
519
551
  subprocess.run(
520
552
  command,
521
553
  stdout=None,
@@ -783,7 +815,10 @@ class _AppStateTracker:
783
815
  signal.signal(signal.SIGTERM, signal_handler)
784
816
 
785
817
 
786
- def run_clientappio_api_grpc(address: str) -> tuple[grpc.Server, ClientAppIoServicer]:
818
+ def run_clientappio_api_grpc(
819
+ address: str,
820
+ certificates: Optional[tuple[bytes, bytes, bytes]],
821
+ ) -> tuple[grpc.Server, ClientAppIoServicer]:
787
822
  """Run ClientAppIo API gRPC server."""
788
823
  clientappio_servicer: grpc.Server = ClientAppIoServicer()
789
824
  clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
@@ -794,6 +829,7 @@ def run_clientappio_api_grpc(address: str) -> tuple[grpc.Server, ClientAppIoServ
794
829
  ),
795
830
  server_address=address,
796
831
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
832
+ certificates=certificates,
797
833
  )
798
834
  log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
799
835
  clientappio_grpc_server.start()