flwr 1.17.0__py3-none-any.whl → 1.19.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +15 -0
  3. flwr/app/error.py +68 -0
  4. flwr/app/metadata.py +223 -0
  5. flwr/cli/__init__.py +1 -1
  6. flwr/cli/app.py +21 -2
  7. flwr/cli/build.py +83 -58
  8. flwr/cli/cli_user_auth_interceptor.py +1 -1
  9. flwr/cli/config_utils.py +53 -17
  10. flwr/cli/example.py +1 -1
  11. flwr/cli/install.py +1 -1
  12. flwr/cli/log.py +4 -4
  13. flwr/cli/login/__init__.py +1 -1
  14. flwr/cli/login/login.py +15 -8
  15. flwr/cli/ls.py +16 -37
  16. flwr/cli/new/__init__.py +1 -1
  17. flwr/cli/new/new.py +4 -4
  18. flwr/cli/new/templates/__init__.py +1 -1
  19. flwr/cli/new/templates/app/__init__.py +1 -1
  20. flwr/cli/new/templates/app/code/__init__.py +1 -1
  21. flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
  22. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
  23. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +4 -4
  24. flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
  25. flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
  26. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -17
  28. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
  29. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  30. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  31. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  33. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  34. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  35. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  36. flwr/cli/run/__init__.py +1 -1
  37. flwr/cli/run/run.py +11 -19
  38. flwr/cli/stop.py +3 -3
  39. flwr/cli/utils.py +42 -17
  40. flwr/client/__init__.py +3 -3
  41. flwr/client/client.py +1 -1
  42. flwr/client/client_app.py +140 -138
  43. flwr/client/clientapp/__init__.py +1 -8
  44. flwr/client/clientapp/utils.py +1 -1
  45. flwr/client/dpfedavg_numpy_client.py +1 -1
  46. flwr/client/grpc_adapter_client/__init__.py +1 -1
  47. flwr/client/grpc_adapter_client/connection.py +5 -5
  48. flwr/client/grpc_rere_client/__init__.py +1 -1
  49. flwr/client/grpc_rere_client/client_interceptor.py +1 -1
  50. flwr/client/grpc_rere_client/connection.py +131 -61
  51. flwr/client/grpc_rere_client/grpc_adapter.py +35 -7
  52. flwr/client/message_handler/__init__.py +1 -1
  53. flwr/client/message_handler/message_handler.py +2 -2
  54. flwr/client/mod/__init__.py +1 -1
  55. flwr/client/mod/centraldp_mods.py +1 -1
  56. flwr/client/mod/comms_mods.py +39 -20
  57. flwr/client/mod/localdp_mod.py +6 -6
  58. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  59. flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
  60. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  61. flwr/client/mod/utils.py +1 -1
  62. flwr/client/numpy_client.py +1 -1
  63. flwr/client/rest_client/__init__.py +1 -1
  64. flwr/client/rest_client/connection.py +174 -68
  65. flwr/client/run_info_store.py +1 -1
  66. flwr/client/typing.py +1 -1
  67. flwr/clientapp/__init__.py +15 -0
  68. flwr/common/__init__.py +3 -3
  69. flwr/common/address.py +1 -1
  70. flwr/common/args.py +1 -1
  71. flwr/common/auth_plugin/__init__.py +3 -1
  72. flwr/common/auth_plugin/auth_plugin.py +30 -4
  73. flwr/common/config.py +1 -1
  74. flwr/common/constant.py +37 -8
  75. flwr/common/context.py +1 -1
  76. flwr/common/date.py +1 -1
  77. flwr/common/differential_privacy.py +1 -1
  78. flwr/common/differential_privacy_constants.py +1 -1
  79. flwr/common/dp.py +1 -1
  80. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  81. flwr/common/exit/exit.py +6 -6
  82. flwr/common/exit_handlers.py +31 -1
  83. flwr/common/grpc.py +1 -1
  84. flwr/common/heartbeat.py +165 -0
  85. flwr/common/inflatable.py +290 -0
  86. flwr/common/inflatable_grpc_utils.py +99 -0
  87. flwr/common/inflatable_rest_utils.py +99 -0
  88. flwr/common/inflatable_utils.py +341 -0
  89. flwr/common/logger.py +1 -1
  90. flwr/common/message.py +137 -252
  91. flwr/common/object_ref.py +1 -1
  92. flwr/common/parameter.py +1 -1
  93. flwr/common/pyproject.py +1 -1
  94. flwr/common/record/__init__.py +3 -2
  95. flwr/common/record/array.py +323 -0
  96. flwr/common/record/arrayrecord.py +121 -243
  97. flwr/common/record/configrecord.py +71 -16
  98. flwr/common/record/conversion_utils.py +2 -2
  99. flwr/common/record/metricrecord.py +71 -20
  100. flwr/common/record/recorddict.py +207 -90
  101. flwr/common/record/typeddict.py +1 -1
  102. flwr/common/recorddict_compat.py +2 -2
  103. flwr/common/retry_invoker.py +15 -11
  104. flwr/common/secure_aggregation/__init__.py +1 -1
  105. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  106. flwr/common/secure_aggregation/crypto/shamir.py +52 -30
  107. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
  108. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  109. flwr/common/secure_aggregation/quantization.py +1 -1
  110. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  111. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  112. flwr/common/serde.py +60 -184
  113. flwr/common/serde_utils.py +175 -0
  114. flwr/common/telemetry.py +2 -2
  115. flwr/common/typing.py +6 -4
  116. flwr/common/version.py +1 -1
  117. flwr/compat/__init__.py +15 -0
  118. flwr/compat/client/__init__.py +15 -0
  119. flwr/{client → compat/client}/app.py +71 -211
  120. flwr/{client → compat/client}/grpc_client/__init__.py +1 -1
  121. flwr/{client → compat/client}/grpc_client/connection.py +13 -13
  122. flwr/compat/common/__init__.py +15 -0
  123. flwr/compat/server/__init__.py +15 -0
  124. flwr/compat/server/app.py +174 -0
  125. flwr/compat/simulation/__init__.py +15 -0
  126. flwr/proto/__init__.py +1 -1
  127. flwr/proto/fleet_pb2.py +32 -27
  128. flwr/proto/fleet_pb2.pyi +49 -35
  129. flwr/proto/fleet_pb2_grpc.py +117 -13
  130. flwr/proto/fleet_pb2_grpc.pyi +47 -6
  131. flwr/proto/heartbeat_pb2.py +33 -0
  132. flwr/proto/heartbeat_pb2.pyi +66 -0
  133. flwr/proto/heartbeat_pb2_grpc.py +4 -0
  134. flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
  135. flwr/proto/message_pb2.py +28 -11
  136. flwr/proto/message_pb2.pyi +125 -0
  137. flwr/proto/recorddict_pb2.py +16 -28
  138. flwr/proto/recorddict_pb2.pyi +46 -64
  139. flwr/proto/run_pb2.py +24 -32
  140. flwr/proto/run_pb2.pyi +4 -52
  141. flwr/proto/serverappio_pb2.py +32 -23
  142. flwr/proto/serverappio_pb2.pyi +45 -3
  143. flwr/proto/serverappio_pb2_grpc.py +138 -34
  144. flwr/proto/serverappio_pb2_grpc.pyi +54 -13
  145. flwr/proto/simulationio_pb2.py +12 -11
  146. flwr/proto/simulationio_pb2_grpc.py +35 -0
  147. flwr/proto/simulationio_pb2_grpc.pyi +14 -0
  148. flwr/server/__init__.py +2 -2
  149. flwr/server/app.py +69 -187
  150. flwr/server/client_manager.py +1 -1
  151. flwr/server/client_proxy.py +1 -1
  152. flwr/server/compat/__init__.py +1 -1
  153. flwr/server/compat/app.py +1 -1
  154. flwr/server/compat/app_utils.py +51 -29
  155. flwr/server/compat/legacy_context.py +1 -1
  156. flwr/server/criterion.py +1 -1
  157. flwr/server/fleet_event_log_interceptor.py +2 -2
  158. flwr/server/grid/grid.py +3 -3
  159. flwr/server/grid/grpc_grid.py +104 -34
  160. flwr/server/grid/inmemory_grid.py +5 -4
  161. flwr/server/history.py +1 -1
  162. flwr/server/run_serverapp.py +1 -1
  163. flwr/server/server.py +1 -1
  164. flwr/server/server_app.py +65 -58
  165. flwr/server/server_config.py +1 -1
  166. flwr/server/serverapp/__init__.py +1 -1
  167. flwr/server/serverapp/app.py +19 -1
  168. flwr/server/serverapp_components.py +1 -1
  169. flwr/server/strategy/__init__.py +1 -1
  170. flwr/server/strategy/aggregate.py +1 -1
  171. flwr/server/strategy/bulyan.py +2 -2
  172. flwr/server/strategy/dp_adaptive_clipping.py +17 -17
  173. flwr/server/strategy/dp_fixed_clipping.py +17 -17
  174. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  175. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  176. flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
  177. flwr/server/strategy/fedadagrad.py +1 -1
  178. flwr/server/strategy/fedadam.py +1 -1
  179. flwr/server/strategy/fedavg.py +1 -1
  180. flwr/server/strategy/fedavg_android.py +1 -1
  181. flwr/server/strategy/fedavgm.py +1 -1
  182. flwr/server/strategy/fedmedian.py +1 -1
  183. flwr/server/strategy/fedopt.py +1 -1
  184. flwr/server/strategy/fedprox.py +1 -1
  185. flwr/server/strategy/fedtrimmedavg.py +1 -1
  186. flwr/server/strategy/fedxgb_bagging.py +1 -1
  187. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  188. flwr/server/strategy/fedxgb_nn_avg.py +3 -2
  189. flwr/server/strategy/fedyogi.py +1 -1
  190. flwr/server/strategy/krum.py +1 -1
  191. flwr/server/strategy/qfedavg.py +1 -1
  192. flwr/server/strategy/strategy.py +1 -1
  193. flwr/server/superlink/__init__.py +1 -1
  194. flwr/server/superlink/ffs/__init__.py +3 -1
  195. flwr/server/superlink/ffs/disk_ffs.py +1 -1
  196. flwr/server/superlink/ffs/ffs.py +1 -1
  197. flwr/server/superlink/ffs/ffs_factory.py +1 -1
  198. flwr/server/superlink/fleet/__init__.py +1 -1
  199. flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
  200. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +14 -4
  201. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  202. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  203. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  204. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
  206. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  207. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
  208. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
  209. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  210. flwr/server/superlink/fleet/message_handler/message_handler.py +136 -19
  211. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  212. flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -12
  213. flwr/server/superlink/fleet/vce/__init__.py +1 -1
  214. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
  215. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  216. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
  217. flwr/server/superlink/fleet/vce/vce_api.py +7 -4
  218. flwr/server/superlink/linkstate/__init__.py +1 -1
  219. flwr/server/superlink/linkstate/in_memory_linkstate.py +139 -44
  220. flwr/server/superlink/linkstate/linkstate.py +54 -21
  221. flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
  222. flwr/server/superlink/linkstate/sqlite_linkstate.py +150 -56
  223. flwr/server/superlink/linkstate/utils.py +34 -30
  224. flwr/server/superlink/serverappio/serverappio_grpc.py +3 -0
  225. flwr/server/superlink/serverappio/serverappio_servicer.py +211 -57
  226. flwr/server/superlink/simulation/__init__.py +1 -1
  227. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  228. flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
  229. flwr/server/superlink/utils.py +45 -3
  230. flwr/server/typing.py +1 -1
  231. flwr/server/utils/__init__.py +1 -1
  232. flwr/server/utils/tensorboard.py +1 -1
  233. flwr/server/utils/validator.py +3 -3
  234. flwr/server/workflow/__init__.py +1 -1
  235. flwr/server/workflow/constant.py +1 -1
  236. flwr/server/workflow/default_workflows.py +1 -1
  237. flwr/server/workflow/secure_aggregation/__init__.py +1 -1
  238. flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
  239. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  240. flwr/serverapp/__init__.py +15 -0
  241. flwr/simulation/__init__.py +1 -1
  242. flwr/simulation/app.py +18 -1
  243. flwr/simulation/legacy_app.py +1 -1
  244. flwr/simulation/ray_transport/__init__.py +1 -1
  245. flwr/simulation/ray_transport/ray_actor.py +1 -1
  246. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  247. flwr/simulation/ray_transport/utils.py +1 -1
  248. flwr/simulation/run_simulation.py +2 -2
  249. flwr/simulation/simulationio_connection.py +1 -1
  250. flwr/supercore/__init__.py +15 -0
  251. flwr/supercore/object_store/__init__.py +24 -0
  252. flwr/supercore/object_store/in_memory_object_store.py +229 -0
  253. flwr/supercore/object_store/object_store.py +192 -0
  254. flwr/supercore/object_store/object_store_factory.py +44 -0
  255. flwr/superexec/__init__.py +1 -1
  256. flwr/superexec/app.py +1 -1
  257. flwr/superexec/deployment.py +7 -3
  258. flwr/superexec/exec_event_log_interceptor.py +4 -4
  259. flwr/superexec/exec_grpc.py +8 -4
  260. flwr/superexec/exec_servicer.py +126 -24
  261. flwr/superexec/exec_user_auth_interceptor.py +38 -9
  262. flwr/superexec/executor.py +5 -1
  263. flwr/superexec/simulation.py +8 -2
  264. flwr/superlink/__init__.py +15 -0
  265. flwr/{client/supernode → supernode}/__init__.py +1 -8
  266. flwr/{client/nodestate/nodestate.py → supernode/cli/__init__.py} +8 -15
  267. flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +4 -13
  268. flwr/supernode/cli/flwr_clientapp.py +81 -0
  269. flwr/{client → supernode}/nodestate/__init__.py +1 -1
  270. flwr/supernode/nodestate/in_memory_nodestate.py +190 -0
  271. flwr/supernode/nodestate/nodestate.py +212 -0
  272. flwr/{client → supernode}/nodestate/nodestate_factory.py +1 -1
  273. flwr/supernode/runtime/__init__.py +15 -0
  274. flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +26 -57
  275. flwr/supernode/servicer/__init__.py +15 -0
  276. flwr/supernode/servicer/clientappio/__init__.py +24 -0
  277. flwr/{client/clientapp → supernode/servicer/clientappio}/clientappio_servicer.py +1 -1
  278. flwr/supernode/start_client_internal.py +491 -0
  279. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/METADATA +6 -5
  280. flwr-1.19.0.dist-info/RECORD +365 -0
  281. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/WHEEL +1 -1
  282. {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/entry_points.txt +2 -2
  283. flwr/client/heartbeat.py +0 -74
  284. flwr/client/nodestate/in_memory_nodestate.py +0 -38
  285. flwr-1.17.0.dist-info/LICENSE +0 -202
  286. flwr-1.17.0.dist-info/RECORD +0 -333
@@ -0,0 +1,491 @@
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
+ """Main loop for Flower SuperNode."""
16
+
17
+
18
+ import os
19
+ import subprocess
20
+ import time
21
+ from collections.abc import Iterator
22
+ from contextlib import contextmanager
23
+ from logging import INFO, WARN
24
+ from os import urandom
25
+ from pathlib import Path
26
+ from typing import Callable, Optional, Union, cast
27
+
28
+ import grpc
29
+ from cryptography.hazmat.primitives.asymmetric import ec
30
+ from grpc import RpcError
31
+
32
+ from flwr.client.grpc_adapter_client.connection import grpc_adapter
33
+ from flwr.client.grpc_rere_client.connection import grpc_request_response
34
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
35
+ from flwr.common.address import parse_address
36
+ from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
37
+ from flwr.common.constant import (
38
+ CLIENT_OCTET,
39
+ CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
40
+ ISOLATION_MODE_SUBPROCESS,
41
+ MAX_RETRY_DELAY,
42
+ RUN_ID_NUM_BYTES,
43
+ SERVER_OCTET,
44
+ TRANSPORT_TYPE_GRPC_ADAPTER,
45
+ TRANSPORT_TYPE_GRPC_RERE,
46
+ TRANSPORT_TYPE_REST,
47
+ TRANSPORT_TYPES,
48
+ )
49
+ from flwr.common.exit import ExitCode, flwr_exit
50
+ from flwr.common.grpc import generic_create_grpc_server
51
+ from flwr.common.logger import log
52
+ from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
53
+ from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
54
+ from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
55
+ from flwr.server.superlink.ffs import Ffs, FfsFactory
56
+ from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
57
+ from flwr.supernode.nodestate import NodeState, NodeStateFactory
58
+ from flwr.supernode.servicer.clientappio import ClientAppInputs, ClientAppIoServicer
59
+
60
+ DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
61
+
62
+
63
+ # pylint: disable=import-outside-toplevel
64
+ # pylint: disable=too-many-branches
65
+ # pylint: disable=too-many-locals
66
+ # pylint: disable=too-many-statements
67
+ # pylint: disable=too-many-arguments
68
+ def start_client_internal(
69
+ *,
70
+ server_address: str,
71
+ node_config: UserConfig,
72
+ root_certificates: Optional[Union[bytes, str]] = None,
73
+ insecure: Optional[bool] = None,
74
+ transport: str,
75
+ authentication_keys: Optional[
76
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
77
+ ] = None,
78
+ max_retries: Optional[int] = None,
79
+ max_wait_time: Optional[float] = None,
80
+ flwr_path: Optional[Path] = None,
81
+ isolation: str = ISOLATION_MODE_SUBPROCESS,
82
+ clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
83
+ ) -> None:
84
+ """Start a Flower client node which connects to a Flower server.
85
+
86
+ Parameters
87
+ ----------
88
+ server_address : str
89
+ The IPv4 or IPv6 address of the server. If the Flower
90
+ server runs on the same machine on port 8080, then `server_address`
91
+ would be `"[::]:8080"`.
92
+ node_config: UserConfig
93
+ The configuration of the node.
94
+ root_certificates : Optional[Union[bytes, str]] (default: None)
95
+ The PEM-encoded root certificates as a byte string or a path string.
96
+ If provided, a secure connection using the certificates will be
97
+ established to an SSL-enabled Flower server.
98
+ insecure : Optional[bool] (default: None)
99
+ Starts an insecure gRPC connection when True. Enables HTTPS connection
100
+ when False, using system certificates if `root_certificates` is None.
101
+ transport : str
102
+ Configure the transport layer. Allowed values:
103
+ - 'grpc-rere': gRPC, request-response
104
+ - 'grpc-adapter': gRPC via 3rd party adapter (experimental)
105
+ - 'rest': HTTP (experimental)
106
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
107
+ Tuple containing the elliptic curve private key and public key for
108
+ authentication from the cryptography library.
109
+ Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
110
+ Used to establish an authenticated connection with the server.
111
+ max_retries: Optional[int] (default: None)
112
+ The maximum number of times the client will try to connect to the
113
+ server before giving up in case of a connection error. If set to None,
114
+ there is no limit to the number of tries.
115
+ max_wait_time: Optional[float] (default: None)
116
+ The maximum duration before the client stops trying to
117
+ connect to the server in case of connection error.
118
+ If set to None, there is no limit to the total time.
119
+ flwr_path: Optional[Path] (default: None)
120
+ The fully resolved path containing installed Flower Apps.
121
+ isolation : str (default: ISOLATION_MODE_SUBPROCESS)
122
+ Isolation mode for `ClientApp`. Possible values are `subprocess` and
123
+ `process`. If `subprocess`, the `ClientApp` runs in a subprocess started
124
+ by the SueprNode and communicates using gRPC at the address
125
+ `clientappio_api_address`. If `process`, the `ClientApp` runs in a separate
126
+ isolated process and communicates using gRPC at the address
127
+ `clientappio_api_address`.
128
+ clientappio_api_address : str
129
+ (default: `CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS`)
130
+ The SuperNode gRPC server address.
131
+ """
132
+ if insecure is None:
133
+ insecure = root_certificates is None
134
+
135
+ _clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
136
+ address=clientappio_api_address,
137
+ certificates=None,
138
+ )
139
+
140
+ # Initialize factories
141
+ state_factory = NodeStateFactory()
142
+ ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
143
+ object_store_factory = ObjectStoreFactory()
144
+
145
+ # Initialize NodeState, Ffs, and ObjectStore
146
+ state = state_factory.state()
147
+ ffs = ffs_factory.ffs()
148
+ store = object_store_factory.store()
149
+
150
+ with _init_connection(
151
+ transport=transport,
152
+ server_address=server_address,
153
+ insecure=insecure,
154
+ root_certificates=root_certificates,
155
+ authentication_keys=authentication_keys,
156
+ max_retries=max_retries,
157
+ max_wait_time=max_wait_time,
158
+ ) as conn:
159
+ receive, send, create_node, _, get_run, get_fab = conn
160
+
161
+ # Call create_node fn to register node
162
+ # and store node_id in state
163
+ if (node_id := create_node()) is None:
164
+ raise ValueError("Failed to register SuperNode with the SuperLink")
165
+ state.set_node_id(node_id)
166
+
167
+ # pylint: disable=too-many-nested-blocks
168
+ while True:
169
+ # The signature of the function will change after
170
+ # completing the transition to the `NodeState`-based SuperNode
171
+ run_id = _pull_and_store_message(
172
+ state=state,
173
+ ffs=ffs,
174
+ object_store=store,
175
+ node_config=node_config,
176
+ receive=receive,
177
+ get_run=get_run,
178
+ get_fab=get_fab,
179
+ )
180
+
181
+ if run_id is None:
182
+ time.sleep(3) # Wait for 3s before asking again
183
+ continue
184
+
185
+ try:
186
+ # Retrieve message, context, run and fab for this run
187
+ message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
188
+ context = cast(Context, state.get_context(run_id))
189
+ run = cast(Run, state.get_run(run_id))
190
+ fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
191
+
192
+ # Two isolation modes:
193
+ # 1. `subprocess`: SuperNode is starting the ClientApp
194
+ # process as a subprocess.
195
+ # 2. `process`: ClientApp process gets started separately
196
+ # (via `flwr-clientapp`), for example, in a separate
197
+ # Docker container.
198
+
199
+ # Generate SuperNode token
200
+ token = int.from_bytes(urandom(RUN_ID_NUM_BYTES), "little")
201
+
202
+ # Mode 1: SuperNode starts ClientApp as subprocess
203
+ start_subprocess = isolation == ISOLATION_MODE_SUBPROCESS
204
+
205
+ # Share Message and Context with servicer
206
+ clientappio_servicer.set_inputs(
207
+ clientapp_input=ClientAppInputs(
208
+ message=message,
209
+ context=context,
210
+ run=run,
211
+ fab=fab,
212
+ token=token,
213
+ ),
214
+ token_returned=start_subprocess,
215
+ )
216
+
217
+ if start_subprocess:
218
+ _octet, _colon, _port = clientappio_api_address.rpartition(":")
219
+ io_address = (
220
+ f"{CLIENT_OCTET}:{_port}"
221
+ if _octet == SERVER_OCTET
222
+ else clientappio_api_address
223
+ )
224
+ # Start ClientApp subprocess
225
+ command = [
226
+ "flwr-clientapp",
227
+ "--clientappio-api-address",
228
+ io_address,
229
+ "--token",
230
+ str(token),
231
+ "--parent-pid",
232
+ str(os.getpid()),
233
+ "--insecure",
234
+ ]
235
+ subprocess.run(command, check=False)
236
+ else:
237
+ # Wait for output to become available
238
+ while not clientappio_servicer.has_outputs():
239
+ time.sleep(0.1)
240
+
241
+ outputs = clientappio_servicer.get_outputs()
242
+ reply_message, context = outputs.message, outputs.context
243
+
244
+ # Update context in the state
245
+ state.store_context(context)
246
+
247
+ # Send
248
+ send(reply_message)
249
+
250
+ # Delete messages from the state
251
+ state.delete_messages(
252
+ message_ids=[
253
+ message.metadata.message_id,
254
+ message.metadata.reply_to_message_id,
255
+ ]
256
+ )
257
+
258
+ log(INFO, "Sent reply")
259
+
260
+ except RunNotRunningException:
261
+ log(INFO, "")
262
+ log(
263
+ INFO,
264
+ "SuperNode aborted sending the reply message. "
265
+ "Run ID %s is not in `RUNNING` status.",
266
+ run_id,
267
+ )
268
+ log(INFO, "")
269
+
270
+
271
+ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
272
+ state: NodeState,
273
+ ffs: Ffs,
274
+ object_store: ObjectStore, # pylint: disable=unused-argument
275
+ node_config: UserConfig,
276
+ receive: Callable[[], Optional[Message]],
277
+ get_run: Callable[[int], Run],
278
+ get_fab: Callable[[str, int], Fab],
279
+ ) -> Optional[int]:
280
+ """Pull a message from the SuperLink and store it in the state.
281
+
282
+ This function current returns None if no message is received,
283
+ or run_id if a message is received and processed successfully.
284
+ This behavior will change in the future to return None after
285
+ completing transition to the `NodeState`-based SuperNode.
286
+ """
287
+ message = None
288
+ try:
289
+ # Pull message
290
+ if (message := receive()) is None:
291
+ return None
292
+
293
+ # Log message reception
294
+ log(INFO, "")
295
+ if message.metadata.group_id:
296
+ log(
297
+ INFO,
298
+ "[RUN %s, ROUND %s]",
299
+ message.metadata.run_id,
300
+ message.metadata.group_id,
301
+ )
302
+ else:
303
+ log(INFO, "[RUN %s]", message.metadata.run_id)
304
+ log(
305
+ INFO,
306
+ "Received: %s message %s",
307
+ message.metadata.message_type,
308
+ message.metadata.message_id,
309
+ )
310
+
311
+ # Ensure the run and FAB are available
312
+ run_id = message.metadata.run_id
313
+
314
+ # Check if the message is from an unknown run
315
+ if (run_info := state.get_run(run_id)) is None:
316
+ # Pull run info from SuperLink
317
+ run_info = get_run(run_id)
318
+ state.store_run(run_info)
319
+
320
+ # Pull and store the FAB
321
+ fab = get_fab(run_info.fab_hash, run_id)
322
+ ffs.put(fab.content, {})
323
+
324
+ # Initialize the context
325
+ run_cfg = get_fused_config_from_fab(fab.content, run_info)
326
+ run_ctx = Context(
327
+ run_id=run_id,
328
+ node_id=state.get_node_id(),
329
+ node_config=node_config,
330
+ state=RecordDict(),
331
+ run_config=run_cfg,
332
+ )
333
+ state.store_context(run_ctx)
334
+
335
+ # Store the message in the state
336
+ state.store_message(message)
337
+ except RunNotRunningException:
338
+ if message is None:
339
+ log(
340
+ INFO,
341
+ "Run transitioned to a non-`RUNNING` status while receiving a message. "
342
+ "Ignoring the message.",
343
+ )
344
+ else:
345
+ log(
346
+ INFO,
347
+ "Run ID %s is not in `RUNNING` status. Ignoring message %s.",
348
+ run_id,
349
+ message.metadata.message_id,
350
+ )
351
+ return None
352
+
353
+ return run_id
354
+
355
+
356
+ @contextmanager
357
+ def _init_connection( # pylint: disable=too-many-positional-arguments
358
+ transport: str,
359
+ server_address: str,
360
+ insecure: bool,
361
+ root_certificates: Optional[Union[bytes, str]] = None,
362
+ authentication_keys: Optional[
363
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
364
+ ] = None,
365
+ max_retries: Optional[int] = None,
366
+ max_wait_time: Optional[float] = None,
367
+ ) -> Iterator[
368
+ tuple[
369
+ Callable[[], Optional[Message]],
370
+ Callable[[Message], None],
371
+ Callable[[], Optional[int]],
372
+ Callable[[], None],
373
+ Callable[[int], Run],
374
+ Callable[[str, int], Fab],
375
+ ]
376
+ ]:
377
+ """Establish a connection to the Fleet API server at SuperLink."""
378
+ # Parse IP address
379
+ parsed_address = parse_address(server_address)
380
+ if not parsed_address:
381
+ flwr_exit(
382
+ ExitCode.COMMON_ADDRESS_INVALID,
383
+ f"SuperLink address ({server_address}) cannot be parsed.",
384
+ )
385
+ host, port, is_v6 = parsed_address
386
+ address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}"
387
+
388
+ # Use either gRPC bidirectional streaming or REST request/response
389
+ if transport == TRANSPORT_TYPE_REST:
390
+ try:
391
+ from requests.exceptions import ConnectionError as RequestsConnectionError
392
+
393
+ from flwr.client.rest_client.connection import http_request_response
394
+ except ModuleNotFoundError:
395
+ flwr_exit(ExitCode.COMMON_MISSING_EXTRA_REST)
396
+ if server_address[:4] != "http":
397
+ flwr_exit(ExitCode.SUPERNODE_REST_ADDRESS_INVALID)
398
+ connection, error_type = http_request_response, RequestsConnectionError
399
+ elif transport == TRANSPORT_TYPE_GRPC_RERE:
400
+ connection, error_type = grpc_request_response, RpcError
401
+ elif transport == TRANSPORT_TYPE_GRPC_ADAPTER:
402
+ connection, error_type = grpc_adapter, RpcError
403
+ else:
404
+ raise ValueError(
405
+ f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES})"
406
+ )
407
+
408
+ # Create RetryInvoker
409
+ retry_invoker = _make_fleet_connection_retry_invoker(
410
+ max_retries=max_retries,
411
+ max_wait_time=max_wait_time,
412
+ connection_error_type=error_type,
413
+ )
414
+
415
+ # Establish connection
416
+ with connection(
417
+ address,
418
+ insecure,
419
+ retry_invoker,
420
+ GRPC_MAX_MESSAGE_LENGTH,
421
+ root_certificates,
422
+ authentication_keys,
423
+ ) as conn:
424
+ yield conn
425
+
426
+
427
+ def _make_fleet_connection_retry_invoker(
428
+ max_retries: Optional[int] = None,
429
+ max_wait_time: Optional[float] = None,
430
+ connection_error_type: type[Exception] = RpcError,
431
+ ) -> RetryInvoker:
432
+ """Create a retry invoker for fleet connection."""
433
+
434
+ def _on_success(retry_state: RetryState) -> None:
435
+ if retry_state.tries > 1:
436
+ log(
437
+ INFO,
438
+ "Connection successful after %.2f seconds and %s tries.",
439
+ retry_state.elapsed_time,
440
+ retry_state.tries,
441
+ )
442
+
443
+ def _on_backoff(retry_state: RetryState) -> None:
444
+ if retry_state.tries == 1:
445
+ log(WARN, "Connection attempt failed, retrying...")
446
+ else:
447
+ log(
448
+ WARN,
449
+ "Connection attempt failed, retrying in %.2f seconds",
450
+ retry_state.actual_wait,
451
+ )
452
+
453
+ return RetryInvoker(
454
+ wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
455
+ recoverable_exceptions=connection_error_type,
456
+ max_tries=max_retries + 1 if max_retries is not None else None,
457
+ max_time=max_wait_time,
458
+ on_giveup=lambda retry_state: (
459
+ log(
460
+ WARN,
461
+ "Giving up reconnection after %.2f seconds and %s tries.",
462
+ retry_state.elapsed_time,
463
+ retry_state.tries,
464
+ )
465
+ if retry_state.tries > 1
466
+ else None
467
+ ),
468
+ on_success=_on_success,
469
+ on_backoff=_on_backoff,
470
+ )
471
+
472
+
473
+ def run_clientappio_api_grpc(
474
+ address: str,
475
+ certificates: Optional[tuple[bytes, bytes, bytes]],
476
+ ) -> tuple[grpc.Server, ClientAppIoServicer]:
477
+ """Run ClientAppIo API gRPC server."""
478
+ clientappio_servicer: grpc.Server = ClientAppIoServicer()
479
+ clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
480
+ clientappio_grpc_server = generic_create_grpc_server(
481
+ servicer_and_add_fn=(
482
+ clientappio_servicer,
483
+ clientappio_add_servicer_to_server_fn,
484
+ ),
485
+ server_address=address,
486
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
487
+ certificates=certificates,
488
+ )
489
+ log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
490
+ clientappio_grpc_server.start()
491
+ return clientappio_grpc_server, clientappio_servicer
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: flwr
3
- Version: 1.17.0
3
+ Version: 1.19.0
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
- Home-page: https://flower.ai
6
5
  License: Apache-2.0
