flwr-nightly 1.11.0.dev20240813__py3-none-any.whl → 1.11.0.dev20240822__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/config_utils.py +2 -2
- flwr/cli/install.py +3 -1
- flwr/cli/run/run.py +15 -11
- flwr/client/app.py +132 -14
- flwr/client/clientapp/__init__.py +22 -0
- flwr/client/clientapp/app.py +233 -0
- flwr/client/clientapp/clientappio_servicer.py +244 -0
- flwr/client/clientapp/utils.py +108 -0
- flwr/client/grpc_rere_client/connection.py +9 -1
- flwr/client/node_state.py +17 -4
- flwr/client/rest_client/connection.py +16 -3
- flwr/client/supernode/__init__.py +0 -2
- flwr/client/supernode/app.py +36 -164
- flwr/common/__init__.py +4 -0
- flwr/common/config.py +31 -10
- flwr/common/record/configsrecord.py +49 -15
- flwr/common/record/metricsrecord.py +54 -14
- flwr/common/record/parametersrecord.py +84 -17
- flwr/common/record/recordset.py +80 -8
- flwr/common/record/typeddict.py +20 -58
- flwr/common/recordset_compat.py +6 -6
- flwr/common/serde.py +24 -2
- flwr/common/typing.py +1 -0
- flwr/proto/clientappio_pb2.py +17 -13
- flwr/proto/clientappio_pb2.pyi +24 -2
- flwr/proto/clientappio_pb2_grpc.py +34 -0
- flwr/proto/clientappio_pb2_grpc.pyi +13 -0
- flwr/proto/exec_pb2.py +16 -15
- flwr/proto/exec_pb2.pyi +7 -4
- flwr/proto/message_pb2.py +2 -2
- flwr/proto/message_pb2.pyi +4 -1
- flwr/server/app.py +15 -0
- flwr/server/driver/grpc_driver.py +1 -0
- flwr/server/run_serverapp.py +18 -2
- flwr/server/server.py +3 -1
- flwr/server/superlink/driver/driver_grpc.py +3 -0
- flwr/server/superlink/driver/driver_servicer.py +32 -4
- flwr/server/superlink/ffs/disk_ffs.py +6 -3
- flwr/server/superlink/ffs/ffs.py +3 -3
- flwr/server/superlink/ffs/ffs_factory.py +47 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +12 -4
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +8 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +16 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -2
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/state/in_memory_state.py +7 -5
- flwr/server/superlink/state/sqlite_state.py +17 -7
- flwr/server/superlink/state/state.py +4 -3
- flwr/server/workflow/default_workflows.py +3 -1
- flwr/simulation/run_simulation.py +5 -67
- flwr/superexec/app.py +3 -3
- flwr/superexec/deployment.py +8 -9
- flwr/superexec/exec_servicer.py +1 -1
- {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240822.dist-info}/METADATA +2 -2
- {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240822.dist-info}/RECORD +58 -53
- {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240822.dist-info}/entry_points.txt +1 -1
- {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240822.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240822.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Copyright 2024 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 dataclasses import dataclass
|
|
19
|
+
from logging import DEBUG, ERROR
|
|
20
|
+
from typing import Optional, cast
|
|
21
|
+
|
|
22
|
+
import grpc
|
|
23
|
+
|
|
24
|
+
from flwr.common import Context, Message, typing
|
|
25
|
+
from flwr.common.logger import log
|
|
26
|
+
from flwr.common.serde import (
|
|
27
|
+
clientappstatus_to_proto,
|
|
28
|
+
context_from_proto,
|
|
29
|
+
context_to_proto,
|
|
30
|
+
fab_to_proto,
|
|
31
|
+
message_from_proto,
|
|
32
|
+
message_to_proto,
|
|
33
|
+
run_to_proto,
|
|
34
|
+
)
|
|
35
|
+
from flwr.common.typing import Fab, Run
|
|
36
|
+
|
|
37
|
+
# pylint: disable=E0611
|
|
38
|
+
from flwr.proto import clientappio_pb2_grpc
|
|
39
|
+
from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
|
|
40
|
+
GetTokenRequest,
|
|
41
|
+
GetTokenResponse,
|
|
42
|
+
PullClientAppInputsRequest,
|
|
43
|
+
PullClientAppInputsResponse,
|
|
44
|
+
PushClientAppOutputsRequest,
|
|
45
|
+
PushClientAppOutputsResponse,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ClientAppInputs:
|
|
51
|
+
"""Specify the inputs to the ClientApp."""
|
|
52
|
+
|
|
53
|
+
message: Message
|
|
54
|
+
context: Context
|
|
55
|
+
run: Run
|
|
56
|
+
fab: Optional[Fab]
|
|
57
|
+
token: int
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class ClientAppOutputs:
|
|
62
|
+
"""Specify the outputs from the ClientApp."""
|
|
63
|
+
|
|
64
|
+
message: Message
|
|
65
|
+
context: Context
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# pylint: disable=C0103,W0613,W0201
|
|
69
|
+
class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
70
|
+
"""ClientAppIo API servicer."""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
self.clientapp_input: Optional[ClientAppInputs] = None
|
|
74
|
+
self.clientapp_output: Optional[ClientAppOutputs] = None
|
|
75
|
+
self.token_returned: bool = False
|
|
76
|
+
self.inputs_returned: bool = False
|
|
77
|
+
|
|
78
|
+
def GetToken(
|
|
79
|
+
self, request: GetTokenRequest, context: grpc.ServicerContext
|
|
80
|
+
) -> GetTokenResponse:
|
|
81
|
+
"""Get token."""
|
|
82
|
+
log(DEBUG, "ClientAppIo.GetToken")
|
|
83
|
+
|
|
84
|
+
# Fail if no ClientAppInputs are available
|
|
85
|
+
if self.clientapp_input is None:
|
|
86
|
+
context.abort(
|
|
87
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
88
|
+
"No inputs available.",
|
|
89
|
+
)
|
|
90
|
+
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
|
91
|
+
|
|
92
|
+
# Fail if token was already returned in a previous call
|
|
93
|
+
if self.token_returned:
|
|
94
|
+
context.abort(
|
|
95
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
96
|
+
"Token already returned. A token can be returned only once.",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# If
|
|
100
|
+
# - ClientAppInputs is set, and
|
|
101
|
+
# - token hasn't been returned before,
|
|
102
|
+
# return token
|
|
103
|
+
self.token_returned = True
|
|
104
|
+
return GetTokenResponse(token=clientapp_input.token)
|
|
105
|
+
|
|
106
|
+
def PullClientAppInputs(
|
|
107
|
+
self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
|
|
108
|
+
) -> PullClientAppInputsResponse:
|
|
109
|
+
"""Pull Message, Context, and Run."""
|
|
110
|
+
log(DEBUG, "ClientAppIo.PullClientAppInputs")
|
|
111
|
+
|
|
112
|
+
# Fail if no ClientAppInputs are available
|
|
113
|
+
if self.clientapp_input is None:
|
|
114
|
+
context.abort(
|
|
115
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
116
|
+
"No inputs available.",
|
|
117
|
+
)
|
|
118
|
+
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
|
119
|
+
|
|
120
|
+
# Fail if token wasn't returned in a previous call
|
|
121
|
+
if not self.token_returned:
|
|
122
|
+
context.abort(
|
|
123
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
124
|
+
"Token hasn't been returned."
|
|
125
|
+
"Token must be returned before can be returned only once.",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Fail if token isn't matching
|
|
129
|
+
if request.token != clientapp_input.token:
|
|
130
|
+
context.abort(
|
|
131
|
+
grpc.StatusCode.INVALID_ARGUMENT,
|
|
132
|
+
"Mismatch between ClientApp and SuperNode token",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Success
|
|
136
|
+
self.inputs_returned = True
|
|
137
|
+
return PullClientAppInputsResponse(
|
|
138
|
+
message=message_to_proto(clientapp_input.message),
|
|
139
|
+
context=context_to_proto(clientapp_input.context),
|
|
140
|
+
run=run_to_proto(clientapp_input.run),
|
|
141
|
+
fab=fab_to_proto(clientapp_input.fab) if clientapp_input.fab else None,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def PushClientAppOutputs(
|
|
145
|
+
self, request: PushClientAppOutputsRequest, context: grpc.ServicerContext
|
|
146
|
+
) -> PushClientAppOutputsResponse:
|
|
147
|
+
"""Push Message and Context."""
|
|
148
|
+
log(DEBUG, "ClientAppIo.PushClientAppOutputs")
|
|
149
|
+
|
|
150
|
+
# Fail if no ClientAppInputs are available
|
|
151
|
+
if not self.clientapp_input:
|
|
152
|
+
context.abort(
|
|
153
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
154
|
+
"No inputs available.",
|
|
155
|
+
)
|
|
156
|
+
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
|
157
|
+
|
|
158
|
+
# Fail if token wasn't returned in a previous call
|
|
159
|
+
if not self.token_returned:
|
|
160
|
+
context.abort(
|
|
161
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
162
|
+
"Token hasn't been returned."
|
|
163
|
+
"Token must be returned before can be returned only once.",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Fail if inputs weren't delivered in a previous call
|
|
167
|
+
if not self.inputs_returned:
|
|
168
|
+
context.abort(
|
|
169
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
170
|
+
"Inputs haven't been delivered."
|
|
171
|
+
"Inputs must be delivered before can be returned only once.",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Fail if token isn't matching
|
|
175
|
+
if request.token != clientapp_input.token:
|
|
176
|
+
context.abort(
|
|
177
|
+
grpc.StatusCode.INVALID_ARGUMENT,
|
|
178
|
+
"Mismatch between ClientApp and SuperNode token",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Preconditions met
|
|
182
|
+
try:
|
|
183
|
+
# Update Message and Context
|
|
184
|
+
self.clientapp_output = ClientAppOutputs(
|
|
185
|
+
message=message_from_proto(request.message),
|
|
186
|
+
context=context_from_proto(request.context),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Set status
|
|
190
|
+
code = typing.ClientAppOutputCode.SUCCESS
|
|
191
|
+
status = typing.ClientAppOutputStatus(code=code, message="Success")
|
|
192
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
193
|
+
log(ERROR, "ClientApp failed to push message to SuperNode, %s", e)
|
|
194
|
+
code = typing.ClientAppOutputCode.UNKNOWN_ERROR
|
|
195
|
+
status = typing.ClientAppOutputStatus(code=code, message="Unkonwn error")
|
|
196
|
+
|
|
197
|
+
# Return status to ClientApp process
|
|
198
|
+
proto_status = clientappstatus_to_proto(status=status)
|
|
199
|
+
return PushClientAppOutputsResponse(status=proto_status)
|
|
200
|
+
|
|
201
|
+
def set_inputs(
|
|
202
|
+
self, clientapp_input: ClientAppInputs, token_returned: bool
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Set ClientApp inputs.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
clientapp_input : ClientAppInputs
|
|
209
|
+
The inputs to the ClientApp.
|
|
210
|
+
token_returned : bool
|
|
211
|
+
A boolean indicating if the token has been returned.
|
|
212
|
+
Set to `True` when passing the token to `flwr-clientap`
|
|
213
|
+
and `False` otherwise.
|
|
214
|
+
"""
|
|
215
|
+
if (
|
|
216
|
+
self.clientapp_input is not None
|
|
217
|
+
or self.clientapp_output is not None
|
|
218
|
+
or self.token_returned
|
|
219
|
+
):
|
|
220
|
+
raise ValueError(
|
|
221
|
+
"ClientAppInputs and ClientAppOutputs must not be set before "
|
|
222
|
+
"calling `set_inputs`."
|
|
223
|
+
)
|
|
224
|
+
log(DEBUG, "ClientAppInputs set (token: %s)", clientapp_input.token)
|
|
225
|
+
self.clientapp_input = clientapp_input
|
|
226
|
+
self.token_returned = token_returned
|
|
227
|
+
|
|
228
|
+
def has_outputs(self) -> bool:
|
|
229
|
+
"""Check if ClientAppOutputs are available."""
|
|
230
|
+
return self.clientapp_output is not None
|
|
231
|
+
|
|
232
|
+
def get_outputs(self) -> ClientAppOutputs:
|
|
233
|
+
"""Get ClientApp outputs."""
|
|
234
|
+
if self.clientapp_output is None:
|
|
235
|
+
raise ValueError("ClientAppOutputs not set before calling `get_outputs`.")
|
|
236
|
+
|
|
237
|
+
# Set outputs to a local variable and clear state
|
|
238
|
+
output: ClientAppOutputs = self.clientapp_output
|
|
239
|
+
self.clientapp_input = None
|
|
240
|
+
self.clientapp_output = None
|
|
241
|
+
self.token_returned = False
|
|
242
|
+
self.inputs_returned = False
|
|
243
|
+
|
|
244
|
+
return output
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Copyright 2024 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 loading utils."""
|
|
16
|
+
|
|
17
|
+
from logging import DEBUG
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Callable, Optional
|
|
20
|
+
|
|
21
|
+
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
22
|
+
from flwr.common.config import (
|
|
23
|
+
get_flwr_dir,
|
|
24
|
+
get_metadata_from_config,
|
|
25
|
+
get_project_config,
|
|
26
|
+
get_project_dir,
|
|
27
|
+
)
|
|
28
|
+
from flwr.common.logger import log
|
|
29
|
+
from flwr.common.object_ref import load_app, validate
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_load_client_app_fn(
|
|
33
|
+
default_app_ref: str,
|
|
34
|
+
app_path: Optional[str],
|
|
35
|
+
multi_app: bool,
|
|
36
|
+
flwr_dir: Optional[str] = None,
|
|
37
|
+
) -> Callable[[str, str], ClientApp]:
|
|
38
|
+
"""Get the load_client_app_fn function.
|
|
39
|
+
|
|
40
|
+
If `multi_app` is True, this function loads the specified ClientApp
|
|
41
|
+
based on `fab_id` and `fab_version`. If `fab_id` is empty, a default
|
|
42
|
+
ClientApp will be loaded.
|
|
43
|
+
|
|
44
|
+
If `multi_app` is False, it ignores `fab_id` and `fab_version` and
|
|
45
|
+
loads a default ClientApp.
|
|
46
|
+
"""
|
|
47
|
+
if not multi_app:
|
|
48
|
+
log(
|
|
49
|
+
DEBUG,
|
|
50
|
+
"Flower SuperNode will load and validate ClientApp `%s`",
|
|
51
|
+
default_app_ref,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
valid, error_msg = validate(default_app_ref, project_dir=app_path)
|
|
55
|
+
if not valid and error_msg:
|
|
56
|
+
raise LoadClientAppError(error_msg) from None
|
|
57
|
+
|
|
58
|
+
def _load(fab_id: str, fab_version: str) -> ClientApp:
|
|
59
|
+
runtime_app_dir = Path(app_path if app_path else "").absolute()
|
|
60
|
+
# If multi-app feature is disabled
|
|
61
|
+
if not multi_app:
|
|
62
|
+
# Set app reference
|
|
63
|
+
client_app_ref = default_app_ref
|
|
64
|
+
# If multi-app feature is enabled but app directory is provided
|
|
65
|
+
elif app_path is not None:
|
|
66
|
+
config = get_project_config(runtime_app_dir)
|
|
67
|
+
this_fab_version, this_fab_id = get_metadata_from_config(config)
|
|
68
|
+
|
|
69
|
+
if this_fab_version != fab_version or this_fab_id != fab_id:
|
|
70
|
+
raise LoadClientAppError(
|
|
71
|
+
f"FAB ID or version mismatch: Expected FAB ID '{this_fab_id}' and "
|
|
72
|
+
f"FAB version '{this_fab_version}', but received FAB ID '{fab_id}' "
|
|
73
|
+
f"and FAB version '{fab_version}'.",
|
|
74
|
+
) from None
|
|
75
|
+
|
|
76
|
+
# log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
|
|
77
|
+
|
|
78
|
+
# Set app reference
|
|
79
|
+
client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
|
|
80
|
+
# If multi-app feature is enabled
|
|
81
|
+
else:
|
|
82
|
+
try:
|
|
83
|
+
runtime_app_dir = get_project_dir(
|
|
84
|
+
fab_id, fab_version, get_flwr_dir(flwr_dir)
|
|
85
|
+
)
|
|
86
|
+
config = get_project_config(runtime_app_dir)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
raise LoadClientAppError("Failed to load ClientApp") from e
|
|
89
|
+
|
|
90
|
+
# Set app reference
|
|
91
|
+
client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
|
|
92
|
+
|
|
93
|
+
# Load ClientApp
|
|
94
|
+
log(
|
|
95
|
+
DEBUG,
|
|
96
|
+
"Loading ClientApp `%s`",
|
|
97
|
+
client_app_ref,
|
|
98
|
+
)
|
|
99
|
+
client_app = load_app(client_app_ref, LoadClientAppError, runtime_app_dir)
|
|
100
|
+
|
|
101
|
+
if not isinstance(client_app, ClientApp):
|
|
102
|
+
raise LoadClientAppError(
|
|
103
|
+
f"Attribute {client_app_ref} is not of type {ClientApp}",
|
|
104
|
+
) from None
|
|
105
|
+
|
|
106
|
+
return client_app
|
|
107
|
+
|
|
108
|
+
return _load
|
|
@@ -46,6 +46,7 @@ from flwr.common.serde import (
|
|
|
46
46
|
user_config_from_proto,
|
|
47
47
|
)
|
|
48
48
|
from flwr.common.typing import Fab, Run
|
|
49
|
+
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
49
50
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
50
51
|
CreateNodeRequest,
|
|
51
52
|
DeleteNodeRequest,
|
|
@@ -286,12 +287,19 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
286
287
|
run_id,
|
|
287
288
|
get_run_response.run.fab_id,
|
|
288
289
|
get_run_response.run.fab_version,
|
|
290
|
+
get_run_response.run.fab_hash,
|
|
289
291
|
user_config_from_proto(get_run_response.run.override_config),
|
|
290
292
|
)
|
|
291
293
|
|
|
292
294
|
def get_fab(fab_hash: str) -> Fab:
|
|
293
295
|
# Call FleetAPI
|
|
294
|
-
|
|
296
|
+
get_fab_request = GetFabRequest(hash_str=fab_hash)
|
|
297
|
+
get_fab_response: GetFabResponse = retry_invoker.invoke(
|
|
298
|
+
stub.GetFab,
|
|
299
|
+
request=get_fab_request,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return Fab(get_fab_response.fab.hash_str, get_fab_response.fab.content)
|
|
295
303
|
|
|
296
304
|
try:
|
|
297
305
|
# Yield methods
|
flwr/client/node_state.py
CHANGED
|
@@ -20,8 +20,12 @@ from pathlib import Path
|
|
|
20
20
|
from typing import Dict, Optional
|
|
21
21
|
|
|
22
22
|
from flwr.common import Context, RecordSet
|
|
23
|
-
from flwr.common.config import
|
|
24
|
-
|
|
23
|
+
from flwr.common.config import (
|
|
24
|
+
get_fused_config,
|
|
25
|
+
get_fused_config_from_dir,
|
|
26
|
+
get_fused_config_from_fab,
|
|
27
|
+
)
|
|
28
|
+
from flwr.common.typing import Fab, Run, UserConfig
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
@dataclass()
|
|
@@ -44,12 +48,14 @@ class NodeState:
|
|
|
44
48
|
self.node_config = node_config
|
|
45
49
|
self.run_infos: Dict[int, RunInfo] = {}
|
|
46
50
|
|
|
51
|
+
# pylint: disable=too-many-arguments
|
|
47
52
|
def register_context(
|
|
48
53
|
self,
|
|
49
54
|
run_id: int,
|
|
50
55
|
run: Optional[Run] = None,
|
|
51
56
|
flwr_path: Optional[Path] = None,
|
|
52
57
|
app_dir: Optional[str] = None,
|
|
58
|
+
fab: Optional[Fab] = None,
|
|
53
59
|
) -> None:
|
|
54
60
|
"""Register new run context for this node."""
|
|
55
61
|
if run_id not in self.run_infos:
|
|
@@ -65,8 +71,15 @@ class NodeState:
|
|
|
65
71
|
else:
|
|
66
72
|
raise ValueError("The specified `app_dir` must be a directory.")
|
|
67
73
|
else:
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
if run:
|
|
75
|
+
if fab:
|
|
76
|
+
# Load pyproject.toml from FAB file and fuse
|
|
77
|
+
initial_run_config = get_fused_config_from_fab(fab.content, run)
|
|
78
|
+
else:
|
|
79
|
+
# Load pyproject.toml from installed FAB and fuse
|
|
80
|
+
initial_run_config = get_fused_config(run, flwr_path)
|
|
81
|
+
else:
|
|
82
|
+
initial_run_config = {}
|
|
70
83
|
self.run_infos[run_id] = RunInfo(
|
|
71
84
|
initial_run_config=initial_run_config,
|
|
72
85
|
context=Context(
|
|
@@ -46,6 +46,7 @@ from flwr.common.serde import (
|
|
|
46
46
|
user_config_from_proto,
|
|
47
47
|
)
|
|
48
48
|
from flwr.common.typing import Fab, Run
|
|
49
|
+
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
49
50
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
50
51
|
CreateNodeRequest,
|
|
51
52
|
CreateNodeResponse,
|
|
@@ -74,6 +75,7 @@ PATH_PULL_TASK_INS: str = "api/v0/fleet/pull-task-ins"
|
|
|
74
75
|
PATH_PUSH_TASK_RES: str = "api/v0/fleet/push-task-res"
|
|
75
76
|
PATH_PING: str = "api/v0/fleet/ping"
|
|
76
77
|
PATH_GET_RUN: str = "/api/v0/fleet/get-run"
|
|
78
|
+
PATH_GET_FAB: str = "/api/v0/fleet/get-fab"
|
|
77
79
|
|
|
78
80
|
T = TypeVar("T", bound=GrpcMessage)
|
|
79
81
|
|
|
@@ -358,18 +360,29 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
|
|
|
358
360
|
# Send the request
|
|
359
361
|
res = _request(req, GetRunResponse, PATH_GET_RUN)
|
|
360
362
|
if res is None:
|
|
361
|
-
return Run(run_id, "", "", {})
|
|
363
|
+
return Run(run_id, "", "", "", {})
|
|
362
364
|
|
|
363
365
|
return Run(
|
|
364
366
|
run_id,
|
|
365
367
|
res.run.fab_id,
|
|
366
368
|
res.run.fab_version,
|
|
369
|
+
res.run.fab_hash,
|
|
367
370
|
user_config_from_proto(res.run.override_config),
|
|
368
371
|
)
|
|
369
372
|
|
|
370
373
|
def get_fab(fab_hash: str) -> Fab:
|
|
371
|
-
#
|
|
372
|
-
|
|
374
|
+
# Construct the request
|
|
375
|
+
req = GetFabRequest(hash_str=fab_hash)
|
|
376
|
+
|
|
377
|
+
# Send the request
|
|
378
|
+
res = _request(req, GetFabResponse, PATH_GET_FAB)
|
|
379
|
+
if res is None:
|
|
380
|
+
return Fab("", b"")
|
|
381
|
+
|
|
382
|
+
return Fab(
|
|
383
|
+
res.fab.hash_str,
|
|
384
|
+
res.fab.content,
|
|
385
|
+
)
|
|
373
386
|
|
|
374
387
|
try:
|
|
375
388
|
# Yield methods
|
|
@@ -15,12 +15,10 @@
|
|
|
15
15
|
"""Flower SuperNode."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from .app import flwr_clientapp as flwr_clientapp
|
|
19
18
|
from .app import run_client_app as run_client_app
|
|
20
19
|
from .app import run_supernode as run_supernode
|
|
21
20
|
|
|
22
21
|
__all__ = [
|
|
23
|
-
"flwr_clientapp",
|
|
24
22
|
"run_client_app",
|
|
25
23
|
"run_supernode",
|
|
26
24
|
]
|