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
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,29 +17,33 @@ 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\"
|
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=
|
29
|
-
_globals['
|
30
|
-
_globals['
|
31
|
-
_globals['
|
32
|
-
_globals['
|
33
|
-
_globals['
|
34
|
-
_globals['
|
35
|
-
_globals['
|
36
|
-
_globals['
|
37
|
-
_globals['
|
38
|
-
_globals['
|
39
|
-
_globals['
|
40
|
-
_globals['
|
41
|
-
_globals['
|
42
|
-
_globals['
|
43
|
-
_globals['
|
44
|
-
_globals['
|
27
|
+
_globals['_CLIENTAPPOUTPUTCODE']._serialized_start=795
|
28
|
+
_globals['_CLIENTAPPOUTPUTCODE']._serialized_end=871
|
29
|
+
_globals['_GETRUNIDSWITHPENDINGMESSAGESREQUEST']._serialized_start=114
|
30
|
+
_globals['_GETRUNIDSWITHPENDINGMESSAGESREQUEST']._serialized_end=151
|
31
|
+
_globals['_GETRUNIDSWITHPENDINGMESSAGESRESPONSE']._serialized_start=153
|
32
|
+
_globals['_GETRUNIDSWITHPENDINGMESSAGESRESPONSE']._serialized_end=208
|
33
|
+
_globals['_REQUESTTOKENREQUEST']._serialized_start=210
|
34
|
+
_globals['_REQUESTTOKENREQUEST']._serialized_end=247
|
35
|
+
_globals['_REQUESTTOKENRESPONSE']._serialized_start=249
|
36
|
+
_globals['_REQUESTTOKENRESPONSE']._serialized_end=286
|
37
|
+
_globals['_CLIENTAPPOUTPUTSTATUS']._serialized_start=288
|
38
|
+
_globals['_CLIENTAPPOUTPUTSTATUS']._serialized_end=375
|
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
|
45
49
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/clientappio_pb2.pyi
CHANGED
@@ -7,6 +7,7 @@ import flwr.proto.fab_pb2
|
|
7
7
|
import flwr.proto.message_pb2
|
8
8
|
import flwr.proto.run_pb2
|
9
9
|
import google.protobuf.descriptor
|
10
|
+
import google.protobuf.internal.containers
|
10
11
|
import google.protobuf.internal.enum_type_wrapper
|
11
12
|
import google.protobuf.message
|
12
13
|
import typing
|
@@ -31,44 +32,69 @@ UNKNOWN_ERROR: ClientAppOutputCode.ValueType # 2
|
|
31
32
|
global___ClientAppOutputCode = ClientAppOutputCode
|
32
33
|
|
33
34
|
|
34
|
-
class
|
35
|
+
class GetRunIdsWithPendingMessagesRequest(google.protobuf.message.Message):
|
35
36
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
def __init__(self,
|
38
|
+
) -> None: ...
|
39
|
+
global___GetRunIdsWithPendingMessagesRequest = GetRunIdsWithPendingMessagesRequest
|
40
|
+
|
41
|
+
class GetRunIdsWithPendingMessagesResponse(google.protobuf.message.Message):
|
42
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
43
|
+
RUN_IDS_FIELD_NUMBER: builtins.int
|
44
|
+
@property
|
45
|
+
def run_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
|
46
|
+
"""List of run IDs"""
|
47
|
+
pass
|
40
48
|
def __init__(self,
|
41
49
|
*,
|
42
|
-
|
43
|
-
message: typing.Text = ...,
|
50
|
+
run_ids: typing.Optional[typing.Iterable[builtins.int]] = ...,
|
44
51
|
) -> None: ...
|
45
|
-
def ClearField(self, field_name: typing_extensions.Literal["
|
46
|
-
|
52
|
+
def ClearField(self, field_name: typing_extensions.Literal["run_ids",b"run_ids"]) -> None: ...
|
53
|
+
global___GetRunIdsWithPendingMessagesResponse = GetRunIdsWithPendingMessagesResponse
|
47
54
|
|
48
|
-
class
|
55
|
+
class RequestTokenRequest(google.protobuf.message.Message):
|
49
56
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
57
|
+
RUN_ID_FIELD_NUMBER: builtins.int
|
58
|
+
run_id: builtins.int
|
50
59
|
def __init__(self,
|
60
|
+
*,
|
61
|
+
run_id: builtins.int = ...,
|
51
62
|
) -> None: ...
|
52
|
-
|
63
|
+
def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
|
64
|
+
global___RequestTokenRequest = RequestTokenRequest
|
53
65
|
|
54
|
-
class
|
66
|
+
class RequestTokenResponse(google.protobuf.message.Message):
|
55
67
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
56
68
|
TOKEN_FIELD_NUMBER: builtins.int
|
57
|
-
token:
|
69
|
+
token: typing.Text
|
58
70
|
def __init__(self,
|
59
71
|
*,
|
60
|
-
token:
|
72
|
+
token: typing.Text = ...,
|
61
73
|
) -> None: ...
|
62
74
|
def ClearField(self, field_name: typing_extensions.Literal["token",b"token"]) -> None: ...
|
63
|
-
|
75
|
+
global___RequestTokenResponse = RequestTokenResponse
|
76
|
+
|
77
|
+
class ClientAppOutputStatus(google.protobuf.message.Message):
|
78
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
79
|
+
CODE_FIELD_NUMBER: builtins.int
|
80
|
+
MESSAGE_FIELD_NUMBER: builtins.int
|
81
|
+
code: global___ClientAppOutputCode.ValueType
|
82
|
+
message: typing.Text
|
83
|
+
def __init__(self,
|
84
|
+
*,
|
85
|
+
code: global___ClientAppOutputCode.ValueType = ...,
|
86
|
+
message: typing.Text = ...,
|
87
|
+
) -> None: ...
|
88
|
+
def ClearField(self, field_name: typing_extensions.Literal["code",b"code","message",b"message"]) -> None: ...
|
89
|
+
global___ClientAppOutputStatus = ClientAppOutputStatus
|
64
90
|
|
65
91
|
class PullClientAppInputsRequest(google.protobuf.message.Message):
|
66
92
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
67
93
|
TOKEN_FIELD_NUMBER: builtins.int
|
68
|
-
token:
|
94
|
+
token: typing.Text
|
69
95
|
def __init__(self,
|
70
96
|
*,
|
71
|
-
token:
|
97
|
+
token: typing.Text = ...,
|
72
98
|
) -> None: ...
|
73
99
|
def ClearField(self, field_name: typing_extensions.Literal["token",b"token"]) -> None: ...
|
74
100
|
global___PullClientAppInputsRequest = PullClientAppInputsRequest
|
@@ -103,14 +129,14 @@ class PushClientAppOutputsRequest(google.protobuf.message.Message):
|
|
103
129
|
TOKEN_FIELD_NUMBER: builtins.int
|
104
130
|
MESSAGE_FIELD_NUMBER: builtins.int
|
105
131
|
CONTEXT_FIELD_NUMBER: builtins.int
|
106
|
-
token:
|
132
|
+
token: typing.Text
|
107
133
|
@property
|
108
134
|
def message(self) -> flwr.proto.message_pb2.Message: ...
|
109
135
|
@property
|
110
136
|
def context(self) -> flwr.proto.message_pb2.Context: ...
|
111
137
|
def __init__(self,
|
112
138
|
*,
|
113
|
-
token:
|
139
|
+
token: typing.Text = ...,
|
114
140
|
message: typing.Optional[flwr.proto.message_pb2.Message] = ...,
|
115
141
|
context: typing.Optional[flwr.proto.message_pb2.Context] = ...,
|
116
142
|
) -> None: ...
|
@@ -14,10 +14,15 @@ class ClientAppIoStub(object):
|
|
14
14
|
Args:
|
15
15
|
channel: A grpc.Channel.
|
16
16
|
"""
|
17
|
-
self.
|
18
|
-
'/flwr.proto.ClientAppIo/
|
19
|
-
request_serializer=flwr_dot_proto_dot_clientappio__pb2.
|
20
|
-
response_deserializer=flwr_dot_proto_dot_clientappio__pb2.
|
17
|
+
self.GetRunIdsWithPendingMessages = channel.unary_unary(
|
18
|
+
'/flwr.proto.ClientAppIo/GetRunIdsWithPendingMessages',
|
19
|
+
request_serializer=flwr_dot_proto_dot_clientappio__pb2.GetRunIdsWithPendingMessagesRequest.SerializeToString,
|
20
|
+
response_deserializer=flwr_dot_proto_dot_clientappio__pb2.GetRunIdsWithPendingMessagesResponse.FromString,
|
21
|
+
)
|
22
|
+
self.RequestToken = channel.unary_unary(
|
23
|
+
'/flwr.proto.ClientAppIo/RequestToken',
|
24
|
+
request_serializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenRequest.SerializeToString,
|
25
|
+
response_deserializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenResponse.FromString,
|
21
26
|
)
|
22
27
|
self.PullClientAppInputs = channel.unary_unary(
|
23
28
|
'/flwr.proto.ClientAppIo/PullClientAppInputs',
|
@@ -34,22 +39,29 @@ class ClientAppIoStub(object):
|
|
34
39
|
class ClientAppIoServicer(object):
|
35
40
|
"""Missing associated documentation comment in .proto file."""
|
36
41
|
|
37
|
-
def
|
38
|
-
"""Get
|
42
|
+
def GetRunIdsWithPendingMessages(self, request, context):
|
43
|
+
"""Get run IDs with pending messages
|
44
|
+
"""
|
45
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
46
|
+
context.set_details('Method not implemented!')
|
47
|
+
raise NotImplementedError('Method not implemented!')
|
48
|
+
|
49
|
+
def RequestToken(self, request, context):
|
50
|
+
"""Request token
|
39
51
|
"""
|
40
52
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
41
53
|
context.set_details('Method not implemented!')
|
42
54
|
raise NotImplementedError('Method not implemented!')
|
43
55
|
|
44
56
|
def PullClientAppInputs(self, request, context):
|
45
|
-
"""
|
57
|
+
"""Pull client app inputs
|
46
58
|
"""
|
47
59
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
48
60
|
context.set_details('Method not implemented!')
|
49
61
|
raise NotImplementedError('Method not implemented!')
|
50
62
|
|
51
63
|
def PushClientAppOutputs(self, request, context):
|
52
|
-
"""
|
64
|
+
"""Push client app outputs
|
53
65
|
"""
|
54
66
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
55
67
|
context.set_details('Method not implemented!')
|
@@ -58,10 +70,15 @@ class ClientAppIoServicer(object):
|
|
58
70
|
|
59
71
|
def add_ClientAppIoServicer_to_server(servicer, server):
|
60
72
|
rpc_method_handlers = {
|
61
|
-
'
|
62
|
-
servicer.
|
63
|
-
request_deserializer=flwr_dot_proto_dot_clientappio__pb2.
|
64
|
-
response_serializer=flwr_dot_proto_dot_clientappio__pb2.
|
73
|
+
'GetRunIdsWithPendingMessages': grpc.unary_unary_rpc_method_handler(
|
74
|
+
servicer.GetRunIdsWithPendingMessages,
|
75
|
+
request_deserializer=flwr_dot_proto_dot_clientappio__pb2.GetRunIdsWithPendingMessagesRequest.FromString,
|
76
|
+
response_serializer=flwr_dot_proto_dot_clientappio__pb2.GetRunIdsWithPendingMessagesResponse.SerializeToString,
|
77
|
+
),
|
78
|
+
'RequestToken': grpc.unary_unary_rpc_method_handler(
|
79
|
+
servicer.RequestToken,
|
80
|
+
request_deserializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenRequest.FromString,
|
81
|
+
response_serializer=flwr_dot_proto_dot_clientappio__pb2.RequestTokenResponse.SerializeToString,
|
65
82
|
),
|
66
83
|
'PullClientAppInputs': grpc.unary_unary_rpc_method_handler(
|
67
84
|
servicer.PullClientAppInputs,
|
@@ -84,7 +101,24 @@ class ClientAppIo(object):
|
|
84
101
|
"""Missing associated documentation comment in .proto file."""
|
85
102
|
|
86
103
|
@staticmethod
|
87
|
-
def
|
104
|
+
def GetRunIdsWithPendingMessages(request,
|
105
|
+
target,
|
106
|
+
options=(),
|
107
|
+
channel_credentials=None,
|
108
|
+
call_credentials=None,
|
109
|
+
insecure=False,
|
110
|
+
compression=None,
|
111
|
+
wait_for_ready=None,
|
112
|
+
timeout=None,
|
113
|
+
metadata=None):
|
114
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.ClientAppIo/GetRunIdsWithPendingMessages',
|
115
|
+
flwr_dot_proto_dot_clientappio__pb2.GetRunIdsWithPendingMessagesRequest.SerializeToString,
|
116
|
+
flwr_dot_proto_dot_clientappio__pb2.GetRunIdsWithPendingMessagesResponse.FromString,
|
117
|
+
options, channel_credentials,
|
118
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
119
|
+
|
120
|
+
@staticmethod
|
121
|
+
def RequestToken(request,
|
88
122
|
target,
|
89
123
|
options=(),
|
90
124
|
channel_credentials=None,
|
@@ -94,9 +128,9 @@ class ClientAppIo(object):
|
|
94
128
|
wait_for_ready=None,
|
95
129
|
timeout=None,
|
96
130
|
metadata=None):
|
97
|
-
return grpc.experimental.unary_unary(request, target, '/flwr.proto.ClientAppIo/
|
98
|
-
flwr_dot_proto_dot_clientappio__pb2.
|
99
|
-
flwr_dot_proto_dot_clientappio__pb2.
|
131
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.ClientAppIo/RequestToken',
|
132
|
+
flwr_dot_proto_dot_clientappio__pb2.RequestTokenRequest.SerializeToString,
|
133
|
+
flwr_dot_proto_dot_clientappio__pb2.RequestTokenResponse.FromString,
|
100
134
|
options, channel_credentials,
|
101
135
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
102
136
|
|
@@ -8,29 +8,42 @@ import grpc
|
|
8
8
|
|
9
9
|
class ClientAppIoStub:
|
10
10
|
def __init__(self, channel: grpc.Channel) -> None: ...
|
11
|
-
|
12
|
-
flwr.proto.clientappio_pb2.
|
13
|
-
flwr.proto.clientappio_pb2.
|
14
|
-
"""Get
|
11
|
+
GetRunIdsWithPendingMessages: grpc.UnaryUnaryMultiCallable[
|
12
|
+
flwr.proto.clientappio_pb2.GetRunIdsWithPendingMessagesRequest,
|
13
|
+
flwr.proto.clientappio_pb2.GetRunIdsWithPendingMessagesResponse]
|
14
|
+
"""Get run IDs with pending messages"""
|
15
|
+
|
16
|
+
RequestToken: grpc.UnaryUnaryMultiCallable[
|
17
|
+
flwr.proto.clientappio_pb2.RequestTokenRequest,
|
18
|
+
flwr.proto.clientappio_pb2.RequestTokenResponse]
|
19
|
+
"""Request token"""
|
15
20
|
|
16
21
|
PullClientAppInputs: grpc.UnaryUnaryMultiCallable[
|
17
22
|
flwr.proto.clientappio_pb2.PullClientAppInputsRequest,
|
18
23
|
flwr.proto.clientappio_pb2.PullClientAppInputsResponse]
|
19
|
-
"""
|
24
|
+
"""Pull client app inputs"""
|
20
25
|
|
21
26
|
PushClientAppOutputs: grpc.UnaryUnaryMultiCallable[
|
22
27
|
flwr.proto.clientappio_pb2.PushClientAppOutputsRequest,
|
23
28
|
flwr.proto.clientappio_pb2.PushClientAppOutputsResponse]
|
24
|
-
"""
|
29
|
+
"""Push client app outputs"""
|
25
30
|
|
26
31
|
|
27
32
|
class ClientAppIoServicer(metaclass=abc.ABCMeta):
|
28
33
|
@abc.abstractmethod
|
29
|
-
def
|
30
|
-
request: flwr.proto.clientappio_pb2.
|
34
|
+
def GetRunIdsWithPendingMessages(self,
|
35
|
+
request: flwr.proto.clientappio_pb2.GetRunIdsWithPendingMessagesRequest,
|
36
|
+
context: grpc.ServicerContext,
|
37
|
+
) -> flwr.proto.clientappio_pb2.GetRunIdsWithPendingMessagesResponse:
|
38
|
+
"""Get run IDs with pending messages"""
|
39
|
+
pass
|
40
|
+
|
41
|
+
@abc.abstractmethod
|
42
|
+
def RequestToken(self,
|
43
|
+
request: flwr.proto.clientappio_pb2.RequestTokenRequest,
|
31
44
|
context: grpc.ServicerContext,
|
32
|
-
) -> flwr.proto.clientappio_pb2.
|
33
|
-
"""
|
45
|
+
) -> flwr.proto.clientappio_pb2.RequestTokenResponse:
|
46
|
+
"""Request token"""
|
34
47
|
pass
|
35
48
|
|
36
49
|
@abc.abstractmethod
|
@@ -38,7 +51,7 @@ class ClientAppIoServicer(metaclass=abc.ABCMeta):
|
|
38
51
|
request: flwr.proto.clientappio_pb2.PullClientAppInputsRequest,
|
39
52
|
context: grpc.ServicerContext,
|
40
53
|
) -> flwr.proto.clientappio_pb2.PullClientAppInputsResponse:
|
41
|
-
"""
|
54
|
+
"""Pull client app inputs"""
|
42
55
|
pass
|
43
56
|
|
44
57
|
@abc.abstractmethod
|
@@ -46,7 +59,7 @@ class ClientAppIoServicer(metaclass=abc.ABCMeta):
|
|
46
59
|
request: flwr.proto.clientappio_pb2.PushClientAppOutputsRequest,
|
47
60
|
context: grpc.ServicerContext,
|
48
61
|
) -> flwr.proto.clientappio_pb2.PushClientAppOutputsResponse:
|
49
|
-
"""
|
62
|
+
"""Push client app outputs"""
|
50
63
|
pass
|
51
64
|
|
52
65
|
|
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,11 +41,11 @@ 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,
|
47
|
-
run_once=(args.token is not None),
|
48
|
+
run_once=(args.token is not None) or args.run_once,
|
48
49
|
token=args.token,
|
49
50
|
flwr_dir=args.flwr_dir,
|
50
51
|
certificates=None,
|
@@ -66,7 +67,7 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
66
67
|
)
|
67
68
|
parser.add_argument(
|
68
69
|
"--token",
|
69
|
-
type=
|
70
|
+
type=str,
|
70
71
|
required=False,
|
71
72
|
help="Unique token generated by SuperNode for each ClientApp execution",
|
72
73
|
)
|
@@ -77,5 +78,11 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
77
78
|
help="The PID of the parent process. When set, the process will terminate "
|
78
79
|
"when the parent process exits.",
|
79
80
|
)
|
81
|
+
parser.add_argument(
|
82
|
+
"--run-once",
|
83
|
+
action="store_true",
|
84
|
+
help="When set, this process will start a single ClientApp for a pending "
|
85
|
+
"message. If there is no pending message, the process will exit.",
|
86
|
+
)
|
80
87
|
add_args_flwr_app_common(parser=parser)
|
81
88
|
return parser
|