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
@@ -17,20 +17,17 @@
17
17
  import time
18
18
  import warnings
19
19
  from collections.abc import Iterable
20
- from logging import DEBUG, WARNING
21
- from typing import Optional, cast
20
+ from logging import DEBUG, INFO, WARN, WARNING
21
+ from typing import Any, Optional, cast
22
22
 
23
23
  import grpc
24
24
 
25
25
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
26
- from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS
26
+ from flwr.common.constant import MAX_RETRY_DELAY, SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS
27
27
  from flwr.common.grpc import create_channel
28
28
  from flwr.common.logger import log
29
- from flwr.common.serde import (
30
- message_from_taskres,
31
- message_to_taskins,
32
- user_config_from_proto,
33
- )
29
+ from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
30
+ from flwr.common.serde import message_from_taskres, message_to_taskins, run_from_proto
34
31
  from flwr.common.typing import Run
35
32
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
36
33
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
@@ -70,7 +67,7 @@ class GrpcDriver(Driver):
70
67
 
71
68
  def __init__( # pylint: disable=too-many-arguments
72
69
  self,
73
- serverappio_service_address: str = SERVERAPPIO_API_DEFAULT_ADDRESS,
70
+ serverappio_service_address: str = SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
74
71
  root_certificates: Optional[bytes] = None,
75
72
  ) -> None:
76
73
  self._addr = serverappio_service_address
@@ -79,6 +76,7 @@ class GrpcDriver(Driver):
79
76
  self._grpc_stub: Optional[ServerAppIoStub] = None
80
77
  self._channel: Optional[grpc.Channel] = None
81
78
  self.node = Node(node_id=0, anonymous=True)
79
+ self._retry_invoker = _make_simple_grpc_retry_invoker()
82
80
 
83
81
  @property
84
82
  def _is_connected(self) -> bool:
@@ -99,6 +97,7 @@ class GrpcDriver(Driver):
99
97
  root_certificates=self._cert,
100
98
  )
101
99
  self._grpc_stub = ServerAppIoStub(self._channel)
100
+ _wrap_stub(self._grpc_stub, self._retry_invoker)
102
101
  log(DEBUG, "[Driver] Connected to %s", self._addr)
103
102
 
104
103
  def _disconnect(self) -> None:
@@ -119,13 +118,7 @@ class GrpcDriver(Driver):
119
118
  res: GetRunResponse = self._stub.GetRun(req)
120
119
  if not res.HasField("run"):
121
120
  raise RuntimeError(f"Cannot find the run with ID: {run_id}")
122
- self._run = Run(
123
- run_id=res.run.run_id,
124
- fab_id=res.run.fab_id,
125
- fab_version=res.run.fab_version,
126
- fab_hash=res.run.fab_hash,
127
- override_config=user_config_from_proto(res.run.override_config),
128
- )
121
+ self._run = run_from_proto(res.run)
129
122
 
130
123
  @property
131
124
  def run(self) -> Run:
@@ -265,3 +258,60 @@ class GrpcDriver(Driver):
265
258
  return
266
259
  # Disconnect
267
260
  self._disconnect()
