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
@@ -15,12 +15,21 @@
15
15
  """MetricRecord."""
16
16
 
17
17
 
18
+ from __future__ import annotations
19
+
18
20
  from logging import WARN
19
- from typing import Optional, get_args
21
+ from typing import cast, get_args
20
22
 
21
23
  from flwr.common.typing import MetricRecordValues, MetricScalar
22
24
 
25
+ # pylint: disable=E0611
26
+ from flwr.proto.recorddict_pb2 import MetricRecord as ProtoMetricRecord
27
+ from flwr.proto.recorddict_pb2 import MetricRecordValue as ProtoMetricRecordValue
28
+
29
+ # pylint: enable=E0611
30
+ from ..inflatable import InflatableObject, add_header_to_object_body, get_object_body
23
31
  from ..logger import log
32
+ from ..serde_utils import record_value_dict_from_proto, record_value_dict_to_proto
24
33
  from .typeddict import TypedDict
25
34
 
26
35
 
@@ -59,8 +68,8 @@ def _check_value(value: MetricRecordValues) -> None:
59
68
  is_valid(value)
60
69
 
61
70
 
62
- class MetricRecord(TypedDict[str, MetricRecordValues]):
63
- """Metrics recod.
71
+ class MetricRecord(TypedDict[str, MetricRecordValues], InflatableObject):
72
+ """Metric record.
64
73
 
65
74
  A :code:`MetricRecord` is a Python dictionary designed to ensure that
66
75
  each key-value pair adheres to specified data types. A :code:`MetricRecord`
@@ -89,27 +98,27 @@ class MetricRecord(TypedDict[str, MetricRecordValues]):
89
98
  Common to these examples is that the output can be typically represented by
90
99
  a single scalar (:code:`int`, :code:`float`) or list of scalars.
91
100
 
92
- Let's see some examples of how to construct a :code:`MetricRecord` from scratch:
101
+ Let's see some examples of how to construct a :code:`MetricRecord` from scratch::
102
+
103
+ from flwr.common import MetricRecord
93
104
 
94
- >>> from flwr.common import MetricRecord
95
- >>>
96
- >>> # A `MetricRecord` is a specialized Python dictionary
97
- >>> record = MetricRecord({"accuracy": 0.94})
98
- >>> # You can add more content to an existing record
99
- >>> record["loss"] = 0.01
100
- >>> # It also supports lists
101
- >>> record["loss-historic"] = [0.9, 0.5, 0.01]
105
+ # A `MetricRecord` is a specialized Python dictionary
106
+ record = MetricRecord({"accuracy": 0.94})
107
+ # You can add more content to an existing record
108
+ record["loss"] = 0.01
109
+ # It also supports lists
110
+ record["loss-historic"] = [0.9, 0.5, 0.01]
102
111
 
103
112
  Since types are enforced, the types of the objects inserted are checked. For a
104
113
  :code:`MetricRecord`, value types allowed are those in defined in
105
114
  :code:`flwr.common.MetricRecordValues`. Similarly, only :code:`str` keys are
106
- allowed.
115
+ allowed::
107
116
 
108
- >>> from flwr.common import MetricRecord
109
- >>>
110
- >>> record = MetricRecord() # an empty record
111
- >>> # Add unsupported value
112
- >>> record["something-unsupported"] = {'a': 123} # Will throw a `TypeError`
117
+ from flwr.common import MetricRecord
118
+
119
+ record = MetricRecord() # an empty record
120
+ # Add unsupported value
121
+ record["something-unsupported"] = {'a': 123} # Will throw a `TypeError`
113
122
 
114
123
  If you need a more versatily type of record try :code:`ConfigRecord` or
115
124
  :code:`ArrayRecord`.
@@ -117,7 +126,7 @@ class MetricRecord(TypedDict[str, MetricRecordValues]):
117
126
 
118
127
  def __init__(
119
128
  self,
120
- metric_dict: Optional[dict[str, MetricRecordValues]] = None,
129
+ metric_dict: dict[str, MetricRecordValues] | None = None,
121
130
  keep_input: bool = True,
122
131
  ) -> None:
