flwr-nightly 1.13.0.dev20241114__py3-none-any.whl → 1.13.0.dev20241116__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.

@@ -57,6 +57,8 @@ from .utils import (
57
57
  generate_rand_int_from_bytes,
58
58
  has_valid_sub_status,
59
59
  is_valid_transition,
60
+ verify_found_taskres,
61
+ verify_taskins_ids,
60
62
  )
61
63
 
62
64
  SQL_CREATE_TABLE_NODE = """
@@ -510,136 +512,67 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
510
512
 
511
513
  # pylint: disable-next=R0912,R0915,R0914
512
514
  def get_task_res(self, task_ids: set[UUID]) -> list[TaskRes]:
513
- """Get TaskRes for task_ids.
515
+ """Get TaskRes for the given TaskIns IDs."""
516
+ ret: dict[UUID, TaskRes] = {}
514
517
 
515
- Usually, the ServerAppIo API calls this method to get results for instructions
516
- it has previously scheduled.
517
-
518
- Retrieves all TaskRes for the given `task_ids` and returns and empty list if
519
- none could be found.
520
-
521
- Constraints
522
- -----------
523
- If `limit` is not `None`, return, at most, `limit` number of TaskRes. The limit
524
- will only take effect if enough task_ids are in the set AND are currently
525
- available. If `limit` is set, it has to be greater than zero.
526
- """
527
- # Check if corresponding TaskIns exists and is not expired
528
- task_ids_placeholders = ",".join([f":id_{i}" for i in range(len(task_ids))])
518
+ # Verify TaskIns IDs
519
+ current = time.time()
529
520
  query = f"""
530
521
  SELECT *
531
522
  FROM task_ins
532
- WHERE task_id IN ({task_ids_placeholders})
533
- AND (created_at + ttl) > CAST(strftime('%s', 'now') AS REAL)
523
+ WHERE task_id IN ({",".join(["?"] * len(task_ids))});
534
524
  """
535
- query += ";"
536
-
537
- task_ins_data = {}
538
- for index, task_id in enumerate(task_ids):
539
- task_ins_data[f"id_{index}"] = str(task_id)
540
-
541
- task_ins_rows = self.query(query, task_ins_data)
542
-
543
- if not task_ins_rows:
544
- return []
545
-
546
- for row in task_ins_rows:
547
- # Convert values from sint64 to uint64
525
+ rows = self.query(query, tuple(str(task_id) for task_id in task_ids))
526
+ found_task_ins_dict: dict[UUID, TaskIns] = {}
527
+ for row in rows:
548
528
  convert_sint64_values_in_dict_to_uint64(
549
529
  row, ["run_id", "producer_node_id", "consumer_node_id"]
550
530
  )
551
- task_ins = dict_to_task_ins(row)
552
- if task_ins.task.created_at + task_ins.task.ttl <= time.time():
553
- log(WARNING, "TaskIns with task_id %s is expired.", task_ins.task_id)
554
- task_ids.remove(UUID(task_ins.task_id))
531
+ found_task_ins_dict[UUID(row["task_id"])] = dict_to_task_ins(row)
555
532
 
556
- # Retrieve all anonymous Tasks
557
- if len(task_ids) == 0:
558
- return []
533
+ ret = verify_taskins_ids(
534
+ inquired_taskins_ids=task_ids,
535
+ found_taskins_dict=found_task_ins_dict,
536
+ current_time=current,
537
+ )
559
538
 
560
- placeholders = ",".join([f":id_{i}" for i in range(len(task_ids))])
539
+ # Find all TaskRes
561
540
  query = f"""
562
541
  SELECT *
563
542
  FROM task_res
