flwr 1.17.0__py3-none-any.whl → 1.19.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.
Files changed (286) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +15 -0
  3. flwr/app/error.py +68 -0
  4. flwr/app/metadata.py +223 -0
  5. flwr/cli/__init__.py +1 -1
  6. flwr/cli/app.py +21 -2
  7. flwr/cli/build.py +83 -58
  8. flwr/cli/cli_user_auth_interceptor.py +1 -1
  9. flwr/cli/config_utils.py +53 -17
  10. flwr/cli/example.py +1 -1
  11. flwr/cli/install.py +1 -1
  12. flwr/cli/log.py +4 -4
  13. flwr/cli/login/__init__.py +1 -1
  14. flwr/cli/login/login.py +15 -8
  15. flwr/cli/ls.py +16 -37
  16. flwr/cli/new/__init__.py +1 -1
  17. flwr/cli/new/new.py +4 -4
  18. flwr/cli/new/templates/__init__.py +1 -1
  19. flwr/cli/new/templates/app/__init__.py +1 -1
  20. flwr/cli/new/templates/app/code/__init__.py +1 -1
  21. flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
  22. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
  23. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +4 -4
  24. flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
  25. flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
  26. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -17
  28. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
  29. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  30. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  31. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  33. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  34. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  35. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  36. flwr/cli/run/__init__.py +1 -1
  37. flwr/cli/run/run.py +11 -19
  38. flwr/cli/stop.py +3 -3
  39. flwr/cli/utils.py +42 -17
  40. flwr/client/__init__.py +3 -3
  41. flwr/client/client.py +1 -1
  42. flwr/client/client_app.py +140 -138
  43. flwr/client/clientapp/__init__.py +1 -8
  44. flwr/client/clientapp/utils.py +1 -1
  45. flwr/client/dpfedavg_numpy_client.py +1 -1
  46. flwr/client/grpc_adapter_client/__init__.py +1 -1
  47. flwr/client/grpc_adapter_client/connection.py +5 -5
  48. flwr/client/grpc_rere_client/__init__.py +1 -1
  49. flwr/client/grpc_rere_client/client_interceptor.py +1 -1
  50. flwr/client/grpc_rere_client/connection.py +131 -61
  51. flwr/client/grpc_rere_client/grpc_adapter.py +35 -7
  52. flwr/client/message_handler/__init__.py +1 -1
  53. flwr/client/message_handler/message_handler.py +2 -2
  54. flwr/client/mod/__init__.py +1 -1
  55. flwr/client/mod/centraldp_mods.py +1 -1
  56. flwr/client/mod/comms_mods.py +39 -20
  57. flwr/client/mod/localdp_mod.py +6 -6
  58. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  59. flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
  60. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  61. flwr/client/mod/utils.py +1 -1
  62. flwr/client/numpy_client.py +1 -1
  63. flwr/client/rest_client/__init__.py +1 -1
  64. flwr/client/rest_client/connection.py +174 -68
  65. flwr/client/run_info_store.py +1 -1
  66. flwr/client/typing.py +1 -1
  67. flwr/clientapp/__init__.py +15 -0
  68. flwr/common/__init__.py +3 -3
  69. flwr/common/address.py +1 -1
  70. flwr/common/args.py +1 -1
  71. flwr/common/auth_plugin/__init__.py +3 -1
  72. flwr/common/auth_plugin/auth_plugin.py +30 -4
  73. flwr/common/config.py +1 -1
  74. flwr/common/constant.py +37 -8
  75. flwr/common/context.py +1 -1
  76. flwr/common/date.py +1 -1
  77. flwr/common/differential_privacy.py +1 -1
  78. flwr/common/differential_privacy_constants.py +1 -1
  79. flwr/common/dp.py +1 -1
  80. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  81. flwr/common/exit/exit.py +6 -6
  82. flwr/common/exit_handlers.py +31 -1
  83. flwr/common/grpc.py +1 -1
  84. flwr/common/heartbeat.py +165 -0
  85. flwr/common/inflatable.py +290 -0
  86. flwr/common/inflatable_grpc_utils.py +99 -0
  87. flwr/common/inflatable_rest_utils.py +99 -0
  88. flwr/common/inflatable_utils.py +341 -0
  89. flwr/common/logger.py +1 -1
  90. flwr/common/message.py +137 -252
  91. flwr/common/object_ref.py +1 -1
  92. flwr/common/parameter.py +1 -1
  93. flwr/common/pyproject.py +1 -1
  94. flwr/common/record/__init__.py +3 -2
  95. flwr/common/record/array.py +323 -0
  96. flwr/common/record/arrayrecord.py +121 -243
  97. flwr/common/record/configrecord.py +71 -16
  98. flwr/common/record/conversion_utils.py +2 -2
  99. flwr/common/record/metricrecord.py +71 -20
  100. flwr/common/record/recorddict.py +207 -90
  101. flwr/common/record/typeddict.py +1 -1
  102. flwr/common/recorddict_compat.py +2 -2
  103. flwr/common/retry_invoker.py +15 -11
  104. flwr/common/secure_aggregation/__init__.py +1 -1
  105. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  106. flwr/common/secure_aggregation/crypto/shamir.py +52 -30
  107. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
  108. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  109. flwr/common/secure_aggregation/quantization.py +1 -1
  110. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  111. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  112. flwr/common/serde.py +60 -184
  113. flwr/common/serde_utils.py +175 -0
  114. flwr/common/telemetry.py +2 -2
  115. flwr/common/typing.py +6 -4
  116. flwr/common/version.py +1 -1
  117. flwr/compat/__init__.py +15 -0
  118. flwr/compat/client/__init__.py +15 -0
  119. flwr/{client → compat/client}/app.py +71 -211
  120. flwr/{client → compat/client}/grpc_client/__init__.py +1 -1
  121. flwr/{client → compat/client}/grpc_client/connection.py +13 -13
  122. flwr/compat/common/__init__.py +15 -0
  123. flwr/compat/server/__init__.py +15 -0
  124. flwr/compat/server/app.py +174 -0
  125. flwr/compat/simulation/__init__.py +15 -0
  126. flwr/proto/__init__.py +1 -1
  127. flwr/proto/fleet_pb2.py +32 -27
  128. flwr/proto/fleet_pb2.pyi +49 -35
  129. flwr/proto/fleet_pb2_grpc.py +117 -13
  130. flwr/proto/fleet_pb2_grpc.pyi +47 -6
  131. flwr/proto/heartbeat_pb2.py +33 -0
  132. flwr/proto/heartbeat_pb2.pyi +66 -0
  133. flwr/proto/heartbeat_pb2_grpc.py +4 -0
  134. flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
  135. flwr/proto/message_pb2.py +28 -11
  136. flwr/proto/message_pb2.pyi +125 -0
  137. flwr/proto/recorddict_pb2.py +16 -28
  138. flwr/proto/recorddict_pb2.pyi +46 -64
  139. flwr/proto/run_pb2.py +24 -32
  140. flwr/proto/run_pb2.pyi +4 -52
  141. flwr/proto/serverappio_pb2.py +32 -23
  142. flwr/proto/serverappio_pb2.pyi +45 -3
  143. flwr/proto/serverappio_pb2_grpc.py +138 -34
  144. flwr/proto/serverappio_pb2_grpc.pyi +54 -13
  145. flwr/proto/simulationio_pb2.py +12 -11
  146. flwr/proto/simulationio_pb2_grpc.py +35 -0
  147. flwr/proto/simulationio_pb2_grpc.pyi +14 -0
  148. flwr/server/__init__.py +2 -2
  149. flwr/server/app.py +69 -187
  150. flwr/server/client_manager.py +1 -1
  151. flwr/server/client_proxy.py +1 -1
  152. flwr/server/compat/__init__.py +1 -1
  153. flwr/server/compat/app.py +1 -1
  154. flwr/server/compat/app_utils.py +51 -29
  155. flwr/server/compat/legacy_context.py +1 -1
  156. flwr/server/criterion.py +1 -1
  157. flwr/server/fleet_event_log_interceptor.py +2 -2
  158. flwr/server/grid/grid.py +3 -3
  159. flwr/server/grid/grpc_grid.py +104 -34
  160. flwr/server/grid/inmemory_grid.py +5 -4
  161. flwr/server/history.py +1 -1
  162. flwr/server/run_serverapp.py +1 -1
  163. flwr/server/server.py +1 -1
  164. flwr/server/server_app.py +65 -58
  165. flwr/server/server_config.py +1 -1
  166. flwr/server/serverapp/__init__.py +1 -1
  167. flwr/server/serverapp/app.py +19 -1
  168. flwr/server/serverapp_components.py +1 -1
  169. flwr/server/strategy/__init__.py +1 -1
  170. flwr/server/strategy/aggregate.py +1 -1
  171. flwr/server/strategy/bulyan.py +2 -2
  172. flwr/server/strategy/dp_adaptive_clipping.py +17 -17
  173. flwr/server/strategy/dp_fixed_clipping.py +17 -17
  174. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  175. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  176. flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
  177. flwr/server/strategy/fedadagrad.py +1 -1
  178. flwr/server/strategy/fedadam.py +1 -1
  179. flwr/server/strategy/fedavg.py +1 -1
  180. flwr/server/strategy/fedavg_android.py +1 -1
  181. flwr/server/strategy/fedavgm.py +1 -1
  182. flwr/server/strategy/fedmedian.py +1 -1
  183. flwr/server/strategy/fedopt.py +1 -1
  184. flwr/server/strategy/fedprox.py +1 -1
  185. flwr/server/strategy/fedtrimmedavg.py +1 -1
  186. flwr/server/strategy/fedxgb_bagging.py +1 -1
  187. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  188. flwr/server/strategy/fedxgb_nn_avg.py +3 -2
  189. flwr/server/strategy/fedyogi.py +1 -1
  190. flwr/server/strategy/krum.py +1 -1
  191. flwr/server/strategy/qfedavg.py +1 -1
  192. flwr/server/strategy/strategy.py +1 -1
  193. flwr/server/superlink/__init__.py +1 -1
  194. flwr/server/superlink/ffs/__init__.py +3 -1
  195. flwr/server/superlink/ffs/disk_ffs.py +1 -1
  196. flwr/server/superlink/ffs/ffs.py +1 -1
  197. flwr/server/superlink/ffs/ffs_factory.py +1 -1
  198. flwr/server/superlink/fleet/__init__.py +1 -1
  199. flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
  200. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +14 -4
  201. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  202. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  203. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  204. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
  206. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  207. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
  208. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
  209. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  210. flwr/server/superlink/fleet/message_handler/message_handler.py +136 -19
  211. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  212. flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -12
  213. flwr/server/superlink/fleet/vce/__init__.py +1 -1
  214. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
  215. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  216. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
  217. flwr/server/superlink/fleet/vce/vce_api.py +7 -4
  218. flwr/server/superlink/linkstate/__init__.py +1 -1
  219. flwr/server/superlink/linkstate/in_memory_linkstate.py +139 -44
  220. flwr/server/superlink/linkstate/linkstate.py +54 -21
  221. flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
  222. flwr/server/superlink/linkstate/sqlite_linkstate.py +150 -56
  223. flwr/server/superlink/linkstate/utils.py +34 -30
  224. flwr/server/superlink/serverappio/serverappio_grpc.py +3 -0
  225. flwr/server/superlink/serverappio/serverappio_servicer.py +211 -57
  226. flwr/server/superlink/simulation/__init__.py +1 -1
  227. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  228. flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
  229. flwr/server/superlink/utils.py +45 -3
  230. flwr/server/typing.py +1 -1
  231. flwr/server/utils/__init__.py +1 -1
  232. flwr/server/utils/tensorboard.py +1 -1
  233. flwr/server/utils/validator.py +3 -3
  234. flwr/server/workflow/__init__.py +1 -1
  235. flwr/server/workflow/constant.py +1 -1
  236. flwr/server/workflow/default_workflows.py +1 -1
  237. flwr/server/workflow/secure_aggregation/__init__.py +1 -1
  238. flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
  239. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  240. flwr/serverapp/__init__.py +15 -0
  241. flwr/simulation/__init__.py +1 -1
  242. flwr/simulation/app.py +18 -1
  243. flwr/simulation/legacy_app.py +1 -1
  244. flwr/simulation/ray_transport/__init__.py +1 -1
  245. flwr/simulation/ray_transport/ray_actor.py +1 -1
  246. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  247. flwr/simulation/ray_transport/utils.py +1 -1
  248. flwr/simulation/run_simulation.py +2 -2
  249. flwr/simulation/simulationio_connection.py +1 -1
  250. flwr/supercore/__init__.py +15 -0
  251. flwr/supercore/object_store/__init__.py +24 -0
  252. flwr/supercore/object_store/in_memory_object_store.py +229 -0
  253. flwr/supercore/object_store/object_store.py +192 -0
  254. flwr/supercore/object_store/object_store_factory.py +44 -0
  255. flwr/superexec/__init__.py +1 -1
  256. flwr/superexec/app.py +1 -1
  257. flwr/superexec/deployment.py +7 -3
  258. flwr/superexec/exec_event_log_interceptor.py +4 -4
  259. flwr/superexec/exec_grpc.py +8 -4
  260. flwr/superexec/exec_servicer.py +126 -24
  261. flwr/superexec/exec_user_auth_interceptor.py +38 -9
  262. flwr/superexec/executor.py +5 -1
  263. flwr/superexec/simulation.py +8 -2
  264. flwr/superlink/__init__.py +15 -0
  265. flwr/{client/supernode → supernode}/__init__.py +1 -8
  266. flwr/{client/nodestate/nodestate.py → supernode/cli/__init__.py} +8 -15
  267. flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +4 -13
  268. flwr/supernode/cli/flwr_clientapp.py +81 -0
  269. flwr/{client → supernode}/nodestate/__init__.py +1 -1
  270. flwr/supernode/nodestate/in_memory_nodestate.py +190 -0
  271. flwr/supernode/nodestate/nodestate.py +212 -0
  272. flwr/{client → supernode}/nodestate/nodestate_factory.py +1 -1
  273. flwr/supernode/runtime/__init__.py +15 -0
  274. flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +26 -57
  275. flwr/supernode/servicer/__init__.py +15 -0
  276. flwr/supernode/servicer/clientappio/__init__.py +24 -0
  277. flwr/{client/clientapp → supernode/servicer/clientappio}/clientappio_servicer.py +1 -1
  278. flwr/supernode/start_client_internal.py +491 -0
  279. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/METADATA +6 -5
  280. flwr-1.19.0.dist-info/RECORD +365 -0
  281. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/WHEEL +1 -1
  282. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/entry_points.txt +2 -2
  283. flwr/client/heartbeat.py +0 -74
  284. flwr/client/nodestate/in_memory_nodestate.py +0 -38
  285. flwr-1.17.0.dist-info/LICENSE +0 -202
  286. flwr-1.17.0.dist-info/RECORD +0 -333