261
+
262
+
263
+ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
264
+ """Create a simple gRPC retry invoker."""
265
+
266
+ def _on_sucess(retry_state: RetryState) -> None:
267
+ if retry_state.tries > 1:
268
+ log(
269
+ INFO,
270
+ "Connection successful after %.2f seconds and %s tries.",
271
+ retry_state.elapsed_time,
272
+ retry_state.tries,
273
+ )
274
+
275
+ def _on_backoff(retry_state: RetryState) -> None:
276
+ if retry_state.tries == 1:
277
+ log(WARN, "Connection attempt failed, retrying...")
278
+ else:
279
+ log(
280
+ WARN,
281
+ "Connection attempt failed, retrying in %.2f seconds",
282
+ retry_state.actual_wait,
283
+ )
284
+
285
+ def _on_giveup(retry_state: RetryState) -> None:
286
+ if retry_state.tries > 1:
287
+ log(
288
+ WARN,
289
+ "Giving up reconnection after %.2f seconds and %s tries.",
290
+ retry_state.elapsed_time,
291
+ retry_state.tries,
292
+ )
293
+
294
+ return RetryInvoker(
295
+ wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
296
+ recoverable_exceptions=grpc.RpcError,
297
+ max_tries=None,
298
+ max_time=None,
299
+ on_success=_on_sucess,
300
+ on_backoff=_on_backoff,
301
+ on_giveup=_on_giveup,
302
+ should_giveup=lambda e: e.code() != grpc.StatusCode.UNAVAILABLE, # type: ignore
303
+ )
304
+
305
+
306
+ def _wrap_stub(stub: ServerAppIoStub, retry_invoker: RetryInvoker) -> None:
307
+ """Wrap the gRPC stub with a retry invoker."""
308
+
309
+ def make_lambda(original_method: Any) -> Any:
310
+ return lambda *args, **kwargs: retry_invoker.invoke(
311
+ original_method, *args, **kwargs
312
+ )
313
+
314
+ for method_name in vars(stub):
315
+ method = getattr(stub, method_name)
316
+ if callable(method):
317
+ setattr(stub, method_name, make_lambda(method))
@@ -15,33 +15,15 @@
15
15
  """Run ServerApp."""
16
16
 
17
17
 
18
- import argparse
19
18
  import sys
20
- from logging import DEBUG, INFO, WARN
21
- from pathlib import Path
19
+ from logging import DEBUG, ERROR
22
20
  from typing import Optional
23
21
 
24
- from flwr.cli.config_utils import get_fab_metadata
25
- from flwr.cli.install import install_from_fab
26
- from flwr.common import Context, EventType, RecordSet, event
27
- from flwr.common.config import (
28
- get_flwr_dir,
29
- get_fused_config_from_dir,
30
- get_metadata_from_config,
31
- get_project_config,
32
- get_project_dir,
33
- )
34
- from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS
35
- from flwr.common.logger import log, update_console_handler, warn_deprecated_feature
22
+ from flwr.common import Context
23
+ from flwr.common.logger import log, warn_unsupported_feature
36
24
  from flwr.common.object_ref import load_app
37
- from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
38
- from flwr.proto.run_pb2 import ( # pylint: disable=E0611
39
- CreateRunRequest,
40
- CreateRunResponse,
41
- )
42
25
 
43
26
  from .driver import Driver
44
- from .driver.grpc_driver import GrpcDriver
45
27
  from .server_app import LoadServerAppError, ServerApp
46
28
 
47
29
 