123
132
  super().__init__(_check_key, _check_value)
@@ -143,6 +152,48 @@ class MetricRecord(TypedDict[str, MetricRecordValues]):
143
152
  num_bytes += len(k)
144
153
  return num_bytes
145
154
 
155
+ def deflate(self) -> bytes:
156
+ """Deflate object."""
157
+ protos = record_value_dict_to_proto(self, [float, int], ProtoMetricRecordValue)
158
+ obj_body = ProtoMetricRecord(
159
+ items=[ProtoMetricRecord.Item(key=k, value=v) for k, v in protos.items()]
160
+ ).SerializeToString()
161
+ return add_header_to_object_body(object_body=obj_body, obj=self)
162
+
163
+ @classmethod
164
+ def inflate(
165
+ cls, object_content: bytes, children: dict[str, InflatableObject] | None = None
166
+ ) -> MetricRecord:
167
+ """Inflate a MetricRecord from bytes.
168
+
169
+ Parameters
170
+ ----------
171
+ object_content : bytes
172
+ The deflated object content of the MetricRecord.
173
+
174
+ children : Optional[dict[str, InflatableObject]] (default: None)
175
+ Must be ``None``. ``MetricRecord`` does not support child objects.
176
+ Providing any children will raise a ``ValueError``.
177
+
178
+ Returns
179
+ -------
180
+ MetricRecord
181
+ The inflated MetricRecord.
182
+ """
183
+ if children:
184
+ raise ValueError("`MetricRecord` objects do not have children.")
185
+
186
+ obj_body = get_object_body(object_content, cls)
187
+ metric_record_proto = ProtoMetricRecord.FromString(obj_body)
188
+ protos = {item.key: item.value for item in metric_record_proto.items}
189
+ return cls(
190
+ metric_dict=cast(
191
+ dict[str, MetricRecordValues],
192
+ record_value_dict_from_proto(protos),
193
+ ),
194
+ keep_input=False,
195
+ )
196
+
146
197
 
147
198
  class MetricsRecord(MetricRecord):
148
199
  """Deprecated class ``MetricsRecord``, use ``MetricRecord`` instead.
@@ -174,7 +225,7 @@ class MetricsRecord(MetricRecord):
174
225
 
175
226
  def __init__(
176
227
  self,
177
- metric_dict: Optional[dict[str, MetricRecordValues]] = None,
228
+ metric_dict: dict[str, MetricRecordValues] | None = None,
178
229
  keep_input: bool = True,
179
230
  ):
180
231
  if not MetricsRecord._warning_logged:
@@ -17,10 +17,12 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import json
20
21
  from logging import WARN
21
22
  from textwrap import indent
22
23
  from typing import TypeVar, Union, cast
23
24
 
25
+ from ..inflatable import InflatableObject, add_header_to_object_body, get_object_body
24
26
  from ..logger import log
25
27
  from .arrayrecord import ArrayRecord
26
28
  from .configrecord import ConfigRecord
@@ -29,6 +31,22 @@ from .typeddict import TypedDict
29
31
 
30
32
  RecordType = Union[ArrayRecord, MetricRecord, ConfigRecord]
31
33
 
34
+
35
+ class _WarningTracker:
36
+ """A class to track warnings for deprecated properties."""
37
+
38
+ def __init__(self) -> None:
39
+ # These variables are used to ensure that the deprecation warnings
40
+ # for the deprecated properties/class are logged only once.
41
+ self.recordset_init_logged = False
42
+ self.recorddict_init_logged = False
43
+ self.parameters_records_logged = False
44
+ self.metrics_records_logged = False
45
+ self.configs_records_logged = False
46
+
47
+
48
+ _warning_tracker = _WarningTracker()
49
+
32
50
  T = TypeVar("T")
33
51
 
34
52
 
@@ -81,7 +99,7 @@ class _SyncedDict(TypedDict[str, T]):
81
99
  )
82
100
 
83
101
 
84
- class RecordDict(TypedDict[str, RecordType]):
102
+ class RecordDict(TypedDict[str, RecordType], InflatableObject):
85
103
  """RecordDict stores groups of arrays, metrics and configs.
