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.
Files changed (248) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/cli/__init__.py +1 -1
  3. flwr/cli/app.py +21 -2
  4. flwr/cli/build.py +1 -1
  5. flwr/cli/cli_user_auth_interceptor.py +1 -1
  6. flwr/cli/config_utils.py +53 -17
  7. flwr/cli/example.py +1 -1
  8. flwr/cli/install.py +1 -1
  9. flwr/cli/log.py +1 -1
  10. flwr/cli/login/__init__.py +1 -1
  11. flwr/cli/login/login.py +12 -1
  12. flwr/cli/ls.py +1 -1
  13. flwr/cli/new/__init__.py +1 -1
  14. flwr/cli/new/new.py +4 -4
  15. flwr/cli/new/templates/__init__.py +1 -1
  16. flwr/cli/new/templates/app/__init__.py +1 -1
  17. flwr/cli/new/templates/app/code/__init__.py +1 -1
  18. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
  19. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +5 -5
  20. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
  21. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  22. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
  23. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  24. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  26. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  28. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  29. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  30. flwr/cli/run/__init__.py +1 -1
  31. flwr/cli/run/run.py +6 -10
  32. flwr/cli/stop.py +1 -1
  33. flwr/cli/utils.py +11 -12
  34. flwr/client/__init__.py +1 -1
  35. flwr/client/app.py +58 -56
  36. flwr/client/client.py +1 -1
  37. flwr/client/client_app.py +231 -166
  38. flwr/client/clientapp/__init__.py +1 -1
  39. flwr/client/clientapp/app.py +3 -3
  40. flwr/client/clientapp/clientappio_servicer.py +1 -1
  41. flwr/client/clientapp/utils.py +1 -1
  42. flwr/client/dpfedavg_numpy_client.py +1 -1
  43. flwr/client/grpc_adapter_client/__init__.py +1 -1
  44. flwr/client/grpc_adapter_client/connection.py +1 -1
  45. flwr/client/grpc_client/__init__.py +1 -1
  46. flwr/client/grpc_client/connection.py +37 -34
  47. flwr/client/grpc_rere_client/__init__.py +1 -1
  48. flwr/client/grpc_rere_client/client_interceptor.py +1 -1
  49. flwr/client/grpc_rere_client/connection.py +1 -1
  50. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  51. flwr/client/heartbeat.py +1 -1
  52. flwr/client/message_handler/__init__.py +1 -1
  53. flwr/client/message_handler/message_handler.py +28 -28
  54. flwr/client/mod/__init__.py +3 -3
  55. flwr/client/mod/centraldp_mods.py +8 -8
  56. flwr/client/mod/comms_mods.py +17 -23
  57. flwr/client/mod/localdp_mod.py +10 -10
  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 +32 -32
  61. flwr/client/mod/utils.py +1 -1
  62. flwr/client/nodestate/__init__.py +1 -1
  63. flwr/client/nodestate/in_memory_nodestate.py +1 -1
  64. flwr/client/nodestate/nodestate.py +1 -1
  65. flwr/client/nodestate/nodestate_factory.py +1 -1
  66. flwr/client/numpy_client.py +1 -1
  67. flwr/client/rest_client/__init__.py +1 -1
  68. flwr/client/rest_client/connection.py +1 -1
  69. flwr/client/run_info_store.py +3 -3
  70. flwr/client/supernode/__init__.py +1 -1
  71. flwr/client/supernode/app.py +1 -1
  72. flwr/client/typing.py +1 -1
  73. flwr/common/__init__.py +13 -5
  74. flwr/common/address.py +1 -1
  75. flwr/common/args.py +1 -1
  76. flwr/common/auth_plugin/__init__.py +1 -1
  77. flwr/common/auth_plugin/auth_plugin.py +1 -1
  78. flwr/common/config.py +5 -5
  79. flwr/common/constant.py +7 -7
  80. flwr/common/context.py +5 -5
  81. flwr/common/date.py +1 -1
  82. flwr/common/differential_privacy.py +1 -1
  83. flwr/common/differential_privacy_constants.py +1 -1
  84. flwr/common/dp.py +1 -1
  85. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  86. flwr/common/exit/exit.py +6 -6
  87. flwr/common/exit_handlers.py +1 -1
  88. flwr/common/grpc.py +1 -1
  89. flwr/common/logger.py +3 -3
  90. flwr/common/message.py +344 -102
  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 +9 -5
  95. flwr/common/record/arrayrecord.py +626 -0
  96. flwr/common/record/{configsrecord.py → configrecord.py} +83 -37
  97. flwr/common/record/conversion_utils.py +2 -2
  98. flwr/common/record/{metricsrecord.py → metricrecord.py} +90 -44
  99. flwr/common/record/recorddict.py +337 -0
  100. flwr/common/record/typeddict.py +1 -1
  101. flwr/common/recorddict_compat.py +410 -0
  102. flwr/common/retry_invoker.py +10 -10
  103. flwr/common/secure_aggregation/__init__.py +1 -1
  104. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  105. flwr/common/secure_aggregation/crypto/shamir.py +52 -30
  106. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
  107. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  108. flwr/common/secure_aggregation/quantization.py +1 -1
  109. flwr/common/secure_aggregation/secaggplus_constants.py +2 -2
  110. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  111. flwr/common/serde.py +67 -72
  112. flwr/common/telemetry.py +2 -2
  113. flwr/common/typing.py +9 -9
  114. flwr/common/version.py +1 -1
  115. flwr/proto/__init__.py +1 -1
  116. flwr/proto/exec_pb2.py +3 -3
  117. flwr/proto/exec_pb2.pyi +3 -3
  118. flwr/proto/message_pb2.py +12 -12
  119. flwr/proto/message_pb2.pyi +9 -9
  120. flwr/proto/recorddict_pb2.py +70 -0
  121. flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
  122. flwr/proto/run_pb2.py +31 -31
  123. flwr/proto/run_pb2.pyi +3 -3
  124. flwr/server/__init__.py +4 -2
  125. flwr/server/app.py +67 -12
  126. flwr/server/client_manager.py +1 -1
  127. flwr/server/client_proxy.py +1 -1
  128. flwr/server/compat/__init__.py +3 -3
  129. flwr/server/compat/app.py +12 -12
  130. flwr/server/compat/app_utils.py +17 -17
  131. flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +39 -39
  132. flwr/server/compat/legacy_context.py +1 -1
  133. flwr/server/criterion.py +1 -1
  134. flwr/server/fleet_event_log_interceptor.py +94 -0
  135. flwr/server/{driver → grid}/__init__.py +8 -7
  136. flwr/server/{driver/driver.py → grid/grid.py} +48 -19
  137. flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +87 -64
  138. flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +24 -34
  139. flwr/server/history.py +1 -1
  140. flwr/server/run_serverapp.py +5 -5
  141. flwr/server/server.py +1 -1
  142. flwr/server/server_app.py +98 -71
  143. flwr/server/server_config.py +1 -1
  144. flwr/server/serverapp/__init__.py +1 -1
  145. flwr/server/serverapp/app.py +11 -11
  146. flwr/server/serverapp_components.py +1 -1
  147. flwr/server/strategy/__init__.py +1 -1
  148. flwr/server/strategy/aggregate.py +1 -1
  149. flwr/server/strategy/bulyan.py +2 -2
  150. flwr/server/strategy/dp_adaptive_clipping.py +17 -17
  151. flwr/server/strategy/dp_fixed_clipping.py +17 -17
  152. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  153. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  154. flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
  155. flwr/server/strategy/fedadagrad.py +1 -1
  156. flwr/server/strategy/fedadam.py +1 -1
  157. flwr/server/strategy/fedavg.py +1 -1
  158. flwr/server/strategy/fedavg_android.py +1 -1
  159. flwr/server/strategy/fedavgm.py +1 -1
  160. flwr/server/strategy/fedmedian.py +1 -1
  161. flwr/server/strategy/fedopt.py +1 -1
  162. flwr/server/strategy/fedprox.py +1 -1
  163. flwr/server/strategy/fedtrimmedavg.py +1 -1
  164. flwr/server/strategy/fedxgb_bagging.py +1 -1
  165. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  166. flwr/server/strategy/fedxgb_nn_avg.py +3 -2
  167. flwr/server/strategy/fedyogi.py +1 -1
  168. flwr/server/strategy/krum.py +1 -1
  169. flwr/server/strategy/qfedavg.py +1 -1
  170. flwr/server/strategy/strategy.py +1 -1
  171. flwr/server/superlink/__init__.py +1 -1
  172. flwr/server/superlink/ffs/__init__.py +1 -1
  173. flwr/server/superlink/ffs/disk_ffs.py +1 -1
  174. flwr/server/superlink/ffs/ffs.py +1 -1
  175. flwr/server/superlink/ffs/ffs_factory.py +1 -1
  176. flwr/server/superlink/fleet/__init__.py +1 -1
  177. flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
  178. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  179. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  180. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  181. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  182. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  183. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
  184. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  185. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
  186. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
  187. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  188. flwr/server/superlink/fleet/message_handler/message_handler.py +1 -1
  189. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  190. flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -1
  191. flwr/server/superlink/fleet/vce/__init__.py +1 -1
  192. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
  193. flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
  194. flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -3
  195. flwr/server/superlink/fleet/vce/vce_api.py +2 -4
  196. flwr/server/superlink/linkstate/__init__.py +1 -1
  197. flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -9
  198. flwr/server/superlink/linkstate/linkstate.py +5 -5
  199. flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
  200. flwr/server/superlink/linkstate/sqlite_linkstate.py +62 -28
  201. flwr/server/superlink/linkstate/utils.py +94 -28
  202. flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
  203. flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
  204. flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +4 -4
  205. flwr/server/superlink/simulation/__init__.py +1 -1
  206. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  207. flwr/server/superlink/simulation/simulationio_servicer.py +3 -3
  208. flwr/server/superlink/utils.py +1 -1
  209. flwr/server/typing.py +4 -4
  210. flwr/server/utils/__init__.py +1 -1
  211. flwr/server/utils/tensorboard.py +1 -1
  212. flwr/server/utils/validator.py +5 -5
  213. flwr/server/workflow/__init__.py +1 -1
  214. flwr/server/workflow/constant.py +1 -1
  215. flwr/server/workflow/default_workflows.py +49 -58
  216. flwr/server/workflow/secure_aggregation/__init__.py +1 -1
  217. flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
  218. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +49 -51
  219. flwr/simulation/__init__.py +1 -1
  220. flwr/simulation/app.py +3 -3
  221. flwr/simulation/legacy_app.py +1 -1
  222. flwr/simulation/ray_transport/__init__.py +1 -1
  223. flwr/simulation/ray_transport/ray_actor.py +5 -3
  224. flwr/simulation/ray_transport/ray_client_proxy.py +35 -33
  225. flwr/simulation/ray_transport/utils.py +1 -1
  226. flwr/simulation/run_simulation.py +17 -17
  227. flwr/simulation/simulationio_connection.py +1 -1
  228. flwr/superexec/__init__.py +1 -1
  229. flwr/superexec/app.py +1 -1
  230. flwr/superexec/deployment.py +5 -5
  231. flwr/superexec/exec_event_log_interceptor.py +135 -0
  232. flwr/superexec/exec_grpc.py +11 -5
  233. flwr/superexec/exec_servicer.py +3 -3
  234. flwr/superexec/exec_user_auth_interceptor.py +19 -3
  235. flwr/superexec/executor.py +4 -4
  236. flwr/superexec/simulation.py +4 -4
  237. {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/METADATA +3 -3
  238. flwr-1.18.0.dist-info/RECORD +332 -0
  239. flwr/common/record/parametersrecord.py +0 -339
  240. flwr/common/record/recordset.py +0 -209
  241. flwr/common/recordset_compat.py +0 -418
  242. flwr/proto/recordset_pb2.py +0 -70
  243. flwr-1.16.0.dist-info/LICENSE +0 -202
  244. flwr-1.16.0.dist-info/RECORD +0 -331
  245. /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
  246. /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
  247. {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/WHEEL +0 -0
  248. {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,337 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """RecordDict."""
16
+
17
+
18
+ from __future__ import annotations
19
+
20
+ from logging import WARN
21
+ from textwrap import indent
22
+ from typing import TypeVar, Union, cast
23
+
24
+ from ..logger import log
25
+ from .arrayrecord import ArrayRecord
26
+ from .configrecord import ConfigRecord
27
+ from .metricrecord import MetricRecord
28
+ from .typeddict import TypedDict
29
+
30
+ RecordType = Union[ArrayRecord, MetricRecord, ConfigRecord]
31
+
32
+
33
+ class _WarningTracker:
34
+ """A class to track warnings for deprecated properties."""
35
+
36
+ def __init__(self) -> None:
37
+ # These variables are used to ensure that the deprecation warnings
38
+ # for the deprecated properties/class are logged only once.
39
+ self.recordset_init_logged = False
40
+ self.recorddict_init_logged = False
41
+ self.parameters_records_logged = False
42
+ self.metrics_records_logged = False
43
+ self.configs_records_logged = False
44
+
45
+
46
+ _warning_tracker = _WarningTracker()
47
+
48
+ T = TypeVar("T")
49
+
50
+
51
+ def _check_key(key: str) -> None:
52
+ if not isinstance(key, str):
53
+ raise TypeError(
54
+ f"Expected `{str.__name__}`, but "
55
+ f"received `{type(key).__name__}` for the key."
56
+ )
57
+
58
+
59
+ def _check_value(value: RecordType) -> None:
60
+ if not isinstance(value, (ArrayRecord, MetricRecord, ConfigRecord)):
61
+ raise TypeError(
62
+ f"Expected `{ArrayRecord.__name__}`, `{MetricRecord.__name__}`, "
63
+ f"or `{ConfigRecord.__name__}` but received "
64
+ f"`{type(value).__name__}` for the value."
65
+ )
66
+
67
+
68
+ class _SyncedDict(TypedDict[str, T]):
69
+ """A synchronized dictionary that mirrors changes to an underlying RecordDict.
70
+
71
+ This dictionary ensures that any modifications (set or delete operations)
72
+ are automatically reflected in the associated `RecordDict`. Only values of
73
+ the specified `allowed_type` are permitted.
74
+ """
75
+
76
+ def __init__(self, ref_recorddict: RecordDict, allowed_type: type[T]) -> None:
77
+ if not issubclass(allowed_type, (ArrayRecord, MetricRecord, ConfigRecord)):
78
+ raise TypeError(f"{allowed_type} is not a valid type.")
79
+ super().__init__(_check_key, self.check_value)
80
+ self.recorddict = ref_recorddict
81
+ self.allowed_type = allowed_type
82
+
83
+ def __setitem__(self, key: str, value: T) -> None:
84
+ super().__setitem__(key, value)
85
+ self.recorddict[key] = cast(RecordType, value)
86
+
87
+ def __delitem__(self, key: str) -> None:
88
+ super().__delitem__(key)
89
+ del self.recorddict[key]
90
+
91
+ def check_value(self, value: T) -> None:
92
+ """Check if value is of expected type."""
93
+ if not isinstance(value, self.allowed_type):
94
+ raise TypeError(
95
+ f"Expected `{self.allowed_type.__name__}`, but "
96
+ f"received `{type(value).__name__}` for the value."
97
+ )
98
+
99
+
100
+ class RecordDict(TypedDict[str, RecordType]):
101
+ """RecordDict stores groups of arrays, metrics and configs.
102
+
103
+ A :class:`RecordDict` is the unified mechanism by which arrays,
104
+ metrics and configs can be either stored as part of a :class:`Context`
105
+ in your apps or communicated as part of a :class:`Message` between
106
+ your apps.
107
+
108
+ Parameters
109
+ ----------
110
+ records : Optional[dict[str, RecordType]]
111
+ A dictionary mapping string keys to record instances, where each value
112
+ is either a :class:`ParametersRecord`, :class:`MetricsRecord`,
113
+ or :class:`ConfigsRecord`.
114
+
115
+ Examples
116
+ --------
117
+ A :class:`RecordDict` can hold three types of records, each designed
118
+ with an specific purpose. What is common to all of them is that they
119
+ are Python dictionaries designed to ensure that each key-value pair
120
+ adheres to specified data types.
121
+
122
+ Let's see an example::
123
+
124
+ from flwr.common import RecordDict
125
+ from flwr.common import ArrayRecord, ConfigRecord, MetricRecord
126
+
127
+ # Let's begin with an empty record
128
+ my_records = RecordDict()
129
+
130
+ # We can create a ConfigRecord
131
+ c_record = ConfigRecord({"lr": 0.1, "batch-size": 128})
132
+ # Adding it to the RecordDict would look like this
133
+ my_records["my_config"] = c_record
134
+
135
+ # We can create a MetricRecord following a similar process
136
+ m_record = MetricRecord({"accuracy": 0.93, "losses": [0.23, 0.1]})
137
+ # Adding it to the RecordDict would look like this
138
+ my_records["my_metrics"] = m_record
139
+
140
+ Adding an :code:`ArrayRecord` follows the same steps as above but first,
141
+ the array needs to be serialized and represented as a :code:`flwr.common.Array`.
142
+ For example::
143
+
144
+ from flwr.common import Array
145
+ # Creating an ArrayRecord would look like this
146
+ arr_np = np.random.randn(3, 3)
147
+
148
+ # You can use the built-in tool to serialize the array
149
+ arr = Array(arr_np)
150
+
151
+ # Finally, create the record
152
+ arr_record = ArrayRecord({"my_array": arr})
153
+
154
+ # Adding it to the RecordDict would look like this
155
+ my_records["my_parameters"] = arr_record
156
+
157
+ For additional examples on how to construct each of the records types shown
158
+ above, please refer to the documentation for :code:`ConfigRecord`,
159
+ :code:`MetricRecord` and :code:`ArrayRecord`.
160
+ """
161
+
162
+ def __init__(
163
+ self,
164
+ records: dict[str, RecordType] | None = None,
165
+ *,
166
+ parameters_records: dict[str, ArrayRecord] | None = None,
167
+ metrics_records: dict[str, MetricRecord] | None = None,
168
+ configs_records: dict[str, ConfigRecord] | None = None,
169
+ ) -> None:
170
+ super().__init__(_check_key, _check_value)
171
+
172
+ # Warning for deprecated usage
173
+ if (
174
+ parameters_records is not None
175
+ or metrics_records is not None
176
+ or configs_records is not None
177
+ ):
178
+ log(
179
+ WARN,
180
+ "The arguments `parameters_records`, `metrics_records`, and "
181
+ "`configs_records` of `RecordDict` are deprecated and will "
182
+ "be removed in a future release. "
183
+ "Please pass all records using the `records` argument instead.",
184
+ )
185
+ records = records or {}
186
+ records.update(parameters_records or {})
187
+ records.update(metrics_records or {})
188
+ records.update(configs_records or {})
189
+
190
+ if records is not None:
191
+ for key, record in records.items():
192
+ self[key] = record
193
+
194
+ @property
195
+ def array_records(self) -> TypedDict[str, ArrayRecord]:
196
+ """Dictionary holding only ArrayRecord instances."""
197
+ synced_dict = _SyncedDict[ArrayRecord](self, ArrayRecord)
198
+ for key, record in self.items():
199
+ if isinstance(record, ArrayRecord):
200
+ synced_dict[key] = record
201
+ return synced_dict
202
+
203
+ @property
204
+ def metric_records(self) -> TypedDict[str, MetricRecord]:
205
+ """Dictionary holding only MetricRecord instances."""
206
+ synced_dict = _SyncedDict[MetricRecord](self, MetricRecord)
207
+ for key, record in self.items():
208
+ if isinstance(record, MetricRecord):
209
+ synced_dict[key] = record
210
+ return synced_dict
211
+
212
+ @property
213
+ def config_records(self) -> TypedDict[str, ConfigRecord]:
214
+ """Dictionary holding only ConfigRecord instances."""
215
+ synced_dict = _SyncedDict[ConfigRecord](self, ConfigRecord)
216
+ for key, record in self.items():
217
+ if isinstance(record, ConfigRecord):
218
+ synced_dict[key] = record
219
+ return synced_dict
220
+
221
+ def __repr__(self) -> str:
222
+ """Return a string representation of this instance."""
223
+ flds = ("array_records", "metric_records", "config_records")
224
+ fld_views = [f"{fld}={dict(getattr(self, fld))!r}" for fld in flds]
225
+ view = indent(",\n".join(fld_views), " ")
226
+ return f"{self.__class__.__qualname__}(\n{view}\n)"
227
+
228
+ def __setitem__(self, key: str, value: RecordType) -> None:
229
+ """Set the given key to the given value after type checking."""
230
+ original_value = self.get(key, None)
231
+ super().__setitem__(key, value)
232
+ if original_value is not None and not isinstance(value, type(original_value)):
233
+ log(
234
+ WARN,
235
+ "Key '%s' was overwritten: record of type `%s` replaced with type `%s`",
236
+ key,
237
+ type(original_value).__name__,
238
+ type(value).__name__,
239
+ )
240
+
241
+ @property
242
+ def parameters_records(self) -> TypedDict[str, ArrayRecord]:
243
+ """Deprecated property.
244
+
245
+ Use ``array_records`` instead.
246
+ """
247
+ if _warning_tracker.parameters_records_logged:
248
+ _warning_tracker.parameters_records_logged = True
249
+ log(
250
+ WARN,
251
+ "The `parameters_records` property of `RecordDict` "
252
+ "(formerly `RecordSet`) is deprecated and will be removed in a "
253
+ "future release. Please use the `array_records` property instead.",
254
+ )
255
+ return self.array_records
256
+
257
+ @property
258
+ def metrics_records(self) -> TypedDict[str, MetricRecord]:
259
+ """Deprecated property.
260
+
261
+ Use ``metric_records`` instead.
262
+ """
263
+ if not _warning_tracker.metrics_records_logged:
264
+ _warning_tracker.metrics_records_logged = True
265
+ log(
266
+ WARN,
267
+ "The `metrics_records` property of `RecordDict` "
268
+ "(formerly `RecordSet`) is deprecated and will be removed in a "
269
+ "future release. Please use the `metric_records` property instead.",
270
+ )
271
+ return self.metric_records
272
+
273
+ @property
274
+ def configs_records(self) -> TypedDict[str, ConfigRecord]:
275
+ """Deprecated property.
276
+
277
+ Use ``config_records`` instead.
278
+ """
279
+ if not _warning_tracker.configs_records_logged:
280
+ _warning_tracker.configs_records_logged = True
281
+ log(
282
+ WARN,
283
+ "The `configs_records` property of `RecordDict` "
284
+ "(formerly `RecordSet`) is deprecated and will be removed in a "
285
+ "future release. Please use the `config_records` property instead.",
286
+ )
287
+ return self.config_records
288
+
289
+
290
+ class RecordSet(RecordDict):
291
+ """Deprecated class ``RecordSet``, use ``RecordDict`` instead.
292
+
293
+ This class exists solely for backward compatibility with legacy
294
+ code that previously used ``RecordSet``. It has been renamed
295
+ to ``RecordDict`` and will be removed in a future release.
296
+
297
+ .. warning::
298
+ ``RecordSet`` is deprecated and will be removed in a future release.
299
+ Use ``RecordDict`` instead.
300
+
301
+ Examples
302
+ --------
303
+ Legacy (deprecated) usage::
304
+
305
+ from flwr.common import RecordSet
306
+
307
+ my_content = RecordSet()
308
+
309
+ Updated usage::
310
+
311
+ from flwr.common import RecordDict
312
+
313
+ my_content = RecordDict()
314
+ """
315
+
316
+ def __init__(
317
+ self,
318
+ records: dict[str, RecordType] | None = None,
319
+ *,
320
+ parameters_records: dict[str, ArrayRecord] | None = None,
321
+ metrics_records: dict[str, MetricRecord] | None = None,
322
+ configs_records: dict[str, ConfigRecord] | None = None,
323
+ ) -> None:
324
+ if not _warning_tracker.recordset_init_logged:
325
+ _warning_tracker.recordset_init_logged = True
326
+ log(
327
+ WARN,
328
+ "The `RecordSet` class has been renamed to `RecordDict`. "
329
+ "Support for `RecordSet` will be removed in a future release. "
330
+ "Please update your code accordingly.",
331
+ )
332
+ super().__init__(
333
+ records,
334
+ parameters_records=parameters_records,
335
+ metrics_records=metrics_records,
336
+ configs_records=configs_records,
337
+ )
@@ -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.