flwr-nightly 1.20.0.dev20250717__py3-none-any.whl → 1.20.0.dev20250720__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 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
@@ -49,6 +49,9 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
49
49
  Callable[[], None],
50
50
  Callable[[int], Run],
51
51
  Callable[[str, int], Fab],
52
+ Callable[[int, str], bytes],
53
+ Callable[[int, str, bytes], None],
54
+ Callable[[int, str], None],
52
55
  ]
53
56
  ]:
54
57
  """Primitives for request/response-based interaction with a server via GrpcAdapter.
@@ -83,6 +86,9 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
83
86
  delete_node : Optional[Callable]
84
87
  get_run : Optional[Callable]
85
88
  get_fab : Optional[Callable]
89
+ pull_object : Callable[[str], bytes]
90
+ push_object : Callable[[str, bytes], None]
91
+ confirm_message_received : Callable[[str], None]
86
92
  """
87
93
  if authentication_keys is not None:
88
94
  log(ERROR, "Client authentication is not supported for this transport type.")
@@ -35,6 +35,7 @@ from flwr.common.inflatable import (
35
35
  no_object_id_recompute,
36
36
  )
37
37
  from flwr.common.inflatable_protobuf_utils import (
38
+ make_confirm_message_received_fn_protobuf,
38
39
  make_pull_object_fn_protobuf,
39
40
  make_push_object_fn_protobuf,
40
41
  )
@@ -94,6 +95,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
94
95
  Callable[[], None],
95
96
  Callable[[int], Run],
96
97
  Callable[[str, int], Fab],
98
+ Callable[[int, str], bytes],
99
+ Callable[[int, str, bytes], None],
100
+ Callable[[int, str], None],
97
101
  ]
98
102
  ]:
99
103
  """Primitives for request/response-based interaction with a server.
@@ -136,6 +140,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
136
140
  create_node : Optional[Callable]
137
141
  delete_node : Optional[Callable]
138
142
  get_run : Optional[Callable]
143
+ pull_object : Callable[[str], bytes]
144
+ push_object : Callable[[str, bytes], None]
145
+ confirm_message_received : Callable[[str], None]
139
146
  """
140
147
  if isinstance(root_certificates, str):
141
148
  root_certificates = Path(root_certificates).read_bytes()
@@ -354,9 +361,58 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
354
361
 
355
362
  return Fab(get_fab_response.fab.hash_str, get_fab_response.fab.content)
356
363
 
364
+ def pull_object(run_id: int, object_id: str) -> bytes:
365
+ """Pull the object from the SuperLink."""
366
+ # Check Node
367
+ if node is None:
368
+ raise RuntimeError("Node instance missing")
369
+
370
+ fn = make_pull_object_fn_protobuf(
371
+ pull_object_protobuf=stub.PullObject,
372
+ node=node,
373
+ run_id=run_id,
374
+ )
375
+ return fn(object_id)
376
+
377
+ def push_object(run_id: int, object_id: str, contents: bytes) -> None:
378
+ """Push the object to the SuperLink."""
379
+ # Check Node
380
+ if node is None:
381
+ raise RuntimeError("Node instance missing")
382
+
383
+ fn = make_push_object_fn_protobuf(
384
+ push_object_protobuf=stub.PushObject,
385
+ node=node,
386
+ run_id=run_id,
387
+ )
388
+ fn(object_id, contents)
389
+
390
+ def confirm_message_received(run_id: int, object_id: str) -> None:
391
+ """Confirm that the message has been received."""
392
+ # Check Node
393
+ if node is None:
394
+ raise RuntimeError("Node instance missing")
395
+
396
+ fn = make_confirm_message_received_fn_protobuf(
397
+ confirm_message_received_protobuf=stub.ConfirmMessageReceived,
398
+ node=node,
399
+ run_id=run_id,
400
+ )
401
+ fn(object_id)
402
+
357
403
  try:
358
404
  # Yield methods
359
- yield (receive, send, create_node, delete_node, get_run, get_fab)
405
+ yield (
406
+ receive,
407
+ send,
408
+ create_node,
409
+ delete_node,
410
+ get_run,
411
+ get_fab,
412
+ pull_object,
413
+ push_object,
414
+ confirm_message_received,
415
+ )
360
416
  except Exception as exc: # pylint: disable=broad-except
