flwr-nightly 1.19.0.dev20250521__py3-none-any.whl → 1.19.0.dev20250522__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.
@@ -45,10 +45,10 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
45
45
  tuple[
46
46
  Callable[[], Optional[Message]],
47
47
  Callable[[Message], None],
48
- Optional[Callable[[], Optional[int]]],
49
- Optional[Callable[[], None]],
50
- Optional[Callable[[int], Run]],
51
- Optional[Callable[[str, int], Fab]],
48
+ Callable[[], Optional[int]],
49
+ Callable[[], None],
50
+ Callable[[int], Run],
51
+ Callable[[str, int], Fab],
52
52
  ]
53
53
  ]:
54
54
  """Primitives for request/response-based interaction with a server via GrpcAdapter.
@@ -74,10 +74,10 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
74
74
  tuple[
75
75
  Callable[[], Optional[Message]],
76
76
  Callable[[Message], None],
77
- Optional[Callable[[], Optional[int]]],
78
- Optional[Callable[[], None]],
79
- Optional[Callable[[int], Run]],
80
- Optional[Callable[[str, int], Fab]],
77
+ Callable[[], Optional[int]],
78
+ Callable[[], None],
79
+ Callable[[int], Run],
80
+ Callable[[str, int], Fab],
81
81
  ]
82
82
  ]:
83
83
  """Primitives for request/response-based interaction with a server.
@@ -87,10 +87,10 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
87
87
  tuple[
88
88
  Callable[[], Optional[Message]],
89
89
  Callable[[Message], None],
90
- Optional[Callable[[], Optional[int]]],
91
- Optional[Callable[[], None]],
92
- Optional[Callable[[int], Run]],
93
- Optional[Callable[[str, int], Fab]],
90
+ Callable[[], Optional[int]],
91
+ Callable[[], None],
92
+ Callable[[int], Run],
93
+ Callable[[str, int], Fab],
94
94
  ]
95
95
  ]:
96
96
  """Primitives for request/response-based interaction with a server.
@@ -24,7 +24,7 @@ from contextlib import AbstractContextManager
24
24
  from logging import ERROR, INFO, WARN
25
25
  from os import urandom
26
26
  from pathlib import Path
27
- from typing import Callable, Optional, Union, cast
27
+ from typing import Callable, Optional, Union
28
28
 
29
29
  import grpc
30
30
  from cryptography.hazmat.primitives.asymmetric import ec
@@ -32,32 +32,25 @@ from grpc import RpcError
32
32
 
33
33
  from flwr.app.error import Error
34
34
  from flwr.cli.config_utils import get_fab_metadata
35
- from flwr.cli.install import install_from_fab
36
- from flwr.client.client import Client
37
- from flwr.client.client_app import ClientApp, LoadClientAppError
38
35
  from flwr.client.clientapp.app import flwr_clientapp
39
36
  from flwr.client.clientapp.clientappio_servicer import (
40
37
  ClientAppInputs,
41
38
  ClientAppIoServicer,
42
39
  )
43
40
  from flwr.client.grpc_adapter_client.connection import grpc_adapter
44
- from flwr.client.grpc_client.connection import grpc_connection
45
41
  from flwr.client.grpc_rere_client.connection import grpc_request_response
46
42
  from flwr.client.message_handler.message_handler import handle_control_message
47
43
  from flwr.client.run_info_store import DeprecatedRunInfoStore
48
- from flwr.client.typing import ClientFnExt
49
- from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message
44
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Message
50
45
  from flwr.common.address import parse_address
51
46
  from flwr.common.constant import (
52
47
  CLIENT_OCTET,
53
48
  CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
54
- ISOLATION_MODE_PROCESS,
55
49
  ISOLATION_MODE_SUBPROCESS,
56
50
  MAX_RETRY_DELAY,
57
51
  RUN_ID_NUM_BYTES,
58
52
  SERVER_OCTET,
59
53
  TRANSPORT_TYPE_GRPC_ADAPTER,
60
- TRANSPORT_TYPE_GRPC_BIDI,
61
54
  TRANSPORT_TYPE_GRPC_RERE,
62
55
  TRANSPORT_TYPE_REST,
63
56
  TRANSPORT_TYPES,
@@ -72,20 +65,6 @@ from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
72
65
  from flwr.supernode.nodestate import NodeStateFactory
73
66
 
74
67
 
75
- def _check_actionable_client(
76
- client: Optional[Client], client_fn: Optional[ClientFnExt]
77
- ) -> None:
78
- if client_fn is None and client is None:
79
- raise ValueError(
80
- "Both `client_fn` and `client` are `None`, but one is required"
81
- )
82
-
83
- if client_fn is not None and client is not None:
84
- raise ValueError(
85
- "Both `client_fn` and `client` are provided, but only one is allowed"
86
- )
87
-
88
-
89
68
  # pylint: disable=import-outside-toplevel
90
69
  # pylint: disable=too-many-branches
91
70
  # pylint: disable=too-many-locals
@@ -95,21 +74,18 @@ def start_client_internal(
95
74
  *,
96
75
  server_address: str,
97
76
  node_config: UserConfig,
98
- load_client_app_fn: Optional[Callable[[str, str, str], ClientApp]] = None,
99
- client_fn: Optional[ClientFnExt] = None,
100
- client: Optional[Client] = None,
101
77
  grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
102
78
  root_certificates: Optional[Union[bytes, str]] = None,
103
79
  insecure: Optional[bool] = None,
104
- transport: Optional[str] = None,
80
+ transport: str,
105
81
  authentication_keys: Optional[
106
82
  tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
107
83
  ] = None,
108
84
  max_retries: Optional[int] = None,
109
85
  max_wait_time: Optional[float] = None,
110
86
  flwr_path: Optional[Path] = None,
111
- isolation: Optional[str] = None,
112
- clientappio_api_address: Optional[str] = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
87
+ isolation: str = ISOLATION_MODE_SUBPROCESS,
88
+ clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
113
89
  ) -> None:
114
90
  """Start a Flower client node which connects to a Flower server.
115
91
 
@@ -121,13 +97,6 @@ def start_client_internal(
121
97
  would be `"[::]:8080"`.
122
98
  node_config: UserConfig
123
99
  The configuration of the node.
124
- load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None)
125
- A function that can be used to load a `ClientApp` instance.
126
- client_fn : Optional[ClientFnExt]
127
- A callable that instantiates a Client. (default: None)
128
- client : Optional[flwr.client.Client]
129
- An implementation of the abstract base
130
- class `flwr.client.Client` (default: None)
131
100
  grpc_max_message_length : int (default: 536_870_912, this equals 512MB)
