flwr-nightly 1.19.0.dev20250520__py3-none-any.whl → 1.19.0.dev20250521__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/client/__init__.py +2 -2
- flwr/client/start_client_internal.py +608 -0
- flwr/client/supernode/app.py +1 -1
- flwr/common/message.py +82 -1
- flwr/common/serde.py +8 -56
- flwr/common/serde_utils.py +50 -0
- flwr/{client → compat/client}/app.py +12 -10
- flwr/server/superlink/linkstate/sqlite_linkstate.py +2 -6
- flwr/{client → supernode}/nodestate/in_memory_nodestate.py +1 -1
- {flwr_nightly-1.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/METADATA +1 -1
- {flwr_nightly-1.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/RECORD +16 -15
- /flwr/{client → supernode}/nodestate/__init__.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
- {flwr_nightly-1.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/entry_points.txt +0 -0
flwr/client/__init__.py
CHANGED
@@ -15,8 +15,8 @@
|
|
15
15
|
"""Flower client."""
|
16
16
|
|
17
17
|
|
18
|
-
from .app import start_client as start_client
|
19
|
-
from .app import start_numpy_client as start_numpy_client
|
18
|
+
from ..compat.client.app import start_client as start_client # Deprecated
|
19
|
+
from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
|
20
20
|
from .client import Client as Client
|
21
21
|
from .client_app import ClientApp as ClientApp
|
22
22
|
from .numpy_client import NumPyClient as NumPyClient
|
@@ -0,0 +1,608 @@
|
|
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
|
+
"""Main loop for Flower SuperNode."""
|
16
|
+
|
17
|
+
|
18
|
+
import multiprocessing
|
19
|
+
import os
|
20
|
+
import sys
|
21
|
+
import threading
|
22
|
+
import time
|
23
|
+
from contextlib import AbstractContextManager
|
24
|
+
from logging import ERROR, INFO, WARN
|
25
|
+
from os import urandom
|
26
|
+
from pathlib import Path
|
27
|
+
from typing import Callable, Optional, Union, cast
|
28
|
+
|
29
|
+
import grpc
|
30
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
31
|
+
from grpc import RpcError
|
32
|
+
|
33
|
+
from flwr.app.error import Error
|
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
|
+
from flwr.client.clientapp.app import flwr_clientapp
|
39
|
+
from flwr.client.clientapp.clientappio_servicer import (
|
40
|
+
ClientAppInputs,
|
41
|
+
ClientAppIoServicer,
|
42
|
+
)
|
43
|
+
from flwr.client.grpc_adapter_client.connection import grpc_adapter
|
44
|
+
from flwr.client.grpc_client.connection import grpc_connection
|
45
|
+
from flwr.client.grpc_rere_client.connection import grpc_request_response
|
46
|
+
from flwr.client.message_handler.message_handler import handle_control_message
|
47
|
+
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
|
50
|
+
from flwr.common.address import parse_address
|
51
|
+
from flwr.common.constant import (
|
52
|
+
CLIENT_OCTET,
|
53
|
+
CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
54
|
+
ISOLATION_MODE_PROCESS,
|
55
|
+
ISOLATION_MODE_SUBPROCESS,
|
56
|
+
MAX_RETRY_DELAY,
|
57
|
+
RUN_ID_NUM_BYTES,
|
58
|
+
SERVER_OCTET,
|
59
|
+
TRANSPORT_TYPE_GRPC_ADAPTER,
|
60
|
+
TRANSPORT_TYPE_GRPC_BIDI,
|
61
|
+
TRANSPORT_TYPE_GRPC_RERE,
|
62
|
+
TRANSPORT_TYPE_REST,
|
63
|
+
TRANSPORT_TYPES,
|
64
|
+
ErrorCode,
|
65
|
+
)
|
66
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
67
|
+
from flwr.common.grpc import generic_create_grpc_server
|
68
|
+
from flwr.common.logger import log
|
69
|
+
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
70
|
+
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
71
|
+
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
72
|
+
from flwr.supernode.nodestate import NodeStateFactory
|
73
|
+
|
74
|
+
|
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
|
+
# pylint: disable=import-outside-toplevel
|
90
|
+
# pylint: disable=too-many-branches
|
91
|
+
# pylint: disable=too-many-locals
|
92
|
+
# pylint: disable=too-many-statements
|
93
|
+
# pylint: disable=too-many-arguments
|
94
|
+
def start_client_internal(
|
95
|
+
*,
|
96
|
+
server_address: str,
|
97
|
+
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
|
+
grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
102
|
+
root_certificates: Optional[Union[bytes, str]] = None,
|
103
|
+
insecure: Optional[bool] = None,
|
104
|
+
transport: Optional[str] = None,
|
105
|
+
authentication_keys: Optional[
|
106
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
107
|
+
] = None,
|
108
|
+
max_retries: Optional[int] = None,
|
109
|
+
max_wait_time: Optional[float] = None,
|
110
|
+
flwr_path: Optional[Path] = None,
|
111
|
+
isolation: Optional[str] = None,
|
112
|
+
clientappio_api_address: Optional[str] = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
113
|
+
) -> None:
|
114
|
+
"""Start a Flower client node which connects to a Flower server.
|
115
|
+
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
server_address : str
|
119
|
+
The IPv4 or IPv6 address of the server. If the Flower
|
120
|
+
server runs on the same machine on port 8080, then `server_address`
|
121
|
+
would be `"[::]:8080"`.
|
122
|
+
node_config: UserConfig
|
123
|
+
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
|
+
grpc_max_message_length : int (default: 536_870_912, this equals 512MB)
|
132
|
+
The maximum length of gRPC messages that can be exchanged with the
|
133
|
+
Flower server. The default should be sufficient for most models.
|
134
|
+
Users who train very large models might need to increase this
|
135
|
+
value. Note that the Flower server needs to be started with the
|
136
|
+
same value (see `flwr.server.start_server`), otherwise it will not
|
137
|
+
know about the increased limit and block larger messages.
|
138
|
+
root_certificates : Optional[Union[bytes, str]] (default: None)
|
139
|
+
The PEM-encoded root certificates as a byte string or a path string.
|
140
|
+
If provided, a secure connection using the certificates will be
|
141
|
+
established to an SSL-enabled Flower server.
|
142
|
+
insecure : Optional[bool] (default: None)
|
143
|
+
Starts an insecure gRPC connection when True. Enables HTTPS connection
|
144
|
+
when False, using system certificates if `root_certificates` is None.
|
145
|
+
transport : Optional[str] (default: None)
|
146
|
+
Configure the transport layer. Allowed values:
|
147
|
+
- 'grpc-bidi': gRPC, bidirectional streaming
|
148
|
+
- 'grpc-rere': gRPC, request-response (experimental)
|
149
|
+
- 'rest': HTTP (experimental)
|
150
|
+
authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
|
151
|
+
Tuple containing the elliptic curve private key and public key for
|
152
|
+
authentication from the cryptography library.
|
153
|
+
Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
|
154
|
+
Used to establish an authenticated connection with the server.
|
155
|
+
max_retries: Optional[int] (default: None)
|
156
|
+
The maximum number of times the client will try to connect to the
|
157
|
+
server before giving up in case of a connection error. If set to None,
|
158
|
+
there is no limit to the number of tries.
|
159
|
+
max_wait_time: Optional[float] (default: None)
|
160
|
+
The maximum duration before the client stops trying to
|
161
|
+
connect to the server in case of connection error.
|
162
|
+
If set to None, there is no limit to the total time.
|
163
|
+
flwr_path: Optional[Path] (default: None)
|
164
|
+
The fully resolved path containing installed Flower Apps.
|
165
|
+
isolation : Optional[str] (default: None)
|
166
|
+
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
|
169
|
+
by the SueprNode and communicates using gRPC at the address
|
170
|
+
`clientappio_api_address`. If `process`, the `ClientApp` runs in a separate
|
171
|
+
isolated process and communicates using gRPC at the address
|
172
|
+
`clientappio_api_address`.
|
173
|
+
clientappio_api_address : Optional[str]
|
174
|
+
(default: `CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS`)
|
175
|
+
The SuperNode gRPC server address.
|
176
|
+
"""
|
177
|
+
if insecure is None:
|
178
|
+
insecure = root_certificates is None
|
179
|
+
|
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
|
215
|
+
|
216
|
+
# Initialize connection context manager
|
217
|
+
connection, address, connection_error_type = _init_connection(
|
218
|
+
transport, server_address
|
219
|
+
)
|
220
|
+
|
221
|
+
def _on_sucess(retry_state: RetryState) -> None:
|
222
|
+
if retry_state.tries > 1:
|
223
|
+
log(
|
224
|
+
INFO,
|
225
|
+
"Connection successful after %.2f seconds and %s tries.",
|
226
|
+
retry_state.elapsed_time,
|
227
|
+
retry_state.tries,
|
228
|
+
)
|
229
|
+
|
230
|
+
def _on_backoff(retry_state: RetryState) -> None:
|
231
|
+
if retry_state.tries == 1:
|
232
|
+
log(WARN, "Connection attempt failed, retrying...")
|
233
|
+
else:
|
234
|
+
log(
|
235
|
+
WARN,
|
236
|
+
"Connection attempt failed, retrying in %.2f seconds",
|
237
|
+
retry_state.actual_wait,
|
238
|
+
)
|
239
|
+
|
240
|
+
retry_invoker = RetryInvoker(
|
241
|
+
wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
|
242
|
+
recoverable_exceptions=connection_error_type,
|
243
|
+
max_tries=max_retries + 1 if max_retries is not None else None,
|
244
|
+
max_time=max_wait_time,
|
245
|
+
on_giveup=lambda retry_state: (
|
246
|
+
log(
|
247
|
+
WARN,
|
248
|
+
"Giving up reconnection after %.2f seconds and %s tries.",
|
249
|
+
retry_state.elapsed_time,
|
250
|
+
retry_state.tries,
|
251
|
+
)
|
252
|
+
if retry_state.tries > 1
|
253
|
+
else None
|
254
|
+
),
|
255
|
+
on_success=_on_sucess,
|
256
|
+
on_backoff=_on_backoff,
|
257
|
+
)
|
258
|
+
|
259
|
+
# DeprecatedRunInfoStore gets initialized when the first connection is established
|
260
|
+
run_info_store: Optional[DeprecatedRunInfoStore] = None
|
261
|
+
state_factory = NodeStateFactory()
|
262
|
+
state = state_factory.state()
|
263
|
+
mp_spawn_context = multiprocessing.get_context("spawn")
|
264
|
+
|
265
|
+
runs: dict[int, Run] = {}
|
266
|
+
|
267
|
+
while True:
|
268
|
+
sleep_duration: int = 0
|
269
|
+
with connection(
|
270
|
+
address,
|
271
|
+
insecure,
|
272
|
+
retry_invoker,
|
273
|
+
grpc_max_message_length,
|
274
|
+
root_certificates,
|
275
|
+
authentication_keys,
|
276
|
+
) as conn:
|
277
|
+
receive, send, create_node, delete_node, get_run, get_fab = conn
|
278
|
+
|
279
|
+
# Register node when connecting the first time
|
280
|
+
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
|
+
)
|
305
|
+
|
306
|
+
# pylint: disable=too-many-nested-blocks
|
307
|
+
while True:
|
308
|
+
try:
|
309
|
+
# Receive
|
310
|
+
message = receive()
|
311
|
+
if message is None:
|
312
|
+
time.sleep(3) # Wait for 3s before asking again
|
313
|
+
continue
|
314
|
+
|
315
|
+
log(INFO, "")
|
316
|
+
if len(message.metadata.group_id) > 0:
|
317
|
+
log(
|
318
|
+
INFO,
|
319
|
+
"[RUN %s, ROUND %s]",
|
320
|
+
message.metadata.run_id,
|
321
|
+
message.metadata.group_id,
|
322
|
+
)
|
323
|
+
log(
|
324
|
+
INFO,
|
325
|
+
"Received: %s message %s",
|
326
|
+
message.metadata.message_type,
|
327
|
+
message.metadata.message_id,
|
328
|
+
)
|
329
|
+
|
330
|
+
# Handle control message
|
331
|
+
out_message, sleep_duration = handle_control_message(message)
|
332
|
+
if out_message:
|
333
|
+
send(out_message)
|
334
|
+
break
|
335
|
+
|
336
|
+
# Get run info
|
337
|
+
run_id = message.metadata.run_id
|
338
|
+
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)
|
344
|
+
|
345
|
+
run: Run = runs[run_id]
|
346
|
+
if get_fab is not None and run.fab_hash:
|
347
|
+
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
|
+
fab_id, fab_version = get_fab_metadata(fab.content)
|
352
|
+
else:
|
353
|
+
fab = None
|
354
|
+
fab_id, fab_version = run.fab_id, run.fab_version
|
355
|
+
|
356
|
+
run.fab_id, run.fab_version = fab_id, fab_version
|
357
|
+
|
358
|
+
# Register context for this run
|
359
|
+
run_info_store.register_context(
|
360
|
+
run_id=run_id,
|
361
|
+
run=run,
|
362
|
+
flwr_path=flwr_path,
|
363
|
+
fab=fab,
|
364
|
+
)
|
365
|
+
|
366
|
+
# Retrieve context for this run
|
367
|
+
context = run_info_store.retrieve_context(run_id=run_id)
|
368
|
+
# Create an error reply message that will never be used to prevent
|
369
|
+
# the used-before-assignment linting error
|
370
|
+
reply_message = Message(
|
371
|
+
Error(code=ErrorCode.UNKNOWN, reason="Unknown"),
|
372
|
+
reply_to=message,
|
373
|
+
)
|
374
|
+
|
375
|
+
# Handle app loading and task message
|
376
|
+
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
|
+
)
|
402
|
+
|
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
|
440
|
+
)
|
441
|
+
|
442
|
+
# Execute ClientApp
|
443
|
+
reply_message = client_app(message=message, context=context)
|
444
|
+
except Exception as ex: # pylint: disable=broad-exception-caught
|
445
|
+
|
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
|
+
# Don't update/change DeprecatedRunInfoStore
|
453
|
+
|
454
|
+
e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION
|
455
|
+
# Ex fmt: "<class 'ZeroDivisionError'>:<'division by zero'>"
|
456
|
+
reason = str(type(ex)) + ":<'" + str(ex) + "'>"
|
457
|
+
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
|
+
|
466
|
+
log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
|
467
|
+
|
468
|
+
# Create error message
|
469
|
+
reply_message = Message(
|
470
|
+
Error(code=e_code, reason=reason),
|
471
|
+
reply_to=message,
|
472
|
+
)
|
473
|
+
else:
|
474
|
+
# No exception, update node state
|
475
|
+
run_info_store.update_context(
|
476
|
+
run_id=run_id,
|
477
|
+
context=context,
|
478
|
+
)
|
479
|
+
|
480
|
+
# Send
|
481
|
+
send(reply_message)
|
482
|
+
log(INFO, "Sent reply")
|
483
|
+
|
484
|
+
except RunNotRunningException:
|
485
|
+
log(INFO, "")
|
486
|
+
log(
|
487
|
+
INFO,
|
488
|
+
"SuperNode aborted sending the reply message. "
|
489
|
+
"Run ID %s is not in `RUNNING` status.",
|
490
|
+
run_id,
|
491
|
+
)
|
492
|
+
log(INFO, "")
|
493
|
+
# pylint: enable=too-many-nested-blocks
|
494
|
+
|
495
|
+
# Unregister node
|
496
|
+
if delete_node is not None:
|
497
|
+
delete_node() # pylint: disable=not-callable
|
498
|
+
|
499
|
+
if sleep_duration == 0:
|
500
|
+
log(INFO, "Disconnect and shut down")
|
501
|
+
break
|
502
|
+
|
503
|
+
# Sleep and reconnect afterwards
|
504
|
+
log(
|
505
|
+
INFO,
|
506
|
+
"Disconnect, then re-establish connection after %s second(s)",
|
507
|
+
sleep_duration,
|
508
|
+
)
|
509
|
+
time.sleep(sleep_duration)
|
510
|
+
|
511
|
+
|
512
|
+
def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
513
|
+
Callable[
|
514
|
+
[
|
515
|
+
str,
|
516
|
+
bool,
|
517
|
+
RetryInvoker,
|
518
|
+
int,
|
519
|
+
Union[bytes, str, None],
|
520
|
+
Optional[tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]],
|
521
|
+
],
|
522
|
+
AbstractContextManager[
|
523
|
+
tuple[
|
524
|
+
Callable[[], Optional[Message]],
|
525
|
+
Callable[[Message], None],
|
526
|
+
Optional[Callable[[], Optional[int]]],
|
527
|
+
Optional[Callable[[], None]],
|
528
|
+
Optional[Callable[[int], Run]],
|
529
|
+
Optional[Callable[[str, int], Fab]],
|
530
|
+
]
|
531
|
+
],
|
532
|
+
],
|
533
|
+
str,
|
534
|
+
type[Exception],
|
535
|
+
]:
|
536
|
+
# Parse IP address
|
537
|
+
parsed_address = parse_address(server_address)
|
538
|
+
if not parsed_address:
|
539
|
+
flwr_exit(
|
540
|
+
ExitCode.COMMON_ADDRESS_INVALID,
|
541
|
+
f"SuperLink address ({server_address}) cannot be parsed.",
|
542
|
+
)
|
543
|
+
host, port, is_v6 = parsed_address
|
544
|
+
address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}"
|
545
|
+
|
546
|
+
# Set the default transport layer
|
547
|
+
if transport is None:
|
548
|
+
transport = TRANSPORT_TYPE_GRPC_BIDI
|
549
|
+
|
550
|
+
# Use either gRPC bidirectional streaming or REST request/response
|
551
|
+
if transport == TRANSPORT_TYPE_REST:
|
552
|
+
try:
|
553
|
+
from requests.exceptions import ConnectionError as RequestsConnectionError
|
554
|
+
|
555
|
+
from flwr.client.rest_client.connection import http_request_response
|
556
|
+
except ModuleNotFoundError:
|
557
|
+
flwr_exit(ExitCode.COMMON_MISSING_EXTRA_REST)
|
558
|
+
if server_address[:4] != "http":
|
559
|
+
flwr_exit(ExitCode.SUPERNODE_REST_ADDRESS_INVALID)
|
560
|
+
connection, error_type = http_request_response, RequestsConnectionError
|
561
|
+
elif transport == TRANSPORT_TYPE_GRPC_RERE:
|
562
|
+
connection, error_type = grpc_request_response, RpcError
|
563
|
+
elif transport == TRANSPORT_TYPE_GRPC_ADAPTER:
|
564
|
+
connection, error_type = grpc_adapter, RpcError
|
565
|
+
elif transport == TRANSPORT_TYPE_GRPC_BIDI:
|
566
|
+
connection, error_type = grpc_connection, RpcError
|
567
|
+
else:
|
568
|
+
raise ValueError(
|
569
|
+
f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES})"
|
570
|
+
)
|
571
|
+
|
572
|
+
return connection, address, error_type
|
573
|
+
|
574
|
+
|
575
|
+
def _run_flwr_clientapp(args: list[str], main_pid: int) -> None:
|
576
|
+
# Monitor the main process in case of SIGKILL
|
577
|
+
def main_process_monitor() -> None:
|
578
|
+
while True:
|
579
|
+
time.sleep(1)
|
580
|
+
if os.getppid() != main_pid:
|
581
|
+
os.kill(os.getpid(), 9)
|
582
|
+
|
583
|
+
threading.Thread(target=main_process_monitor, daemon=True).start()
|
584
|
+
|
585
|
+
# Run the command
|
586
|
+
sys.argv = args
|
587
|
+
flwr_clientapp()
|
588
|
+
|
589
|
+
|
590
|
+
def run_clientappio_api_grpc(
|
591
|
+
address: str,
|
592
|
+
certificates: Optional[tuple[bytes, bytes, bytes]],
|
593
|
+
) -> tuple[grpc.Server, ClientAppIoServicer]:
|
594
|
+
"""Run ClientAppIo API gRPC server."""
|
595
|
+
clientappio_servicer: grpc.Server = ClientAppIoServicer()
|
596
|
+
clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
|
597
|
+
clientappio_grpc_server = generic_create_grpc_server(
|
598
|
+
servicer_and_add_fn=(
|
599
|
+
clientappio_servicer,
|
600
|
+
clientappio_add_servicer_to_server_fn,
|
601
|
+
),
|
602
|
+
server_address=address,
|
603
|
+
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
604
|
+
certificates=certificates,
|
605
|
+
)
|
606
|
+
log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
|
607
|
+
clientappio_grpc_server.start()
|
608
|
+
return clientappio_grpc_server, clientappio_servicer
|
flwr/client/supernode/app.py
CHANGED
@@ -43,8 +43,8 @@ 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 ..app import start_client_internal
|
47
46
|
from ..clientapp.utils import get_load_client_app_fn
|
47
|
+
from ..start_client_internal import start_client_internal
|
48
48
|
|
49
49
|
|
50
50
|
def run_supernode() -> None:
|
flwr/common/message.py
CHANGED
@@ -22,12 +22,25 @@ from typing import Any, cast, overload
|
|
22
22
|
|
23
23
|
from flwr.common.date import now
|
24
24
|
from flwr.common.logger import warn_deprecated_feature
|
25
|
+
from flwr.proto.message_pb2 import Message as ProtoMessage # pylint: disable=E0611
|
25
26
|
|
26
27
|
from ..app.error import Error
|
27
28
|
from ..app.metadata import Metadata
|
28
29
|
from .constant import MESSAGE_TTL_TOLERANCE
|
30
|
+
from .inflatable import (
|
31
|
+
InflatableObject,
|
32
|
+
add_header_to_object_body,
|
33
|
+
get_object_body,
|
34
|
+
get_object_children_ids_from_object_content,
|
35
|
+
)
|
29
36
|
from .logger import log
|
30
37
|
from .record import RecordDict
|
38
|
+
from .serde_utils import (
|
39
|
+
error_from_proto,
|
40
|
+
error_to_proto,
|
41
|
+
metadata_from_proto,
|
42
|
+
metadata_to_proto,
|
43
|
+
)
|
31
44
|
|
32
45
|
DEFAULT_TTL = 43200 # This is 12 hours
|
33
46
|
MESSAGE_INIT_ERROR_MESSAGE = (
|
@@ -58,7 +71,7 @@ class MessageInitializationError(TypeError):
|
|
58
71
|
super().__init__(message or MESSAGE_INIT_ERROR_MESSAGE)
|
59
72
|
|
60
73
|
|
61
|
-
class Message:
|
74
|
+
class Message(InflatableObject):
|
62
75
|
"""Represents a message exchanged between ClientApp and ServerApp.
|
63
76
|
|
64
77
|
This class encapsulates the payload and metadata necessary for communication
|
@@ -331,6 +344,74 @@ class Message:
|
|
331
344
|
)
|
332
345
|
return f"{self.__class__.__qualname__}({view})"
|
333
346
|
|
347
|
+
@property
|
348
|
+
def children(self) -> dict[str, InflatableObject] | None:
|
349
|
+
"""Return a dictionary of a single RecordDict with its Object IDs as key."""
|
350
|
+
return {self.content.object_id: self.content} if self.has_content() else None
|
351
|
+
|
352
|
+
def deflate(self) -> bytes:
|
353
|
+
"""Deflate message."""
|
354
|
+
# Store message metadata and error in object body
|
355
|
+
obj_body = ProtoMessage(
|
356
|
+
metadata=metadata_to_proto(self.metadata),
|
357
|
+
content=None,
|
358
|
+
error=error_to_proto(self.error) if self.has_error() else None,
|
359
|
+
).SerializeToString(deterministic=True)
|
360
|
+
|
361
|
+
return add_header_to_object_body(object_body=obj_body, obj=self)
|
362
|
+
|
363
|
+
@classmethod
|
364
|
+
def inflate(
|
365
|
+
cls, object_content: bytes, children: dict[str, InflatableObject] | None = None
|
366
|
+
) -> Message:
|
367
|
+
"""Inflate an Message from bytes.
|
368
|
+
|
369
|
+
Parameters
|
370
|
+
----------
|
371
|
+
object_content : bytes
|
372
|
+
The deflated object content of the Message.
|
373
|
+
children : Optional[dict[str, InflatableObject]] (default: None)
|
374
|
+
Dictionary of children InflatableObjects mapped to their Object IDs.
|
375
|
+
These children enable the full inflation of the Message.
|
376
|
+
|
377
|
+
Returns
|
378
|
+
-------
|
379
|
+
Message
|
380
|
+
The inflated Message.
|
381
|
+
"""
|
382
|
+
if children is None:
|
383
|
+
children = {}
|
384
|
+
|
385
|
+
# Get the children id from the deflated message
|
386
|
+
children_ids = get_object_children_ids_from_object_content(object_content)
|
387
|
+
|
388
|
+
# If the message had content, only one children is possible
|
389
|
+
# If the message carried an error, the returned listed should be empty
|
390
|
+
if children_ids != list(children.keys()):
|
391
|
+
raise ValueError(
|
392
|
+
f"Mismatch in children object IDs: expected {children_ids}, but "
|
393
|
+
f"received {list(children.keys())}. The provided children must exactly "
|
394
|
+
"match the IDs specified in the object head."
|
395
|
+
)
|
396
|
+
|
397
|
+
# Inflate content
|
398
|
+
obj_body = get_object_body(object_content, cls)
|
399
|
+
proto_message = ProtoMessage.FromString(obj_body)
|
400
|
+
|
401
|
+
# Prepare content if error wasn't set in protobuf message
|
402
|
+
if proto_message.HasField("error"):
|
403
|
+
content = None
|
404
|
+
error = error_from_proto(proto_message.error)
|
405
|
+
else:
|
406
|
+
content = cast(RecordDict, children[children_ids[0]])
|
407
|
+
error = None
|
408
|
+
# Return message
|
409
|
+
return make_message(
|
410
|
+
metadata=metadata_from_proto(proto_message.metadata),
|
411
|
+
content=content,
|
412
|
+
error=error,
|
413
|
+
)
|
414
|
+
|
334
415
|
|
335
416
|
def make_message(
|
336
417
|
metadata: Metadata, content: RecordDict | None = None, error: Error | None = None
|
flwr/common/serde.py
CHANGED
@@ -20,11 +20,9 @@ from typing import Any, cast
|
|
20
20
|
|
21
21
|
# pylint: disable=E0611
|
22
22
|
from flwr.proto.clientappio_pb2 import ClientAppOutputCode, ClientAppOutputStatus
|
23
|
-
from flwr.proto.error_pb2 import Error as ProtoError
|
24
23
|
from flwr.proto.fab_pb2 import Fab as ProtoFab
|
25
24
|
from flwr.proto.message_pb2 import Context as ProtoContext
|
26
25
|
from flwr.proto.message_pb2 import Message as ProtoMessage
|
27
|
-
from flwr.proto.message_pb2 import Metadata as ProtoMetadata
|
28
26
|
from flwr.proto.recorddict_pb2 import Array as ProtoArray
|
29
27
|
from flwr.proto.recorddict_pb2 import ArrayRecord as ProtoArrayRecord
|
30
28
|
from flwr.proto.recorddict_pb2 import ConfigRecord as ProtoConfigRecord
|
@@ -44,9 +42,6 @@ from flwr.proto.transport_pb2 import (
|
|
44
42
|
Status,
|
45
43
|
)
|
46
44
|
|
47
|
-
from ..app.error import Error
|
48
|
-
from ..app.metadata import Metadata
|
49
|
-
|
50
45
|
# pylint: enable=E0611
|
51
46
|
from . import (
|
52
47
|
Array,
|
@@ -59,7 +54,14 @@ from . import (
|
|
59
54
|
)
|
60
55
|
from .constant import INT64_MAX_VALUE
|
61
56
|
from .message import Message, make_message
|
62
|
-
from .serde_utils import
|
57
|
+
from .serde_utils import (
|
58
|
+
error_from_proto,
|
59
|
+
error_to_proto,
|
60
|
+
metadata_from_proto,
|
61
|
+
metadata_to_proto,
|
62
|
+
record_value_dict_from_proto,
|
63
|
+
record_value_dict_to_proto,
|
64
|
+
)
|
63
65
|
|
64
66
|
# === Parameters message ===
|
65
67
|
|
@@ -449,21 +451,6 @@ def config_record_from_proto(record_proto: ProtoConfigRecord) -> ConfigRecord:
|
|
449
451
|
)
|
450
452
|
|
451
453
|
|
452
|
-
# === Error message ===
|
453
|
-
|
454
|
-
|
455
|
-
def error_to_proto(error: Error) -> ProtoError:
|
456
|
-
"""Serialize Error to ProtoBuf."""
|
457
|
-
reason = error.reason if error.reason else ""
|
458
|
-
return ProtoError(code=error.code, reason=reason)
|
459
|
-
|
460
|
-
|
461
|
-
def error_from_proto(error_proto: ProtoError) -> Error:
|
462
|
-
"""Deserialize Error from ProtoBuf."""
|
463
|
-
reason = error_proto.reason if len(error_proto.reason) > 0 else None
|
464
|
-
return Error(code=error_proto.code, reason=reason)
|
465
|
-
|
466
|
-
|
467
454
|
# === RecordDict message ===
|
468
455
|
|
469
456
|
|
@@ -552,41 +539,6 @@ def user_config_value_from_proto(scalar_msg: Scalar) -> typing.UserConfigValue:
|
|
552
539
|
return cast(typing.UserConfigValue, scalar)
|
553
540
|
|
554
541
|
|
555
|
-
# === Metadata messages ===
|
556
|
-
|
557
|
-
|
558
|
-
def metadata_to_proto(metadata: Metadata) -> ProtoMetadata:
|
559
|
-
"""Serialize `Metadata` to ProtoBuf."""
|
560
|
-
proto = ProtoMetadata( # pylint: disable=E1101
|
561
|
-
run_id=metadata.run_id,
|
562
|
-
message_id=metadata.message_id,
|
563
|
-
src_node_id=metadata.src_node_id,
|
564
|
-
dst_node_id=metadata.dst_node_id,
|
565
|
-
reply_to_message_id=metadata.reply_to_message_id,
|
566
|
-
group_id=metadata.group_id,
|
567
|
-
ttl=metadata.ttl,
|
568
|
-
message_type=metadata.message_type,
|
569
|
-
created_at=metadata.created_at,
|
570
|
-
)
|
571
|
-
return proto
|
572
|
-
|
573
|
-
|
574
|
-
def metadata_from_proto(metadata_proto: ProtoMetadata) -> Metadata:
|
575
|
-
"""Deserialize `Metadata` from ProtoBuf."""
|
576
|
-
metadata = Metadata(
|
577
|
-
run_id=metadata_proto.run_id,
|
578
|
-
message_id=metadata_proto.message_id,
|
579
|
-
src_node_id=metadata_proto.src_node_id,
|
580
|
-
dst_node_id=metadata_proto.dst_node_id,
|
581
|
-
reply_to_message_id=metadata_proto.reply_to_message_id,
|
582
|
-
group_id=metadata_proto.group_id,
|
583
|
-
created_at=metadata_proto.created_at,
|
584
|
-
ttl=metadata_proto.ttl,
|
585
|
-
message_type=metadata_proto.message_type,
|
586
|
-
)
|
587
|
-
return metadata
|
588
|
-
|
589
|
-
|
590
542
|
# === Message messages ===
|
591
543
|
|
592
544
|
|
flwr/common/serde_utils.py
CHANGED
@@ -20,6 +20,8 @@ from typing import Any, TypeVar, cast
|
|
20
20
|
from google.protobuf.message import Message as GrpcMessage
|
21
21
|
|
22
22
|
# pylint: disable=E0611
|
23
|
+
from flwr.proto.error_pb2 import Error as ProtoError
|
24
|
+
from flwr.proto.message_pb2 import Metadata as ProtoMetadata
|
23
25
|
from flwr.proto.recorddict_pb2 import (
|
24
26
|
BoolList,
|
25
27
|
BytesList,
|
@@ -29,9 +31,13 @@ from flwr.proto.recorddict_pb2 import (
|
|
29
31
|
UintList,
|
30
32
|
)
|
31
33
|
|
34
|
+
from ..app.error import Error
|
35
|
+
from ..app.metadata import Metadata
|
32
36
|
from .constant import INT64_MAX_VALUE
|
33
37
|
from .record.typeddict import TypedDict
|
34
38
|
|
39
|
+
# pylint: enable=E0611
|
40
|
+
|
35
41
|
_type_to_field: dict[type, str] = {
|
36
42
|
float: "double",
|
37
43
|
int: "sint64",
|
@@ -121,3 +127,47 @@ def record_value_dict_from_proto(
|
|
121
127
|
) -> dict[str, Any]:
|
122
128
|
"""Deserialize the record value dict from ProtoBuf."""
|
123
129
|
return {k: _record_value_from_proto(v) for k, v in value_dict_proto.items()}
|
130
|
+
|
131
|
+
|
132
|
+
def error_to_proto(error: Error) -> ProtoError:
|
133
|
+
"""Serialize Error to ProtoBuf."""
|
134
|
+
reason = error.reason if error.reason else ""
|
135
|
+
return ProtoError(code=error.code, reason=reason)
|
136
|
+
|
137
|
+
|
138
|
+
def error_from_proto(error_proto: ProtoError) -> Error:
|
139
|
+
"""Deserialize Error from ProtoBuf."""
|
140
|
+
reason = error_proto.reason if len(error_proto.reason) > 0 else None
|
141
|
+
return Error(code=error_proto.code, reason=reason)
|
142
|
+
|
143
|
+
|
144
|
+
def metadata_to_proto(metadata: Metadata) -> ProtoMetadata:
|
145
|
+
"""Serialize `Metadata` to ProtoBuf."""
|
146
|
+
proto = ProtoMetadata( # pylint: disable=E1101
|
147
|
+
run_id=metadata.run_id,
|
148
|
+
message_id=metadata.message_id,
|
149
|
+
src_node_id=metadata.src_node_id,
|
150
|
+
dst_node_id=metadata.dst_node_id,
|
151
|
+
reply_to_message_id=metadata.reply_to_message_id,
|
152
|
+
group_id=metadata.group_id,
|
153
|
+
ttl=metadata.ttl,
|
154
|
+
message_type=metadata.message_type,
|
155
|
+
created_at=metadata.created_at,
|
156
|
+
)
|
157
|
+
return proto
|
158
|
+
|
159
|
+
|
160
|
+
def metadata_from_proto(metadata_proto: ProtoMetadata) -> Metadata:
|
161
|
+
"""Deserialize `Metadata` from ProtoBuf."""
|
162
|
+
metadata = Metadata(
|
163
|
+
run_id=metadata_proto.run_id,
|
164
|
+
message_id=metadata_proto.message_id,
|
165
|
+
src_node_id=metadata_proto.src_node_id,
|
166
|
+
dst_node_id=metadata_proto.dst_node_id,
|
167
|
+
reply_to_message_id=metadata_proto.reply_to_message_id,
|
168
|
+
group_id=metadata_proto.group_id,
|
169
|
+
created_at=metadata_proto.created_at,
|
170
|
+
ttl=metadata_proto.ttl,
|
171
|
+
message_type=metadata_proto.message_type,
|
172
|
+
)
|
173
|
+
return metadata
|
@@ -36,7 +36,16 @@ from flwr.cli.install import install_from_fab
|
|
36
36
|
from flwr.client.client import Client
|
37
37
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
38
38
|
from flwr.client.clientapp.app import flwr_clientapp
|
39
|
-
from flwr.client.
|
39
|
+
from flwr.client.clientapp.clientappio_servicer import (
|
40
|
+
ClientAppInputs,
|
41
|
+
ClientAppIoServicer,
|
42
|
+
)
|
43
|
+
from flwr.client.grpc_adapter_client.connection import grpc_adapter
|
44
|
+
from flwr.client.grpc_client.connection import grpc_connection
|
45
|
+
from flwr.client.grpc_rere_client.connection import grpc_request_response
|
46
|
+
from flwr.client.message_handler.message_handler import handle_control_message
|
47
|
+
from flwr.client.numpy_client import NumPyClient
|
48
|
+
from flwr.client.run_info_store import DeprecatedRunInfoStore
|
40
49
|
from flwr.client.typing import ClientFnExt
|
41
50
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
|
42
51
|
from flwr.common.address import parse_address
|
@@ -61,14 +70,7 @@ from flwr.common.logger import log, warn_deprecated_feature
|
|
61
70
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
62
71
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
63
72
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
64
|
-
|
65
|
-
from .clientapp.clientappio_servicer import ClientAppInputs, ClientAppIoServicer
|
66
|
-
from .grpc_adapter_client.connection import grpc_adapter
|
67
|
-
from .grpc_client.connection import grpc_connection
|
68
|
-
from .grpc_rere_client.connection import grpc_request_response
|
69
|
-
from .message_handler.message_handler import handle_control_message
|
70
|
-
from .numpy_client import NumPyClient
|
71
|
-
from .run_info_store import DeprecatedRunInfoStore
|
73
|
+
from flwr.supernode.nodestate import NodeStateFactory
|
72
74
|
|
73
75
|
|
74
76
|
def _check_actionable_client(
|
@@ -781,7 +783,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
|
781
783
|
try:
|
782
784
|
from requests.exceptions import ConnectionError as RequestsConnectionError
|
783
785
|
|
784
|
-
from .rest_client.connection import http_request_response
|
786
|
+
from flwr.client.rest_client.connection import http_request_response
|
785
787
|
except ModuleNotFoundError:
|
786
788
|
flwr_exit(ExitCode.COMMON_MISSING_EXTRA_REST)
|
787
789
|
if server_address[:4] != "http":
|
@@ -40,12 +40,8 @@ from flwr.common.constant import (
|
|
40
40
|
)
|
41
41
|
from flwr.common.message import make_message
|
42
42
|
from flwr.common.record import ConfigRecord
|
43
|
-
from flwr.common.serde import
|
44
|
-
|
45
|
-
error_to_proto,
|
46
|
-
recorddict_from_proto,
|
47
|
-
recorddict_to_proto,
|
48
|
-
)
|
43
|
+
from flwr.common.serde import recorddict_from_proto, recorddict_to_proto
|
44
|
+
from flwr.common.serde_utils import error_from_proto, error_to_proto
|
49
45
|
from flwr.common.typing import Run, RunStatus, UserConfig
|
50
46
|
|
51
47
|
# pylint: disable=E0611
|
{flwr_nightly-1.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.19.0.
|
3
|
+
Version: 1.19.0.dev20250521
|
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.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/RECORD
RENAMED
@@ -74,8 +74,7 @@ flwr/cli/run/__init__.py,sha256=RPyB7KbYTFl6YRiilCch6oezxrLQrl1kijV7BMGkLbA,790
|
|
74
74
|
flwr/cli/run/run.py,sha256=mbyf46Tm3qrL8NW02JyDjs6BI49m9UMzXsGK8-Af1r4,8232
|
75
75
|
flwr/cli/stop.py,sha256=iLbh1dq8XMdcIlh0Lh8ufG6h0VvrP1kyp_mGO-kimt0,4976
|
76
76
|
flwr/cli/utils.py,sha256=FjRYfzTw75qh5YHmrg9XzBA6o73T6xWt9WQYIxq-iHY,11207
|
77
|
-
flwr/client/__init__.py,sha256=
|
78
|
-
flwr/client/app.py,sha256=wTZHxy8FS_lcIP3uXc6IzsDpbeDg0arWJGckCy3Xcn4,34277
|
77
|
+
flwr/client/__init__.py,sha256=boIhKaK6I977zrILmoTutNx94x5jB0e6F1gnAjaRJnI,1250
|
79
78
|
flwr/client/client.py,sha256=3HAchxvknKG9jYbB7swNyDj-e5vUWDuMKoLvbT7jCVM,7895
|
80
79
|
flwr/client/client_app.py,sha256=zVhi-l3chAb06ozFsKwix3hU_RpOLjST13Ha50AVIPE,16918
|
81
80
|
flwr/client/clientapp/__init__.py,sha256=nPMoWEB1FhwexuW-vKdhwFkFr_4MW-2YMZExP9vfTGg,800
|
@@ -101,16 +100,13 @@ flwr/client/mod/secure_aggregation/__init__.py,sha256=k8HYXvqu3pd_V3eZ0_5wwH52o-
|
|
101
100
|
flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=y54DvpM2-DWUiEYqgwZ0DssC1VVRCJEfGgST7O3OcwM,1095
|
102
101
|
flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=aKqjZCrikF73y3E-7h40u-s0H6-hmyd4Ah1LHnrLrIg,19661
|
103
102
|
flwr/client/mod/utils.py,sha256=FUgD2TfcWqSeF6jUKZ4i6Ke56U4Nrv85AeVb93s6R9g,1201
|
104
|
-
flwr/client/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUDfuSHsbU,976
|
105
|
-
flwr/client/nodestate/in_memory_nodestate.py,sha256=CTwkxhCSg8ExaGFILFTuylF0RHrAIAgxMFLQfmaFm1I,1291
|
106
|
-
flwr/client/nodestate/nodestate.py,sha256=-LAjZOnS7VyHC05ll3b31cYDjwAt6l4WmYt7duVLRKk,1024
|
107
|
-
flwr/client/nodestate/nodestate_factory.py,sha256=UYTDCcwK_baHUmkzkJDxL0UEqvtTfOMlQRrROMCd0Xo,1430
|
108
103
|
flwr/client/numpy_client.py,sha256=Qq6ghsIAop2slKqAfgiI5NiHJ4LIxGmrik3Ror4_XVc,9581
|
109
104
|
flwr/client/rest_client/__init__.py,sha256=MBiuK62hj439m9rtwSwI184Hth6Tt5GbmpNMyl3zkZY,735
|
110
105
|
flwr/client/rest_client/connection.py,sha256=Xlf1eEMXq17VVVELPGPT1pqJKw8l0iq4Jnvz13v95C8,12806
|
111
106
|
flwr/client/run_info_store.py,sha256=MaJ3UQ-07hWtK67wnWu0zR29jrk0fsfgJX506dvEOfE,4042
|
107
|
+
flwr/client/start_client_internal.py,sha256=OQBOUlXmb5fSCErD6bdYjl2R4vwhv30_fIyedKU0YG8,25266
|
112
108
|
flwr/client/supernode/__init__.py,sha256=i3gFbV5ie_FGyRMpzOvqtZAi0Z0ChIEJ7I2Kr0ym0PM,793
|
113
|
-
flwr/client/supernode/app.py,sha256=
|
109
|
+
flwr/client/supernode/app.py,sha256=pGHzFlidF4Y74zhFNTqCsB1Hl6x-bq4R2L1ktEZgXXI,8993
|
114
110
|
flwr/client/typing.py,sha256=Jw3rawDzI_-ZDcRmEQcs5gZModY7oeQlEeltYsdOhlU,1048
|
115
111
|
flwr/clientapp/__init__.py,sha256=zGW4z49Ojzoi1hDiRC7kyhLjijUilc6fqHhtM_ATRVA,719
|
116
112
|
flwr/common/__init__.py,sha256=5GCLVk399Az_rTJHNticRlL0Sl_oPw_j5_LuFKfX7-M,4171
|
@@ -136,7 +132,7 @@ flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
|
|
136
132
|
flwr/common/inflatable.py,sha256=ZKW4L2GMAxInUlbNK_zDZs7uW4-CuQui9TnWVglpDic,5279
|
137
133
|
flwr/common/inflatable_grpc_utils.py,sha256=StkhGH8x9zR-p5MH52HdLG9MLzKv_rT8sPdbR9ZzNyE,3368
|
138
134
|
flwr/common/logger.py,sha256=JbRf6E2vQxXzpDBq1T8IDUJo_usu3gjWEBPQ6uKcmdg,13049
|
139
|
-
flwr/common/message.py,sha256=
|
135
|
+
flwr/common/message.py,sha256=dfct6ZGizK2zSj2JLiQTRbOfDNu79KzwUplpQaxFg40,18997
|
140
136
|
flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
|
141
137
|
flwr/common/parameter.py,sha256=UVw6sOgehEFhFs4uUCMl2kfVq1PD6ncmWgPLMsZPKPE,2095
|
142
138
|
flwr/common/pyproject.py,sha256=2SU6yJW7059SbMXgzjOdK1GZRWO6AixDH7BmdxbMvHI,1386
|
@@ -158,13 +154,14 @@ flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=TrggOlizlny3V2KS7-3
|
|
158
154
|
flwr/common/secure_aggregation/quantization.py,sha256=ssFZpiRyj9ltIh0Ai3vGkDqWFO4SoqgoD1mDU9XqMEM,2400
|
159
155
|
flwr/common/secure_aggregation/secaggplus_constants.py,sha256=dGYhWOBMMDJcQH4_tQNC8-Efqm-ecEUNN9ANz59UnCk,2182
|
160
156
|
flwr/common/secure_aggregation/secaggplus_utils.py,sha256=E_xU-Zd45daO1em7M6C2wOjFXVtJf-6tl7fp-7xq1wo,3214
|
161
|
-
flwr/common/serde.py,sha256=
|
162
|
-
flwr/common/serde_utils.py,sha256=
|
157
|
+
flwr/common/serde.py,sha256=_EusvG9FGtOcUxGZWYSsPv7KzTnzadXv4jcttL40Fow,22280
|
158
|
+
flwr/common/serde_utils.py,sha256=zF99EnqTNhEd3Xh3tYy2bZ44_8B-QfwNqsuP7vfLVDs,5735
|
163
159
|
flwr/common/telemetry.py,sha256=jF47v0SbnBd43XamHtl3wKxs3knFUY2p77cm_2lzZ8M,8762
|
164
160
|
flwr/common/typing.py,sha256=97QRfRRS7sQnjkAI5FDZ01-38oQUSz4i1qqewQmBWRg,6886
|
165
161
|
flwr/common/version.py,sha256=7GAGzPn73Mkh09qhrjbmjZQtcqVhBuzhFBaK4Mk4VRk,1325
|
166
162
|
flwr/compat/__init__.py,sha256=gbfDQKKKMZzi3GswyVRgyLdDlHiWj3wU6dg7y6m5O_s,752
|
167
163
|
flwr/compat/client/__init__.py,sha256=qpbo0lcxdNL4qy5KHqiGm8OLxSxkYgI_-dLh5rwhtcI,746
|
164
|
+
flwr/compat/client/app.py,sha256=2_-4oSkzbBdyF56PPWnBbzE9C7nG7UVaTd_5ftFQ2P8,34362
|
168
165
|
flwr/compat/common/__init__.py,sha256=OMnKw4ad0qYMSIA9LZRa2gOkhSOXwAZCpAHnBQE_hFc,746
|
169
166
|
flwr/compat/server/__init__.py,sha256=TGVSoOTuf5T5JHUVrK5wuorQF7L6Wvdem8B4uufvMJY,746
|
170
167
|
flwr/compat/simulation/__init__.py,sha256=MApGa-tysDDw34iSdxZ7TWOKtGJM-z3i8fIRJa0qbZ8,750
|
@@ -306,7 +303,7 @@ flwr/server/superlink/linkstate/__init__.py,sha256=OtsgvDTnZLU3k0sUbkHbqoVwW6ql2
|
|
306
303
|
flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=vvoOWjYlmOlbakH7AzpMh0jB70Qxx7UTlAGqjcA8ctM,25926
|
307
304
|
flwr/server/superlink/linkstate/linkstate.py,sha256=j6nW351t07VrBhFqjO34z8tf2PuKOE9aCX9SqpW96pQ,13100
|
308
305
|
flwr/server/superlink/linkstate/linkstate_factory.py,sha256=8RlosqSpKOoD_vhUUQPY0jtE3A84GeF96Z7sWNkRRcA,2069
|
309
|
-
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=
|
306
|
+
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=z3VABMX_WtAioWJ2aUOsxi53-ecF2c8xEQ69xP3xXW8,43587
|
310
307
|
flwr/server/superlink/linkstate/utils.py,sha256=AJs9jTAEK7JnjF2AODXnOfy0pKAKpe6oUWPCanAP57s,15382
|
311
308
|
flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
|
312
309
|
flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=opJ6SYwIAbu4NWEo3K-VxFO-tMSFmE4H3i2HwHIVRzw,2173
|
@@ -347,7 +344,11 @@ flwr/superexec/executor.py,sha256=M5ucqSE53jfRtuCNf59WFLqQvA1Mln4741TySeZE7qQ,31
|
|
347
344
|
flwr/superexec/simulation.py,sha256=j6YwUvBN7EQ09ID7MYOCVZ70PGbuyBy8f9bXU0EszEM,4088
|
348
345
|
flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
|
349
346
|
flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
347
|
+
flwr/supernode/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUDfuSHsbU,976
|
348
|
+
flwr/supernode/nodestate/in_memory_nodestate.py,sha256=brV7TMMzS93tXk6ntpoYjtPK5qiSF3XD2W-uUdUVucc,1270
|
349
|
+
flwr/supernode/nodestate/nodestate.py,sha256=-LAjZOnS7VyHC05ll3b31cYDjwAt6l4WmYt7duVLRKk,1024
|
350
|
+
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,,
|
File without changes
|
File without changes
|
File without changes
|
{flwr_nightly-1.19.0.dev20250520.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|