564
- WHERE ancestry IN ({placeholders})
565
- AND delivered_at = ""
543
+ WHERE ancestry IN ({",".join(["?"] * len(task_ids))})
544
+ AND delivered_at = "";
566
545
  """
546
+ rows = self.query(query, tuple(str(task_id) for task_id in task_ids))
547
+ for row in rows:
548
+ convert_sint64_values_in_dict_to_uint64(
549
+ row, ["run_id", "producer_node_id", "consumer_node_id"]
550
+ )
551
+ tmp_ret_dict = verify_found_taskres(
552
+ inquired_taskins_ids=task_ids,
553
+ found_taskins_dict=found_task_ins_dict,
554
+ found_taskres_list=[dict_to_task_res(row) for row in rows],
555
+ current_time=current,
556
+ )
557
+ ret.update(tmp_ret_dict)
567
558
 
568
- data: dict[str, Union[str, float, int]] = {}
569
-
570
- query += ";"
571
-
572
- for index, task_id in enumerate(task_ids):
573
- data[f"id_{index}"] = str(task_id)
574
-
575
- rows = self.query(query, data)
576
-
577
- if rows:
578
- # Prepare query
579
- found_task_ids = [row["task_id"] for row in rows]
580
- placeholders = ",".join([f":id_{i}" for i in range(len(found_task_ids))])
581
- query = f"""
582
- UPDATE task_res
583
- SET delivered_at = :delivered_at
584
- WHERE task_id IN ({placeholders})
585
- RETURNING *;
586
- """
587
-
588
- # Prepare data for query
589
- delivered_at = now().isoformat()
590
- data = {"delivered_at": delivered_at}
591
- for index, task_id in enumerate(found_task_ids):
592
- data[f"id_{index}"] = str(task_id)
593
-
594
- # Run query
595
- rows = self.query(query, data)
596
-
597
- for row in rows:
598
- # Convert values from sint64 to uint64
599
- convert_sint64_values_in_dict_to_uint64(
600
- row, ["run_id", "producer_node_id", "consumer_node_id"]
601
- )
602
-
603
- result = [dict_to_task_res(row) for row in rows]
604
-
605
- # 1. Query: Fetch consumer_node_id of remaining task_ids
606
- # Assume the ancestry field only contains one element
607
- data.clear()
608
- replied_task_ids: set[UUID] = {UUID(str(row["ancestry"])) for row in rows}
609
- remaining_task_ids = task_ids - replied_task_ids
610
- placeholders = ",".join([f":id_{i}" for i in range(len(remaining_task_ids))])
611
- query = f"""
612
- SELECT consumer_node_id
613
- FROM task_ins
614
- WHERE task_id IN ({placeholders});
615
- """
616
- for index, task_id in enumerate(remaining_task_ids):
617
- data[f"id_{index}"] = str(task_id)
618
- node_ids = [int(row["consumer_node_id"]) for row in self.query(query, data)]
619
-
620
- # 2. Query: Select offline nodes
621
- placeholders = ",".join([f":id_{i}" for i in range(len(node_ids))])
559
+ # Mark existing TaskRes to be returned as delivered
560
+ delivered_at = now().isoformat()
561
+ for task_res in ret.values():
562
+ task_res.task.delivered_at = delivered_at
563
+ task_res_ids = [task_res.task_id for task_res in ret.values()]
622
564
  query = f"""
623
- SELECT node_id
624
- FROM node
625
- WHERE node_id IN ({placeholders})
626
- AND online_until < :time;
565
+ UPDATE task_res
566
+ SET delivered_at = ?
567
+ WHERE task_id IN ({",".join(["?"] * len(task_res_ids))});
627
568
  """
628
- data = {f"id_{i}": str(node_id) for i, node_id in enumerate(node_ids)}
629
- data["time"] = time.time()
630
- offline_node_ids = [int(row["node_id"]) for row in self.query(query, data)]
569
+ data: list[Any] = [delivered_at] + task_res_ids
570
+ self.query(query, data)
631
571
 
632
- # 3. Query: Select TaskIns for offline nodes
633
- placeholders = ",".join([f":id_{i}" for i in range(len(offline_node_ids))])
634
- query = f"""
635
- SELECT *
636
- FROM task_ins
637
- WHERE consumer_node_id IN ({placeholders});
638
- """
639
- data = {f"id_{i}": str(node_id) for i, node_id in enumerate(offline_node_ids)}
640
- task_ins_rows = self.query(query, data)
572
+ # Cleanup
573
+ self._force_delete_tasks_by_ids(set(ret.keys()))
641
574
 
642
- return result
575
+ return list(ret.values())
643
576
 
644
577
  def num_task_ins(self) -> int:
645
578
  """Calculate the number of task_ins in store.
