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
@@ -15,7 +15,8 @@
15
15
  """GrpcAdapter implementation."""
16
16
 
17
17
 
18
- import sys
18
+ import signal
19
+ import time
19
20
  from logging import DEBUG
20
21
  from typing import Any, TypeVar, cast
21
22
 
@@ -62,6 +63,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
62
63
  PushObjectResponse,
63
64
  )
64
65
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
66
+ from flwr.supercore.constant import FORCE_EXIT_TIMEOUT_SECONDS
65
67
 
66
68
  T = TypeVar("T", bound=GrpcMessage)
67
69
 
@@ -108,7 +110,9 @@ class GrpcAdapter:
108
110
  DEBUG,
109
111
  'Received shutdown signal: exit flag is set to ``"true"``. Exiting...',
110
112
  )
111
- sys.exit(0)
113
+ signal.raise_signal(signal.SIGTERM)
114
+ # Give some time to handle the signal
115
+ time.sleep(FORCE_EXIT_TIMEOUT_SECONDS + 1)
112
116
 
113
117
  # Check the grpc_message_name of the response
114
118
  if container_res.grpc_message_name != response_type.__qualname__:
@@ -15,7 +15,8 @@
15
15
  """Flower client interceptor."""
16
16
 
17
17
 
18
- from typing import Any, Callable
18
+ from collections.abc import Callable
19
+ from typing import Any
19
20
 
20
21
  import grpc
21
22
  from cryptography.hazmat.primitives.asymmetric import ec
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from logging import WARN
19
- from typing import Optional, cast
19
+ from typing import cast
20
20
 