86
104
 
87
105
  A :class:`RecordDict` is the unified mechanism by which arrays,
@@ -103,48 +121,74 @@ class RecordDict(TypedDict[str, RecordType]):
103
121
  are Python dictionaries designed to ensure that each key-value pair
104
122
  adheres to specified data types.
105
123
 
106
- Let's see an example.
107
-
108
- >>> from flwr.common import RecordDict
109
- >>> from flwr.common import ArrayRecord, ConfigRecord, MetricRecord
110
- >>>
111
- >>> # Let's begin with an empty record
112
- >>> my_records = RecordDict()
113
- >>>
114
- >>> # We can create a ConfigRecord
115
- >>> c_record = ConfigRecord({"lr": 0.1, "batch-size": 128})
116
- >>> # Adding it to the RecordDict would look like this
117
- >>> my_records["my_config"] = c_record
118
- >>>
119
- >>> # We can create a MetricRecord following a similar process
120
- >>> m_record = MetricRecord({"accuracy": 0.93, "losses": [0.23, 0.1]})
121
- >>> # Adding it to the RecordDict would look like this
122
- >>> my_records["my_metrics"] = m_record
124
+ Let's see an example::
125
+
126
+ from flwr.common import RecordDict
127
+ from flwr.common import ArrayRecord, ConfigRecord, MetricRecord
128
+
129
+ # Let's begin with an empty record
130
+ my_records = RecordDict()
131
+
132
+ # We can create a ConfigRecord
133
+ c_record = ConfigRecord({"lr": 0.1, "batch-size": 128})
134
+ # Adding it to the RecordDict would look like this
135
+ my_records["my_config"] = c_record
136
+
137
+ # We can create a MetricRecord following a similar process
138
+ m_record = MetricRecord({"accuracy": 0.93, "losses": [0.23, 0.1]})
139
+ # Adding it to the RecordDict would look like this
140
+ my_records["my_metrics"] = m_record
123
141
 
124
142
  Adding an :code:`ArrayRecord` follows the same steps as above but first,
125
143
  the array needs to be serialized and represented as a :code:`flwr.common.Array`.
126
- For example:
127
-
128
- >>> from flwr.common import Array
129
- >>> # Creating an ArrayRecord would look like this
130
- >>> arr_np = np.random.randn(3, 3)
131
- >>>
132
- >>> # You can use the built-in tool to serialize the array
133
- >>> arr = Array(arr_np)
134
- >>>
135
- >>> # Finally, create the record
136
- >>> arr_record = ArrayRecord({"my_array": arr})
137
- >>>
138
- >>> # Adding it to the RecordDict would look like this
139
- >>> my_records["my_parameters"] = arr_record
144
+ For example::
145
+
146
+ from flwr.common import Array
147
+ # Creating an ArrayRecord would look like this
148
+ arr_np = np.random.randn(3, 3)
149
+
150
+ # You can use the built-in tool to serialize the array
151
+ arr = Array(arr_np)
152
+
153
+ # Finally, create the record
154
+ arr_record = ArrayRecord({"my_array": arr})
155
+
156
+ # Adding it to the RecordDict would look like this
157
+ my_records["my_parameters"] = arr_record
140
158
 
141
159
  For additional examples on how to construct each of the records types shown
142
160
  above, please refer to the documentation for :code:`ConfigRecord`,
143
161
  :code:`MetricRecord` and :code:`ArrayRecord`.