@@ -699,6 +632,32 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
699
632
 
700
633
  return None
701
634
 
635
+ def _force_delete_tasks_by_ids(self, task_ids: set[UUID]) -> None:
636
+ """Delete tasks based on a set of TaskIns IDs."""
637
+ if not task_ids:
638
+ return
639
+ if self.conn is None:
640
+ raise AttributeError("LinkState not initialized")
641
+
642
+ placeholders = ",".join([f":id_{index}" for index in range(len(task_ids))])
643
+ data = {f"id_{index}": str(task_id) for index, task_id in enumerate(task_ids)}
644
+
645
+ # Delete task_ins
646
+ query_1 = f"""
647
+ DELETE FROM task_ins
648
+ WHERE task_id IN ({placeholders});
649
+ """
650
+
651
+ # Delete task_res
652
+ query_2 = f"""
653
+ DELETE FROM task_res
654
+ WHERE ancestry IN ({placeholders});
655
+ """
656
+
657
+ with self.conn:
658
+ self.conn.execute(query_1, data)
659
+ self.conn.execute(query_2, data)
660
+
702
661
  def create_node(
703
662
  self, ping_interval: float, public_key: Optional[bytes] = None
704
663
  ) -> int:
@@ -15,15 +15,23 @@
15
15
  """Utility functions for State."""
16
16
 
17
17
 
18
+ from logging import ERROR
18
19
  from os import urandom
20
+ from typing import Optional, Union
21
+ from uuid import UUID, uuid4
19
22
 
20
- from flwr.common import ConfigsRecord, Context, serde
21
- from flwr.common.constant import Status, SubStatus
23
+ from flwr.common import ConfigsRecord, Context, log, now, serde
24
+ from flwr.common.constant import ErrorCode, Status, SubStatus
22
25
  from flwr.common.typing import RunStatus
23
- from flwr.proto.message_pb2 import Context as ProtoContext # pylint: disable=E0611
24
26
 
25
27
  # pylint: disable=E0611
28
+ from flwr.proto.error_pb2 import Error
29
+ from flwr.proto.message_pb2 import Context as ProtoContext
30
+ from flwr.proto.node_pb2 import Node
26
31
  from flwr.proto.recordset_pb2 import ConfigsRecord as ProtoConfigsRecord
32
+ from flwr.proto.task_pb2 import Task, TaskIns, TaskRes
33
+
34
+ # pylint: enable=E0611
27
35
 
