flwr 1.23.0__py3-none-any.whl → 1.24.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 (292) hide show
  1. flwr/__init__.py +16 -5
  2. flwr/app/error.py +2 -2
  3. flwr/app/exception.py +3 -3
  4. flwr/cli/app.py +19 -0
  5. flwr/cli/app_cmd/__init__.py +23 -0
  6. flwr/cli/app_cmd/publish.py +285 -0
  7. flwr/cli/app_cmd/review.py +252 -0
  8. flwr/cli/auth_plugin/auth_plugin.py +4 -5
  9. flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
  10. flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
  11. flwr/cli/build.py +60 -18
  12. flwr/cli/cli_account_auth_interceptor.py +24 -7
  13. flwr/cli/config_utils.py +101 -13
  14. flwr/cli/federation/__init__.py +24 -0
  15. flwr/cli/federation/ls.py +140 -0
  16. flwr/cli/federation/show.py +317 -0
  17. flwr/cli/install.py +91 -13
  18. flwr/cli/log.py +52 -9
  19. flwr/cli/login/login.py +7 -4
  20. flwr/cli/ls.py +170 -130
  21. flwr/cli/new/new.py +33 -50
  22. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
  23. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  24. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  26. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  28. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  29. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  30. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
  31. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
  33. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
  34. flwr/cli/pull.py +10 -5
  35. flwr/cli/run/run.py +77 -30
  36. flwr/cli/run_utils.py +130 -0
  37. flwr/cli/stop.py +25 -7
  38. flwr/cli/supernode/ls.py +16 -8
  39. flwr/cli/supernode/register.py +9 -4
  40. flwr/cli/supernode/unregister.py +5 -3
  41. flwr/cli/utils.py +376 -16
  42. flwr/client/__init__.py +1 -1
  43. flwr/client/dpfedavg_numpy_client.py +4 -1
  44. flwr/client/grpc_adapter_client/connection.py +6 -7
  45. flwr/client/grpc_rere_client/connection.py +10 -11
  46. flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
  47. flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
  48. flwr/client/message_handler/message_handler.py +2 -2
  49. flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
  50. flwr/client/numpy_client.py +1 -1
  51. flwr/client/rest_client/connection.py +12 -14
  52. flwr/client/run_info_store.py +4 -5
  53. flwr/client/typing.py +1 -1
  54. flwr/clientapp/client_app.py +9 -10
  55. flwr/clientapp/mod/centraldp_mods.py +16 -17
  56. flwr/clientapp/mod/localdp_mod.py +8 -9
  57. flwr/clientapp/typing.py +1 -1
  58. flwr/clientapp/utils.py +3 -3
  59. flwr/common/address.py +1 -2
  60. flwr/common/args.py +3 -4
  61. flwr/common/config.py +13 -16
  62. flwr/common/constant.py +5 -2
  63. flwr/common/differential_privacy.py +3 -4
  64. flwr/common/event_log_plugin/event_log_plugin.py +3 -4
  65. flwr/common/exit/exit.py +15 -2
  66. flwr/common/exit/exit_code.py +19 -0
  67. flwr/common/exit/exit_handler.py +6 -2
  68. flwr/common/exit/signal_handler.py +5 -5
  69. flwr/common/grpc.py +6 -6
  70. flwr/common/inflatable_protobuf_utils.py +1 -1
  71. flwr/common/inflatable_utils.py +38 -21
  72. flwr/common/logger.py +19 -19
  73. flwr/common/message.py +4 -4
  74. flwr/common/object_ref.py +7 -7
  75. flwr/common/record/array.py +3 -3
  76. flwr/common/record/arrayrecord.py +18 -30
  77. flwr/common/record/configrecord.py +3 -3
  78. flwr/common/record/recorddict.py +5 -5
  79. flwr/common/record/typeddict.py +9 -2
  80. flwr/common/recorddict_compat.py +7 -10
  81. flwr/common/retry_invoker.py +20 -20
  82. flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
  83. flwr/common/serde.py +5 -4
  84. flwr/common/serde_utils.py +2 -2
  85. flwr/common/telemetry.py +9 -5
  86. flwr/common/typing.py +52 -37
  87. flwr/compat/client/app.py +38 -37
  88. flwr/compat/client/grpc_client/connection.py +11 -11
  89. flwr/compat/server/app.py +5 -6
  90. flwr/proto/appio_pb2.py +13 -3
  91. flwr/proto/appio_pb2.pyi +134 -65
  92. flwr/proto/appio_pb2_grpc.py +20 -0
  93. flwr/proto/appio_pb2_grpc.pyi +27 -0
  94. flwr/proto/clientappio_pb2.py +17 -7
  95. flwr/proto/clientappio_pb2.pyi +15 -0
  96. flwr/proto/clientappio_pb2_grpc.py +206 -40
  97. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  98. flwr/proto/control_pb2.py +71 -52
  99. flwr/proto/control_pb2.pyi +277 -111
  100. flwr/proto/control_pb2_grpc.py +249 -40
  101. flwr/proto/control_pb2_grpc.pyi +185 -52
  102. flwr/proto/error_pb2.py +13 -3
  103. flwr/proto/error_pb2.pyi +24 -6
  104. flwr/proto/error_pb2_grpc.py +20 -0
  105. flwr/proto/error_pb2_grpc.pyi +27 -0
  106. flwr/proto/fab_pb2.py +14 -4
  107. flwr/proto/fab_pb2.pyi +59 -31
  108. flwr/proto/fab_pb2_grpc.py +20 -0
  109. flwr/proto/fab_pb2_grpc.pyi +27 -0
  110. flwr/proto/federation_pb2.py +38 -0
  111. flwr/proto/federation_pb2.pyi +56 -0
  112. flwr/proto/federation_pb2_grpc.py +24 -0
  113. flwr/proto/federation_pb2_grpc.pyi +31 -0
  114. flwr/proto/fleet_pb2.py +14 -4
  115. flwr/proto/fleet_pb2.pyi +137 -61
  116. flwr/proto/fleet_pb2_grpc.py +189 -48
  117. flwr/proto/fleet_pb2_grpc.pyi +175 -61
  118. flwr/proto/grpcadapter_pb2.py +14 -4
  119. flwr/proto/grpcadapter_pb2.pyi +38 -16
  120. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  121. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  122. flwr/proto/heartbeat_pb2.py +17 -7
  123. flwr/proto/heartbeat_pb2.pyi +51 -22
  124. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  125. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  126. flwr/proto/log_pb2.py +13 -3
  127. flwr/proto/log_pb2.pyi +34 -11
  128. flwr/proto/log_pb2_grpc.py +20 -0
  129. flwr/proto/log_pb2_grpc.pyi +27 -0
  130. flwr/proto/message_pb2.py +15 -5
  131. flwr/proto/message_pb2.pyi +154 -86
  132. flwr/proto/message_pb2_grpc.py +20 -0
  133. flwr/proto/message_pb2_grpc.pyi +27 -0
  134. flwr/proto/node_pb2.py +15 -5
  135. flwr/proto/node_pb2.pyi +50 -25
  136. flwr/proto/node_pb2_grpc.py +20 -0
  137. flwr/proto/node_pb2_grpc.pyi +27 -0
  138. flwr/proto/recorddict_pb2.py +13 -3
  139. flwr/proto/recorddict_pb2.pyi +184 -107
  140. flwr/proto/recorddict_pb2_grpc.py +20 -0
  141. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  142. flwr/proto/run_pb2.py +40 -31
  143. flwr/proto/run_pb2.pyi +149 -84
  144. flwr/proto/run_pb2_grpc.py +20 -0
  145. flwr/proto/run_pb2_grpc.pyi +27 -0
  146. flwr/proto/serverappio_pb2.py +13 -3
  147. flwr/proto/serverappio_pb2.pyi +32 -8
  148. flwr/proto/serverappio_pb2_grpc.py +246 -65
  149. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  150. flwr/proto/simulationio_pb2.py +16 -8
  151. flwr/proto/simulationio_pb2.pyi +15 -0
  152. flwr/proto/simulationio_pb2_grpc.py +162 -41
  153. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  154. flwr/proto/transport_pb2.py +20 -10
  155. flwr/proto/transport_pb2.pyi +249 -160
  156. flwr/proto/transport_pb2_grpc.py +35 -4
  157. flwr/proto/transport_pb2_grpc.pyi +38 -8
  158. flwr/server/app.py +38 -17
  159. flwr/server/client_manager.py +4 -5
  160. flwr/server/client_proxy.py +10 -11
  161. flwr/server/compat/app.py +4 -5
  162. flwr/server/compat/app_utils.py +2 -1
  163. flwr/server/compat/grid_client_proxy.py +10 -12
  164. flwr/server/compat/legacy_context.py +3 -4
  165. flwr/server/fleet_event_log_interceptor.py +2 -1
  166. flwr/server/grid/grid.py +2 -3
  167. flwr/server/grid/grpc_grid.py +10 -8
  168. flwr/server/grid/inmemory_grid.py +4 -4
  169. flwr/server/run_serverapp.py +2 -3
  170. flwr/server/server.py +34 -39
  171. flwr/server/server_app.py +7 -8
  172. flwr/server/server_config.py +1 -2
  173. flwr/server/serverapp/app.py +34 -28
  174. flwr/server/serverapp_components.py +4 -5
  175. flwr/server/strategy/aggregate.py +9 -8
  176. flwr/server/strategy/bulyan.py +13 -11
  177. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  178. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  179. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  180. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  181. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  182. flwr/server/strategy/fedadagrad.py +18 -14
  183. flwr/server/strategy/fedadam.py +16 -14
  184. flwr/server/strategy/fedavg.py +16 -17
  185. flwr/server/strategy/fedavg_android.py +15 -15
  186. flwr/server/strategy/fedavgm.py +21 -18
  187. flwr/server/strategy/fedmedian.py +2 -3
  188. flwr/server/strategy/fedopt.py +11 -10
  189. flwr/server/strategy/fedprox.py +10 -9
  190. flwr/server/strategy/fedtrimmedavg.py +12 -11
  191. flwr/server/strategy/fedxgb_bagging.py +13 -11
  192. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  193. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  194. flwr/server/strategy/fedyogi.py +16 -14
  195. flwr/server/strategy/krum.py +12 -11
  196. flwr/server/strategy/qfedavg.py +16 -15
  197. flwr/server/strategy/strategy.py +6 -9
  198. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
  199. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  200. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  201. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  202. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  203. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
  204. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
  205. flwr/server/superlink/fleet/message_handler/message_handler.py +34 -28
  206. flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
  207. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  208. flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
  209. flwr/server/superlink/fleet/vce/vce_api.py +15 -9
  210. flwr/server/superlink/linkstate/in_memory_linkstate.py +115 -150
  211. flwr/server/superlink/linkstate/linkstate.py +59 -43
  212. flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
  213. flwr/server/superlink/linkstate/sqlite_linkstate.py +447 -438
  214. flwr/server/superlink/linkstate/utils.py +6 -6
  215. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  216. flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
  217. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  218. flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
  219. flwr/server/superlink/utils.py +4 -6
  220. flwr/server/typing.py +1 -1
  221. flwr/server/utils/tensorboard.py +15 -8
  222. flwr/server/workflow/default_workflows.py +5 -5
  223. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  224. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
  225. flwr/serverapp/strategy/bulyan.py +16 -15
  226. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  227. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  228. flwr/serverapp/strategy/fedadagrad.py +10 -11
  229. flwr/serverapp/strategy/fedadam.py +10 -11
  230. flwr/serverapp/strategy/fedavg.py +9 -10
  231. flwr/serverapp/strategy/fedavgm.py +17 -16
  232. flwr/serverapp/strategy/fedmedian.py +2 -2
  233. flwr/serverapp/strategy/fedopt.py +10 -11
  234. flwr/serverapp/strategy/fedprox.py +7 -8
  235. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  236. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  237. flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
  238. flwr/serverapp/strategy/fedyogi.py +9 -11
  239. flwr/serverapp/strategy/krum.py +7 -7
  240. flwr/serverapp/strategy/multikrum.py +9 -9
  241. flwr/serverapp/strategy/qfedavg.py +17 -16
  242. flwr/serverapp/strategy/strategy.py +6 -9
  243. flwr/serverapp/strategy/strategy_utils.py +7 -8
  244. flwr/simulation/app.py +46 -42
  245. flwr/simulation/legacy_app.py +12 -12
  246. flwr/simulation/ray_transport/ray_actor.py +10 -11
  247. flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
  248. flwr/simulation/run_simulation.py +43 -43
  249. flwr/simulation/simulationio_connection.py +4 -4
  250. flwr/supercore/cli/flower_superexec.py +3 -4
  251. flwr/supercore/constant.py +31 -1
  252. flwr/supercore/corestate/corestate.py +24 -3
  253. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  254. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  255. flwr/supercore/ffs/disk_ffs.py +1 -2
  256. flwr/supercore/ffs/ffs.py +1 -2
  257. flwr/supercore/ffs/ffs_factory.py +1 -2
  258. flwr/{common → supercore}/heartbeat.py +20 -25
  259. flwr/supercore/object_store/in_memory_object_store.py +1 -2
  260. flwr/supercore/object_store/object_store.py +1 -2
  261. flwr/supercore/object_store/object_store_factory.py +1 -2
  262. flwr/supercore/object_store/sqlite_object_store.py +8 -7
  263. flwr/supercore/primitives/asymmetric.py +1 -1
  264. flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
  265. flwr/supercore/sqlite_mixin.py +37 -34
  266. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  267. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  268. flwr/supercore/superexec/run_superexec.py +9 -13
  269. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  270. flwr/superlink/auth_plugin/auth_plugin.py +6 -9
  271. flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
  272. flwr/superlink/federation/__init__.py +24 -0
  273. flwr/superlink/federation/federation_manager.py +64 -0
  274. flwr/superlink/federation/noop_federation_manager.py +71 -0
  275. flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
  276. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  277. flwr/superlink/servicer/control/control_grpc.py +5 -6
  278. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  279. flwr/superlink/servicer/control/control_servicer.py +102 -18
  280. flwr/supernode/cli/flower_supernode.py +58 -3
  281. flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
  282. flwr/supernode/nodestate/nodestate.py +7 -8
  283. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  284. flwr/supernode/runtime/run_clientapp.py +41 -22
  285. flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
  286. flwr/supernode/start_client_internal.py +158 -42
  287. {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
  288. flwr-1.24.0.dist-info/RECORD +454 -0
  289. flwr/supercore/object_store/utils.py +0 -43
  290. flwr-1.23.0.dist-info/RECORD +0 -439
  291. {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
  292. {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
@@ -63,7 +63,7 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
63
63
 
64
64
  A typed dictionary (``str`` to :class:`Array`) that can store named arrays,
65
65
  including model parameters, gradients, embeddings or non-parameter arrays.
66
- Internally, this behaves similarly to an ``OrderedDict[str, Array]``.
66
+ Internally, this behaves similarly to an ``dict[str, Array]``.
67
67
  An ``ArrayRecord`` can be viewed as an equivalent to PyTorch's ``state_dict``,
68
68
  but it holds arrays in a serialized form.
69
69
 
@@ -80,13 +80,13 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
80
80
 
81
81
  Parameters
82
82
  ----------
83
- array_dict : Optional[OrderedDict[str, Array]] (default: None)
83
+ array_dict : Optional[dict[str, Array]] (default: None)
84
84
  An existing dictionary containing named :class:`Array` instances. If
85
85
  provided, these entries will be used directly to populate the record.
86
86
  numpy_ndarrays : Optional[list[NDArray]] (default: None)
87
87
  A list of NumPy arrays. Each array will be automatically converted
88
88
  into an :class:`Array` and stored in this record with generated keys.
89
- torch_state_dict : Optional[OrderedDict[str, torch.Tensor]] (default: None)
89
+ torch_state_dict : Optional[dict[str, torch.Tensor]] (default: None)
90
90
  A PyTorch ``state_dict`` (``str`` keys to ``torch.Tensor`` values). Each
91
91
  tensor will be converted into an :class:`Array` and stored in this record.
92
92
  keep_input : bool (default: True)
@@ -127,31 +127,23 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
127
127
  """
128
128
 
129
129
  @overload
130
- def __init__(self) -> None: ... # noqa: E704
130
+ def __init__(self) -> None: ...
131
131
 
132
132
  @overload
133
- def __init__( # noqa: E704
134
- self, array_dict: OrderedDict[str, Array], *, keep_input: bool = True
133
+ def __init__(
134
+ self, array_dict: dict[str, Array], *, keep_input: bool = True
135
135
  ) -> None: ...
136
136
 
137
137
  @overload
138
- def __init__( # noqa: E704
138
+ def __init__(
139
139
  self, numpy_ndarrays: list[NDArray], *, keep_input: bool = True
140
140
  ) -> None: ...
141
141
 
142
142
  @overload
143
- def __init__( # noqa: E704
143
+ def __init__(
144
144
  self,
145
- torch_state_dict: OrderedDict[str, torch.Tensor],
146
- *,
147
- keep_input: bool = True,
148
- ) -> None: ...
149
-
150
- # This is also required for PyTorch state dict because they are not strongly typed
151
- @overload
152
- def __init__( # noqa: E704
153
- self,
154
- torch_state_dict: dict[str, Any],
145
+ # `Any` is required for PyTorch state dict because they are not strongly typed
146
+ torch_state_dict: dict[str, torch.Tensor] | dict[str, Any],
155
147
  *,
156
148
  keep_input: bool = True,
157
149
  ) -> None: ...
@@ -160,15 +152,15 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
160
152
  self,
161
153
  *args: Any,
162
154
  numpy_ndarrays: list[NDArray] | None = None,
163
- torch_state_dict: OrderedDict[str, torch.Tensor] | dict[str, Any] | None = None,
164
- array_dict: OrderedDict[str, Array] | None = None,
155
+ torch_state_dict: dict[str, torch.Tensor] | dict[str, Any] | None = None,
156
+ array_dict: dict[str, Array] | None = None,
165
157
  keep_input: bool = True,
166
158
  ) -> None:
167
159
  super().__init__(_check_key, _check_value)
168
160
 
169
161
  # Determine the initialization method and validates input arguments.
170
162
  # Support the following initialization formats:
171
- # 1. cls(array_dict: OrderedDict[str, Array], keep_input: bool)
163
+ # 1. cls(array_dict: dict[str, Array], keep_input: bool)
172
164
  # 2. cls(numpy_ndarrays: list[NDArray], keep_input: bool)
173
165
  # 3. cls(torch_state_dict: dict[str, torch.Tensor], keep_input: bool)
174
166
 
@@ -213,7 +205,7 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
213
205
  and all(isinstance(k, str) for k in arg.keys())
214
206
  and all(isinstance(v, Array) for v in arg.values())
215
207
  ):
216
- array_dict = cast(OrderedDict[str, Array], arg)
208
+ array_dict = cast(dict[str, Array], arg)
217
209
  converted = self.from_array_dict(array_dict, keep_input=keep_input)
218
210
  self.__dict__.update(converted.__dict__)
219
211
  return
@@ -239,9 +231,7 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
239
231
  and all(isinstance(k, str) for k in arg.keys())
240
232
  and all(isinstance(v, torch.Tensor) for v in arg.values())
241
233
  ):
242
- torch_state_dict = cast(
243
- OrderedDict[str, torch.Tensor], arg # type: ignore
244
- )
234
+ torch_state_dict = cast(dict[str, torch.Tensor], arg) # type: ignore
245
235
  converted = self.from_torch_state_dict(
246
236
  torch_state_dict, keep_input=keep_input
247
237
  )
@@ -253,7 +243,7 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
253
243
  @classmethod
254
244
  def from_array_dict(
255
245
  cls,
256
- array_dict: OrderedDict[str, Array],
246
+ array_dict: dict[str, Array],
257
247
  *,
258
248
  keep_input: bool = True,
259
249
  ) -> ArrayRecord:
@@ -300,7 +290,7 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
300
290
  @classmethod
301
291
  def from_torch_state_dict(
302
292
  cls,
303
- state_dict: OrderedDict[str, torch.Tensor],
293
+ state_dict: dict[str, torch.Tensor],
304
294
  *,
305
295
  keep_input: bool = True,
306
296
  ) -> ArrayRecord:
@@ -433,9 +423,7 @@ class ArrayRecord(TypedDict[str, Array], InflatableObject):
433
423
 
434
424
  # Instantiate new ArrayRecord
435
425
  return ArrayRecord(
436
- OrderedDict(
437
- {name: children[object_id] for name, object_id in array_refs.items()}
438
- )
426
+ {name: children[object_id] for name, object_id in array_refs.items()}
439
427
  )
440
428
 
441
429
  @property
@@ -142,11 +142,11 @@ class ConfigRecord(TypedDict[str, ConfigRecordValues], InflatableObject):
142
142
  var_bytes = 0
143
143
  if isinstance(value, bool):
144
144
  var_bytes = 1
145
- elif isinstance(value, (int, float)):
145
+ elif isinstance(value, (int | float)):
146
146
  var_bytes = (
147
147
  8 # the profobufing represents int/floats in ConfigRecords as 64bit
148
148
  )
149
- if isinstance(value, (str, bytes)):
149
+ if isinstance(value, (str | bytes)):
150
150
  var_bytes = len(value)
151
151
  if var_bytes == 0:
152
152
  raise ValueError(
@@ -159,7 +159,7 @@ class ConfigRecord(TypedDict[str, ConfigRecordValues], InflatableObject):
159
159
 
160
160
  for k, v in self.items():
161
161
  if isinstance(v, list):
162
- if isinstance(v[0], (bytes, str)):
162
+ if isinstance(v[0], (bytes | str)):
163
163
  # not all str are of equal length necessarily
164
164
  # for both the footprint of each element is 1 Byte
165
165
  num_bytes += int(sum(len(s) for s in v)) # type: ignore
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  import json
21
21
  from logging import WARN
22
22
  from textwrap import indent
23
- from typing import TypeVar, Union, cast
23
+ from typing import TypeVar, cast
24
24
 
25
25
  from ..inflatable import InflatableObject, add_header_to_object_body, get_object_body
26
26
  from ..logger import log
@@ -29,7 +29,7 @@ from .configrecord import ConfigRecord
29
29
  from .metricrecord import MetricRecord
30
30
  from .typeddict import TypedDict
31
31
 
32
- RecordType = Union[ArrayRecord, MetricRecord, ConfigRecord]
32
+ RecordType = ArrayRecord | MetricRecord | ConfigRecord
33
33
 
34
34
 
35
35
  class _WarningTracker:
@@ -59,7 +59,7 @@ def _check_key(key: str) -> None:
59
59
 
60
60
 
61
61
  def _check_value(value: RecordType) -> None:
62
- if not isinstance(value, (ArrayRecord, MetricRecord, ConfigRecord)):
62
+ if not isinstance(value, (ArrayRecord | MetricRecord | ConfigRecord)):
63
63
  raise TypeError(
64
64
  f"Expected `{ArrayRecord.__name__}`, `{MetricRecord.__name__}`, "
65
65
  f"or `{ConfigRecord.__name__}` but received "
@@ -76,7 +76,7 @@ class _SyncedDict(TypedDict[str, T]):
76
76
  """
77
77
 
78
78
  def __init__(self, ref_recorddict: RecordDict, allowed_type: type[T]) -> None:
79
- if not issubclass(allowed_type, (ArrayRecord, MetricRecord, ConfigRecord)):
79
+ if not issubclass(allowed_type, (ArrayRecord | MetricRecord | ConfigRecord)):
80
80
  raise TypeError(f"{allowed_type} is not a valid type.")
81
81
  super().__init__(_check_key, self.check_value)
82
82
  self.recorddict = ref_recorddict
@@ -341,7 +341,7 @@ class RecordDict(TypedDict[str, RecordType], InflatableObject):
341
341
 
342
342
  # Ensure children are one of the *Record objects exepecte in a RecordDict
343
343
  if not all(
344
- isinstance(ch, (ArrayRecord, ConfigRecord, MetricRecord))
344
+ isinstance(ch, (ArrayRecord | ConfigRecord | MetricRecord))
345
345
  for ch in children.values()
346
346
  ):
347
347
  raise ValueError(
@@ -15,8 +15,15 @@
15
15
  """Typed dict base class for *Records."""
16
16
 
17
17
 
18
- from collections.abc import ItemsView, Iterator, KeysView, MutableMapping, ValuesView
19
- from typing import Callable, Generic, TypeVar, cast
18
+ from collections.abc import (
19
+ Callable,
20
+ ItemsView,
21
+ Iterator,
22
+ KeysView,
23
+ MutableMapping,
24
+ ValuesView,
25
+ )
26
+ from typing import Generic, TypeVar, cast
20
27
 
21
28
  from typing_extensions import Self
22
29
 
@@ -15,9 +15,8 @@
15
15
  """RecordDict utilities."""
16
16
 
17
17
 
18
- from collections import OrderedDict
19
18
  from collections.abc import Mapping
20
- from typing import Union, cast, get_args
19
+ from typing import cast, get_args
21
20
 
22
21
  from . import Array, ArrayRecord, ConfigRecord, MetricRecord, RecordDict
23
22
  from .typing import (
@@ -104,25 +103,23 @@ def parameters_to_arrayrecord(parameters: Parameters, keep_input: bool) -> Array
104
103
  tensor_type = parameters.tensor_type
105
104
 
106
105
  num_arrays = len(parameters.tensors)
107
- ordered_dict = OrderedDict()
106
+ array_dict = {}
108
107
  for idx in range(num_arrays):
109
108
  if keep_input:
110
109
  tensor = parameters.tensors[idx]
111
110
  else:
112
111
  tensor = parameters.tensors.pop(0)
113
- ordered_dict[str(idx)] = Array(
114
- data=tensor, dtype="", stype=tensor_type, shape=()
115
- )
112
+ array_dict[str(idx)] = Array(data=tensor, dtype="", stype=tensor_type, shape=())
116
113
 
117
114
  if num_arrays == 0:
118
- ordered_dict[EMPTY_TENSOR_KEY] = Array(
115
+ array_dict[EMPTY_TENSOR_KEY] = Array(
119
116
  data=b"", dtype="", stype=tensor_type, shape=()
120
117
  )
121
- return ArrayRecord(ordered_dict, keep_input=keep_input)
118
+ return ArrayRecord(array_dict, keep_input=keep_input)
122
119
 
123
120
 
124
121
  def _check_mapping_from_recordscalartype_to_scalar(
125
- record_data: Mapping[str, Union[ConfigRecordValues, MetricRecordValues]]
122
+ record_data: Mapping[str, ConfigRecordValues | MetricRecordValues],
126
123
  ) -> dict[str, Scalar]:
127
124
  """Check mapping `common.*RecordValues` into `common.Scalar` is possible."""
128
125
  for value in record_data.values():
@@ -157,7 +154,7 @@ def _recorddict_to_fit_or_evaluate_ins_components(
157
154
 
158
155
 
159
156
  def _fit_or_evaluate_ins_to_recorddict(
160
- ins: Union[FitIns, EvaluateIns], keep_input: bool
157
+ ins: FitIns | EvaluateIns, keep_input: bool
161
158
  ) -> RecordDict:
162
159
  recorddict = RecordDict()
163
160
 
@@ -19,10 +19,10 @@ import itertools
19
19
  import random
20
20
  import threading
21
21
  import time
22
- from collections.abc import Generator, Iterable
22
+ from collections.abc import Callable, Generator, Iterable
23
23
  from dataclasses import dataclass
24
24
  from logging import INFO, WARN
25
- from typing import Any, Callable, Optional, Union, cast
25
+ from typing import Any, cast
26
26
 
27
27
  import grpc
28
28
 
@@ -39,7 +39,7 @@ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
39
39
  def exponential(
40
40
  base_delay: float = 1,
41
41
  multiplier: float = 2,
42
- max_delay: Optional[int] = None,
42
+ max_delay: int | None = None,
43
43
  ) -> Generator[float, None, None]:
44
44
  """Wait time generator for exponential backoff strategy.
45
45
 
@@ -66,7 +66,7 @@ def exponential(
66
66
 
67
67
 
68
68
  def constant(
69
- interval: Union[float, Iterable[float]] = 1,
69
+ interval: float | Iterable[float] = 1,
70
70
  ) -> Generator[float, None, None]:
71
71
  """Wait time generator for specified intervals.
72
72
 
@@ -114,8 +114,8 @@ class RetryState:
114
114
  kwargs: dict[str, Any]
115
115
  tries: int
116
116
  elapsed_time: float
117
- exception: Optional[Exception] = None
118
- actual_wait: Optional[float] = None
117
+ exception: Exception | None = None
118
+ actual_wait: float | None = None
119
119
 
120
120
 
121
121
  # pylint: disable-next=too-many-instance-attributes
@@ -184,16 +184,16 @@ class RetryInvoker:
184
184
  def __init__(
185
185
  self,
186
186
  wait_gen_factory: Callable[[], Generator[float, None, None]],
187
- recoverable_exceptions: Union[type[Exception], tuple[type[Exception], ...]],
188
- max_tries: Optional[int],
189
- max_time: Optional[float],
187
+ recoverable_exceptions: type[Exception] | tuple[type[Exception], ...],
188
+ max_tries: int | None,
189
+ max_time: float | None,
190
190
  *,
191
- on_success: Optional[Callable[[RetryState], None]] = None,
192
- on_backoff: Optional[Callable[[RetryState], None]] = None,
193
- on_giveup: Optional[Callable[[RetryState], None]] = None,
194
- jitter: Optional[Callable[[float], float]] = full_jitter,
195
- should_giveup: Optional[Callable[[Exception], bool]] = None,
196
- wait_function: Optional[Callable[[float], None]] = None,
191
+ on_success: Callable[[RetryState], None] | None = None,
192
+ on_backoff: Callable[[RetryState], None] | None = None,
193
+ on_giveup: Callable[[RetryState], None] | None = None,
194
+ jitter: Callable[[float], float] | None = full_jitter,
195
+ should_giveup: Callable[[Exception], bool] | None = None,
196
+ wait_function: Callable[[float], None] | None = None,
197
197
  ) -> None:
198
198
  self.wait_gen_factory = wait_gen_factory
199
199
  self.recoverable_exceptions = recoverable_exceptions
@@ -253,7 +253,7 @@ class RetryInvoker:
253
253
  """
254
254
 
255
255
  def try_call_event_handler(
256
- handler: Optional[Callable[[RetryState], None]]
256
+ handler: Callable[[RetryState], None] | None,
257
257
  ) -> None:
258
258
  if handler is not None:
259
259
  handler(cast(RetryState, ref_state[0]))
@@ -261,7 +261,7 @@ class RetryInvoker:
261
261
  try_cnt = 0
262
262
  wait_generator = self.wait_gen_factory()
263
263
  start = time.monotonic()
264
- ref_state: list[Optional[RetryState]] = [None]
264
+ ref_state: list[RetryState | None] = [None]
265
265
 
266
266
  while True:
267
267
  try_cnt += 1
@@ -387,9 +387,9 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
387
387
 
388
388
 
389
389
  def _wrap_stub(
390
- stub: Union[
391
- ServerAppIoStub, ClientAppIoStub, SimulationIoStub, FleetStub, GrpcAdapter
392
- ],
390
+ stub: (
391
+ ServerAppIoStub | ClientAppIoStub | SimulationIoStub | FleetStub | GrpcAdapter
392
+ ),
393
393
  retry_invoker: RetryInvoker,
394
394
  ) -> None:
395
395
  """Wrap a gRPC stub with a retry invoker."""
@@ -15,7 +15,7 @@
15
15
  """Utility functions for performing operations on Numpy NDArrays."""
16
16
 
17
17
 
18
- from typing import Any, Union
18
+ from typing import Any
19
19
 
20
20
  import numpy as np
21
21
  from numpy.typing import DTypeLike, NDArray
@@ -68,14 +68,14 @@ def parameters_mod(parameters: list[NDArray[Any]], divisor: int) -> list[NDArray
68
68
 
69
69
 
70
70
  def parameters_multiply(
71
- parameters: list[NDArray[Any]], multiplier: Union[int, float]
71
+ parameters: list[NDArray[Any]], multiplier: int | float
72
72
  ) -> list[NDArray[Any]]:
73
73
  """Multiply parameters by an integer/float multiplier."""
74
74
  return [parameters[idx] * multiplier for idx in range(len(parameters))]
75
75
 
76
76
 
77
77
  def parameters_divide(
78
- parameters: list[NDArray[Any]], divisor: Union[int, float]
78
+ parameters: list[NDArray[Any]], divisor: int | float
79
79
  ) -> list[NDArray[Any]]:
80
80
  """Divide weight by an integer/float divisor."""
81
81
  return [parameters[idx] / divisor for idx in range(len(parameters))]
flwr/common/serde.py CHANGED
@@ -15,7 +15,6 @@
15
15
  """ProtoBuf serialization and deserialization."""
16
16
 
17
17
 
18
- from collections import OrderedDict
19
18
  from typing import Any, cast
20
19
 
21
20
  # pylint: disable=E0611
@@ -410,9 +409,9 @@ def array_record_from_proto(
410
409
  ) -> ArrayRecord:
411
410
  """Deserialize ArrayRecord from ProtoBuf."""
412
411
  return ArrayRecord(
413
- array_dict=OrderedDict(
414
- {item.key: array_from_proto(item.value) for item in record_proto.items}
415
- ),
412
+ array_dict={
413
+ item.key: array_from_proto(item.value) for item in record_proto.items
414
+ },
416
415
  keep_input=False,
417
416
  )
418
417
 
@@ -632,6 +631,7 @@ def run_to_proto(run: typing.Run) -> ProtoRun:
632
631
  finished_at=run.finished_at,
633
632
  status=run_status_to_proto(run.status),
634
633
  flwr_aid=run.flwr_aid,
634
+ federation=run.federation,
635
635
  )
636
636
  return proto
637
637
 
@@ -650,6 +650,7 @@ def run_from_proto(run_proto: ProtoRun) -> typing.Run:
650
650
  finished_at=run_proto.finished_at,
651
651
  status=run_status_from_proto(run_proto.status),
652
652
  flwr_aid=run_proto.flwr_aid,
653
+ federation=run_proto.federation,
653
654
  )
654
655
  return run
655
656
 
@@ -114,7 +114,7 @@ def record_value_dict_to_proto(
114
114
  Note: `bool` MUST be put in the front of allowd_types if it exists.
115
115
  """
116
116
  # Move bool to the front
117
- if bool in allowed_types and allowed_types[0] != bool:
117
+ if bool in allowed_types and allowed_types[0] is not bool:
118
118
  allowed_types.remove(bool)
119
119
  allowed_types.insert(0, bool)
120
120
 
@@ -125,7 +125,7 @@ def record_value_dict_to_proto(
125
125
 
126
126
 
127
127
  def record_value_dict_from_proto(
128
- value_dict_proto: MutableMapping[str, Any]
128
+ value_dict_proto: MutableMapping[str, Any],
129
129
  ) -> dict[str, Any]:
130
130
  """Deserialize the record value dict from ProtoBuf."""
131
131
  return {k: _record_value_from_proto(v) for k, v in value_dict_proto.items()}
flwr/common/telemetry.py CHANGED
@@ -25,7 +25,7 @@ import uuid
25
25
  from concurrent.futures import Future, ThreadPoolExecutor
26
26
  from enum import Enum, auto
27
27
  from pathlib import Path
28
- from typing import Any, Optional, Union, cast
28
+ from typing import Any, cast
29
29
 
30
30
  from flwr.common.constant import FLWR_DIR
31
31
  from flwr.common.version import package_name, package_version
@@ -56,7 +56,7 @@ def _configure_logger(log_level: int) -> None:
56
56
  _configure_logger(LOGGER_LEVEL)
57
57
 
58
58
 
59
- def log(msg: Union[str, Exception]) -> None:
59
+ def log(msg: str | Exception) -> None:
60
60
  """Log message using logger at DEBUG level."""
61
61
  logging.getLogger(LOGGER_NAME).log(LOGGER_LEVEL, msg)
62
62
 
@@ -161,6 +161,10 @@ class EventType(str, Enum):
161
161
  FLWR_SERVERAPP_RUN_ENTER = auto()
162
162
  FLWR_SERVERAPP_RUN_LEAVE = auto()
163
163
 
164
+ # CLI: flwr-clientapp
165
+ FLWR_CLIENTAPP_RUN_ENTER = auto()
166
+ FLWR_CLIENTAPP_RUN_LEAVE = auto()
167
+
164
168
  # --- Simulation Engine ------------------------------------------------------------
165
169
 
166
170
  # CLI: flower-simulation
@@ -188,7 +192,7 @@ class EventType(str, Enum):
188
192
 
189
193
  # Use the ThreadPoolExecutor with max_workers=1 to have a queue
190
194
  # and also ensure that telemetry calls are not blocking.
191
- state: dict[str, Union[Optional[str], Optional[ThreadPoolExecutor]]] = {
195
+ state: dict[str, str | None | ThreadPoolExecutor | None] = {
192
196
  # Will be assigned ThreadPoolExecutor(max_workers=1)
193
197
  # in event() the first time it's required
194
198
  "executor": None,
@@ -200,7 +204,7 @@ state: dict[str, Union[Optional[str], Optional[ThreadPoolExecutor]]] = {
200
204
 
201
205
  def event(
202
206
  event_type: EventType,
203
- event_details: Optional[dict[str, Any]] = None,
207
+ event_details: dict[str, Any] | None = None,
204
208
  ) -> Future: # type: ignore
205
209
  """Submit create_event to ThreadPoolExecutor to avoid blocking."""
206
210
  if state["executor"] is None:
@@ -212,7 +216,7 @@ def event(
212
216
  return result
213
217
 
214
218
 
215
- def create_event(event_type: EventType, event_details: Optional[dict[str, Any]]) -> str:
219
+ def create_event(event_type: EventType, event_details: dict[str, Any] | None) -> str:
216
220
  """Create telemetry event."""
217
221
  if state["source"] is None:
218
222
  state["source"] = _get_source_id()
flwr/common/typing.py CHANGED
@@ -15,13 +15,16 @@
15
15
  """Flower type definitions."""
16
16
 
17
17
 
18
+ from collections.abc import Callable
18
19
  from dataclasses import dataclass
19
20
  from enum import Enum
20
- from typing import Any, Callable, Optional, Union
21
+ from typing import Any
21
22
 
22
23
  import numpy as np
23
24
  import numpy.typing as npt
24
25
 
26
+ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
27
+
25
28
  NDArray = npt.NDArray[Any]
26
29
  NDArrayInt = npt.NDArray[np.int_]
27
30
  NDArrayFloat = npt.NDArray[np.float64]
@@ -31,29 +34,29 @@ NDArrays = list[NDArray]
31
34
  # ProtoBuf considers to be "Scalar Value Types", even though some of them arguably do
32
35
  # not conform to other definitions of what a scalar is. Source:
33
36
  # https://developers.google.com/protocol-buffers/docs/overview#scalar
34
- Scalar = Union[bool, bytes, float, int, str]
35
- Value = Union[
36
- bool,
37
- bytes,
38
- float,
39
- int,
40
- str,
41
- list[bool],
42
- list[bytes],
43
- list[float],
44
- list[int],
45
- list[str],
46
- ]
37
+ Scalar = bool | bytes | float | int | str
38
+ Value = (
39
+ bool
40
+ | bytes
41
+ | float
42
+ | int
43
+ | str
44
+ | list[bool]
45
+ | list[bytes]
46
+ | list[float]
47
+ | list[int]
48
+ | list[str]
49
+ )
50
+
47
51
 
48
52
  # Value types for common.MetricRecord
49
- MetricScalar = Union[int, float]
50
- MetricScalarList = Union[list[int], list[float]]
51
- MetricRecordValues = Union[MetricScalar, MetricScalarList]
53
+ MetricScalar = int | float
54
+ MetricScalarList = list[int] | list[float]
55
+ MetricRecordValues = MetricScalar | MetricScalarList
52
56
  # Value types for common.ConfigRecord
53
- ConfigScalar = Union[MetricScalar, str, bytes, bool]
54
- ConfigScalarList = Union[MetricScalarList, list[str], list[bytes], list[bool]]
55
- ConfigRecordValues = Union[ConfigScalar, ConfigScalarList]
56
-
57
+ ConfigScalar = MetricScalar | str | bytes | bool
58
+ ConfigScalarList = MetricScalarList | list[str] | list[bytes] | list[bool]
59
+ ConfigRecordValues = ConfigScalar | ConfigScalarList
57
60
  Metrics = dict[str, Scalar]
58
61
  MetricsAggregationFn = Callable[[list[tuple[int, Metrics]]], Metrics]
59
62
 
@@ -61,7 +64,7 @@ Config = dict[str, Scalar]
61
64
  Properties = dict[str, Scalar]
62
65
 
63
66
  # Value type for user configs
64
- UserConfigValue = Union[bool, float, int, str]
67
+ UserConfigValue = bool | float | int | str
65
68
  UserConfig = dict[str, UserConfigValue]
66
69
 
67
70
 
@@ -177,7 +180,7 @@ class GetPropertiesRes:
177
180
  class ReconnectIns:
178
181
  """ReconnectIns message from server to client."""
179
182
 
180
- seconds: Optional[int]
183
+ seconds: int | None
181
184
 
182
185
 
183
186
  @dataclass
@@ -191,20 +194,20 @@ class DisconnectRes:
191
194
  class ServerMessage:
192
195
  """ServerMessage is a container used to hold one instruction message."""
193
196
 
194
- get_properties_ins: Optional[GetPropertiesIns] = None
195
- get_parameters_ins: Optional[GetParametersIns] = None
196
- fit_ins: Optional[FitIns] = None
197
- evaluate_ins: Optional[EvaluateIns] = None
197
+ get_properties_ins: GetPropertiesIns | None = None
198
+ get_parameters_ins: GetParametersIns | None = None
199
+ fit_ins: FitIns | None = None
200
+ evaluate_ins: EvaluateIns | None = None
198
201
 
199
202
 
200
203
  @dataclass
201
204
  class ClientMessage:
202
205
  """ClientMessage is a container used to hold one result message."""
203
206
 
204
- get_properties_res: Optional[GetPropertiesRes] = None
205
- get_parameters_res: Optional[GetParametersRes] = None
206
- fit_res: Optional[FitRes] = None
207
- evaluate_res: Optional[EvaluateRes] = None
207
+ get_properties_res: GetPropertiesRes | None = None
208
+ get_parameters_res: GetParametersRes | None = None
209
+ fit_res: FitRes | None = None
210
+ evaluate_res: EvaluateRes | None = None
208
211
 
209
212
 
210
213
  @dataclass
@@ -231,6 +234,7 @@ class Run: # pylint: disable=too-many-instance-attributes
231
234
  finished_at: str
232
235
  status: RunStatus
233
236
  flwr_aid: str
237
+ federation: str
234
238
 
235
239
  @classmethod
236
240
  def create_empty(cls, run_id: int) -> "Run":
@@ -247,6 +251,7 @@ class Run: # pylint: disable=too-many-instance-attributes
247
251
  finished_at="",
248
252
  status=RunStatus(status="", sub_status="", details=""),
249
253
  flwr_aid="",
254
+ federation="",
250
255
  )
251
256
 
252
257
 
@@ -295,16 +300,16 @@ class AccountAuthCredentials:
295
300
  class AccountInfo:
296
301
  """User information for event log."""
297
302
 
298
- flwr_aid: Optional[str]
299
- account_name: Optional[str]
303
+ flwr_aid: str | None
304
+ account_name: str | None
300
305
 
301
306
 
302
307
  @dataclass
303
308
  class Actor:
304
309
  """Event log actor."""
305
310
 
306
- actor_id: Optional[str]
307
- description: Optional[str]
311
+ actor_id: str | None
312
+ description: str | None
308
313
  ip_address: str
309
314
 
310
315
 
@@ -313,8 +318,8 @@ class Event:
313
318
  """Event log description."""
314
319
 
315
320
  action: str
316
- run_id: Optional[int]
317
- fab_hash: Optional[str]
321
+ run_id: int | None
322
+ fab_hash: str | None
318
323
 
319
324
 
320
325
  @dataclass
@@ -325,3 +330,13 @@ class LogEntry:
325
330
  actor: Actor
326
331
  event: Event
327
332
  status: str
333
+
334
+
335
+ @dataclass
336
+ class Federation:
337
+ """Federation details."""
338
+
339
+ name: str
340
+ member_aids: list[str]
341
+ nodes: list[NodeInfo]
342
+ runs: list[Run]