144
162
  """
145
163
 
146
- def __init__(self, records: dict[str, RecordType] | None = None) -> None:
164
+ def __init__(
165
+ self,
166
+ records: dict[str, RecordType] | None = None,
167
+ *,
168
+ parameters_records: dict[str, ArrayRecord] | None = None,
169
+ metrics_records: dict[str, MetricRecord] | None = None,
170
+ configs_records: dict[str, ConfigRecord] | None = None,
171
+ ) -> None:
147
172
  super().__init__(_check_key, _check_value)
173
+
174
+ # Warning for deprecated usage
175
+ if (
176
+ parameters_records is not None
177
+ or metrics_records is not None
178
+ or configs_records is not None
179
+ ):
180
+ log(
181
+ WARN,
182
+ "The arguments `parameters_records`, `metrics_records`, and "
183
+ "`configs_records` of `RecordDict` are deprecated and will "
184
+ "be removed in a future release. "
185
+ "Please pass all records using the `records` argument instead.",
186
+ )
187
+ records = records or {}
188
+ records.update(parameters_records or {})
189
+ records.update(metrics_records or {})
190
+ records.update(configs_records or {})
191
+
148
192
  if records is not None:
149
193
  for key, record in records.items():
150
194
  self[key] = record
@@ -196,6 +240,120 @@ class RecordDict(TypedDict[str, RecordType]):
196
240
  type(value).__name__,
197
241
  )
198
242
 
243
+ @property
244
+ def parameters_records(self) -> TypedDict[str, ArrayRecord]:
245
+ """Deprecated property.
246
+
247
+ Use ``array_records`` instead.
248
+ """
249
+ if _warning_tracker.parameters_records_logged:
250
+ _warning_tracker.parameters_records_logged = True
251
+ log(
252
+ WARN,
253
+ "The `parameters_records` property of `RecordDict` "
254
+ "(formerly `RecordSet`) is deprecated and will be removed in a "
255
+ "future release. Please use the `array_records` property instead.",
256
+ )
257
+ return self.array_records
258
+
259
+ @property
260
+ def metrics_records(self) -> TypedDict[str, MetricRecord]:
261
+ """Deprecated property.
262
+
263
+ Use ``metric_records`` instead.
264
+ """
265
+ if not _warning_tracker.metrics_records_logged:
266
+ _warning_tracker.metrics_records_logged = True
267
+ log(
268
+ WARN,
269
+ "The `metrics_records` property of `RecordDict` "
270
+ "(formerly `RecordSet`) is deprecated and will be removed in a "
271
+ "future release. Please use the `metric_records` property instead.",
272
+ )
273
+ return self.metric_records
274
+
275
+ @property
276
+ def configs_records(self) -> TypedDict[str, ConfigRecord]:
277
+ """Deprecated property.
278
+
279
+ Use ``config_records`` instead.
280
+ """
281
+ if not _warning_tracker.configs_records_logged:
282
+ _warning_tracker.configs_records_logged = True
283
+ log(
284
+ WARN,
285
+ "The `configs_records` property of `RecordDict` "
286
+ "(formerly `RecordSet`) is deprecated and will be removed in a "
287
+ "future release. Please use the `config_records` property instead.",
288
+ )
289
+ return self.config_records
290
+
291
+ @property
292
+ def children(self) -> dict[str, InflatableObject]:
293
+ """Return a dictionary of records with their Object IDs as keys."""
294
+ return {record.object_id: record for record in self.values()}
295
+
296
+ def deflate(self) -> bytes:
297
+ """Deflate the RecordDict."""
298
+ # record_name: record_object_id mapping
299
+ record_refs: dict[str, str] = {}
300
+
301
+ for record_name, record in self.items():
302
+ record_refs[record_name] = record.object_id
303
+
304
+ # Serialize references dict
305
+ object_body = json.dumps(record_refs).encode("utf-8")
306
+ return add_header_to_object_body(object_body=object_body, obj=self)
307
+
308
+ @classmethod
309
+ def inflate(
310
+ cls, object_content: bytes, children: dict[str, InflatableObject] | None = None
311
+ ) -> RecordDict:
312
+ """Inflate an RecordDict from bytes.
313
+
314
+ Parameters
315
+ ----------
316
+ object_content : bytes
317
+ The deflated object content of the RecordDict.
318
+ children : Optional[dict[str, InflatableObject]] (default: None)
319
+ Dictionary of children InflatableObjects mapped to their Object IDs.
320
+ These children enable the full inflation of the RecordDict. Default is None.
321
+
322
+ Returns
323
+ -------
324
+ RecordDict
325
+ The inflated RecordDict.
326
+ """
327
+ if children is None:
328
+ children = {}
329
+
330
+ # Inflate mapping of record_names (keys in the RecordDict) to Record' object IDs
331
+ obj_body = get_object_body(object_content, cls)
332
+ record_refs: dict[str, str] = json.loads(obj_body.decode(encoding="utf-8"))
333
+
334
+ unique_records = set(record_refs.values())
335
+ children_obj_ids = set(children.keys())
336
+ if unique_records != children_obj_ids:
337
+ raise ValueError(
338
+ "Unexpected set of `children`. "
339
+ f"Expected {unique_records} but got {children_obj_ids}."
340
+ )
341
+
342
+ # Ensure children are one of the *Record objects exepecte in a RecordDict
343
+ if not all(
344
+ isinstance(ch, (ArrayRecord, ConfigRecord, MetricRecord))
345
+ for ch in children.values()
346
+ ):
347
+ raise ValueError(
348
+ "`Children` are expected to be of type `ArrayRecord`, "
349
+ "`ConfigRecord` or `MetricRecord`."
350
+ )
351
+
352
+ # Instantiate new RecordDict
353
+ return RecordDict(
354
+ {name: children[object_id] for name, object_id in record_refs.items()} # type: ignore
355
+ )
356
+
199
357
 
200
358
  class RecordSet(RecordDict):
201
359
  """Deprecated class ``RecordSet``, use ``RecordDict`` instead.
