flwr-nightly 1.19.0.dev20250616__py3-none-any.whl → 1.20.0.dev20250618__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/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/run.py +45 -38
- flwr/proto/clientappio_pb2.py +27 -19
- flwr/proto/clientappio_pb2.pyi +47 -4
- flwr/proto/clientappio_pb2_grpc.py +70 -2
- flwr/proto/clientappio_pb2_grpc.pyi +30 -4
- flwr/server/app.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +1 -1
- 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 +1 -2
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +1 -1
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/superexec/deployment.py +1 -2
- flwr/superexec/exec_grpc.py +1 -1
- flwr/superexec/exec_servicer.py +1 -1
- flwr/superexec/executor.py +1 -1
- flwr/superexec/simulation.py +1 -2
- flwr/supernode/cli/flwr_clientapp.py +8 -2
- flwr/supernode/nodestate/in_memory_nodestate.py +11 -2
- flwr/supernode/nodestate/nodestate.py +15 -0
- flwr/supernode/runtime/run_clientapp.py +22 -18
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +83 -85
- flwr/supernode/start_client_internal.py +23 -36
- {flwr_nightly-1.19.0.dev20250616.dist-info → flwr_nightly-1.20.0.dev20250618.dist-info}/METADATA +1 -1
- {flwr_nightly-1.19.0.dev20250616.dist-info → flwr_nightly-1.20.0.dev20250618.dist-info}/RECORD +41 -41
- /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_nightly-1.19.0.dev20250616.dist-info → flwr_nightly-1.20.0.dev20250618.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.19.0.dev20250616.dist-info → flwr_nightly-1.20.0.dev20250618.dist-info}/entry_points.txt +0 -0
flwr/superexec/exec_grpc.py
CHANGED
@@ -27,8 +27,8 @@ from flwr.common.grpc import generic_create_grpc_server
|
|
27
27
|
from flwr.common.logger import log
|
28
28
|
from flwr.common.typing import UserConfig
|
29
29
|
from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
|
30
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
31
30
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
31
|
+
from flwr.supercore.ffs import FfsFactory
|
32
32
|
from flwr.supercore.object_store import ObjectStoreFactory
|
33
33
|
from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
|
34
34
|
from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
|
flwr/superexec/exec_servicer.py
CHANGED
@@ -52,8 +52,8 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
52
52
|
StreamLogsRequest,
|
53
53
|
StreamLogsResponse,
|
54
54
|
)
|
55
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
56
55
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
56
|
+
from flwr.supercore.ffs import FfsFactory
|
57
57
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
58
58
|
|
59
59
|
from .exec_user_auth_interceptor import shared_account_info
|
flwr/superexec/executor.py
CHANGED
@@ -22,8 +22,8 @@ from typing import Optional
|
|
22
22
|
|
23
23
|
from flwr.common import ConfigRecord
|
24
24
|
from flwr.common.typing import UserConfig
|
25
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
26
25
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
26
|
+
from flwr.supercore.ffs import FfsFactory
|
27
27
|
|
28
28
|
|
29
29
|
@dataclass
|
flwr/superexec/simulation.py
CHANGED
@@ -25,9 +25,8 @@ from flwr.cli.config_utils import get_fab_metadata
|
|
25
25
|
from flwr.common import ConfigRecord, Context, RecordDict
|
26
26
|
from flwr.common.logger import log
|
27
27
|
from flwr.common.typing import Fab, UserConfig
|
28
|
-
from flwr.server.superlink.ffs import Ffs
|
29
|
-
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
30
28
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
29
|
+
from flwr.supercore.ffs import Ffs, FfsFactory
|
31
30
|
|
32
31
|
from .executor import Executor
|
33
32
|
|
@@ -44,7 +44,7 @@ def flwr_clientapp() -> None:
|
|
44
44
|
)
|
45
45
|
run_clientapp(
|
46
46
|
clientappio_api_address=args.clientappio_api_address,
|
47
|
-
run_once=(args.token is not None),
|
47
|
+
run_once=(args.token is not None) or args.run_once,
|
48
48
|
token=args.token,
|
49
49
|
flwr_dir=args.flwr_dir,
|
50
50
|
certificates=None,
|
@@ -66,7 +66,7 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
66
66
|
)
|
67
67
|
parser.add_argument(
|
68
68
|
"--token",
|
69
|
-
type=
|
69
|
+
type=str,
|
70
70
|
required=False,
|
71
71
|
help="Unique token generated by SuperNode for each ClientApp execution",
|
72
72
|
)
|
@@ -77,5 +77,11 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
77
77
|
help="The PID of the parent process. When set, the process will terminate "
|
78
78
|
"when the parent process exits.",
|
79
79
|
)
|
80
|
+
parser.add_argument(
|
81
|
+
"--run-once",
|
82
|
+
action="store_true",
|
83
|
+
help="When set, this process will start a single ClientApp for a pending "
|
84
|
+
"message. If there is no pending message, the process will exit.",
|
85
|
+
)
|
80
86
|
add_args_flwr_app_common(parser=parser)
|
81
87
|
return parser
|
@@ -51,8 +51,9 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
51
51
|
# Store run ID to Context mapping
|
52
52
|
self.ctx_store: dict[int, Context] = {}
|
53
53
|
self.lock_ctx_store = Lock()
|
54
|
-
# Store run ID to token mapping
|
54
|
+
# Store run ID to token mapping and token to run ID mapping
|
55
55
|
self.token_store: dict[int, str] = {}
|
56
|
+
self.token_to_run_id: dict[str, int] = {}
|
56
57
|
self.lock_token_store = Lock()
|
57
58
|
|
58
59
|
def set_node_id(self, node_id: Optional[int]) -> None:
|
@@ -177,6 +178,7 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
177
178
|
if run_id in self.token_store:
|
178
179
|
raise ValueError("Token already created for this run ID")
|
179
180
|
self.token_store[run_id] = token
|
181
|
+
self.token_to_run_id[token] = run_id
|
180
182
|
return token
|
181
183
|
|
182
184
|
def verify_token(self, run_id: int, token: str) -> bool:
|
@@ -187,4 +189,11 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
187
189
|
def delete_token(self, run_id: int) -> None:
|
188
190
|
"""Delete the token for the given run ID."""
|
189
191
|
with self.lock_token_store:
|
190
|
-
self.token_store.pop(run_id, None)
|
192
|
+
token = self.token_store.pop(run_id, None)
|
193
|
+
if token is not None:
|
194
|
+
self.token_to_run_id.pop(token, None)
|
195
|
+
|
196
|
+
def get_run_id_by_token(self, token: str) -> Optional[int]:
|
197
|
+
"""Get the run ID associated with a given token."""
|
198
|
+
with self.lock_token_store:
|
199
|
+
return self.token_to_run_id.get(token)
|
@@ -210,3 +210,18 @@ class NodeState(ABC):
|
|
210
210
|
run_id : int
|
211
211
|
The ID of the run for which to delete the token.
|
212
212
|
"""
|
213
|
+
|
214
|
+
@abstractmethod
|
215
|
+
def get_run_id_by_token(self, token: str) -> Optional[int]:
|
216
|
+
"""Get the run ID associated with a given token.
|
217
|
+
|
218
|
+
Parameters
|
219
|
+
----------
|
220
|
+
token : str
|
221
|
+
The token to look up.
|
222
|
+
|
223
|
+
Returns
|
224
|
+
-------
|
225
|
+
Optional[int]
|
226
|
+
The run ID if the token is valid, otherwise None.
|
227
|
+
"""
|
@@ -46,12 +46,14 @@ 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
|
57
59
|
|
@@ -59,7 +61,7 @@ from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
59
61
|
def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
60
62
|
clientappio_api_address: str,
|
61
63
|
run_once: bool,
|
62
|
-
token: Optional[
|
64
|
+
token: Optional[str] = None,
|
63
65
|
flwr_dir: Optional[str] = None,
|
64
66
|
certificates: Optional[bytes] = None,
|
65
67
|
parent_pid: Optional[int] = None,
|
@@ -84,9 +86,8 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
84
86
|
|
85
87
|
while True:
|
86
88
|
# If token is not set, loop until token is received from SuperNode
|
87
|
-
|
89
|
+
if token is None:
|
88
90
|
token = get_token(stub)
|
89
|
-
time.sleep(1)
|
90
91
|
|
91
92
|
# Pull Message, Context, Run and (optional) FAB from SuperNode
|
92
93
|
message, context, run, fab = pull_clientappinputs(stub=stub, token=token)
|
@@ -172,23 +173,26 @@ def start_parent_process_monitor(
|
|
172
173
|
threading.Thread(target=monitor, daemon=True).start()
|
173
174
|
|
174
175
|
|
175
|
-
def get_token(stub:
|
176
|
+
def get_token(stub: ClientAppIoStub) -> str:
|
176
177
|
"""Get a token from SuperNode."""
|
177
178
|
log(DEBUG, "[flwr-clientapp] Request token")
|
178
|
-
|
179
|
-
res:
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
179
|
+
while True:
|
180
|
+
res: GetRunIdsWithPendingMessagesResponse = stub.GetRunIdsWithPendingMessages(
|
181
|
+
GetRunIdsWithPendingMessagesRequest()
|
182
|
+
)
|
183
|
+
|
184
|
+
for run_id in res.run_ids:
|
185
|
+
tk_res: RequestTokenResponse = stub.RequestToken(
|
186
|
+
RequestTokenRequest(run_id=run_id)
|
187
|
+
)
|
188
|
+
if tk_res.token:
|
189
|
+
return tk_res.token
|
190
|
+
|
191
|
+
time.sleep(1) # Wait before retrying to get run IDs
|
188
192
|
|
189
193
|
|
190
194
|
def pull_clientappinputs(
|
191
|
-
stub:
|
195
|
+
stub: ClientAppIoStub, token: str
|
192
196
|
) -> tuple[Message, Context, Run, Optional[Fab]]:
|
193
197
|
"""Pull ClientAppInputs from SuperNode."""
|
194
198
|
log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", token)
|
@@ -207,7 +211,7 @@ def pull_clientappinputs(
|
|
207
211
|
|
208
212
|
|
209
213
|
def push_clientappoutputs(
|
210
|
-
stub:
|
214
|
+
stub: ClientAppIoStub, token: str, message: Message, context: Context
|
211
215
|
) -> PushClientAppOutputsResponse:
|
212
216
|
"""Push ClientAppOutputs to SuperNode."""
|
213
217
|
log(INFO, "[flwr-clientapp] Push `ClientAppOutputs` for token %s", token)
|
@@ -37,13 +37,20 @@ from flwr.common.typing import Fab, Run
|
|
37
37
|
# pylint: disable=E0611
|
38
38
|
from flwr.proto import clientappio_pb2_grpc
|
39
39
|
from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
|
40
|
+
GetRunIdsWithPendingMessagesRequest,
|
41
|
+
GetRunIdsWithPendingMessagesResponse,
|
40
42
|
GetTokenRequest,
|
41
43
|
GetTokenResponse,
|
42
44
|
PullClientAppInputsRequest,
|
43
45
|
PullClientAppInputsResponse,
|
44
46
|
PushClientAppOutputsRequest,
|
45
47
|
PushClientAppOutputsResponse,
|
48
|
+
RequestTokenRequest,
|
49
|
+
RequestTokenResponse,
|
46
50
|
)
|
51
|
+
from flwr.supercore.ffs import FfsFactory
|
52
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
53
|
+
from flwr.supernode.nodestate import NodeStateFactory
|
47
54
|
|
48
55
|
|
49
56
|
@dataclass
|
@@ -54,7 +61,6 @@ class ClientAppInputs:
|
|
54
61
|
context: Context
|
55
62
|
run: Run
|
56
63
|
fab: Optional[Fab]
|
57
|
-
token: int
|
58
64
|
|
59
65
|
|
60
66
|
@dataclass
|
@@ -69,11 +75,56 @@ class ClientAppOutputs:
|
|
69
75
|
class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
70
76
|
"""ClientAppIo API servicer."""
|
71
77
|
|
72
|
-
def __init__(
|
78
|
+
def __init__(
|
79
|
+
self,
|
80
|
+
state_factory: NodeStateFactory,
|
81
|
+
ffs_factory: FfsFactory,
|
82
|
+
objectstore_factory: ObjectStoreFactory,
|
83
|
+
) -> None:
|
84
|
+
self.state_factory = state_factory
|
85
|
+
self.ffs_factory = ffs_factory
|
86
|
+
self.objectstore_factory = objectstore_factory
|
87
|
+
|
73
88
|
self.clientapp_input: Optional[ClientAppInputs] = None
|
74
89
|
self.clientapp_output: Optional[ClientAppOutputs] = None
|
75
90
|
self.token_returned: bool = False
|
76
|
-
|
91
|
+
|
92
|
+
def GetRunIdsWithPendingMessages(
|
93
|
+
self,
|
94
|
+
request: GetRunIdsWithPendingMessagesRequest,
|
95
|
+
context: grpc.ServicerContext,
|
96
|
+
) -> GetRunIdsWithPendingMessagesResponse:
|
97
|
+
"""Get run IDs with pending messages."""
|
98
|
+
log(DEBUG, "ClientAppIo.GetRunIdsWithPendingMessages")
|
99
|
+
|
100
|
+
# Initialize state connection
|
101
|
+
state = self.state_factory.state()
|
102
|
+
|
103
|
+
# Get run IDs with pending messages
|
104
|
+
run_ids = state.get_run_ids_with_pending_messages()
|
105
|
+
|
106
|
+
# Return run IDs
|
107
|
+
return GetRunIdsWithPendingMessagesResponse(run_ids=run_ids)
|
108
|
+
|
109
|
+
def RequestToken(
|
110
|
+
self, request: RequestTokenRequest, context: grpc.ServicerContext
|
111
|
+
) -> RequestTokenResponse:
|
112
|
+
"""Request token."""
|
113
|
+
log(DEBUG, "ClientAppIo.RequestToken")
|
114
|
+
|
115
|
+
# Initialize state connection
|
116
|
+
state = self.state_factory.state()
|
117
|
+
|
118
|
+
# Attempt to create a token for the provided run ID
|
119
|
+
try:
|
120
|
+
token = state.create_token(request.run_id)
|
121
|
+
except ValueError:
|
122
|
+
# Return an empty token if A token already exists for this run ID,
|
123
|
+
# indicating the run is in progress
|
124
|
+
return RequestTokenResponse(token="")
|
125
|
+
|
126
|
+
# Return the token
|
127
|
+
return RequestTokenResponse(token=token)
|
77
128
|
|
78
129
|
def GetToken(
|
79
130
|
self, request: GetTokenRequest, context: grpc.ServicerContext
|
@@ -87,7 +138,6 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
87
138
|
grpc.StatusCode.FAILED_PRECONDITION,
|
88
139
|
"No inputs available.",
|
89
140
|
)
|
90
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
91
141
|
|
92
142
|
# Fail if token was already returned in a previous call
|
93
143
|
if self.token_returned:
|
@@ -101,7 +151,7 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
101
151
|
# - token hasn't been returned before,
|
102
152
|
# return token
|
103
153
|
self.token_returned = True
|
104
|
-
return GetTokenResponse(token=
|
154
|
+
return GetTokenResponse(token=123) # To be deleted
|
105
155
|
|
106
156
|
def PullClientAppInputs(
|
107
157
|
self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
|
@@ -109,36 +159,30 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
109
159
|
"""Pull Message, Context, and Run."""
|
110
160
|
log(DEBUG, "ClientAppIo.PullClientAppInputs")
|
111
161
|
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
116
|
-
"No inputs available.",
|
117
|
-
)
|
118
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
162
|
+
# Initialize state and ffs connection
|
163
|
+
state = self.state_factory.state()
|
164
|
+
ffs = self.ffs_factory.ffs()
|
119
165
|
|
120
|
-
#
|
121
|
-
|
166
|
+
# Validate the token
|
167
|
+
run_id = state.get_run_id_by_token(request.token)
|
168
|
+
if run_id is None or not state.verify_token(run_id, request.token):
|
122
169
|
context.abort(
|
123
|
-
grpc.StatusCode.
|
124
|
-
"
|
125
|
-
"Token must be returned before can be returned only once.",
|
170
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
171
|
+
"Invalid token.",
|
126
172
|
)
|
173
|
+
raise RuntimeError("This line should never be reached.")
|
127
174
|
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
)
|
175
|
+
# Retrieve message, context, run and fab for this run
|
176
|
+
message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
|
177
|
+
context = cast(Context, state.get_context(run_id))
|
178
|
+
run = cast(Run, state.get_run(run_id))
|
179
|
+
fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
|
134
180
|
|
135
|
-
# Success
|
136
|
-
self.inputs_returned = True
|
137
181
|
return PullClientAppInputsResponse(
|
138
|
-
message=message_to_proto(
|
139
|
-
context=context_to_proto(
|
140
|
-
run=run_to_proto(
|
141
|
-
fab=fab_to_proto(
|
182
|
+
message=message_to_proto(message),
|
183
|
+
context=context_to_proto(context),
|
184
|
+
run=run_to_proto(run),
|
185
|
+
fab=fab_to_proto(fab),
|
142
186
|
)
|
143
187
|
|
144
188
|
def PushClientAppOutputs(
|
@@ -147,36 +191,20 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
147
191
|
"""Push Message and Context."""
|
148
192
|
log(DEBUG, "ClientAppIo.PushClientAppOutputs")
|
149
193
|
|
150
|
-
#
|
151
|
-
|
152
|
-
context.abort(
|
153
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
154
|
-
"No inputs available.",
|
155
|
-
)
|
156
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
194
|
+
# Initialize state connection
|
195
|
+
state = self.state_factory.state()
|
157
196
|
|
158
|
-
#
|
159
|
-
|
197
|
+
# Validate the token
|
198
|
+
run_id = state.get_run_id_by_token(request.token)
|
199
|
+
if run_id is None or not state.verify_token(run_id, request.token):
|
160
200
|
context.abort(
|
161
|
-
grpc.StatusCode.
|
162
|
-
"
|
163
|
-
"Token must be returned before can be returned only once.",
|
201
|
+
grpc.StatusCode.PERMISSION_DENIED,
|
202
|
+
"Invalid token.",
|
164
203
|
)
|
204
|
+
raise RuntimeError("This line should never be reached.")
|
165
205
|
|
166
|
-
#
|
167
|
-
|
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
|
-
)
|
206
|
+
# Delete the token
|
207
|
+
state.delete_token(run_id)
|
180
208
|
|
181
209
|
# Preconditions met
|
182
210
|
try:
|
@@ -198,33 +226,6 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
198
226
|
proto_status = clientappstatus_to_proto(status=status)
|
199
227
|
return PushClientAppOutputsResponse(status=proto_status)
|
200
228
|
|
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
229
|
def has_outputs(self) -> bool:
|
229
230
|
"""Check if ClientAppOutputs are available."""
|
230
231
|
return self.clientapp_output is not None
|
@@ -236,9 +237,6 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
236
237
|
|
237
238
|
# Set outputs to a local variable and clear state
|
238
239
|
output: ClientAppOutputs = self.clientapp_output
|
239
|
-
self.clientapp_input = None
|
240
240
|
self.clientapp_output = None
|
241
|
-
self.token_returned = False
|
242
|
-
self.inputs_returned = False
|
243
241
|
|
244
242
|
return output
|
@@ -21,9 +21,8 @@ import time
|
|
21
21
|
from collections.abc import Iterator
|
22
22
|
from contextlib import contextmanager
|
23
23
|
from logging import INFO, WARN
|
24
|
-
from os import urandom
|
25
24
|
from pathlib import Path
|
26
|
-
from typing import Callable, Optional, Union
|
25
|
+
from typing import Callable, Optional, Union
|
27
26
|
|
28
27
|
import grpc
|
29
28
|
from cryptography.hazmat.primitives.asymmetric import ec
|
@@ -39,7 +38,6 @@ from flwr.common.constant import (
|
|
39
38
|
CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
40
39
|
ISOLATION_MODE_SUBPROCESS,
|
41
40
|
MAX_RETRY_DELAY,
|
42
|
-
RUN_ID_NUM_BYTES,
|
43
41
|
SERVER_OCTET,
|
44
42
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
45
43
|
TRANSPORT_TYPE_GRPC_RERE,
|
@@ -52,10 +50,10 @@ from flwr.common.logger import log
|
|
52
50
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
53
51
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
54
52
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
55
|
-
from flwr.
|
53
|
+
from flwr.supercore.ffs import Ffs, FfsFactory
|
56
54
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
57
55
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
58
|
-
from flwr.supernode.servicer.clientappio import
|
56
|
+
from flwr.supernode.servicer.clientappio import ClientAppIoServicer
|
59
57
|
|
60
58
|
DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
|
61
59
|
|
@@ -132,16 +130,20 @@ def start_client_internal(
|
|
132
130
|
if insecure is None:
|
133
131
|
insecure = root_certificates is None
|
134
132
|
|
135
|
-
_clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
|
136
|
-
address=clientappio_api_address,
|
137
|
-
certificates=None,
|
138
|
-
)
|
139
|
-
|
140
133
|
# Initialize factories
|
141
134
|
state_factory = NodeStateFactory()
|
142
135
|
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
143
136
|
object_store_factory = ObjectStoreFactory()
|
144
137
|
|
138
|
+
# Launch ClientAppIo API server
|
139
|
+
_clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
|
140
|
+
address=clientappio_api_address,
|
141
|
+
state_factory=state_factory,
|
142
|
+
ffs_factory=ffs_factory,
|
143
|
+
objectstore_factory=object_store_factory,
|
144
|
+
certificates=None,
|
145
|
+
)
|
146
|
+
|
145
147
|
# Initialize NodeState, Ffs, and ObjectStore
|
146
148
|
state = state_factory.state()
|
147
149
|
ffs = ffs_factory.ffs()
|
@@ -183,12 +185,6 @@ def start_client_internal(
|
|
183
185
|
continue
|
184
186
|
|
185
187
|
try:
|
186
|
-
# Retrieve message, context, run and fab for this run
|
187
|
-
message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
|
188
|
-
context = cast(Context, state.get_context(run_id))
|
189
|
-
run = cast(Run, state.get_run(run_id))
|
190
|
-
fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
|
191
|
-
|
192
188
|
# Two isolation modes:
|
193
189
|
# 1. `subprocess`: SuperNode is starting the ClientApp
|
194
190
|
# process as a subprocess.
|
@@ -196,24 +192,9 @@ def start_client_internal(
|
|
196
192
|
# (via `flwr-clientapp`), for example, in a separate
|
197
193
|
# Docker container.
|
198
194
|
|
199
|
-
# Generate SuperNode token
|
200
|
-
token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
|
201
|
-
|
202
195
|
# Mode 1: SuperNode starts ClientApp as subprocess
|
203
196
|
start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
|
204
197
|
|
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
198
|
if start_subprocess:
|
218
199
|
_octet, _colon, _port = clientappio_api_address.rpartition(":")
|
219
200
|
io_address = (
|
@@ -226,11 +207,10 @@ def start_client_internal(
|
|
226
207
|
"flwr-clientapp",
|
227
208
|
"--clientappio-api-address",
|
228
209
|
io_address,
|
229
|
-
"--token",
|
230
|
-
str(token),
|
231
210
|
"--parent-pid",
|
232
211
|
str(os.getpid()),
|
233
212
|
"--insecure",
|
213
|
+
"--run-once",
|
234
214
|
]
|
235
215
|
subprocess.run(command, check=False)
|
236
216
|
else:
|
@@ -250,8 +230,8 @@ def start_client_internal(
|
|
250
230
|
# Delete messages from the state
|
251
231
|
state.delete_messages(
|
252
232
|
message_ids=[
|
253
|
-
|
254
|
-
|
233
|
+
reply_message.metadata.message_id,
|
234
|
+
reply_message.metadata.reply_to_message_id,
|
255
235
|
]
|
256
236
|
)
|
257
237
|
|
@@ -472,10 +452,17 @@ def _make_fleet_connection_retry_invoker(
|
|
472
452
|
|
473
453
|
def run_clientappio_api_grpc(
|
474
454
|
address: str,
|
455
|
+
state_factory: NodeStateFactory,
|
456
|
+
ffs_factory: FfsFactory,
|
457
|
+
objectstore_factory: ObjectStoreFactory,
|
475
458
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
476
459
|
) -> tuple[grpc.Server, ClientAppIoServicer]:
|
477
460
|
"""Run ClientAppIo API gRPC server."""
|
478
|
-
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
461
|
+
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
462
|
+
state_factory=state_factory,
|
463
|
+
ffs_factory=ffs_factory,
|
464
|
+
objectstore_factory=objectstore_factory,
|
465
|
+
)
|
479
466
|
clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
|
480
467
|
clientappio_grpc_server = generic_create_grpc_server(
|
481
468
|
servicer_and_add_fn=(
|
{flwr_nightly-1.19.0.dev20250616.dist-info → flwr_nightly-1.20.0.dev20250618.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.20.0.dev20250618
|
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
|