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.
- flwr/client/app.py +39 -11
- flwr/client/clientapp/app.py +29 -22
- flwr/client/supernode/app.py +29 -17
- flwr/common/constant.py +21 -6
- flwr/server/app.py +37 -12
- flwr/server/driver/grpc_driver.py +2 -2
- flwr/server/run_serverapp.py +8 -238
- flwr/server/serverapp/app.py +33 -22
- flwr/server/superlink/linkstate/in_memory_linkstate.py +55 -28
- flwr/server/superlink/linkstate/linkstate.py +19 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +70 -111
- flwr/server/superlink/linkstate/utils.py +182 -3
- flwr/simulation/app.py +3 -3
- flwr/simulation/legacy_app.py +21 -1
- flwr/simulation/simulationio_connection.py +2 -2
- flwr/superexec/deployment.py +9 -5
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/METADATA +5 -4
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/RECORD +21 -21
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
515
|
+
"""Get TaskRes for the given TaskIns IDs."""
|
|
516
|
+
ret: dict[UUID, TaskRes] = {}
|
|
514
517
|
|
|
515
|
-
|
|
516
|
-
|
|
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 ({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
539
|
+
# Find all TaskRes
|
|
561
540
|
query = f"""
|
|
562
541
|
SELECT *
|
|
563
542
|
FROM task_res
|
|
564
|
-
WHERE ancestry IN ({
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
WHERE
|
|
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
|
|
629
|
-
|
|
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
|
-
#
|
|
633
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
142
|
+
simulationio_service_address=simulationio_api_address,
|
|
143
143
|
root_certificates=certificates,
|
|
144
144
|
)
|
|
145
145
|
|
flwr/simulation/legacy_app.py
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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
|
flwr/superexec/deployment.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
{flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/METADATA
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.13.0.
|
|
4
|
-
Summary: Flower: A Friendly Federated
|
|
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
|
|
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
|
[](https://hub.docker.com/u/flwr)
|
|
75
76
|
[](https://flower.ai/join-slack)
|
|
76
77
|
|
|
77
|
-
Flower (`flwr`) is a framework for building federated
|
|
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
|