@@ -87,221 +69,9 @@ def run(
87
69
  # pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
88
70
  def run_server_app() -> None:
89
71
  """Run Flower server app."""
90
- event(EventType.RUN_SERVER_APP_ENTER)
91
-
92
- args = _parse_args_run_server_app().parse_args()
93
-
94
- # Check if the server app reference is passed.
95
- # Since Flower 1.11, passing a reference is not allowed.
96
- app_path: Optional[str] = args.app
97
- # If the provided app_path doesn't exist, and contains a ":",
98
- # it is likely to be a server app reference instead of a path.
99
- if app_path is not None and not Path(app_path).exists() and ":" in app_path:
100
- sys.exit(
101
- "It appears you've passed a reference like `server:app`.\n\n"
102
- "Note that since version `1.11.0`, `flower-server-app` no longer supports "
103
- "passing a reference to a `ServerApp` attribute. Instead, you need to pass "
104
- "the path to Flower app via the argument `--app`. This is the path to a "
105
- "directory containing a `pyproject.toml`. You can create a valid Flower "
106
- "app by executing `flwr new` and following the prompt."
107
- )
108
-
109
- if args.server != SERVERAPPIO_API_DEFAULT_ADDRESS:
110
- warn = "Passing flag --server is deprecated. Use --superlink instead."
111
- warn_deprecated_feature(warn)
112
-
113
- if args.superlink != SERVERAPPIO_API_DEFAULT_ADDRESS:
114
- # if `--superlink` also passed, then
115
- # warn user that this argument overrides what was passed with `--server`
116
- log(
117
- WARN,
118
- "Both `--server` and `--superlink` were passed. "
119
- "`--server` will be ignored. Connecting to the "
120
- "SuperLink ServerAppIo API at %s.",
121
- args.superlink,
122
- )
123
- else:
124
- args.superlink = args.server
125
-
126
- update_console_handler(
127
- level=DEBUG if args.verbose else INFO,
128
- timestamps=args.verbose,
129
- colored=True,
130
- )
131
-
132
- # Obtain certificates
133
- if args.insecure:
134
- if args.root_certificates is not None:
135
- sys.exit(
136
- "Conflicting options: The '--insecure' flag disables HTTPS, "
137
- "but '--root-certificates' was also specified. Please remove "
138
- "the '--root-certificates' option when running in insecure mode, "
139
- "or omit '--insecure' to use HTTPS."
140
- )
141
- log(
142
- WARN,
143
- "Option `--insecure` was set. "
144
- "Starting insecure HTTP client connected to %s.",
145
- args.superlink,
146
- )
147
- root_certificates = None
148
- else:
149
- # Load the certificates if provided, or load the system certificates
150
- cert_path = args.root_certificates
151
- if cert_path is None:
152
- root_certificates = None
153
- else:
154
- root_certificates = Path(cert_path).read_bytes()
155
- log(
156
- DEBUG,
157
- "Starting secure HTTPS client connected to %s "
158
- "with the following certificates: %s.",
159
- args.superlink,
160
- cert_path,
161
- )
162
-
163
- if not (app_path is None) ^ (args.run_id is None):
164
- raise sys.exit(
165
- "Please provide either a Flower App path or a Run ID, but not both. "
166
- "For more details, use: ``flower-server-app -h``"
167
- )
168
-
169
- # Initialize GrpcDriver
170
- if app_path is None:
171
- # User provided `--run-id`, but not `app_dir`
172
- driver = GrpcDriver(
173
- serverappio_service_address=args.superlink,
174
- root_certificates=root_certificates,
175
- )
176
- flwr_dir = get_flwr_dir(args.flwr_dir)
177
- driver.set_run(args.run_id)
178
- run_ = driver.run
179
- if not run_.fab_hash:
180
- raise ValueError("FAB hash not provided.")
181
- fab_req = GetFabRequest(hash_str=run_.fab_hash)
182
- # pylint: disable-next=W0212
183
- fab_res: GetFabResponse = driver._stub.GetFab(fab_req)
184
- if fab_res.fab.hash_str != run_.fab_hash:
185
- raise ValueError("FAB hashes don't match.")
186
- install_from_fab(fab_res.fab.content, flwr_dir, True)
187
- fab_id, fab_version = get_fab_metadata(fab_res.fab.content)
188
-
189
- app_path = str(get_project_dir(fab_id, fab_version, run_.fab_hash, flwr_dir))
190
- config = get_project_config(app_path)
191
- run_id = run_.run_id
192
- else:
193
- # User provided `app_dir`, but not `--run-id`
194
- # Create run if run_id is not provided
195
- driver = GrpcDriver(
196
- serverappio_service_address=args.superlink,
197
- root_certificates=root_certificates,
198
- )
199
- # Load config from the project directory
200
- config = get_project_config(app_path)
201
- fab_version, fab_id = get_metadata_from_config(config)
202
-
203
- # Create run
204
- req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version)
205
- res: CreateRunResponse = driver._stub.CreateRun(req) # pylint: disable=W0212
206
- # Fetch full `Run` using `run_id`
207
- driver.set_run(res.run_id) # pylint: disable=W0212
208
- run_id = res.run_id
209
-
210
- # Obtain server app reference and the run config
211
- server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
212
- server_app_run_config = get_fused_config_from_dir(
213
- Path(app_path), driver.run.override_config
72
+ warn_unsupported_feature(
73
+ "The command `flower-server-app` is deprecated and no longer in use. "
74
+ "Use the `flwr-serverapp` exclusively instead."
214
75
  )
215
-
216
- log(DEBUG, "Flower will load ServerApp `%s` in %s", server_app_attr, app_path)
217
-
218
- log(
219
- DEBUG,
220
- "root_certificates: `%s`",
221
- root_certificates,
222
- )
223
-
224
- # Initialize Context
225
- context = Context(
226
- run_id=run_id,
227
- node_id=0,
228
- node_config={},
229
- state=RecordSet(),
230
- run_config=server_app_run_config,
231
- )
232
-
233
- # Run the ServerApp with the Driver
234
- run(
235
- driver=driver,
236
- context=context,
237
- server_app_dir=app_path,
238
- server_app_attr=server_app_attr,
239
- )
240
-
241
- # Clean up
242
- driver.close()
243
-
244
- event(EventType.RUN_SERVER_APP_LEAVE)
245
-
246
-
247
- def _parse_args_run_server_app() -> argparse.ArgumentParser:
248
- """Parse flower-server-app command line arguments."""
249
- parser = argparse.ArgumentParser(
250
- description="Start a Flower server app",
251
- )
252
-
253
- parser.add_argument(
254
- "app",
255
- nargs="?",
256
- default=None,
257
- help="Load and run the `ServerApp` from the specified Flower App path. "
258
- "The `pyproject.toml` file must be located in the root of this path.",
259
- )
260
- parser.add_argument(
261
- "--insecure",
262
- action="store_true",
263
- help="Run the `ServerApp` without HTTPS. By default, the app runs with "
264
- "HTTPS enabled. Use this flag only if you understand the risks.",
265
- )
266
- parser.add_argument(
267
- "--verbose",
268
- action="store_true",
269
- help="Set the logging to `DEBUG`.",
270
- )
271
- parser.add_argument(
272
- "--root-certificates",
273
- metavar="ROOT_CERT",
274
- type=str,
275
- help="Specifies the path to the PEM-encoded root certificate file for "
276
- "establishing secure HTTPS connections.",
277
- )
278
- parser.add_argument(
279
- "--server",
280
- default=SERVERAPPIO_API_DEFAULT_ADDRESS,
281
- help="Server address",
282
- )
283
- parser.add_argument(
284
- "--superlink",
285
- default=SERVERAPPIO_API_DEFAULT_ADDRESS,
286
- help="SuperLink ServerAppIo API (gRPC-rere) address "
287
- "(IPv4, IPv6, or a domain name)",
288
- )
289
- parser.add_argument(
290
- "--run-id",
291
- default=None,
292
- type=int,
293
- help="The identifier of the run.",
294
- )
295
- parser.add_argument(
296
- "--flwr-dir",
297
- default=None,
298
- help="""The path containing installed Flower Apps.
299
- By default, this value is equal to:
300
-
301
- - `$FLWR_HOME/` if `$FLWR_HOME` is defined
302
- - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
303
- - `$HOME/.flwr/` in all other cases
304
- """,
305
- )
306
-
307
- return parser
76
+ log(ERROR, "`flower-server-app` used.")
77
+ sys.exit()
@@ -15,6 +15,7 @@
15
15
  """Flower ServerApp process."""
16
16
 
17
17
  import argparse
18
+ import sys
18
19
  from logging import DEBUG, ERROR, INFO
19
20
  from pathlib import Path
20
21
  from queue import Queue
@@ -23,14 +24,18 @@ from typing import Optional
23
24
 
24
25
  from flwr.cli.config_utils import get_fab_metadata
25
26
  from flwr.cli.install import install_from_fab
26
- from flwr.common.args import add_args_flwr_app_common, try_obtain_certificates
27
+ from flwr.common.args import add_args_flwr_app_common
27
28
  from flwr.common.config import (
28
29
  get_flwr_dir,
29
30
  get_fused_config_from_dir,
30
31
  get_project_config,
31
32
  get_project_dir,
32
33
  )
33
- from flwr.common.constant import Status, SubStatus
34
+ from flwr.common.constant import (
35
+ SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
36
+ Status,
37
+ SubStatus,
38
+ )
34
39
  from flwr.common.logger import (
35
40
  log,
36
41
  mirror_output_to_queue,
@@ -62,37 +67,29 @@ def flwr_serverapp() -> None:
62
67
  log_queue: Queue[Optional[str]] = Queue()
63
68
  mirror_output_to_queue(log_queue)
64
69
 
65
- parser = argparse.ArgumentParser(
66
- description="Run a Flower ServerApp",
67
- )
68
- parser.add_argument(
69
- "--superlink",
70
- type=str,
71
- help="Address of SuperLink's ServerAppIo API",
72
- )
73
- parser.add_argument(
74
- "--run-once",
75
- action="store_true",
76
- help="When set, this process will start a single ServerApp for a pending Run. "
77
- "If there is no pending Run, the process will exit.",
78
- )
79
- add_args_flwr_app_common(parser=parser)
80
- args = parser.parse_args()
70
+ args = _parse_args_run_flwr_serverapp().parse_args()
81
71
 
82
72
  log(INFO, "Starting Flower ServerApp")
83
- certificates = try_obtain_certificates(args)
73
+
74
+ if not args.insecure:
75
+ log(
76
+ ERROR,
77
+ "`flwr-serverapp` does not support TLS yet. "
78
+ "Please use the '--insecure' flag.",
79
+ )
80
+ sys.exit(1)
84
81
 
85
82
  log(
86
83
  DEBUG,
87
84
  "Starting isolated `ServerApp` connected to SuperLink's ServerAppIo API at %s",
88
- args.superlink,
85
+ args.serverappio_api_address,
89
86
  )
90
87
  run_serverapp(
91
- superlink=args.superlink,
88
+ serverappio_api_address=args.serverappio_api_address,
92
89
  log_queue=log_queue,
93
90
  run_once=args.run_once,
94
91
  flwr_dir=args.flwr_dir,
95
- certificates=certificates,
92
+ certificates=None,
96
93
  )
97
94
 
98
95
  # Restore stdout/stderr
@@ -100,7 +97,7 @@ def flwr_serverapp() -> None:
100
97
 
101
98
 
102
99
  def run_serverapp( # pylint: disable=R0914, disable=W0212
103
- superlink: str,
100
+ serverappio_api_address: str,
104
101
  log_queue: Queue[Optional[str]],
105
102
  run_once: bool,
106
103
  flwr_dir: Optional[str] = None,
@@ -108,7 +105,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
108
105
  ) -> None:
109
106
  """Run Flower ServerApp process."""
110
107
  driver = GrpcDriver(
111
- serverappio_service_address=superlink,
108
+ serverappio_service_address=serverappio_api_address,
112
109
  root_certificates=certificates,
113
110
  )
114
111
 
@@ -196,6 +193,12 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
196
193
  run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(ex))
197
194
 
198
195
  finally:
196
+ # Stop log uploader for this run and upload final logs
197
+ if log_uploader:
198
+ stop_log_uploader(log_queue, log_uploader)
199
+ log_uploader = None
200
+
201
+ # Update run status
199
202
  if run_status:
200
203
  run_status_proto = run_status_to_proto(run_status)
201
204
  driver._stub.UpdateRunStatus(
@@ -204,11 +207,28 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
204
207
  )
205
208
  )
