flwr 1.16.0__py3-none-any.whl → 1.18.0__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/__init__.py +1 -1
- flwr/cli/__init__.py +1 -1
- flwr/cli/app.py +21 -2
- flwr/cli/build.py +1 -1
- flwr/cli/cli_user_auth_interceptor.py +1 -1
- flwr/cli/config_utils.py +53 -17
- flwr/cli/example.py +1 -1
- flwr/cli/install.py +1 -1
- flwr/cli/log.py +1 -1
- flwr/cli/login/__init__.py +1 -1
- flwr/cli/login/login.py +12 -1
- flwr/cli/ls.py +1 -1
- flwr/cli/new/__init__.py +1 -1
- flwr/cli/new/new.py +4 -4
- flwr/cli/new/templates/__init__.py +1 -1
- flwr/cli/new/templates/app/__init__.py +1 -1
- flwr/cli/new/templates/app/code/__init__.py +1 -1
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +5 -5
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/__init__.py +1 -1
- flwr/cli/run/run.py +6 -10
- flwr/cli/stop.py +1 -1
- flwr/cli/utils.py +11 -12
- flwr/client/__init__.py +1 -1
- flwr/client/app.py +58 -56
- flwr/client/client.py +1 -1
- flwr/client/client_app.py +231 -166
- flwr/client/clientapp/__init__.py +1 -1
- flwr/client/clientapp/app.py +3 -3
- flwr/client/clientapp/clientappio_servicer.py +1 -1
- flwr/client/clientapp/utils.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +1 -1
- flwr/client/grpc_adapter_client/__init__.py +1 -1
- flwr/client/grpc_adapter_client/connection.py +1 -1
- flwr/client/grpc_client/__init__.py +1 -1
- flwr/client/grpc_client/connection.py +37 -34
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +1 -1
- flwr/client/grpc_rere_client/connection.py +1 -1
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/heartbeat.py +1 -1
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +28 -28
- flwr/client/mod/__init__.py +3 -3
- flwr/client/mod/centraldp_mods.py +8 -8
- flwr/client/mod/comms_mods.py +17 -23
- flwr/client/mod/localdp_mod.py +10 -10
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +32 -32
- flwr/client/mod/utils.py +1 -1
- flwr/client/nodestate/__init__.py +1 -1
- flwr/client/nodestate/in_memory_nodestate.py +1 -1
- flwr/client/nodestate/nodestate.py +1 -1
- flwr/client/nodestate/nodestate_factory.py +1 -1
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +1 -1
- flwr/client/run_info_store.py +3 -3
- flwr/client/supernode/__init__.py +1 -1
- flwr/client/supernode/app.py +1 -1
- flwr/client/typing.py +1 -1
- flwr/common/__init__.py +13 -5
- flwr/common/address.py +1 -1
- flwr/common/args.py +1 -1
- flwr/common/auth_plugin/__init__.py +1 -1
- flwr/common/auth_plugin/auth_plugin.py +1 -1
- flwr/common/config.py +5 -5
- flwr/common/constant.py +7 -7
- flwr/common/context.py +5 -5
- flwr/common/date.py +1 -1
- flwr/common/differential_privacy.py +1 -1
- flwr/common/differential_privacy_constants.py +1 -1
- flwr/common/dp.py +1 -1
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/exit/exit.py +6 -6
- flwr/common/exit_handlers.py +1 -1
- flwr/common/grpc.py +1 -1
- flwr/common/logger.py +3 -3
- flwr/common/message.py +344 -102
- flwr/common/object_ref.py +1 -1
- flwr/common/parameter.py +1 -1
- flwr/common/pyproject.py +1 -1
- flwr/common/record/__init__.py +9 -5
- flwr/common/record/arrayrecord.py +626 -0
- flwr/common/record/{configsrecord.py → configrecord.py} +83 -37
- flwr/common/record/conversion_utils.py +2 -2
- flwr/common/record/{metricsrecord.py → metricrecord.py} +90 -44
- flwr/common/record/recorddict.py +337 -0
- flwr/common/record/typeddict.py +1 -1
- flwr/common/recorddict_compat.py +410 -0
- flwr/common/retry_invoker.py +10 -10
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +52 -30
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
- flwr/common/secure_aggregation/quantization.py +1 -1
- flwr/common/secure_aggregation/secaggplus_constants.py +2 -2
- flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
- flwr/common/serde.py +67 -72
- flwr/common/telemetry.py +2 -2
- flwr/common/typing.py +9 -9
- flwr/common/version.py +1 -1
- flwr/proto/__init__.py +1 -1
- flwr/proto/exec_pb2.py +3 -3
- flwr/proto/exec_pb2.pyi +3 -3
- flwr/proto/message_pb2.py +12 -12
- flwr/proto/message_pb2.pyi +9 -9
- flwr/proto/recorddict_pb2.py +70 -0
- flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
- flwr/proto/run_pb2.py +31 -31
- flwr/proto/run_pb2.pyi +3 -3
- flwr/server/__init__.py +4 -2
- flwr/server/app.py +67 -12
- flwr/server/client_manager.py +1 -1
- flwr/server/client_proxy.py +1 -1
- flwr/server/compat/__init__.py +3 -3
- flwr/server/compat/app.py +12 -12
- flwr/server/compat/app_utils.py +17 -17
- flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +39 -39
- flwr/server/compat/legacy_context.py +1 -1
- flwr/server/criterion.py +1 -1
- flwr/server/fleet_event_log_interceptor.py +94 -0
- flwr/server/{driver → grid}/__init__.py +8 -7
- flwr/server/{driver/driver.py → grid/grid.py} +48 -19
- flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +87 -64
- flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +24 -34
- flwr/server/history.py +1 -1
- flwr/server/run_serverapp.py +5 -5
- flwr/server/server.py +1 -1
- flwr/server/server_app.py +98 -71
- flwr/server/server_config.py +1 -1
- flwr/server/serverapp/__init__.py +1 -1
- flwr/server/serverapp/app.py +11 -11
- flwr/server/serverapp_components.py +1 -1
- flwr/server/strategy/__init__.py +1 -1
- flwr/server/strategy/aggregate.py +1 -1
- flwr/server/strategy/bulyan.py +2 -2
- flwr/server/strategy/dp_adaptive_clipping.py +17 -17
- flwr/server/strategy/dp_fixed_clipping.py +17 -17
- flwr/server/strategy/dpfedavg_adaptive.py +1 -1
- flwr/server/strategy/dpfedavg_fixed.py +1 -1
- flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
- flwr/server/strategy/fedadagrad.py +1 -1
- flwr/server/strategy/fedadam.py +1 -1
- flwr/server/strategy/fedavg.py +1 -1
- flwr/server/strategy/fedavg_android.py +1 -1
- flwr/server/strategy/fedavgm.py +1 -1
- flwr/server/strategy/fedmedian.py +1 -1
- flwr/server/strategy/fedopt.py +1 -1
- flwr/server/strategy/fedprox.py +1 -1
- flwr/server/strategy/fedtrimmedavg.py +1 -1
- flwr/server/strategy/fedxgb_bagging.py +1 -1
- flwr/server/strategy/fedxgb_cyclic.py +1 -1
- flwr/server/strategy/fedxgb_nn_avg.py +3 -2
- flwr/server/strategy/fedyogi.py +1 -1
- flwr/server/strategy/krum.py +1 -1
- flwr/server/strategy/qfedavg.py +1 -1
- flwr/server/strategy/strategy.py +1 -1
- flwr/server/superlink/__init__.py +1 -1
- flwr/server/superlink/ffs/__init__.py +1 -1
- flwr/server/superlink/ffs/disk_ffs.py +1 -1
- flwr/server/superlink/ffs/ffs.py +1 -1
- flwr/server/superlink/ffs/ffs_factory.py +1 -1
- flwr/server/superlink/fleet/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +1 -1
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -1
- flwr/server/superlink/fleet/vce/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
- flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -3
- flwr/server/superlink/fleet/vce/vce_api.py +2 -4
- flwr/server/superlink/linkstate/__init__.py +1 -1
- flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -9
- flwr/server/superlink/linkstate/linkstate.py +5 -5
- flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +62 -28
- flwr/server/superlink/linkstate/utils.py +94 -28
- flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
- flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
- flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +4 -4
- flwr/server/superlink/simulation/__init__.py +1 -1
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +3 -3
- flwr/server/superlink/utils.py +1 -1
- flwr/server/typing.py +4 -4
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +1 -1
- flwr/server/utils/validator.py +5 -5
- flwr/server/workflow/__init__.py +1 -1
- flwr/server/workflow/constant.py +1 -1
- flwr/server/workflow/default_workflows.py +49 -58
- flwr/server/workflow/secure_aggregation/__init__.py +1 -1
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +49 -51
- flwr/simulation/__init__.py +1 -1
- flwr/simulation/app.py +3 -3
- flwr/simulation/legacy_app.py +1 -1
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +5 -3
- flwr/simulation/ray_transport/ray_client_proxy.py +35 -33
- flwr/simulation/ray_transport/utils.py +1 -1
- flwr/simulation/run_simulation.py +17 -17
- flwr/simulation/simulationio_connection.py +1 -1
- flwr/superexec/__init__.py +1 -1
- flwr/superexec/app.py +1 -1
- flwr/superexec/deployment.py +5 -5
- flwr/superexec/exec_event_log_interceptor.py +135 -0
- flwr/superexec/exec_grpc.py +11 -5
- flwr/superexec/exec_servicer.py +3 -3
- flwr/superexec/exec_user_auth_interceptor.py +19 -3
- flwr/superexec/executor.py +4 -4
- flwr/superexec/simulation.py +4 -4
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/METADATA +3 -3
- flwr-1.18.0.dist-info/RECORD +332 -0
- flwr/common/record/parametersrecord.py +0 -339
- flwr/common/record/recordset.py +0 -209
- flwr/common/recordset_compat.py +0 -418
- flwr/proto/recordset_pb2.py +0 -70
- flwr-1.16.0.dist-info/LICENSE +0 -202
- flwr-1.16.0.dist-info/RECORD +0 -331
- /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
- /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/WHEEL +0 -0
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/entry_points.txt +0 -0
flwr/common/message.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -17,19 +17,47 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import time
|
|
21
20
|
from logging import WARNING
|
|
22
|
-
from typing import Optional, cast
|
|
21
|
+
from typing import Any, Optional, cast, overload
|
|
23
22
|
|
|
24
|
-
from .
|
|
23
|
+
from flwr.common.date import now
|
|
24
|
+
from flwr.common.logger import warn_deprecated_feature
|
|
25
|
+
|
|
26
|
+
from .constant import MESSAGE_TTL_TOLERANCE, MessageType, MessageTypeLegacy
|
|
25
27
|
from .logger import log
|
|
26
|
-
from .record import
|
|
28
|
+
from .record import RecordDict
|
|
27
29
|
|
|
28
30
|
DEFAULT_TTL = 43200 # This is 12 hours
|
|
31
|
+
MESSAGE_INIT_ERROR_MESSAGE = (
|
|
32
|
+
"Invalid arguments for Message. Expected one of the documented "
|
|
33
|
+
"signatures: Message(content: RecordDict, dst_node_id: int, message_type: str,"
|
|
34
|
+
" *, [ttl: float, group_id: str]) or Message(content: RecordDict | error: Error,"
|
|
35
|
+
" *, reply_to: Message, [ttl: float])."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _WarningTracker:
|
|
40
|
+
"""A class to track warnings for deprecated properties."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
# These variables are used to ensure that the deprecation warnings
|
|
44
|
+
# for the deprecated properties/class are logged only once.
|
|
45
|
+
self.create_error_reply_logged = False
|
|
46
|
+
self.create_reply_logged = False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_warning_tracker = _WarningTracker()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MessageInitializationError(TypeError):
|
|
53
|
+
"""Error raised when initializing a message with invalid arguments."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, message: str | None = None) -> None:
|
|
56
|
+
super().__init__(message or MESSAGE_INIT_ERROR_MESSAGE)
|
|
29
57
|
|
|
30
58
|
|
|
31
59
|
class Metadata: # pylint: disable=too-many-instance-attributes
|
|
32
|
-
"""
|
|
60
|
+
"""The class representing metadata associated with the current message.
|
|
33
61
|
|
|
34
62
|
Parameters
|
|
35
63
|
----------
|
|
@@ -41,11 +69,13 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
41
69
|
An identifier for the node sending this message.
|
|
42
70
|
dst_node_id : int
|
|
43
71
|
An identifier for the node receiving this message.
|
|
44
|
-
|
|
45
|
-
An identifier for the message this message
|
|
72
|
+
reply_to_message_id : str
|
|
73
|
+
An identifier for the message to which this message is a reply.
|
|
46
74
|
group_id : str
|
|
47
75
|
An identifier for grouping messages. In some settings,
|
|
48
76
|
this is used as the FL round.
|
|
77
|
+
created_at : float
|
|
78
|
+
Unix timestamp when the message was created.
|
|
49
79
|
ttl : float
|
|
50
80
|
Time-to-live for this message in seconds.
|
|
51
81
|
message_type : str
|
|
@@ -59,8 +89,9 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
59
89
|
message_id: str,
|
|
60
90
|
src_node_id: int,
|
|
61
91
|
dst_node_id: int,
|
|
62
|
-
|
|
92
|
+
reply_to_message_id: str,
|
|
63
93
|
group_id: str,
|
|
94
|
+
created_at: float,
|
|
64
95
|
ttl: float,
|
|
65
96
|
message_type: str,
|
|
66
97
|
) -> None:
|
|
@@ -69,12 +100,14 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
69
100
|
"_message_id": message_id,
|
|
70
101
|
"_src_node_id": src_node_id,
|
|
71
102
|
"_dst_node_id": dst_node_id,
|
|
72
|
-
"
|
|
103
|
+
"_reply_to_message_id": reply_to_message_id,
|
|
73
104
|
"_group_id": group_id,
|
|
105
|
+
"_created_at": created_at,
|
|
74
106
|
"_ttl": ttl,
|
|
75
107
|
"_message_type": message_type,
|
|
76
108
|
}
|
|
77
109
|
self.__dict__.update(var_dict)
|
|
110
|
+
self.message_type = message_type # Trigger validation
|
|
78
111
|
|
|
79
112
|
@property
|
|
80
113
|
def run_id(self) -> int:
|
|
@@ -92,9 +125,9 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
92
125
|
return cast(int, self.__dict__["_src_node_id"])
|
|
93
126
|
|
|
94
127
|
@property
|
|
95
|
-
def
|
|
96
|
-
"""An identifier for the message this message
|
|
97
|
-
return cast(str, self.__dict__["
|
|
128
|
+
def reply_to_message_id(self) -> str:
|
|
129
|
+
"""An identifier for the message to which this message is a reply."""
|
|
130
|
+
return cast(str, self.__dict__["_reply_to_message_id"])
|
|
98
131
|
|
|
99
132
|
@property
|
|
100
133
|
def dst_node_id(self) -> int:
|
|
@@ -123,7 +156,7 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
123
156
|
|
|
124
157
|
@created_at.setter
|
|
125
158
|
def created_at(self, value: float) -> None:
|
|
126
|
-
"""Set creation timestamp
|
|
159
|
+
"""Set creation timestamp of this message."""
|
|
127
160
|
self.__dict__["_created_at"] = value
|
|
128
161
|
|
|
129
162
|
@property
|
|
@@ -154,6 +187,17 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
154
187
|
@message_type.setter
|
|
155
188
|
def message_type(self, value: str) -> None:
|
|
156
189
|
"""Set message_type."""
|
|
190
|
+
# Validate message type
|
|
191
|
+
if validate_legacy_message_type(value):
|
|
192
|
+
pass # Backward compatibility for legacy message types
|
|
193
|
+
elif not validate_message_type(value):
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Invalid message type: '{value}'. "
|
|
196
|
+
"Expected format: '<category>' or '<category>.<action>', "
|
|
197
|
+
"where <category> must be 'train', 'evaluate', or 'query', "
|
|
198
|
+
"and <action> must be a valid Python identifier."
|
|
199
|
+
)
|
|
200
|
+
|
|
157
201
|
self.__dict__["_message_type"] = value
|
|
158
202
|
|
|
159
203
|
def __repr__(self) -> str:
|
|
@@ -169,7 +213,7 @@ class Metadata: # pylint: disable=too-many-instance-attributes
|
|
|
169
213
|
|
|
170
214
|
|
|
171
215
|
class Error:
|
|
172
|
-
"""
|
|
216
|
+
"""The class storing information about an error that occurred.
|
|
173
217
|
|
|
174
218
|
Parameters
|
|
175
219
|
----------
|
|
@@ -209,31 +253,148 @@ class Error:
|
|
|
209
253
|
|
|
210
254
|
|
|
211
255
|
class Message:
|
|
212
|
-
"""
|
|
256
|
+
"""Represents a message exchanged between ClientApp and ServerApp.
|
|
257
|
+
|
|
258
|
+
This class encapsulates the payload and metadata necessary for communication
|
|
259
|
+
between a ClientApp and a ServerApp.
|
|
213
260
|
|
|
214
261
|
Parameters
|
|
215
262
|
----------
|
|
216
|
-
|
|
217
|
-
A dataclass including information about the message to be executed.
|
|
218
|
-
content : Optional[RecordSet]
|
|
263
|
+
content : Optional[RecordDict] (default: None)
|
|
219
264
|
Holds records either sent by another entity (e.g. sent by the server-side
|
|
220
265
|
logic to a client, or vice-versa) or that will be sent to it.
|
|
221
|
-
error : Optional[Error]
|
|
266
|
+
error : Optional[Error] (default: None)
|
|
222
267
|
A dataclass that captures information about an error that took place
|
|
223
268
|
when processing another message.
|
|
269
|
+
dst_node_id : Optional[int] (default: None)
|
|
270
|
+
An identifier for the node receiving this message.
|
|
271
|
+
message_type : Optional[str] (default: None)
|
|
272
|
+
A string that encodes the action to be executed on
|
|
273
|
+
the receiving end.
|
|
274
|
+
ttl : Optional[float] (default: None)
|
|
275
|
+
Time-to-live (TTL) for this message in seconds. If `None` (default),
|
|
276
|
+
the TTL is set to 43,200 seconds (12 hours).
|
|
277
|
+
group_id : Optional[str] (default: None)
|
|
278
|
+
An identifier for grouping messages. In some settings, this is used as
|
|
279
|
+
the FL round.
|
|
280
|
+
reply_to : Optional[Message] (default: None)
|
|
281
|
+
The instruction message to which this message is a reply. This message does
|
|
282
|
+
not retain the original message's content but derives its metadata from it.
|
|
224
283
|
"""
|
|
225
284
|
|
|
226
|
-
|
|
285
|
+
@overload
|
|
286
|
+
def __init__( # pylint: disable=too-many-arguments # noqa: E704
|
|
227
287
|
self,
|
|
228
|
-
|
|
229
|
-
|
|
288
|
+
content: RecordDict,
|
|
289
|
+
dst_node_id: int,
|
|
290
|
+
message_type: str,
|
|
291
|
+
*,
|
|
292
|
+
ttl: float | None = None,
|
|
293
|
+
group_id: str | None = None,
|
|
294
|
+
) -> None: ...
|
|
295
|
+
|
|
296
|
+
@overload
|
|
297
|
+
def __init__( # noqa: E704
|
|
298
|
+
self, content: RecordDict, *, reply_to: Message, ttl: float | None = None
|
|
299
|
+
) -> None: ...
|
|
300
|
+
|
|
301
|
+
@overload
|
|
302
|
+
def __init__( # noqa: E704
|
|
303
|
+
self, error: Error, *, reply_to: Message, ttl: float | None = None
|
|
304
|
+
) -> None: ...
|
|
305
|
+
|
|
306
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
307
|
+
self,
|
|
308
|
+
*args: Any,
|
|
309
|
+
dst_node_id: int | None = None,
|
|
310
|
+
message_type: str | None = None,
|
|
311
|
+
content: RecordDict | None = None,
|
|
230
312
|
error: Error | None = None,
|
|
313
|
+
ttl: float | None = None,
|
|
314
|
+
group_id: str | None = None,
|
|
315
|
+
reply_to: Message | None = None,
|
|
316
|
+
metadata: Metadata | None = None,
|
|
231
317
|
) -> None:
|
|
232
|
-
|
|
233
|
-
|
|
318
|
+
# Set positional arguments
|
|
319
|
+
content, error, dst_node_id, message_type = _extract_positional_args(
|
|
320
|
+
*args,
|
|
321
|
+
content=content,
|
|
322
|
+
error=error,
|
|
323
|
+
dst_node_id=dst_node_id,
|
|
324
|
+
message_type=message_type,
|
|
325
|
+
)
|
|
326
|
+
_check_arg_types(
|
|
327
|
+
dst_node_id=dst_node_id,
|
|
328
|
+
message_type=message_type,
|
|
329
|
+
content=content,
|
|
330
|
+
error=error,
|
|
331
|
+
ttl=ttl,
|
|
332
|
+
group_id=group_id,
|
|
333
|
+
reply_to=reply_to,
|
|
334
|
+
metadata=metadata,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Set metadata directly (This is for internal use only)
|
|
338
|
+
if metadata is not None:
|
|
339
|
+
# When metadata is set, all other arguments must be None,
|
|
340
|
+
# except `content`, `error`, or `content_or_error`
|
|
341
|
+
if any(
|
|
342
|
+
x is not None
|
|
343
|
+
for x in [dst_node_id, message_type, ttl, group_id, reply_to]
|
|
344
|
+
):
|
|
345
|
+
raise MessageInitializationError(
|
|
346
|
+
f"Invalid arguments for {Message.__qualname__}. "
|
|
347
|
+
"Expected only `metadata` to be set when creating a message "
|
|
348
|
+
"with provided metadata."
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Create metadata for an instruction message
|
|
352
|
+
elif reply_to is None:
|
|
353
|
+
# Check arguments
|
|
354
|
+
# `content`, `dst_node_id` and `message_type` must be set
|
|
355
|
+
if not (
|
|
356
|
+
isinstance(content, RecordDict)
|
|
357
|
+
and isinstance(dst_node_id, int)
|
|
358
|
+
and isinstance(message_type, str)
|
|
359
|
+
):
|
|
360
|
+
raise MessageInitializationError()
|
|
361
|
+
|
|
362
|
+
# Set metadata
|
|
363
|
+
metadata = Metadata(
|
|
364
|
+
run_id=0, # Will be set before pushed
|
|
365
|
+
message_id="", # Will be set by the SuperLink
|
|
366
|
+
src_node_id=0, # Will be set before pushed
|
|
367
|
+
dst_node_id=dst_node_id,
|
|
368
|
+
# Instruction messages do not reply to any message
|
|
369
|
+
reply_to_message_id="",
|
|
370
|
+
group_id=group_id or "",
|
|
371
|
+
created_at=now().timestamp(),
|
|
372
|
+
ttl=ttl or DEFAULT_TTL,
|
|
373
|
+
message_type=message_type,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Create metadata for a reply message
|
|
377
|
+
else:
|
|
378
|
+
# Check arguments
|
|
379
|
+
# `dst_node_id`, `message_type` and `group_id` must not be set
|
|
380
|
+
if any(x is not None for x in [dst_node_id, message_type, group_id]):
|
|
381
|
+
raise MessageInitializationError()
|
|
382
|
+
|
|
383
|
+
# Set metadata
|
|
384
|
+
current = now().timestamp()
|
|
385
|
+
metadata = Metadata(
|
|
386
|
+
run_id=reply_to.metadata.run_id,
|
|
387
|
+
message_id="", # Will be set by the SuperLink
|
|
388
|
+
src_node_id=reply_to.metadata.dst_node_id,
|
|
389
|
+
dst_node_id=reply_to.metadata.src_node_id,
|
|
390
|
+
reply_to_message_id=reply_to.metadata.message_id,
|
|
391
|
+
group_id=reply_to.metadata.group_id,
|
|
392
|
+
created_at=current,
|
|
393
|
+
ttl=_limit_reply_ttl(current, ttl, reply_to),
|
|
394
|
+
message_type=reply_to.metadata.message_type,
|
|
395
|
+
)
|
|
234
396
|
|
|
235
|
-
metadata.
|
|
236
|
-
metadata.delivered_at = ""
|
|
397
|
+
metadata.delivered_at = "" # Backward compatibility
|
|
237
398
|
var_dict = {
|
|
238
399
|
"_metadata": metadata,
|
|
239
400
|
"_content": content,
|
|
@@ -247,17 +408,17 @@ class Message:
|
|
|
247
408
|
return cast(Metadata, self.__dict__["_metadata"])
|
|
248
409
|
|
|
249
410
|
@property
|
|
250
|
-
def content(self) ->
|
|
411
|
+
def content(self) -> RecordDict:
|
|
251
412
|
"""The content of this message."""
|
|
252
413
|
if self.__dict__["_content"] is None:
|
|
253
414
|
raise ValueError(
|
|
254
415
|
"Message content is None. Use <message>.has_content() "
|
|
255
416
|
"to check if a message has content."
|
|
256
417
|
)
|
|
257
|
-
return cast(
|
|
418
|
+
return cast(RecordDict, self.__dict__["_content"])
|
|
258
419
|
|
|
259
420
|
@content.setter
|
|
260
|
-
def content(self, value:
|
|
421
|
+
def content(self, value: RecordDict) -> None:
|
|
261
422
|
"""Set content."""
|
|
262
423
|
if self.__dict__["_error"] is None:
|
|
263
424
|
self.__dict__["_content"] = value
|
|
@@ -308,33 +469,27 @@ class Message:
|
|
|
308
469
|
message : Message
|
|
309
470
|
A Message containing only the relevant error and metadata.
|
|
310
471
|
"""
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if ttl is None:
|
|
318
|
-
# Set TTL equal to the remaining time for the received message to expire
|
|
319
|
-
ttl = self.metadata.ttl - (
|
|
320
|
-
message.metadata.created_at - self.metadata.created_at
|
|
472
|
+
if not _warning_tracker.create_error_reply_logged:
|
|
473
|
+
_warning_tracker.create_error_reply_logged = True
|
|
474
|
+
warn_deprecated_feature(
|
|
475
|
+
"`Message.create_error_reply` is deprecated. "
|
|
476
|
+
"Instead of calling `some_message.create_error_reply(some_error, "
|
|
477
|
+
"ttl=...)`, use `Message(some_error, reply_to=some_message, ttl=...)`."
|
|
321
478
|
)
|
|
322
|
-
|
|
479
|
+
if ttl is not None:
|
|
480
|
+
return Message(error, reply_to=self, ttl=ttl)
|
|
481
|
+
return Message(error, reply_to=self)
|
|
323
482
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return message
|
|
327
|
-
|
|
328
|
-
def create_reply(self, content: RecordSet, ttl: float | None = None) -> Message:
|
|
483
|
+
def create_reply(self, content: RecordDict, ttl: float | None = None) -> Message:
|
|
329
484
|
"""Create a reply to this message with specified content and TTL.
|
|
330
485
|
|
|
331
486
|
The method generates a new `Message` as a reply to this message.
|
|
332
487
|
It inherits 'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from
|
|
333
|
-
this message and sets '
|
|
488
|
+
this message and sets 'reply_to_message_id' to the ID of this message.
|
|
334
489
|
|
|
335
490
|
Parameters
|
|
336
491
|
----------
|
|
337
|
-
content :
|
|
492
|
+
content : RecordDict
|
|
338
493
|
The content for the reply message.
|
|
339
494
|
ttl : Optional[float] (default: None)
|
|
340
495
|
Time-to-live for this message in seconds. If unset, it will be set based
|
|
@@ -348,25 +503,16 @@ class Message:
|
|
|
348
503
|
Message
|
|
349
504
|
A new `Message` instance representing the reply.
|
|
350
505
|
"""
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
content=content,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
if ttl is None:
|
|
361
|
-
# Set TTL equal to the remaining time for the received message to expire
|
|
362
|
-
ttl = self.metadata.ttl - (
|
|
363
|
-
message.metadata.created_at - self.metadata.created_at
|
|
506
|
+
if not _warning_tracker.create_reply_logged:
|
|
507
|
+
_warning_tracker.create_reply_logged = True
|
|
508
|
+
warn_deprecated_feature(
|
|
509
|
+
"`Message.create_reply` is deprecated. "
|
|
510
|
+
"Instead of calling `some_message.create_reply(some_content, ttl=...)`"
|
|
511
|
+
", use `Message(some_content, reply_to=some_message, ttl=...)`."
|
|
364
512
|
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
self
|
|
368
|
-
|
|
369
|
-
return message
|
|
513
|
+
if ttl is not None:
|
|
514
|
+
return Message(content, reply_to=self, ttl=ttl)
|
|
515
|
+
return Message(content, reply_to=self)
|
|
370
516
|
|
|
371
517
|
def __repr__(self) -> str:
|
|
372
518
|
"""Return a string representation of this instance."""
|
|
@@ -379,41 +525,137 @@ class Message:
|
|
|
379
525
|
)
|
|
380
526
|
return f"{self.__class__.__qualname__}({view})"
|
|
381
527
|
|
|
382
|
-
def _limit_message_res_ttl(self, message: Message) -> None:
|
|
383
|
-
"""Limit the TTL of the provided Message to not exceed the expiration time of
|
|
384
|
-
this Message it replies to.
|
|
385
528
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
529
|
+
def make_message(
|
|
530
|
+
metadata: Metadata, content: RecordDict | None = None, error: Error | None = None
|
|
531
|
+
) -> Message:
|
|
532
|
+
"""Create a message with the provided metadata, content, and error."""
|
|
533
|
+
return Message(metadata=metadata, content=content, error=error) # type: ignore
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _limit_reply_ttl(
|
|
537
|
+
current: float, reply_ttl: float | None, reply_to: Message
|
|
538
|
+
) -> float:
|
|
539
|
+
"""Limit the TTL of a reply message such that it does exceed the expiration time of
|
|
540
|
+
the message it replies to."""
|
|
541
|
+
# Calculate the maximum allowed TTL
|
|
542
|
+
max_allowed_ttl = reply_to.metadata.created_at + reply_to.metadata.ttl - current
|
|
543
|
+
|
|
544
|
+
if reply_ttl is not None and reply_ttl - max_allowed_ttl > MESSAGE_TTL_TOLERANCE:
|
|
545
|
+
log(
|
|
546
|
+
WARNING,
|
|
547
|
+
"The reply TTL of %.2f seconds exceeded the "
|
|
548
|
+
"allowed maximum of %.2f seconds. "
|
|
549
|
+
"The TTL has been updated to the allowed maximum.",
|
|
550
|
+
reply_ttl,
|
|
551
|
+
max_allowed_ttl,
|
|
394
552
|
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
553
|
+
return max_allowed_ttl
|
|
554
|
+
|
|
555
|
+
return reply_ttl or max_allowed_ttl
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _extract_positional_args(
|
|
559
|
+
*args: Any,
|
|
560
|
+
content: RecordDict | None,
|
|
561
|
+
error: Error | None,
|
|
562
|
+
dst_node_id: int | None,
|
|
563
|
+
message_type: str | None,
|
|
564
|
+
) -> tuple[RecordDict | None, Error | None, int | None, str | None]:
|
|
565
|
+
"""Extract positional arguments for the `Message` constructor."""
|
|
566
|
+
content_or_error = args[0] if args else None
|
|
567
|
+
if len(args) > 1:
|
|
568
|
+
if dst_node_id is not None:
|
|
569
|
+
raise MessageInitializationError()
|
|
570
|
+
dst_node_id = args[1]
|
|
571
|
+
if len(args) > 2:
|
|
572
|
+
if message_type is not None:
|
|
573
|
+
raise MessageInitializationError()
|
|
574
|
+
message_type = args[2]
|
|
575
|
+
if len(args) > 3:
|
|
576
|
+
raise MessageInitializationError()
|
|
577
|
+
|
|
578
|
+
# One and only one of `content_or_error`, `content` and `error` must be set
|
|
579
|
+
if sum(x is not None for x in [content_or_error, content, error]) != 1:
|
|
580
|
+
raise MessageInitializationError()
|
|
581
|
+
|
|
582
|
+
# Set `content` or `error` based on `content_or_error`
|
|
583
|
+
if content_or_error is not None: # This means `content` and `error` are None
|
|
584
|
+
if isinstance(content_or_error, RecordDict):
|
|
585
|
+
content = content_or_error
|
|
586
|
+
elif isinstance(content_or_error, Error):
|
|
587
|
+
error = content_or_error
|
|
588
|
+
else:
|
|
589
|
+
raise MessageInitializationError()
|
|
590
|
+
return content, error, dst_node_id, message_type
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _check_arg_types( # pylint: disable=too-many-arguments, R0917
|
|
594
|
+
dst_node_id: int | None = None,
|
|
595
|
+
message_type: str | None = None,
|
|
596
|
+
content: RecordDict | None = None,
|
|
597
|
+
error: Error | None = None,
|
|
598
|
+
ttl: float | None = None,
|
|
599
|
+
group_id: str | None = None,
|
|
600
|
+
reply_to: Message | None = None,
|
|
601
|
+
metadata: Metadata | None = None,
|
|
602
|
+
) -> None:
|
|
603
|
+
"""Check argument types for the `Message` constructor."""
|
|
604
|
+
# pylint: disable=too-many-boolean-expressions
|
|
605
|
+
if (
|
|
606
|
+
(dst_node_id is None or isinstance(dst_node_id, int))
|
|
607
|
+
and (message_type is None or isinstance(message_type, str))
|
|
608
|
+
and (content is None or isinstance(content, RecordDict))
|
|
609
|
+
and (error is None or isinstance(error, Error))
|
|
610
|
+
and (ttl is None or isinstance(ttl, (int, float)))
|
|
611
|
+
and (group_id is None or isinstance(group_id, str))
|
|
612
|
+
and (reply_to is None or isinstance(reply_to, Message))
|
|
613
|
+
and (metadata is None or isinstance(metadata, Metadata))
|
|
614
|
+
):
|
|
615
|
+
return
|
|
616
|
+
raise MessageInitializationError()
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def validate_message_type(message_type: str) -> bool:
|
|
620
|
+
"""Validate if the message type is valid.
|
|
621
|
+
|
|
622
|
+
A valid message type format must be one of the following:
|
|
623
|
+
|
|
624
|
+
- "<category>"
|
|
625
|
+
- "<category>.<action>"
|
|
626
|
+
|
|
627
|
+
where `category` must be one of "train", "evaluate", or "query",
|
|
628
|
+
and `action` must be a valid Python identifier.
|
|
629
|
+
"""
|
|
630
|
+
# Check if conforming to the format "<category>"
|
|
631
|
+
valid_types = {
|
|
632
|
+
MessageType.TRAIN,
|
|
633
|
+
MessageType.EVALUATE,
|
|
634
|
+
MessageType.QUERY,
|
|
635
|
+
MessageType.SYSTEM,
|
|
636
|
+
}
|
|
637
|
+
if message_type in valid_types:
|
|
638
|
+
return True
|
|
639
|
+
|
|
640
|
+
# Check if conforming to the format "<category>.<action>"
|
|
641
|
+
if message_type.count(".") != 1:
|
|
642
|
+
return False
|
|
643
|
+
|
|
644
|
+
category, action = message_type.split(".")
|
|
645
|
+
if category in valid_types and action.isidentifier():
|
|
646
|
+
return True
|
|
647
|
+
|
|
648
|
+
return False
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def validate_legacy_message_type(message_type: str) -> bool:
|
|
652
|
+
"""Validate if the legacy message type is valid."""
|
|
653
|
+
# Backward compatibility for legacy message types
|
|
654
|
+
if message_type in (
|
|
655
|
+
MessageTypeLegacy.GET_PARAMETERS,
|
|
656
|
+
MessageTypeLegacy.GET_PROPERTIES,
|
|
657
|
+
"reconnect",
|
|
658
|
+
):
|
|
659
|
+
return True
|
|
660
|
+
|
|
661
|
+
return False
|
flwr/common/object_ref.py
CHANGED
flwr/common/parameter.py
CHANGED
flwr/common/pyproject.py
CHANGED
flwr/common/record/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -15,17 +15,21 @@
|
|
|
15
15
|
"""Record APIs."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from .
|
|
18
|
+
from .arrayrecord import Array, ArrayRecord, ParametersRecord
|
|
19
|
+
from .configrecord import ConfigRecord, ConfigsRecord
|
|
19
20
|
from .conversion_utils import array_from_numpy
|
|
20
|
-
from .
|
|
21
|
-
from .
|
|
22
|
-
from .recordset import RecordSet
|
|
21
|
+
from .metricrecord import MetricRecord, MetricsRecord
|
|
22
|
+
from .recorddict import RecordDict, RecordSet
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
"Array",
|
|
26
|
+
"ArrayRecord",
|
|
27
|
+
"ConfigRecord",
|
|
26
28
|
"ConfigsRecord",
|
|
29
|
+
"MetricRecord",
|
|
27
30
|
"MetricsRecord",
|
|
28
31
|
"ParametersRecord",
|
|
32
|
+
"RecordDict",
|
|
29
33
|
"RecordSet",
|
|
30
34
|
"array_from_numpy",
|
|
31
35
|
]
|