flwr-nightly 1.9.0.dev20240417__py3-none-any.whl → 1.9.0.dev20240419__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.

@@ -21,7 +21,9 @@ import threading
21
21
  from contextlib import contextmanager
22
22
  from copy import copy
23
23
  from logging import ERROR, INFO, WARN
24
- from typing import Callable, Iterator, Optional, Tuple, Union
24
+ from typing import Callable, Iterator, Optional, Tuple, Type, TypeVar, Union
25
+
26
+ from google.protobuf.message import Message as GrpcMessage
25
27
 
26
28
  from flwr.client.heartbeat import start_ping_loop
27
29
  from flwr.client.message_handler.message_handler import validate_out_message
@@ -42,6 +44,9 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
42
44
  CreateNodeRequest,
43
45
  CreateNodeResponse,
44
46
  DeleteNodeRequest,
47
+ DeleteNodeResponse,
48
+ GetRunRequest,
49
+ GetRunResponse,
45
50
  PingRequest,
46
51
  PingResponse,
47
52
  PullTaskInsRequest,
@@ -63,6 +68,9 @@ PATH_DELETE_NODE: str = "api/v0/fleet/delete-node"
63
68
  PATH_PULL_TASK_INS: str = "api/v0/fleet/pull-task-ins"
64
69
  PATH_PUSH_TASK_RES: str = "api/v0/fleet/push-task-res"
65
70
  PATH_PING: str = "api/v0/fleet/ping"
71
+ PATH_GET_RUN: str = "/api/v0/fleet/get-run"
72
+
73
+ T = TypeVar("T", bound=GrpcMessage)
66
74
 
67
75
 
68
76
  @contextmanager
@@ -80,6 +88,7 @@ def http_request_response( # pylint: disable=R0914, R0915
80
88
  Callable[[Message], None],
81
89
  Optional[Callable[[], None]],
82
90
  Optional[Callable[[], None]],
91
+ Optional[Callable[[int], Tuple[str, str]]],
83
92
  ]
84
93
  ]:
