flwr-nightly 1.20.0.dev20250718__py3-none-any.whl → 1.20.0.dev20250721__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.
- flwr/cli/build.py +1 -1
- flwr/client/grpc_adapter_client/connection.py +11 -4
- flwr/client/grpc_rere_client/connection.py +91 -93
- flwr/client/rest_client/connection.py +128 -139
- flwr/common/inflatable_utils.py +50 -12
- flwr/server/superlink/fleet/message_handler/message_handler.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +1 -2
- flwr/supercore/object_store/utils.py +48 -0
- flwr/supernode/runtime/run_clientapp.py +66 -12
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +16 -4
- flwr/supernode/start_client_internal.py +84 -21
- {flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/METADATA +1 -1
- {flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/RECORD +15 -14
- {flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/entry_points.txt +0 -0
flwr/cli/build.py
CHANGED
@@ -184,7 +184,7 @@ def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
|
|
184
184
|
# Read the file content manually
|
185
185
|
file_contents = file_path.read_bytes()
|
186
186
|
|
187
|
-
archive_path = str(file_path.relative_to(app))
|
187
|
+
archive_path = str(file_path.relative_to(app)).replace("\\", "/")
|
188
188
|
write_to_zip(fab_file, archive_path, file_contents)
|
189
189
|
|
190
190
|
# Calculate file info
|
@@ -29,6 +29,7 @@ from flwr.common.logger import log
|
|
29
29
|
from flwr.common.message import Message
|
30
30
|
from flwr.common.retry_invoker import RetryInvoker
|
31
31
|
from flwr.common.typing import Fab, Run
|
32
|
+
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
32
33
|
|
33
34
|
|
34
35
|
@contextmanager
|
@@ -43,12 +44,15 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
43
44
|
] = None,
|
44
45
|
) -> Iterator[
|
45
46
|
tuple[
|
46
|
-
Callable[[], Optional[Message]],
|
47
|
-
Callable[[Message],
|
47
|
+
Callable[[], Optional[tuple[Message, ObjectTree]]],
|
48
|
+
Callable[[Message, ObjectTree], set[str]],
|
48
49
|
Callable[[], Optional[int]],
|
49
50
|
Callable[[], None],
|
50
51
|
Callable[[int], Run],
|
51
52
|
Callable[[str, int], Fab],
|
53
|
+
Callable[[int, str], bytes],
|
54
|
+
Callable[[int, str, bytes], None],
|
55
|
+
Callable[[int, str], None],
|
52
56
|
]
|
53
57
|
]:
|
54
58
|
"""Primitives for request/response-based interaction with a server via GrpcAdapter.
|
@@ -77,12 +81,15 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
77
81
|
|
78
82
|
Returns
|
79
83
|
-------
|
80
|
-
receive : Callable
|
81
|
-
send : Callable
|
84
|
+
receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
|
85
|
+
send : Callable[[Message, ObjectTree], set[str]]
|
82
86
|
create_node : Optional[Callable]
|
83
87
|
delete_node : Optional[Callable]
|
84
88
|
get_run : Optional[Callable]
|
85
89
|
get_fab : Optional[Callable]
|
90
|
+
pull_object : Callable[[str], bytes]
|
91
|
+
push_object : Callable[[str, bytes], None]
|
92
|
+
confirm_message_received : Callable[[str], None]
|
86
93
|
"""
|
87
94
|
if authentication_keys is not None:
|
88
95
|
log(ERROR, "Client authentication is not supported for this transport type.")
|
@@ -17,7 +17,7 @@
|
|
17
17
|
|
18
18
|
from collections.abc import Iterator, Sequence
|
19
19
|
from contextlib import contextmanager
|
20
|
-
from logging import
|
20
|
+
from logging import ERROR
|
21
21
|
from pathlib import Path
|
22
22
|
from typing import Callable, Optional, Union, cast
|
23
23
|
|
@@ -28,28 +28,18 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
28
28
|
from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
|
29
29
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
30
30
|
from flwr.common.heartbeat import HeartbeatSender
|
31
|
-
from flwr.common.inflatable import (
|
32
|
-
get_all_nested_objects,
|
33
|
-
get_object_tree,
|
34
|
-
iterate_object_tree,
|
35
|
-
no_object_id_recompute,
|
36
|
-
)
|
37
31
|
from flwr.common.inflatable_protobuf_utils import (
|
32
|
+
make_confirm_message_received_fn_protobuf,
|
38
33
|
make_pull_object_fn_protobuf,
|
39
34
|
make_push_object_fn_protobuf,
|
40
35
|
)
|
41
|
-
from flwr.common.inflatable_utils import (
|
42
|
-
inflate_object_from_contents,
|
43
|
-
pull_objects,
|
44
|
-
push_objects,
|
45
|
-
)
|
46
36
|
from flwr.common.logger import log
|
47
37
|
from flwr.common.message import Message, remove_content_from_message
|
48
38
|
from flwr.common.retry_invoker import RetryInvoker, _wrap_stub
|
49
39
|
from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
|
50
40
|
generate_key_pairs,
|
51
41
|
)
|
52
|
-
from flwr.common.serde import message_to_proto, run_from_proto
|
42
|
+
from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
|
53
43
|
from flwr.common.typing import Fab, Run, RunNotRunningException
|
54
44
|
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
55
45
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
@@ -65,9 +55,7 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
65
55
|
SendNodeHeartbeatRequest,
|
66
56
|
SendNodeHeartbeatResponse,
|
67
57
|
)
|
68
|
-
from flwr.proto.message_pb2 import
|
69
|
-
ConfirmMessageReceivedRequest,
|
70
|
-
)
|
58
|
+
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
71
59
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
72
60
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
73
61
|
|
@@ -88,12 +76,15 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
88
76
|
adapter_cls: Optional[Union[type[FleetStub], type[GrpcAdapter]]] = None,
|
89
77
|
) -> Iterator[
|
90
78
|
tuple[
|
91
|
-
Callable[[], Optional[Message]],
|
92
|
-
Callable[[Message],
|
79
|
+
Callable[[], Optional[tuple[Message, ObjectTree]]],
|
80
|
+
Callable[[Message, ObjectTree], set[str]],
|
93
81
|
Callable[[], Optional[int]],
|
94
82
|
Callable[[], None],
|
95
83
|
Callable[[int], Run],
|
96
84
|
Callable[[str, int], Fab],
|
85
|
+
Callable[[int, str], bytes],
|
86
|
+
Callable[[int, str, bytes], None],
|
87
|
+
Callable[[int, str], None],
|
97
88
|
]
|
98
89
|
]:
|
99
90
|
"""Primitives for request/response-based interaction with a server.
|
@@ -136,6 +127,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
136
127
|
create_node : Optional[Callable]
|
137
128
|
delete_node : Optional[Callable]
|
138
129
|
get_run : Optional[Callable]
|
130
|
+
pull_object : Callable[[str], bytes]
|
131
|
+
push_object : Callable[[str, bytes], None]
|
132
|
+
confirm_message_received : Callable[[str], None]
|
139
133
|
"""
|
140
134
|
if isinstance(root_certificates, str):
|
141
135
|
root_certificates = Path(root_certificates).read_bytes()
|
@@ -246,98 +240,53 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
246
240
|
# Cleanup
|
247
241
|
node = None
|
248
242
|
|
249
|
-
def receive() -> Optional[Message]:
|
250
|
-
"""
|
243
|
+
def receive() -> Optional[tuple[Message, ObjectTree]]:
|
244
|
+
"""Pull a message with its ObjectTree from SuperLink."""
|
251
245
|
# Get Node
|
252
246
|
if node is None:
|
253
247
|
log(ERROR, "Node instance missing")
|
254
248
|
return None
|
255
249
|
|
256
|
-
#
|
250
|
+
# Try to pull a message with its object tree from SuperLink
|
257
251
|
request = PullMessagesRequest(node=node)
|
258
252
|
response: PullMessagesResponse = stub.PullMessages(request=request)
|
259
253
|
|
260
|
-
#
|
261
|
-
|
262
|
-
None
|
263
|
-
)
|
254
|
+
# If no messages are available, return None
|
255
|
+
if len(response.messages_list) == 0:
|
256
|
+
return None
|
264
257
|
|
265
|
-
#
|
266
|
-
|
267
|
-
|
268
|
-
):
|
269
|
-
message_proto = None
|
258
|
+
# Get the current Message and its object tree
|
259
|
+
message_proto = response.messages_list[0]
|
260
|
+
object_tree = response.message_object_trees[0]
|
270
261
|
|
271
262
|
# Construct the Message
|
272
|
-
in_message
|
273
|
-
|
274
|
-
if message_proto:
|
275
|
-
msg_id = message_proto.metadata.message_id
|
276
|
-
run_id = message_proto.metadata.run_id
|
277
|
-
object_tree = response.message_object_trees[0]
|
278
|
-
all_object_contents = pull_objects(
|
279
|
-
[tree.object_id for tree in iterate_object_tree(object_tree)],
|
280
|
-
pull_object_fn=make_pull_object_fn_protobuf(
|
281
|
-
pull_object_protobuf=stub.PullObject,
|
282
|
-
node=node,
|
283
|
-
run_id=run_id,
|
284
|
-
),
|
285
|
-
)
|
286
|
-
|
287
|
-
# Confirm that the message has been received
|
288
|
-
stub.ConfirmMessageReceived(
|
289
|
-
ConfirmMessageReceivedRequest(
|
290
|
-
node=node, run_id=run_id, message_object_id=msg_id
|
291
|
-
)
|
292
|
-
)
|
293
|
-
|
294
|
-
in_message = cast(
|
295
|
-
Message, inflate_object_from_contents(msg_id, all_object_contents)
|
296
|
-
)
|
297
|
-
# The deflated message doesn't contain the message_id (its own object_id)
|
298
|
-
# Inject
|
299
|
-
in_message.metadata.__dict__["_message_id"] = msg_id
|
263
|
+
in_message = message_from_proto(message_proto)
|
300
264
|
|
301
|
-
# Return the
|
302
|
-
return in_message
|
265
|
+
# Return the Message and its object tree
|
266
|
+
return in_message, object_tree
|
303
267
|
|
304
|
-
def send(message: Message) ->
|
305
|
-
"""Send message
|
268
|
+
def send(message: Message, object_tree: ObjectTree) -> set[str]:
|
269
|
+
"""Send the message with its ObjectTree to SuperLink."""
|
306
270
|
# Get Node
|
307
271
|
if node is None:
|
308
272
|
log(ERROR, "Node instance missing")
|
309
|
-
return
|
273
|
+
return set()
|
310
274
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
object_tree = get_object_tree(message)
|
275
|
+
# Remove the content from the message if it has
|
276
|
+
if message.has_content():
|
277
|
+
message = remove_content_from_message(message)
|
315
278
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
)
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
if response.objects_to_push:
|
328
|
-
objs_to_push = set(
|
329
|
-
response.objects_to_push[message.object_id].object_ids
|
330
|
-
)
|
331
|
-
push_objects(
|
332
|
-
all_objects,
|
333
|
-
push_object_fn=make_push_object_fn_protobuf(
|
334
|
-
push_object_protobuf=stub.PushObject,
|
335
|
-
node=node,
|
336
|
-
run_id=message.metadata.run_id,
|
337
|
-
),
|
338
|
-
object_ids_to_push=objs_to_push,
|
339
|
-
)
|
340
|
-
log(DEBUG, "Pushed %s objects to servicer.", len(objs_to_push))
|
279
|
+
# Send the message with its ObjectTree to SuperLink
|
280
|
+
request = PushMessagesRequest(
|
281
|
+
node=node,
|
282
|
+
messages_list=[message_to_proto(message)],
|
283
|
+
message_object_trees=[object_tree],
|
284
|
+
)
|
285
|
+
response: PushMessagesResponse = stub.PushMessages(request=request)
|
286
|
+
|
287
|
+
# Get and return the object IDs to push
|
288
|
+
object_ids_to_push = response.objects_to_push[object_tree.object_id]
|
289
|
+
return set(object_ids_to_push.object_ids)
|
341
290
|
|
342
291
|
def get_run(run_id: int) -> Run:
|
343
292
|
# Call FleetAPI
|
@@ -354,9 +303,58 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
354
303
|
|
355
304
|
return Fab(get_fab_response.fab.hash_str, get_fab_response.fab.content)
|
356
305
|
|
306
|
+
def pull_object(run_id: int, object_id: str) -> bytes:
|
307
|
+
"""Pull the object from the SuperLink."""
|
308
|
+
# Check Node
|
309
|
+
if node is None:
|
310
|
+
raise RuntimeError("Node instance missing")
|
311
|
+
|
312
|
+
fn = make_pull_object_fn_protobuf(
|
313
|
+
pull_object_protobuf=stub.PullObject,
|
314
|
+
node=node,
|
315
|
+
run_id=run_id,
|
316
|
+
)
|
317
|
+
return fn(object_id)
|
318
|
+
|
319
|
+
def push_object(run_id: int, object_id: str, contents: bytes) -> None:
|
320
|
+
"""Push the object to the SuperLink."""
|
321
|
+
# Check Node
|
322
|
+
if node is None:
|
323
|
+
raise RuntimeError("Node instance missing")
|
324
|
+
|
325
|
+
fn = make_push_object_fn_protobuf(
|
326
|
+
push_object_protobuf=stub.PushObject,
|
327
|
+
node=node,
|
328
|
+
run_id=run_id,
|
329
|
+
)
|
330
|
+
fn(object_id, contents)
|
331
|
+
|
332
|
+
def confirm_message_received(run_id: int, object_id: str) -> None:
|
333
|
+
"""Confirm that the message has been received."""
|
334
|
+
# Check Node
|
335
|
+
if node is None:
|
336
|
+
raise RuntimeError("Node instance missing")
|
337
|
+
|
338
|
+
fn = make_confirm_message_received_fn_protobuf(
|
339
|
+
confirm_message_received_protobuf=stub.ConfirmMessageReceived,
|
340
|
+
node=node,
|
341
|
+
run_id=run_id,
|
342
|
+
)
|
343
|
+
fn(object_id)
|
344
|
+
|
357
345
|
try:
|
358
346
|
# Yield methods
|
359
|
-
yield (
|
347
|
+
yield (
|
348
|
+
receive,
|
349
|
+
send,
|
350
|
+
create_node,
|
351
|
+
delete_node,
|
352
|
+
get_run,
|
353
|
+
get_fab,
|
354
|
+
pull_object,
|
355
|
+
push_object,
|
356
|
+
confirm_message_received,
|
357
|
+
)
|
360
358
|
except Exception as exc: # pylint: disable=broad-except
|
361
359
|
log(ERROR, exc)
|
362
360
|
# Cleanup
|
@@ -17,8 +17,8 @@
|
|
17
17
|
|
18
18
|
from collections.abc import Iterator
|
19
19
|
from contextlib import contextmanager
|
20
|
-
from logging import
|
21
|
-
from typing import Callable, Optional, TypeVar, Union
|
20
|
+
from logging import ERROR, WARN
|
21
|
+
from typing import Callable, Optional, TypeVar, Union
|
22
22
|
|
23
23
|
from cryptography.hazmat.primitives.asymmetric import ec
|
24
24
|
from google.protobuf.message import Message as GrpcMessage
|
@@ -28,25 +28,15 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
28
28
|
from flwr.common.constant import HEARTBEAT_DEFAULT_INTERVAL
|
29
29
|
from flwr.common.exit import ExitCode, flwr_exit
|
30
30
|
from flwr.common.heartbeat import HeartbeatSender
|
31
|
-
from flwr.common.inflatable import (
|
32
|
-
get_all_nested_objects,
|
33
|
-
get_object_tree,
|
34
|
-
iterate_object_tree,
|
35
|
-
no_object_id_recompute,
|
36
|
-
)
|
37
31
|
from flwr.common.inflatable_protobuf_utils import (
|
32
|
+
make_confirm_message_received_fn_protobuf,
|
38
33
|
make_pull_object_fn_protobuf,
|
39
34
|
make_push_object_fn_protobuf,
|
40
35
|
)
|
41
|
-
from flwr.common.inflatable_utils import (
|
42
|
-
inflate_object_from_contents,
|
43
|
-
pull_objects,
|
44
|
-
push_objects,
|
45
|
-
)
|
46
36
|
from flwr.common.logger import log
|
47
37
|
from flwr.common.message import Message, remove_content_from_message
|
48
38
|
from flwr.common.retry_invoker import RetryInvoker
|
49
|
-
from flwr.common.serde import message_to_proto, run_from_proto
|
39
|
+
from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
|
50
40
|
from flwr.common.typing import Fab, Run
|
51
41
|
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
52
42
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
@@ -66,6 +56,7 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
66
56
|
from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
67
57
|
ConfirmMessageReceivedRequest,
|
68
58
|
ConfirmMessageReceivedResponse,
|
59
|
+
ObjectTree,
|
69
60
|
PullObjectRequest,
|
70
61
|
PullObjectResponse,
|
71
62
|
PushObjectRequest,
|
@@ -108,12 +99,15 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
108
99
|
] = None,
|
109
100
|
) -> Iterator[
|
110
101
|
tuple[
|
111
|
-
Callable[[], Optional[Message]],
|
112
|
-
Callable[[Message],
|
102
|
+
Callable[[], Optional[tuple[Message, ObjectTree]]],
|
103
|
+
Callable[[Message, ObjectTree], set[str]],
|
113
104
|
Callable[[], Optional[int]],
|
114
105
|
Callable[[], None],
|
115
106
|
Callable[[int], Run],
|
116
107
|
Callable[[str, int], Fab],
|
108
|
+
Callable[[int, str], bytes],
|
109
|
+
Callable[[int, str, bytes], None],
|
110
|
+
Callable[[int, str], None],
|
117
111
|
]
|
118
112
|
]:
|
119
113
|
"""Primitives for request/response-based interaction with a server.
|
@@ -149,6 +143,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
149
143
|
create_node : Optional[Callable]
|
150
144
|
delete_node : Optional[Callable]
|
151
145
|
get_run : Optional[Callable]
|
146
|
+
pull_object : Callable[[str], bytes]
|
147
|
+
push_object : Callable[[str, bytes], None]
|
148
|
+
confirm_message_received : Callable[[str], None]
|
152
149
|
"""
|
153
150
|
log(
|
154
151
|
WARN,
|
@@ -230,6 +227,38 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
230
227
|
grpc_res.ParseFromString(res.content)
|
231
228
|
return grpc_res
|
232
229
|
|
230
|
+
def _pull_object_protobuf(request: PullObjectRequest) -> PullObjectResponse:
|
231
|
+
res = _request(
|
232
|
+
req=request,
|
233
|
+
res_type=PullObjectResponse,
|
234
|
+
api_path=PATH_PULL_OBJECT,
|
235
|
+
)
|
236
|
+
if res is None:
|
237
|
+
raise ValueError(f"{PullObjectResponse.__name__} is None.")
|
238
|
+
return res
|
239
|
+
|
240
|
+
def _push_object_protobuf(request: PushObjectRequest) -> PushObjectResponse:
|
241
|
+
res = _request(
|
242
|
+
req=request,
|
243
|
+
res_type=PushObjectResponse,
|
244
|
+
api_path=PATH_PUSH_OBJECT,
|
245
|
+
)
|
246
|
+
if res is None:
|
247
|
+
raise ValueError(f"{PushObjectResponse.__name__} is None.")
|
248
|
+
return res
|
249
|
+
|
250
|
+
def _confirm_message_received_protobuf(
|
251
|
+
request: ConfirmMessageReceivedRequest,
|
252
|
+
) -> ConfirmMessageReceivedResponse:
|
253
|
+
res = _request(
|
254
|
+
req=request,
|
255
|
+
res_type=ConfirmMessageReceivedResponse,
|
256
|
+
api_path=PATH_CONFIRM_MESSAGE_RECEIVED,
|
257
|
+
)
|
258
|
+
if res is None:
|
259
|
+
raise ValueError(f"{ConfirmMessageReceivedResponse.__name__} is None.")
|
260
|
+
return res
|
261
|
+
|
233
262
|
def send_node_heartbeat() -> bool:
|
234
263
|
# Get Node
|
235
264
|
if node is None:
|
@@ -277,8 +306,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
277
306
|
"""Set delete_node."""
|
278
307
|
nonlocal node
|
279
308
|
if node is None:
|
280
|
-
|
281
|
-
return
|
309
|
+
raise RuntimeError("Node instance missing")
|
282
310
|
|
283
311
|
# Stop the heartbeat sender
|
284
312
|
heartbeat_sender.stop()
|
@@ -294,143 +322,55 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
294
322
|
# Cleanup
|
295
323
|
node = None
|
296
324
|
|
297
|
-
def receive() -> Optional[Message]:
|
298
|
-
"""
|
325
|
+
def receive() -> Optional[tuple[Message, ObjectTree]]:
|
326
|
+
"""Pull a message with its ObjectTree from SuperLink."""
|
299
327
|
# Get Node
|
300
328
|
if node is None:
|
301
|
-
|
302
|
-
return None
|
329
|
+
raise RuntimeError("Node instance missing")
|
303
330
|
|
304
|
-
#
|
331
|
+
# Try to pull a message with its object tree from SuperLink
|
305
332
|
req = PullMessagesRequest(node=node)
|
306
|
-
|
307
|
-
# Send the request
|
308
333
|
res = _request(req, PullMessagesResponse, PATH_PULL_MESSAGES)
|
309
334
|
if res is None:
|
310
|
-
|
335
|
+
raise ValueError("PushMessagesResponse is None.")
|
311
336
|
|
312
|
-
#
|
313
|
-
|
337
|
+
# If no messages are available, return None
|
338
|
+
if len(res.messages_list) == 0:
|
339
|
+
return None
|
314
340
|
|
315
|
-
#
|
316
|
-
|
317
|
-
|
318
|
-
):
|
319
|
-
message_proto = None
|
341
|
+
# Get the current Message and its object tree
|
342
|
+
message_proto = res.messages_list[0]
|
343
|
+
object_tree = res.message_object_trees[0]
|
320
344
|
|
321
345
|
# Construct the Message
|
322
|
-
in_message
|
323
|
-
|
324
|
-
if message_proto:
|
325
|
-
log(INFO, "[Node] POST /%s: success", PATH_PULL_MESSAGES)
|
326
|
-
msg_id = message_proto.metadata.message_id
|
327
|
-
run_id = message_proto.metadata.run_id
|
328
|
-
|
329
|
-
def fn(request: PullObjectRequest) -> PullObjectResponse:
|
330
|
-
res = _request(
|
331
|
-
req=request, res_type=PullObjectResponse, api_path=PATH_PULL_OBJECT
|
332
|
-
)
|
333
|
-
if res is None:
|
334
|
-
raise ValueError("PushObjectResponse is None.")
|
335
|
-
return res
|
336
|
-
|
337
|
-
try:
|
338
|
-
object_tree = res.message_object_trees[0]
|
339
|
-
all_object_contents = pull_objects(
|
340
|
-
[tree.object_id for tree in iterate_object_tree(object_tree)],
|
341
|
-
pull_object_fn=make_pull_object_fn_protobuf(
|
342
|
-
pull_object_protobuf=fn,
|
343
|
-
node=node,
|
344
|
-
run_id=run_id,
|
345
|
-
),
|
346
|
-
)
|
347
|
-
|
348
|
-
# Confirm that the message has been received
|
349
|
-
_request(
|
350
|
-
req=ConfirmMessageReceivedRequest(
|
351
|
-
node=node, run_id=run_id, message_object_id=msg_id
|
352
|
-
),
|
353
|
-
res_type=ConfirmMessageReceivedResponse,
|
354
|
-
api_path=PATH_CONFIRM_MESSAGE_RECEIVED,
|
355
|
-
)
|
356
|
-
except ValueError as e:
|
357
|
-
log(
|
358
|
-
ERROR,
|
359
|
-
"Pulling objects failed. Potential irrecoverable error: %s",
|
360
|
-
str(e),
|
361
|
-
)
|
362
|
-
in_message = cast(
|
363
|
-
Message, inflate_object_from_contents(msg_id, all_object_contents)
|
364
|
-
)
|
365
|
-
# The deflated message doesn't contain the message_id (its own object_id)
|
366
|
-
# Inject
|
367
|
-
in_message.metadata.__dict__["_message_id"] = msg_id
|
346
|
+
in_message = message_from_proto(message_proto)
|
368
347
|
|
369
|
-
|
348
|
+
# Return the Message and its object tree
|
349
|
+
return in_message, object_tree
|
370
350
|
|
371
|
-
def send(message: Message) ->
|
372
|
-
"""Send
|
351
|
+
def send(message: Message, object_tree: ObjectTree) -> set[str]:
|
352
|
+
"""Send the message with its ObjectTree to SuperLink."""
|
373
353
|
# Get Node
|
374
354
|
if node is None:
|
375
|
-
|
376
|
-
return
|
355
|
+
raise RuntimeError("Node instance missing")
|
377
356
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
object_tree = get_object_tree(message)
|
357
|
+
# Remove the content from the message if it has
|
358
|
+
if message.has_content():
|
359
|
+
message = remove_content_from_message(message)
|
382
360
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
)
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
)
|
361
|
+
# Send the message with its ObjectTree to SuperLink
|
362
|
+
req = PushMessagesRequest(
|
363
|
+
node=node,
|
364
|
+
messages_list=[message_to_proto(message)],
|
365
|
+
message_object_trees=[object_tree],
|
366
|
+
)
|
367
|
+
res = _request(req, PushMessagesResponse, PATH_PUSH_MESSAGES)
|
368
|
+
if res is None:
|
369
|
+
raise ValueError("PushMessagesResponse is None.")
|
392
370
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
log(
|
397
|
-
INFO,
|
398
|
-
"[Node] POST /%s: success, created result %s",
|
399
|
-
PATH_PUSH_MESSAGES,
|
400
|
-
res.results, # pylint: disable=no-member
|
401
|
-
)
|
402
|
-
|
403
|
-
if res and res.objects_to_push:
|
404
|
-
objs_to_push = set(res.objects_to_push[message.object_id].object_ids)
|
405
|
-
|
406
|
-
def fn(request: PushObjectRequest) -> PushObjectResponse:
|
407
|
-
res = _request(
|
408
|
-
req=request,
|
409
|
-
res_type=PushObjectResponse,
|
410
|
-
api_path=PATH_PUSH_OBJECT,
|
411
|
-
)
|
412
|
-
if res is None:
|
413
|
-
raise ValueError("PushObjectResponse is None.")
|
414
|
-
return res
|
415
|
-
|
416
|
-
try:
|
417
|
-
push_objects(
|
418
|
-
all_objects,
|
419
|
-
push_object_fn=make_push_object_fn_protobuf(
|
420
|
-
push_object_protobuf=fn,
|
421
|
-
node=node,
|
422
|
-
run_id=message_proto.metadata.run_id,
|
423
|
-
),
|
424
|
-
object_ids_to_push=objs_to_push,
|
425
|
-
)
|
426
|
-
log(DEBUG, "Pushed %s objects to servicer.", len(objs_to_push))
|
427
|
-
except ValueError as e:
|
428
|
-
log(
|
429
|
-
ERROR,
|
430
|
-
"Pushing objects failed. Potential irrecoverable error: %s",
|
431
|
-
str(e),
|
432
|
-
)
|
433
|
-
log(ERROR, str(e))
|
371
|
+
# Get and return the object IDs to push
|
372
|
+
object_ids_to_push = res.objects_to_push[object_tree.object_id]
|
373
|
+
return set(object_ids_to_push.object_ids)
|
434
374
|
|
435
375
|
def get_run(run_id: int) -> Run:
|
436
376
|
# Construct the request
|
@@ -457,9 +397,58 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
457
397
|
res.fab.content,
|
458
398
|
)
|
459
399
|
|
400
|
+
def pull_object(run_id: int, object_id: str) -> bytes:
|
401
|
+
"""Pull the object from the SuperLink."""
|
402
|
+
# Check Node
|
403
|
+
if node is None:
|
404
|
+
raise RuntimeError("Node instance missing")
|
405
|
+
|
406
|
+
fn = make_pull_object_fn_protobuf(
|
407
|
+
pull_object_protobuf=_pull_object_protobuf,
|
408
|
+
node=node,
|
409
|
+
run_id=run_id,
|
410
|
+
)
|
411
|
+
return fn(object_id)
|
412
|
+
|
413
|
+
def push_object(run_id: int, object_id: str, contents: bytes) -> None:
|
414
|
+
"""Push the object to the SuperLink."""
|
415
|
+
# Check Node
|
416
|
+
if node is None:
|
417
|
+
raise RuntimeError("Node instance missing")
|
418
|
+
|
419
|
+
fn = make_push_object_fn_protobuf(
|
420
|
+
push_object_protobuf=_push_object_protobuf,
|
421
|
+
node=node,
|
422
|
+
run_id=run_id,
|
423
|
+
)
|
424
|
+
fn(object_id, contents)
|
425
|
+
|
426
|
+
def confirm_message_received(run_id: int, object_id: str) -> None:
|
427
|
+
"""Confirm that the message has been received."""
|
428
|
+
# Check Node
|
429
|
+
if node is None:
|
430
|
+
raise RuntimeError("Node instance missing")
|
431
|
+
|
432
|
+
fn = make_confirm_message_received_fn_protobuf(
|
433
|
+
confirm_message_received_protobuf=_confirm_message_received_protobuf,
|
434
|
+
node=node,
|
435
|
+
run_id=run_id,
|
436
|
+
)
|
437
|
+
fn(object_id)
|
438
|
+
|
460
439
|
try:
|
461
440
|
# Yield methods
|
462
|
-
yield (
|
441
|
+
yield (
|
442
|
+
receive,
|
443
|
+
send,
|
444
|
+
create_node,
|
445
|
+
delete_node,
|
446
|
+
get_run,
|
447
|
+
get_fab,
|
448
|
+
pull_object,
|
449
|
+
push_object,
|
450
|
+
confirm_message_received,
|
451
|
+
)
|
463
452
|
except Exception as exc: # pylint: disable=broad-except
|
464
453
|
log(ERROR, exc)
|
465
454
|
# Cleanup
|
flwr/common/inflatable_utils.py
CHANGED
@@ -19,6 +19,7 @@ import os
|
|
19
19
|
import random
|
20
20
|
import threading
|
21
21
|
import time
|
22
|
+
from collections.abc import Iterable, Iterator
|
22
23
|
from typing import Callable, Optional, TypeVar
|
23
24
|
|
24
25
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
@@ -110,24 +111,61 @@ def push_objects(
|
|
110
111
|
max_concurrent_pushes : int (default: MAX_CONCURRENT_PUSHES)
|
111
112
|
The maximum number of concurrent pushes to perform.
|
112
113
|
"""
|
113
|
-
if object_ids_to_push is not None:
|
114
|
-
# Filter objects to push only those with IDs in the set
|
115
|
-
objects = {k: v for k, v in objects.items() if k in object_ids_to_push}
|
116
|
-
|
117
114
|
lock = threading.Lock()
|
118
115
|
|
119
|
-
def
|
116
|
+
def iter_dict_items() -> Iterator[tuple[str, bytes]]:
|
117
|
+
"""Iterate over the dictionary items."""
|
118
|
+
for obj_id in list(objects.keys()):
|
119
|
+
# Skip the object if no need to push it
|
120
|
+
if object_ids_to_push is not None and obj_id not in object_ids_to_push:
|
121
|
+
continue
|
122
|
+
|
123
|
+
# Deflate the object content
|
124
|
+
object_content = objects[obj_id].deflate()
|
125
|
+
if not keep_objects:
|
126
|
+
with lock:
|
127
|
+
del objects[obj_id]
|
128
|
+
|
129
|
+
yield obj_id, object_content
|
130
|
+
|
131
|
+
push_object_contents_from_iterable(
|
132
|
+
iter_dict_items(),
|
133
|
+
push_object_fn,
|
134
|
+
max_concurrent_pushes=max_concurrent_pushes,
|
135
|
+
)
|
136
|
+
|
137
|
+
|
138
|
+
def push_object_contents_from_iterable(
|
139
|
+
object_contents: Iterable[tuple[str, bytes]],
|
140
|
+
push_object_fn: Callable[[str, bytes], None],
|
141
|
+
*,
|
142
|
+
max_concurrent_pushes: int = MAX_CONCURRENT_PUSHES,
|
143
|
+
) -> None:
|
144
|
+
"""Push multiple object contents to the servicer.
|
145
|
+
|
146
|
+
Parameters
|
147
|
+
----------
|
148
|
+
object_contents : Iterable[tuple[str, bytes]]
|
149
|
+
An iterable of `(object_id, object_content)` pairs.
|
150
|
+
`object_id` is the object ID, and `object_content` is the object content.
|
151
|
+
push_object_fn : Callable[[str, bytes], None]
|
152
|
+
A function that takes an object ID and its content as bytes, and pushes
|
153
|
+
it to the servicer. This function should raise `ObjectIdNotPreregisteredError`
|
154
|
+
if the object ID is not pre-registered.
|
155
|
+
max_concurrent_pushes : int (default: MAX_CONCURRENT_PUSHES)
|
156
|
+
The maximum number of concurrent pushes to perform.
|
157
|
+
"""
|
158
|
+
|
159
|
+
def push(args: tuple[str, bytes]) -> None:
|
120
160
|
"""Push a single object."""
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
del objects[obj_id]
|
125
|
-
push_object_fn(obj_id, object_content)
|
161
|
+
obj_id, obj_content = args
|
162
|
+
# Push the object using the provided function
|
163
|
+
push_object_fn(obj_id, obj_content)
|
126
164
|
|
127
|
-
# Push all
|
165
|
+
# Push all object contents concurrently
|
128
166
|
num_workers = get_num_workers(max_concurrent_pushes)
|
129
167
|
with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:
|
130
|
-
list(executor.map(push,
|
168
|
+
list(executor.map(push, object_contents))
|
131
169
|
|
132
170
|
|
133
171
|
def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
@@ -61,8 +61,7 @@ from flwr.server.superlink.linkstate import LinkState
|
|
61
61
|
from flwr.server.superlink.utils import check_abort
|
62
62
|
from flwr.supercore.ffs import Ffs
|
63
63
|
from flwr.supercore.object_store import NoObjectInStoreError, ObjectStore
|
64
|
-
|
65
|
-
from ...utils import store_mapping_and_register_objects
|
64
|
+
from flwr.supercore.object_store.utils import store_mapping_and_register_objects
|
66
65
|
|
67
66
|
|
68
67
|
def create_node(
|
@@ -87,8 +87,7 @@ from flwr.server.superlink.utils import abort_if
|
|
87
87
|
from flwr.server.utils.validator import validate_message
|
88
88
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
89
89
|
from flwr.supercore.object_store import NoObjectInStoreError, ObjectStoreFactory
|
90
|
-
|
91
|
-
from ..utils import store_mapping_and_register_objects
|
90
|
+
from flwr.supercore.object_store.utils import store_mapping_and_register_objects
|
92
91
|
|
93
92
|
|
94
93
|
class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Copyright 2025 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
|
+
"""Utils for ObjectStore."""
|
16
|
+
|
17
|
+
|
18
|
+
from typing import Union
|
19
|
+
|
20
|
+
from flwr.proto.appio_pb2 import PushAppMessagesRequest # pylint: disable=E0611
|
21
|
+
from flwr.proto.fleet_pb2 import PushMessagesRequest # pylint: disable=E0611
|
22
|
+
from flwr.proto.message_pb2 import ObjectIDs # pylint: disable=E0611
|
23
|
+
|
24
|
+
from . import ObjectStore
|
25
|
+
|
26
|
+
|
27
|
+
def store_mapping_and_register_objects(
|
28
|
+
store: ObjectStore, request: Union[PushAppMessagesRequest, PushMessagesRequest]
|
29
|
+
) -> dict[str, ObjectIDs]:
|
30
|
+
"""Store Message object to descendants mapping and preregister objects."""
|
31
|
+
if not request.messages_list:
|
32
|
+
return {}
|
33
|
+
|
34
|
+
objects_to_push: dict[str, ObjectIDs] = {}
|
35
|
+
|
36
|
+
# Get run_id from the first message in the list
|
37
|
+
# All messages of a request should in the same run
|
38
|
+
run_id = request.messages_list[0].metadata.run_id
|
39
|
+
|
40
|
+
for object_tree in request.message_object_trees:
|
41
|
+
# Preregister
|
42
|
+
object_ids_just_registered = store.preregister(run_id, object_tree)
|
43
|
+
# Keep track of objects that need to be pushed
|
44
|
+
objects_to_push[object_tree.object_id] = ObjectIDs(
|
45
|
+
object_ids=object_ids_just_registered
|
46
|
+
)
|
47
|
+
|
48
|
+
return objects_to_push
|
@@ -32,13 +32,24 @@ from flwr.common import Context, Message
|
|
32
32
|
from flwr.common.config import get_flwr_dir
|
33
33
|
from flwr.common.constant import ErrorCode
|
34
34
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
35
|
+
from flwr.common.inflatable import (
|
36
|
+
get_all_nested_objects,
|
37
|
+
get_object_tree,
|
38
|
+
no_object_id_recompute,
|
39
|
+
)
|
40
|
+
from flwr.common.inflatable_protobuf_utils import (
|
41
|
+
make_confirm_message_received_fn_protobuf,
|
42
|
+
make_pull_object_fn_protobuf,
|
43
|
+
make_push_object_fn_protobuf,
|
44
|
+
)
|
45
|
+
from flwr.common.inflatable_utils import pull_and_inflate_object_from_tree, push_objects
|
35
46
|
from flwr.common.logger import log
|
47
|
+
from flwr.common.message import remove_content_from_message
|
36
48
|
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
37
49
|
from flwr.common.serde import (
|
38
50
|
context_from_proto,
|
39
51
|
context_to_proto,
|
40
52
|
fab_from_proto,
|
41
|
-
message_from_proto,
|
42
53
|
message_to_proto,
|
43
54
|
run_from_proto,
|
44
55
|
)
|
@@ -61,6 +72,7 @@ from flwr.proto.clientappio_pb2 import (
|
|
61
72
|
RequestTokenResponse,
|
62
73
|
)
|
63
74
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
75
|
+
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
64
76
|
from flwr.supercore.utils import mask_string
|
65
77
|
|
66
78
|
|
@@ -204,12 +216,6 @@ def pull_clientappinputs(
|
|
204
216
|
masked_token = mask_string(token)
|
205
217
|
log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
|
206
218
|
try:
|
207
|
-
# Pull Message
|
208
|
-
pull_msg_res: PullAppMessagesResponse = stub.PullMessage(
|
209
|
-
PullAppMessagesRequest(token=token)
|
210
|
-
)
|
211
|
-
message = message_from_proto(pull_msg_res.messages_list[0])
|
212
|
-
|
213
219
|
# Pull Context, Run and (optional) FAB
|
214
220
|
res: PullAppInputsResponse = stub.PullClientAppInputs(
|
215
221
|
PullAppInputsRequest(token=token)
|
@@ -217,6 +223,26 @@ def pull_clientappinputs(
|
|
217
223
|
context = context_from_proto(res.context)
|
218
224
|
run = run_from_proto(res.run)
|
219
225
|
fab = fab_from_proto(res.fab) if res.fab else None
|
226
|
+
|
227
|
+
# Pull and inflate the message
|
228
|
+
pull_msg_res: PullAppMessagesResponse = stub.PullMessage(
|
229
|
+
PullAppMessagesRequest(token=token)
|
230
|
+
)
|
231
|
+
run_id = context.run_id
|
232
|
+
node = Node(node_id=context.node_id)
|
233
|
+
object_tree = pull_msg_res.message_object_trees[0]
|
234
|
+
message = pull_and_inflate_object_from_tree(
|
235
|
+
object_tree,
|
236
|
+
make_pull_object_fn_protobuf(stub.PullObject, node, run_id),
|
237
|
+
make_confirm_message_received_fn_protobuf(
|
238
|
+
stub.ConfirmMessageReceived, node, run_id
|
239
|
+
),
|
240
|
+
return_type=Message,
|
241
|
+
)
|
242
|
+
|
243
|
+
# Set the message ID
|
244
|
+
# The deflated message doesn't contain the message_id (its own object_id)
|
245
|
+
message.metadata.__dict__["_message_id"] = object_tree.object_id
|
220
246
|
return message, context, run, fab
|
221
247
|
except grpc.RpcError as e:
|
222
248
|
log(ERROR, "[PullClientAppInputs] gRPC error occurred: %s", str(e))
|
@@ -231,15 +257,43 @@ def push_clientappoutputs(
|
|
231
257
|
log(INFO, "[flwr-clientapp] Push `ClientAppOutputs` for token %s", masked_token)
|
232
258
|
# Set message ID
|
233
259
|
message.metadata.__dict__["_message_id"] = message.object_id
|
234
|
-
proto_message = message_to_proto(message)
|
260
|
+
proto_message = message_to_proto(remove_content_from_message(message))
|
235
261
|
proto_context = context_to_proto(context)
|
236
262
|
|
237
263
|
try:
|
238
264
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
265
|
+
with no_object_id_recompute():
|
266
|
+
# Get object tree and all objects to push
|
267
|
+
object_tree = get_object_tree(message)
|
268
|
+
|
269
|
+
# Push Message
|
270
|
+
# This is temporary. The message should not contain its content
|
271
|
+
push_msg_res = stub.PushMessage(
|
272
|
+
PushAppMessagesRequest(
|
273
|
+
token=token,
|
274
|
+
messages_list=[proto_message],
|
275
|
+
message_object_trees=[object_tree],
|
276
|
+
)
|
277
|
+
)
|
278
|
+
del proto_message
|
279
|
+
|
280
|
+
# Retrieve the object IDs to push
|
281
|
+
object_ids_to_push = set(
|
282
|
+
push_msg_res.objects_to_push[object_tree.object_id].object_ids
|
283
|
+
)
|
284
|
+
|
285
|
+
# Push all objects
|
286
|
+
all_objects = get_all_nested_objects(message)
|
287
|
+
del message
|
288
|
+
push_objects(
|
289
|
+
all_objects,
|
290
|
+
make_push_object_fn_protobuf(
|
291
|
+
stub.PushObject,
|
292
|
+
Node(node_id=context.node_id),
|
293
|
+
run_id=context.run_id,
|
294
|
+
),
|
295
|
+
object_ids_to_push=object_ids_to_push,
|
296
|
+
)
|
243
297
|
|
244
298
|
# Push Context
|
245
299
|
res: PushAppOutputsResponse = stub.PushClientAppOutputs(
|
@@ -63,6 +63,7 @@ from flwr.proto.message_pb2 import (
|
|
63
63
|
# pylint: disable=E0601
|
64
64
|
from flwr.supercore.ffs import FfsFactory
|
65
65
|
from flwr.supercore.object_store import NoObjectInStoreError, ObjectStoreFactory
|
66
|
+
from flwr.supercore.object_store.utils import store_mapping_and_register_objects
|
66
67
|
from flwr.supernode.nodestate import NodeStateFactory
|
67
68
|
|
68
69
|
|
@@ -178,8 +179,9 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
178
179
|
self, request: PullAppMessagesRequest, context: grpc.ServicerContext
|
179
180
|
) -> PullAppMessagesResponse:
|
180
181
|
"""Pull one Message."""
|
181
|
-
# Initialize state and
|
182
|
+
# Initialize state and store connection
|
182
183
|
state = self.state_factory.state()
|
184
|
+
store = self.objectstore_factory.store()
|
183
185
|
|
184
186
|
# Validate the token
|
185
187
|
run_id = state.get_run_id_by_token(request.token)
|
@@ -193,14 +195,21 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
193
195
|
# Retrieve message for this run
|
194
196
|
message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
|
195
197
|
|
196
|
-
|
198
|
+
# Retrieve the object tree for the message
|
199
|
+
object_tree = store.get_object_tree(message.metadata.message_id)
|
200
|
+
|
201
|
+
return PullAppMessagesResponse(
|
202
|
+
messages_list=[message_to_proto(message)],
|
203
|
+
message_object_trees=[object_tree],
|
204
|
+
)
|
197
205
|
|
198
206
|
def PushMessage(
|
199
207
|
self, request: PushAppMessagesRequest, context: grpc.ServicerContext
|
200
208
|
) -> PushAppMessagesResponse:
|
201
209
|
"""Push one Message."""
|
202
|
-
# Initialize state connection
|
210
|
+
# Initialize state and store connection
|
203
211
|
state = self.state_factory.state()
|
212
|
+
store = self.objectstore_factory.store()
|
204
213
|
|
205
214
|
# Validate the token
|
206
215
|
run_id = state.get_run_id_by_token(request.token)
|
@@ -214,7 +223,10 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
214
223
|
# Save the message to the state
|
215
224
|
state.store_message(message_from_proto(request.messages_list[0]))
|
216
225
|
|
217
|
-
|
226
|
+
# Store Message object to descendants mapping and preregister objects
|
227
|
+
objects_to_push = store_mapping_and_register_objects(store, request=request)
|
228
|
+
|
229
|
+
return PushAppMessagesResponse(objects_to_push=objects_to_push)
|
218
230
|
|
219
231
|
def PushObject(
|
220
232
|
self, request: PushObjectRequest, context: grpc.ServicerContext
|
@@ -20,6 +20,7 @@ import subprocess
|
|
20
20
|
import time
|
21
21
|
from collections.abc import Iterator
|
22
22
|
from contextlib import contextmanager
|
23
|
+
from functools import partial
|
23
24
|
from logging import INFO, WARN
|
24
25
|
from pathlib import Path
|
25
26
|
from typing import Callable, Optional, Union
|
@@ -47,16 +48,17 @@ from flwr.common.constant import (
|
|
47
48
|
from flwr.common.exit import ExitCode, flwr_exit
|
48
49
|
from flwr.common.exit_handlers import register_exit_handlers
|
49
50
|
from flwr.common.grpc import generic_create_grpc_server
|
50
|
-
from flwr.common.inflatable import
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
from flwr.common.inflatable import iterate_object_tree
|
52
|
+
from flwr.common.inflatable_utils import (
|
53
|
+
pull_objects,
|
54
|
+
push_object_contents_from_iterable,
|
54
55
|
)
|
55
56
|
from flwr.common.logger import log
|
56
57
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
57
58
|
from flwr.common.telemetry import EventType
|
58
59
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
59
60
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
61
|
+
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
60
62
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
61
63
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
62
64
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
@@ -172,7 +174,17 @@ def start_client_internal(
|
|
172
174
|
max_retries=max_retries,
|
173
175
|
max_wait_time=max_wait_time,
|
174
176
|
) as conn:
|
175
|
-
|
177
|
+
(
|
178
|
+
receive,
|
179
|
+
send,
|
180
|
+
create_node,
|
181
|
+
_,
|
182
|
+
get_run,
|
183
|
+
get_fab,
|
184
|
+
pull_object,
|
185
|
+
push_object,
|
186
|
+
confirm_message_received,
|
187
|
+
) = conn
|
176
188
|
|
177
189
|
# Call create_node fn to register node
|
178
190
|
# and store node_id in state
|
@@ -192,6 +204,8 @@ def start_client_internal(
|
|
192
204
|
receive=receive,
|
193
205
|
get_run=get_run,
|
194
206
|
get_fab=get_fab,
|
207
|
+
pull_object=pull_object,
|
208
|
+
confirm_message_received=confirm_message_received,
|
195
209
|
)
|
196
210
|
|
197
211
|
# Two isolation modes:
|
@@ -223,7 +237,12 @@ def start_client_internal(
|
|
223
237
|
]
|
224
238
|
subprocess.run(command, check=False)
|
225
239
|
|
226
|
-
_push_messages(
|
240
|
+
_push_messages(
|
241
|
+
state=state,
|
242
|
+
object_store=store,
|
243
|
+
send=send,
|
244
|
+
push_object=push_object,
|
245
|
+
)
|
227
246
|
|
228
247
|
# Sleep for 3 seconds before the next iteration
|
229
248
|
time.sleep(3)
|
@@ -232,11 +251,13 @@ def start_client_internal(
|
|
232
251
|
def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
233
252
|
state: NodeState,
|
234
253
|
ffs: Ffs,
|
235
|
-
object_store: ObjectStore,
|
254
|
+
object_store: ObjectStore,
|
236
255
|
node_config: UserConfig,
|
237
|
-
receive: Callable[[], Optional[Message]],
|
256
|
+
receive: Callable[[], Optional[tuple[Message, ObjectTree]]],
|
238
257
|
get_run: Callable[[int], Run],
|
239
258
|
get_fab: Callable[[str, int], Fab],
|
259
|
+
pull_object: Callable[[int, str], bytes],
|
260
|
+
confirm_message_received: Callable[[int, str], None],
|
240
261
|
) -> Optional[int]:
|
241
262
|
"""Pull a message from the SuperLink and store it in the state.
|
242
263
|
|
@@ -248,8 +269,9 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
248
269
|
message = None
|
249
270
|
try:
|
250
271
|
# Pull message
|
251
|
-
if (
|
272
|
+
if (recv := receive()) is None:
|
252
273
|
return None
|
274
|
+
message, object_tree = recv
|
253
275
|
|
254
276
|
# Log message reception
|
255
277
|
log(INFO, "")
|
@@ -293,16 +315,22 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
293
315
|
)
|
294
316
|
state.store_context(run_ctx)
|
295
317
|
|
296
|
-
#
|
297
|
-
|
318
|
+
# Preregister the object tree of the message
|
319
|
+
obj_ids_to_pull = object_store.preregister(run_id, object_tree)
|
320
|
+
|
321
|
+
# Store the message in the state (note this message has no content)
|
298
322
|
state.store_message(message)
|
299
323
|
|
300
|
-
#
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
324
|
+
# Pull and store objects of the message in the ObjectStore
|
325
|
+
obj_contents = pull_objects(
|
326
|
+
obj_ids_to_pull,
|
327
|
+
pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
|
328
|
+
)
|
329
|
+
for obj_id in list(obj_contents.keys()):
|
330
|
+
object_store.put(obj_id, obj_contents.pop(obj_id))
|
331
|
+
|
332
|
+
# Confirm that the message was received
|
333
|
+
confirm_message_received(run_id, message.metadata.message_id)
|
306
334
|
|
307
335
|
except RunNotRunningException:
|
308
336
|
if message is None:
|
@@ -325,7 +353,9 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
325
353
|
|
326
354
|
def _push_messages(
|
327
355
|
state: NodeState,
|
328
|
-
|
356
|
+
object_store: ObjectStore,
|
357
|
+
send: Callable[[Message, ObjectTree], set[str]],
|
358
|
+
push_object: Callable[[int, str, bytes], None],
|
329
359
|
) -> None:
|
330
360
|
"""Push reply messages to the SuperLink."""
|
331
361
|
# Get messages to send
|
@@ -349,9 +379,34 @@ def _push_messages(
|
|
349
379
|
message.metadata.message_type,
|
350
380
|
)
|
351
381
|
|
382
|
+
# Get the object tree for the message
|
383
|
+
object_tree = object_store.get_object_tree(message.metadata.message_id)
|
384
|
+
|
385
|
+
# Define the iterator for yielding object contents
|
386
|
+
# This will yield (object_id, content) pairs
|
387
|
+
def yield_object_contents(_obj_tree: ObjectTree) -> Iterator[tuple[str, bytes]]:
|
388
|
+
for tree in iterate_object_tree(_obj_tree):
|
389
|
+
while (content := object_store.get(tree.object_id)) is None:
|
390
|
+
# Wait for the content to be available
|
391
|
+
time.sleep(0.5)
|
392
|
+
|
393
|
+
yield tree.object_id, content
|
394
|
+
|
352
395
|
# Send the message
|
353
396
|
try:
|
354
|
-
|
397
|
+
# Send the reply message with its ObjectTree
|
398
|
+
send(message, object_tree)
|
399
|
+
|
400
|
+
# Push object contents from the ObjectStore
|
401
|
+
run_id = message.metadata.run_id
|
402
|
+
push_object_contents_from_iterable(
|
403
|
+
yield_object_contents(object_tree),
|
404
|
+
# Use functools.partial to bind run_id explicitly,
|
405
|
+
# avoiding late binding issues and satisfying flake8 (B023)
|
406
|
+
# Equivalent to:
|
407
|
+
# lambda object_id, content: push_object(run_id, object_id, content)
|
408
|
+
push_object_fn=partial(push_object, run_id),
|
409
|
+
)
|
355
410
|
log(INFO, "Sent successfully")
|
356
411
|
except RunNotRunningException:
|
357
412
|
log(
|
@@ -369,6 +424,11 @@ def _push_messages(
|
|
369
424
|
]
|
370
425
|
)
|
371
426
|
|
427
|
+
# Delete all its objects from the ObjectStore
|
428
|
+
# No need to delete objects of the message it replies to, as it is
|
429
|
+
# already deleted when the ClientApp calls `ConfirmMessageReceived`
|
430
|
+
object_store.delete(message.metadata.message_id)
|
431
|
+
|
372
432
|
|
373
433
|
@contextmanager
|
374
434
|
def _init_connection( # pylint: disable=too-many-positional-arguments
|
@@ -383,12 +443,15 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
383
443
|
max_wait_time: Optional[float] = None,
|
384
444
|
) -> Iterator[
|
385
445
|
tuple[
|
386
|
-
Callable[[], Optional[Message]],
|
387
|
-
Callable[[Message],
|
446
|
+
Callable[[], Optional[tuple[Message, ObjectTree]]],
|
447
|
+
Callable[[Message, ObjectTree], set[str]],
|
388
448
|
Callable[[], Optional[int]],
|
389
449
|
Callable[[], None],
|
390
450
|
Callable[[int], Run],
|
391
451
|
Callable[[str, int], Fab],
|
452
|
+
Callable[[int, str], bytes],
|
453
|
+
Callable[[int, str, bytes], None],
|
454
|
+
Callable[[int, str], None],
|
392
455
|
]
|
393
456
|
]:
|
394
457
|
"""Establish a connection to the Fleet API server at SuperLink."""
|
{flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.20.0.
|
3
|
+
Version: 1.20.0.dev20250721
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
|
{flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/RECORD
RENAMED
@@ -6,7 +6,7 @@ flwr/cli/__init__.py,sha256=EfMGmHoobET6P2blBt_eOByXL8299MgFfB7XNdaPQ6I,720
|
|
6
6
|
flwr/cli/app.py,sha256=AKCP45Dkbpvdil_4Ir9S93L3HP3iUOnHmcZjscoM8uU,1856
|
7
7
|
flwr/cli/auth_plugin/__init__.py,sha256=FyaoqPzcxlBTFfJ2sBRC5USwQLmAhFr5KuBwfMO4bmo,1052
|
8
8
|
flwr/cli/auth_plugin/oidc_cli_plugin.py,sha256=gIhW6Jg9QAo-jL43LYPpw_kn7pdUZZae0s0H8dEgjLM,5384
|
9
|
-
flwr/cli/build.py,sha256=
|
9
|
+
flwr/cli/build.py,sha256=hE54Q_eMdWLpVKSVC2aQaUxVaiUlWnAosGNvIPSEg6Y,7284
|
10
10
|
flwr/cli/cli_user_auth_interceptor.py,sha256=-JqDXpeZNQVwoSG7hMKsiS5qY5k5oklNSlQOVpM0-aY,3126
|
11
11
|
flwr/cli/config_utils.py,sha256=IAVn2uWTXpN72YYt7raLtwp8ziwZugUKSURpc471VzU,9123
|
12
12
|
flwr/cli/constant.py,sha256=g7Ad7o3DJDkJNrWS0T3SSJETWSTkkVJWGpLM8zlbpcY,1289
|
@@ -81,10 +81,10 @@ flwr/client/clientapp/__init__.py,sha256=Zw9qP5nHFnJ9K1dcR4cdY0fRqN-FaMYFSHJFXoF
|
|
81
81
|
flwr/client/clientapp/utils.py,sha256=LsiW1OL2VPcjom3xN29pgBQC0UrttQ-xWL_GF1fkKDo,4344
|
82
82
|
flwr/client/dpfedavg_numpy_client.py,sha256=3hul067cT2E9jBhzp7bFnFAZ_D2nWcIUEdHYE05FpzU,7404
|
83
83
|
flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ81wvZm0-lOuuBA,742
|
84
|
-
flwr/client/grpc_adapter_client/connection.py,sha256=
|
84
|
+
flwr/client/grpc_adapter_client/connection.py,sha256=JGv02EjSOAG1E5BRUD4lwXc1LLiYJ0OhInvp31qx1cU,4404
|
85
85
|
flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
|
86
86
|
flwr/client/grpc_rere_client/client_interceptor.py,sha256=zFaVHw6AxeNO-7eCKKb-RxrPa7zbM5Z-2-1Efc4adQY,2451
|
87
|
-
flwr/client/grpc_rere_client/connection.py,sha256=
|
87
|
+
flwr/client/grpc_rere_client/connection.py,sha256=__WQgS02WgOXopDnuighOzWLBVQY-0YCGtVjFhLaTZQ,13603
|
88
88
|
flwr/client/grpc_rere_client/grpc_adapter.py,sha256=dLGB5GriszAmtgvuFGuz_F7rIwpzLfDxhJ7T3Un-Ce0,6694
|
89
89
|
flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
|
90
90
|
flwr/client/message_handler/message_handler.py,sha256=X9SXX6et97Lw9_DGD93HKsEBGNjXClcFgc_5aLK0oiU,6541
|
@@ -98,7 +98,7 @@ flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=aKqjZCrikF73y3E-7h40
|
|
98
98
|
flwr/client/mod/utils.py,sha256=FUgD2TfcWqSeF6jUKZ4i6Ke56U4Nrv85AeVb93s6R9g,1201
|
99
99
|
flwr/client/numpy_client.py,sha256=Qq6ghsIAop2slKqAfgiI5NiHJ4LIxGmrik3Ror4_XVc,9581
|
100
100
|
flwr/client/rest_client/__init__.py,sha256=MBiuK62hj439m9rtwSwI184Hth6Tt5GbmpNMyl3zkZY,735
|
101
|
-
flwr/client/rest_client/connection.py,sha256=
|
101
|
+
flwr/client/rest_client/connection.py,sha256=nGWgvOEvXpGcpu6G2Uxz34qOnB1NutIVi4tDMgvvJ1E,15738
|
102
102
|
flwr/client/run_info_store.py,sha256=MaJ3UQ-07hWtK67wnWu0zR29jrk0fsfgJX506dvEOfE,4042
|
103
103
|
flwr/client/typing.py,sha256=Jw3rawDzI_-ZDcRmEQcs5gZModY7oeQlEeltYsdOhlU,1048
|
104
104
|
flwr/clientapp/__init__.py,sha256=zGW4z49Ojzoi1hDiRC7kyhLjijUilc6fqHhtM_ATRVA,719
|
@@ -124,7 +124,7 @@ flwr/common/grpc.py,sha256=y70hUFvXkIf3l03xOhlb7qhS6W1UJZRSZqCdB0ir0v8,10381
|
|
124
124
|
flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
|
125
125
|
flwr/common/inflatable.py,sha256=GDL9oBKs16_yyVdlH6kBf493O5xll_h9V7XB5Mpx1Hc,9524
|
126
126
|
flwr/common/inflatable_protobuf_utils.py,sha256=JtRqp-fV47goDM2y8JRQ7AmwwjeGaWexwoMWLcxX5gE,5056
|
127
|
-
flwr/common/inflatable_utils.py,sha256=
|
127
|
+
flwr/common/inflatable_utils.py,sha256=tYNrsdUWIw94p1oBPojg2wlD_-tnspWU2tbmIRafB6U,17401
|
128
128
|
flwr/common/logger.py,sha256=JbRf6E2vQxXzpDBq1T8IDUJo_usu3gjWEBPQ6uKcmdg,13049
|
129
129
|
flwr/common/message.py,sha256=xAL7iZN5-n-xPQpgoSFvxNrzs8fmiiPfoU0DjNQEhRw,19953
|
130
130
|
flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
|
@@ -289,7 +289,7 @@ flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt
|
|
289
289
|
flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=X7-z4oReIH5ghMfmMXML3SSpa2bhRsuIvt2OZs82BUk,8675
|
290
290
|
flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=9_RaYWMqFdpFi8QcE7Nv8-pRjWJ2dLHxezrwhd1tAYk,6845
|
291
291
|
flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
|
292
|
-
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=
|
292
|
+
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=3Wg20bFo1tZfkzTQUerPVSHXyOuUqNuitEib3W_Dy-U,8691
|
293
293
|
flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=Lzc93nA7tDqoy-zRUaPG316oqFiZX1HUCL5ELaXY_xw,735
|
294
294
|
flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=mxWKwGpgHPqd7cGFqd2ASnR-KZduIzLfT-d2yiNCqQ0,9257
|
295
295
|
flwr/server/superlink/fleet/vce/__init__.py,sha256=XOKbAWOzlCqEOQ3M2cBYkH7HKA7PxlbCJMunt-ty-DY,784
|
@@ -305,7 +305,7 @@ flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=E699Ak0jMF3N7i1SIeFRu
|
|
305
305
|
flwr/server/superlink/linkstate/utils.py,sha256=IeLh7iGRCHU5MEWOl7iriaSE4L__8GWOa2OleXadK5M,15444
|
306
306
|
flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
|
307
307
|
flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=zcvzDhCAnlFxAwCiJUHNm6IE7-rk5jeZqSmPgjEY3AU,2307
|
308
|
-
flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=
|
308
|
+
flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=m0c05XQwRWwymOqbVYWDuDKRryIT3PxGhB8OM6YPyaM,18253
|
309
309
|
flwr/server/superlink/simulation/__init__.py,sha256=Ry8DrNaZCMcQXvUc4FoCN2m3dvUQgWjasfp015o3Ec4,718
|
310
310
|
flwr/server/superlink/simulation/simulationio_grpc.py,sha256=VqWKxjpd4bCgPFKsgtIZPk9YcG0kc1EEmr5k20EKty4,2205
|
311
311
|
flwr/server/superlink/simulation/simulationio_servicer.py,sha256=m1T1zvEn81jlfx9hVTqmeWxAu6APCS2YW8l5O0OQvhU,7724
|
@@ -343,6 +343,7 @@ flwr/supercore/object_store/__init__.py,sha256=cdfPAmjINY6iOp8oI_LdcVh2simg469Mk
|
|
343
343
|
flwr/supercore/object_store/in_memory_object_store.py,sha256=CGY43syxDGrUPcdOzRH3hNrfeqmoTOY_wjo3qaAHuNk,9612
|
344
344
|
flwr/supercore/object_store/object_store.py,sha256=wC6Pxq89a7FwmIMJE3ZLPPy2i7Gdss7-8RUapECCAPY,5099
|
345
345
|
flwr/supercore/object_store/object_store_factory.py,sha256=QVwE2ywi7vsj2iKfvWWnNw3N_I7Rz91NUt2RpcbJ7iM,1527
|
346
|
+
flwr/supercore/object_store/utils.py,sha256=-WwBa6ejMNm9ahmNZP39IHutS0cwingmeqCoxTmATQM,1845
|
346
347
|
flwr/supercore/utils.py,sha256=ebuHMbeA8eXisX0oMPqBK3hk7uVnIE_yiqWVz8YbkpQ,1324
|
347
348
|
flwr/superexec/__init__.py,sha256=YFqER0IJc1XEWfsX6AxZ9LSRq0sawPYrNYki-brvTIc,715
|
348
349
|
flwr/superexec/app.py,sha256=U2jjOHb2LGWoU7vrl9_czTzre9O2mPxu3CPGUZ86sK4,1465
|
@@ -364,12 +365,12 @@ flwr/supernode/nodestate/in_memory_nodestate.py,sha256=LF3AbaW0bcZHY5yKWwUJSU2RZ
|
|
364
365
|
flwr/supernode/nodestate/nodestate.py,sha256=kkGFxYnLIwT4-UmlPnf6HvAUpPey2urUNrweGybAIY4,6398
|
365
366
|
flwr/supernode/nodestate/nodestate_factory.py,sha256=UYTDCcwK_baHUmkzkJDxL0UEqvtTfOMlQRrROMCd0Xo,1430
|
366
367
|
flwr/supernode/runtime/__init__.py,sha256=JQdqd2EMTn-ORMeTvewYYh52ls0YKP68jrps1qioxu4,718
|
367
|
-
flwr/supernode/runtime/run_clientapp.py,sha256=
|
368
|
+
flwr/supernode/runtime/run_clientapp.py,sha256=woAO8rXclt5eZeNHokhBChgxMf-TAzqWnHCkoiSsLVs,10765
|
368
369
|
flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca8gxdEo,717
|
369
370
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
370
|
-
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=
|
371
|
-
flwr/supernode/start_client_internal.py,sha256=
|
372
|
-
flwr_nightly-1.20.0.
|
373
|
-
flwr_nightly-1.20.0.
|
374
|
-
flwr_nightly-1.20.0.
|
375
|
-
flwr_nightly-1.20.0.
|
371
|
+
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=SOx719uqdkIHryBri-XpuYpALQE__hxNtDjSAdeUtug,10059
|
372
|
+
flwr/supernode/start_client_internal.py,sha256=Sa9HT-YrS_RPkY44Dv6X-EZOX8HGupJyS4EY_e0x8u8,21367
|
373
|
+
flwr_nightly-1.20.0.dev20250721.dist-info/METADATA,sha256=leBKwaZsOax_yJrfB6silj5VVDX6i3Oe_GHSF2D3yV0,15966
|
374
|
+
flwr_nightly-1.20.0.dev20250721.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
375
|
+
flwr_nightly-1.20.0.dev20250721.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
|
376
|
+
flwr_nightly-1.20.0.dev20250721.dist-info/RECORD,,
|
{flwr_nightly-1.20.0.dev20250718.dist-info → flwr_nightly-1.20.0.dev20250721.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|