21
21
  from flwr.client.client import (
22
22
  maybe_call_evaluate,
@@ -53,7 +53,7 @@ class UnknownServerMessage(Exception):
53
53
  """Exception indicating that the received message is unknown."""
54
54
 
55
55
 
56
- def handle_control_message(message: Message) -> tuple[Optional[Message], int]:
56
+ def handle_control_message(message: Message) -> tuple[Message | None, int]:
57
57
  """Handle control part of the incoming message.
58
58
 
59
59
  Parameters
@@ -112,9 +112,9 @@ class SecAggPlusState:
112
112
  updated_values = [
113
113
  tuple(values[i : i + 2]) for i in range(0, len(values), 2)
114
114
  ]
115
- new_v = dict(zip(keys, updated_values))
115
+ new_v = dict(zip(keys, updated_values, strict=True))
116
116
  else:
117
- new_v = dict(zip(keys, values))
117
+ new_v = dict(zip(keys, values, strict=True))
118
118
  self.__setattr__(k, new_v)
119
119
 
120
120
  def to_dict(self) -> dict[str, ConfigRecordValues]:
@@ -426,7 +426,7 @@ def _collect_masked_vectors(
426
426
  raise ValueError("Not enough available neighbour clients.")
427
427
 
428
428
  # Decrypt ciphertexts, verify their sources, and store shares.
429
- for src, ciphertext in zip(srcs, ciphertexts):
429
+ for src, ciphertext in zip(srcs, ciphertexts, strict=True):
430
430
  shared_key = state.ss2_dict[src]
431
431
  plaintext = decrypt(shared_key, ciphertext)
432
432
  actual_src, dst, rd_seed_share, sk1_share = share_keys_plaintext_separate(
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from abc import ABC
19
- from typing import Callable
19
+ from collections.abc import Callable
20
20
 
21
21
  from flwr.client.client import Client
22
22
  from flwr.common import (
@@ -15,10 +15,10 @@
15
15
  """Contextmanager for a REST request-response channel to the Flower server."""
16
16
 
17
17
 
18
- from collections.abc import Iterator
18
+ from collections.abc import Callable, Iterator
19
19
  from contextlib import contextmanager
20
20
  from logging import ERROR, WARN
21
- from typing import Callable, Optional, TypeVar, Union
21
+ from typing import TypeVar
22
22
 
23
23
  from cryptography.hazmat.primitives.asymmetric import ec
24
24
  from google.protobuf.message import Message as GrpcMessage
@@ -27,7 +27,6 @@ from requests.exceptions import ConnectionError as RequestsConnectionError
27
27
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
28
28
  from flwr.common.constant import HEARTBEAT_DEFAULT_INTERVAL
29
29
  from flwr.common.exit import ExitCode, flwr_exit
30
- from flwr.common.heartbeat import HeartbeatSender
31
30
  from flwr.common.inflatable_protobuf_utils import (
32
31
  make_confirm_message_received_fn_protobuf,
33
32
  make_pull_object_fn_protobuf,
@@ -73,6 +72,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
73
72
  )
74
73
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
75
74
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
75
+ from flwr.supercore.heartbeat import HeartbeatSender
76
76
  from flwr.supercore.primitives.asymmetric import generate_key_pairs, public_key_to_bytes
77
77
 
78
78
  try:
@@ -103,16 +103,14 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
103
103
  insecure: bool, # pylint: disable=unused-argument
104
104
  retry_invoker: RetryInvoker,
105
105
  max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
106
- root_certificates: Optional[
107
- Union[bytes, str]
108
- ] = None, # pylint: disable=unused-argument
109
- authentication_keys: Optional[ # pylint: disable=unused-argument
110
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
111
- ] = None,
106
+ root_certificates: bytes | str | None = None, # pylint: disable=unused-argument
107
+ authentication_keys: (
108
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
109
+ ) = None,
112
110
  ) -> Iterator[
113
111
  tuple[
114
112
  int,
115
- Callable[[], Optional[tuple[Message, ObjectTree]]],
113
+ Callable[[], tuple[Message, ObjectTree] | None],
116
114
  Callable[[Message, ObjectTree], set[str]],
117
115
  Callable[[int], Run],
118
116
  Callable[[str, int], Fab],
@@ -172,7 +170,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
172
170
  # Otherwise any server can fake its identity
173
171
  # Please refer to:
174
172
  # https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification
175
- verify: Union[bool, str] = True
173
+ verify: bool | str = True
176
174
  if isinstance(root_certificates, str):
177
175
  verify = root_certificates
178
176
  elif isinstance(root_certificates, bytes):
@@ -192,7 +190,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
192
190
  node_pk = public_key_to_bytes(authentication_keys[1])
193
191
 
194
192
  # Shared variables for inner functions
195
- node: Optional[Node] = None
193
+ node: Node | None = None
196
194
 
197
195
  # Remove should_giveup from RetryInvoker as REST does not support gRPC status codes
198
196
  retry_invoker.should_giveup = None
@@ -203,7 +201,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
203
201
 
204
202
  def _request(
205
203
  req: GrpcMessage, res_type: type[T], api_path: str, retry: bool = True
206
- ) -> Optional[T]:
204
+ ) -> T | None:
207
205
  # Serialize the request
208
206
  req_bytes = req.SerializeToString()
209
207
 
@@ -369,7 +367,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
369
367
  # Cleanup
370
368
  node = None
371
369
 
372
- def receive() -> Optional[tuple[Message, ObjectTree]]:
370
+ def receive() -> tuple[Message, ObjectTree] | None:
373
371
  """Pull a message with its ObjectTree from SuperLink."""
374
372
  # Get Node
375
373
  if node is None:
@@ -17,7 +17,6 @@
17
17
 
18
18
  from dataclasses import dataclass
19
19
  from pathlib import Path
20
- from typing import Optional
21
20
 
22
21
  from flwr.common import Context, RecordDict
23
22
  from flwr.common.config import (
@@ -52,10 +51,10 @@ class DeprecatedRunInfoStore:
52
51
  def register_context(
53
52
  self,
54
53
  run_id: int,
55
- run: Optional[Run] = None,
56
- flwr_path: Optional[Path] = None,
57
- app_dir: Optional[str] = None,
58
- fab: Optional[Fab] = None,
54
+ run: Run | None = None,
55
+ flwr_path: Path | None = None,
56
+ app_dir: str | None = None,
57
+ fab: Fab | None = None,
59
58
  ) -> None:
60
59
  """Register new run context for this node."""
61
60
  if run_id not in self.run_infos:
flwr/client/typing.py CHANGED
@@ -15,7 +15,7 @@
15
15
  """Custom types for Flower clients."""
16
16
 
17
17
 
18
- from typing import Callable
18
+ from collections.abc import Callable
19
19
 
20
20
  from flwr.common import Context, Message
21
21
 
@@ -16,9 +16,8 @@
16
16
 
17
17
 
18
18
  import inspect
19
- from collections.abc import Iterator
19
+ from collections.abc import Callable, Iterator
20
20
  from contextlib import contextmanager
21
- from typing import Callable, Optional
22
21
 
23
22
  from flwr.app.metadata import validate_message_type
24
23
  from flwr.client.client import Client
@@ -109,14 +108,14 @@ class ClientApp:
109
108
 
110
109
  def __init__(
111
110
  self,
112
- client_fn: Optional[ClientFnExt] = None, # Only for backward compatibility
113
- mods: Optional[list[Mod]] = None,
111
+ client_fn: ClientFnExt | None = None, # Only for backward compatibility
112
+ mods: list[Mod] | None = None,
114
113
  ) -> None:
115
114
  self._mods: list[Mod] = mods if mods is not None else []
116
115
  self._registered_funcs: dict[str, ClientAppCallable] = {}
117
116
 
118
117
  # Create wrapper function for `handle`
119
- self._call: Optional[ClientAppCallable] = None
118
+ self._call: ClientAppCallable | None = None
120
119
  if client_fn is not None:
121
120
 
122
121
  client_fn = _inspect_maybe_adapt_client_fn_signature(client_fn)
@@ -163,7 +162,7 @@ class ClientApp:
163
162
  raise ValueError(f"No {category} function registered with name '{action}'")
164
163
 
165
164
  def train(
166
- self, action: str = DEFAULT_ACTION, *, mods: Optional[list[Mod]] = None
165
+ self, action: str = DEFAULT_ACTION, *, mods: list[Mod] | None = None
167
166
  ) -> Callable[[ClientAppCallable], ClientAppCallable]:
168
167
  """Register a train function with the ``ClientApp``.
169
168
 
@@ -218,7 +217,7 @@ class ClientApp:
218
217
  return _get_decorator(self, MessageType.TRAIN, action, mods)
219
218
 
220
219
  def evaluate(
221
- self, action: str = DEFAULT_ACTION, *, mods: Optional[list[Mod]] = None
220
+ self, action: str = DEFAULT_ACTION, *, mods: list[Mod] | None = None
222
221
  ) -> Callable[[ClientAppCallable], ClientAppCallable]:
223
222
  """Register an evaluate function with the ``ClientApp``.
224
223
 
@@ -273,7 +272,7 @@ class ClientApp:
273
272
  return _get_decorator(self, MessageType.EVALUATE, action, mods)
274
273
 
275
274
  def query(
276
- self, action: str = DEFAULT_ACTION, *, mods: Optional[list[Mod]] = None
275
+ self, action: str = DEFAULT_ACTION, *, mods: list[Mod] | None = None
277
276
  ) -> Callable[[ClientAppCallable], ClientAppCallable]:
278
277
  """Register a query function with the ``ClientApp``.
279
278
 
@@ -355,7 +354,7 @@ class ClientApp:
355
354
  """
356
355
 
357
356
  def lifespan_decorator(
358
- lifespan_fn: Callable[[Context], Iterator[None]]
357
+ lifespan_fn: Callable[[Context], Iterator[None]],
359
358
  ) -> Callable[[Context], Iterator[None]]:
360
359
  """Register the lifespan fn with the ServerApp object."""
361
360
 
@@ -398,7 +397,7 @@ class LoadClientAppError(Exception):
398
397
 
399
398
 
400
399
  def _get_decorator(
401
- app: ClientApp, category: str, action: str, mods: Optional[list[Mod]]
400
+ app: ClientApp, category: str, action: str, mods: list[Mod] | None
402
401
  ) -> Callable[[ClientAppCallable], ClientAppCallable]:
403
402
  """Get the decorator for the given category and action."""
404
403
  # pylint: disable=protected-access
@@ -15,7 +15,6 @@
15
15
  """Clipping modifiers for central DP with client-side clipping."""
16
16
 
17
17
 
18
- from collections import OrderedDict
19
18
  from logging import ERROR, INFO
20
19
  from typing import cast
21
20
 
@@ -105,14 +104,14 @@ def fixedclipping_mod(
105
104
  )
106
105
  # Replace outgoing ArrayRecord's Array while preserving their keys
107
106
  out_msg.content.array_records[new_array_record_key] = ArrayRecord(
108
- OrderedDict(
109
- {
110
- k: Array(v)
111
- for k, v in zip(
112
- client_to_server_arrecord.keys(), client_to_server_ndarrays
113
- )
114
- }
115
- )
107
+ {
108
+ k: Array(v)
109
+ for k, v in zip(
110
+ client_to_server_arrecord.keys(),
111
+ client_to_server_ndarrays,
112
+ strict=True,
113
+ )
114
+ }
116
115
  )
117
116
  return out_msg
118
117
 
@@ -192,14 +191,14 @@ def adaptiveclipping_mod(
192
191
  )
193
192
  # Replace outgoing ArrayRecord's Array while preserving their keys
194
193
  out_msg.content.array_records[new_array_record_key] = ArrayRecord(
195
- OrderedDict(
196
- {
197
- k: Array(v)
198
- for k, v in zip(
199
- client_to_server_arrecord.keys(), client_to_server_ndarrays
200
- )
201
- }
202
- )
194
+ {
195
+ k: Array(v)
196
+ for k, v in zip(
197
+ client_to_server_arrecord.keys(),
198
+ client_to_server_ndarrays,
199
+ strict=True,
200
+ )
201
+ }
203
202
  )
204
203
  # Add to the MetricRecords the norm bit (recall reply messages only contain
205
204
  # one MetricRecord)
@@ -15,7 +15,6 @@
15
15
  """Local DP modifier."""
16
16
 
17
17
 
18
- from collections import OrderedDict
19
18
  from logging import INFO
20
19
 
21
20
  import numpy as np
@@ -157,13 +156,13 @@ class LocalDpMod:
157
156
 
158
157
  # Replace outgoing ArrayRecord's Array while preserving their keys
159
158
  out_msg.content[new_array_record_key] = ArrayRecord(
160
- OrderedDict(
161
- {
162
- k: Array(v)
163
- for k, v in zip(
164
- client_to_server_arrecord.keys(), client_to_server_ndarrays
165
- )
166
- }
167
- )
159
+ {
160
+ k: Array(v)
161
+ for k, v in zip(
162
+ client_to_server_arrecord.keys(),
163
+ client_to_server_ndarrays,
164
+ strict=True,
165
+ )
166
+ }
168
167
  )
169
168
  return out_msg
flwr/clientapp/typing.py CHANGED
@@ -15,7 +15,7 @@
15
15
  """Custom types for Flower clients."""
16
16
 
17
17
 
18
- from typing import Callable
18
+ from collections.abc import Callable
19
19
 
20
20
  from flwr.common import Context, Message
21
21
 
flwr/clientapp/utils.py CHANGED
@@ -15,9 +15,9 @@
15
15
  """Flower ClientApp loading utils."""
16
16
 
17
17
 
18
+ from collections.abc import Callable
18
19
  from logging import DEBUG
19
20
  from pathlib import Path
20
- from typing import Callable, Optional
21
21
 
22
22
  from flwr.clientapp.client_app import ClientApp, LoadClientAppError
23
23
  from flwr.common.config import (
@@ -32,9 +32,9 @@ from flwr.common.object_ref import load_app, validate
32
32
 
33
33
  def get_load_client_app_fn(
34
34
  default_app_ref: str,
35
- app_path: Optional[str],
35
+ app_path: str | None,
36
36
  multi_app: bool,
37
- flwr_dir: Optional[str] = None,
37
+ flwr_dir: str | None = None,
38
38
  ) -> Callable[[str, str, str], ClientApp]:
39
39
  """Get the load_client_app_fn function.
40
40
 
flwr/common/address.py CHANGED
@@ -18,14 +18,13 @@
18
18
  import re
19
19
  import socket
20
20
  from ipaddress import ip_address
21
- from typing import Optional
22
21
 
23
22
  import grpc
24
23
 
25
24
  IPV6: int = 6
26
25
 
27
26
 
28
- def parse_address(address: str) -> Optional[tuple[str, int, Optional[bool]]]:
27
+ def parse_address(address: str) -> tuple[str, int, bool | None] | None:
29
28
  """Parse an IP address into host, port, and version.
30
29
 
31
30
  Parameters
flwr/common/args.py CHANGED
@@ -20,7 +20,6 @@ import sys
20
20
  from logging import DEBUG, ERROR, INFO, WARN
21
21
  from os.path import isfile
22
22
  from pathlib import Path
23
- from typing import Optional, Union
24
23
 
25
24
  from flwr.common.constant import TRANSPORT_TYPE_REST
26
25
  from flwr.common.logger import log
@@ -70,9 +69,9 @@ def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
70
69
  def try_obtain_root_certificates(
71
70
  args: argparse.Namespace,
72
71
  grpc_server_address: str,
73
- ) -> Optional[Union[bytes, str]]:
72
+ ) -> bytes | str | None:
74
73
  """Validate and return the root certificates."""
75
- root_cert_path: Optional[str] = args.root_certificates
74
+ root_cert_path: str | None = args.root_certificates
76
75
  if args.insecure:
77
76
  if root_cert_path is not None:
78
77
  sys.exit(
@@ -111,7 +110,7 @@ def try_obtain_root_certificates(
111
110
 
112
111
  def try_obtain_server_certificates(
113
112
  args: argparse.Namespace,
114
- ) -> Optional[tuple[bytes, bytes, bytes]]:
113
+ ) -> tuple[bytes, bytes, bytes] | None:
115
114
  """Validate and return the CA cert, server cert, and server private key."""
116
115
  if args.insecure:
117
116
  log(
flwr/common/config.py CHANGED
@@ -20,7 +20,7 @@ import re
20
20
  import zipfile
21
21
  from io import BytesIO
22
22
  from pathlib import Path
23
- from typing import IO, Any, Optional, TypeVar, Union, cast, get_args
23
+ from typing import IO, Any, TypeVar, cast, get_args
24
24
 
25
25
  import tomli
26
26
  import typer
@@ -39,7 +39,7 @@ from . import ConfigRecord, object_ref
39
39
  T_dict = TypeVar("T_dict", bound=dict[str, Any]) # pylint: disable=invalid-name
40
40
 
41
41
 
42
- def get_flwr_dir(provided_path: Optional[str] = None) -> Path:
42
+ def get_flwr_dir(provided_path: str | None = None) -> Path:
43
43
  """Return the Flower home directory based on env variables."""
44
44
  if provided_path is None or not Path(provided_path).is_dir():
45
45
  return Path(
@@ -55,7 +55,7 @@ def get_project_dir(
55
55
  fab_id: str,
56
56
  fab_version: str,
57
57
  fab_hash: str,
58
- flwr_dir: Optional[Union[str, Path]] = None,
58
+ flwr_dir: str | Path | None = None,
59
59
  ) -> Path:
60
60
  """Return the project directory based on the given fab_id and fab_version."""
61
61
  # Check the fab_id
@@ -73,7 +73,7 @@ def get_project_dir(
73
73
  )
74
74
 
75
75
 
76
- def get_project_config(project_dir: Union[str, Path]) -> dict[str, Any]:
76
+ def get_project_config(project_dir: str | Path) -> dict[str, Any]:
77
77
  """Return pyproject.toml in the given project directory."""
78
78
  # Load pyproject.toml file
79
79
  toml_path = Path(project_dir) / FAB_CONFIG_FILE
@@ -134,7 +134,7 @@ def get_fused_config_from_dir(
134
134
  return fuse_dicts(flat_default_config, override_config)
135
135
 
136
136
 
137
- def get_fused_config_from_fab(fab_file: Union[Path, bytes], run: Run) -> UserConfig:
137
+ def get_fused_config_from_fab(fab_file: Path | bytes, run: Run) -> UserConfig:
138
138
  """Fuse default config in a `FAB` with overrides in a `Run`.
139
139
 
140
140
  This enables obtaining a run-config without having to install the FAB. This
@@ -146,7 +146,7 @@ def get_fused_config_from_fab(fab_file: Union[Path, bytes], run: Run) -> UserCon
146
146
  return fuse_dicts(flat_config_flat, run.override_config)
147
147
 
148
148
 
149
- def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
149
+ def get_fused_config(run: Run, flwr_dir: Path | None) -> UserConfig:
150
150
  """Merge the overrides from a `Run` with the config from a FAB.
151
151
 
152
152
  Get the config using the fab_id and the fab_version, remove the nesting by adding
@@ -165,9 +165,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
165
165
  return get_fused_config_from_dir(project_dir, run.override_config)
166
166
 
167
167
 
168
- def flatten_dict(
169
- raw_dict: Optional[dict[str, Any]], parent_key: str = ""
170
- ) -> UserConfig:
168
+ def flatten_dict(raw_dict: dict[str, Any] | None, parent_key: str = "") -> UserConfig:
171
169
  """Flatten dict by joining nested keys with a given separator."""
172
170
  if raw_dict is None:
173
171
  return {}
@@ -205,9 +203,7 @@ def unflatten_dict(flat_dict: dict[str, Any]) -> dict[str, Any]:
205
203
  return unflattened_dict
206
204
 
207
205
 
208
- def parse_config_args(
209
- config: Optional[list[str]], flatten: bool = True
210
- ) -> dict[str, Any]:
206
+ def parse_config_args(config: list[str] | None, flatten: bool = True) -> dict[str, Any]:
211
207
  """Parse separator separated list of key-value pairs separated by '='."""
212
208
  overrides: UserConfig = {}
213
209
 
@@ -246,6 +242,7 @@ def parse_config_args(
246
242
  "space-separated key-value pairs.",
247
243
  fg=typer.colors.RED,
248
244
  bold=True,
245
+ err=True,
249
246
  )
250
247
  raise typer.Exit(code=1) from err
251
248
 
@@ -269,7 +266,7 @@ def user_config_to_configrecord(config: UserConfig) -> ConfigRecord:
269
266
  return c_record
270
267
 
271
268
 
272
- def get_fab_config(fab_file: Union[Path, bytes]) -> dict[str, Any]:
269
+ def get_fab_config(fab_file: Path | bytes) -> dict[str, Any]:
273
270
  """Extract the config from a FAB file or path.
274
271
 
275
272
  Parameters
@@ -283,7 +280,7 @@ def get_fab_config(fab_file: Union[Path, bytes]) -> dict[str, Any]:
283
280
  Dict[str, Any]
284
281
  The `config` of the given Flower App Bundle.
285
282
  """
286
- fab_file_archive: Union[Path, IO[bytes]]
283
+ fab_file_archive: Path | IO[bytes]
287
284
  if isinstance(fab_file, bytes):
288
285
  fab_file_archive = BytesIO(fab_file)
289
286
  elif isinstance(fab_file, Path):
@@ -319,7 +316,7 @@ def _validate_run_config(config_dict: dict[str, Any], errors: list[str]) -> None
319
316
 
320
317
  # pylint: disable=too-many-branches
321
318
  def validate_fields_in_config(
322
- config: dict[str, Any]
319
+ config: dict[str, Any],
323
320
  ) -> tuple[bool, list[str], list[str]]:
324
321
  """Validate pyproject.toml fields."""
325
322
  errors = []
@@ -368,7 +365,7 @@ def validate_fields_in_config(
368
365
  def validate_config(
369
366
  config: dict[str, Any],
370
367
  check_module: bool = True,
371
- project_dir: Optional[Union[str, Path]] = None,
368
+ project_dir: str | Path | None = None,
372
369
  ) -> tuple[bool, list[str], list[str]]:
373
370
  """Validate pyproject.toml."""
374
371
  is_valid, errors, warnings = validate_fields_in_config(config)
flwr/common/constant.py CHANGED
@@ -88,6 +88,7 @@ FAB_INCLUDE_PATTERNS = (
88
88
  )
89
89
  # FAB file exclude patterns (gitignore-style patterns)
90
90
  FAB_EXCLUDE_PATTERNS = (
91
+ f"{FLWR_DIR}/**", # Exclude the .flwr directory
91
92
  "**/__pycache__/**",
92
93
  FAB_CONFIG_FILE, # Exclude the original pyproject.toml
93
94
  )
@@ -227,6 +228,8 @@ class ErrorCode:
227
228
  REPLY_MESSAGE_UNAVAILABLE = 4
228
229
  NODE_UNAVAILABLE = 5
229
230
  MOD_FAILED_PRECONDITION = 6
231
+ INVALID_FAB = 7
232
+ CLIENT_APP_CRASHED = 8
230
233
 
231
234
  def __new__(cls) -> ErrorCode:
232
235
  """Prevent instantiation."""
@@ -319,5 +322,5 @@ class ExecPluginType:
319
322
 
320
323
 
321
324
  # Constants for No-op auth plugins
322
- NOOP_FLWR_AID = "<none>"
323
- NOOP_ACCOUNT_NAME = "sys_noauth"
325
+ NOOP_FLWR_AID = "<id:none>"
326
+ NOOP_ACCOUNT_NAME = "<name:none>"
@@ -16,7 +16,6 @@
16
16
 
17
17
 
18
18
  from logging import WARNING
19
- from typing import Optional
20
19
 
21
20
  import numpy as np
22
21
 
@@ -70,7 +69,7 @@ def compute_clip_model_update(
70
69
  """Compute model update (param1 - param2) and clip it.
71
70
 
72
71
  Then add the clipped value to param1."""
73
- model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2)]
72
+ model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2, strict=True)]
74
73
  clip_inputs_inplace(model_update, clipping_norm)
75
74
 
76
75
  for i, _ in enumerate(param2):
@@ -98,7 +97,7 @@ def compute_adaptive_clip_model_update(
98
97
  model update = param1 - param2
99
98
  Return the norm_bit
100
99
  """
101
- model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2)]
100
+ model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2, strict=True)]
102
101
  norm_bit = adaptive_clip_inputs_inplace(model_update, clipping_norm)