132
101
  The maximum length of gRPC messages that can be exchanged with the
133
102
  Flower server. The default should be sufficient for most models.
@@ -142,10 +111,10 @@ def start_client_internal(
142
111
  insecure : Optional[bool] (default: None)
143
112
  Starts an insecure gRPC connection when True. Enables HTTPS connection
144
113
  when False, using system certificates if `root_certificates` is None.
145
- transport : Optional[str] (default: None)
114
+ transport : str
146
115
  Configure the transport layer. Allowed values:
147
- - 'grpc-bidi': gRPC, bidirectional streaming
148
- - 'grpc-rere': gRPC, request-response (experimental)
116
+ - 'grpc-rere': gRPC, request-response
117
+ - 'grpc-adapter': gRPC via 3rd party adapter (experimental)
149
118
  - 'rest': HTTP (experimental)
150
119
  authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
151
120
  Tuple containing the elliptic curve private key and public key for
@@ -162,56 +131,24 @@ def start_client_internal(
162
131
  If set to None, there is no limit to the total time.
163
132
  flwr_path: Optional[Path] (default: None)
164
133
  The fully resolved path containing installed Flower Apps.
165
- isolation : Optional[str] (default: None)
134
+ isolation : str (default: ISOLATION_MODE_SUBPROCESS)
166
135
  Isolation mode for `ClientApp`. Possible values are `subprocess` and
167
- `process`. Defaults to `None`, which runs the `ClientApp` in the same process
168
- as the SuperNode. If `subprocess`, the `ClientApp` runs in a subprocess started
136
+ `process`. If `subprocess`, the `ClientApp` runs in a subprocess started
169
137
  by the SueprNode and communicates using gRPC at the address
170
138
  `clientappio_api_address`. If `process`, the `ClientApp` runs in a separate
171
139
  isolated process and communicates using gRPC at the address
172
140
  `clientappio_api_address`.
173
- clientappio_api_address : Optional[str]
141
+ clientappio_api_address : str
174
142
  (default: `CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS`)
175
143
  The SuperNode gRPC server address.
176
144
  """
177
145
  if insecure is None:
178
146
  insecure = root_certificates is None
179
147
 
180
- if load_client_app_fn is None:
181
- _check_actionable_client(client, client_fn)
182
-
183
- if client_fn is None:
184
- # Wrap `Client` instance in `client_fn`
185
- def single_client_factory(
186
- context: Context, # pylint: disable=unused-argument
187
- ) -> Client:
188
- if client is None: # Added this to keep mypy happy
189
- raise ValueError(
190
- "Both `client_fn` and `client` are `None`, but one is required"
191
- )
192
- return client # Always return the same instance
193
-
194
- client_fn = single_client_factory
195
-
196
- def _load_client_app(_1: str, _2: str, _3: str) -> ClientApp:
197
- return ClientApp(client_fn=client_fn)
198
-
199
- load_client_app_fn = _load_client_app
200
-
201
- if isolation:
202
- if clientappio_api_address is None:
203
- raise ValueError(
204
- f"`clientappio_api_address` required when `isolation` is "
205
- f"{ISOLATION_MODE_SUBPROCESS} or {ISOLATION_MODE_PROCESS}",
206
- )
207
- _clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
208
- address=clientappio_api_address,
209
- certificates=None,
210
- )
211
- clientappio_api_address = cast(str, clientappio_api_address)
212
-
213
- # At this point, only `load_client_app_fn` should be used
214
- # Both `client` and `client_fn` must not be used directly
148
+ _clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
149
+ address=clientappio_api_address,
150
+ certificates=None,
151
+ )
215
152
 
216
153
  # Initialize connection context manager
217
154
  connection, address, connection_error_type = _init_connection(
@@ -278,30 +215,15 @@ def start_client_internal(
278
215
 
279
216
  # Register node when connecting the first time
280
217
  if run_info_store is None:
281
- if create_node is None:
282
- if transport not in ["grpc-bidi", None]:
283
- raise NotImplementedError(
284
- "All transports except `grpc-bidi` require "
285
- "an implementation for `create_node()`.'"
286
- )
287
- # gRPC-bidi doesn't have the concept of node_id,
288
- # so we set it to -1
289
- run_info_store = DeprecatedRunInfoStore(
290
- node_id=-1,
291
- node_config={},
292
- )
293
- else:
294
- # Call create_node fn to register node
295
- # and store node_id in state
296
- if (node_id := create_node()) is None:
297
- raise ValueError(
298
- "Failed to register SuperNode with the SuperLink"
299
- )
300
- state.set_node_id(node_id)
301
- run_info_store = DeprecatedRunInfoStore(
302
- node_id=state.get_node_id(),
303
- node_config=node_config,
304
- )
218
+ # Call create_node fn to register node
219
+ # and store node_id in state
220
+ if (node_id := create_node()) is None:
221
+ raise ValueError("Failed to register SuperNode with the SuperLink")
222
+ state.set_node_id(node_id)
223
+ run_info_store = DeprecatedRunInfoStore(
224
+ node_id=state.get_node_id(),
225
+ node_config=node_config,
226
+ )
305
227
 
306
228
  # pylint: disable=too-many-nested-blocks
307
229
  while True:
@@ -336,18 +258,11 @@ def start_client_internal(
336
258
  # Get run info
337
259
  run_id = message.metadata.run_id
338
260
  if run_id not in runs:
339
- if get_run is not None:
340
- runs[run_id] = get_run(run_id)
341
- # If get_run is None, i.e., in grpc-bidi mode
342
- else:
343
- runs[run_id] = Run.create_empty(run_id=run_id)
261
+ runs[run_id] = get_run(run_id)
344
262
 
345
263
  run: Run = runs[run_id]
346
264
  if get_fab is not None and run.fab_hash:
347
265
  fab = get_fab(run.fab_hash, run_id)
348
- if not isolation:
349
- # If `ClientApp` runs in the same process, install the FAB
350
- install_from_fab(fab.content, flwr_path, True)
351
266
  fab_id, fab_version = get_fab_metadata(fab.content)
352
267
  else:
353
268
  fab = None
@@ -374,94 +289,72 @@ def start_client_internal(
374
289
 
375
290
  # Handle app loading and task message
376
291
  try:
377
- if isolation:
378
- # Two isolation modes:
379
- # 1. `subprocess`: SuperNode is starting the ClientApp
380
- # process as a subprocess.
381
- # 2. `process`: ClientApp process gets started separately
382
- # (via `flwr-clientapp`), for example, in a separate
383
- # Docker container.
384
-
385
- # Generate SuperNode token
386
- token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
387
-
388
- # Mode 1: SuperNode starts ClientApp as subprocess
389
- start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
390
-
391
- # Share Message and Context with servicer
392
- clientappio_servicer.set_inputs(
393
- clientapp_input=ClientAppInputs(
394
- message=message,
395
- context=context,
396
- run=run,
397
- fab=fab,
398
- token=token,
399
- ),
400
- token_returned=start_subprocess,
401
- )
292
+ # Two isolation modes:
293
+ # 1. `subprocess`: SuperNode is starting the ClientApp
294
+ # process as a subprocess.
295
+ # 2. `process`: ClientApp process gets started separately
296
+ # (via `flwr-clientapp`), for example, in a separate
297
+ # Docker container.
298
+
299
+ # Generate SuperNode token
300
+ token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
301
+
302
+ # Mode 1: SuperNode starts ClientApp as subprocess
303
+ start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
304
+
305
+ # Share Message and Context with servicer
306
+ clientappio_servicer.set_inputs(
307
+ clientapp_input=ClientAppInputs(
308
+ message=message,
309
+ context=context,
310
+ run=run,
311
+ fab=fab,
312
+ token=token,
313
+ ),
314
+ token_returned=start_subprocess,
315
+ )
402
316
 
403
- if start_subprocess:
404
- _octet, _colon, _port = (
405
- clientappio_api_address.rpartition(":")
406
- )
407
- io_address = (
408
- f"{CLIENT_OCTET}:{_port}"
409
- if _octet == SERVER_OCTET
410
- else clientappio_api_address
411
- )
412
- # Start ClientApp subprocess
413
- command = [
414
- "flwr-clientapp",
415
- "--clientappio-api-address",
416
- io_address,
417
- "--token",
418
- str(token),
419
- ]
420
- command.append("--insecure")
421
-
422
- proc = mp_spawn_context.Process(
423
- target=_run_flwr_clientapp,
424
- args=(command, os.getpid()),
425
- daemon=True,
426
- )
427
- proc.start()
428
- proc.join()
429
- else:
430
- # Wait for output to become available
431
- while not clientappio_servicer.has_outputs():
432
- time.sleep(0.1)
433
-
434
- outputs = clientappio_servicer.get_outputs()
435
- reply_message, context = outputs.message, outputs.context
436
- else:
437
- # Load ClientApp instance
438
- client_app: ClientApp = load_client_app_fn(
439
- fab_id, fab_version, run.fab_hash
317
+ if start_subprocess:
318
+ _octet, _colon, _port = clientappio_api_address.rpartition(
319
+ ":"
320
+ )
321
+ io_address = (
322
+ f"{CLIENT_OCTET}:{_port}"
323
+ if _octet == SERVER_OCTET
324
+ else clientappio_api_address
440
325
  )
326
+ # Start ClientApp subprocess
327
+ command = [
328
+ "flwr-clientapp",
329
+ "--clientappio-api-address",
330
+ io_address,
331
+ "--token",
332
+ str(token),
333
+ ]
334
+ command.append("--insecure")
335
+
336
+ proc = mp_spawn_context.Process(
337
+ target=_run_flwr_clientapp,
338
+ args=(command, os.getpid()),
339
+ daemon=True,
340
+ )
341
+ proc.start()
342
+ proc.join()
343
+ else:
344
+ # Wait for output to become available
345
+ while not clientappio_servicer.has_outputs():
346
+ time.sleep(0.1)
441
347
 
442
- # Execute ClientApp
443
- reply_message = client_app(message=message, context=context)
348
+ outputs = clientappio_servicer.get_outputs()
349
+ reply_message, context = outputs.message, outputs.context
444
350
  except Exception as ex: # pylint: disable=broad-exception-caught
445
351
 
446
- # Legacy grpc-bidi
447
- if transport in ["grpc-bidi", None]:
448
- log(ERROR, "Client raised an exception.", exc_info=ex)
449
- # Raise exception, crash process
450
- raise ex
451
-
452
352
  # Don't update/change DeprecatedRunInfoStore
453
353
 
454
354
  e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION
455
355
  # Ex fmt: "<class 'ZeroDivisionError'>:<'division by zero'>"
456
356
  reason = str(type(ex)) + ":<'" + str(ex) + "'>"
457
357
  exc_entity = "ClientApp"
458
- if isinstance(ex, LoadClientAppError):
459
- reason = (
460
- "An exception was raised when attempting to load "
461
- "`ClientApp`"
462
- )
463
- e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION
464
- exc_entity = "SuperNode"
465
358
 
466
359
  log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
467
360
 
@@ -509,7 +402,7 @@ def start_client_internal(
509
402
  time.sleep(sleep_duration)
510
403
 
511
404
 
512
- def _init_connection(transport: Optional[str], server_address: str) -> tuple[
405
+ def _init_connection(transport: str, server_address: str) -> tuple[
513
406
  Callable[
514
407
  [
515
408
  str,
@@ -523,10 +416,10 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
523
416
  tuple[
524
417
  Callable[[], Optional[Message]],
525
418
  Callable[[Message], None],
526
- Optional[Callable[[], Optional[int]]],
527
- Optional[Callable[[], None]],
528
- Optional[Callable[[int], Run]],
529
- Optional[Callable[[str, int], Fab]],
419
+ Callable[[], Optional[int]],
420
+ Callable[[], None],
421
+ Callable[[int], Run],
422
+ Callable[[str, int], Fab],
530
423
  ]
531
424
  ],
532
425
  ],
@@ -543,10 +436,6 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
543
436
  host, port, is_v6 = parsed_address
544
437
  address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}"
545
438
 
546
- # Set the default transport layer
547
- if transport is None:
548
- transport = TRANSPORT_TYPE_GRPC_BIDI
549
-
550
439
  # Use either gRPC bidirectional streaming or REST request/response
551
440
  if transport == TRANSPORT_TYPE_REST:
552
441
  try:
@@ -562,8 +451,6 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
562
451
  connection, error_type = grpc_request_response, RpcError
563
452
  elif transport == TRANSPORT_TYPE_GRPC_ADAPTER:
564
453
  connection, error_type = grpc_adapter, RpcError
565
- elif transport == TRANSPORT_TYPE_GRPC_BIDI:
566
- connection, error_type = grpc_connection, RpcError
567
454
  else:
568
455
  raise ValueError(
569
456
  f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES})"
@@ -43,7 +43,6 @@ from flwr.common.exit import ExitCode, flwr_exit
43
43
  from flwr.common.exit_handlers import register_exit_handlers
44
44
  from flwr.common.logger import log
45
45
 
46
- from ..clientapp.utils import get_load_client_app_fn
47
46
  from ..start_client_internal import start_client_internal
48
47
 
49
48
 
@@ -64,12 +63,6 @@ def run_supernode() -> None:
64
63
  )
65
64
 
66
65
  root_certificates = try_obtain_root_certificates(args, args.superlink)
67
- load_fn = get_load_client_app_fn(
68
- default_app_ref="",
69
- app_path=None,
70
- flwr_dir=args.flwr_dir,
71
- multi_app=True,
72
- )
73
66
  authentication_keys = _try_setup_client_authentication(args)
74
67
 
75
68
  log(DEBUG, "Isolation mode: %s", args.isolation)
@@ -82,7 +75,6 @@ def run_supernode() -> None:
82
75
 
83
76
  start_client_internal(
84
77
  server_address=args.superlink,
85
- load_client_app_fn=load_fn,
86
78
  transport=args.transport,
87
79
  root_certificates=root_certificates,
88
80
  insecure=args.insecure,
flwr/common/inflatable.py CHANGED
@@ -112,6 +112,29 @@ def _get_object_body(object_content: bytes) -> bytes:
112
112
  return object_content.split(HEAD_BODY_DIVIDER, 1)[1]
113
113
 
114
114
 
115
+ def is_valid_sha256_hash(object_id: str) -> bool:
116
+ """Check if the given string is a valid SHA-256 hash.
117
+
118
+ Parameters
119
+ ----------
120
+ object_id : str
121
+ The string to check.
122
+
123
+ Returns
124
+ -------
125
+ bool
126
+ ``True`` if the string is a valid SHA-256 hash, ``False`` otherwise.
127
+ """
128
+ if len(object_id) != 64:
129
+ return False
130
+ try:
131
+ # If base 16 int conversion succeeds, it's a valid hexadecimal str
132
+ int(object_id, 16)
133
+ return True
134
+ except ValueError:
135
+ return False
136
+
137
+
115
138
  def get_object_type_from_object_content(object_content: bytes) -> str:
116
139
  """Return object type from bytes."""
117
140
  return get_object_head_values_from_object_content(object_content)[0]
@@ -31,6 +31,7 @@ from .inflatable import (
31
31
  get_object_head_values_from_object_content,
32
32
  get_object_id,
33
33
  )
34
+ from .message import Message
34
35
  from .record import Array, ArrayRecord, ConfigRecord, MetricRecord, RecordDict
35
36
 
36
37
  # Helper registry that maps names of classes to their type
@@ -38,6 +39,7 @@ inflatable_class_registry: dict[str, type[InflatableObject]] = {
38
39
  Array.__qualname__: Array,
39
40
  ArrayRecord.__qualname__: ArrayRecord,
40
41
  ConfigRecord.__qualname__: ConfigRecord,
42
+ Message.__qualname__: Message,
41
43
  MetricRecord.__qualname__: MetricRecord,
42
44
  RecordDict.__qualname__: RecordDict,
43
45
  }
flwr/compat/client/app.py CHANGED
@@ -41,7 +41,6 @@ from flwr.client.clientapp.clientappio_servicer import (
41
41
  ClientAppIoServicer,
42
42
  )
43
43
  from flwr.client.grpc_adapter_client.connection import grpc_adapter
44
- from flwr.client.grpc_client.connection import grpc_connection
45
44
  from flwr.client.grpc_rere_client.connection import grpc_request_response
46
45
  from flwr.client.message_handler.message_handler import handle_control_message
47
46
  from flwr.client.numpy_client import NumPyClient
@@ -69,6 +68,7 @@ from flwr.common.grpc import generic_create_grpc_server
69
68
  from flwr.common.logger import log, warn_deprecated_feature
70
69
  from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
71
70
  from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
71
+ from flwr.compat.client.grpc_client.connection import grpc_connection
72
72
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
73
73
  from flwr.supernode.nodestate import NodeStateFactory
74
74
 
@@ -794,7 +794,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
794
794
  elif transport == TRANSPORT_TYPE_GRPC_ADAPTER:
795
795
  connection, error_type = grpc_adapter, RpcError
796
796
  elif transport == TRANSPORT_TYPE_GRPC_BIDI:
797
- connection, error_type = grpc_connection, RpcError
797
+ connection, error_type = grpc_connection, RpcError # type: ignore[assignment]
798
798
  else:
799
799
  raise ValueError(
800
800
  f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES})"
flwr/server/app.py CHANGED
@@ -71,6 +71,7 @@ from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server
71
71
  from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
72
72
  from flwr.server.serverapp.app import flwr_serverapp
73
73
  from flwr.simulation.app import flwr_simulation
74
+ from flwr.supercore.object_store import ObjectStoreFactory
74
75
  from flwr.superexec.app import load_executor
75
76
  from flwr.superexec.exec_grpc import run_exec_api_grpc
76
77
 
@@ -307,6 +308,9 @@ def run_superlink() -> None:
307
308
  # Initialize FfsFactory
308
309
  ffs_factory = FfsFactory(args.storage_dir)
309
310
 
311
+ # Initialize ObjectStoreFactory
312
+ objectstore_factory = ObjectStoreFactory()
313
+
310
314
  # Start Exec API
311
315
  executor = load_executor(args)
312
316
  exec_server: grpc.Server = run_exec_api_grpc(
@@ -343,6 +347,7 @@ def run_superlink() -> None:
343
347
  address=serverappio_address,
344
348
  state_factory=state_factory,
345
349
  ffs_factory=ffs_factory,
350
+ objectstore_factory=objectstore_factory,
346
351
  certificates=None, # ServerAppIo API doesn't support SSL yet
347
352
  )
348
353
  grpc_servers.append(serverappio_server)
@@ -421,6 +426,7 @@ def run_superlink() -> None:
421
426
  address=fleet_address,
422
427
  state_factory=state_factory,
423
428
  ffs_factory=ffs_factory,
429
+ objectstore_factory=objectstore_factory,
424
430
  certificates=certificates,
425
431
  interceptors=interceptors,
426
432
  )
@@ -430,6 +436,7 @@ def run_superlink() -> None:
430
436
  address=fleet_address,
431
437
  state_factory=state_factory,
432
438
  ffs_factory=ffs_factory,
439
+ objectstore_factory=objectstore_factory,
433
440
  certificates=certificates,
434
441
  )
435
442
  grpc_servers.append(fleet_server)
@@ -668,10 +675,11 @@ def _try_obtain_fleet_event_log_writer_plugin() -> Optional[EventLogWriterPlugin
668
675
  sys.exit("No Fleet API event log writer plugins are currently supported.")
669
676
 
670
677
 
671
- def _run_fleet_api_grpc_rere(
678
+ def _run_fleet_api_grpc_rere( # pylint: disable=R0913, R0917
672
679
  address: str,
673
680
  state_factory: LinkStateFactory,
674
681
  ffs_factory: FfsFactory,
682
+ objectstore_factory: ObjectStoreFactory,
675
683
  certificates: Optional[tuple[bytes, bytes, bytes]],
676
684
  interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None,
677
685
  ) -> grpc.Server:
@@ -680,6 +688,7 @@ def _run_fleet_api_grpc_rere(
680
688
  fleet_servicer = FleetServicer(
681
689
  state_factory=state_factory,
682
690
  ffs_factory=ffs_factory,
691
+ objectstore_factory=objectstore_factory,
683
692
  )
684
693
  fleet_add_servicer_to_server_fn = add_FleetServicer_to_server
685
694
  fleet_grpc_server = generic_create_grpc_server(
@@ -700,6 +709,7 @@ def _run_fleet_api_grpc_adapter(
700
709
  address: str,
701
710
  state_factory: LinkStateFactory,
702
711
  ffs_factory: FfsFactory,
712
+ objectstore_factory: ObjectStoreFactory,
703
713
  certificates: Optional[tuple[bytes, bytes, bytes]],
704
714
  ) -> grpc.Server:
705
715
  """Run Fleet API (GrpcAdapter)."""
@@ -707,6 +717,7 @@ def _run_fleet_api_grpc_adapter(
707
717
  fleet_servicer = GrpcAdapterServicer(
708
718
  state_factory=state_factory,
709
719
  ffs_factory=ffs_factory,
720
+ objectstore_factory=objectstore_factory,
710
721
  )
711
722
  fleet_add_servicer_to_server_fn = add_GrpcAdapterServicer_to_server
712
723
  fleet_grpc_server = generic_create_grpc_server(
@@ -50,16 +50,21 @@ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
50
50
  from flwr.server.superlink.fleet.message_handler import message_handler
51
51
  from flwr.server.superlink.linkstate import LinkStateFactory
52
52
  from flwr.server.superlink.utils import abort_grpc_context
53
+ from flwr.supercore.object_store import ObjectStoreFactory
53
54
 
54
55
 
55
56
  class FleetServicer(fleet_pb2_grpc.FleetServicer):
56
57
  """Fleet API servicer."""
57
58
 
58
59
  def __init__(
59
- self, state_factory: LinkStateFactory, ffs_factory: FfsFactory
60
+ self,
61
+ state_factory: LinkStateFactory,
62
+ ffs_factory: FfsFactory,
63
+ objectstore_factory: ObjectStoreFactory,
60
64
  ) -> None:
61
65
  self.state_factory = state_factory
62
66
  self.ffs_factory = ffs_factory
67
+ self.objectstore_factory = objectstore_factory
63
68
 
64
69
  def CreateNode(
65
70
  self, request: CreateNodeRequest, context: grpc.ServicerContext
@@ -28,6 +28,7 @@ from flwr.proto.serverappio_pb2_grpc import ( # pylint: disable=E0611
28
28
  )
29
29
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
30
30
  from flwr.server.superlink.linkstate import LinkStateFactory
31
+ from flwr.supercore.object_store import ObjectStoreFactory
31
32
 
32
33
  from .serverappio_servicer import ServerAppIoServicer
33
34
 
@@ -36,6 +37,7 @@ def run_serverappio_api_grpc(
36
37
  address: str,
37
38
  state_factory: LinkStateFactory,
38
39
  ffs_factory: FfsFactory,
40
+ objectstore_factory: ObjectStoreFactory,
39
41
  certificates: Optional[tuple[bytes, bytes, bytes]],
40
42
  ) -> grpc.Server:
41
43
  """Run ServerAppIo API (gRPC, request-response)."""
@@ -43,6 +45,7 @@ def run_serverappio_api_grpc(
43
45
  serverappio_servicer: grpc.Server = ServerAppIoServicer(
44
46
  state_factory=state_factory,
45
47
  ffs_factory=ffs_factory,
48
+ objectstore_factory=objectstore_factory,
46
49
  )
47
50
  serverappio_add_servicer_to_server_fn = add_ServerAppIoServicer_to_server
48
51
  serverappio_grpc_server = generic_create_grpc_server(
@@ -83,16 +83,21 @@ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
83
83
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
84
84
  from flwr.server.superlink.utils import abort_if
85
85
  from flwr.server.utils.validator import validate_message
86
+ from flwr.supercore.object_store import ObjectStoreFactory
86
87
 
87
88
 
88
89
  class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
89
90
  """ServerAppIo API servicer."""
90
91
 
91
92
  def __init__(
92
- self, state_factory: LinkStateFactory, ffs_factory: FfsFactory
93
+ self,
94
+ state_factory: LinkStateFactory,
95
+ ffs_factory: FfsFactory,
96
+ objectstore_factory: ObjectStoreFactory,
93
97
  ) -> None:
94
98
  self.state_factory = state_factory
95
99
  self.ffs_factory = ffs_factory
100
+ self.objectstore_factory = objectstore_factory
96
101
  self.lock = threading.RLock()
97
102
 
98
103
  def GetNodes(
@@ -0,0 +1,23 @@
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
+ """Flower ObjectStore."""
16
+
17
+ from .object_store import ObjectStore
18
+ from .object_store_factory import ObjectStoreFactory
19
+
20
+ __all__ = [
21
+ "ObjectStore",
22
+ "ObjectStoreFactory",
23
+ ]
@@ -0,0 +1,65 @@
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
+ """Flower in-memory ObjectStore implementation."""
16
+
17
+
18
+ from typing import Optional
19
+
20
+ from flwr.common.inflatable import get_object_id, is_valid_sha256_hash
21
+
22
+ from .object_store import ObjectStore
23
+
24
+
25
+ class InMemoryObjectStore(ObjectStore):
26
+ """In-memory implementation of the ObjectStore interface."""
27
+
28
+ def __init__(self, verify: bool = True) -> None:
29
+ self.verify = verify
30
+ self.store: dict[str, bytes] = {}
31
+
32
+ def put(self, object_id: str, object_content: bytes) -> None:
33
+ """Put an object into the store."""
34
+ # Verify object ID format (must be a valid sha256 hash)
35
+ if not is_valid_sha256_hash(object_id):
36
+ raise ValueError(f"Invalid object ID format: {object_id}")
37
+
38
+ # Verify object_id and object_content match
39
+ if self.verify:
40
+ object_id_from_content = get_object_id(object_content)
41
+ if object_id != object_id_from_content:
42
+ raise ValueError(f"Object ID {object_id} does not match content hash")
43
+
44
+ # Return if object is already present in the store
45
+ if object_id in self.store:
46
+ return
47
+
48
+ self.store[object_id] = object_content
49
+
50
+ def get(self, object_id: str) -> Optional[bytes]:
51
+ """Get an object from the store."""
52
+ return self.store.get(object_id)
53
+
54
+ def delete(self, object_id: str) -> None:
55
+ """Delete an object from the store."""
56
+ if object_id in self.store:
57
+ del self.store[object_id]
58
+
59
+ def clear(self) -> None:
60
+ """Clear the store."""
61
+ self.store.clear()
62
+
63
+ def __contains__(self, object_id: str) -> bool:
64
+ """Check if an object_id is in the store."""
65
+ return object_id in self.store
@@ -0,0 +1,86 @@
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
+ """Flower abstract ObjectStore definition."""
16
+
17
+
18
+ import abc
19
+ from typing import Optional
20
+
21
+
22
+ class ObjectStore(abc.ABC):
23
+ """Abstract base class for `ObjectStore` implementations.
24
+
25
+ This class defines the interface for an object store that can store, retrieve, and
26
+ delete objects identified by object IDs.
27
+ """
28
+
29
+ @abc.abstractmethod
30
+ def put(self, object_id: str, object_content: bytes) -> None:
31
+ """Put an object into the store.
32
+
33
+ Parameters
34
+ ----------
35
+ object_id : str
36
+ The object_id under which to store the object.
37
+ object_content : bytes
38
+ The deflated object to store.
39
+ """
40
+
41
+ @abc.abstractmethod
42
+ def get(self, object_id: str) -> Optional[bytes]:
43
+ """Get an object from the store.
44
+
45
+ Parameters
46
+ ----------
47
+ object_id : str
48
+ The object_id under which the object is stored.
49
+
50
+ Returns
51
+ -------
52
+ bytes
53
+ The object stored under the given object_id.
54
+ """
55
+
56
+ @abc.abstractmethod
57
+ def delete(self, object_id: str) -> None:
58
+ """Delete an object from the store.
59
+
60
+ Parameters
61
+ ----------
62
+ object_id : str
63
+ The object_id under which the object is stored.
64
+ """
65
+
66
+ @abc.abstractmethod
67
+ def clear(self) -> None:
68
+ """Clear the store.
69
+
70
+ This method should remove all objects from the store.
71
+ """
72
+
73
+ @abc.abstractmethod
74
+ def __contains__(self, object_id: str) -> bool:
75
+ """Check if an object_id is in the store.
76
+
77
+ Parameters
78
+ ----------
79
+ object_id : str
80
+ The object_id to check.
81
+
82
+ Returns
83
+ -------
84
+ bool
85
+ True if the object_id is in the store, False otherwise.
86
+ """
@@ -0,0 +1,44 @@
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
+ """Factory class that creates ObjectStore instances."""
16
+
17
+
18
+ from logging import DEBUG
19
+ from typing import Optional
20
+
21
+ from flwr.common.logger import log
22
+
23
+ from .in_memory_object_store import InMemoryObjectStore
24
+ from .object_store import ObjectStore
25
+
26
+
27
+ class ObjectStoreFactory:
28
+ """Factory class that creates ObjectStore instances."""
29
+
30
+ def __init__(self) -> None:
31
+ self.store_instance: Optional[ObjectStore] = None
32
+
33
+ def store(self) -> ObjectStore:
34
+ """Return an ObjectStore instance and create it, if necessary.
35
+
36
+ Returns
37
+ -------
38
+ ObjectStore
39
+ An ObjectStore instance for storing objects by object_id.
40
+ """
41
+ if self.store_instance is None:
42
+ self.store_instance = InMemoryObjectStore()
43
+ log(DEBUG, "Using InMemoryObjectStore")
44
+ return self.store_instance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.19.0.dev20250521
3
+ Version: 1.19.0.dev20250522
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
@@ -83,12 +83,10 @@ flwr/client/clientapp/clientappio_servicer.py,sha256=LmzkxtNQBn5vVrHc0Bhq2WqaK6-
83
83
  flwr/client/clientapp/utils.py,sha256=LsiW1OL2VPcjom3xN29pgBQC0UrttQ-xWL_GF1fkKDo,4344
84
84
  flwr/client/dpfedavg_numpy_client.py,sha256=3hul067cT2E9jBhzp7bFnFAZ_D2nWcIUEdHYE05FpzU,7404
85
85
  flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ81wvZm0-lOuuBA,742
86
- flwr/client/grpc_adapter_client/connection.py,sha256=JHhSjgVENVyNfLq3A-ET_k-Bc7q14g3jLtmIteJda0M,4006
87
- flwr/client/grpc_client/__init__.py,sha256=MDOckOODn-FJnkkFEfb2JO-2G97wrBr_TTqht-LhOcY,735
88
- flwr/client/grpc_client/connection.py,sha256=xAyvcTVr7bkwUfR5P3D_LKlZYiyySpt5sEwORA1h8Gc,9189
86
+ flwr/client/grpc_adapter_client/connection.py,sha256=aj5tTYyE8z2hQLXPPydsJiz8gBDIWLUhfWvqYkAL1L4,3966
89
87
  flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
90
88
  flwr/client/grpc_rere_client/client_interceptor.py,sha256=zFaVHw6AxeNO-7eCKKb-RxrPa7zbM5Z-2-1Efc4adQY,2451
91
- flwr/client/grpc_rere_client/connection.py,sha256=MCUkpx2KGETTPOrBGZfp2rktMOHmuPR4x0eqcevZapo,12064
89
+ flwr/client/grpc_rere_client/connection.py,sha256=ErDTr00KNJASx7hsI2sDFkj8fj8k5y2NFQ4wsYELe6Y,12024
92
90
  flwr/client/grpc_rere_client/grpc_adapter.py,sha256=JvMZ7vCFTaTEo6AzKYh3zDmeQAU7VSjdysbC6t3ufWg,6351
93
91
  flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
94
92
  flwr/client/message_handler/message_handler.py,sha256=-vZKGg2gP81182LFXDmiZtajLlIfZjV6FyMS43qQVwU,6532
@@ -102,11 +100,11 @@ flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=aKqjZCrikF73y3E-7h40
102
100
  flwr/client/mod/utils.py,sha256=FUgD2TfcWqSeF6jUKZ4i6Ke56U4Nrv85AeVb93s6R9g,1201
103
101
  flwr/client/numpy_client.py,sha256=Qq6ghsIAop2slKqAfgiI5NiHJ4LIxGmrik3Ror4_XVc,9581
104
102
  flwr/client/rest_client/__init__.py,sha256=MBiuK62hj439m9rtwSwI184Hth6Tt5GbmpNMyl3zkZY,735
105
- flwr/client/rest_client/connection.py,sha256=Xlf1eEMXq17VVVELPGPT1pqJKw8l0iq4Jnvz13v95C8,12806
103
+ flwr/client/rest_client/connection.py,sha256=6yBh2Eeso0XLtinAs2kOHkSnge7C-co_a_QfBaAEudU,12766
106
104
  flwr/client/run_info_store.py,sha256=MaJ3UQ-07hWtK67wnWu0zR29jrk0fsfgJX506dvEOfE,4042
107
- flwr/client/start_client_internal.py,sha256=OQBOUlXmb5fSCErD6bdYjl2R4vwhv30_fIyedKU0YG8,25266
105
+ flwr/client/start_client_internal.py,sha256=-FOBQE65a-ZsuTUiW8WcZoBZt9q_b3ee-JK5-H8ivME,19850
108
106
  flwr/client/supernode/__init__.py,sha256=i3gFbV5ie_FGyRMpzOvqtZAi0Z0ChIEJ7I2Kr0ym0PM,793
109
- flwr/client/supernode/app.py,sha256=pGHzFlidF4Y74zhFNTqCsB1Hl6x-bq4R2L1ktEZgXXI,8993
107
+ flwr/client/supernode/app.py,sha256=an-aT2zZEL5Mv7StgE1el0-fgIvKSQIuihJubRUuzyo,8753
110
108
  flwr/client/typing.py,sha256=Jw3rawDzI_-ZDcRmEQcs5gZModY7oeQlEeltYsdOhlU,1048
111
109
  flwr/clientapp/__init__.py,sha256=zGW4z49Ojzoi1hDiRC7kyhLjijUilc6fqHhtM_ATRVA,719
112
110
  flwr/common/__init__.py,sha256=5GCLVk399Az_rTJHNticRlL0Sl_oPw_j5_LuFKfX7-M,4171
@@ -129,8 +127,8 @@ flwr/common/exit/exit_code.py,sha256=PNEnCrZfOILjfDAFu5m-2YWEJBrk97xglq4zCUlqV7E
129
127
  flwr/common/exit_handlers.py,sha256=MEk5_savTLphn-6lW57UQlos-XrFA39XEBn-OF1vXXg,3174
130
128
  flwr/common/grpc.py,sha256=manTaHaPiyYngUq1ErZvvV2B2GxlXUUUGRy3jc3TBIQ,9798
131
129
  flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
132
- flwr/common/inflatable.py,sha256=ZKW4L2GMAxInUlbNK_zDZs7uW4-CuQui9TnWVglpDic,5279
133
- flwr/common/inflatable_grpc_utils.py,sha256=StkhGH8x9zR-p5MH52HdLG9MLzKv_rT8sPdbR9ZzNyE,3368
130
+ flwr/common/inflatable.py,sha256=i7tYRS5ql4630wN7O0YLCoWZMXk3iNQwOnnrZs4dlDE,5813
131
+ flwr/common/inflatable_grpc_utils.py,sha256=On_RT8cYChwE3-1MlY21s_cVXgYCGeKTONRafm-rmEk,3432
134
132
  flwr/common/logger.py,sha256=JbRf6E2vQxXzpDBq1T8IDUJo_usu3gjWEBPQ6uKcmdg,13049
135
133
  flwr/common/message.py,sha256=dfct6ZGizK2zSj2JLiQTRbOfDNu79KzwUplpQaxFg40,18997
136
134
  flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
@@ -161,7 +159,9 @@ flwr/common/typing.py,sha256=97QRfRRS7sQnjkAI5FDZ01-38oQUSz4i1qqewQmBWRg,6886
161
159
  flwr/common/version.py,sha256=7GAGzPn73Mkh09qhrjbmjZQtcqVhBuzhFBaK4Mk4VRk,1325
162
160
  flwr/compat/__init__.py,sha256=gbfDQKKKMZzi3GswyVRgyLdDlHiWj3wU6dg7y6m5O_s,752
163
161
  flwr/compat/client/__init__.py,sha256=qpbo0lcxdNL4qy5KHqiGm8OLxSxkYgI_-dLh5rwhtcI,746
164
- flwr/compat/client/app.py,sha256=2_-4oSkzbBdyF56PPWnBbzE9C7nG7UVaTd_5ftFQ2P8,34362
162
+ flwr/compat/client/app.py,sha256=49novX4eW9uZiU4zxIPNWzThcs0TuihygLtsT_T8Zjs,34397
163
+ flwr/compat/client/grpc_client/__init__.py,sha256=MDOckOODn-FJnkkFEfb2JO-2G97wrBr_TTqht-LhOcY,735
164
+ flwr/compat/client/grpc_client/connection.py,sha256=xAyvcTVr7bkwUfR5P3D_LKlZYiyySpt5sEwORA1h8Gc,9189
165
165
  flwr/compat/common/__init__.py,sha256=OMnKw4ad0qYMSIA9LZRa2gOkhSOXwAZCpAHnBQE_hFc,746
166
166
  flwr/compat/server/__init__.py,sha256=TGVSoOTuf5T5JHUVrK5wuorQF7L6Wvdem8B4uufvMJY,746
167
167
  flwr/compat/simulation/__init__.py,sha256=MApGa-tysDDw34iSdxZ7TWOKtGJM-z3i8fIRJa0qbZ8,750
@@ -228,7 +228,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
228
228
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
229
229
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
230
  flwr/server/__init__.py,sha256=e5PjIYUkzaQpcrUrvgb_6rDGzNqw1RfRsggdHlhfeyY,1569
231
- flwr/server/app.py,sha256=VeWKXtWwHS2tHhaI-qA3_8cvmeYt4JF-oabzh60WnyQ,33620
231
+ flwr/server/app.py,sha256=cWJuEhnizI_7XJObT8dTm6X8yvY-A9Idp6Co8jonSmU,34150
232
232
  flwr/server/client_manager.py,sha256=5jCGavVli7XdupvWWo7ru3PdFTlRU8IGvHFSSoUVLRs,6227
233
233
  flwr/server/client_proxy.py,sha256=sv0E9AldBYOvc3pusqFh-GnyreeMfsXQ1cuTtxTq_wY,2399
234
234
  flwr/server/compat/__init__.py,sha256=0IsttWvY15qO98_1GyzVC-vR1e_ZPXOdu2qUlOkYMPE,886
@@ -288,7 +288,7 @@ flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py,sha256=KouR9PUcrPmMtoLooF4O
288
288
  flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=iSf0mbBAlig7G6subQwBSVjcUCgSihONKdZ1RmQPTOk,4887
289
289
  flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=OsS-6GgCIzMMZDVu5Y-OKjynHVUrpdc_5OrtuB-IbU0,5174
290
290
  flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt2wB38_QltcpOC0gLbpFs,758
291
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=mUEEMqwkI3b5e7hYckRtMaYgARtRDPyrcCDv14-w6t4,6901
291
+ flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=--Xfd3FqBUIKHHQ1oS53Zal3rqqxrCORcpbFQeGSDlY,7081
292
292
  flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=DrHubsaLgJCwCeeJPYogQTiP0xYqjxwnT9rh7OP7BoU,6984
293
293
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
294
294
  flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=56LC_cPa_pZT1715WTCN0d7MmvdRXlyO7g_hb9zdJrA,5427
@@ -306,8 +306,8 @@ flwr/server/superlink/linkstate/linkstate_factory.py,sha256=8RlosqSpKOoD_vhUUQPY
306
306
  flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=z3VABMX_WtAioWJ2aUOsxi53-ecF2c8xEQ69xP3xXW8,43587
307
307
  flwr/server/superlink/linkstate/utils.py,sha256=AJs9jTAEK7JnjF2AODXnOfy0pKAKpe6oUWPCanAP57s,15382
308
308
  flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
309
- flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=opJ6SYwIAbu4NWEo3K-VxFO-tMSFmE4H3i2HwHIVRzw,2173
310
- flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=27ETy6-dW23Y6aH-7PpfmSX-0MJZC4qEWkQjdnEmrLU,14869
309
+ flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=6-FUUt0GiLcBPljj8bBrUNeAITUoDQOLzaMihKo52hg,2326
310
+ flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=EgTdKwpPZdAN6LnDNWy450aJQ-zh-bmcVTr739yRx_M,15049
311
311
  flwr/server/superlink/simulation/__init__.py,sha256=Ry8DrNaZCMcQXvUc4FoCN2m3dvUQgWjasfp015o3Ec4,718
312
312
  flwr/server/superlink/simulation/simulationio_grpc.py,sha256=0l0F-UjYEk6W7HZmI28PbJQLFxSi_vBHRkdchgdaSMQ,2224
313
313
  flwr/server/superlink/simulation/simulationio_servicer.py,sha256=aJezU8RSJswcmWm7Eoy0BqsU13jrcfuFwX3ljm-cORM,7719
@@ -333,6 +333,10 @@ flwr/simulation/ray_transport/utils.py,sha256=KrexpWYCF-dAF3UHc9yDbPQWO-ahMT-BbD
333
333
  flwr/simulation/run_simulation.py,sha256=Nvw_6hI71aE2nU95_tt1F9VSo3OJWrvA97e3XZuqE4k,20310
334
334
  flwr/simulation/simulationio_connection.py,sha256=mzS1C6EEREwQDPceDo30anAasmTDLb9qqV2tXlBhOUA,3494
335
335
  flwr/supercore/__init__.py,sha256=pqkFoow_E6UhbBlhmoD1gmTH-33yJRhBsIZqxRPFZ7U,755
336
+ flwr/supercore/object_store/__init__.py,sha256=7dvl-iNyZjEla9lLijc6Hn6HbdxXwsiA_ktF7PCYcSY,861
337
+ flwr/supercore/object_store/in_memory_object_store.py,sha256=ifHYpey40jQEFpnhl1SzukL9wqjxJNRdXbg1q_abnpM,2388
338
+ flwr/supercore/object_store/object_store.py,sha256=T24iVoWLM0D_hJw34jbuI4mxTaDW7PMBO2oKjCPeb_U,2460
339
+ flwr/supercore/object_store/object_store_factory.py,sha256=QVwE2ywi7vsj2iKfvWWnNw3N_I7Rz91NUt2RpcbJ7iM,1527
336
340
  flwr/superexec/__init__.py,sha256=YFqER0IJc1XEWfsX6AxZ9LSRq0sawPYrNYki-brvTIc,715
337
341
  flwr/superexec/app.py,sha256=U2jjOHb2LGWoU7vrl9_czTzre9O2mPxu3CPGUZ86sK4,1465
338
342
  flwr/superexec/deployment.py,sha256=2wBBZgdNAn1Ik1M3HGg4t23CV8oZqzDz1zkOBzHjZLE,6734
@@ -348,7 +352,7 @@ flwr/supernode/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUD
348
352
  flwr/supernode/nodestate/in_memory_nodestate.py,sha256=brV7TMMzS93tXk6ntpoYjtPK5qiSF3XD2W-uUdUVucc,1270
349
353
  flwr/supernode/nodestate/nodestate.py,sha256=-LAjZOnS7VyHC05ll3b31cYDjwAt6l4WmYt7duVLRKk,1024
350
354
  flwr/supernode/nodestate/nodestate_factory.py,sha256=UYTDCcwK_baHUmkzkJDxL0UEqvtTfOMlQRrROMCd0Xo,1430
351
- flwr_nightly-1.19.0.dev20250521.dist-info/METADATA,sha256=ly012I_t7MGbuecV64LFyv0MfIHht6Lp5QTkWuJF1-A,15910
352
- flwr_nightly-1.19.0.dev20250521.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
353
- flwr_nightly-1.19.0.dev20250521.dist-info/entry_points.txt,sha256=2-1L-GNKhwGw2_7_RoH55vHw2SIHjdAQy3HAVAWl9PY,374
354
- flwr_nightly-1.19.0.dev20250521.dist-info/RECORD,,
355
+ flwr_nightly-1.19.0.dev20250522.dist-info/METADATA,sha256=wBiKx7nd4BQMsaqKkTQJzxbEEdq0GLoD2aSx6Yy1jpw,15910
356
+ flwr_nightly-1.19.0.dev20250522.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
357
+ flwr_nightly-1.19.0.dev20250522.dist-info/entry_points.txt,sha256=2-1L-GNKhwGw2_7_RoH55vHw2SIHjdAQy3HAVAWl9PY,374
358
+ flwr_nightly-1.19.0.dev20250522.dist-info/RECORD,,
File without changes