flwr/common/message.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
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.
@@ -18,14 +18,32 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from logging import WARNING
21
- from typing import Any, Optional, cast, overload
21
+ from typing import Any, cast, overload
22
22
 
23
23
  from flwr.common.date import now
24
24
  from flwr.common.logger import warn_deprecated_feature
25
-
26
- from .constant import MESSAGE_TTL_TOLERANCE, MessageType, MessageTypeLegacy
25
+ from flwr.proto.message_pb2 import Message as ProtoMessage # pylint: disable=E0611
26
+ from flwr.proto.message_pb2 import Metadata as ProtoMetadata # pylint: disable=E0611
27
+ from flwr.proto.message_pb2 import ObjectIDs # pylint: disable=E0611
28
+
29
+ from ..app.error import Error
30
+ from ..app.metadata import Metadata
31
+ from .constant import MESSAGE_TTL_TOLERANCE
32
+ from .inflatable import (
33
+ InflatableObject,
34
+ add_header_to_object_body,
35
+ get_descendant_object_ids,
36
+ get_object_body,
37
+ get_object_children_ids_from_object_content,
38
+ )
27
39
  from .logger import log
28
40
  from .record import RecordDict
41
+ from .serde_utils import (
42
+ error_from_proto,
43
+ error_to_proto,
44
+ metadata_from_proto,
45
+ metadata_to_proto,
46
+ )
29
47
 