28
36
  NODE_UNAVAILABLE_ERROR_REASON = (
29
37
  "Error: Node Unavailable - The destination node is currently unavailable. "
@@ -43,6 +51,13 @@ VALID_RUN_SUB_STATUSES = {
43
51
  SubStatus.FAILED,
44
52
  SubStatus.STOPPED,
45
53
  }
54
+ MESSAGE_UNAVAILABLE_ERROR_REASON = (
55
+ "Error: Message Unavailable - The requested message could not be found in the "
56
+ "database. It may have expired due to its TTL or never existed."
57
+ )
58
+ REPLY_MESSAGE_UNAVAILABLE_ERROR_REASON = (
59
+ "Error: Reply Message Unavailable - The reply message has expired."
60
+ )
46
61
 
47
62
 
48
63
  def generate_rand_int_from_bytes(num_bytes: int) -> int:
@@ -208,3 +223,167 @@ def has_valid_sub_status(status: RunStatus) -> bool:
208
223
  if status.status == Status.FINISHED:
209
224
  return status.sub_status in VALID_RUN_SUB_STATUSES
210
225
  return status.sub_status == ""
226
+
227
+
228
+ def create_taskres_for_unavailable_taskins(taskins_id: Union[str, UUID]) -> TaskRes:
229
+ """Generate a TaskRes with a TaskIns unavailable error.
230
+
231
+ Parameters
232
+ ----------
233
+ taskins_id : Union[str, UUID]
234
+ The ID of the unavailable TaskIns.
235
+
236
+ Returns
237
+ -------
238
+ TaskRes
239
+ A TaskRes with an error code MESSAGE_UNAVAILABLE to indicate that the
240
+ inquired TaskIns ID cannot be found (due to non-existence or expiration).
241
+ """
242
+ current_time = now().timestamp()
243
+ return TaskRes(
244
+ task_id=str(uuid4()),
245
+ group_id="", # Unknown group ID
246
+ run_id=0, # Unknown run ID
247
+ task=Task(
248
+ # This function is only called by SuperLink, and thus it's the producer.
249
+ producer=Node(node_id=0, anonymous=False),
250
+ consumer=Node(node_id=0, anonymous=False),
251
+ created_at=current_time,
252
+ ttl=0,
253
+ ancestry=[str(taskins_id)],
254
+ task_type="", # Unknown message type
255
+ error=Error(
256
+ code=ErrorCode.MESSAGE_UNAVAILABLE,
257
+ reason=MESSAGE_UNAVAILABLE_ERROR_REASON,
258
+ ),
259
+ ),
260
+ )
261
+
262
+
263
+ def create_taskres_for_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes:
264
+ """Generate a TaskRes with a reply message unavailable error from a TaskIns.
265
+
266
+ Parameters
267
+ ----------
268
+ ref_taskins : TaskIns
269
+ The reference TaskIns object.
270
+
271
+ Returns
272
+ -------
273
+ TaskRes
274
+ The generated TaskRes with an error code REPLY_MESSAGE_UNAVAILABLE_ERROR_REASON,
275
+ indicating that the original TaskRes has expired.
276
+ """
277
+ current_time = now().timestamp()
278
+ ttl = ref_taskins.task.ttl - (current_time - ref_taskins.task.created_at)
279
+ if ttl < 0:
280
+ log(ERROR, "Creating TaskRes for TaskIns that exceeds its TTL.")
281
+ ttl = 0
282
+ return TaskRes(
283
+ task_id=str(uuid4()),
284
+ group_id=ref_taskins.group_id,
285
+ run_id=ref_taskins.run_id,
286
+ task=Task(
287
+ # This function is only called by SuperLink, and thus it's the producer.
288
+ producer=Node(node_id=0, anonymous=False),
289
+ consumer=Node(node_id=0, anonymous=False),
290
+ created_at=current_time,
291
+ ttl=ttl,
292
+ ancestry=[ref_taskins.task_id],
293
+ task_type=ref_taskins.task.task_type,
294
+ error=Error(
295
+ code=ErrorCode.REPLY_MESSAGE_UNAVAILABLE,
296
+ reason=REPLY_MESSAGE_UNAVAILABLE_ERROR_REASON,
297
+ ),
298
+ ),
299
+ )
300
+
301
+
302
+ def has_expired(task_ins_or_res: Union[TaskIns, TaskRes], current_time: float) -> bool:
303
+ """Check if the TaskIns/TaskRes has expired."""
304
+ return task_ins_or_res.task.ttl + task_ins_or_res.task.created_at < current_time
305
+
306
+
307
+ def verify_taskins_ids(
308
+ inquired_taskins_ids: set[UUID],
309
+ found_taskins_dict: dict[UUID, TaskIns],
310
+ current_time: Optional[float] = None,
311
+ update_set: bool = True,
312
+ ) -> dict[UUID, TaskRes]:
313
+ """Verify found TaskIns and generate error TaskRes for invalid ones.
314
+
315
+ Parameters
316
+ ----------
317
+ inquired_taskins_ids : set[UUID]
318
+ Set of TaskIns IDs for which to generate error TaskRes if invalid.
319
+ found_taskins_dict : dict[UUID, TaskIns]
320
+ Dictionary containing all found TaskIns indexed by their IDs.
321
+ current_time : Optional[float] (default: None)
322
+ The current time to check for expiration. If set to `None`, the current time
323
+ will automatically be set to the current timestamp using `now().timestamp()`.
324
+ update_set : bool (default: True)
325
+ If True, the `inquired_taskins_ids` will be updated to remove invalid ones,
326
+ by default True.
327
+
328
+ Returns
329
+ -------
330
+ dict[UUID, TaskRes]
331
+ A dictionary of error TaskRes indexed by the corresponding TaskIns ID.
332
+ """
333
+ ret_dict = {}
334
+ current = current_time if current_time else now().timestamp()
335
+ for taskins_id in list(inquired_taskins_ids):
336
+ # Generate error TaskRes if the task_ins doesn't exist or has expired
337
+ taskins = found_taskins_dict.get(taskins_id)
338
+ if taskins is None or has_expired(taskins, current):
339
+ if update_set:
340
+ inquired_taskins_ids.remove(taskins_id)
341
+ taskres = create_taskres_for_unavailable_taskins(taskins_id)
342
+ ret_dict[taskins_id] = taskres
343
+ return ret_dict
344
+
345
+
346
+ def verify_found_taskres(
347
+ inquired_taskins_ids: set[UUID],
348
+ found_taskins_dict: dict[UUID, TaskIns],
349
+ found_taskres_list: list[TaskRes],
350
+ current_time: Optional[float] = None,
351
+ update_set: bool = True,
352
+ ) -> dict[UUID, TaskRes]:
353
+ """Verify found TaskRes and generate error TaskRes for invalid ones.
354
+
355
+ Parameters
356
+ ----------
357
+ inquired_taskins_ids : set[UUID]
358
+ Set of TaskIns IDs for which to generate error TaskRes if invalid.
359
+ found_taskins_dict : dict[UUID, TaskIns]
360
+ Dictionary containing all found TaskIns indexed by their IDs.
361
+ found_taskres_list : dict[TaskIns, TaskRes]
362
+ List of found TaskRes to be verified.
363
+ current_time : Optional[float] (default: None)
364
+ The current time to check for expiration. If set to `None`, the current time
365
+ will automatically be set to the current timestamp using `now().timestamp()`.
366
+ update_set : bool (default: True)
367
+ If True, the `inquired_taskins_ids` will be updated to remove ones
368
+ that have a TaskRes, by default True.
369
+
370
+ Returns
371
+ -------
372
+ dict[UUID, TaskRes]
373
+ A dictionary of TaskRes indexed by the corresponding TaskIns ID.
374
+ """
375
+ ret_dict: dict[UUID, TaskRes] = {}
376
+ current = current_time if current_time else now().timestamp()
377
+ for taskres in found_taskres_list:
378
+ taskins_id = UUID(taskres.task.ancestry[0])
379
+ if update_set:
380
+ inquired_taskins_ids.remove(taskins_id)
381
+ # Check if the TaskRes has expired
382
+ if has_expired(taskres, current):
383
+ # No need to insert the error TaskRes
384
+ taskres = create_taskres_for_unavailable_taskres(
385
+ found_taskins_dict[taskins_id]
386
+ )
387
+ taskres.task.delivered_at = now().isoformat()
388
+ ret_dict[taskins_id] = taskres
389
+ return ret_dict
flwr/simulation/app.py CHANGED
@@ -119,7 +119,7 @@ def flwr_simulation() -> None:
119
119
  args.superlink,
120
120
  )
