flwr 1.18.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/app/__init__.py +15 -0
- flwr/app/error.py +68 -0
- flwr/app/metadata.py +223 -0
- flwr/cli/build.py +94 -59
- flwr/cli/log.py +3 -3
- flwr/cli/login/login.py +3 -7
- flwr/cli/ls.py +15 -36
- 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/code/client.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +25 -17
- 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 +48 -49
- flwr/cli/stop.py +2 -2
- flwr/cli/utils.py +38 -5
- flwr/client/__init__.py +2 -2
- flwr/client/client_app.py +1 -1
- flwr/client/clientapp/__init__.py +0 -7
- flwr/client/grpc_adapter_client/connection.py +15 -8
- flwr/client/grpc_rere_client/connection.py +142 -97
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -6
- flwr/client/message_handler/message_handler.py +1 -1
- flwr/client/mod/comms_mods.py +36 -17
- flwr/client/rest_client/connection.py +176 -103
- flwr/clientapp/__init__.py +15 -0
- flwr/common/__init__.py +2 -2
- flwr/common/auth_plugin/__init__.py +2 -0
- flwr/common/auth_plugin/auth_plugin.py +29 -3
- flwr/common/constant.py +39 -8
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/exit/exit_code.py +16 -1
- flwr/common/exit_handlers.py +30 -0
- flwr/common/grpc.py +12 -1
- flwr/common/heartbeat.py +165 -0
- flwr/common/inflatable.py +290 -0
- flwr/common/inflatable_protobuf_utils.py +141 -0
- flwr/common/inflatable_utils.py +508 -0
- flwr/common/message.py +110 -242
- flwr/common/record/__init__.py +2 -1
- flwr/common/record/array.py +402 -0
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/record/arrayrecord.py +103 -225
- flwr/common/record/configrecord.py +59 -4
- flwr/common/record/conversion_utils.py +1 -1
- flwr/common/record/metricrecord.py +55 -4
- flwr/common/record/recorddict.py +69 -1
- flwr/common/recorddict_compat.py +2 -2
- flwr/common/retry_invoker.py +5 -1
- flwr/common/serde.py +59 -211
- flwr/common/serde_utils.py +175 -0
- flwr/common/typing.py +5 -3
- flwr/compat/__init__.py +15 -0
- flwr/compat/client/__init__.py +15 -0
- flwr/{client → compat/client}/app.py +28 -185
- flwr/compat/common/__init__.py +15 -0
- flwr/compat/server/__init__.py +15 -0
- flwr/compat/server/app.py +174 -0
- flwr/compat/simulation/__init__.py +15 -0
- 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 +24 -27
- flwr/proto/fleet_pb2.pyi +19 -35
- flwr/proto/fleet_pb2_grpc.py +117 -13
- flwr/proto/fleet_pb2_grpc.pyi +47 -6
- flwr/proto/heartbeat_pb2.py +33 -0
- flwr/proto/heartbeat_pb2.pyi +66 -0
- flwr/proto/heartbeat_pb2_grpc.py +4 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +28 -11
- flwr/proto/message_pb2.pyi +125 -0
- flwr/proto/recorddict_pb2.py +16 -28
- flwr/proto/recorddict_pb2.pyi +46 -64
- flwr/proto/run_pb2.py +24 -32
- flwr/proto/run_pb2.pyi +4 -52
- flwr/proto/serverappio_pb2.py +9 -23
- flwr/proto/serverappio_pb2.pyi +0 -110
- flwr/proto/serverappio_pb2_grpc.py +177 -72
- flwr/proto/serverappio_pb2_grpc.pyi +75 -33
- flwr/proto/simulationio_pb2.py +12 -11
- flwr/proto/simulationio_pb2_grpc.py +35 -0
- flwr/proto/simulationio_pb2_grpc.pyi +14 -0
- flwr/server/__init__.py +1 -1
- flwr/server/app.py +69 -187
- flwr/server/compat/app_utils.py +50 -28
- flwr/server/fleet_event_log_interceptor.py +6 -2
- flwr/server/grid/grpc_grid.py +148 -41
- flwr/server/grid/inmemory_grid.py +5 -4
- flwr/server/serverapp/app.py +45 -17
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +21 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +130 -19
- flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -13
- flwr/server/superlink/fleet/vce/vce_api.py +6 -3
- flwr/server/superlink/linkstate/in_memory_linkstate.py +138 -43
- flwr/server/superlink/linkstate/linkstate.py +53 -20
- flwr/server/superlink/linkstate/sqlite_linkstate.py +149 -55
- flwr/server/superlink/linkstate/utils.py +33 -29
- flwr/server/superlink/serverappio/serverappio_grpc.py +4 -1
- flwr/server/superlink/serverappio/serverappio_servicer.py +230 -84
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
- flwr/server/superlink/utils.py +9 -2
- flwr/server/utils/validator.py +2 -2
- flwr/serverapp/__init__.py +15 -0
- flwr/simulation/app.py +25 -0
- flwr/simulation/run_simulation.py +17 -0
- flwr/supercore/__init__.py +15 -0
- flwr/{server/superlink → supercore}/ffs/__init__.py +2 -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/__init__.py +24 -0
- flwr/supercore/object_store/in_memory_object_store.py +229 -0
- flwr/supercore/object_store/object_store.py +170 -0
- flwr/supercore/object_store/object_store_factory.py +44 -0
- flwr/supercore/object_store/utils.py +43 -0
- flwr/supercore/scheduler/__init__.py +22 -0
- flwr/supercore/scheduler/plugin.py +71 -0
- flwr/{client/nodestate/nodestate.py → supercore/utils.py} +14 -13
- flwr/superexec/deployment.py +7 -4
- flwr/superexec/exec_event_log_interceptor.py +8 -4
- flwr/superexec/exec_grpc.py +25 -5
- flwr/superexec/exec_license_interceptor.py +82 -0
- flwr/superexec/exec_servicer.py +135 -24
- flwr/superexec/exec_user_auth_interceptor.py +45 -8
- flwr/superexec/executor.py +5 -1
- flwr/superexec/simulation.py +8 -3
- flwr/superlink/__init__.py +15 -0
- flwr/{client/supernode → supernode}/__init__.py +0 -7
- flwr/supernode/cli/__init__.py +24 -0
- flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +3 -19
- flwr/supernode/cli/flwr_clientapp.py +88 -0
- flwr/supernode/nodestate/in_memory_nodestate.py +199 -0
- flwr/supernode/nodestate/nodestate.py +227 -0
- flwr/supernode/runtime/__init__.py +15 -0
- flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +135 -89
- flwr/supernode/scheduler/__init__.py +22 -0
- flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
- flwr/supernode/servicer/__init__.py +15 -0
- flwr/supernode/servicer/clientappio/__init__.py +22 -0
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +303 -0
- flwr/supernode/start_client_internal.py +589 -0
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/METADATA +6 -4
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/RECORD +171 -123
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +1 -1
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +2 -2
- flwr/client/clientapp/clientappio_servicer.py +0 -244
- flwr/client/heartbeat.py +0 -74
- flwr/client/nodestate/in_memory_nodestate.py +0 -38
- /flwr/{client → compat/client}/grpc_client/__init__.py +0 -0
- /flwr/{client → compat/client}/grpc_client/connection.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- /flwr/{client → supernode}/nodestate/__init__.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
|
@@ -15,83 +15,80 @@
|
|
|
15
15
|
"""Flower ClientApp process."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import argparse
|
|
19
18
|
import gc
|
|
19
|
+
import os
|
|
20
|
+
import threading
|
|
20
21
|
import time
|
|
21
22
|
from logging import DEBUG, ERROR, INFO
|
|
22
23
|
from typing import Optional
|
|
23
24
|
|
|
24
25
|
import grpc
|
|
25
26
|
|
|
27
|
+
from flwr.app.error import Error
|
|
26
28
|
from flwr.cli.install import install_from_fab
|
|
27
29
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
30
|
+
from flwr.client.clientapp.utils import get_load_client_app_fn
|
|
28
31
|
from flwr.common import Context, Message
|
|
29
|
-
from flwr.common.args import add_args_flwr_app_common
|
|
30
32
|
from flwr.common.config import get_flwr_dir
|
|
31
|
-
from flwr.common.constant import
|
|
32
|
-
from flwr.common.exit import ExitCode, flwr_exit
|
|
33
|
+
from flwr.common.constant import ErrorCode
|
|
33
34
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
35
|
+
from flwr.common.inflatable import (
|
|
36
|
+
get_all_nested_objects,
|
|
37
|
+
get_object_tree,
|
|
38
|
+
no_object_id_recompute,
|
|
39
|
+
)
|
|
40
|
+
from flwr.common.inflatable_protobuf_utils import (
|
|
41
|
+
make_confirm_message_received_fn_protobuf,
|
|
42
|
+
make_pull_object_fn_protobuf,
|
|
43
|
+
make_push_object_fn_protobuf,
|
|
44
|
+
)
|
|
45
|
+
from flwr.common.inflatable_utils import pull_and_inflate_object_from_tree, push_objects
|
|
34
46
|
from flwr.common.logger import log
|
|
35
|
-
from flwr.common.message import
|
|
47
|
+
from flwr.common.message import remove_content_from_message
|
|
36
48
|
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
|
37
49
|
from flwr.common.serde import (
|
|
38
50
|
context_from_proto,
|
|
39
51
|
context_to_proto,
|
|
40
52
|
fab_from_proto,
|
|
41
|
-
message_from_proto,
|
|
42
53
|
message_to_proto,
|
|
43
54
|
run_from_proto,
|
|
44
55
|
)
|
|
45
56
|
from flwr.common.typing import Fab, Run
|
|
57
|
+
from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
|
|
58
|
+
PullAppInputsRequest,
|
|
59
|
+
PullAppInputsResponse,
|
|
60
|
+
PullAppMessagesRequest,
|
|
61
|
+
PullAppMessagesResponse,
|
|
62
|
+
PushAppMessagesRequest,
|
|
63
|
+
PushAppOutputsRequest,
|
|
64
|
+
PushAppOutputsResponse,
|
|
65
|
+
)
|
|
46
66
|
|
|
47
67
|
# pylint: disable=E0611
|
|
48
68
|
from flwr.proto.clientappio_pb2 import (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
PushClientAppOutputsRequest,
|
|
54
|
-
PushClientAppOutputsResponse,
|
|
69
|
+
GetRunIdsWithPendingMessagesRequest,
|
|
70
|
+
GetRunIdsWithPendingMessagesResponse,
|
|
71
|
+
RequestTokenRequest,
|
|
72
|
+
RequestTokenResponse,
|
|
55
73
|
)
|
|
56
74
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
57
|
-
|
|
58
|
-
from .utils import
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def flwr_clientapp() -> None:
|
|
62
|
-
"""Run process-isolated Flower ClientApp."""
|
|
63
|
-
args = _parse_args_run_flwr_clientapp().parse_args()
|
|
64
|
-
if not args.insecure:
|
|
65
|
-
flwr_exit(
|
|
66
|
-
ExitCode.COMMON_TLS_NOT_SUPPORTED,
|
|
67
|
-
"flwr-clientapp does not support TLS yet.",
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
log(INFO, "Start `flwr-clientapp` process")
|
|
71
|
-
log(
|
|
72
|
-
DEBUG,
|
|
73
|
-
"`flwr-clientapp` will attempt to connect to SuperNode's "
|
|
74
|
-
"ClientAppIo API at %s with token %s",
|
|
75
|
-
args.clientappio_api_address,
|
|
76
|
-
args.token,
|
|
77
|
-
)
|
|
78
|
-
run_clientapp(
|
|
79
|
-
clientappio_api_address=args.clientappio_api_address,
|
|
80
|
-
run_once=(args.token is not None),
|
|
81
|
-
token=args.token,
|
|
82
|
-
flwr_dir=args.flwr_dir,
|
|
83
|
-
certificates=None,
|
|
84
|
-
)
|
|
75
|
+
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
76
|
+
from flwr.supercore.utils import mask_string
|
|
85
77
|
|
|
86
78
|
|
|
87
|
-
def run_clientapp( # pylint: disable=R0914
|
|
79
|
+
def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
88
80
|
clientappio_api_address: str,
|
|
89
81
|
run_once: bool,
|
|
90
|
-
token: Optional[
|
|
82
|
+
token: Optional[str] = None,
|
|
91
83
|
flwr_dir: Optional[str] = None,
|
|
92
84
|
certificates: Optional[bytes] = None,
|
|
85
|
+
parent_pid: Optional[int] = None,
|
|
93
86
|
) -> None:
|
|
94
87
|
"""Run Flower ClientApp process."""
|
|
88
|
+
# Monitor the main process in case of SIGKILL
|
|
89
|
+
if parent_pid is not None:
|
|
90
|
+
start_parent_process_monitor(parent_pid)
|
|
91
|
+
|
|
95
92
|
channel = create_channel(
|
|
96
93
|
server_address=clientappio_api_address,
|
|
97
94
|
insecure=(certificates is None),
|
|
@@ -107,9 +104,8 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
107
104
|
|
|
108
105
|
while True:
|
|
109
106
|
# If token is not set, loop until token is received from SuperNode
|
|
110
|
-
|
|
107
|
+
if token is None:
|
|
111
108
|
token = get_token(stub)
|
|
112
|
-
time.sleep(1)
|
|
113
109
|
|
|
114
110
|
# Pull Message, Context, Run and (optional) FAB from SuperNode
|
|
115
111
|
message, context, run, fab = pull_clientappinputs(stub=stub, token=token)
|
|
@@ -181,34 +177,72 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
181
177
|
channel.close()
|
|
182
178
|
|
|
183
179
|
|
|
184
|
-
def
|
|
180
|
+
def start_parent_process_monitor(
|
|
181
|
+
parent_pid: int,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Monitor the parent process and exit if it terminates."""
|
|
184
|
+
|
|
185
|
+
def monitor() -> None:
|
|
186
|
+
while True:
|
|
187
|
+
time.sleep(0.2)
|
|
188
|
+
if os.getppid() != parent_pid:
|
|
189
|
+
os.kill(os.getpid(), 9)
|
|
190
|
+
|
|
191
|
+
threading.Thread(target=monitor, daemon=True).start()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_token(stub: ClientAppIoStub) -> str:
|
|
185
195
|
"""Get a token from SuperNode."""
|
|
186
196
|
log(DEBUG, "[flwr-clientapp] Request token")
|
|
187
|
-
|
|
188
|
-
res:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
while True:
|
|
198
|
+
res: GetRunIdsWithPendingMessagesResponse = stub.GetRunIdsWithPendingMessages(
|
|
199
|
+
GetRunIdsWithPendingMessagesRequest()
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
for run_id in res.run_ids:
|
|
203
|
+
tk_res: RequestTokenResponse = stub.RequestToken(
|
|
204
|
+
RequestTokenRequest(run_id=run_id)
|
|
205
|
+
)
|
|
206
|
+
if tk_res.token:
|
|
207
|
+
return tk_res.token
|
|
208
|
+
|
|
209
|
+
time.sleep(1) # Wait before retrying to get run IDs
|
|
197
210
|
|
|
198
211
|
|
|
199
212
|
def pull_clientappinputs(
|
|
200
|
-
stub:
|
|
213
|
+
stub: ClientAppIoStub, token: str
|
|
201
214
|
) -> tuple[Message, Context, Run, Optional[Fab]]:
|
|
202
215
|
"""Pull ClientAppInputs from SuperNode."""
|
|
203
|
-
|
|
216
|
+
masked_token = mask_string(token)
|
|
217
|
+
log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
|
|
204
218
|
try:
|
|
205
|
-
|
|
206
|
-
|
|
219
|
+
# Pull Context, Run and (optional) FAB
|
|
220
|
+
res: PullAppInputsResponse = stub.PullClientAppInputs(
|
|
221
|
+
PullAppInputsRequest(token=token)
|
|
207
222
|
)
|
|
208
|
-
message = message_from_proto(res.message)
|
|
209
223
|
context = context_from_proto(res.context)
|
|
210
224
|
run = run_from_proto(res.run)
|
|
211
225
|
fab = fab_from_proto(res.fab) if res.fab else None
|
|
226
|
+
|
|
227
|
+
# Pull and inflate the message
|
|
228
|
+
pull_msg_res: PullAppMessagesResponse = stub.PullMessage(
|
|
229
|
+
PullAppMessagesRequest(token=token)
|
|
230
|
+
)
|
|
231
|
+
run_id = context.run_id
|
|
232
|
+
node = Node(node_id=context.node_id)
|
|
233
|
+
object_tree = pull_msg_res.message_object_trees[0]
|
|
234
|
+
message = pull_and_inflate_object_from_tree(
|
|
235
|
+
object_tree,
|
|
236
|
+
make_pull_object_fn_protobuf(stub.PullObject, node, run_id),
|
|
237
|
+
make_confirm_message_received_fn_protobuf(
|
|
238
|
+
stub.ConfirmMessageReceived, node, run_id
|
|
239
|
+
),
|
|
240
|
+
return_type=Message,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Set the message ID
|
|
244
|
+
# The deflated message doesn't contain the message_id (its own object_id)
|
|
245
|
+
message.metadata.__dict__["_message_id"] = object_tree.object_id
|
|
212
246
|
return message, context, run, fab
|
|
213
247
|
except grpc.RpcError as e:
|
|
214
248
|
log(ERROR, "[PullClientAppInputs] gRPC error occurred: %s", str(e))
|
|
@@ -216,42 +250,54 @@ def pull_clientappinputs(
|
|
|
216
250
|
|
|
217
251
|
|
|
218
252
|
def push_clientappoutputs(
|
|
219
|
-
stub:
|
|
220
|
-
) ->
|
|
253
|
+
stub: ClientAppIoStub, token: str, message: Message, context: Context
|
|
254
|
+
) -> PushAppOutputsResponse:
|
|
221
255
|
"""Push ClientAppOutputs to SuperNode."""
|
|
222
|
-
|
|
223
|
-
|
|
256
|
+
masked_token = mask_string(token)
|
|
257
|
+
log(INFO, "[flwr-clientapp] Push `ClientAppOutputs` for token %s", masked_token)
|
|
258
|
+
# Set message ID
|
|
259
|
+
message.metadata.__dict__["_message_id"] = message.object_id
|
|
260
|
+
proto_message = message_to_proto(remove_content_from_message(message))
|
|
224
261
|
proto_context = context_to_proto(context)
|
|
225
262
|
|
|
226
263
|
try:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
264
|
+
|
|
265
|
+
with no_object_id_recompute():
|
|
266
|
+
# Get object tree and all objects to push
|
|
267
|
+
object_tree = get_object_tree(message)
|
|
268
|
+
|
|
269
|
+
# Push Message
|
|
270
|
+
# This is temporary. The message should not contain its content
|
|
271
|
+
push_msg_res = stub.PushMessage(
|
|
272
|
+
PushAppMessagesRequest(
|
|
273
|
+
token=token,
|
|
274
|
+
messages_list=[proto_message],
|
|
275
|
+
message_object_trees=[object_tree],
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
del proto_message
|
|
279
|
+
|
|
280
|
+
# Retrieve the object IDs to push
|
|
281
|
+
object_ids_to_push = set(push_msg_res.objects_to_push)
|
|
282
|
+
|
|
283
|
+
# Push all objects
|
|
284
|
+
all_objects = get_all_nested_objects(message)
|
|
285
|
+
del message
|
|
286
|
+
push_objects(
|
|
287
|
+
all_objects,
|
|
288
|
+
make_push_object_fn_protobuf(
|
|
289
|
+
stub.PushObject,
|
|
290
|
+
Node(node_id=context.node_id),
|
|
291
|
+
run_id=context.run_id,
|
|
292
|
+
),
|
|
293
|
+
object_ids_to_push=object_ids_to_push,
|
|
230
294
|
)
|
|
295
|
+
|
|
296
|
+
# Push Context
|
|
297
|
+
res: PushAppOutputsResponse = stub.PushClientAppOutputs(
|
|
298
|
+
PushAppOutputsRequest(token=token, context=proto_context)
|
|
231
299
|
)
|
|
232
300
|
return res
|
|
233
301
|
except grpc.RpcError as e:
|
|
234
302
|
log(ERROR, "[PushClientAppOutputs] gRPC error occurred: %s", str(e))
|
|
235
303
|
raise e
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
239
|
-
"""Parse flwr-clientapp command line arguments."""
|
|
240
|
-
parser = argparse.ArgumentParser(
|
|
241
|
-
description="Run a Flower ClientApp",
|
|
242
|
-
)
|
|
243
|
-
parser.add_argument(
|
|
244
|
-
"--clientappio-api-address",
|
|
245
|
-
default=CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
246
|
-
type=str,
|
|
247
|
-
help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
|
|
248
|
-
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
249
|
-
)
|
|
250
|
-
parser.add_argument(
|
|
251
|
-
"--token",
|
|
252
|
-
type=int,
|
|
253
|
-
required=False,
|
|
254
|
-
help="Unique token generated by SuperNode for each ClientApp execution",
|
|
255
|
-
)
|
|
256
|
-
add_args_flwr_app_common(parser=parser)
|
|
257
|
-
return parser
|
|
@@ -0,0 +1,22 @@
|
|
|
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 ClientApp Scheduler."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .simple_clientapp_scheduler_plugin import SimpleClientAppSchedulerPlugin
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"SimpleClientAppSchedulerPlugin",
|
|
22
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
"""Simple Flower ClientApp Scheduler plugin."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
from flwr.supercore.scheduler import SchedulerPlugin
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SimpleClientAppSchedulerPlugin(SchedulerPlugin):
|
|
27
|
+
"""Simple Flower ClientApp Scheduler plugin.
|
|
28
|
+
|
|
29
|
+
The plugin always selects the first candidate run ID.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
|
|
33
|
+
"""Select a run ID to execute from a sequence of candidates."""
|
|
34
|
+
if not candidate_run_ids:
|
|
35
|
+
return None
|
|
36
|
+
return candidate_run_ids[0]
|
|
37
|
+
|
|
38
|
+
def launch_app(self, token: str, run_id: int) -> None:
|
|
39
|
+
"""Launch the application associated with a given run ID and token."""
|
|
40
|
+
cmds = ["flwr-clientapp", "--insecure"]
|
|
41
|
+
cmds += ["--clientappio-api-address", self.appio_api_address]
|
|
42
|
+
cmds += ["--token", token]
|
|
43
|
+
cmds += ["--parent-pid", str(os.getpid())]
|
|
44
|
+
cmds += ["--flwr-dir", self.flwr_dir]
|
|
45
|
+
# Launch the client app without waiting for it to complete.
|
|
46
|
+
# Since we don't need to manage the process, we intentionally avoid using
|
|
47
|
+
# a `with` statement. Suppress the pylint warning for it in this case.
|
|
48
|
+
# pylint: disable-next=consider-using-with
|
|
49
|
+
subprocess.Popen(cmds)
|
|
@@ -0,0 +1,15 @@
|
|
|
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 SuperNode servicers."""
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
"""ClientAppIo API Servicer."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .clientappio_servicer import ClientAppIoServicer
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ClientAppIoServicer",
|
|
22
|
+
]
|