flwr-nightly 1.20.0.dev20250617__py3-none-any.whl → 1.20.0.dev20250619__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 +14 -4
- flwr/client/grpc_rere_client/connection.py +0 -24
- flwr/client/rest_client/connection.py +0 -24
- flwr/common/constant.py +1 -0
- flwr/proto/clientappio_pb2.py +23 -19
- flwr/proto/clientappio_pb2.pyi +45 -19
- flwr/proto/clientappio_pb2_grpc.py +50 -16
- flwr/proto/clientappio_pb2_grpc.pyi +25 -12
- flwr/supercore/utils.py +32 -0
- flwr/superexec/exec_servicer.py +9 -0
- flwr/supernode/cli/flower_supernode.py +0 -7
- flwr/supernode/cli/flwr_clientapp.py +10 -3
- flwr/supernode/runtime/run_clientapp.py +29 -20
- flwr/supernode/servicer/clientappio/__init__.py +1 -3
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +84 -167
- flwr/supernode/start_client_internal.py +107 -97
- {flwr_nightly-1.20.0.dev20250617.dist-info → flwr_nightly-1.20.0.dev20250619.dist-info}/METADATA +1 -1
- {flwr_nightly-1.20.0.dev20250617.dist-info → flwr_nightly-1.20.0.dev20250619.dist-info}/RECORD +20 -19
- {flwr_nightly-1.20.0.dev20250617.dist-info → flwr_nightly-1.20.0.dev20250619.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.20.0.dev20250617.dist-info → flwr_nightly-1.20.0.dev20250619.dist-info}/entry_points.txt +0 -0
@@ -46,20 +46,23 @@ from flwr.common.typing import Fab, Run
|
|
46
46
|
|
47
47
|
# pylint: disable=E0611
|
48
48
|
from flwr.proto.clientappio_pb2 import (
|
49
|
-
|
50
|
-
|
49
|
+
GetRunIdsWithPendingMessagesRequest,
|
50
|
+
GetRunIdsWithPendingMessagesResponse,
|
51
51
|
PullClientAppInputsRequest,
|
52
52
|
PullClientAppInputsResponse,
|
53
53
|
PushClientAppOutputsRequest,
|
54
54
|
PushClientAppOutputsResponse,
|
55
|
+
RequestTokenRequest,
|
56
|
+
RequestTokenResponse,
|
55
57
|
)
|
56
58
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
59
|
+
from flwr.supercore.utils import mask_string
|
57
60
|
|
58
61
|
|
59
62
|
def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
60
63
|
clientappio_api_address: str,
|
61
64
|
run_once: bool,
|
62
|
-
token: Optional[
|
65
|
+
token: Optional[str] = None,
|
63
66
|
flwr_dir: Optional[str] = None,
|
64
67
|
certificates: Optional[bytes] = None,
|
65
68
|
parent_pid: Optional[int] = None,
|
@@ -84,9 +87,8 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
84
87
|
|
85
88
|
while True:
|
86
89
|
# If token is not set, loop until token is received from SuperNode
|
87
|
-
|
90
|
+
if token is None:
|
88
91
|
token = get_token(stub)
|
89
|
-
time.sleep(1)
|
90
92
|
|
91
93
|
# Pull Message, Context, Run and (optional) FAB from SuperNode
|
92
94
|
message, context, run, fab = pull_clientappinputs(stub=stub, token=token)
|
@@ -172,26 +174,30 @@ def start_parent_process_monitor(
|
|
172
174
|
threading.Thread(target=monitor, daemon=True).start()
|
173
175
|
|
174
176
|
|
175
|
-
def get_token(stub:
|
177
|
+
def get_token(stub: ClientAppIoStub) -> str:
|
176
178
|
"""Get a token from SuperNode."""
|
177
179
|
log(DEBUG, "[flwr-clientapp] Request token")
|
178
|
-
|
179
|
-
res:
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
180
|
+
while True:
|
181
|
+
res: GetRunIdsWithPendingMessagesResponse = stub.GetRunIdsWithPendingMessages(
|
182
|
+
GetRunIdsWithPendingMessagesRequest()
|
183
|
+
)
|
184
|
+
|
185
|
+
for run_id in res.run_ids:
|
186
|
+
tk_res: RequestTokenResponse = stub.RequestToken(
|
187
|
+
RequestTokenRequest(run_id=run_id)
|
188
|
+
)
|
189
|
+
if tk_res.token:
|
190
|
+
return tk_res.token
|
191
|
+
|
192
|
+
time.sleep(1) # Wait before retrying to get run IDs
|
188
193
|
|
189
194
|
|
190
195
|
def pull_clientappinputs(
|
191
|
-
stub:
|
196
|
+
stub: ClientAppIoStub, token: str
|
192
197
|
) -> tuple[Message, Context, Run, Optional[Fab]]:
|
193
198
|
"""Pull ClientAppInputs from SuperNode."""
|
194
|
-
|
199
|
+
masked_token = mask_string(token)
|
200
|
+
log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
|
195
201
|
try:
|
196
202
|
res: PullClientAppInputsResponse = stub.PullClientAppInputs(
|
197
203
|
PullClientAppInputsRequest(token=token)
|
@@ -207,10 +213,13 @@ def pull_clientappinputs(
|
|
207
213
|
|
208
214
|
|
209
215
|
def push_clientappoutputs(
|
210
|
-
stub:
|
216
|
+
stub: ClientAppIoStub, token: str, message: Message, context: Context
|
211
217
|
) -> PushClientAppOutputsResponse:
|
212
218
|
"""Push ClientAppOutputs to SuperNode."""
|
213
|
-
|
219
|
+
masked_token = mask_string(token)
|
220
|
+
log(INFO, "[flwr-clientapp] Push `ClientAppOutputs` for token %s", masked_token)
|
221
|
+
# Set message ID
|
222
|
+
message.metadata.__dict__["_message_id"] = message.object_id
|
214
223
|
proto_message = message_to_proto(message)
|
215
224
|
proto_context = context_to_proto(context)
|
216
225
|
|
@@ -15,10 +15,8 @@
|
|
15
15
|
"""ClientAppIo API Servicer."""
|
16
16
|
|
17
17
|
|
18
|
-
from .clientappio_servicer import
|
18
|
+
from .clientappio_servicer import ClientAppIoServicer
|
19
19
|
|
20
20
|
__all__ = [
|
21
|
-
"ClientAppInputs",
|
22
21
|
"ClientAppIoServicer",
|
23
|
-
"ClientAppOutputs",
|
24
22
|
]
|
@@ -15,16 +15,14 @@
|
|
15
15
|
"""ClientAppIo API servicer."""
|
16
16
|
|
17
17
|
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from typing import Optional, cast
|
18
|
+
from logging import DEBUG
|
19
|
+
from typing import cast
|
21
20
|
|
22
21
|
import grpc
|
23
22
|
|
24
|
-
from flwr.common import Context
|
23
|
+
from flwr.common import Context
|
25
24
|
from flwr.common.logger import log
|
26
25
|
from flwr.common.serde import (
|
27
|
-
clientappstatus_to_proto,
|
28
26
|
context_from_proto,
|
29
27
|
context_to_proto,
|
30
28
|
fab_to_proto,
|
@@ -37,71 +35,70 @@ from flwr.common.typing import Fab, Run
|
|
37
35
|
# pylint: disable=E0611
|
38
36
|
from flwr.proto import clientappio_pb2_grpc
|
39
37
|
from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
|
40
|
-
|
41
|
-
|
38
|
+
GetRunIdsWithPendingMessagesRequest,
|
39
|
+
GetRunIdsWithPendingMessagesResponse,
|
42
40
|
PullClientAppInputsRequest,
|
43
41
|
PullClientAppInputsResponse,
|
44
42
|
PushClientAppOutputsRequest,
|
45
43
|
PushClientAppOutputsResponse,
|
44
|
+
RequestTokenRequest,
|
45
|
+
RequestTokenResponse,
|
46
46
|
)
|
47
|
+
from flwr.supercore.ffs import FfsFactory
|
48
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
49
|
+
from flwr.supernode.nodestate import NodeStateFactory
|
47
50
|
|
48
51
|
|
49
|
-
|
50
|
-
class
|
51
|
-
"""
|
52
|
-
|
53
|
-
message: Message
|
54
|
-
context: Context
|
55
|
-
run: Run
|
56
|
-
fab: Optional[Fab]
|
57
|
-
token: int
|
58
|
-
|
52
|
+
# pylint: disable=C0103,W0613,W0201
|
53
|
+
class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
54
|
+
"""ClientAppIo API servicer."""
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
def __init__(
|
57
|
+
self,
|
58
|
+
state_factory: NodeStateFactory,
|
59
|
+
ffs_factory: FfsFactory,
|
60
|
+
objectstore_factory: ObjectStoreFactory,
|
61
|
+
) -> None:
|
62
|
+
self.state_factory = state_factory
|
63
|
+
self.ffs_factory = ffs_factory
|
64
|
+
self.objectstore_factory = objectstore_factory
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
+
def GetRunIdsWithPendingMessages(
|
67
|
+
self,
|
68
|
+
request: GetRunIdsWithPendingMessagesRequest,
|
69
|
+
context: grpc.ServicerContext,
|
70
|
+
) -> GetRunIdsWithPendingMessagesResponse:
|
71
|
+
"""Get run IDs with pending messages."""
|
72
|
+
log(DEBUG, "ClientAppIo.GetRunIdsWithPendingMessages")
|
66
73
|
|
74
|
+
# Initialize state connection
|
75
|
+
state = self.state_factory.state()
|
67
76
|
|
68
|
-
#
|
69
|
-
|
70
|
-
"""ClientAppIo API servicer."""
|
77
|
+
# Get run IDs with pending messages
|
78
|
+
run_ids = state.get_run_ids_with_pending_messages()
|
71
79
|
|
72
|
-
|
73
|
-
|
74
|
-
self.clientapp_output: Optional[ClientAppOutputs] = None
|
75
|
-
self.token_returned: bool = False
|
76
|
-
self.inputs_returned: bool = False
|
80
|
+
# Return run IDs
|
81
|
+
return GetRunIdsWithPendingMessagesResponse(run_ids=run_ids)
|
77
82
|
|
78
|
-
def
|
79
|
-
self, request:
|
80
|
-
) ->
|
81
|
-
"""
|
82
|
-
log(DEBUG, "ClientAppIo.
|
83
|
+
def RequestToken(
|
84
|
+
self, request: RequestTokenRequest, context: grpc.ServicerContext
|
85
|
+
) -> RequestTokenResponse:
|
86
|
+
"""Request token."""
|
87
|
+
log(DEBUG, "ClientAppIo.RequestToken")
|
83
88
|
|
84
|
-
#
|
85
|
-
|
86
|
-
context.abort(
|
87
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
88
|
-
"No inputs available.",
|
89
|
-
)
|
90
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
89
|
+
# Initialize state connection
|
90
|
+
state = self.state_factory.state()
|
91
91
|
|
92
|
-
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
92
|
+
# Attempt to create a token for the provided run ID
|
93
|
+
try:
|
94
|
+
token = state.create_token(request.run_id)
|
95
|
+
except ValueError:
|
96
|
+
# Return an empty token if A token already exists for this run ID,
|
97
|
+
# indicating the run is in progress
|
98
|
+
return RequestTokenResponse(token="")
|
98
99
|
|
99
|
-
#
|
100
|
-
|
101
|
-
# - token hasn't been returned before,
|
102
|
-
# return token
|
103
|
-
self.token_returned = True
|
104
|
-
return GetTokenResponse(token=clientapp_input.token)
|
100
|
+
# Return the token
|
101
|
+
return RequestTokenResponse(token=token)
|
105
102
|
|
106
103
|
def PullClientAppInputs(
|
107
104
|
self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
|
@@ -109,36 +106,30 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
109
106
|
"""Pull Message, Context, and Run."""
|
110
107
|
log(DEBUG, "ClientAppIo.PullClientAppInputs")
|
111
108
|
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
116
|
-
"No inputs available.",
|
117
|
-
)
|
118
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
109
|
+
# Initialize state and ffs connection
|
110
|
+
state = self.state_factory.state()
|
111
|
+
ffs = self.ffs_factory.ffs()
|
119
112
|
|
120
|
-
#
|
121
|
-
|
113
|
+
# Validate the token
|
114
|
+
run_id = state.get_run_id_by_token(request.token)
|
115
|
+
if run_id is None or not state.verify_token(run_id, request.token):
|
122
116
|
context.abort(
|
123
|
-
grpc.StatusCode.
|
124
|
-
"
|
125
|
-
"Token must be returned before can be returned only once.",
|
117
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
118
|
+
"Invalid token.",
|
126
119
|
)
|
120
|
+
raise RuntimeError("This line should never be reached.")
|
127
121
|
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
)
|
122
|
+
# Retrieve message, context, run and fab for this run
|
123
|
+
message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
|
124
|
+
context = cast(Context, state.get_context(run_id))
|
125
|
+
run = cast(Run, state.get_run(run_id))
|
126
|
+
fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
|
134
127
|
|
135
|
-
# Success
|
136
|
-
self.inputs_returned = True
|
137
128
|
return PullClientAppInputsResponse(
|
138
|
-
message=message_to_proto(
|
139
|
-
context=context_to_proto(
|
140
|
-
run=run_to_proto(
|
141
|
-
fab=fab_to_proto(
|
129
|
+
message=message_to_proto(message),
|
130
|
+
context=context_to_proto(context),
|
131
|
+
run=run_to_proto(run),
|
132
|
+
fab=fab_to_proto(fab),
|
142
133
|
)
|
143
134
|
|
144
135
|
def PushClientAppOutputs(
|
@@ -147,98 +138,24 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
147
138
|
"""Push Message and Context."""
|
148
139
|
log(DEBUG, "ClientAppIo.PushClientAppOutputs")
|
149
140
|
|
150
|
-
#
|
151
|
-
|
152
|
-
context.abort(
|
153
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
154
|
-
"No inputs available.",
|
155
|
-
)
|
156
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
141
|
+
# Initialize state connection
|
142
|
+
state = self.state_factory.state()
|
157
143
|
|
158
|
-
#
|
159
|
-
|
144
|
+
# Validate the token
|
145
|
+
run_id = state.get_run_id_by_token(request.token)
|
146
|
+
if run_id is None or not state.verify_token(run_id, request.token):
|
160
147
|
context.abort(
|
161
|
-
grpc.StatusCode.
|
162
|
-
"
|
163
|
-
"Token must be returned before can be returned only once.",
|
148
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
149
|
+
"Invalid token.",
|
164
150
|
)
|
151
|
+
raise RuntimeError("This line should never be reached.")
|
165
152
|
|
166
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
170
|
-
"Inputs haven't been delivered."
|
171
|
-
"Inputs must be delivered before can be returned only once.",
|
172
|
-
)
|
153
|
+
# Save the message and context to the state
|
154
|
+
state.store_message(message_from_proto(request.message))
|
155
|
+
state.store_context(context_from_proto(request.context))
|
173
156
|
|
174
|
-
#
|
175
|
-
|
176
|
-
|
177
|
-
grpc.StatusCode.INVALID_ARGUMENT,
|
178
|
-
"Mismatch between ClientApp and SuperNode token",
|
179
|
-
)
|
157
|
+
# Remove the token to make the run eligible for processing
|
158
|
+
# A run associated with a token cannot be handled until its token is cleared
|
159
|
+
state.delete_token(run_id)
|
180
160
|
|
181
|
-
|
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
|
161
|
+
return PushClientAppOutputsResponse()
|