121
121
  run_simulation_process(
122
- superlink=args.superlink,
122
+ simulationio_api_address=args.superlink,
123
123
  log_queue=log_queue,
124
124
  run_once=args.run_once,
125
125
  flwr_dir_=args.flwr_dir,
@@ -131,7 +131,7 @@ def flwr_simulation() -> None:
131
131
 
132
132
 
133
133
  def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R0915
134
- superlink: str,
134
+ simulationio_api_address: str,
135
135
  log_queue: Queue[Optional[str]],
136
136
  run_once: bool,
137
137
  flwr_dir_: Optional[str] = None,
@@ -139,7 +139,7 @@ def run_simulation_process( # pylint: disable=R0914, disable=W0212, disable=R09
139
139
  ) -> None:
140
140
  """Run Flower Simulation process."""
141
141
  conn = SimulationIoConnection(
142
- simulationio_service_address=superlink,
142
+ simulationio_service_address=simulationio_api_address,
143
143
  root_certificates=certificates,
144
144
  )
145
145
 
@@ -30,7 +30,12 @@ from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy
30
30
  from flwr.client import ClientFnExt
31
31
  from flwr.common import EventType, event
32
32
  from flwr.common.constant import NODE_ID_NUM_BYTES
33
- from flwr.common.logger import log, set_logger_propagation, warn_unsupported_feature
33
+ from flwr.common.logger import (
34
+ log,
35
+ set_logger_propagation,
36
+ warn_deprecated_feature,
37
+ warn_unsupported_feature,
38
+ )
34
39
  from flwr.server.client_manager import ClientManager
35
40
  from flwr.server.history import History
36
41
  from flwr.server.server import Server, init_defaults, run_fl
@@ -108,6 +113,11 @@ def start_simulation(
108
113
  ) -> History:
109
114
  """Start a Ray-based Flower simulation server.
110
115
 
116
+ Warning
117
+ -------
118
+ This function is deprecated since 1.13.0. Use :code: `flwr run` to start a Flower
119
+ simulation.
120
+
111
121
  Parameters
112
122
  ----------
113
123
  client_fn : ClientFnExt
@@ -183,6 +193,16 @@ def start_simulation(
183
193
  Object containing metrics from training.
184
194
  """ # noqa: E501
185
195
  # pylint: disable-msg=too-many-locals
196
+ msg = (
197
+ "flwr.simulation.start_simulation() is deprecated."
198
+ "\n\tInstead, use the `flwr run` CLI command to start a local simulation "
199
+ "in your Flower app, as shown for example below:"
200
+ "\n\n\t\t$ flwr new # Create a new Flower app from a template"
201
+ "\n\n\t\t$ flwr run # Run the Flower app in Simulation Mode"
202
+ "\n\n\tUsing `start_simulation()` is deprecated."
203
+ )
204
+ warn_deprecated_feature(name=msg)
205
+
186
206
  event(
187
207
  EventType.START_SIMULATION_ENTER,
188
208
  {"num_clients": len(clients_ids) if clients_ids is not None else num_clients},
@@ -20,7 +20,7 @@ from typing import Optional, cast
20
20
 
21
21
  import grpc
22
22
 
23
- from flwr.common.constant import SIMULATIONIO_API_DEFAULT_ADDRESS
23
+ from flwr.common.constant import SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS
24
24
  from flwr.common.grpc import create_channel
25
25
  from flwr.common.logger import log
26
26
  from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
@@ -41,7 +41,7 @@ class SimulationIoConnection:
41
41
 
42
42
  def __init__( # pylint: disable=too-many-arguments
43
43
  self,
44
- simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_ADDRESS,
44
+ simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS,
45
45
  root_certificates: Optional[bytes] = None,
46
46
  ) -> None:
47
47
  self._addr = simulationio_service_address
@@ -23,7 +23,11 @@ from typing_extensions import override
23
23
 
24
24
  from flwr.cli.config_utils import get_fab_metadata
25
25
  from flwr.common import ConfigsRecord, Context, RecordSet
26
- from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS, Status, SubStatus
26
+ from flwr.common.constant import (
27
+ SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
28
+ Status,
29
+ SubStatus,
30
+ )
27
31
  from flwr.common.logger import log
28
32
  from flwr.common.typing import Fab, RunStatus, UserConfig
29
33
  from flwr.server.superlink.ffs import Ffs
@@ -38,7 +42,7 @@ class DeploymentEngine(Executor):
38
42
 
39
43
  Parameters
40
44
  ----------
41
- superlink: str (default: "0.0.0.0:9091")
45
+ serverappio_api_address: str (default: "127.0.0.1:9091")
42
46
  Address of the SuperLink to connect to.
43
47
  root_certificates: Optional[str] (default: None)
44
48
  Specifies the path to the PEM-encoded root certificate file for
@@ -49,11 +53,11 @@ class DeploymentEngine(Executor):
49
53
 
50
54
  def __init__(
51
55
  self,
52
- superlink: str = SERVERAPPIO_API_DEFAULT_ADDRESS,
56
+ serverappio_api_address: str = SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
53
57
  root_certificates: Optional[str] = None,
54
58
  flwr_dir: Optional[str] = None,
55
59
  ) -> None:
56
- self.superlink = superlink
60
+ self.serverappio_api_address = serverappio_api_address
57
61
  if root_certificates is None:
58
62
  self.root_certificates = None
59
63
  self.root_certificates_bytes = None
@@ -110,7 +114,7 @@ class DeploymentEngine(Executor):
110
114
  if superlink_address := config.get("superlink"):
111
115
  if not isinstance(superlink_address, str):
112
116
  raise ValueError("The `superlink` value should be of type `str`.")
113
- self.superlink = superlink_address
117
+ self.serverappio_api_address = superlink_address
114
118
  if root_certificates := config.get("root-certificates"):
115
119
  if not isinstance(root_certificates, str):
116
120
  raise ValueError(
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.13.0.dev20241114
4
- Summary: Flower: A Friendly Federated Learning Framework
3
+ Version: 1.13.0.dev20241116
4
+ Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
7
7
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
@@ -42,6 +42,7 @@ Requires-Dist: protobuf (>=4.25.2,<5.0.0)
42
42
  Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
43
43
  Requires-Dist: ray (==2.10.0) ; (python_version >= "3.9" and python_version < "3.12") and (extra == "simulation")
44
44
  Requires-Dist: requests (>=2.31.0,<3.0.0) ; extra == "rest"
45
+ Requires-Dist: rich (>=13.5.0,<14.0.0)
45
46
  Requires-Dist: starlette (>=0.31.0,<0.32.0) ; extra == "rest"
46
47
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
47
48
  Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
@@ -51,7 +52,7 @@ Project-URL: Documentation, https://flower.ai
51
52
  Project-URL: Repository, https://github.com/adap/flower
52
53
  Description-Content-Type: text/markdown
53
54
 
54
- # Flower: A Friendly Federated Learning Framework
55
+ # Flower: A Friendly Federated AI Framework
55
56
 
56
57
  <p align="center">
57
58
  <a href="https://flower.ai/">
@@ -74,7 +75,7 @@ Description-Content-Type: text/markdown
74
75
  [![Docker Hub](https://img.shields.io/badge/Docker%20Hub-flwr-blue)](https://hub.docker.com/u/flwr)
75
76
  [![Slack](https://img.shields.io/badge/Chat-Slack-red)](https://flower.ai/join-slack)
76
77
 
77
- Flower (`flwr`) is a framework for building federated learning systems. The
78
+ Flower (`flwr`) is a framework for building federated AI systems. The
78
79
  design of Flower is based on a few guiding principles:
79
80
 
80
81
  - **Customizable**: Federated learning systems vary wildly from one use case to