361
417
  log(ERROR, exc)
362
418
  # Cleanup
@@ -35,6 +35,7 @@ from flwr.common.inflatable import (
35
35
  no_object_id_recompute,
36
36
  )
37
37
  from flwr.common.inflatable_protobuf_utils import (
38
+ make_confirm_message_received_fn_protobuf,
38
39
  make_pull_object_fn_protobuf,
39
40
  make_push_object_fn_protobuf,
40
41
  )
@@ -114,6 +115,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
114
115
  Callable[[], None],
115
116
  Callable[[int], Run],
116
117
  Callable[[str, int], Fab],
118
+ Callable[[int, str], bytes],
119
+ Callable[[int, str, bytes], None],
120
+ Callable[[int, str], None],
117
121
  ]
118
122
  ]:
119
123
  """Primitives for request/response-based interaction with a server.
@@ -149,6 +153,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
149
153
  create_node : Optional[Callable]
150
154
  delete_node : Optional[Callable]
151
155
  get_run : Optional[Callable]
156
+ pull_object : Callable[[str], bytes]
157
+ push_object : Callable[[str, bytes], None]
158
+ confirm_message_received : Callable[[str], None]
152
159
  """
153
160
  log(
154
161
  WARN,
@@ -230,6 +237,38 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
230
237
  grpc_res.ParseFromString(res.content)
231
238
  return grpc_res
232
239
 
240
+ def _pull_object_protobuf(request: PullObjectRequest) -> PullObjectResponse:
241
+ res = _request(
242
+ req=request,
243
+ res_type=PullObjectResponse,
244
+ api_path=PATH_PULL_OBJECT,
245
+ )
246
+ if res is None:
247
+ raise ValueError(f"{PullObjectResponse.__name__} is None.")
248
+ return res
249
+
250
+ def _push_object_protobuf(request: PushObjectRequest) -> PushObjectResponse:
251
+ res = _request(
252
+ req=request,
253
+ res_type=PushObjectResponse,
254
+ api_path=PATH_PUSH_OBJECT,
255
+ )
256
+ if res is None:
257
+ raise ValueError(f"{PushObjectResponse.__name__} is None.")
258
+ return res
259
+
260
+ def _confirm_message_received_protobuf(
261
+ request: ConfirmMessageReceivedRequest,
262
+ ) -> ConfirmMessageReceivedResponse:
263
+ res = _request(
264
+ req=request,
265
+ res_type=ConfirmMessageReceivedResponse,
266
+ api_path=PATH_CONFIRM_MESSAGE_RECEIVED,
267
+ )
268
+ if res is None:
269
+ raise ValueError(f"{ConfirmMessageReceivedResponse.__name__} is None.")
270
+ return res
271
+
233
272
  def send_node_heartbeat() -> bool:
234
273
  # Get Node
235
274
  if node is None:
@@ -326,20 +365,12 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
326
365
  msg_id = message_proto.metadata.message_id
327
366
  run_id = message_proto.metadata.run_id
328
367
 
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
368
  try:
338
369
  object_tree = res.message_object_trees[0]
339
370
  all_object_contents = pull_objects(
340
371
  [tree.object_id for tree in iterate_object_tree(object_tree)],
341
372
  pull_object_fn=make_pull_object_fn_protobuf(
342
- pull_object_protobuf=fn,
373
+ pull_object_protobuf=_pull_object_protobuf,
343
374
  node=node,
344
375
  run_id=run_id,
345
376
  ),
@@ -403,21 +434,11 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
403
434
  if res and res.objects_to_push:
404
435
  objs_to_push = set(res.objects_to_push[message.object_id].object_ids)
405
436
 
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
437
  try:
417
438
  push_objects(
418
439
  all_objects,
419
440
  push_object_fn=make_push_object_fn_protobuf(
420
- push_object_protobuf=fn,
441
+ push_object_protobuf=_push_object_protobuf,
421
442
  node=node,
422
443
  run_id=message_proto.metadata.run_id,
423
444
  ),
@@ -457,9 +478,58 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
457
478
  res.fab.content,
458
479
  )
459
480
 
481
+ def pull_object(run_id: int, object_id: str) -> bytes:
482
+ """Pull the object from the SuperLink."""
483
+ # Check Node
484
+ if node is None:
485
+ raise RuntimeError("Node instance missing")
486
+
487
+ fn = make_pull_object_fn_protobuf(
488
+ pull_object_protobuf=_pull_object_protobuf,
489
+ node=node,
490
+ run_id=run_id,
491
+ )
492
+ return fn(object_id)
493
+
494
+ def push_object(run_id: int, object_id: str, contents: bytes) -> None:
495
+ """Push the object to the SuperLink."""
496
+ # Check Node
497
+ if node is None:
498
+ raise RuntimeError("Node instance missing")
499
+
500
+ fn = make_push_object_fn_protobuf(
501
+ push_object_protobuf=_push_object_protobuf,
502
+ node=node,
503
+ run_id=run_id,
504
+ )
505
+ fn(object_id, contents)
506
+
507
+ def confirm_message_received(run_id: int, object_id: str) -> None:
508
+ """Confirm that the message has been received."""
509
+ # Check Node
510
+ if node is None:
511
+ raise RuntimeError("Node instance missing")
512
+
513
+ fn = make_confirm_message_received_fn_protobuf(
514
+ confirm_message_received_protobuf=_confirm_message_received_protobuf,
515
+ node=node,
516
+ run_id=run_id,
517
+ )
518
+ fn(object_id)
519
+
460
520
  try:
461
521
  # Yield methods
462
- yield (receive, send, create_node, delete_node, get_run, get_fab)
522
+ yield (
523
+ receive,
524
+ send,
525
+ create_node,
526
+ delete_node,
527
+ get_run,
528
+ get_fab,
529
+ pull_object,
530
+ push_object,
531
+ confirm_message_received,
532
+ )
463
533
  except Exception as exc: # pylint: disable=broad-except
464
534
  log(ERROR, exc)
465
535
  # Cleanup
@@ -18,6 +18,8 @@
18
18
  from typing import Callable
19
19
 
20
20
  from flwr.proto.message_pb2 import ( # pylint: disable=E0611
21
+ ConfirmMessageReceivedRequest,
22
+ ConfirmMessageReceivedResponse,
21
23
  PullObjectRequest,
22
24
  PullObjectResponse,
23
25
  PushObjectRequest,
@@ -27,6 +29,10 @@ from flwr.proto.node_pb2 import Node # pylint: disable=E0611
27
29
 
28
30
  from .inflatable_utils import ObjectIdNotPreregisteredError, ObjectUnavailableError
29
31
 
32
+ ConfirmMessageReceivedProtobuf = Callable[
33
+ [ConfirmMessageReceivedRequest], ConfirmMessageReceivedResponse
34
+ ]
35
+
30
36
 
31
37
  def make_pull_object_fn_protobuf(
32
38
  pull_object_protobuf: Callable[[PullObjectRequest], PullObjectResponse],
@@ -99,3 +105,37 @@ def make_push_object_fn_protobuf(
99
105
  raise ObjectIdNotPreregisteredError(object_id)
100
106
 
101
107
  return push_object_fn
108
+
109
+
110
+ def make_confirm_message_received_fn_protobuf(
111
+ confirm_message_received_protobuf: ConfirmMessageReceivedProtobuf,
112
+ node: Node,
113
+ run_id: int,
114
+ ) -> Callable[[str], None]:
115
+ """Create a confirm message received function that uses protobuf.
116
+
117
+ Parameters
118
+ ----------
119
+ confirm_message_received_protobuf : ConfirmMessageReceivedProtobuf
120
+ A callable that takes a `ConfirmMessageReceivedRequest` and returns a
121
+ `ConfirmMessageReceivedResponse`, confirming message receipt.
122
+ This function is typically backed by a gRPC client stub.
123
+ node : Node
124
+ The node making the request.
125
+ run_id : int
126
+ The run ID for the current message.
127
+
128
+ Returns
129
+ -------
130
+ Callable[[str], None]
131
+ A wrapper function that takes an object ID and confirms that
132
+ the message has been received.
133
+ """
134
+
135
+ def confirm_message_received_fn(object_id: str) -> None:
136
+ request = ConfirmMessageReceivedRequest(
137
+ node=node, run_id=run_id, message_object_id=object_id
138
+ )
139
+ confirm_message_received_protobuf(request)
140
+
141
+ return confirm_message_received_fn
@@ -19,7 +19,10 @@ import os
19
19
  import random
20
20
  import threading
21
21
  import time
22
- from typing import Callable, Optional
22
+ from collections.abc import Iterable, Iterator
23
+ from typing import Callable, Optional, TypeVar
24
+
25
+ from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
23
26
 
24
27
  from .constant import (
25
28
  HEAD_BODY_DIVIDER,
@@ -38,6 +41,7 @@ from .inflatable import (
38
41
  get_object_head_values_from_object_content,
39
42
  get_object_id,
40
43
  is_valid_sha256_hash,
44
+ iterate_object_tree,
41
45
  )
42
46
  from .message import Message
43
47
  from .record import Array, ArrayRecord, ConfigRecord, MetricRecord, RecordDict
@@ -54,6 +58,8 @@ inflatable_class_registry: dict[str, type[InflatableObject]] = {
54
58
  RecordDict.__qualname__: RecordDict,
55
59
  }
56
60
 
61
+ T = TypeVar("T", bound=InflatableObject)
62
+
57
63
 
58
64
  class ObjectUnavailableError(Exception):
59
65
  """Exception raised when an object has been pre-registered but is not yet
@@ -105,24 +111,61 @@ def push_objects(
105
111
  max_concurrent_pushes : int (default: MAX_CONCURRENT_PUSHES)
106
112
  The maximum number of concurrent pushes to perform.
107
113
  """
108
- if object_ids_to_push is not None:
109
- # Filter objects to push only those with IDs in the set
110
- objects = {k: v for k, v in objects.items() if k in object_ids_to_push}
111
-
112
114
  lock = threading.Lock()
113
115
 
114
- def push(obj_id: str) -> None:
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:
115
160
  """Push a single object."""
116
- object_content = objects[obj_id].deflate()
117
- if not keep_objects:
118
- with lock:
119
- del objects[obj_id]
120
- 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)
121
164
 
122
- # Push all objects concurrently
165
+ # Push all object contents concurrently
123
166
  num_workers = get_num_workers(max_concurrent_pushes)
124
167
  with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:
125
- list(executor.map(push, list(objects.keys())))
168
+ list(executor.map(push, object_contents))
126
169
 
127
170
 
128
171
  def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
@@ -348,3 +391,75 @@ def validate_object_content(content: bytes) -> None:
348
391
  raise UnexpectedObjectContentError(
349
392
  object_id=get_object_id(content), reason=str(err)
350
393
  ) from err
394
+
395
+
396
+ def pull_and_inflate_object_from_tree( # pylint: disable=R0913
397
+ object_tree: ObjectTree,
398
+ pull_object_fn: Callable[[str], bytes],
399
+ confirm_object_received_fn: Callable[[str], None],
400
+ *,
401
+ return_type: type[T] = InflatableObject, # type: ignore
402
+ max_concurrent_pulls: int = MAX_CONCURRENT_PULLS,
403
+ max_time: Optional[float] = PULL_MAX_TIME,
404
+ max_tries_per_object: Optional[int] = PULL_MAX_TRIES_PER_OBJECT,
405
+ initial_backoff: float = PULL_INITIAL_BACKOFF,
406
+ backoff_cap: float = PULL_BACKOFF_CAP,
407
+ ) -> T:
408
+ """Pull and inflate the head object from the provided object tree.
409
+
410
+ Parameters
411
+ ----------
412
+ object_tree : ObjectTree
413
+ The object tree containing the object ID and its descendants.
414
+ pull_object_fn : Callable[[str], bytes]
415
+ A function that takes an object ID and returns the object content as bytes.
416
+ confirm_object_received_fn : Callable[[str], None]
417
+ A function to confirm that the object has been received.
418
+ return_type : type[T] (default: InflatableObject)
419
+ The type of the object to return. Must be a subclass of `InflatableObject`.
420
+ max_concurrent_pulls : int (default: MAX_CONCURRENT_PULLS)
421
+ The maximum number of concurrent pulls to perform.
422
+ max_time : Optional[float] (default: PULL_MAX_TIME)
423
+ The maximum time to wait for all pulls to complete. If `None`, waits
424
+ indefinitely.
425
+ max_tries_per_object : Optional[int] (default: PULL_MAX_TRIES_PER_OBJECT)
426
+ The maximum number of attempts to pull each object. If `None`, pulls
427
+ indefinitely until the object is available.
428
+ initial_backoff : float (default: PULL_INITIAL_BACKOFF)
429
+ The initial backoff time in seconds for retrying pulls after an
430
+ `ObjectUnavailableError`.
431
+ backoff_cap : float (default: PULL_BACKOFF_CAP)
432
+ The maximum backoff time in seconds. Backoff times will not exceed this value.
433
+
434
+ Returns
435
+ -------
436
+ T
437
+ An instance of the specified return type containing the inflated object.
438
+ """
439
+ # Pull the main object and all its descendants
440
+ pulled_object_contents = pull_objects(
441
+ [tree.object_id for tree in iterate_object_tree(object_tree)],
442
+ pull_object_fn,
443
+ max_concurrent_pulls=max_concurrent_pulls,
444
+ max_time=max_time,
445
+ max_tries_per_object=max_tries_per_object,
446
+ initial_backoff=initial_backoff,
447
+ backoff_cap=backoff_cap,
448
+ )
449
+
450
+ # Confirm that all objects were pulled
451
+ confirm_object_received_fn(object_tree.object_id)
452
+
453
+ # Inflate the main object
454
+ inflated_object = inflate_object_from_contents(
455
+ object_tree.object_id, pulled_object_contents, keep_object_contents=False
456
+ )
457
+
458
+ # Check if the inflated object is of the expected type
459
+ if not isinstance(inflated_object, return_type):
460
+ raise TypeError(
461
+ f"Expected object of type {return_type.__name__}, "
462
+ f"but got {type(inflated_object).__name__}."
463
+ )
464
+
465
+ return inflated_object
@@ -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,23 @@ 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
36
47
  from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
37
48
  from flwr.common.serde import (
38
49
  context_from_proto,
39
50
  context_to_proto,
40
51
  fab_from_proto,
41
- message_from_proto,
42
52
  message_to_proto,
43
53
  run_from_proto,
44
54
  )
@@ -61,6 +71,7 @@ from flwr.proto.clientappio_pb2 import (
61
71
  RequestTokenResponse,
62
72
  )
63
73
  from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
74
+ from flwr.proto.node_pb2 import Node # pylint: disable=E0611
64
75
  from flwr.supercore.utils import mask_string
65
76
 
66
77
 
@@ -204,12 +215,6 @@ def pull_clientappinputs(
204
215
  masked_token = mask_string(token)
205
216
  log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
206
217
  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
218
  # Pull Context, Run and (optional) FAB
214
219
  res: PullAppInputsResponse = stub.PullClientAppInputs(
215
220
  PullAppInputsRequest(token=token)
@@ -217,6 +222,26 @@ def pull_clientappinputs(
217
222
  context = context_from_proto(res.context)
218
223
  run = run_from_proto(res.run)
219
224
  fab = fab_from_proto(res.fab) if res.fab else None
225
+
226
+ # Pull and inflate the message
227
+ pull_msg_res: PullAppMessagesResponse = stub.PullMessage(
228
+ PullAppMessagesRequest(token=token)
229
+ )
230
+ run_id = context.run_id
231
+ node = Node(node_id=context.node_id)
232
+ object_tree = pull_msg_res.message_object_trees[0]
233
+ message = pull_and_inflate_object_from_tree(
234
+ object_tree,
235
+ make_pull_object_fn_protobuf(stub.PullObject, node, run_id),
236
+ make_confirm_message_received_fn_protobuf(
237
+ stub.ConfirmMessageReceived, node, run_id
238
+ ),
239
+ return_type=Message,
240
+ )
241
+
242
+ # Set the message ID
243
+ # The deflated message doesn't contain the message_id (its own object_id)
244
+ message.metadata.__dict__["_message_id"] = object_tree.object_id
220
245
  return message, context, run, fab
221
246
  except grpc.RpcError as e:
222
247
  log(ERROR, "[PullClientAppInputs] gRPC error occurred: %s", str(e))
@@ -236,10 +261,38 @@ def push_clientappoutputs(
236
261
 
237
262
  try:
238
263
 
239
- # Push Message
240
- _ = stub.PushMessage(
241
- PushAppMessagesRequest(token=token, messages_list=[proto_message])
242
- )
264
+ with no_object_id_recompute():
265
+ # Get object tree and all objects to push
266
+ object_tree = get_object_tree(message)
267
+
268
+ # Push Message
269
+ # This is temporary. The message should not contain its content
270
+ push_msg_res = stub.PushMessage(
271
+ PushAppMessagesRequest(
272
+ token=token,
273
+ messages_list=[proto_message],
274
+ message_object_trees=[object_tree],
275
+ )
276
+ )
277
+ del proto_message
278
+
279
+ # Retrieve the object IDs to push
280
+ object_ids_to_push = set(
281
+ push_msg_res.objects_to_push[object_tree.object_id].object_ids
282
+ )
283
+
284
+ # Push all objects
285
+ all_objects = get_all_nested_objects(message)
286
+ del message
287
+ push_objects(
288
+ all_objects,
289
+ make_push_object_fn_protobuf(
290
+ stub.PushObject,
291
+ Node(node_id=context.node_id),
292
+ run_id=context.run_id,
293
+ ),
294
+ object_ids_to_push=object_ids_to_push,
295
+ )
243
296
 
244
297
  # Push Context
245
298
  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 ffs connection
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
- return PullAppMessagesResponse(messages_list=[message_to_proto(message)])
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
- return PushAppMessagesResponse()
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
@@ -172,7 +172,17 @@ def start_client_internal(
172
172
  max_retries=max_retries,
173
173
  max_wait_time=max_wait_time,
174
174
  ) as conn:
175
- receive, send, create_node, _, get_run, get_fab = conn
175
+ (
176
+ receive,
177
+ send,
178
+ create_node,
179
+ _,
180
+ get_run,
181
+ get_fab,
182
+ pull_object,
183
+ push_object,
184
+ confirm_message_received,
185
+ ) = conn
176
186
 
177
187
  # Call create_node fn to register node
178
188
  # and store node_id in state
@@ -192,6 +202,8 @@ def start_client_internal(
192
202
  receive=receive,
193
203
  get_run=get_run,
194
204
  get_fab=get_fab,
205
+ pull_object=pull_object,
206
+ confirm_message_received=confirm_message_received,
195
207
  )
196
208
 
197
209
  # Two isolation modes:
@@ -223,7 +235,12 @@ def start_client_internal(
223
235
  ]
224
236
  subprocess.run(command, check=False)
225
237
 
226
- _push_messages(state=state, send=send)
238
+ _push_messages(
239
+ state=state,
240
+ store=store,
241
+ send=send,
242
+ push_object=push_object,
243
+ )
227
244
 
228
245
  # Sleep for 3 seconds before the next iteration
229
246
  time.sleep(3)
@@ -232,11 +249,13 @@ def start_client_internal(
232
249
  def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
233
250
  state: NodeState,
234
251
  ffs: Ffs,
235
- object_store: ObjectStore, # pylint: disable=unused-argument
252
+ object_store: ObjectStore,
236
253
  node_config: UserConfig,
237
254
  receive: Callable[[], Optional[Message]],
238
255
  get_run: Callable[[int], Run],
239
256
  get_fab: Callable[[str, int], Fab],
257
+ pull_object: Callable[[int, str], bytes], # pylint: disable=W0613
258
+ confirm_message_received: Callable[[int, str], None], # pylint: disable=W0613
240
259
  ) -> Optional[int]:
241
260
  """Pull a message from the SuperLink and store it in the state.
242
261
 
@@ -325,7 +344,9 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
325
344
 
326
345
  def _push_messages(
327
346
  state: NodeState,
347
+ store: ObjectStore, # pylint: disable=W0613
328
348
  send: Callable[[Message], None],
349
+ push_object: Callable[[int, str, bytes], None], # pylint: disable=W0613
329
350
  ) -> None:
330
351
  """Push reply messages to the SuperLink."""
331
352
  # Get messages to send
@@ -389,6 +410,9 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
389
410
  Callable[[], None],
390
411
  Callable[[int], Run],
391
412
  Callable[[str, int], Fab],
413
+ Callable[[int, str], bytes],
414
+ Callable[[int, str, bytes], None],
415
+ Callable[[int, str], None],
392
416
  ]
393
417
  ]:
394
418
  """Establish a connection to the Fleet API server at SuperLink."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.20.0.dev20250717
3
+ Version: 1.20.0.dev20250720
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
@@ -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=7OrcTqrjJd-iVq-MCtCIBCfvzi1JoqwUxeZDGKDKu6I,7265
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=aj5tTYyE8z2hQLXPPydsJiz8gBDIWLUhfWvqYkAL1L4,3966
84
+ flwr/client/grpc_adapter_client/connection.py,sha256=GnEGIejz8QFlmldQUXrFQ3Rle1wHroJOCVoYIT5V1Og,4223
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=jndP_Z8yNGHocJfucUspk2bcrjPeI3s-nOb1tEeDlT4,13732
87
+ flwr/client/grpc_rere_client/connection.py,sha256=mfbaS2IZwd5vZ3whmy6XRb4c8kKzRLlbWy8SsG74LJU,15460
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=zZ-HYLPCQxKtTo1Sc-n8jkl_GB4bpfViuif8h9HFSqI,16388
101
+ flwr/client/rest_client/connection.py,sha256=nmmC3D3CB2SQKd-emQD1uVnqoUS-qHmteNlmBgZwGqs,18521
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
@@ -123,8 +123,8 @@ flwr/common/exit_handlers.py,sha256=IaqJ60fXZuu7McaRYnoYKtlbH9t4Yl9goNExKqtmQbs,
123
123
  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
- flwr/common/inflatable_protobuf_utils.py,sha256=lvKR5jD5P8AlpknD_1BlvUoTuT6Iyd8oodXfUQvjDRU,3746
127
- flwr/common/inflatable_utils.py,sha256=yew5VU8po8yZsmoTVxg-tB5vrvnb2mvBAE55qjiOAq8,12840
126
+ flwr/common/inflatable_protobuf_utils.py,sha256=JtRqp-fV47goDM2y8JRQ7AmwwjeGaWexwoMWLcxX5gE,5056
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=k_okVvuvbQgnBAWtXpOf0UrXcMQmRZtKKTS-as1xjig,8667
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=7oOFTjS5uSvdH0LSw-mDYulDMSItEM_QmXFQv_6ZjOU,18228
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=VcgRgPhrefAFpcAMfwCFSVa3HJsrlca8PNmeJD0on-k,8668
368
+ flwr/supernode/runtime/run_clientapp.py,sha256=YI7su6aJDqIOvBUe4byLx05Us-wqLFSqIqoDWKSnCBM,10676
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=OclO38I6NhKwM2ZnHz9NjrxW4RA4_v8M4GkLzsReG2c,9477
371
- flwr/supernode/start_client_internal.py,sha256=_ZqSfL_j4qn6Cg-P6sv3k_n1ZG62J_teokBxnWrXrPE,18772
372
- flwr_nightly-1.20.0.dev20250717.dist-info/METADATA,sha256=VlPfht2YzCaqiIBeDUy6f8p4AHm88CNaVCeEh8tGhMA,15966
373
- flwr_nightly-1.20.0.dev20250717.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
374
- flwr_nightly-1.20.0.dev20250717.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
375
- flwr_nightly-1.20.0.dev20250717.dist-info/RECORD,,
371
+ flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=SOx719uqdkIHryBri-XpuYpALQE__hxNtDjSAdeUtug,10059
372
+ flwr/supernode/start_client_internal.py,sha256=na3wQ5Ny-eDSsKKeN87guD0bTjfH4XugKDYhcmU23qI,19531
373
+ flwr_nightly-1.20.0.dev20250720.dist-info/METADATA,sha256=n-8GKATRVeEv833raNa0eqHt4C17AnBgeBkIv1_6teY,15966
374
+ flwr_nightly-1.20.0.dev20250720.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
375
+ flwr_nightly-1.20.0.dev20250720.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
376
+ flwr_nightly-1.20.0.dev20250720.dist-info/RECORD,,