30
48
  DEFAULT_TTL = 43200 # This is 12 hours
31
49
  MESSAGE_INIT_ERROR_MESSAGE = (
@@ -36,210 +54,27 @@ MESSAGE_INIT_ERROR_MESSAGE = (
36
54
  )
37
55
 
38
56
 
39
- class MessageInitializationError(TypeError):
40
- """Error raised when initializing a message with invalid arguments."""
41
-
42
- def __init__(self, message: str | None = None) -> None:
43
- super().__init__(message or MESSAGE_INIT_ERROR_MESSAGE)
44
-
45
-
46
- class Metadata: # pylint: disable=too-many-instance-attributes
47
- """The class representing metadata associated with the current message.
48
-
49
- Parameters
50
- ----------
51
- run_id : int
52
- An identifier for the current run.
53
- message_id : str
54
- An identifier for the current message.
55
- src_node_id : int
56
- An identifier for the node sending this message.
57
- dst_node_id : int
58
- An identifier for the node receiving this message.
59
- reply_to_message_id : str
60
- An identifier for the message to which this message is a reply.
61
- group_id : str
62
- An identifier for grouping messages. In some settings,
63
- this is used as the FL round.
64
- created_at : float
65
- Unix timestamp when the message was created.
66
- ttl : float
67
- Time-to-live for this message in seconds.
68
- message_type : str
69
- A string that encodes the action to be executed on
70
- the receiving end.
71
- """
72
-
73
- def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
74
- self,
75
- run_id: int,
76
- message_id: str,
77
- src_node_id: int,
78
- dst_node_id: int,
79
- reply_to_message_id: str,
80
- group_id: str,
81
- created_at: float,
82
- ttl: float,
83
- message_type: str,
84
- ) -> None:
85
- var_dict = {
86
- "_run_id": run_id,
87
- "_message_id": message_id,
88
- "_src_node_id": src_node_id,
89
- "_dst_node_id": dst_node_id,
90
- "_reply_to_message_id": reply_to_message_id,
91
- "_group_id": group_id,
92
- "_created_at": created_at,
93
- "_ttl": ttl,
94
- "_message_type": message_type,
95
- }
96
- self.__dict__.update(var_dict)
97
- self.message_type = message_type # Trigger validation
98
-
99
- @property
100
- def run_id(self) -> int:
101
- """An identifier for the current run."""
102
- return cast(int, self.__dict__["_run_id"])
103
-
104
- @property
105
- def message_id(self) -> str:
106
- """An identifier for the current message."""
107
- return cast(str, self.__dict__["_message_id"])
108
-
109
- @property
110
- def src_node_id(self) -> int:
111
- """An identifier for the node sending this message."""
112
- return cast(int, self.__dict__["_src_node_id"])
113
-
114
- @property
115
- def reply_to_message_id(self) -> str:
116
- """An identifier for the message to which this message is a reply."""
117
- return cast(str, self.__dict__["_reply_to_message_id"])
118
-
119
- @property
120
- def dst_node_id(self) -> int:
121
- """An identifier for the node receiving this message."""
122
- return cast(int, self.__dict__["_dst_node_id"])
123
-
124
- @dst_node_id.setter
125
- def dst_node_id(self, value: int) -> None:
126
- """Set dst_node_id."""
127
- self.__dict__["_dst_node_id"] = value
128
-
129
- @property
130
- def group_id(self) -> str:
131
- """An identifier for grouping messages."""
132
- return cast(str, self.__dict__["_group_id"])
133
-
134
- @group_id.setter
135
- def group_id(self, value: str) -> None:
136
- """Set group_id."""
137
- self.__dict__["_group_id"] = value
138
-
139
- @property
140
- def created_at(self) -> float:
141
- """Unix timestamp when the message was created."""
142
- return cast(float, self.__dict__["_created_at"])
143
-
144
- @created_at.setter
145
- def created_at(self, value: float) -> None:
146
- """Set creation timestamp of this message."""
147
- self.__dict__["_created_at"] = value
148
-
149
- @property
150
- def delivered_at(self) -> str:
151
- """Unix timestamp when the message was delivered."""
152
- return cast(str, self.__dict__["_delivered_at"])
153
-
154
- @delivered_at.setter
155
- def delivered_at(self, value: str) -> None:
156
- """Set delivery timestamp of this message."""
157
- self.__dict__["_delivered_at"] = value
158
-
159
- @property
160
- def ttl(self) -> float:
161
- """Time-to-live for this message."""
162
- return cast(float, self.__dict__["_ttl"])
163
-
164
- @ttl.setter
165
- def ttl(self, value: float) -> None:
166
- """Set ttl."""
167
- self.__dict__["_ttl"] = value
168
-
169
- @property
170
- def message_type(self) -> str:
171
- """A string that encodes the action to be executed on the receiving end."""
172
- return cast(str, self.__dict__["_message_type"])
173
-
174
- @message_type.setter
175
- def message_type(self, value: str) -> None:
176
- """Set message_type."""
177
- # Validate message type
178
- if validate_legacy_message_type(value):
179
- pass # Backward compatibility for legacy message types
180
- elif not validate_message_type(value):
181
- raise ValueError(
182
- f"Invalid message type: '{value}'. "
183
- "Expected format: '<category>' or '<category>.<action>', "
184
- "where <category> must be 'train', 'evaluate', or 'query', "
185
- "and <action> must be a valid Python identifier."
186
- )
187
-
188
- self.__dict__["_message_type"] = value
189
-
190
- def __repr__(self) -> str:
191
- """Return a string representation of this instance."""
192
- view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
193
- return f"{self.__class__.__qualname__}({view})"
194
-
195
- def __eq__(self, other: object) -> bool:
196
- """Compare two instances of the class."""
197
- if not isinstance(other, self.__class__):
198
- raise NotImplementedError
199
- return self.__dict__ == other.__dict__
200
-
57
+ class _WarningTracker:
58
+ """A class to track warnings for deprecated properties."""
201
59
 
