flwr-nightly 1.20.0.dev20250618__py3-none-any.whl → 1.20.0.dev20250620__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 +13 -17
- flwr/proto/clientappio_pb2.pyi +0 -17
- flwr/proto/clientappio_pb2_grpc.py +0 -34
- flwr/proto/clientappio_pb2_grpc.pyi +0 -13
- flwr/server/serverapp/app.py +12 -6
- 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 +2 -1
- flwr/supernode/runtime/run_clientapp.py +7 -2
- flwr/supernode/servicer/clientappio/__init__.py +1 -3
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -91
- flwr/supernode/start_client_internal.py +89 -66
- {flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/METADATA +1 -1
- {flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/RECORD +21 -20
- {flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/entry_points.txt +0 -0
flwr/cli/build.py
CHANGED
@@ -25,7 +25,12 @@ import pathspec
|
|
25
25
|
import tomli_w
|
26
26
|
import typer
|
27
27
|
|
28
|
-
from flwr.common.constant import
|
28
|
+
from flwr.common.constant import (
|
29
|
+
FAB_ALLOWED_EXTENSIONS,
|
30
|
+
FAB_DATE,
|
31
|
+
FAB_HASH_TRUNCATION,
|
32
|
+
FAB_MAX_SIZE,
|
33
|
+
)
|
29
34
|
|
30
35
|
from .config_utils import load as load_toml
|
31
36
|
from .config_utils import load_and_validate
|
@@ -57,7 +62,7 @@ def build(
|
|
57
62
|
Optional[Path],
|
58
63
|
typer.Option(help="Path of the Flower App to bundle into a FAB"),
|
59
64
|
] = None,
|
60
|
-
) ->
|
65
|
+
) -> None:
|
61
66
|
"""Build a Flower App into a Flower App Bundle (FAB).
|
62
67
|
|
63
68
|
You can run ``flwr build`` without any arguments to bundle the app located in the
|
@@ -119,8 +124,6 @@ def build(
|
|
119
124
|
f"🎊 Successfully built {fab_filename}", fg=typer.colors.GREEN, bold=True
|
120
125
|
)
|
121
126
|
|
122
|
-
return fab_filename, fab_hash
|
123
|
-
|
124
127
|
|
125
128
|
def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
|
126
129
|
"""Build a FAB in memory and return the bytes, hash, and config.
|
@@ -193,6 +196,13 @@ def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
|
|
193
196
|
write_to_zip(fab_file, ".info/CONTENT", list_file_content)
|
194
197
|
|
195
198
|
fab_bytes = fab_buffer.getvalue()
|
199
|
+
if len(fab_bytes) > FAB_MAX_SIZE:
|
200
|
+
raise ValueError(
|
201
|
+
f"FAB size exceeds maximum allowed size of {FAB_MAX_SIZE:,} bytes."
|
202
|
+
"To reduce the package size, consider ignoring unnecessary files "
|
203
|
+
"via your `.gitignore` file or excluding them from the build."
|
204
|
+
)
|
205
|
+
|
196
206
|
fab_hash = hashlib.sha256(fab_bytes).hexdigest()
|
197
207
|
|
198
208
|
return fab_bytes, fab_hash, config
|
@@ -17,7 +17,6 @@
|
|
17
17
|
|
18
18
|
from collections.abc import Iterator, Sequence
|
19
19
|
from contextlib import contextmanager
|
20
|
-
from copy import copy
|
21
20
|
from logging import DEBUG, ERROR
|
22
21
|
from pathlib import Path
|
23
22
|
from typing import Callable, Optional, Union, cast
|
@@ -25,8 +24,6 @@ from typing import Callable, Optional, Union, cast
|
|
25
24
|
import grpc
|
26
25
|
from cryptography.hazmat.primitives.asymmetric import ec
|
27
26
|
|
28
|
-
from flwr.app.metadata import Metadata
|
29
|
-
from flwr.client.message_handler.message_handler import validate_out_message
|
30
27
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
31
28
|
from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
|
32
29
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
@@ -163,7 +160,6 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
163
160
|
if adapter_cls is None:
|
164
161
|
adapter_cls = FleetStub
|
165
162
|
stub = adapter_cls(channel)
|
166
|
-
metadata: Optional[Metadata] = None
|
167
163
|
node: Optional[Node] = None
|
168
164
|
|
169
165
|
def _should_giveup_fn(e: Exception) -> bool:
|
@@ -300,10 +296,6 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
300
296
|
# Inject
|
301
297
|
in_message.metadata.__dict__["_message_id"] = msg_id
|
302
298
|
|
303
|
-
# Remember `metadata` of the in message
|
304
|
-
nonlocal metadata
|
305
|
-
metadata = copy(in_message.metadata) if in_message else None
|
306
|
-
|
307
299
|
# Return the message if available
|
308
300
|
return in_message
|
309
301
|
|
@@ -314,19 +306,6 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
314
306
|
log(ERROR, "Node instance missing")
|
315
307
|
return
|
316
308
|
|
317
|
-
# Get the metadata of the incoming message
|
318
|
-
nonlocal metadata
|
319
|
-
if metadata is None:
|
320
|
-
log(ERROR, "No current message")
|
321
|
-
return
|
322
|
-
|
323
|
-
# Set message_id
|
324
|
-
message.metadata.__dict__["_message_id"] = message.object_id
|
325
|
-
# Validate out message
|
326
|
-
if not validate_out_message(message, metadata):
|
327
|
-
log(ERROR, "Invalid out message")
|
328
|
-
return
|
329
|
-
|
330
309
|
with no_object_id_recompute():
|
331
310
|
# Get all nested objects
|
332
311
|
all_objects = get_all_nested_objects(message)
|
@@ -358,9 +337,6 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
358
337
|
)
|
359
338
|
log(DEBUG, "Pushed %s objects to servicer.", len(objs_to_push))
|
360
339
|
|
361
|
-
# Cleanup
|
362
|
-
metadata = None
|
363
|
-
|
364
340
|
def get_run(run_id: int) -> Run:
|
365
341
|
# Call FleetAPI
|
366
342
|
get_run_request = GetRunRequest(node=node, run_id=run_id)
|
@@ -16,7 +16,6 @@
|
|
16
16
|
|
17
17
|
from collections.abc import Iterator
|
18
18
|
from contextlib import contextmanager
|
19
|
-
from copy import copy
|
20
19
|
from logging import DEBUG, ERROR, INFO, WARN
|
21
20
|
from typing import Callable, Optional, TypeVar, Union, cast
|
22
21
|
|
@@ -24,8 +23,6 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|
24
23
|
from google.protobuf.message import Message as GrpcMessage
|
25
24
|
from requests.exceptions import ConnectionError as RequestsConnectionError
|
26
25
|
|
27
|
-
from flwr.app.metadata import Metadata
|
28
|
-
from flwr.client.message_handler.message_handler import validate_out_message
|
29
26
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
30
27
|
from flwr.common.constant import HEARTBEAT_DEFAULT_INTERVAL
|
31
28
|
from flwr.common.exit import ExitCode, flwr_exit
|
@@ -178,7 +175,6 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
178
175
|
log(ERROR, "Client authentication is not supported for this transport type.")
|
179
176
|
|
180
177
|
# Shared variables for inner functions
|
181
|
-
metadata: Optional[Metadata] = None
|
182
178
|
node: Optional[Node] = None
|
183
179
|
|
184
180
|
###########################################################################
|
@@ -367,10 +363,6 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
367
363
|
# Inject
|
368
364
|
in_message.metadata.__dict__["_message_id"] = msg_id
|
369
365
|
|
370
|
-
# Remember `metadata` of the in message
|
371
|
-
nonlocal metadata
|
372
|
-
metadata = copy(in_message.metadata) if in_message else None
|
373
|
-
|
374
366
|
return in_message
|
375
367
|
|
376
368
|
def send(message: Message) -> None:
|
@@ -380,19 +372,6 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
380
372
|
log(ERROR, "Node instance missing")
|
381
373
|
return
|
382
374
|
|
383
|
-
# Get incoming message
|
384
|
-
nonlocal metadata
|
385
|
-
if metadata is None:
|
386
|
-
log(ERROR, "No current message")
|
387
|
-
return
|
388
|
-
|
389
|
-
# Set message_id
|
390
|
-
message.metadata.__dict__["_message_id"] = message.object_id
|
391
|
-
# Validate out message
|
392
|
-
if not validate_out_message(message, metadata):
|
393
|
-
log(ERROR, "Invalid out message")
|
394
|
-
return
|
395
|
-
|
396
375
|
with no_object_id_recompute():
|
397
376
|
# Get all nested objects
|
398
377
|
all_objects = get_all_nested_objects(message)
|
@@ -450,9 +429,6 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
450
429
|
)
|
451
430
|
log(ERROR, str(e))
|
452
431
|
|
453
|
-
# Cleanup
|
454
|
-
metadata = None
|
455
|
-
|
456
432
|
def get_run(run_id: int) -> Run:
|
457
433
|
# Construct the request
|
458
434
|
req = GetRunRequest(node=node, run_id=run_id)
|
flwr/common/constant.py
CHANGED
@@ -74,6 +74,7 @@ FAB_ALLOWED_EXTENSIONS = {".py", ".toml", ".md"}
|
|
74
74
|
FAB_CONFIG_FILE = "pyproject.toml"
|
75
75
|
FAB_DATE = (2024, 10, 1, 0, 0, 0)
|
76
76
|
FAB_HASH_TRUNCATION = 8
|
77
|
+
FAB_MAX_SIZE = 10 * 1024 * 1024 # 10 MB
|
77
78
|
FLWR_DIR = ".flwr" # The default Flower directory: ~/.flwr/
|
78
79
|
FLWR_HOME = "FLWR_HOME" # If set, override the default Flower directory
|
79
80
|
|
flwr/proto/clientappio_pb2.py
CHANGED
@@ -17,15 +17,15 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
17
17
|
from flwr.proto import message_pb2 as flwr_dot_proto_dot_message__pb2
|
18
18
|
|
19
19
|
|
20
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/clientappio.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x18\x66lwr/proto/message.proto\"%\n#GetRunIdsWithPendingMessagesRequest\"7\n$GetRunIdsWithPendingMessagesResponse\x12\x0f\n\x07run_ids\x18\x01 \x03(\x04\"%\n\x13RequestTokenRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"%\n\x14RequestTokenResponse\x12\r\n\x05token\x18\x01 \x01(\t\"W\n\x15\x43lientAppOutputStatus\x12-\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x1f.flwr.proto.ClientAppOutputCode\x12\x0f\n\x07message\x18\x02 \x01(\t\"
|
20
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/clientappio.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x18\x66lwr/proto/message.proto\"%\n#GetRunIdsWithPendingMessagesRequest\"7\n$GetRunIdsWithPendingMessagesResponse\x12\x0f\n\x07run_ids\x18\x01 \x03(\x04\"%\n\x13RequestTokenRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"%\n\x14RequestTokenResponse\x12\r\n\x05token\x18\x01 \x01(\t\"W\n\x15\x43lientAppOutputStatus\x12-\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x1f.flwr.proto.ClientAppOutputCode\x12\x0f\n\x07message\x18\x02 \x01(\t\"+\n\x1aPullClientAppInputsRequest\x12\r\n\x05token\x18\x01 \x01(\t\"\xa5\x01\n\x1bPullClientAppInputsResponse\x12$\n\x07message\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Message\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\"x\n\x1bPushClientAppOutputsRequest\x12\r\n\x05token\x18\x01 \x01(\t\x12$\n\x07message\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Message\x12$\n\x07\x63ontext\x18\x03 \x01(\x0b\x32\x13.flwr.proto.Context\"Q\n\x1cPushClientAppOutputsResponse\x12\x31\n\x06status\x18\x01 \x01(\x0b\x32!.flwr.proto.ClientAppOutputStatus*L\n\x13\x43lientAppOutputCode\x12\x0b\n\x07SUCCESS\x10\x00\x12\x15\n\x11\x44\x45\x41\x44LINE_EXCEEDED\x10\x01\x12\x11\n\rUNKNOWN_ERROR\x10\x02\x32\xbf\x03\n\x0b\x43lientAppIo\x12\x83\x01\n\x1cGetRunIdsWithPendingMessages\x12/.flwr.proto.GetRunIdsWithPendingMessagesRequest\x1a\x30.flwr.proto.GetRunIdsWithPendingMessagesResponse\"\x00\x12S\n\x0cRequestToken\x12\x1f.flwr.proto.RequestTokenRequest\x1a .flwr.proto.RequestTokenResponse\"\x00\x12h\n\x13PullClientAppInputs\x12&.flwr.proto.PullClientAppInputsRequest\x1a\'.flwr.proto.PullClientAppInputsResponse\"\x00\x12k\n\x14PushClientAppOutputs\x12\'.flwr.proto.PushClientAppOutputsRequest\x1a(.flwr.proto.PushClientAppOutputsResponse\"\x00\x62\x06proto3')
|
21
21
|
|
22
22
|
_globals = globals()
|
23
23
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
24
24
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.clientappio_pb2', _globals)
|
25
25
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
26
26
|
DESCRIPTOR._options = None
|
27
|
-
_globals['_CLIENTAPPOUTPUTCODE']._serialized_start=
|
28
|
-
_globals['_CLIENTAPPOUTPUTCODE']._serialized_end=
|
27
|
+
_globals['_CLIENTAPPOUTPUTCODE']._serialized_start=795
|
28
|
+
_globals['_CLIENTAPPOUTPUTCODE']._serialized_end=871
|
29
29
|
_globals['_GETRUNIDSWITHPENDINGMESSAGESREQUEST']._serialized_start=114
|
30
30
|
_globals['_GETRUNIDSWITHPENDINGMESSAGESREQUEST']._serialized_end=151
|
31
31
|
_globals['_GETRUNIDSWITHPENDINGMESSAGESRESPONSE']._serialized_start=153
|
@@ -36,18 +36,14 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
36
36
|
_globals['_REQUESTTOKENRESPONSE']._serialized_end=286
|
37
37
|
_globals['_CLIENTAPPOUTPUTSTATUS']._serialized_start=288
|
38
38
|
_globals['_CLIENTAPPOUTPUTSTATUS']._serialized_end=375
|
39
|
-
_globals['
|
40
|
-
_globals['
|
41
|
-
_globals['
|
42
|
-
_globals['
|
43
|
-
_globals['
|
44
|
-
_globals['
|
45
|
-
_globals['
|
46
|
-
_globals['
|
47
|
-
_globals['
|
48
|
-
_globals['
|
49
|
-
_globals['_PUSHCLIENTAPPOUTPUTSRESPONSE']._serialized_start=766
|
50
|
-
_globals['_PUSHCLIENTAPPOUTPUTSRESPONSE']._serialized_end=847
|
51
|
-
_globals['_CLIENTAPPIO']._serialized_start=928
|
52
|
-
_globals['_CLIENTAPPIO']._serialized_end=1448
|
39
|
+
_globals['_PULLCLIENTAPPINPUTSREQUEST']._serialized_start=377
|
40
|
+
_globals['_PULLCLIENTAPPINPUTSREQUEST']._serialized_end=420
|
41
|
+
_globals['_PULLCLIENTAPPINPUTSRESPONSE']._serialized_start=423
|
42
|
+
_globals['_PULLCLIENTAPPINPUTSRESPONSE']._serialized_end=588
|
43
|
+
_globals['_PUSHCLIENTAPPOUTPUTSREQUEST']._serialized_start=590
|
44
|
+
_globals['_PUSHCLIENTAPPOUTPUTSREQUEST']._serialized_end=710
|
45
|
+
_globals['_PUSHCLIENTAPPOUTPUTSRESPONSE']._serialized_start=712
|
46
|
+
_globals['_PUSHCLIENTAPPOUTPUTSRESPONSE']._serialized_end=793
|
47
|
+
_globals['_CLIENTAPPIO']._serialized_start=874
|
48
|
+
_globals['_CLIENTAPPIO']._serialized_end=1321
|
53
49
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/clientappio_pb2.pyi
CHANGED
@@ -88,23 +88,6 @@ class ClientAppOutputStatus(google.protobuf.message.Message):
|
|
88
88
|
def ClearField(self, field_name: typing_extensions.Literal["code",b"code","message",b"message"]) -> None: ...
|
89
89
|
global___ClientAppOutputStatus = ClientAppOutputStatus
|
90
90
|
|
91
|
-
class GetTokenRequest(google.protobuf.message.Message):
|
92
|
-
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
93
|
-
def __init__(self,
|
94
|
-
) -> None: ...
|
95
|
-
global___GetTokenRequest = GetTokenRequest
|
96
|
-
|
97
|
-
class GetTokenResponse(google.protobuf.message.Message):
|
98
|
-
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
99
|
-
TOKEN_FIELD_NUMBER: builtins.int
|
100
|
-
token: builtins.int
|
101
|
-
def __init__(self,
|
102
|
-
*,
|
103
|
-
token: builtins.int = ...,
|
104
|
-
) -> None: ...
|
105
|
-
def ClearField(self, field_name: typing_extensions.Literal["token",b"token"]) -> None: ...
|
106
|
-
global___GetTokenResponse = GetTokenResponse
|
107
|
-
|
108
91
|
class PullClientAppInputsRequest(google.protobuf.message.Message):
|
109
92
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
110
93
|
TOKEN_FIELD_NUMBER: builtins.int
|
@@ -24,11 +24,6 @@ class ClientAppIoStub(object):
|
|
24
24
|
request_serializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenRequest.SerializeToString,
|
25
25
|
response_deserializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenResponse.FromString,
|
26
26
|
)
|
27
|
-
self.GetToken = channel.unary_unary(
|
28
|
-
'/flwr.proto.ClientAppIo/GetToken',
|
29
|
-
request_serializer=flwr_dot_proto_dot_clientappio__pb2.GetTokenRequest.SerializeToString,
|
30
|
-
response_deserializer=flwr_dot_proto_dot_clientappio__pb2.GetTokenResponse.FromString,
|
31
|
-
)
|
32
27
|
self.PullClientAppInputs = channel.unary_unary(
|
33
28
|
'/flwr.proto.ClientAppIo/PullClientAppInputs',
|
34
29
|
request_serializer=flwr_dot_proto_dot_clientappio__pb2.PullClientAppInputsRequest.SerializeToString,
|
@@ -58,13 +53,6 @@ class ClientAppIoServicer(object):
|
|
58
53
|
context.set_details('Method not implemented!')
|
59
54
|
raise NotImplementedError('Method not implemented!')
|
60
55
|
|
61
|
-
def GetToken(self, request, context):
|
62
|
-
"""Get token
|
63
|
-
"""
|
64
|
-
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
65
|
-
context.set_details('Method not implemented!')
|
66
|
-
raise NotImplementedError('Method not implemented!')
|
67
|
-
|
68
56
|
def PullClientAppInputs(self, request, context):
|
69
57
|
"""Pull client app inputs
|
70
58
|
"""
|
@@ -92,11 +80,6 @@ def add_ClientAppIoServicer_to_server(servicer, server):
|
|
92
80
|
request_deserializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenRequest.FromString,
|
93
81
|
response_serializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenResponse.SerializeToString,
|
94
82
|
),
|
95
|
-
'GetToken': grpc.unary_unary_rpc_method_handler(
|
96
|
-
servicer.GetToken,
|
97
|
-
request_deserializer=flwr_dot_proto_dot_clientappio__pb2.GetTokenRequest.FromString,
|
98
|
-
response_serializer=flwr_dot_proto_dot_clientappio__pb2.GetTokenResponse.SerializeToString,
|
99
|
-
),
|
100
83
|
'PullClientAppInputs': grpc.unary_unary_rpc_method_handler(
|
101
84
|
servicer.PullClientAppInputs,
|
102
85
|
request_deserializer=flwr_dot_proto_dot_clientappio__pb2.PullClientAppInputsRequest.FromString,
|
@@ -151,23 +134,6 @@ class ClientAppIo(object):
|
|
151
134
|
options, channel_credentials,
|
152
135
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
153
136
|
|
154
|
-
@staticmethod
|
155
|
-
def GetToken(request,
|
156
|
-
target,
|
157
|
-
options=(),
|
158
|
-
channel_credentials=None,
|
159
|
-
call_credentials=None,
|
160
|
-
insecure=False,
|
161
|
-
compression=None,
|
162
|
-
wait_for_ready=None,
|
163
|
-
timeout=None,
|
164
|
-
metadata=None):
|
165
|
-
return grpc.experimental.unary_unary(request, target, '/flwr.proto.ClientAppIo/GetToken',
|
166
|
-
flwr_dot_proto_dot_clientappio__pb2.GetTokenRequest.SerializeToString,
|
167
|
-
flwr_dot_proto_dot_clientappio__pb2.GetTokenResponse.FromString,
|
168
|
-
options, channel_credentials,
|
169
|
-
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
170
|
-
|
171
137
|
@staticmethod
|
172
138
|
def PullClientAppInputs(request,
|
173
139
|
target,
|
@@ -18,11 +18,6 @@ class ClientAppIoStub:
|
|
18
18
|
flwr.proto.clientappio_pb2.RequestTokenResponse]
|
19
19
|
"""Request token"""
|
20
20
|
|
21
|
-
GetToken: grpc.UnaryUnaryMultiCallable[
|
22
|
-
flwr.proto.clientappio_pb2.GetTokenRequest,
|
23
|
-
flwr.proto.clientappio_pb2.GetTokenResponse]
|
24
|
-
"""Get token"""
|
25
|
-
|
26
21
|
PullClientAppInputs: grpc.UnaryUnaryMultiCallable[
|
27
22
|
flwr.proto.clientappio_pb2.PullClientAppInputsRequest,
|
28
23
|
flwr.proto.clientappio_pb2.PullClientAppInputsResponse]
|
@@ -51,14 +46,6 @@ class ClientAppIoServicer(metaclass=abc.ABCMeta):
|
|
51
46
|
"""Request token"""
|
52
47
|
pass
|
53
48
|
|
54
|
-
@abc.abstractmethod
|
55
|
-
def GetToken(self,
|
56
|
-
request: flwr.proto.clientappio_pb2.GetTokenRequest,
|
57
|
-
context: grpc.ServicerContext,
|
58
|
-
) -> flwr.proto.clientappio_pb2.GetTokenResponse:
|
59
|
-
"""Get token"""
|
60
|
-
pass
|
61
|
-
|
62
49
|
@abc.abstractmethod
|
63
50
|
def PullClientAppInputs(self,
|
64
51
|
request: flwr.proto.clientappio_pb2.PullClientAppInputsRequest,
|
flwr/server/serverapp/app.py
CHANGED
@@ -107,11 +107,6 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
107
107
|
certificates: Optional[bytes] = None,
|
108
108
|
) -> None:
|
109
109
|
"""Run Flower ServerApp process."""
|
110
|
-
grid = GrpcGrid(
|
111
|
-
serverappio_service_address=serverappio_api_address,
|
112
|
-
root_certificates=certificates,
|
113
|
-
)
|
114
|
-
|
115
110
|
# Resolve directory where FABs are installed
|
116
111
|
flwr_dir_ = get_flwr_dir(flwr_dir)
|
117
112
|
log_uploader = None
|
@@ -119,9 +114,16 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
119
114
|
hash_run_id = None
|
120
115
|
run_status = None
|
121
116
|
heartbeat_sender = None
|
117
|
+
grid = None
|
122
118
|
while True:
|
123
119
|
|
124
120
|
try:
|
121
|
+
# Initialize the GrpcGrid
|
122
|
+
grid = GrpcGrid(
|
123
|
+
serverappio_service_address=serverappio_api_address,
|
124
|
+
root_certificates=certificates,
|
125
|
+
)
|
126
|
+
|
125
127
|
# Pull ServerAppInputs from LinkState
|
126
128
|
req = PullServerAppInputsRequest()
|
127
129
|
log(DEBUG, "[flwr-serverapp] Pull ServerAppInputs")
|
@@ -236,7 +238,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
236
238
|
log_uploader = None
|
237
239
|
|
238
240
|
# Update run status
|
239
|
-
if run_status:
|
241
|
+
if run_status and grid:
|
240
242
|
run_status_proto = run_status_to_proto(run_status)
|
241
243
|
grid._stub.UpdateRunStatus(
|
242
244
|
UpdateRunStatusRequest(
|
@@ -244,6 +246,10 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
|
|
244
246
|
)
|
245
247
|
)
|
246
248
|
|
249
|
+
# Close the Grpc connection
|
250
|
+
if grid:
|
251
|
+
grid.close()
|
252
|
+
|
247
253
|
event(
|
248
254
|
EventType.FLWR_SERVERAPP_RUN_LEAVE,
|
249
255
|
event_details={"run-id-hash": hash_run_id, "success": success},
|
flwr/supercore/utils.py
ADDED
@@ -0,0 +1,32 @@
|
|
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
|
+
"""Utility functions for the infrastructure."""
|
16
|
+
|
17
|
+
|
18
|
+
def mask_string(value: str, head: int = 4, tail: int = 4) -> str:
|
19
|
+
"""Mask a string by preserving only the head and tail characters.
|
20
|
+
|
21
|
+
Mask a string for safe display by preserving the head and tail characters,
|
22
|
+
and replacing the middle with '...'. Useful for logging tokens, secrets,
|
23
|
+
or IDs without exposing sensitive data.
|
24
|
+
|
25
|
+
Notes
|
26
|
+
-----
|
27
|
+
If the string is shorter than the combined length of `head` and `tail`,
|
28
|
+
the original string is returned unchanged.
|
29
|
+
"""
|
30
|
+
if len(value) <= head + tail:
|
31
|
+
return value
|
32
|
+
return f"{value[:head]}...{value[-tail:]}"
|
flwr/superexec/exec_servicer.py
CHANGED
@@ -25,6 +25,7 @@ import grpc
|
|
25
25
|
from flwr.common import now
|
26
26
|
from flwr.common.auth_plugin import ExecAuthPlugin
|
27
27
|
from flwr.common.constant import (
|
28
|
+
FAB_MAX_SIZE,
|
28
29
|
LOG_STREAM_INTERVAL,
|
29
30
|
RUN_ID_NOT_FOUND_MESSAGE,
|
30
31
|
Status,
|
@@ -84,6 +85,14 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
84
85
|
"""Create run ID."""
|
85
86
|
log(INFO, "ExecServicer.StartRun")
|
86
87
|
|
88
|
+
if len(request.fab.content) > FAB_MAX_SIZE:
|
89
|
+
log(
|
90
|
+
ERROR,
|
91
|
+
"FAB size exceeds maximum allowed size of %d bytes.",
|
92
|
+
FAB_MAX_SIZE,
|
93
|
+
)
|
94
|
+
return StartRunResponse()
|
95
|
+
|
87
96
|
flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
|
88
97
|
run_id = self.executor.start_run(
|
89
98
|
request.fab.content,
|
@@ -40,7 +40,6 @@ from flwr.common.constant import (
|
|
40
40
|
TRANSPORT_TYPE_REST,
|
41
41
|
)
|
42
42
|
from flwr.common.exit import ExitCode, flwr_exit
|
43
|
-
from flwr.common.exit_handlers import register_exit_handlers
|
44
43
|
from flwr.common.logger import log
|
45
44
|
from flwr.supernode.start_client_internal import start_client_internal
|
46
45
|
|
@@ -66,12 +65,6 @@ def flower_supernode() -> None:
|
|
66
65
|
|
67
66
|
log(DEBUG, "Isolation mode: %s", args.isolation)
|
68
67
|
|
69
|
-
# Register handlers for graceful shutdown
|
70
|
-
register_exit_handlers(
|
71
|
-
event_type=EventType.RUN_SUPERNODE_LEAVE,
|
72
|
-
exit_message="SuperNode terminated gracefully.",
|
73
|
-
)
|
74
|
-
|
75
68
|
start_client_internal(
|
76
69
|
server_address=args.superlink,
|
77
70
|
transport=args.transport,
|
@@ -22,6 +22,7 @@ from flwr.common.args import add_args_flwr_app_common
|
|
22
22
|
from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS
|
23
23
|
from flwr.common.exit import ExitCode, flwr_exit
|
24
24
|
from flwr.common.logger import log
|
25
|
+
from flwr.supercore.utils import mask_string
|
25
26
|
from flwr.supernode.runtime.run_clientapp import run_clientapp
|
26
27
|
|
27
28
|
|
@@ -40,7 +41,7 @@ def flwr_clientapp() -> None:
|
|
40
41
|
"`flwr-clientapp` will attempt to connect to SuperNode's "
|
41
42
|
"ClientAppIo API at %s with token %s",
|
42
43
|
args.clientappio_api_address,
|
43
|
-
args.token,
|
44
|
+
mask_string(args.token) if args.token else "None",
|
44
45
|
)
|
45
46
|
run_clientapp(
|
46
47
|
clientappio_api_address=args.clientappio_api_address,
|
@@ -56,6 +56,7 @@ from flwr.proto.clientappio_pb2 import (
|
|
56
56
|
RequestTokenResponse,
|
57
57
|
)
|
58
58
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
59
|
+
from flwr.supercore.utils import mask_string
|
59
60
|
|
60
61
|
|
61
62
|
def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
@@ -195,7 +196,8 @@ def pull_clientappinputs(
|
|
195
196
|
stub: ClientAppIoStub, token: str
|
196
197
|
) -> tuple[Message, Context, Run, Optional[Fab]]:
|
197
198
|
"""Pull ClientAppInputs from SuperNode."""
|
198
|
-
|
199
|
+
masked_token = mask_string(token)
|
200
|
+
log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
|
199
201
|
try:
|
200
202
|
res: PullClientAppInputsResponse = stub.PullClientAppInputs(
|
201
203
|
PullClientAppInputsRequest(token=token)
|
@@ -214,7 +216,10 @@ def push_clientappoutputs(
|
|
214
216
|
stub: ClientAppIoStub, token: str, message: Message, context: Context
|
215
217
|
) -> PushClientAppOutputsResponse:
|
216
218
|
"""Push ClientAppOutputs to SuperNode."""
|
217
|
-
|
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
|
218
223
|
proto_message = message_to_proto(message)
|
219
224
|
proto_context = context_to_proto(context)
|
220
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,
|
@@ -39,8 +37,6 @@ from flwr.proto import clientappio_pb2_grpc
|
|
39
37
|
from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
|
40
38
|
GetRunIdsWithPendingMessagesRequest,
|
41
39
|
GetRunIdsWithPendingMessagesResponse,
|
42
|
-
GetTokenRequest,
|
43
|
-
GetTokenResponse,
|
44
40
|
PullClientAppInputsRequest,
|
45
41
|
PullClientAppInputsResponse,
|
46
42
|
PushClientAppOutputsRequest,
|
@@ -53,24 +49,6 @@ from flwr.supercore.object_store import ObjectStoreFactory
|
|
53
49
|
from flwr.supernode.nodestate import NodeStateFactory
|
54
50
|
|
55
51
|
|
56
|
-
@dataclass
|
57
|
-
class ClientAppInputs:
|
58
|
-
"""Specify the inputs to the ClientApp."""
|
59
|
-
|
60
|
-
message: Message
|
61
|
-
context: Context
|
62
|
-
run: Run
|
63
|
-
fab: Optional[Fab]
|
64
|
-
|
65
|
-
|
66
|
-
@dataclass
|
67
|
-
class ClientAppOutputs:
|
68
|
-
"""Specify the outputs from the ClientApp."""
|
69
|
-
|
70
|
-
message: Message
|
71
|
-
context: Context
|
72
|
-
|
73
|
-
|
74
52
|
# pylint: disable=C0103,W0613,W0201
|
75
53
|
class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
76
54
|
"""ClientAppIo API servicer."""
|
@@ -85,10 +63,6 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
85
63
|
self.ffs_factory = ffs_factory
|
86
64
|
self.objectstore_factory = objectstore_factory
|
87
65
|
|
88
|
-
self.clientapp_input: Optional[ClientAppInputs] = None
|
89
|
-
self.clientapp_output: Optional[ClientAppOutputs] = None
|
90
|
-
self.token_returned: bool = False
|
91
|
-
|
92
66
|
def GetRunIdsWithPendingMessages(
|
93
67
|
self,
|
94
68
|
request: GetRunIdsWithPendingMessagesRequest,
|
@@ -126,33 +100,6 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
126
100
|
# Return the token
|
127
101
|
return RequestTokenResponse(token=token)
|
128
102
|
|
129
|
-
def GetToken(
|
130
|
-
self, request: GetTokenRequest, context: grpc.ServicerContext
|
131
|
-
) -> GetTokenResponse:
|
132
|
-
"""Get token."""
|
133
|
-
log(DEBUG, "ClientAppIo.GetToken")
|
134
|
-
|
135
|
-
# Fail if no ClientAppInputs are available
|
136
|
-
if self.clientapp_input is None:
|
137
|
-
context.abort(
|
138
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
139
|
-
"No inputs available.",
|
140
|
-
)
|
141
|
-
|
142
|
-
# Fail if token was already returned in a previous call
|
143
|
-
if self.token_returned:
|
144
|
-
context.abort(
|
145
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
146
|
-
"Token already returned. A token can be returned only once.",
|
147
|
-
)
|
148
|
-
|
149
|
-
# If
|
150
|
-
# - ClientAppInputs is set, and
|
151
|
-
# - token hasn't been returned before,
|
152
|
-
# return token
|
153
|
-
self.token_returned = True
|
154
|
-
return GetTokenResponse(token=123) # To be deleted
|
155
|
-
|
156
103
|
def PullClientAppInputs(
|
157
104
|
self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
|
158
105
|
) -> PullClientAppInputsResponse:
|
@@ -203,40 +150,12 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
203
150
|
)
|
204
151
|
raise RuntimeError("This line should never be reached.")
|
205
152
|
|
206
|
-
#
|
207
|
-
state.
|
208
|
-
|
209
|
-
# Preconditions met
|
210
|
-
try:
|
211
|
-
# Update Message and Context
|
212
|
-
self.clientapp_output = ClientAppOutputs(
|
213
|
-
message=message_from_proto(request.message),
|
214
|
-
context=context_from_proto(request.context),
|
215
|
-
)
|
216
|
-
|
217
|
-
# Set status
|
218
|
-
code = typing.ClientAppOutputCode.SUCCESS
|
219
|
-
status = typing.ClientAppOutputStatus(code=code, message="Success")
|
220
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
221
|
-
log(ERROR, "ClientApp failed to push message to SuperNode, %s", e)
|
222
|
-
code = typing.ClientAppOutputCode.UNKNOWN_ERROR
|
223
|
-
status = typing.ClientAppOutputStatus(code=code, message="Unkonwn error")
|
224
|
-
|
225
|
-
# Return status to ClientApp process
|
226
|
-
proto_status = clientappstatus_to_proto(status=status)
|
227
|
-
return PushClientAppOutputsResponse(status=proto_status)
|
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))
|
228
156
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
def get_outputs(self) -> ClientAppOutputs:
|
234
|
-
"""Get ClientApp outputs."""
|
235
|
-
if self.clientapp_output is None:
|
236
|
-
raise ValueError("ClientAppOutputs not set before calling `get_outputs`.")
|
237
|
-
|
238
|
-
# Set outputs to a local variable and clear state
|
239
|
-
output: ClientAppOutputs = self.clientapp_output
|
240
|
-
self.clientapp_output = None
|
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)
|
241
160
|
|
242
|
-
return
|
161
|
+
return PushClientAppOutputsResponse()
|
@@ -45,9 +45,11 @@ from flwr.common.constant import (
|
|
45
45
|
TRANSPORT_TYPES,
|
46
46
|
)
|
47
47
|
from flwr.common.exit import ExitCode, flwr_exit
|
48
|
+
from flwr.common.exit_handlers import register_exit_handlers
|
48
49
|
from flwr.common.grpc import generic_create_grpc_server
|
49
50
|
from flwr.common.logger import log
|
50
51
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
52
|
+
from flwr.common.telemetry import EventType
|
51
53
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
52
54
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
53
55
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
@@ -136,7 +138,7 @@ def start_client_internal(
|
|
136
138
|
object_store_factory = ObjectStoreFactory()
|
137
139
|
|
138
140
|
# Launch ClientAppIo API server
|
139
|
-
|
141
|
+
clientappio_server = run_clientappio_api_grpc(
|
140
142
|
address=clientappio_api_address,
|
141
143
|
state_factory=state_factory,
|
142
144
|
ffs_factory=ffs_factory,
|
@@ -144,6 +146,13 @@ def start_client_internal(
|
|
144
146
|
certificates=None,
|
145
147
|
)
|
146
148
|
|
149
|
+
# Register handlers for graceful shutdown
|
150
|
+
register_exit_handlers(
|
151
|
+
event_type=EventType.RUN_SUPERNODE_LEAVE,
|
152
|
+
exit_message="SuperNode terminated gracefully.",
|
153
|
+
grpc_servers=[clientappio_server],
|
154
|
+
)
|
155
|
+
|
147
156
|
# Initialize NodeState, Ffs, and ObjectStore
|
148
157
|
state = state_factory.state()
|
149
158
|
ffs = ffs_factory.ffs()
|
@@ -180,72 +189,39 @@ def start_client_internal(
|
|
180
189
|
get_fab=get_fab,
|
181
190
|
)
|
182
191
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
_octet, _colon, _port = clientappio_api_address.rpartition(":")
|
200
|
-
io_address = (
|
201
|
-
f"{CLIENT_OCTET}:{_port}"
|
202
|
-
if _octet == SERVER_OCTET
|
203
|
-
else clientappio_api_address
|
204
|
-
)
|
205
|
-
# Start ClientApp subprocess
|
206
|
-
command = [
|
207
|
-
"flwr-clientapp",
|
208
|
-
"--clientappio-api-address",
|
209
|
-
io_address,
|
210
|
-
"--parent-pid",
|
211
|
-
str(os.getpid()),
|
212
|
-
"--insecure",
|
213
|
-
"--run-once",
|
214
|
-
]
|
215
|
-
subprocess.run(command, check=False)
|
216
|
-
else:
|
217
|
-
# Wait for output to become available
|
218
|
-
while not clientappio_servicer.has_outputs():
|
219
|
-
time.sleep(0.1)
|
220
|
-
|
221
|
-
outputs = clientappio_servicer.get_outputs()
|
222
|
-
reply_message, context = outputs.message, outputs.context
|
223
|
-
|
224
|
-
# Update context in the state
|
225
|
-
state.store_context(context)
|
226
|
-
|
227
|
-
# Send
|
228
|
-
send(reply_message)
|
229
|
-
|
230
|
-
# Delete messages from the state
|
231
|
-
state.delete_messages(
|
232
|
-
message_ids=[
|
233
|
-
reply_message.metadata.message_id,
|
234
|
-
reply_message.metadata.reply_to_message_id,
|
235
|
-
]
|
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
|
+
# Mode 1: SuperNode starts ClientApp as subprocess
|
200
|
+
start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
|
201
|
+
|
202
|
+
if start_subprocess and run_id is not None:
|
203
|
+
_octet, _colon, _port = clientappio_api_address.rpartition(":")
|
204
|
+
io_address = (
|
205
|
+
f"{CLIENT_OCTET}:{_port}"
|
206
|
+
if _octet == SERVER_OCTET
|
207
|
+
else clientappio_api_address
|
236
208
|
)
|
209
|
+
# Start ClientApp subprocess
|
210
|
+
command = [
|
211
|
+
"flwr-clientapp",
|
212
|
+
"--clientappio-api-address",
|
213
|
+
io_address,
|
214
|
+
"--parent-pid",
|
215
|
+
str(os.getpid()),
|
216
|
+
"--insecure",
|
217
|
+
"--run-once",
|
218
|
+
]
|
219
|
+
subprocess.run(command, check=False)
|
237
220
|
|
238
|
-
|
221
|
+
_push_messages(state=state, send=send)
|
239
222
|
|
240
|
-
|
241
|
-
|
242
|
-
log(
|
243
|
-
INFO,
|
244
|
-
"SuperNode aborted sending the reply message. "
|
245
|
-
"Run ID %s is not in `RUNNING` status.",
|
246
|
-
run_id,
|
247
|
-
)
|
248
|
-
log(INFO, "")
|
223
|
+
# Sleep for 3 seconds before the next iteration
|
224
|
+
time.sleep(3)
|
249
225
|
|
250
226
|
|
251
227
|
def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
@@ -333,6 +309,53 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
333
309
|
return run_id
|
334
310
|
|
335
311
|
|
312
|
+
def _push_messages(
|
313
|
+
state: NodeState,
|
314
|
+
send: Callable[[Message], None],
|
315
|
+
) -> None:
|
316
|
+
"""Push reply messages to the SuperLink."""
|
317
|
+
# Get messages to send
|
318
|
+
reply_messages = state.get_messages(is_reply=True)
|
319
|
+
|
320
|
+
for message in reply_messages:
|
321
|
+
# Log message sending
|
322
|
+
log(INFO, "")
|
323
|
+
if message.metadata.group_id:
|
324
|
+
log(
|
325
|
+
INFO,
|
326
|
+
"[RUN %s, ROUND %s]",
|
327
|
+
message.metadata.run_id,
|
328
|
+
message.metadata.group_id,
|
329
|
+
)
|
330
|
+
else:
|
331
|
+
log(INFO, "[RUN %s]", message.metadata.run_id)
|
332
|
+
log(
|
333
|
+
INFO,
|
334
|
+
"Sending: %s message",
|
335
|
+
message.metadata.message_type,
|
336
|
+
)
|
337
|
+
|
338
|
+
# Send the message
|
339
|
+
try:
|
340
|
+
send(message)
|
341
|
+
log(INFO, "Sent successfully")
|
342
|
+
except RunNotRunningException:
|
343
|
+
log(
|
344
|
+
INFO,
|
345
|
+
"Run ID %s is not in `RUNNING` status. Ignoring reply message %s.",
|
346
|
+
message.metadata.run_id,
|
347
|
+
message.metadata.message_id,
|
348
|
+
)
|
349
|
+
finally:
|
350
|
+
# Delete the message from the state
|
351
|
+
state.delete_messages(
|
352
|
+
message_ids=[
|
353
|
+
message.metadata.message_id,
|
354
|
+
message.metadata.reply_to_message_id,
|
355
|
+
]
|
356
|
+
)
|
357
|
+
|
358
|
+
|
336
359
|
@contextmanager
|
337
360
|
def _init_connection( # pylint: disable=too-many-positional-arguments
|
338
361
|
transport: str,
|
@@ -456,7 +479,7 @@ def run_clientappio_api_grpc(
|
|
456
479
|
ffs_factory: FfsFactory,
|
457
480
|
objectstore_factory: ObjectStoreFactory,
|
458
481
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
459
|
-
) ->
|
482
|
+
) -> grpc.Server:
|
460
483
|
"""Run ClientAppIo API gRPC server."""
|
461
484
|
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
462
485
|
state_factory=state_factory,
|
@@ -475,4 +498,4 @@ def run_clientappio_api_grpc(
|
|
475
498
|
)
|
476
499
|
log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
|
477
500
|
clientappio_grpc_server.start()
|
478
|
-
return clientappio_grpc_server
|
501
|
+
return clientappio_grpc_server
|
{flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.20.0.
|
3
|
+
Version: 1.20.0.dev20250620
|
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
|
{flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/RECORD
RENAMED
@@ -6,7 +6,7 @@ flwr/cli/__init__.py,sha256=EfMGmHoobET6P2blBt_eOByXL8299MgFfB7XNdaPQ6I,720
|
|
6
6
|
flwr/cli/app.py,sha256=AKCP45Dkbpvdil_4Ir9S93L3HP3iUOnHmcZjscoM8uU,1856
|
7
7
|
flwr/cli/auth_plugin/__init__.py,sha256=FyaoqPzcxlBTFfJ2sBRC5USwQLmAhFr5KuBwfMO4bmo,1052
|
8
8
|
flwr/cli/auth_plugin/oidc_cli_plugin.py,sha256=gIhW6Jg9QAo-jL43LYPpw_kn7pdUZZae0s0H8dEgjLM,5384
|
9
|
-
flwr/cli/build.py,sha256=
|
9
|
+
flwr/cli/build.py,sha256=7OrcTqrjJd-iVq-MCtCIBCfvzi1JoqwUxeZDGKDKu6I,7265
|
10
10
|
flwr/cli/cli_user_auth_interceptor.py,sha256=-JqDXpeZNQVwoSG7hMKsiS5qY5k5oklNSlQOVpM0-aY,3126
|
11
11
|
flwr/cli/config_utils.py,sha256=IAVn2uWTXpN72YYt7raLtwp8ziwZugUKSURpc471VzU,9123
|
12
12
|
flwr/cli/constant.py,sha256=g7Ad7o3DJDkJNrWS0T3SSJETWSTkkVJWGpLM8zlbpcY,1289
|
@@ -84,7 +84,7 @@ flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ
|
|
84
84
|
flwr/client/grpc_adapter_client/connection.py,sha256=aj5tTYyE8z2hQLXPPydsJiz8gBDIWLUhfWvqYkAL1L4,3966
|
85
85
|
flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
|
86
86
|
flwr/client/grpc_rere_client/client_interceptor.py,sha256=zFaVHw6AxeNO-7eCKKb-RxrPa7zbM5Z-2-1Efc4adQY,2451
|
87
|
-
flwr/client/grpc_rere_client/connection.py,sha256=
|
87
|
+
flwr/client/grpc_rere_client/connection.py,sha256=5XNwDiac3YEXjyosSmiGz3lJyGNzA8I1I-ft4z08uIw,13619
|
88
88
|
flwr/client/grpc_rere_client/grpc_adapter.py,sha256=dLGB5GriszAmtgvuFGuz_F7rIwpzLfDxhJ7T3Un-Ce0,6694
|
89
89
|
flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
|
90
90
|
flwr/client/message_handler/message_handler.py,sha256=X9SXX6et97Lw9_DGD93HKsEBGNjXClcFgc_5aLK0oiU,6541
|
@@ -98,7 +98,7 @@ flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=aKqjZCrikF73y3E-7h40
|
|
98
98
|
flwr/client/mod/utils.py,sha256=FUgD2TfcWqSeF6jUKZ4i6Ke56U4Nrv85AeVb93s6R9g,1201
|
99
99
|
flwr/client/numpy_client.py,sha256=Qq6ghsIAop2slKqAfgiI5NiHJ4LIxGmrik3Ror4_XVc,9581
|
100
100
|
flwr/client/rest_client/__init__.py,sha256=MBiuK62hj439m9rtwSwI184Hth6Tt5GbmpNMyl3zkZY,735
|
101
|
-
flwr/client/rest_client/connection.py,sha256=
|
101
|
+
flwr/client/rest_client/connection.py,sha256=hp-bVcqG0Ul4OmITxcqEHOsGtJuyNevndP-B8trwlns,16270
|
102
102
|
flwr/client/run_info_store.py,sha256=MaJ3UQ-07hWtK67wnWu0zR29jrk0fsfgJX506dvEOfE,4042
|
103
103
|
flwr/client/typing.py,sha256=Jw3rawDzI_-ZDcRmEQcs5gZModY7oeQlEeltYsdOhlU,1048
|
104
104
|
flwr/clientapp/__init__.py,sha256=zGW4z49Ojzoi1hDiRC7kyhLjijUilc6fqHhtM_ATRVA,719
|
@@ -108,7 +108,7 @@ flwr/common/args.py,sha256=-aX_jVnSaDrJR2KZ8Wq0Y3dQHII4R4MJtJOIXzVUA0c,5417
|
|
108
108
|
flwr/common/auth_plugin/__init__.py,sha256=3rzPkVLn9WyB5n7HLk1XGDw3SLCqRWAU1_CnglcWPfw,970
|
109
109
|
flwr/common/auth_plugin/auth_plugin.py,sha256=kXx5o39vJchaPv28sK9qO6H_UXSWym6zRBbCa7sUwtQ,4825
|
110
110
|
flwr/common/config.py,sha256=glcZDjco-amw1YfQcYTFJ4S1pt9APoexT-mf1QscuHs,13960
|
111
|
-
flwr/common/constant.py,sha256=
|
111
|
+
flwr/common/constant.py,sha256=HO1y0YYHZUCIDt5QQnvCTSYVeKhkIJve0RbQ3nW7jHU,8191
|
112
112
|
flwr/common/context.py,sha256=Be8obQR_OvEDy1OmshuUKxGRQ7Qx89mf5F4xlhkR10s,2407
|
113
113
|
flwr/common/date.py,sha256=1ZT2cRSpC2DJqprOVTLXYCR_O2_OZR0zXO_brJ3LqWc,1554
|
114
114
|
flwr/common/differential_privacy.py,sha256=FdlpdpPl_H_2HJa8CQM1iCUGBBQ5Dc8CzxmHERM-EoE,6148
|
@@ -164,10 +164,10 @@ flwr/compat/server/__init__.py,sha256=TGVSoOTuf5T5JHUVrK5wuorQF7L6Wvdem8B4uufvMJ
|
|
164
164
|
flwr/compat/server/app.py,sha256=_lIe7Q4KUk-olq9PYBxIsO3UaOn6N92CWgWQ4hRcAZw,6490
|
165
165
|
flwr/compat/simulation/__init__.py,sha256=MApGa-tysDDw34iSdxZ7TWOKtGJM-z3i8fIRJa0qbZ8,750
|
166
166
|
flwr/proto/__init__.py,sha256=S3VbQzVwNC1P-3_9EdrXuwgptO-BVuuAe20Z_OUc1cQ,683
|
167
|
-
flwr/proto/clientappio_pb2.py,sha256=
|
168
|
-
flwr/proto/clientappio_pb2.pyi,sha256=
|
169
|
-
flwr/proto/clientappio_pb2_grpc.py,sha256=
|
170
|
-
flwr/proto/clientappio_pb2_grpc.pyi,sha256=
|
167
|
+
flwr/proto/clientappio_pb2.py,sha256=jkTJnHtHOylYTV0pxfFAaA0CtIPGrwGCcVgCg6i0LhU,4337
|
168
|
+
flwr/proto/clientappio_pb2.pyi,sha256=qrH9KeJ8YXRa9iQYlKV8-kwXrmxGr6AJp5f7Yx88CEg,6843
|
169
|
+
flwr/proto/clientappio_pb2_grpc.py,sha256=vstXed6-uSOqM0qbaZBwYIgHHs7GH6oKqOq0TniboOk,8035
|
170
|
+
flwr/proto/clientappio_pb2_grpc.pyi,sha256=828mbHoq0SxZ3NRmGqiZmpb4KtLPi71piQBMF_2EZxk,2399
|
171
171
|
flwr/proto/error_pb2.py,sha256=PQVWrfjVPo88ql_KgV9nCxyQNCcV9PVfmcw7sOzTMro,1084
|
172
172
|
flwr/proto/error_pb2.pyi,sha256=ZNH4HhJTU_KfMXlyCeg8FwU-fcUYxTqEmoJPtWtHikc,734
|
173
173
|
flwr/proto/error_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
@@ -246,7 +246,7 @@ flwr/server/server.py,sha256=39m4FSN2T-uVA-no9nstN0eWW0co-IUUAIMmpd3V7Jc,17893
|
|
246
246
|
flwr/server/server_app.py,sha256=8uagoZX-3CY3tazPqkIV9jY-cN0YrRRrDmVe23o0AV0,9515
|
247
247
|
flwr/server/server_config.py,sha256=e_6ddh0riwOJsdNn2BFev344uMWfDk9n7dyjNpPgm1w,1349
|
248
248
|
flwr/server/serverapp/__init__.py,sha256=xcC0T_MQSMS9cicUzUUpMNCOsF2d8Oh_8jvnoBLuZvo,800
|
249
|
-
flwr/server/serverapp/app.py,sha256=
|
249
|
+
flwr/server/serverapp/app.py,sha256=CQZy_oyDHBQFokkBnwaYvo0X_YFKKYekwTxiGZPtD1w,9491
|
250
250
|
flwr/server/serverapp_components.py,sha256=dfSqmrsVy3arKXpl3ZIBQWdV8rehfIms8aJooyzdmEM,2118
|
251
251
|
flwr/server/strategy/__init__.py,sha256=HhsSWMWaC7oCb2g7Kqn1MBKdrfvgi8VxACy9ZL706Q0,2836
|
252
252
|
flwr/server/strategy/aggregate.py,sha256=smlKKy-uFUuuFR12vlclucnwSQWRz78R79-Km4RWqbw,13978
|
@@ -335,31 +335,32 @@ flwr/supercore/object_store/__init__.py,sha256=cdfPAmjINY6iOp8oI_LdcVh2simg469Mk
|
|
335
335
|
flwr/supercore/object_store/in_memory_object_store.py,sha256=oflJcOuVNgx9A-B2da4VHDb1qj_Jub9wKFOrUBgtz_U,9630
|
336
336
|
flwr/supercore/object_store/object_store.py,sha256=VlZz-yzoWZtITbnYD8vwLZbFosv7vgr1XVNzByObeY0,5853
|
337
337
|
flwr/supercore/object_store/object_store_factory.py,sha256=QVwE2ywi7vsj2iKfvWWnNw3N_I7Rz91NUt2RpcbJ7iM,1527
|
338
|
+
flwr/supercore/utils.py,sha256=ebuHMbeA8eXisX0oMPqBK3hk7uVnIE_yiqWVz8YbkpQ,1324
|
338
339
|
flwr/superexec/__init__.py,sha256=YFqER0IJc1XEWfsX6AxZ9LSRq0sawPYrNYki-brvTIc,715
|
339
340
|
flwr/superexec/app.py,sha256=U2jjOHb2LGWoU7vrl9_czTzre9O2mPxu3CPGUZ86sK4,1465
|
340
341
|
flwr/superexec/deployment.py,sha256=cFxhFom-0zv93HLNjNcUdBy3Sf6JwshRoXPQtcZunF0,6797
|
341
342
|
flwr/superexec/exec_event_log_interceptor.py,sha256=7aBjZ4lkpOIyWut0s394OpMePr16g_Te594s-9aDM9Q,5774
|
342
343
|
flwr/superexec/exec_grpc.py,sha256=lpc_rgRjtHHMzcdOzznl12D4vT22JqV5acdy45YDb0k,3498
|
343
|
-
flwr/superexec/exec_servicer.py,sha256=
|
344
|
+
flwr/superexec/exec_servicer.py,sha256=c0nwdFBiS6CbKrRA7ffOpsgASOLeaRV_ICwxDfxNGAg,12498
|
344
345
|
flwr/superexec/exec_user_auth_interceptor.py,sha256=HpGHTcDKzB7XUiQHXgntNVFYL-VfP9Wj5tEVc04VOOw,5820
|
345
346
|
flwr/superexec/executor.py,sha256=LaErHRJvNggjWV6FI6eajgKfnwOvSv2UqzFH253yDro,3265
|
346
347
|
flwr/superexec/simulation.py,sha256=62rSLcS-1wnMsMsafSQuIDLs5ZS6Ail1spkZ-alNNTg,4156
|
347
348
|
flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
|
348
349
|
flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
|
349
350
|
flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
|
350
|
-
flwr/supernode/cli/flower_supernode.py,sha256=
|
351
|
-
flwr/supernode/cli/flwr_clientapp.py,sha256=
|
351
|
+
flwr/supernode/cli/flower_supernode.py,sha256=fAkk9zGhoP8Sv05EkdXRiCtirTAzWkSZBqRoaDdgflk,8529
|
352
|
+
flwr/supernode/cli/flwr_clientapp.py,sha256=zaro6BoUEmfKIPQYuyJ9oR4rrHSS3bufhEqxcTo5VZU,3153
|
352
353
|
flwr/supernode/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUDfuSHsbU,976
|
353
354
|
flwr/supernode/nodestate/in_memory_nodestate.py,sha256=LF3AbaW0bcZHY5yKWwUJSU2RZbMynt-YjFysGqvTOQY,7338
|
354
355
|
flwr/supernode/nodestate/nodestate.py,sha256=kkGFxYnLIwT4-UmlPnf6HvAUpPey2urUNrweGybAIY4,6398
|
355
356
|
flwr/supernode/nodestate/nodestate_factory.py,sha256=UYTDCcwK_baHUmkzkJDxL0UEqvtTfOMlQRrROMCd0Xo,1430
|
356
357
|
flwr/supernode/runtime/__init__.py,sha256=JQdqd2EMTn-ORMeTvewYYh52ls0YKP68jrps1qioxu4,718
|
357
|
-
flwr/supernode/runtime/run_clientapp.py,sha256=
|
358
|
+
flwr/supernode/runtime/run_clientapp.py,sha256=wDOs0SbTQJxcm4z63qK4mHomKXjyW-VMsUjD-mXD5X4,8248
|
358
359
|
flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca8gxdEo,717
|
359
|
-
flwr/supernode/servicer/clientappio/__init__.py,sha256=
|
360
|
-
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=
|
361
|
-
flwr/supernode/start_client_internal.py,sha256=
|
362
|
-
flwr_nightly-1.20.0.
|
363
|
-
flwr_nightly-1.20.0.
|
364
|
-
flwr_nightly-1.20.0.
|
365
|
-
flwr_nightly-1.20.0.
|
360
|
+
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
361
|
+
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=d3GdIabycUoDBDL_eVlt513knGSjQW3-9lG6Cw4QEk4,5719
|
362
|
+
flwr/supernode/start_client_internal.py,sha256=DAXuReZ1FCXt9Y1KbM0p-dI50ROWPEJXzfKrl14OE6k,18233
|
363
|
+
flwr_nightly-1.20.0.dev20250620.dist-info/METADATA,sha256=YNBc0zQFwQfdbdsas9XYKH5jPgEyt8gQR_Jrh1wkh0A,15910
|
364
|
+
flwr_nightly-1.20.0.dev20250620.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
365
|
+
flwr_nightly-1.20.0.dev20250620.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
|
366
|
+
flwr_nightly-1.20.0.dev20250620.dist-info/RECORD,,
|
{flwr_nightly-1.20.0.dev20250618.dist-info → flwr_nightly-1.20.0.dev20250620.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|