85
94
  """Primitives for request/response-based interaction with a server.
@@ -141,55 +150,72 @@ def http_request_response( # pylint: disable=R0914, R0915
141
150
  ping_stop_event = threading.Event()
142
151
 
143
152
  ###########################################################################
144
- # ping/create_node/delete_node/receive/send functions
153
+ # ping/create_node/delete_node/receive/send/get_run functions
145
154
  ###########################################################################
146
155
 
147
- def ping() -> None:
148
- # Get Node
149
- if node is None:
150
- log(ERROR, "Node instance missing")
151
- return
152
-
153
- # Construct the ping request
154
- req = PingRequest(node=node, ping_interval=PING_DEFAULT_INTERVAL)
155
- req_bytes: bytes = req.SerializeToString()
156
+ def _request(
157
+ req: GrpcMessage, res_type: Type[T], api_path: str, retry: bool = True
158
+ ) -> Optional[T]:
159
+ # Serialize the request
160
+ req_bytes = req.SerializeToString()
156
161
 
157
162
  # Send the request
158
- res = requests.post(
159
- url=f"{base_url}/{PATH_PING}",
160
- headers={
161
- "Accept": "application/protobuf",
162
- "Content-Type": "application/protobuf",
163
- },
164
- data=req_bytes,
165
- verify=verify,
166
- timeout=PING_CALL_TIMEOUT,
167
- )
163
+ def post() -> requests.Response:
164
+ return requests.post(
165
+ f"{base_url}/{api_path}",
166
+ data=req_bytes,
167
+ headers={
168
+ "Accept": "application/protobuf",
169
+ "Content-Type": "application/protobuf",
170
+ },
171
+ verify=verify,
172
+ timeout=None,
173
+ )
174
+
175
+ if retry:
176
+ res: requests.Response = retry_invoker.invoke(post)
177
+ else:
178
+ res = post()
168
179
 
169
180
  # Check status code and headers
170
181
  if res.status_code != 200:
171
- return
182
+ return None
172
183
  if "content-type" not in res.headers:
173
184
  log(
174
185
  WARN,
175
186
  "[Node] POST /%s: missing header `Content-Type`",
176
- PATH_PULL_TASK_INS,
187
+ api_path,
177
188
  )
178
- return
189
+ return None
179
190
  if res.headers["content-type"] != "application/protobuf":
180
191
  log(
181
192
  WARN,
182
193
  "[Node] POST /%s: header `Content-Type` has wrong value",
183
- PATH_PULL_TASK_INS,
194
+ api_path,
184
195
  )
185
- return
196
+ return None
186
197
 
187
198
  # Deserialize ProtoBuf from bytes
188
- ping_res = PingResponse()
189
- ping_res.ParseFromString(res.content)
199
+ grpc_res = res_type()
200
+ grpc_res.ParseFromString(res.content)
201
+ return grpc_res
202
+
203
+ def ping() -> None:
204
+ # Get Node
205
+ if node is None:
206
+ log(ERROR, "Node instance missing")
207
+ return
208
+
209
+ # Construct the ping request
210
+ req = PingRequest(node=node, ping_interval=PING_DEFAULT_INTERVAL)
211
+
212
+ # Send the request
213
+ res = _request(req, PingResponse, PATH_PING, retry=False)
214
+ if res is None:
215
+ return
190
216
 
191
217
  # Check if success
192
- if not ping_res.success:
218
+ if not res.success:
193
219
  raise RuntimeError("Ping failed unexpectedly.")
194
220
 
195
221
  # Wait
@@ -201,46 +227,16 @@ def http_request_response( # pylint: disable=R0914, R0915
201
227
 
202
228
  def create_node() -> None:
203
229
  """Set create_node."""
204
- create_node_req_proto = CreateNodeRequest(ping_interval=PING_DEFAULT_INTERVAL)
205
- create_node_req_bytes: bytes = create_node_req_proto.SerializeToString()
206
-
207
- res = retry_invoker.invoke(
208
- requests.post,
209
- url=f"{base_url}/{PATH_CREATE_NODE}",
210
- headers={
211
- "Accept": "application/protobuf",
212
- "Content-Type": "application/protobuf",
213
- },
214
- data=create_node_req_bytes,
215
- verify=verify,
216
- timeout=None,
217
- )
230
+ req = CreateNodeRequest(ping_interval=PING_DEFAULT_INTERVAL)
218
231
 
219
- # Check status code and headers
220
- if res.status_code != 200:
221
- return
222
- if "content-type" not in res.headers:
223
- log(
224
- WARN,
225
- "[Node] POST /%s: missing header `Content-Type`",
226
- PATH_PULL_TASK_INS,
227
- )
228
- return
229
- if res.headers["content-type"] != "application/protobuf":
230
- log(
231
- WARN,
232
- "[Node] POST /%s: header `Content-Type` has wrong value",
233
- PATH_PULL_TASK_INS,
234
- )
232
+ # Send the request
233
+ res = _request(req, CreateNodeResponse, PATH_CREATE_NODE)
234
+ if res is None:
235
235
  return
236
236
 
237
- # Deserialize ProtoBuf from bytes
238
- create_node_response_proto = CreateNodeResponse()
239
- create_node_response_proto.ParseFromString(res.content)
240
-
241
237
  # Remember the node and the ping-loop thread
242
238
  nonlocal node, ping_thread
243
- node = create_node_response_proto.node
239
+ node = res.node
244
240
  ping_thread = start_ping_loop(ping, ping_stop_event)
245
241
 
246
242
  def delete_node() -> None:
@@ -256,36 +252,12 @@ def http_request_response( # pylint: disable=R0914, R0915
256
252
  ping_thread.join()
257
253
 
258
254
  # Send DeleteNode request
259
- delete_node_req_proto = DeleteNodeRequest(node=node)
260
- delete_node_req_req_bytes: bytes = delete_node_req_proto.SerializeToString()
261
- res = retry_invoker.invoke(
262
- requests.post,
263
- url=f"{base_url}/{PATH_DELETE_NODE}",
264
- headers={
265
- "Accept": "application/protobuf",
266
- "Content-Type": "application/protobuf",
267
- },
268
- data=delete_node_req_req_bytes,
269
- verify=verify,
270
- timeout=None,
271
- )
255
+ req = DeleteNodeRequest(node=node)
272
256
 
273
- # Check status code and headers
274
- if res.status_code != 200:
275
- return
276
- if "content-type" not in res.headers:
277
- log(
278
- WARN,
279
- "[Node] POST /%s: missing header `Content-Type`",
280
- PATH_PULL_TASK_INS,
281
- )
257
+ # Send the request
258
+ res = _request(req, DeleteNodeResponse, PATH_CREATE_NODE)
259
+ if res is None:
282
260
  return
283
- if res.headers["content-type"] != "application/protobuf":
284
- log(
285
- WARN,
286
- "[Node] POST /%s: header `Content-Type` has wrong value",
287
- PATH_PULL_TASK_INS,
288
- )
289
261
 
290
262
  # Cleanup
291
263
  node = None
@@ -298,46 +270,15 @@ def http_request_response( # pylint: disable=R0914, R0915
298
270
  return None
299
271
 
300
272
  # Request instructions (task) from server
301
- pull_task_ins_req_proto = PullTaskInsRequest(node=node)
302
- pull_task_ins_req_bytes: bytes = pull_task_ins_req_proto.SerializeToString()
273
+ req = PullTaskInsRequest(node=node)
303
274
 
304
- # Request instructions (task) from server
305
- res = retry_invoker.invoke(
306
- requests.post,
307
- url=f"{base_url}/{PATH_PULL_TASK_INS}",
308
- headers={
309
- "Accept": "application/protobuf",
310
- "Content-Type": "application/protobuf",
311
- },
312
- data=pull_task_ins_req_bytes,
313
- verify=verify,
314
- timeout=None,
315
- )
316
-
317
- # Check status code and headers
318
- if res.status_code != 200:
319
- return None
320
- if "content-type" not in res.headers:
321
- log(
322
- WARN,
323
- "[Node] POST /%s: missing header `Content-Type`",
324
- PATH_PULL_TASK_INS,
325
- )
326
- return None
327
- if res.headers["content-type"] != "application/protobuf":
328
- log(
329
- WARN,
330
- "[Node] POST /%s: header `Content-Type` has wrong value",
331
- PATH_PULL_TASK_INS,
332
- )
275
+ # Send the request
276
+ res = _request(req, PullTaskInsResponse, PATH_PULL_TASK_INS)
277
+ if res is None:
333
278
  return None
334
279
 
335
- # Deserialize ProtoBuf from bytes
336
- pull_task_ins_response_proto = PullTaskInsResponse()
337
- pull_task_ins_response_proto.ParseFromString(res.content)
338
-
339
280
  # Get the current TaskIns
340
- task_ins: Optional[TaskIns] = get_task_ins(pull_task_ins_response_proto)
281
+ task_ins: Optional[TaskIns] = get_task_ins(res)
341
282
 
342
283
  # Discard the current TaskIns if not valid
343
284
  if task_ins is not None and not (
@@ -372,61 +313,39 @@ def http_request_response( # pylint: disable=R0914, R0915
372
313
  if not validate_out_message(message, metadata):
373
314
  log(ERROR, "Invalid out message")
374
315
  return
316
+ metadata = None
375
317
 
376
318
  # Construct TaskRes
377
319
  task_res = message_to_taskres(message)
378
320
 
379
321
  # Serialize ProtoBuf to bytes
380
- push_task_res_request_proto = PushTaskResRequest(task_res_list=[task_res])
381
- push_task_res_request_bytes: bytes = (
382
- push_task_res_request_proto.SerializeToString()
383
- )
384
-
385
- # Send ClientMessage to server
386
- res = retry_invoker.invoke(
387
- requests.post,
388
- url=f"{base_url}/{PATH_PUSH_TASK_RES}",
389
- headers={
390
- "Accept": "application/protobuf",
391
- "Content-Type": "application/protobuf",
392
- },
393
- data=push_task_res_request_bytes,
394
- verify=verify,
395
- timeout=None,
396
- )
397
-
398
- metadata = None
322
+ req = PushTaskResRequest(task_res_list=[task_res])
399
323
 
400
- # Check status code and headers
401
- if res.status_code != 200:
402
- return
403
- if "content-type" not in res.headers:
404
- log(
405
- WARN,
406
- "[Node] POST /%s: missing header `Content-Type`",
407
- PATH_PUSH_TASK_RES,
408
- )
409
- return
410
- if res.headers["content-type"] != "application/protobuf":
411
- log(
412
- WARN,
413
- "[Node] POST /%s: header `Content-Type` has wrong value",
414
- PATH_PUSH_TASK_RES,
415
- )
324
+ # Send the request
325
+ res = _request(req, PushTaskResResponse, PATH_PUSH_TASK_RES)
326
+ if res is None:
416
327
  return
417
328
 
418
- # Deserialize ProtoBuf from bytes
419
- push_task_res_response_proto = PushTaskResResponse()
420
- push_task_res_response_proto.ParseFromString(res.content)
421
329
  log(
422
330
  INFO,
423
331
  "[Node] POST /%s: success, created result %s",
424
332
  PATH_PUSH_TASK_RES,
425
- push_task_res_response_proto.results, # pylint: disable=no-member
333
+ res.results, # pylint: disable=no-member
426
334
  )
427
335
 
336
+ def get_run(run_id: int) -> Tuple[str, str]:
337
+ # Construct the request
338
+ req = GetRunRequest(run_id=run_id)
339
+
340
+ # Send the request
341
+ res = _request(req, GetRunResponse, PATH_GET_RUN)
342
+ if res is None:
343
+ return "", ""
344
+
345
+ return res.run.fab_id, res.run.fab_version
346
+
428
347
  try:
429
348
  # Yield methods
430
- yield (receive, send, create_node, delete_node)
349
+ yield (receive, send, create_node, delete_node, get_run)
431
350
  except Exception as exc: # pylint: disable=broad-except
432
351
  log(ERROR, exc)
@@ -0,0 +1,22 @@
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 SuperNode."""
16
+
17
+
18
+ from .app import run_supernode as run_supernode
19
+
20
+ __all__ = [
21
+ "run_supernode",
22
+ ]
@@ -0,0 +1,107 @@
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 SuperNode."""
16
+
17
+ import argparse
18
+ from logging import DEBUG, INFO
19
+
20
+ from flwr.common import EventType, event
21
+ from flwr.common.exit_handlers import register_exit_handlers
22
+ from flwr.common.logger import log
23
+
24
+
25
+ def run_supernode() -> None:
26
+ """Run Flower SuperNode."""
27
+ log(INFO, "Starting Flower SuperNode")
28
+
29
+ event(EventType.RUN_SUPERNODE_ENTER)
30
+
31
+ args = _parse_args_run_supernode().parse_args()
32
+
33
+ log(
34
+ DEBUG,
35
+ "Flower will load ClientApp `%s`",
36
+ getattr(args, "client-app"),
37
+ )
38
+
39
+ # Graceful shutdown
40
+ register_exit_handlers(
41
+ event_type=EventType.RUN_SUPERNODE_LEAVE,
42
+ )
43
+
44
+
45
+ def _parse_args_run_supernode() -> argparse.ArgumentParser:
46
+ """Parse flower-supernode command line arguments."""
47
+ parser = argparse.ArgumentParser(
48
+ description="Start a Flower SuperNode",
49
+ )
50
+
51
+ parse_args_run_client_app(parser=parser)
52
+
53
+ return parser
54
+
55
+
56
+ def parse_args_run_client_app(parser: argparse.ArgumentParser) -> None:
57
+ """Parse command line arguments."""
58
+ parser.add_argument(
59
+ "client-app",
60
+ help="For example: `client:app` or `project.package.module:wrapper.app`",
61
+ )
62
+ parser.add_argument(
63
+ "--insecure",
64
+ action="store_true",
65
+ help="Run the client without HTTPS. By default, the client runs with "
66
+ "HTTPS enabled. Use this flag only if you understand the risks.",
67
+ )
68
+ parser.add_argument(
69
+ "--rest",
70
+ action="store_true",
71
+ help="Use REST as a transport layer for the client.",
72
+ )
73
+ parser.add_argument(
74
+ "--root-certificates",
75
+ metavar="ROOT_CERT",
76
+ type=str,
77
+ help="Specifies the path to the PEM-encoded root certificate file for "
78
+ "establishing secure HTTPS connections.",
79
+ )
80
+ parser.add_argument(
81
+ "--server",
82
+ default="0.0.0.0:9092",
83
+ help="Server address",
84
+ )
85
+ parser.add_argument(
86
+ "--max-retries",
87
+ type=int,
88
+ default=None,
89
+ help="The maximum number of times the client will try to connect to the"
90
+ "server before giving up in case of a connection error. By default,"
91
+ "it is set to None, meaning there is no limit to the number of tries.",
92
+ )
93
+ parser.add_argument(
94
+ "--max-wait-time",
95
+ type=float,
96
+ default=None,
97
+ help="The maximum duration before the client stops trying to"
98
+ "connect to the server in case of connection error. By default, it"
99
+ "is set to None, meaning there is no limit to the total time.",
100
+ )
101
+ parser.add_argument(
102
+ "--dir",
103
+ default="",
104
+ help="Add specified directory to the PYTHONPATH and load Flower "
105
+ "app from there."
106
+ " Default: current working directory.",
107
+ )
@@ -16,23 +16,20 @@
16
16
 
17
17
 
18
18
  from dataclasses import dataclass
19
- from typing import Callable, Dict, Optional, Type, TypeVar
19
+ from typing import Dict, Optional, cast
20
20
 
21
21
  from .configsrecord import ConfigsRecord
22
22
  from .metricsrecord import MetricsRecord
23
23
  from .parametersrecord import ParametersRecord
24
24
  from .typeddict import TypedDict
25
25
 
26
- T = TypeVar("T")
27
26
 
27
+ class RecordSetData:
28
+ """Inner data container for the RecordSet class."""
28
29
 
29
- @dataclass
30
- class RecordSet:
31
- """RecordSet stores groups of parameters, metrics and configs."""
32
-
33
- _parameters_records: TypedDict[str, ParametersRecord]
34
- _metrics_records: TypedDict[str, MetricsRecord]
35
- _configs_records: TypedDict[str, ConfigsRecord]
30
+ parameters_records: TypedDict[str, ParametersRecord]
31
+ metrics_records: TypedDict[str, MetricsRecord]
32
+ configs_records: TypedDict[str, ConfigsRecord]
36
33
 
37
34
  def __init__(
38
35
  self,
@@ -40,40 +37,82 @@ class RecordSet:
40
37
  metrics_records: Optional[Dict[str, MetricsRecord]] = None,
41
38
  configs_records: Optional[Dict[str, ConfigsRecord]] = None,
42
39
  ) -> None:
43
- def _get_check_fn(__t: Type[T]) -> Callable[[T], None]:
44
- def _check_fn(__v: T) -> None:
45
- if not isinstance(__v, __t):
46
- raise TypeError(f"Expected `{__t}`, but `{type(__v)}` was passed.")
47
-
48
- return _check_fn
49
-
50
- self._parameters_records = TypedDict[str, ParametersRecord](
51
- _get_check_fn(str), _get_check_fn(ParametersRecord)
40
+ self.parameters_records = TypedDict[str, ParametersRecord](
41
+ self._check_fn_str, self._check_fn_params
52
42
  )
53
- self._metrics_records = TypedDict[str, MetricsRecord](
54
- _get_check_fn(str), _get_check_fn(MetricsRecord)
43
+ self.metrics_records = TypedDict[str, MetricsRecord](
44
+ self._check_fn_str, self._check_fn_metrics
55
45
  )
56
- self._configs_records = TypedDict[str, ConfigsRecord](
57
- _get_check_fn(str), _get_check_fn(ConfigsRecord)
46
+ self.configs_records = TypedDict[str, ConfigsRecord](
47
+ self._check_fn_str, self._check_fn_configs
58
48
  )
59
49
  if parameters_records is not None:
60
- self._parameters_records.update(parameters_records)
50
+ self.parameters_records.update(parameters_records)
61
51
  if metrics_records is not None:
62
- self._metrics_records.update(metrics_records)
52
+ self.metrics_records.update(metrics_records)
63
53
  if configs_records is not None:
64
- self._configs_records.update(configs_records)
54
+ self.configs_records.update(configs_records)
55
+
56
+ def _check_fn_str(self, key: str) -> None:
57
+ if not isinstance(key, str):
58
+ raise TypeError(
59
+ f"Expected `{str.__name__}`, but "
60
+ f"received `{type(key).__name__}` for the key."
61
+ )
62
+
63
+ def _check_fn_params(self, record: ParametersRecord) -> None:
64
+ if not isinstance(record, ParametersRecord):
65
+ raise TypeError(
66
+ f"Expected `{ParametersRecord.__name__}`, but "
67
+ f"received `{type(record).__name__}` for the value."
68
+ )
69
+
70
+ def _check_fn_metrics(self, record: MetricsRecord) -> None:
71
+ if not isinstance(record, MetricsRecord):
72
+ raise TypeError(
73
+ f"Expected `{MetricsRecord.__name__}`, but "
74
+ f"received `{type(record).__name__}` for the value."
75
+ )
76
+
77
+ def _check_fn_configs(self, record: ConfigsRecord) -> None:
78
+ if not isinstance(record, ConfigsRecord):
79
+ raise TypeError(
80
+ f"Expected `{ConfigsRecord.__name__}`, but "
81
+ f"received `{type(record).__name__}` for the value."
82
+ )
83
+
84
+
85
+ @dataclass
86
+ class RecordSet:
87
+ """RecordSet stores groups of parameters, metrics and configs."""
88
+
89
+ def __init__(
90
+ self,
91
+ parameters_records: Optional[Dict[str, ParametersRecord]] = None,
92
+ metrics_records: Optional[Dict[str, MetricsRecord]] = None,
93
+ configs_records: Optional[Dict[str, ConfigsRecord]] = None,
94
+ ) -> None:
95
+ data = RecordSetData(
96
+ parameters_records=parameters_records,
97
+ metrics_records=metrics_records,
98
+ configs_records=configs_records,
99
+ )
100
+ setattr(self, "_data", data) # noqa
65
101
 
66
102
  @property
67
103
  def parameters_records(self) -> TypedDict[str, ParametersRecord]:
68
104
  """Dictionary holding ParametersRecord instances."""
69
- return self._parameters_records
105
+ data = cast(RecordSetData, getattr(self, "_data")) # noqa
106
+ return data.parameters_records
70
107
 
71
108
  @property
72
109
  def metrics_records(self) -> TypedDict[str, MetricsRecord]:
73
110
  """Dictionary holding MetricsRecord instances."""
74
- return self._metrics_records
111
+ data = cast(RecordSetData, getattr(self, "_data")) # noqa
112
+ return data.metrics_records
75
113
 
76
114
  @property
77
115
  def configs_records(self) -> TypedDict[str, ConfigsRecord]:
78
116
  """Dictionary holding ConfigsRecord instances."""
79
- return self._configs_records
117
+ data = cast(RecordSetData, getattr(self, "_data")) # noqa
118
+ return data.configs_records
flwr/common/telemetry.py CHANGED
@@ -160,6 +160,10 @@ class EventType(str, Enum):
160
160
  RUN_SERVER_APP_ENTER = auto()
161
161
  RUN_SERVER_APP_LEAVE = auto()
162
162
 
163
+ # SuperNode
164
+ RUN_SUPERNODE_ENTER = auto()
165
+ RUN_SUPERNODE_LEAVE = auto()
166
+
163
167
 
164
168
  # Use the ThreadPoolExecutor with max_workers=1 to have a queue
165
169
  # and also ensure that telemetry calls are not blocking.
flwr/server/app.py CHANGED
@@ -291,9 +291,11 @@ def run_fleet_api() -> None:
291
291
 
292
292
  # pylint: disable=too-many-branches, too-many-locals, too-many-statements
293
293
  def run_superlink() -> None:
294
- """Run Flower server (Driver API and Fleet API)."""
295
- log(INFO, "Starting Flower server")
294
+ """Run Flower SuperLink (Driver API and Fleet API)."""
295
+ log(INFO, "Starting Flower SuperLink")
296
+
296
297
  event(EventType.RUN_SUPERLINK_ENTER)
298
+
297
299
  args = _parse_args_run_superlink().parse_args()
298
300
 
299
301
  # Parse IP address
@@ -568,9 +570,7 @@ def _parse_args_run_fleet_api() -> argparse.ArgumentParser:
568
570
  def _parse_args_run_superlink() -> argparse.ArgumentParser:
569
571
  """Parse command line arguments for both Driver API and Fleet API."""
570
572
  parser = argparse.ArgumentParser(
571
- description="This will start a Flower server "
572
- "(meaning, a Driver API and a Fleet API), "
573
- "that clients will be able to connect to.",
573
+ description="Start a Flower SuperLink",
574
574
  )
575
575
 
576
576
  _add_args_common(parser=parser)