flwr-nightly 1.19.0.dev20250516__py3-none-any.whl → 1.19.0.dev20250521__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/app/__init__.py +15 -0
- flwr/app/error.py +68 -0
- flwr/app/metadata.py +223 -0
- flwr/client/__init__.py +2 -2
- flwr/client/client_app.py +1 -1
- flwr/client/clientapp/app.py +1 -1
- flwr/client/grpc_rere_client/connection.py +2 -1
- flwr/client/rest_client/connection.py +2 -1
- flwr/client/start_client_internal.py +608 -0
- flwr/client/supernode/app.py +1 -1
- flwr/clientapp/__init__.py +15 -0
- flwr/common/__init__.py +2 -2
- flwr/common/inflatable_grpc_utils.py +97 -0
- flwr/common/message.py +87 -245
- flwr/common/record/array.py +1 -1
- flwr/common/record/configrecord.py +1 -1
- flwr/common/serde.py +9 -54
- flwr/common/serde_utils.py +50 -0
- flwr/compat/__init__.py +15 -0
- flwr/compat/client/__init__.py +15 -0
- flwr/{client → compat/client}/app.py +13 -11
- flwr/compat/common/__init__.py +15 -0
- flwr/compat/server/__init__.py +15 -0
- flwr/compat/simulation/__init__.py +15 -0
- flwr/server/superlink/fleet/vce/vce_api.py +1 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +2 -6
- flwr/serverapp/__init__.py +15 -0
- flwr/supercore/__init__.py +15 -0
- flwr/superlink/__init__.py +15 -0
- flwr/supernode/__init__.py +15 -0
- flwr/{client → supernode}/nodestate/in_memory_nodestate.py +1 -1
- {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/METADATA +1 -1
- {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/RECORD +38 -23
- /flwr/{client → supernode}/nodestate/__init__.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
- {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/entry_points.txt +0 -0
flwr/app/__init__.py
ADDED
@@ -0,0 +1,15 @@
|
|
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
|
+
"""Public Flower App APIs."""
|
flwr/app/error.py
ADDED
@@ -0,0 +1,68 @@
|
|
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
|
+
"""Error."""
|
16
|
+
|
17
|
+
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
from typing import Optional, cast
|
21
|
+
|
22
|
+
DEFAULT_TTL = 43200 # This is 12 hours
|
23
|
+
MESSAGE_INIT_ERROR_MESSAGE = (
|
24
|
+
"Invalid arguments for Message. Expected one of the documented "
|
25
|
+
"signatures: Message(content: RecordDict, dst_node_id: int, message_type: str,"
|
26
|
+
" *, [ttl: float, group_id: str]) or Message(content: RecordDict | error: Error,"
|
27
|
+
" *, reply_to: Message, [ttl: float])."
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
class Error:
|
32
|
+
"""The class storing information about an error that occurred.
|
33
|
+
|
34
|
+
Parameters
|
35
|
+
----------
|
36
|
+
code : int
|
37
|
+
An identifier for the error.
|
38
|
+
reason : Optional[str]
|
39
|
+
A reason for why the error arose (e.g. an exception stack-trace)
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(self, code: int, reason: str | None = None) -> None:
|
43
|
+
var_dict = {
|
44
|
+
"_code": code,
|
45
|
+
"_reason": reason,
|
46
|
+
}
|
47
|
+
self.__dict__.update(var_dict)
|
48
|
+
|
49
|
+
@property
|
50
|
+
def code(self) -> int:
|
51
|
+
"""Error code."""
|
52
|
+
return cast(int, self.__dict__["_code"])
|
53
|
+
|
54
|
+
@property
|
55
|
+
def reason(self) -> str | None:
|
56
|
+
"""Reason reported about the error."""
|
57
|
+
return cast(Optional[str], self.__dict__["_reason"])
|
58
|
+
|
59
|
+
def __repr__(self) -> str:
|
60
|
+
"""Return a string representation of this instance."""
|
61
|
+
view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
|
62
|
+
return f"{self.__class__.__qualname__}({view})"
|
63
|
+
|
64
|
+
def __eq__(self, other: object) -> bool:
|
65
|
+
"""Compare two instances of the class."""
|
66
|
+
if not isinstance(other, self.__class__):
|
67
|
+
raise NotImplementedError
|
68
|
+
return self.__dict__ == other.__dict__
|
flwr/app/metadata.py
ADDED
@@ -0,0 +1,223 @@
|
|
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
|
+
"""Metadata."""
|
16
|
+
|
17
|
+
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
from typing import cast
|
21
|
+
|
22
|
+
from ..common.constant import MessageType, MessageTypeLegacy
|
23
|
+
|
24
|
+
|
25
|
+
class Metadata: # pylint: disable=too-many-instance-attributes
|
26
|
+
"""The class representing metadata associated with the current message.
|
27
|
+
|
28
|
+
Parameters
|
29
|
+
----------
|
30
|
+
run_id : int
|
31
|
+
An identifier for the current run.
|
32
|
+
message_id : str
|
33
|
+
An identifier for the current message.
|
34
|
+
src_node_id : int
|
35
|
+
An identifier for the node sending this message.
|
36
|
+
dst_node_id : int
|
37
|
+
An identifier for the node receiving this message.
|
38
|
+
reply_to_message_id : str
|
39
|
+
An identifier for the message to which this message is a reply.
|
40
|
+
group_id : str
|
41
|
+
An identifier for grouping messages. In some settings,
|
42
|
+
this is used as the FL round.
|
43
|
+
created_at : float
|
44
|
+
Unix timestamp when the message was created.
|
45
|
+
ttl : float
|
46
|
+
Time-to-live for this message in seconds.
|
47
|
+
message_type : str
|
48
|
+
A string that encodes the action to be executed on
|
49
|
+
the receiving end.
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
53
|
+
self,
|
54
|
+
run_id: int,
|
55
|
+
message_id: str,
|
56
|
+
src_node_id: int,
|
57
|
+
dst_node_id: int,
|
58
|
+
reply_to_message_id: str,
|
59
|
+
group_id: str,
|
60
|
+
created_at: float,
|
61
|
+
ttl: float,
|
62
|
+
message_type: str,
|
63
|
+
) -> None:
|
64
|
+
var_dict = {
|
65
|
+
"_run_id": run_id,
|
66
|
+
"_message_id": message_id,
|
67
|
+
"_src_node_id": src_node_id,
|
68
|
+
"_dst_node_id": dst_node_id,
|
69
|
+
"_reply_to_message_id": reply_to_message_id,
|
70
|
+
"_group_id": group_id,
|
71
|
+
"_created_at": created_at,
|
72
|
+
"_ttl": ttl,
|
73
|
+
"_message_type": message_type,
|
74
|
+
}
|
75
|
+
self.__dict__.update(var_dict)
|
76
|
+
self.message_type = message_type # Trigger validation
|
77
|
+
|
78
|
+
@property
|
79
|
+
def run_id(self) -> int:
|
80
|
+
"""An identifier for the current run."""
|
81
|
+
return cast(int, self.__dict__["_run_id"])
|
82
|
+
|
83
|
+
@property
|
84
|
+
def message_id(self) -> str:
|
85
|
+
"""An identifier for the current message."""
|
86
|
+
return cast(str, self.__dict__["_message_id"])
|
87
|
+
|
88
|
+
@property
|
89
|
+
def src_node_id(self) -> int:
|
90
|
+
"""An identifier for the node sending this message."""
|
91
|
+
return cast(int, self.__dict__["_src_node_id"])
|
92
|
+
|
93
|
+
@property
|
94
|
+
def reply_to_message_id(self) -> str:
|
95
|
+
"""An identifier for the message to which this message is a reply."""
|
96
|
+
return cast(str, self.__dict__["_reply_to_message_id"])
|
97
|
+
|
98
|
+
@property
|
99
|
+
def dst_node_id(self) -> int:
|
100
|
+
"""An identifier for the node receiving this message."""
|
101
|
+
return cast(int, self.__dict__["_dst_node_id"])
|
102
|
+
|
103
|
+
@dst_node_id.setter
|
104
|
+
def dst_node_id(self, value: int) -> None:
|
105
|
+
"""Set dst_node_id."""
|
106
|
+
self.__dict__["_dst_node_id"] = value
|
107
|
+
|
108
|
+
@property
|
109
|
+
def group_id(self) -> str:
|
110
|
+
"""An identifier for grouping messages."""
|
111
|
+
return cast(str, self.__dict__["_group_id"])
|
112
|
+
|
113
|
+
@group_id.setter
|
114
|
+
def group_id(self, value: str) -> None:
|
115
|
+
"""Set group_id."""
|
116
|
+
self.__dict__["_group_id"] = value
|
117
|
+
|
118
|
+
@property
|
119
|
+
def created_at(self) -> float:
|
120
|
+
"""Unix timestamp when the message was created."""
|
121
|
+
return cast(float, self.__dict__["_created_at"])
|
122
|
+
|
123
|
+
@created_at.setter
|
124
|
+
def created_at(self, value: float) -> None:
|
125
|
+
"""Set creation timestamp of this message."""
|
126
|
+
self.__dict__["_created_at"] = value
|
127
|
+
|
128
|
+
@property
|
129
|
+
def delivered_at(self) -> str:
|
130
|
+
"""Unix timestamp when the message was delivered."""
|
131
|
+
return cast(str, self.__dict__["_delivered_at"])
|
132
|
+
|
133
|
+
@delivered_at.setter
|
134
|
+
def delivered_at(self, value: str) -> None:
|
135
|
+
"""Set delivery timestamp of this message."""
|
136
|
+
self.__dict__["_delivered_at"] = value
|
137
|
+
|
138
|
+
@property
|
139
|
+
def ttl(self) -> float:
|
140
|
+
"""Time-to-live for this message."""
|
141
|
+
return cast(float, self.__dict__["_ttl"])
|
142
|
+
|
143
|
+
@ttl.setter
|
144
|
+
def ttl(self, value: float) -> None:
|
145
|
+
"""Set ttl."""
|
146
|
+
self.__dict__["_ttl"] = value
|
147
|
+
|
148
|
+
@property
|
149
|
+
def message_type(self) -> str:
|
150
|
+
"""A string that encodes the action to be executed on the receiving end."""
|
151
|
+
return cast(str, self.__dict__["_message_type"])
|
152
|
+
|
153
|
+
@message_type.setter
|
154
|
+
def message_type(self, value: str) -> None:
|
155
|
+
"""Set message_type."""
|
156
|
+
# Validate message type
|
157
|
+
if validate_legacy_message_type(value):
|
158
|
+
pass # Backward compatibility for legacy message types
|
159
|
+
elif not validate_message_type(value):
|
160
|
+
raise ValueError(
|
161
|
+
f"Invalid message type: '{value}'. "
|
162
|
+
"Expected format: '<category>' or '<category>.<action>', "
|
163
|
+
"where <category> must be 'train', 'evaluate', or 'query', "
|
164
|
+
"and <action> must be a valid Python identifier."
|
165
|
+
)
|
166
|
+
|
167
|
+
self.__dict__["_message_type"] = value
|
168
|
+
|
169
|
+
def __repr__(self) -> str:
|
170
|
+
"""Return a string representation of this instance."""
|
171
|
+
view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
|
172
|
+
return f"{self.__class__.__qualname__}({view})"
|
173
|
+
|
174
|
+
def __eq__(self, other: object) -> bool:
|
175
|
+
"""Compare two instances of the class."""
|
176
|
+
if not isinstance(other, self.__class__):
|
177
|
+
raise NotImplementedError
|
178
|
+
return self.__dict__ == other.__dict__
|
179
|
+
|
180
|
+
|
181
|
+
def validate_message_type(message_type: str) -> bool:
|
182
|
+
"""Validate if the message type is valid.
|
183
|
+
|
184
|
+
A valid message type format must be one of the following:
|
185
|
+
|
186
|
+
- "<category>"
|
187
|
+
- "<category>.<action>"
|
188
|
+
|
189
|
+
where `category` must be one of "train", "evaluate", or "query",
|
190
|
+
and `action` must be a valid Python identifier.
|
191
|
+
"""
|
192
|
+
# Check if conforming to the format "<category>"
|
193
|
+
valid_types = {
|
194
|
+
MessageType.TRAIN,
|
195
|
+
MessageType.EVALUATE,
|
196
|
+
MessageType.QUERY,
|
197
|
+
MessageType.SYSTEM,
|
198
|
+
}
|
199
|
+
if message_type in valid_types:
|
200
|
+
return True
|
201
|
+
|
202
|
+
# Check if conforming to the format "<category>.<action>"
|
203
|
+
if message_type.count(".") != 1:
|
204
|
+
return False
|
205
|
+
|
206
|
+
category, action = message_type.split(".")
|
207
|
+
if category in valid_types and action.isidentifier():
|
208
|
+
return True
|
209
|
+
|
210
|
+
return False
|
211
|
+
|
212
|
+
|
213
|
+
def validate_legacy_message_type(message_type: str) -> bool:
|
214
|
+
"""Validate if the legacy message type is valid."""
|
215
|
+
# Backward compatibility for legacy message types
|
216
|
+
if message_type in (
|
217
|
+
MessageTypeLegacy.GET_PARAMETERS,
|
218
|
+
MessageTypeLegacy.GET_PROPERTIES,
|
219
|
+
"reconnect",
|
220
|
+
):
|
221
|
+
return True
|
222
|
+
|
223
|
+
return False
|
flwr/client/__init__.py
CHANGED
@@ -15,8 +15,8 @@
|
|
15
15
|
"""Flower client."""
|
16
16
|
|
17
17
|
|
18
|
-
from .app import start_client as start_client
|
19
|
-
from .app import start_numpy_client as start_numpy_client
|
18
|
+
from ..compat.client.app import start_client as start_client # Deprecated
|
19
|
+
from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
|
20
20
|
from .client import Client as Client
|
21
21
|
from .client_app import ClientApp as ClientApp
|
22
22
|
from .numpy_client import NumPyClient as NumPyClient
|
flwr/client/client_app.py
CHANGED
@@ -20,6 +20,7 @@ from collections.abc import Iterator
|
|
20
20
|
from contextlib import contextmanager
|
21
21
|
from typing import Callable, Optional
|
22
22
|
|
23
|
+
from flwr.app.metadata import validate_message_type
|
23
24
|
from flwr.client.client import Client
|
24
25
|
from flwr.client.message_handler.message_handler import (
|
25
26
|
handle_legacy_message_from_msgtype,
|
@@ -28,7 +29,6 @@ from flwr.client.mod.utils import make_ffn
|
|
28
29
|
from flwr.client.typing import ClientFnExt, Mod
|
29
30
|
from flwr.common import Context, Message, MessageType
|
30
31
|
from flwr.common.logger import warn_deprecated_feature
|
31
|
-
from flwr.common.message import validate_message_type
|
32
32
|
|
33
33
|
from .typing import ClientAppCallable
|
34
34
|
|
flwr/client/clientapp/app.py
CHANGED
@@ -23,6 +23,7 @@ from typing import Optional
|
|
23
23
|
|
24
24
|
import grpc
|
25
25
|
|
26
|
+
from flwr.app.error import Error
|
26
27
|
from flwr.cli.install import install_from_fab
|
27
28
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
28
29
|
from flwr.common import Context, Message
|
@@ -32,7 +33,6 @@ from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS, ErrorCo
|
|
32
33
|
from flwr.common.exit import ExitCode, flwr_exit
|
33
34
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
34
35
|
from flwr.common.logger import log
|
35
|
-
from flwr.common.message import Error
|
36
36
|
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
37
37
|
from flwr.common.serde import (
|
38
38
|
context_from_proto,
|
@@ -25,13 +25,14 @@ from typing import Callable, Optional, Union, cast
|
|
25
25
|
import grpc
|
26
26
|
from cryptography.hazmat.primitives.asymmetric import ec
|
27
27
|
|
28
|
+
from flwr.app.metadata import Metadata
|
28
29
|
from flwr.client.message_handler.message_handler import validate_out_message
|
29
30
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
30
31
|
from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
|
31
32
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
32
33
|
from flwr.common.heartbeat import HeartbeatSender
|
33
34
|
from flwr.common.logger import log
|
34
|
-
from flwr.common.message import Message
|
35
|
+
from flwr.common.message import Message
|
35
36
|
from flwr.common.retry_invoker import RetryInvoker
|
36
37
|
from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
|
37
38
|
generate_key_pairs,
|
@@ -25,13 +25,14 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|
25
25
|
from google.protobuf.message import Message as GrpcMessage
|
26
26
|
from requests.exceptions import ConnectionError as RequestsConnectionError
|
27
27
|
|
28
|
+
from flwr.app.metadata import Metadata
|
28
29
|
from flwr.client.message_handler.message_handler import validate_out_message
|
29
30
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
30
31
|
from flwr.common.constant import HEARTBEAT_DEFAULT_INTERVAL
|
31
32
|
from flwr.common.exit import ExitCode, flwr_exit
|
32
33
|
from flwr.common.heartbeat import HeartbeatSender
|
33
34
|
from flwr.common.logger import log
|
34
|
-
from flwr.common.message import Message
|
35
|
+
from flwr.common.message import Message
|
35
36
|
from flwr.common.retry_invoker import RetryInvoker
|
36
37
|
from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
|
37
38
|
from flwr.common.typing import Fab, Run
|