103
102
 
104
103
  for i, _ in enumerate(param2):
@@ -125,7 +124,7 @@ def add_gaussian_noise_to_params(
125
124
  def compute_adaptive_noise_params(
126
125
  noise_multiplier: float,
127
126
  num_sampled_clients: float,
128
- clipped_count_stddev: Optional[float],
127
+ clipped_count_stddev: float | None,
129
128
  ) -> tuple[float, float]:
130
129
  """Compute noising parameters for the adaptive clipping.
131
130
 
@@ -16,7 +16,6 @@
16
16
 
17
17
 
18
18
  from abc import ABC, abstractmethod
19
- from typing import Optional, Union
20
19
 
21
20
  import grpc
22
21
  from google.protobuf.message import Message as GrpcMessage
@@ -36,7 +35,7 @@ class EventLogWriterPlugin(ABC):
36
35
  self,
37
36
  request: GrpcMessage,
38
37
  context: grpc.ServicerContext,
39
- account_info: Optional[AccountInfo],
38
+ account_info: AccountInfo | None,
40
39
  method_name: str,
41
40
  ) -> LogEntry:
42
41
  """Compose pre-event log entry from the provided request and context."""
@@ -46,9 +45,9 @@ class EventLogWriterPlugin(ABC):
46
45
  self,
47
46
  request: GrpcMessage,
48
47
  context: grpc.ServicerContext,
49
- account_info: Optional[AccountInfo],
48
+ account_info: AccountInfo | None,
50
49
  method_name: str,
51
- response: Optional[Union[GrpcMessage, BaseException]],
50
+ response: GrpcMessage | BaseException | None,
52
51
  ) -> LogEntry:
53
52
  """Compose post-event log entry from the provided response and context."""
54
53