206
209
 
207
- # Stop log uploader for this run
208
- if log_uploader:
209
- stop_log_uploader(log_queue, log_uploader)
210
- log_uploader = None
211
-
212
210
  # Stop the loop if `flwr-serverapp` is expected to process a single run
213
211
  if run_once:
214
212
  break
213
+
214
+
215
+ def _parse_args_run_flwr_serverapp() -> argparse.ArgumentParser:
216
+ """Parse flwr-serverapp command line arguments."""
217
+ parser = argparse.ArgumentParser(
218
+ description="Run a Flower ServerApp",
219
+ )
220
+ parser.add_argument(
221
+ "--serverappio-api-address",
222
+ default=SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
223
+ type=str,
224
+ help="Address of SuperLink's ServerAppIo API (IPv4, IPv6, or a domain name)."
225
+ f"By default, it is set to {SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
226
+ )
227
+ parser.add_argument(
228
+ "--run-once",
229
+ action="store_true",
230
+ help="When set, this process will start a single ServerApp for a pending Run. "
231
+ "If there is no pending Run, the process will exit.",
232
+ )
233
+ add_args_flwr_app_common(parser=parser)
234
+ return parser
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import sys
21
21
  from collections.abc import Awaitable
22
- from typing import Callable, TypeVar
22
+ from typing import Callable, TypeVar, cast
23
23
 