7
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
8
7
  Author: The Flower Authors
@@ -19,8 +18,8 @@ Classifier: Programming Language :: Python :: 3
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3 :: Only
23
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3 :: Only
24
23
  Classifier: Programming Language :: Python :: 3.9
25
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
25
  Classifier: Topic :: Scientific/Engineering
@@ -32,6 +31,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
31
  Classifier: Typing :: Typed
33
32
  Provides-Extra: rest
34
33
  Provides-Extra: simulation
34
+ Requires-Dist: click (<8.2.0)
35
35
  Requires-Dist: cryptography (>=44.0.1,<45.0.0)
36
36
  Requires-Dist: grpcio (>=1.62.3,<2.0.0,!=1.65.0)
37
37
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
@@ -49,6 +49,7 @@ Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
49
49
  Requires-Dist: typer (>=0.12.5,<0.13.0)
50
50
  Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
51
51
  Project-URL: Documentation, https://flower.ai
52
+ Project-URL: Homepage, https://flower.ai
52
53
  Project-URL: Repository, https://github.com/adap/flower
53
54
  Description-Content-Type: text/markdown
54
55
 
@@ -56,7 +57,7 @@ Description-Content-Type: text/markdown
56
57
 
57
58
  <p align="center">
58
59
  <a href="https://flower.ai/">
59
- <img src="https://flower.ai/_next/image/?url=%2F_next%2Fstatic%2Fmedia%2Fflower_white_border.c2012e70.png&w=640&q=75" width="140px" alt="Flower Website" />
60
+ <img src="https://flower.ai/_next/image/?url=%2F_next%2Fstatic%2Fmedia%2Fflwr-head.4d68867a.png&w=384&q=75" width="140px" alt="Flower Website" />
60
61
  </a>
61
62
  </p>
62
63
  <p align="center">