@@ -223,66 +381,25 @@ class RecordSet(RecordDict):
223
381
  my_content = RecordDict()
224
382
  """
225
383
 
226
- _warning_logged = False
227
- _warning_logged_params = False
228
- _warning_logged_metrics = False
229
- _warning_logged_configs = False
230
-
231
- def __init__(self, records: dict[str, RecordType] | None = None) -> None:
232
- if not RecordSet._warning_logged:
233
- RecordSet._warning_logged = True
384
+ def __init__(
385
+ self,
386
+ records: dict[str, RecordType] | None = None,
387
+ *,
388
+ parameters_records: dict[str, ArrayRecord] | None = None,
389
+ metrics_records: dict[str, MetricRecord] | None = None,
390
+ configs_records: dict[str, ConfigRecord] | None = None,
391
+ ) -> None:
392
+ if not _warning_tracker.recordset_init_logged:
393
+ _warning_tracker.recordset_init_logged = True
234
394
  log(
235
395
  WARN,
236
396
  "The `RecordSet` class has been renamed to `RecordDict`. "
237
397
  "Support for `RecordSet` will be removed in a future release. "
238
398
  "Please update your code accordingly.",
239
399
  )
240
- super().__init__(records)
241
-
242
- @property
243
- def parameters_records(self) -> TypedDict[str, ArrayRecord]:
244
- """Deprecated property.
245
-
246
- Use ``array_records`` instead.
247
- """
248
- if not RecordSet._warning_logged_params:
249
- RecordSet._warning_logged_params = True
250
- log(
251
- WARN,
252
- "`RecordSet.parameters_records` has been deprecated "
253
- "and will be removed in a future release. Please use "
254
- "`RecordDict.array_records` instead.",
255
- )
256
- return self.array_records
257
-
258
- @property
259
- def metrics_records(self) -> TypedDict[str, MetricRecord]:
260
- """Deprecated property.
261
-
262
- Use ``metric_records`` instead.
263
- """
264
- if not RecordSet._warning_logged_metrics:
265
- RecordSet._warning_logged_metrics = True
266
- log(
267
- WARN,
268
- "`RecordSet.metrics_records` has been deprecated "
269
- "and will be removed in a future release. Please use "
270
- "`RecordDict.metric_records` instead.",
271
- )
272
- return self.metric_records
273
-
274
- @property
275
- def configs_records(self) -> TypedDict[str, ConfigRecord]:
276
- """Deprecated property.
277
-
278
- Use ``config_records`` instead.
279
- """
280
- if not RecordSet._warning_logged_configs:
281
- RecordSet._warning_logged_configs = True
282
- log(
283
- WARN,
284
- "`RecordSet.configs_records` has been deprecated "
285
- "and will be removed in a future release. Please use "
286
- "`RecordDict.config_records` instead.",
287
- )
288
- return self.config_records
400
+ super().__init__(
401
+ records,
402
+ parameters_records=parameters_records,
403
+ metrics_records=metrics_records,
404
+ configs_records=configs_records,
405
+ )
@@ -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.
@@ -111,12 +111,12 @@ def parameters_to_arrayrecord(parameters: Parameters, keep_input: bool) -> Array
111
111
  else:
112
112
  tensor = parameters.tensors.pop(0)
113
113
  ordered_dict[str(idx)] = Array(
114
- data=tensor, dtype="", stype=tensor_type, shape=[]
114
+ data=tensor, dtype="", stype=tensor_type, shape=()
115
115
  )
116
116
 
117
117
  if num_arrays == 0:
118
118
  ordered_dict[EMPTY_TENSOR_KEY] = Array(
119
- data=b"", dtype="", stype=tensor_type, shape=[]
119
+ data=b"", dtype="", stype=tensor_type, shape=()
120
120
  )
121
121
  return ArrayRecord(ordered_dict, keep_input=keep_input)
122
122
 
@@ -1,4 +1,4 @@
1
- # Copyright 2023 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.
@@ -25,10 +25,12 @@ from typing import Any, Callable, Optional, Union, cast
25
25
 
26
26
  import grpc
27
27
 
28
+ from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter
28
29
  from flwr.common.constant import MAX_RETRY_DELAY
29
30
  from flwr.common.logger import log
30
31
  from flwr.common.typing import RunNotRunningException
31
32
  from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
33
+ from flwr.proto.fleet_pb2_grpc import FleetStub
32
34
  from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
33
35
  from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
34
36
 
@@ -166,15 +168,15 @@ class RetryInvoker:
166
168
 
167
169
  Examples
168
170
  --------
169
- Initialize a `RetryInvoker` with exponential backoff and invoke a function:
170
-
171
- >>> invoker = RetryInvoker(
172
- ... exponential, # Or use `lambda: exponential(3, 2)` to pass arguments
173
- ... grpc.RpcError,
174
- ... max_tries=3,
175
- ... max_time=None,
176
- ... )
177
- >>> invoker.invoke(my_func, arg1, arg2, kw1=kwarg1)
171
+ Initialize a `RetryInvoker` with exponential backoff and invoke a function::
172
+
173
+ invoker = RetryInvoker(
174
+ exponential, # Or use `lambda: exponential(3, 2)` to pass arguments
175
+ grpc.RpcError,
176
+ max_tries=3,
177
+ max_time=None,
178
+ )
179
+ invoker.invoke(my_func, arg1, arg2, kw1=kwarg1)
178
180
  """
179
181
 
180
182
  # pylint: disable-next=too-many-arguments
@@ -366,7 +368,9 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
366
368
 
367
369
 
368
370
  def _wrap_stub(
369
- stub: Union[ServerAppIoStub, ClientAppIoStub, SimulationIoStub],
371
+ stub: Union[
372
+ ServerAppIoStub, ClientAppIoStub, SimulationIoStub, FleetStub, GrpcAdapter
373
+ ],
370
374
  retry_invoker: RetryInvoker,
371
375
  ) -> None:
372
376
  """Wrap a gRPC stub with a retry invoker."""
@@ -1,4 +1,4 @@
1
- # Copyright 2023 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 2023 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.