202
- class Error:
203
- """The class storing information about an error that occurred.
60
+ def __init__(self) -> None:
61
+ # These variables are used to ensure that the deprecation warnings
62
+ # for the deprecated properties/class are logged only once.
63
+ self.create_error_reply_logged = False
64
+ self.create_reply_logged = False
204
65
 
205
- Parameters
206
- ----------
207
- code : int
208
- An identifier for the error.
209
- reason : Optional[str]
210
- A reason for why the error arose (e.g. an exception stack-trace)
211
- """
212
66
 
213
- def __init__(self, code: int, reason: str | None = None) -> None:
214
- var_dict = {
215
- "_code": code,
216
- "_reason": reason,
217
- }
218
- self.__dict__.update(var_dict)
67
+ _warning_tracker = _WarningTracker()
219
68
 
220
- @property
221
- def code(self) -> int:
222
- """Error code."""
223
- return cast(int, self.__dict__["_code"])
224
69
 
225
- @property
226
- def reason(self) -> str | None:
227
- """Reason reported about the error."""
228
- return cast(Optional[str], self.__dict__["_reason"])
229
-
230
- def __repr__(self) -> str:
231
- """Return a string representation of this instance."""
232
- view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()])
233
- return f"{self.__class__.__qualname__}({view})"
70
+ class MessageInitializationError(TypeError):
71
+ """Error raised when initializing a message with invalid arguments."""
234
72
 
