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.
Files changed (38) hide show
  1. flwr/app/__init__.py +15 -0
  2. flwr/app/error.py +68 -0
  3. flwr/app/metadata.py +223 -0
  4. flwr/client/__init__.py +2 -2
  5. flwr/client/client_app.py +1 -1
  6. flwr/client/clientapp/app.py +1 -1
  7. flwr/client/grpc_rere_client/connection.py +2 -1
  8. flwr/client/rest_client/connection.py +2 -1
  9. flwr/client/start_client_internal.py +608 -0
  10. flwr/client/supernode/app.py +1 -1
  11. flwr/clientapp/__init__.py +15 -0
  12. flwr/common/__init__.py +2 -2
  13. flwr/common/inflatable_grpc_utils.py +97 -0
  14. flwr/common/message.py +87 -245
  15. flwr/common/record/array.py +1 -1
  16. flwr/common/record/configrecord.py +1 -1
  17. flwr/common/serde.py +9 -54
  18. flwr/common/serde_utils.py +50 -0
  19. flwr/compat/__init__.py +15 -0
  20. flwr/compat/client/__init__.py +15 -0
  21. flwr/{client → compat/client}/app.py +13 -11
  22. flwr/compat/common/__init__.py +15 -0
  23. flwr/compat/server/__init__.py +15 -0
  24. flwr/compat/simulation/__init__.py +15 -0
  25. flwr/server/superlink/fleet/vce/vce_api.py +1 -1
  26. flwr/server/superlink/linkstate/sqlite_linkstate.py +2 -6
  27. flwr/serverapp/__init__.py +15 -0
  28. flwr/supercore/__init__.py +15 -0
  29. flwr/superlink/__init__.py +15 -0
  30. flwr/supernode/__init__.py +15 -0
  31. flwr/{client → supernode}/nodestate/in_memory_nodestate.py +1 -1
  32. {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/METADATA +1 -1
  33. {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/RECORD +38 -23
  34. /flwr/{client → supernode}/nodestate/__init__.py +0 -0
  35. /flwr/{client → supernode}/nodestate/nodestate.py +0 -0
  36. /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
  37. {flwr_nightly-1.19.0.dev20250516.dist-info → flwr_nightly-1.19.0.dev20250521.dist-info}/WHEEL +0 -0
  38. {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
 
@@ -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, Metadata
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, Metadata
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