flwr 1.12.0__py3-none-any.whl → 1.13.1__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 (110) hide show
  1. flwr/cli/app.py +2 -0
  2. flwr/cli/build.py +2 -2
  3. flwr/cli/config_utils.py +97 -0
  4. flwr/cli/install.py +0 -16
  5. flwr/cli/log.py +63 -97
  6. flwr/cli/ls.py +228 -0
  7. flwr/cli/new/new.py +23 -13
  8. flwr/cli/new/templates/app/README.md.tpl +11 -0
  9. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -1
  12. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  13. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  14. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  15. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  16. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  17. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  18. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
  19. flwr/cli/run/run.py +37 -89
  20. flwr/client/app.py +73 -34
  21. flwr/client/clientapp/app.py +58 -37
  22. flwr/client/grpc_rere_client/connection.py +7 -12
  23. flwr/client/nodestate/__init__.py +25 -0
  24. flwr/client/nodestate/in_memory_nodestate.py +38 -0
  25. flwr/client/nodestate/nodestate.py +30 -0
  26. flwr/client/nodestate/nodestate_factory.py +37 -0
  27. flwr/client/rest_client/connection.py +4 -14
  28. flwr/client/{node_state.py → run_info_store.py} +4 -3
  29. flwr/client/supernode/app.py +34 -58
  30. flwr/common/args.py +152 -0
  31. flwr/common/config.py +10 -0
  32. flwr/common/constant.py +59 -7
  33. flwr/common/context.py +9 -4
  34. flwr/common/date.py +21 -3
  35. flwr/common/grpc.py +4 -1
  36. flwr/common/logger.py +108 -1
  37. flwr/common/object_ref.py +47 -16
  38. flwr/common/serde.py +34 -0
  39. flwr/common/telemetry.py +0 -6
  40. flwr/common/typing.py +32 -2
  41. flwr/proto/exec_pb2.py +23 -17
  42. flwr/proto/exec_pb2.pyi +58 -22
  43. flwr/proto/exec_pb2_grpc.py +34 -0
  44. flwr/proto/exec_pb2_grpc.pyi +13 -0
  45. flwr/proto/log_pb2.py +29 -0
  46. flwr/proto/log_pb2.pyi +39 -0
  47. flwr/proto/log_pb2_grpc.py +4 -0
  48. flwr/proto/log_pb2_grpc.pyi +4 -0
  49. flwr/proto/message_pb2.py +8 -8
  50. flwr/proto/message_pb2.pyi +4 -1
  51. flwr/proto/run_pb2.py +32 -27
  52. flwr/proto/run_pb2.pyi +44 -1
  53. flwr/proto/serverappio_pb2.py +52 -0
  54. flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +54 -0
  55. flwr/proto/serverappio_pb2_grpc.py +376 -0
  56. flwr/proto/serverappio_pb2_grpc.pyi +147 -0
  57. flwr/proto/simulationio_pb2.py +38 -0
  58. flwr/proto/simulationio_pb2.pyi +65 -0
  59. flwr/proto/simulationio_pb2_grpc.py +205 -0
  60. flwr/proto/simulationio_pb2_grpc.pyi +81 -0
  61. flwr/server/app.py +297 -162
  62. flwr/server/driver/driver.py +15 -1
  63. flwr/server/driver/grpc_driver.py +89 -50
  64. flwr/server/driver/inmemory_driver.py +6 -16
  65. flwr/server/run_serverapp.py +11 -235
  66. flwr/server/{superlink/state → serverapp}/__init__.py +3 -9
  67. flwr/server/serverapp/app.py +234 -0
  68. flwr/server/strategy/aggregate.py +4 -4
  69. flwr/server/strategy/fedadam.py +11 -1
  70. flwr/server/superlink/driver/__init__.py +1 -1
  71. flwr/server/superlink/driver/{driver_grpc.py → serverappio_grpc.py} +19 -16
  72. flwr/server/superlink/driver/{driver_servicer.py → serverappio_servicer.py} +125 -39
  73. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +4 -2
  74. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -2
  75. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -2
  76. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -2
  77. flwr/server/superlink/fleet/message_handler/message_handler.py +7 -7
  78. flwr/server/superlink/fleet/rest_rere/rest_api.py +10 -9
  79. flwr/server/superlink/fleet/vce/vce_api.py +23 -23
  80. flwr/server/superlink/linkstate/__init__.py +28 -0
  81. flwr/server/superlink/{state/in_memory_state.py → linkstate/in_memory_linkstate.py} +237 -64
  82. flwr/server/superlink/{state/state.py → linkstate/linkstate.py} +166 -22
  83. flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +9 -9
  84. flwr/server/superlink/{state/sqlite_state.py → linkstate/sqlite_linkstate.py} +383 -174
  85. flwr/server/superlink/linkstate/utils.py +389 -0
  86. flwr/server/superlink/simulation/__init__.py +15 -0
  87. flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
  88. flwr/server/superlink/simulation/simulationio_servicer.py +153 -0
  89. flwr/simulation/__init__.py +5 -1
  90. flwr/simulation/app.py +236 -347
  91. flwr/simulation/legacy_app.py +402 -0
  92. flwr/simulation/ray_transport/ray_client_proxy.py +2 -2
  93. flwr/simulation/run_simulation.py +56 -141
  94. flwr/simulation/simulationio_connection.py +86 -0
  95. flwr/superexec/app.py +6 -134
  96. flwr/superexec/deployment.py +70 -69
  97. flwr/superexec/exec_grpc.py +15 -8
  98. flwr/superexec/exec_servicer.py +65 -65
  99. flwr/superexec/executor.py +26 -7
  100. flwr/superexec/simulation.py +62 -150
  101. {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/METADATA +9 -7
  102. {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/RECORD +105 -85
  103. {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/entry_points.txt +2 -0
  104. flwr/client/node_state_tests.py +0 -66
  105. flwr/proto/driver_pb2.py +0 -42
  106. flwr/proto/driver_pb2_grpc.py +0 -239
  107. flwr/proto/driver_pb2_grpc.pyi +0 -94
  108. flwr/server/superlink/state/utils.py +0 -148
  109. {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/LICENSE +0 -0
  110. {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/WHEEL +0 -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.11.1",
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,8 +8,9 @@ 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
+ "torch==2.3.1",
13
14
  "trl==0.8.1",
14
15
  "bitsandbytes==0.43.0",
15
16
  "scipy==1.13.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.11.1",
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.11.1",
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.11.1",
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.11.1",
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.11.1",
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.11.1",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
13
  "scikit-learn>=1.1.1",
14
14
  ]
@@ -8,9 +8,9 @@ version = "1.0.0"
8
8
  description = ""
9
9
  license = "Apache-2.0"
10
10
  dependencies = [
11
- "flwr[simulation]>=1.11.1",
11
+ "flwr[simulation]>=1.13.0",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
- "tensorflow>=2.11.1",
13
+ "tensorflow>=2.11.1,<2.18.0",
14
14
  ]
15
15
 
16
16
  [tool.hatch.build.targets.wheel]
flwr/cli/run/run.py CHANGED
@@ -16,7 +16,6 @@
16
16
 
17
17
  import json
18
18
  import subprocess
19
- import sys
20
19
  from logging import DEBUG
21
20
  from pathlib import Path
22
21
  from typing import Annotated, Any, Optional
@@ -24,11 +23,24 @@ from typing import Annotated, Any, Optional
24
23
  import typer
25
24
 
26
25
  from flwr.cli.build import build
27
- from flwr.cli.config_utils import load_and_validate
28
- from flwr.common.config import flatten_dict, parse_config_args
26
+ from flwr.cli.config_utils import (
27
+ load_and_validate,
28
+ validate_certificate_in_federation_config,
29
+ validate_federation_in_project_config,
30
+ validate_project_config,
31
+ )
32
+ from flwr.common.config import (
33
+ flatten_dict,
34
+ parse_config_args,
35
+ user_config_to_configsrecord,
36
+ )
29
37
  from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
30
38
  from flwr.common.logger import log
31
- from flwr.common.serde import fab_to_proto, user_config_to_proto
39
+ from flwr.common.serde import (
40
+ configs_record_to_proto,
41
+ fab_to_proto,
42
+ user_config_to_proto,
43
+ )
32
44
  from flwr.common.typing import Fab
33
45
  from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
34
46
  from flwr.proto.exec_pb2_grpc import ExecStub
@@ -79,96 +91,28 @@ def run(
79
91
 
80
92
  pyproject_path = app / "pyproject.toml" if app else None
81
93
  config, errors, warnings = load_and_validate(path=pyproject_path)
82
-
83
- if config is None:
84
- typer.secho(
85
- "Project configuration could not be loaded.\n"
86
- "pyproject.toml is invalid:\n"
87
- + "\n".join([f"- {line}" for line in errors]),
88
- fg=typer.colors.RED,
89
- bold=True,
90
- )
91
- sys.exit()
92
-
93
- if warnings:
94
- typer.secho(
95
- "Project configuration is missing the following "
96
- "recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]),
97
- fg=typer.colors.RED,
98
- bold=True,
99
- )
100
-
101
- typer.secho("Success", fg=typer.colors.GREEN)
102
-
103
- federation = federation or config["tool"]["flwr"]["federations"].get("default")
104
-
105
- if federation is None:
106
- typer.secho(
107
- "❌ No federation name was provided and the project's `pyproject.toml` "
108
- "doesn't declare a default federation (with a SuperExec address or an "
109
- "`options.num-supernodes` value).",
110
- fg=typer.colors.RED,
111
- bold=True,
112
- )
113
- raise typer.Exit(code=1)
114
-
115
- # Validate the federation exists in the configuration
116
- federation_config = config["tool"]["flwr"]["federations"].get(federation)
117
- if federation_config is None:
118
- available_feds = {
119
- fed for fed in config["tool"]["flwr"]["federations"] if fed != "default"
120
- }
121
- typer.secho(
122
- f"❌ There is no `{federation}` federation declared in "
123
- "`pyproject.toml`.\n The following federations were found:\n\n"
124
- + "\n".join(available_feds),
125
- fg=typer.colors.RED,
126
- bold=True,
127
- )
128
- raise typer.Exit(code=1)
94
+ config = validate_project_config(config, errors, warnings)
95
+ federation, federation_config = validate_federation_in_project_config(
96
+ federation, config
97
+ )
129
98
 
130
99
  if "address" in federation_config:
131
- _run_with_superexec(app, federation_config, config_overrides, stream)
100
+ _run_with_exec_api(app, federation_config, config_overrides, stream)
132
101
  else:
133
- _run_without_superexec(app, federation_config, config_overrides, federation)
102
+ _run_without_exec_api(app, federation_config, config_overrides, federation)
134
103
 
135
104
 
136
- # pylint: disable=too-many-locals
137
- def _run_with_superexec(
105
+ # pylint: disable-next=too-many-locals
106
+ def _run_with_exec_api(
138
107
  app: Path,
139
108
  federation_config: dict[str, Any],
140
109
  config_overrides: Optional[list[str]],
141
110
  stream: bool,
142
111
  ) -> None:
143
112
 
144
- insecure_str = federation_config.get("insecure")
145
- if root_certificates := federation_config.get("root-certificates"):
146
- root_certificates_bytes = (app / root_certificates).read_bytes()
147
- if insecure := bool(insecure_str):
148
- typer.secho(
149
- "❌ `root_certificates` were provided but the `insecure` parameter"
150
- "is set to `True`.",
151
- fg=typer.colors.RED,
152
- bold=True,
153
- )
154
- raise typer.Exit(code=1)
155
- else:
156
- root_certificates_bytes = None
157
- if insecure_str is None:
158
- typer.secho(
159
- "❌ To disable TLS, set `insecure = true` in `pyproject.toml`.",
160
- fg=typer.colors.RED,
161
- bold=True,
162
- )
163
- raise typer.Exit(code=1)
164
- if not (insecure := bool(insecure_str)):
165
- typer.secho(
166
- "❌ No certificate were given yet `insecure` is set to `False`.",
167
- fg=typer.colors.RED,
168
- bold=True,
169
- )
170
- raise typer.Exit(code=1)
171
-
113
+ insecure, root_certificates_bytes = validate_certificate_in_federation_config(
114
+ app, federation_config
115
+ )
172
116
  channel = create_channel(
173
117
  server_address=federation_config["address"],
174
118
  insecure=insecure,
@@ -181,26 +125,30 @@ def _run_with_superexec(
181
125
 
182
126
  fab_path, fab_hash = build(app)
183
127
  content = Path(fab_path).read_bytes()
128
+
129
+ # Delete FAB file once the bytes is computed
130
+ Path(fab_path).unlink()
131
+
184
132
  fab = Fab(fab_hash, content)
185
133
 
134
+ # Construct a `ConfigsRecord` out of a flattened `UserConfig`
135
+ fed_conf = flatten_dict(federation_config.get("options", {}))
136
+ c_record = user_config_to_configsrecord(fed_conf)
137
+
186
138
  req = StartRunRequest(
187
139
  fab=fab_to_proto(fab),
188
140
  override_config=user_config_to_proto(parse_config_args(config_overrides)),
189
- federation_config=user_config_to_proto(
190
- flatten_dict(federation_config.get("options"))
191
- ),
141
+ federation_options=configs_record_to_proto(c_record),
192
142
  )
193
143
  res = stub.StartRun(req)
194
144
 
195
- # Delete FAB file once it has been sent to the SuperExec
196
- Path(fab_path).unlink()
197
145
  typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
198
146
 
199
147
  if stream:
200
148
  start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
201
149
 
202
150
 
203
- def _run_without_superexec(
151
+ def _run_without_exec_api(
204
152
  app: Optional[Path],
205
153
  federation_config: dict[str, Any],
206
154
  config_overrides: Optional[list[str]],
flwr/client/app.py CHANGED
@@ -32,13 +32,19 @@ from flwr.cli.config_utils import get_fab_metadata
32
32
  from flwr.cli.install import install_from_fab
33
33
  from flwr.client.client import Client
34
34
  from flwr.client.client_app import ClientApp, LoadClientAppError
35
+ from flwr.client.nodestate.nodestate_factory import NodeStateFactory
35
36
  from flwr.client.typing import ClientFnExt
36
37
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
37
38
  from flwr.common.address import parse_address
38
39
  from flwr.common.constant import (
39
- CLIENTAPPIO_API_DEFAULT_ADDRESS,
40
+ CLIENT_OCTET,
41
+ CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
42
+ ISOLATION_MODE_PROCESS,
43
+ ISOLATION_MODE_SUBPROCESS,
44
+ MAX_RETRY_DELAY,
40
45
  MISSING_EXTRA_REST,
41
46
  RUN_ID_NUM_BYTES,
47
+ SERVER_OCTET,
42
48
  TRANSPORT_TYPE_GRPC_ADAPTER,
43
49
  TRANSPORT_TYPE_GRPC_BIDI,
44
50
  TRANSPORT_TYPE_GRPC_RERE,
@@ -52,18 +58,15 @@ from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
52
58
  from flwr.common.typing import Fab, Run, UserConfig
53
59
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
54
60
  from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
55
- from flwr.server.superlink.state.utils import generate_rand_int_from_bytes
61
+ from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
56
62
 
57
63
  from .clientapp.clientappio_servicer import ClientAppInputs, ClientAppIoServicer
58
64
  from .grpc_adapter_client.connection import grpc_adapter
59
65
  from .grpc_client.connection import grpc_connection
60
66
  from .grpc_rere_client.connection import grpc_request_response
61
67
  from .message_handler.message_handler import handle_control_message
62
- from .node_state import NodeState
63
68
  from .numpy_client import NumPyClient
64
-
65
- ISOLATION_MODE_SUBPROCESS = "subprocess"
66
- ISOLATION_MODE_PROCESS = "process"
69
+ from .run_info_store import DeprecatedRunInfoStore
67
70
 
68
71
 
69
72
  def _check_actionable_client(
@@ -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,
@@ -364,8 +386,10 @@ def start_client_internal(
364
386
  on_backoff=_on_backoff,
365
387
  )
366
388
 
367
- # NodeState gets initialized when the first connection is established
368
- node_state: Optional[NodeState] = None
389
+ # DeprecatedRunInfoStore gets initialized when the first connection is established
390
+ run_info_store: Optional[DeprecatedRunInfoStore] = None
391
+ state_factory = NodeStateFactory()
392
+ state = state_factory.state()
369
393
 
370
394
  runs: dict[int, Run] = {}
371
395
 
@@ -382,7 +406,7 @@ def start_client_internal(
382
406
  receive, send, create_node, delete_node, get_run, get_fab = conn
383
407
 
384
408
  # Register node when connecting the first time
385
- if node_state is None:
409
+ if run_info_store is None:
386
410
  if create_node is None:
387
411
  if transport not in ["grpc-bidi", None]:
388
412
  raise NotImplementedError(
@@ -391,19 +415,20 @@ def start_client_internal(
391
415
  )
392
416
  # gRPC-bidi doesn't have the concept of node_id,
393
417
  # so we set it to -1
394
- node_state = NodeState(
418
+ run_info_store = DeprecatedRunInfoStore(
395
419
  node_id=-1,
396
420
  node_config={},
397
421
  )
398
422
  else:
399
423
  # Call create_node fn to register node
400
- node_id: Optional[int] = ( # pylint: disable=assignment-from-none
401
- create_node()
402
- ) # pylint: disable=not-callable
403
- if node_id is None:
404
- raise ValueError("Node registration failed")
405
- node_state = NodeState(
406
- node_id=node_id,
424
+ # and store node_id in state
425
+ if (node_id := create_node()) is None:
426
+ raise ValueError(
427
+ "Failed to register SuperNode with the SuperLink"
428
+ )
429
+ state.set_node_id(node_id)
430
+ run_info_store = DeprecatedRunInfoStore(
431
+ node_id=state.get_node_id(),
407
432
  node_config=node_config,
408
433
  )
409
434
 
@@ -445,7 +470,7 @@ def start_client_internal(
445
470
  runs[run_id] = get_run(run_id)
446
471
  # If get_run is None, i.e., in grpc-bidi mode
447
472
  else:
448
- runs[run_id] = Run(run_id, "", "", "", {})
473
+ runs[run_id] = Run.create_empty(run_id=run_id)
449
474
 
450
475
  run: Run = runs[run_id]
451
476
  if get_fab is not None and run.fab_hash:
@@ -461,7 +486,7 @@ def start_client_internal(
461
486
  run.fab_id, run.fab_version = fab_id, fab_version
462
487
 
463
488
  # Register context for this run
464
- node_state.register_context(
489
+ run_info_store.register_context(
465
490
  run_id=run_id,
466
491
  run=run,
467
492
  flwr_path=flwr_path,
@@ -469,7 +494,7 @@ def start_client_internal(
469
494
  )
470
495
 
471
496
  # Retrieve context for this run
472
- context = node_state.retrieve_context(run_id=run_id)
497
+ context = run_info_store.retrieve_context(run_id=run_id)
473
498
  # Create an error reply message that will never be used to prevent
474
499
  # the used-before-assignment linting error
475
500
  reply_message = message.create_error_reply(
@@ -505,14 +530,24 @@ def start_client_internal(
505
530
  )
506
531
 
507
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
+ )
508
541
  # Start ClientApp subprocess
509
542
  command = [
510
543
  "flwr-clientapp",
511
- "--supernode",
512
- supernode_address,
544
+ "--clientappio-api-address",
545
+ io_address,
513
546
  "--token",
514
547
  str(token),
515
548
  ]
549
+ command.append("--insecure")
550
+
516
551
  subprocess.run(
517
552
  command,
518
553
  stdout=None,
@@ -542,7 +577,7 @@ def start_client_internal(
542
577
  # Raise exception, crash process
543
578
  raise ex
544
579
 
545
- # Don't update/change NodeState
580
+ # Don't update/change DeprecatedRunInfoStore
546
581
 
547
582
  e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION
548
583
  # Ex fmt: "<class 'ZeroDivisionError'>:<'division by zero'>"
@@ -567,7 +602,7 @@ def start_client_internal(
567
602
  )
568
603
  else:
569
604
  # No exception, update node state
570
- node_state.update_context(
605
+ run_info_store.update_context(
571
606
  run_id=run_id,
572
607
  context=context,
573
608
  )
@@ -780,7 +815,10 @@ class _AppStateTracker:
780
815
  signal.signal(signal.SIGTERM, signal_handler)
781
816
 
782
817
 
783
- 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]:
784
822
  """Run ClientAppIo API gRPC server."""
785
823
  clientappio_servicer: grpc.Server = ClientAppIoServicer()
786
824
  clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
@@ -791,6 +829,7 @@ def run_clientappio_api_grpc(address: str) -> tuple[grpc.Server, ClientAppIoServ
791
829
  ),
792
830
  server_address=address,
793
831
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
832
+ certificates=certificates,
794
833
  )
795
834
  log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
796
835
  clientappio_grpc_server.start()