235
- def __eq__(self, other: object) -> bool:
236
- """Compare two instances of the class."""
237
- if not isinstance(other, self.__class__):
238
- raise NotImplementedError
239
- return self.__dict__ == other.__dict__
73
+ def __init__(self, message: str | None = None) -> None:
74
+ super().__init__(message or MESSAGE_INIT_ERROR_MESSAGE)
240
75
 
241
76
 
242
- class Message:
77
+ class Message(InflatableObject):
243
78
  """Represents a message exchanged between ClientApp and ServerApp.
244
79
 
245
80
  This class encapsulates the payload and metadata necessary for communication
@@ -456,11 +291,13 @@ class Message:
456
291
  message : Message
457
292
  A Message containing only the relevant error and metadata.
458
293
  """
459
- warn_deprecated_feature(
460
- "`Message.create_error_reply` is deprecated. "
461
- "Instead of calling `some_message.create_error_reply(some_error, ttl=...)`"
462
- ", use `Message(some_error, reply_to=some_message, ttl=...)`."
463
- )
294
+ if not _warning_tracker.create_error_reply_logged:
295
+ _warning_tracker.create_error_reply_logged = True
296
+ warn_deprecated_feature(
297
+ "`Message.create_error_reply` is deprecated. "
298
+ "Instead of calling `some_message.create_error_reply(some_error, "
299
+ "ttl=...)`, use `Message(some_error, reply_to=some_message, ttl=...)`."
300
+ )
464
301
  if ttl is not None:
