flwr 1.19.0__py3-none-any.whl → 1.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/cli/build.py +15 -5
- flwr/cli/new/new.py +12 -4
- flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
- flwr/cli/new/templates/app/README.md.tpl +5 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -3
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
- flwr/cli/run/run.py +45 -38
- flwr/cli/utils.py +12 -5
- flwr/client/grpc_adapter_client/connection.py +11 -4
- flwr/client/grpc_rere_client/connection.py +92 -117
- flwr/client/rest_client/connection.py +131 -164
- flwr/common/constant.py +3 -1
- flwr/common/exit/exit_code.py +16 -1
- flwr/common/grpc.py +12 -1
- flwr/common/{inflatable_grpc_utils.py → inflatable_protobuf_utils.py} +52 -10
- flwr/common/inflatable_utils.py +191 -24
- flwr/common/record/array.py +101 -22
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/serde.py +0 -28
- flwr/compat/client/app.py +14 -31
- flwr/proto/appio_pb2.py +43 -0
- flwr/proto/appio_pb2.pyi +151 -0
- flwr/proto/appio_pb2_grpc.py +4 -0
- flwr/proto/appio_pb2_grpc.pyi +4 -0
- flwr/proto/clientappio_pb2.py +12 -19
- flwr/proto/clientappio_pb2.pyi +23 -101
- flwr/proto/clientappio_pb2_grpc.py +269 -28
- flwr/proto/clientappio_pb2_grpc.pyi +114 -20
- flwr/proto/fleet_pb2.py +12 -20
- flwr/proto/fleet_pb2.pyi +6 -36
- flwr/proto/serverappio_pb2.py +8 -31
- flwr/proto/serverappio_pb2.pyi +0 -152
- flwr/proto/serverappio_pb2_grpc.py +39 -38
- flwr/proto/serverappio_pb2_grpc.pyi +21 -20
- flwr/server/app.py +1 -1
- flwr/server/fleet_event_log_interceptor.py +4 -0
- flwr/server/grid/grpc_grid.py +91 -54
- flwr/server/serverapp/app.py +27 -17
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +8 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +10 -16
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -2
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -1
- flwr/server/superlink/serverappio/serverappio_servicer.py +35 -43
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +1 -1
- flwr/server/superlink/utils.py +0 -35
- flwr/simulation/app.py +8 -0
- flwr/simulation/run_simulation.py +17 -0
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/supercore/grpc_health/__init__.py +22 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
- flwr/supercore/license_plugin/__init__.py +22 -0
- flwr/supercore/license_plugin/license_plugin.py +26 -0
- flwr/supercore/object_store/in_memory_object_store.py +31 -31
- flwr/supercore/object_store/object_store.py +20 -42
- flwr/supercore/object_store/utils.py +43 -0
- flwr/supercore/scheduler/__init__.py +22 -0
- flwr/supercore/scheduler/plugin.py +71 -0
- flwr/supercore/utils.py +32 -0
- flwr/superexec/deployment.py +1 -2
- flwr/superexec/exec_event_log_interceptor.py +4 -0
- flwr/superexec/exec_grpc.py +18 -2
- flwr/superexec/exec_license_interceptor.py +82 -0
- flwr/superexec/exec_servicer.py +10 -1
- flwr/superexec/exec_user_auth_interceptor.py +10 -2
- flwr/superexec/executor.py +1 -1
- flwr/superexec/simulation.py +1 -2
- flwr/supernode/cli/flower_supernode.py +0 -7
- flwr/supernode/cli/flwr_clientapp.py +10 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +11 -2
- flwr/supernode/nodestate/nodestate.py +15 -0
- flwr/supernode/runtime/run_clientapp.py +110 -33
- flwr/supernode/scheduler/__init__.py +22 -0
- flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
- flwr/supernode/servicer/clientappio/__init__.py +1 -3
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +223 -164
- flwr/supernode/start_client_internal.py +202 -104
- {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/METADATA +2 -1
- {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/RECORD +93 -78
- flwr/common/inflatable_rest_utils.py +0 -99
- /flwr/{server/superlink → supercore}/ffs/__init__.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +0 -0
- {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +0 -0
|
@@ -20,8 +20,8 @@ import subprocess
|
|
|
20
20
|
import time
|
|
21
21
|
from collections.abc import Iterator
|
|
22
22
|
from contextlib import contextmanager
|
|
23
|
+
from functools import partial
|
|
23
24
|
from logging import INFO, WARN
|
|
24
|
-
from os import urandom
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Callable, Optional, Union, cast
|
|
27
27
|
|
|
@@ -39,7 +39,6 @@ from flwr.common.constant import (
|
|
|
39
39
|
CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
40
40
|
ISOLATION_MODE_SUBPROCESS,
|
|
41
41
|
MAX_RETRY_DELAY,
|
|
42
|
-
RUN_ID_NUM_BYTES,
|
|
43
42
|
SERVER_OCTET,
|
|
44
43
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
45
44
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
@@ -47,15 +46,23 @@ from flwr.common.constant import (
|
|
|
47
46
|
TRANSPORT_TYPES,
|
|
48
47
|
)
|
|
49
48
|
from flwr.common.exit import ExitCode, flwr_exit
|
|
49
|
+
from flwr.common.exit_handlers import register_exit_handlers
|
|
50
50
|
from flwr.common.grpc import generic_create_grpc_server
|
|
51
|
+
from flwr.common.inflatable import iterate_object_tree
|
|
52
|
+
from flwr.common.inflatable_utils import (
|
|
53
|
+
pull_objects,
|
|
54
|
+
push_object_contents_from_iterable,
|
|
55
|
+
)
|
|
51
56
|
from flwr.common.logger import log
|
|
52
57
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
58
|
+
from flwr.common.telemetry import EventType
|
|
53
59
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
|
54
60
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
|
55
|
-
from flwr.
|
|
61
|
+
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
62
|
+
from flwr.supercore.ffs import Ffs, FfsFactory
|
|
56
63
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
57
64
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
|
58
|
-
from flwr.supernode.servicer.clientappio import
|
|
65
|
+
from flwr.supernode.servicer.clientappio import ClientAppIoServicer
|
|
59
66
|
|
|
60
67
|
DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
|
|
61
68
|
|
|
@@ -132,16 +139,27 @@ def start_client_internal(
|
|
|
132
139
|
if insecure is None:
|
|
133
140
|
insecure = root_certificates is None
|
|
134
141
|
|
|
135
|
-
_clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
|
|
136
|
-
address=clientappio_api_address,
|
|
137
|
-
certificates=None,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
142
|
# Initialize factories
|
|
141
143
|
state_factory = NodeStateFactory()
|
|
142
144
|
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
|
143
145
|
object_store_factory = ObjectStoreFactory()
|
|
144
146
|
|
|
147
|
+
# Launch ClientAppIo API server
|
|
148
|
+
clientappio_server = run_clientappio_api_grpc(
|
|
149
|
+
address=clientappio_api_address,
|
|
150
|
+
state_factory=state_factory,
|
|
151
|
+
ffs_factory=ffs_factory,
|
|
152
|
+
objectstore_factory=object_store_factory,
|
|
153
|
+
certificates=None,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Register handlers for graceful shutdown
|
|
157
|
+
register_exit_handlers(
|
|
158
|
+
event_type=EventType.RUN_SUPERNODE_LEAVE,
|
|
159
|
+
exit_message="SuperNode terminated gracefully.",
|
|
160
|
+
grpc_servers=[clientappio_server],
|
|
161
|
+
)
|
|
162
|
+
|
|
145
163
|
# Initialize NodeState, Ffs, and ObjectStore
|
|
146
164
|
state = state_factory.state()
|
|
147
165
|
ffs = ffs_factory.ffs()
|
|
@@ -156,7 +174,17 @@ def start_client_internal(
|
|
|
156
174
|
max_retries=max_retries,
|
|
157
175
|
max_wait_time=max_wait_time,
|
|
158
176
|
) as conn:
|
|
159
|
-
|
|
177
|
+
(
|
|
178
|
+
receive,
|
|
179
|
+
send,
|
|
180
|
+
create_node,
|
|
181
|
+
_,
|
|
182
|
+
get_run,
|
|
183
|
+
get_fab,
|
|
184
|
+
pull_object,
|
|
185
|
+
push_object,
|
|
186
|
+
confirm_message_received,
|
|
187
|
+
) = conn
|
|
160
188
|
|
|
161
189
|
# Call create_node fn to register node
|
|
162
190
|
# and store node_id in state
|
|
@@ -176,106 +204,63 @@ def start_client_internal(
|
|
|
176
204
|
receive=receive,
|
|
177
205
|
get_run=get_run,
|
|
178
206
|
get_fab=get_fab,
|
|
207
|
+
pull_object=pull_object,
|
|
208
|
+
confirm_message_received=confirm_message_received,
|
|
179
209
|
)
|
|
180
210
|
|
|
211
|
+
# Two isolation modes:
|
|
212
|
+
# 1. `subprocess`: SuperNode is starting the ClientApp
|
|
213
|
+
# process as a subprocess.
|
|
214
|
+
# 2. `process`: ClientApp process gets started separately
|
|
215
|
+
# (via `flwr-clientapp`), for example, in a separate
|
|
216
|
+
# Docker container.
|
|
217
|
+
|
|
218
|
+
# Mode 1: SuperNode starts ClientApp as subprocess
|
|
219
|
+
start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
|
|
220
|
+
|
|
221
|
+
if start_subprocess and run_id is not None:
|
|
222
|
+
_octet, _colon, _port = clientappio_api_address.rpartition(":")
|
|
223
|
+
io_address = (
|
|
224
|
+
f"{CLIENT_OCTET}:{_port}"
|
|
225
|
+
if _octet == SERVER_OCTET
|
|
226
|
+
else clientappio_api_address
|
|
227
|
+
)
|
|
228
|
+
# Start ClientApp subprocess
|
|
229
|
+
command = [
|
|
230
|
+
"flwr-clientapp",
|
|
231
|
+
"--clientappio-api-address",
|
|
232
|
+
io_address,
|
|
233
|
+
"--parent-pid",
|
|
234
|
+
str(os.getpid()),
|
|
235
|
+
"--insecure",
|
|
236
|
+
"--run-once",
|
|
237
|
+
]
|
|
238
|
+
subprocess.run(command, check=False)
|
|
239
|
+
|
|
240
|
+
# No message has been pulled therefore we can skip the push stage.
|
|
181
241
|
if run_id is None:
|
|
182
|
-
|
|
242
|
+
# If no message was received, wait for a while
|
|
243
|
+
time.sleep(3)
|
|
183
244
|
continue
|
|
184
245
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# Two isolation modes:
|
|
193
|
-
# 1. `subprocess`: SuperNode is starting the ClientApp
|
|
194
|
-
# process as a subprocess.
|
|
195
|
-
# 2. `process`: ClientApp process gets started separately
|
|
196
|
-
# (via `flwr-clientapp`), for example, in a separate
|
|
197
|
-
# Docker container.
|
|
198
|
-
|
|
199
|
-
# Generate SuperNode token
|
|
200
|
-
token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
|
|
201
|
-
|
|
202
|
-
# Mode 1: SuperNode starts ClientApp as subprocess
|
|
203
|
-
start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
|
|
204
|
-
|
|
205
|
-
# Share Message and Context with servicer
|
|
206
|
-
clientappio_servicer.set_inputs(
|
|
207
|
-
clientapp_input=ClientAppInputs(
|
|
208
|
-
message=message,
|
|
209
|
-
context=context,
|
|
210
|
-
run=run,
|
|
211
|
-
fab=fab,
|
|
212
|
-
token=token,
|
|
213
|
-
),
|
|
214
|
-
token_returned=start_subprocess,
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
if start_subprocess:
|
|
218
|
-
_octet, _colon, _port = clientappio_api_address.rpartition(":")
|
|
219
|
-
io_address = (
|
|
220
|
-
f"{CLIENT_OCTET}:{_port}"
|
|
221
|
-
if _octet == SERVER_OCTET
|
|
222
|
-
else clientappio_api_address
|
|
223
|
-
)
|
|
224
|
-
# Start ClientApp subprocess
|
|
225
|
-
command = [
|
|
226
|
-
"flwr-clientapp",
|
|
227
|
-
"--clientappio-api-address",
|
|
228
|
-
io_address,
|
|
229
|
-
"--token",
|
|
230
|
-
str(token),
|
|
231
|
-
"--parent-pid",
|
|
232
|
-
str(os.getpid()),
|
|
233
|
-
"--insecure",
|
|
234
|
-
]
|
|
235
|
-
subprocess.run(command, check=False)
|
|
236
|
-
else:
|
|
237
|
-
# Wait for output to become available
|
|
238
|
-
while not clientappio_servicer.has_outputs():
|
|
239
|
-
time.sleep(0.1)
|
|
240
|
-
|
|
241
|
-
outputs = clientappio_servicer.get_outputs()
|
|
242
|
-
reply_message, context = outputs.message, outputs.context
|
|
243
|
-
|
|
244
|
-
# Update context in the state
|
|
245
|
-
state.store_context(context)
|
|
246
|
-
|
|
247
|
-
# Send
|
|
248
|
-
send(reply_message)
|
|
249
|
-
|
|
250
|
-
# Delete messages from the state
|
|
251
|
-
state.delete_messages(
|
|
252
|
-
message_ids=[
|
|
253
|
-
message.metadata.message_id,
|
|
254
|
-
message.metadata.reply_to_message_id,
|
|
255
|
-
]
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
log(INFO, "Sent reply")
|
|
259
|
-
|
|
260
|
-
except RunNotRunningException:
|
|
261
|
-
log(INFO, "")
|
|
262
|
-
log(
|
|
263
|
-
INFO,
|
|
264
|
-
"SuperNode aborted sending the reply message. "
|
|
265
|
-
"Run ID %s is not in `RUNNING` status.",
|
|
266
|
-
run_id,
|
|
267
|
-
)
|
|
268
|
-
log(INFO, "")
|
|
246
|
+
_push_messages(
|
|
247
|
+
state=state,
|
|
248
|
+
object_store=store,
|
|
249
|
+
send=send,
|
|
250
|
+
push_object=push_object,
|
|
251
|
+
)
|
|
269
252
|
|
|
270
253
|
|
|
271
254
|
def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
272
255
|
state: NodeState,
|
|
273
256
|
ffs: Ffs,
|
|
274
|
-
object_store: ObjectStore,
|
|
257
|
+
object_store: ObjectStore,
|
|
275
258
|
node_config: UserConfig,
|
|
276
|
-
receive: Callable[[], Optional[Message]],
|
|
259
|
+
receive: Callable[[], Optional[tuple[Message, ObjectTree]]],
|
|
277
260
|
get_run: Callable[[int], Run],
|
|
278
261
|
get_fab: Callable[[str, int], Fab],
|
|
262
|
+
pull_object: Callable[[int, str], bytes],
|
|
263
|
+
confirm_message_received: Callable[[int, str], None],
|
|
279
264
|
) -> Optional[int]:
|
|
280
265
|
"""Pull a message from the SuperLink and store it in the state.
|
|
281
266
|
|
|
@@ -287,8 +272,9 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
287
272
|
message = None
|
|
288
273
|
try:
|
|
289
274
|
# Pull message
|
|
290
|
-
if (
|
|
275
|
+
if (recv := receive()) is None:
|
|
291
276
|
return None
|
|
277
|
+
message, object_tree = recv
|
|
292
278
|
|
|
293
279
|
# Log message reception
|
|
294
280
|
log(INFO, "")
|
|
@@ -332,8 +318,23 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
332
318
|
)
|
|
333
319
|
state.store_context(run_ctx)
|
|
334
320
|
|
|
335
|
-
#
|
|
321
|
+
# Preregister the object tree of the message
|
|
322
|
+
obj_ids_to_pull = object_store.preregister(run_id, object_tree)
|
|
323
|
+
|
|
324
|
+
# Store the message in the state (note this message has no content)
|
|
336
325
|
state.store_message(message)
|
|
326
|
+
|
|
327
|
+
# Pull and store objects of the message in the ObjectStore
|
|
328
|
+
obj_contents = pull_objects(
|
|
329
|
+
obj_ids_to_pull,
|
|
330
|
+
pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
|
|
331
|
+
)
|
|
332
|
+
for obj_id in list(obj_contents.keys()):
|
|
333
|
+
object_store.put(obj_id, obj_contents.pop(obj_id))
|
|
334
|
+
|
|
335
|
+
# Confirm that the message was received
|
|
336
|
+
confirm_message_received(run_id, message.metadata.message_id)
|
|
337
|
+
|
|
337
338
|
except RunNotRunningException:
|
|
338
339
|
if message is None:
|
|
339
340
|
log(
|
|
@@ -353,6 +354,93 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
353
354
|
return run_id
|
|
354
355
|
|
|
355
356
|
|
|
357
|
+
def _push_messages(
|
|
358
|
+
state: NodeState,
|
|
359
|
+
object_store: ObjectStore,
|
|
360
|
+
send: Callable[[Message, ObjectTree], set[str]],
|
|
361
|
+
push_object: Callable[[int, str, bytes], None],
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Push reply messages to the SuperLink."""
|
|
364
|
+
# This is to ensure that only one message is processed at a time
|
|
365
|
+
# Wait until a reply message is available
|
|
366
|
+
while not (reply_messages := state.get_messages(is_reply=True)):
|
|
367
|
+
time.sleep(0.5)
|
|
368
|
+
|
|
369
|
+
for message in reply_messages:
|
|
370
|
+
# Log message sending
|
|
371
|
+
log(INFO, "")
|
|
372
|
+
if message.metadata.group_id:
|
|
373
|
+
log(
|
|
374
|
+
INFO,
|
|
375
|
+
"[RUN %s, ROUND %s]",
|
|
376
|
+
message.metadata.run_id,
|
|
377
|
+
message.metadata.group_id,
|
|
378
|
+
)
|
|
379
|
+
else:
|
|
380
|
+
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
381
|
+
log(
|
|
382
|
+
INFO,
|
|
383
|
+
"Sending: %s message",
|
|
384
|
+
message.metadata.message_type,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Get the object tree for the message
|
|
388
|
+
object_tree = object_store.get_object_tree(message.metadata.message_id)
|
|
389
|
+
|
|
390
|
+
# Define the iterator for yielding object contents
|
|
391
|
+
# This will yield (object_id, content) pairs
|
|
392
|
+
def yield_object_contents(
|
|
393
|
+
_obj_tree: ObjectTree, obj_id_set: set[str]
|
|
394
|
+
) -> Iterator[tuple[str, bytes]]:
|
|
395
|
+
for tree in iterate_object_tree(_obj_tree):
|
|
396
|
+
if tree.object_id not in obj_id_set:
|
|
397
|
+
continue
|
|
398
|
+
while (content := object_store.get(tree.object_id)) == b"":
|
|
399
|
+
# Wait for the content to be available
|
|
400
|
+
time.sleep(0.5)
|
|
401
|
+
# At this point, content is guaranteed to be available
|
|
402
|
+
# therefore we can yield it after casting it to bytes
|
|
403
|
+
yield tree.object_id, cast(bytes, content)
|
|
404
|
+
|
|
405
|
+
# Send the message
|
|
406
|
+
try:
|
|
407
|
+
# Send the reply message with its ObjectTree
|
|
408
|
+
# Get the IDs of objects to send
|
|
409
|
+
ids_obj_to_send = send(message, object_tree)
|
|
410
|
+
|
|
411
|
+
# Push object contents from the ObjectStore
|
|
412
|
+
run_id = message.metadata.run_id
|
|
413
|
+
push_object_contents_from_iterable(
|
|
414
|
+
yield_object_contents(object_tree, ids_obj_to_send),
|
|
415
|
+
# Use functools.partial to bind run_id explicitly,
|
|
416
|
+
# avoiding late binding issues and satisfying flake8 (B023)
|
|
417
|
+
# Equivalent to:
|
|
418
|
+
# lambda object_id, content: push_object(run_id, object_id, content)
|
|
419
|
+
push_object_fn=partial(push_object, run_id),
|
|
420
|
+
)
|
|
421
|
+
log(INFO, "Sent successfully")
|
|
422
|
+
except RunNotRunningException:
|
|
423
|
+
log(
|
|
424
|
+
INFO,
|
|
425
|
+
"Run ID %s is not in `RUNNING` status. Ignoring reply message %s.",
|
|
426
|
+
message.metadata.run_id,
|
|
427
|
+
message.metadata.message_id,
|
|
428
|
+
)
|
|
429
|
+
finally:
|
|
430
|
+
# Delete the message from the state
|
|
431
|
+
state.delete_messages(
|
|
432
|
+
message_ids=[
|
|
433
|
+
message.metadata.message_id,
|
|
434
|
+
message.metadata.reply_to_message_id,
|
|
435
|
+
]
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Delete all its objects from the ObjectStore
|
|
439
|
+
# No need to delete objects of the message it replies to, as it is
|
|
440
|
+
# already deleted when the ClientApp calls `ConfirmMessageReceived`
|
|
441
|
+
object_store.delete(message.metadata.message_id)
|
|
442
|
+
|
|
443
|
+
|
|
356
444
|
@contextmanager
|
|
357
445
|
def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
358
446
|
transport: str,
|
|
@@ -366,12 +454,15 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
366
454
|
max_wait_time: Optional[float] = None,
|
|
367
455
|
) -> Iterator[
|
|
368
456
|
tuple[
|
|
369
|
-
Callable[[], Optional[Message]],
|
|
370
|
-
Callable[[Message],
|
|
457
|
+
Callable[[], Optional[tuple[Message, ObjectTree]]],
|
|
458
|
+
Callable[[Message, ObjectTree], set[str]],
|
|
371
459
|
Callable[[], Optional[int]],
|
|
372
460
|
Callable[[], None],
|
|
373
461
|
Callable[[int], Run],
|
|
374
462
|
Callable[[str, int], Fab],
|
|
463
|
+
Callable[[int, str], bytes],
|
|
464
|
+
Callable[[int, str, bytes], None],
|
|
465
|
+
Callable[[int, str], None],
|
|
375
466
|
]
|
|
376
467
|
]:
|
|
377
468
|
"""Establish a connection to the Fleet API server at SuperLink."""
|
|
@@ -472,10 +563,17 @@ def _make_fleet_connection_retry_invoker(
|
|
|
472
563
|
|
|
473
564
|
def run_clientappio_api_grpc(
|
|
474
565
|
address: str,
|
|
566
|
+
state_factory: NodeStateFactory,
|
|
567
|
+
ffs_factory: FfsFactory,
|
|
568
|
+
objectstore_factory: ObjectStoreFactory,
|
|
475
569
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
476
|
-
) ->
|
|
570
|
+
) -> grpc.Server:
|
|
477
571
|
"""Run ClientAppIo API gRPC server."""
|
|
478
|
-
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
|
572
|
+
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
|
573
|
+
state_factory=state_factory,
|
|
574
|
+
ffs_factory=ffs_factory,
|
|
575
|
+
objectstore_factory=objectstore_factory,
|
|
576
|
+
)
|
|
479
577
|
clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
|
|
480
578
|
clientappio_grpc_server = generic_create_grpc_server(
|
|
481
579
|
servicer_and_add_fn=(
|
|
@@ -488,4 +586,4 @@ def run_clientappio_api_grpc(
|
|
|
488
586
|
)
|
|
489
587
|
log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
|
|
490
588
|
clientappio_grpc_server.start()
|
|
491
|
-
return clientappio_grpc_server
|
|
589
|
+
return clientappio_grpc_server
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.20.0
|
|
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
|
|
@@ -34,6 +34,7 @@ Provides-Extra: simulation
|
|
|
34
34
|
Requires-Dist: click (<8.2.0)
|
|
35
35
|
Requires-Dist: cryptography (>=44.0.1,<45.0.0)
|
|
36
36
|
Requires-Dist: grpcio (>=1.62.3,<2.0.0,!=1.65.0)
|
|
37
|
+
Requires-Dist: grpcio-health-checking (>=1.62.3,<2.0.0)
|
|
37
38
|
Requires-Dist: iterators (>=0.0.2,<0.0.3)
|
|
38
39
|
Requires-Dist: numpy (>=1.26.0,<3.0.0)
|
|
39
40
|
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|