24
24
  from google.protobuf.message import Message as GrpcMessage
25
25
 
@@ -39,8 +39,9 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
39
39
  )
40
40
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
41
41
  from flwr.server.superlink.ffs.ffs import Ffs
42
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
42
43
  from flwr.server.superlink.fleet.message_handler import message_handler
43
- from flwr.server.superlink.linkstate import LinkState
44
+ from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
44
45
 
45
46
  try:
46
47
  from starlette.applications import Starlette
@@ -90,7 +91,7 @@ def rest_request_response(
90
91
  async def create_node(request: CreateNodeRequest) -> CreateNodeResponse:
91
92
  """Create Node."""
92
93
  # Get state from app
93
- state: LinkState = app.state.STATE_FACTORY.state()
94
+ state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
94
95
 
95
96
  # Handle message
96
97
  return message_handler.create_node(request=request, state=state)
@@ -100,7 +101,7 @@ async def create_node(request: CreateNodeRequest) -> CreateNodeResponse:
100
101
  async def delete_node(request: DeleteNodeRequest) -> DeleteNodeResponse:
101
102
  """Delete Node Id."""
102
103
  # Get state from app
103
- state: LinkState = app.state.STATE_FACTORY.state()
104
+ state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
104
105
 
105
106
  # Handle message
106
107
  return message_handler.delete_node(request=request, state=state)
@@ -110,7 +111,7 @@ async def delete_node(request: DeleteNodeRequest) -> DeleteNodeResponse:
110
111
  async def pull_task_ins(request: PullTaskInsRequest) -> PullTaskInsResponse:
111
112
  """Pull TaskIns."""
112
113
  # Get state from app
113
- state: LinkState = app.state.STATE_FACTORY.state()
114
+ state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
114
115
 
115
116
  # Handle message
116
117
  return message_handler.pull_task_ins(request=request, state=state)
@@ -121,7 +122,7 @@ async def pull_task_ins(request: PullTaskInsRequest) -> PullTaskInsResponse:
121
122
  async def push_task_res(request: PushTaskResRequest) -> PushTaskResResponse:
122
123
  """Push TaskRes."""
123
124
  # Get state from app
124
- state: LinkState = app.state.STATE_FACTORY.state()
125
+ state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
125
126
 
126
127
  # Handle message
127
128
  return message_handler.push_task_res(request=request, state=state)
@@ -131,7 +132,7 @@ async def push_task_res(request: PushTaskResRequest) -> PushTaskResResponse:
131
132
  async def ping(request: PingRequest) -> PingResponse:
132
133
  """Ping."""
133
134
  # Get state from app
134
- state: LinkState = app.state.STATE_FACTORY.state()
135
+ state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
135
136
 
136
137
  # Handle message
137
138
  return message_handler.ping(request=request, state=state)
@@ -141,7 +142,7 @@ async def ping(request: PingRequest) -> PingResponse:
141
142
  async def get_run(request: GetRunRequest) -> GetRunResponse:
142
143
  """GetRun."""
143
144
  # Get state from app
144
- state: LinkState = app.state.STATE_FACTORY.state()
145
+ state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
145
146
 
146
147
  # Handle message
147
148
  return message_handler.get_run(request=request, state=state)
@@ -151,7 +152,7 @@ async def get_run(request: GetRunRequest) -> GetRunResponse:
151
152
  async def get_fab(request: GetFabRequest) -> GetFabResponse:
152
153
  """GetRun."""
153
154
  # Get ffs from app
154
- ffs: Ffs = app.state.FFS_FACTORY.state()
155
+ ffs: Ffs = cast(FfsFactory, app.state.FFS_FACTORY).ffs()
155
156
 
156
157
  # Handle message
157
158
  return message_handler.get_fab(request=request, ffs=ffs)