465
302
  return Message(error, reply_to=self, ttl=ttl)
466
303
  return Message(error, reply_to=self)
@@ -488,11 +325,13 @@ class Message:
488
325
  Message
489
326
  A new `Message` instance representing the reply.
490
327
  """
491
- warn_deprecated_feature(
492
- "`Message.create_reply` is deprecated. "
493
- "Instead of calling `some_message.create_reply(some_content, ttl=...)`"
494
- ", use `Message(some_content, reply_to=some_message, ttl=...)`."
495
- )
328
+ if not _warning_tracker.create_reply_logged:
329
+ _warning_tracker.create_reply_logged = True
330
+ warn_deprecated_feature(
331
+ "`Message.create_reply` is deprecated. "
332
+ "Instead of calling `some_message.create_reply(some_content, ttl=...)`"
333
+ ", use `Message(some_content, reply_to=some_message, ttl=...)`."
334
+ )
496
335
  if ttl is not None:
497
336
  return Message(content, reply_to=self, ttl=ttl)
498
337
  return Message(content, reply_to=self)
@@ -508,6 +347,77 @@ class Message:
508
347
  )
509
348
  return f"{self.__class__.__qualname__}({view})"
510
349
 
350
+ @property
351
+ def children(self) -> dict[str, InflatableObject] | None:
352
+ """Return a dictionary of a single RecordDict with its Object IDs as key."""
353
+ return {self.content.object_id: self.content} if self.has_content() else None
354
+
355
+ def deflate(self) -> bytes:
356
+ """Deflate message."""
357
+ # Exclude message_id from serialization
358
+ proto_metadata: ProtoMetadata = metadata_to_proto(self.metadata)
359
+ proto_metadata.message_id = ""
360
+ # Store message metadata and error in object body
361
+ obj_body = ProtoMessage(
362
+ metadata=proto_metadata,
363
+ content=None,
364
+ error=error_to_proto(self.error) if self.has_error() else None,
365
+ ).SerializeToString(deterministic=True)
366
+
367
+ return add_header_to_object_body(object_body=obj_body, obj=self)
368
+
369
+ @classmethod
370
+ def inflate(
371
+ cls, object_content: bytes, children: dict[str, InflatableObject] | None = None
372
+ ) -> Message:
373
+ """Inflate an Message from bytes.
374
+
375
+ Parameters
376
+ ----------
377
+ object_content : bytes
378
+ The deflated object content of the Message.
379
+ children : Optional[dict[str, InflatableObject]] (default: None)
380
+ Dictionary of children InflatableObjects mapped to their Object IDs.
381
+ These children enable the full inflation of the Message.
382
+
383
+ Returns
384
+ -------
385
+ Message
386
+ The inflated Message.
387
+ """
388
+ if children is None:
389
+ children = {}
390
+
391
+ # Get the children id from the deflated message
392
+ children_ids = get_object_children_ids_from_object_content(object_content)
393
+
394
+ # If the message had content, only one children is possible
395
+ # If the message carried an error, the returned listed should be empty
396
+ if children_ids != list(children.keys()):
397
+ raise ValueError(
398
+ f"Mismatch in children object IDs: expected {children_ids}, but "
399
+ f"received {list(children.keys())}. The provided children must exactly "
400
+ "match the IDs specified in the object head."
401
+ )
402
+
403
+ # Inflate content
404
+ obj_body = get_object_body(object_content, cls)
405
+ proto_message = ProtoMessage.FromString(obj_body)
406
+
407
+ # Prepare content if error wasn't set in protobuf message
408
+ if proto_message.HasField("error"):
409
+ content = None
410
+ error = error_from_proto(proto_message.error)
411
+ else:
412
+ content = cast(RecordDict, children[children_ids[0]])
413
+ error = None
414
+ # Return message
415
+ return make_message(
416
+ metadata=metadata_from_proto(proto_message.metadata),
417
+ content=content,
418
+ error=error,
419
+ )
420
+
511
421
 
512
422
  def make_message(
513
423
  metadata: Metadata, content: RecordDict | None = None, error: Error | None = None
@@ -516,6 +426,17 @@ def make_message(
516
426
  return Message(metadata=metadata, content=content, error=error) # type: ignore
517
427
 
518
428
 
429
+ def remove_content_from_message(message: Message) -> Message:
430
+ """Return a copy of the Message but with an empty RecordDict as content.
431
+
432
+ If message has no content, it returns itself.
433
+ """
434
+ if message.has_error():
435
+ return message
436
+
437
+ return make_message(metadata=message.metadata, content=RecordDict())
438
+
439
+
519
440
  def _limit_reply_ttl(
520
441
  current: float, reply_ttl: float | None, reply_to: Message
521
442
  ) -> float:
@@ -599,46 +520,10 @@ def _check_arg_types( # pylint: disable=too-many-arguments, R0917
599
520
  raise MessageInitializationError()
600
521
 
601
522
 
602
- def validate_message_type(message_type: str) -> bool:
603
- """Validate if the message type is valid.
604
-
605
- A valid message type format must be one of the following:
606
-
607
- - "<category>"
608
- - "<category>.<action>"
609
-
610
- where `category` must be one of "train", "evaluate", or "query",
611
- and `action` must be a valid Python identifier.
612
- """
613
- # Check if conforming to the format "<category>"
614
- valid_types = {
615
- MessageType.TRAIN,
616
- MessageType.EVALUATE,
617
- MessageType.QUERY,
618
- MessageType.SYSTEM,
523
+ def get_message_to_descendant_id_mapping(message: Message) -> dict[str, ObjectIDs]:
524
+ """Construct a mapping between message object_id and that of its descendants."""
525
+ return {
526
+ message.object_id: ObjectIDs(
527
+ object_ids=list(get_descendant_object_ids(message))
528
+ )
619
529
  }
620
- if message_type in valid_types:
621
- return True
622
-
623
- # Check if conforming to the format "<category>.<action>"
624
- if message_type.count(".") != 1:
625
- return False
626
-
627
- category, action = message_type.split(".")
628
- if category in valid_types and action.isidentifier():
629
- return True
630
-
631
- return False
632
-
633
-
634
- def validate_legacy_message_type(message_type: str) -> bool:
635
- """Validate if the legacy message type is valid."""
636
- # Backward compatibility for legacy message types
637
- if message_type in (
638
- MessageTypeLegacy.GET_PARAMETERS,
639
- MessageTypeLegacy.GET_PROPERTIES,
640
- "reconnect",
641
- ):
642
- return True
643
-
644
- return False
flwr/common/object_ref.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
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.
flwr/common/parameter.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
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.
flwr/common/pyproject.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
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.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
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,7 +15,8 @@
15
15
  """Record APIs."""
16
16
 
17
17
 
18
- from .arrayrecord import Array, ArrayRecord, ParametersRecord
18
+ from .array import Array
19
+ from .arrayrecord import ArrayRecord, ParametersRecord
19
20
  from .configrecord import ConfigRecord, ConfigsRecord
20
21
  from .conversion_utils import array_from_numpy
21
22
  from .metricrecord import MetricRecord, MetricsRecord