flwr-nightly 1.8.0.dev20240315__py3-none-any.whl → 1.15.0.dev20250115__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (312) hide show
  1. flwr/cli/app.py +16 -2
  2. flwr/cli/build.py +181 -0
  3. flwr/cli/cli_user_auth_interceptor.py +90 -0
  4. flwr/cli/config_utils.py +343 -0
  5. flwr/cli/example.py +4 -1
  6. flwr/cli/install.py +253 -0
  7. flwr/cli/log.py +182 -0
  8. flwr/{server/superlink/state → cli/login}/__init__.py +4 -10
  9. flwr/cli/login/login.py +88 -0
  10. flwr/cli/ls.py +327 -0
  11. flwr/cli/new/__init__.py +1 -0
  12. flwr/cli/new/new.py +210 -66
  13. flwr/cli/new/templates/app/.gitignore.tpl +163 -0
  14. flwr/cli/new/templates/app/LICENSE.tpl +202 -0
  15. flwr/cli/new/templates/app/README.baseline.md.tpl +127 -0
  16. flwr/cli/new/templates/app/README.flowertune.md.tpl +66 -0
  17. flwr/cli/new/templates/app/README.md.tpl +16 -32
  18. flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +1 -0
  19. flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
  20. flwr/cli/new/templates/app/code/client.baseline.py.tpl +58 -0
  21. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +55 -0
  22. flwr/cli/new/templates/app/code/client.jax.py.tpl +50 -0
  23. flwr/cli/new/templates/app/code/client.mlx.py.tpl +73 -0
  24. flwr/cli/new/templates/app/code/client.numpy.py.tpl +7 -7
  25. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +30 -21
  26. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +63 -0
  27. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +57 -1
  28. flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +36 -0
  29. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
  30. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +126 -0
  31. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +87 -0
  32. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +78 -0
  33. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +94 -0
  34. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +83 -0
  35. flwr/cli/new/templates/app/code/model.baseline.py.tpl +80 -0
  36. flwr/cli/new/templates/app/code/server.baseline.py.tpl +46 -0
  37. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +38 -0
  38. flwr/cli/new/templates/app/code/server.jax.py.tpl +26 -0
  39. flwr/cli/new/templates/app/code/server.mlx.py.tpl +31 -0
  40. flwr/cli/new/templates/app/code/server.numpy.py.tpl +22 -9
  41. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +21 -18
  42. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +36 -0
  43. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +29 -1
  44. flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +1 -0
  45. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +102 -0
  46. flwr/cli/new/templates/app/code/task.jax.py.tpl +57 -0
  47. flwr/cli/new/templates/app/code/task.mlx.py.tpl +102 -0
  48. flwr/cli/new/templates/app/code/task.numpy.py.tpl +7 -0
  49. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +29 -24
  50. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +67 -0
  51. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +53 -0
  52. flwr/cli/new/templates/app/code/utils.baseline.py.tpl +1 -0
  53. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +138 -0
  54. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +68 -0
  55. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +46 -0
  56. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +35 -0
  57. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +39 -0
  58. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +25 -12
  59. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +29 -14
  60. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +35 -0
  61. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +29 -14
  62. flwr/cli/run/__init__.py +1 -0
  63. flwr/cli/run/run.py +212 -34
  64. flwr/cli/stop.py +130 -0
  65. flwr/cli/utils.py +240 -5
  66. flwr/client/__init__.py +3 -2
  67. flwr/client/app.py +432 -255
  68. flwr/client/client.py +1 -11
  69. flwr/client/client_app.py +74 -13
  70. flwr/client/clientapp/__init__.py +22 -0
  71. flwr/client/clientapp/app.py +259 -0
  72. flwr/client/clientapp/clientappio_servicer.py +244 -0
  73. flwr/client/clientapp/utils.py +115 -0
  74. flwr/client/dpfedavg_numpy_client.py +7 -8
  75. flwr/client/grpc_adapter_client/__init__.py +15 -0
  76. flwr/client/grpc_adapter_client/connection.py +98 -0
  77. flwr/client/grpc_client/connection.py +21 -7
  78. flwr/client/grpc_rere_client/__init__.py +1 -1
  79. flwr/client/grpc_rere_client/client_interceptor.py +176 -0
  80. flwr/client/grpc_rere_client/connection.py +163 -56
  81. flwr/client/grpc_rere_client/grpc_adapter.py +167 -0
  82. flwr/client/heartbeat.py +74 -0
  83. flwr/client/message_handler/__init__.py +1 -1
  84. flwr/client/message_handler/message_handler.py +10 -11
  85. flwr/client/mod/__init__.py +5 -5
  86. flwr/client/mod/centraldp_mods.py +4 -2
  87. flwr/client/mod/comms_mods.py +5 -4
  88. flwr/client/mod/localdp_mod.py +10 -5
  89. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  90. flwr/client/mod/secure_aggregation/secaggplus_mod.py +26 -26
  91. flwr/client/mod/utils.py +2 -4
  92. flwr/client/nodestate/__init__.py +26 -0
  93. flwr/client/nodestate/in_memory_nodestate.py +38 -0
  94. flwr/client/nodestate/nodestate.py +31 -0
  95. flwr/client/nodestate/nodestate_factory.py +38 -0
  96. flwr/client/numpy_client.py +8 -31
  97. flwr/client/rest_client/__init__.py +1 -1
  98. flwr/client/rest_client/connection.py +199 -176
  99. flwr/client/run_info_store.py +112 -0
  100. flwr/client/supernode/__init__.py +24 -0
  101. flwr/client/supernode/app.py +321 -0
  102. flwr/client/typing.py +1 -0
  103. flwr/common/__init__.py +17 -11
  104. flwr/common/address.py +47 -3
  105. flwr/common/args.py +153 -0
  106. flwr/common/auth_plugin/__init__.py +24 -0
  107. flwr/common/auth_plugin/auth_plugin.py +121 -0
  108. flwr/common/config.py +243 -0
  109. flwr/common/constant.py +135 -1
  110. flwr/common/context.py +32 -2
  111. flwr/common/date.py +22 -4
  112. flwr/common/differential_privacy.py +2 -2
  113. flwr/common/dp.py +2 -4
  114. flwr/common/exit_handlers.py +3 -3
  115. flwr/common/grpc.py +164 -5
  116. flwr/common/logger.py +230 -12
  117. flwr/common/message.py +191 -106
  118. flwr/common/object_ref.py +179 -44
  119. flwr/common/pyproject.py +1 -0
  120. flwr/common/record/__init__.py +2 -1
  121. flwr/common/record/configsrecord.py +58 -18
  122. flwr/common/record/metricsrecord.py +57 -17
  123. flwr/common/record/parametersrecord.py +88 -20
  124. flwr/common/record/recordset.py +153 -30
  125. flwr/common/record/typeddict.py +30 -55
  126. flwr/common/recordset_compat.py +31 -12
  127. flwr/common/retry_invoker.py +123 -30
  128. flwr/common/secure_aggregation/__init__.py +1 -1
  129. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  130. flwr/common/secure_aggregation/crypto/shamir.py +11 -11
  131. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +68 -4
  132. flwr/common/secure_aggregation/ndarrays_arithmetic.py +17 -17
  133. flwr/common/secure_aggregation/quantization.py +8 -8
  134. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  135. flwr/common/secure_aggregation/secaggplus_utils.py +10 -12
  136. flwr/common/serde.py +304 -23
  137. flwr/common/telemetry.py +65 -29
  138. flwr/common/typing.py +120 -19
  139. flwr/common/version.py +17 -3
  140. flwr/proto/clientappio_pb2.py +45 -0
  141. flwr/proto/clientappio_pb2.pyi +132 -0
  142. flwr/proto/clientappio_pb2_grpc.py +135 -0
  143. flwr/proto/clientappio_pb2_grpc.pyi +53 -0
  144. flwr/proto/exec_pb2.py +62 -0
  145. flwr/proto/exec_pb2.pyi +212 -0
  146. flwr/proto/exec_pb2_grpc.py +237 -0
  147. flwr/proto/exec_pb2_grpc.pyi +93 -0
  148. flwr/proto/fab_pb2.py +31 -0
  149. flwr/proto/fab_pb2.pyi +65 -0
  150. flwr/proto/fab_pb2_grpc.py +4 -0
  151. flwr/proto/fab_pb2_grpc.pyi +4 -0
  152. flwr/proto/fleet_pb2.py +42 -23
  153. flwr/proto/fleet_pb2.pyi +123 -1
  154. flwr/proto/fleet_pb2_grpc.py +170 -0
  155. flwr/proto/fleet_pb2_grpc.pyi +61 -0
  156. flwr/proto/grpcadapter_pb2.py +32 -0
  157. flwr/proto/grpcadapter_pb2.pyi +43 -0
  158. flwr/proto/grpcadapter_pb2_grpc.py +66 -0
  159. flwr/proto/grpcadapter_pb2_grpc.pyi +24 -0
  160. flwr/proto/log_pb2.py +29 -0
  161. flwr/proto/log_pb2.pyi +39 -0
  162. flwr/proto/log_pb2_grpc.py +4 -0
  163. flwr/proto/log_pb2_grpc.pyi +4 -0
  164. flwr/proto/message_pb2.py +41 -0
  165. flwr/proto/message_pb2.pyi +128 -0
  166. flwr/proto/message_pb2_grpc.py +4 -0
  167. flwr/proto/message_pb2_grpc.pyi +4 -0
  168. flwr/proto/node_pb2.py +2 -2
  169. flwr/proto/node_pb2.pyi +1 -4
  170. flwr/proto/recordset_pb2.py +35 -33
  171. flwr/proto/recordset_pb2.pyi +40 -14
  172. flwr/proto/run_pb2.py +64 -0
  173. flwr/proto/run_pb2.pyi +268 -0
  174. flwr/proto/run_pb2_grpc.py +4 -0
  175. flwr/proto/run_pb2_grpc.pyi +4 -0
  176. flwr/proto/serverappio_pb2.py +52 -0
  177. flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +62 -20
  178. flwr/proto/serverappio_pb2_grpc.py +410 -0
  179. flwr/proto/serverappio_pb2_grpc.pyi +160 -0
  180. flwr/proto/simulationio_pb2.py +38 -0
  181. flwr/proto/simulationio_pb2.pyi +65 -0
  182. flwr/proto/simulationio_pb2_grpc.py +239 -0
  183. flwr/proto/simulationio_pb2_grpc.pyi +94 -0
  184. flwr/proto/task_pb2.py +7 -8
  185. flwr/proto/task_pb2.pyi +8 -5
  186. flwr/proto/transport_pb2.py +8 -8
  187. flwr/proto/transport_pb2.pyi +9 -6
  188. flwr/server/__init__.py +2 -10
  189. flwr/server/app.py +579 -402
  190. flwr/server/client_manager.py +8 -6
  191. flwr/server/compat/app.py +6 -62
  192. flwr/server/compat/app_utils.py +14 -9
  193. flwr/server/compat/driver_client_proxy.py +25 -59
  194. flwr/server/compat/legacy_context.py +5 -4
  195. flwr/server/driver/__init__.py +2 -0
  196. flwr/server/driver/driver.py +36 -131
  197. flwr/server/driver/grpc_driver.py +220 -81
  198. flwr/server/driver/inmemory_driver.py +183 -0
  199. flwr/server/history.py +28 -29
  200. flwr/server/run_serverapp.py +15 -126
  201. flwr/server/server.py +50 -44
  202. flwr/server/server_app.py +59 -10
  203. flwr/server/serverapp/__init__.py +22 -0
  204. flwr/server/serverapp/app.py +256 -0
  205. flwr/server/serverapp_components.py +52 -0
  206. flwr/server/strategy/__init__.py +2 -2
  207. flwr/server/strategy/aggregate.py +37 -23
  208. flwr/server/strategy/bulyan.py +9 -9
  209. flwr/server/strategy/dp_adaptive_clipping.py +25 -25
  210. flwr/server/strategy/dp_fixed_clipping.py +23 -22
  211. flwr/server/strategy/dpfedavg_adaptive.py +8 -8
  212. flwr/server/strategy/dpfedavg_fixed.py +13 -12
  213. flwr/server/strategy/fault_tolerant_fedavg.py +11 -11
  214. flwr/server/strategy/fedadagrad.py +9 -9
  215. flwr/server/strategy/fedadam.py +20 -10
  216. flwr/server/strategy/fedavg.py +16 -16
  217. flwr/server/strategy/fedavg_android.py +17 -17
  218. flwr/server/strategy/fedavgm.py +9 -9
  219. flwr/server/strategy/fedmedian.py +5 -5
  220. flwr/server/strategy/fedopt.py +6 -6
  221. flwr/server/strategy/fedprox.py +7 -7
  222. flwr/server/strategy/fedtrimmedavg.py +8 -8
  223. flwr/server/strategy/fedxgb_bagging.py +12 -12
  224. flwr/server/strategy/fedxgb_cyclic.py +10 -10
  225. flwr/server/strategy/fedxgb_nn_avg.py +6 -6
  226. flwr/server/strategy/fedyogi.py +9 -9
  227. flwr/server/strategy/krum.py +9 -9
  228. flwr/server/strategy/qfedavg.py +16 -16
  229. flwr/server/strategy/strategy.py +10 -10
  230. flwr/server/superlink/driver/__init__.py +2 -2
  231. flwr/server/superlink/driver/serverappio_grpc.py +61 -0
  232. flwr/server/superlink/driver/serverappio_servicer.py +361 -0
  233. flwr/server/superlink/ffs/__init__.py +24 -0
  234. flwr/server/superlink/ffs/disk_ffs.py +108 -0
  235. flwr/server/superlink/ffs/ffs.py +79 -0
  236. flwr/server/superlink/ffs/ffs_factory.py +47 -0
  237. flwr/server/superlink/fleet/__init__.py +1 -1
  238. flwr/server/superlink/fleet/grpc_adapter/__init__.py +15 -0
  239. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +162 -0
  240. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  241. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +4 -2
  242. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -2
  243. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  244. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +5 -154
  245. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  246. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +120 -13
  247. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +228 -0
  248. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  249. flwr/server/superlink/fleet/message_handler/message_handler.py +156 -13
  250. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  251. flwr/server/superlink/fleet/rest_rere/rest_api.py +119 -81
  252. flwr/server/superlink/fleet/vce/__init__.py +1 -0
  253. flwr/server/superlink/fleet/vce/backend/__init__.py +4 -4
  254. flwr/server/superlink/fleet/vce/backend/backend.py +8 -9
  255. flwr/server/superlink/fleet/vce/backend/raybackend.py +87 -68
  256. flwr/server/superlink/fleet/vce/vce_api.py +208 -146
  257. flwr/server/superlink/linkstate/__init__.py +28 -0
  258. flwr/server/superlink/linkstate/in_memory_linkstate.py +569 -0
  259. flwr/server/superlink/linkstate/linkstate.py +376 -0
  260. flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +19 -10
  261. flwr/server/superlink/linkstate/sqlite_linkstate.py +1196 -0
  262. flwr/server/superlink/linkstate/utils.py +399 -0
  263. flwr/server/superlink/simulation/__init__.py +15 -0
  264. flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
  265. flwr/server/superlink/simulation/simulationio_servicer.py +186 -0
  266. flwr/server/superlink/utils.py +65 -0
  267. flwr/server/typing.py +2 -0
  268. flwr/server/utils/__init__.py +1 -1
  269. flwr/server/utils/tensorboard.py +5 -5
  270. flwr/server/utils/validator.py +40 -45
  271. flwr/server/workflow/default_workflows.py +70 -26
  272. flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -0
  273. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +40 -27
  274. flwr/simulation/__init__.py +12 -5
  275. flwr/simulation/app.py +247 -315
  276. flwr/simulation/legacy_app.py +404 -0
  277. flwr/simulation/ray_transport/__init__.py +1 -1
  278. flwr/simulation/ray_transport/ray_actor.py +42 -67
  279. flwr/simulation/ray_transport/ray_client_proxy.py +37 -17
  280. flwr/simulation/ray_transport/utils.py +1 -0
  281. flwr/simulation/run_simulation.py +306 -163
  282. flwr/simulation/simulationio_connection.py +89 -0
  283. flwr/superexec/__init__.py +15 -0
  284. flwr/superexec/app.py +59 -0
  285. flwr/superexec/deployment.py +188 -0
  286. flwr/superexec/exec_grpc.py +80 -0
  287. flwr/superexec/exec_servicer.py +231 -0
  288. flwr/superexec/exec_user_auth_interceptor.py +101 -0
  289. flwr/superexec/executor.py +96 -0
  290. flwr/superexec/simulation.py +124 -0
  291. {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/METADATA +33 -26
  292. flwr_nightly-1.15.0.dev20250115.dist-info/RECORD +328 -0
  293. flwr_nightly-1.15.0.dev20250115.dist-info/entry_points.txt +12 -0
  294. flwr/cli/flower_toml.py +0 -140
  295. flwr/cli/new/templates/app/flower.toml.tpl +0 -13
  296. flwr/cli/new/templates/app/requirements.numpy.txt.tpl +0 -2
  297. flwr/cli/new/templates/app/requirements.pytorch.txt.tpl +0 -4
  298. flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl +0 -4
  299. flwr/client/node_state.py +0 -48
  300. flwr/client/node_state_tests.py +0 -65
  301. flwr/proto/driver_pb2.py +0 -44
  302. flwr/proto/driver_pb2_grpc.py +0 -169
  303. flwr/proto/driver_pb2_grpc.pyi +0 -66
  304. flwr/server/superlink/driver/driver_grpc.py +0 -54
  305. flwr/server/superlink/driver/driver_servicer.py +0 -129
  306. flwr/server/superlink/state/in_memory_state.py +0 -230
  307. flwr/server/superlink/state/sqlite_state.py +0 -630
  308. flwr/server/superlink/state/state.py +0 -154
  309. flwr_nightly-1.8.0.dev20240315.dist-info/RECORD +0 -211
  310. flwr_nightly-1.8.0.dev20240315.dist-info/entry_points.txt +0 -9
  311. {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/LICENSE +0 -0
  312. {flwr_nightly-1.8.0.dev20240315.dist-info → flwr_nightly-1.15.0.dev20250115.dist-info}/WHEEL +0 -0
flwr/common/context.py CHANGED
@@ -18,21 +18,51 @@
18
18
  from dataclasses import dataclass
19
19
 
20
20
  from .record import RecordSet
21
+ from .typing import UserConfig
21
22
 
22
23
 
23
24
  @dataclass
24
25
  class Context:
25
- """State of your run.
26
+ """Context of your run.
26
27
 
27
28
  Parameters
28
29
  ----------
30
+ run_id : int
31
+ The ID that identifies the run.
32
+ node_id : int
33
+ The ID that identifies the node.
34
+ node_config : UserConfig
35
+ A config (key/value mapping) unique to the node and independent of the
36
+ `run_config`. This config persists across all runs this node participates in.
29
37
  state : RecordSet
30
- Holds records added by the entity in a given run and that will stay local.
38
+ Holds records added by the entity in a given `run_id` and that will stay local.
31
39
  This means that the data it holds will never leave the system it's running from.
32
40
  This can be used as an intermediate storage or scratchpad when
33
41
  executing mods. It can also be used as a memory to access
34
42
  at different points during the lifecycle of this entity (e.g. across
35
43
  multiple rounds)
44
+ run_config : UserConfig
45
+ A config (key/value mapping) held by the entity in a given `run_id` and that
46
+ will stay local. It can be used at any point during the lifecycle of this entity
47
+ (e.g. across multiple rounds)
36
48
  """
37
49
 
50
+ run_id: int
51
+ node_id: int
52
+ node_config: UserConfig
38
53
  state: RecordSet
54
+ run_config: UserConfig
55
+
56
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
57
+ self,
58
+ run_id: int,
59
+ node_id: int,
60
+ node_config: UserConfig,
61
+ state: RecordSet,
62
+ run_config: UserConfig,
63
+ ) -> None:
64
+ self.run_id = run_id
65
+ self.node_id = node_id
66
+ self.node_config = node_config
67
+ self.state = state
68
+ self.run_config = run_config
flwr/common/date.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -15,9 +15,27 @@
15
15
  """Flower date utils."""
16
16
 
17
17
 
18
- from datetime import datetime, timezone
18
+ import datetime
19
19
 
20
20
 
21
- def now() -> datetime:
21
+ def now() -> datetime.datetime:
22
22
  """Construct a datetime from time.time() with time zone set to UTC."""
23
- return datetime.now(tz=timezone.utc)
23
+ return datetime.datetime.now(tz=datetime.timezone.utc)
24
+
25
+
26
+ def format_timedelta(td: datetime.timedelta) -> str:
27
+ """Format a timedelta as a string."""
28
+ days = td.days
29
+ hours, remainder = divmod(td.seconds, 3600)
30
+ minutes, seconds = divmod(remainder, 60)
31
+
32
+ if days > 0:
33
+ return f"{days}d {hours:02}:{minutes:02}:{seconds:02}"
34
+ return f"{hours:02}:{minutes:02}:{seconds:02}"
35
+
36
+
37
+ def isoformat8601_utc(dt: datetime.datetime) -> str:
38
+ """Return the datetime formatted as an ISO 8601 string with a trailing 'Z'."""
39
+ if dt.tzinfo != datetime.timezone.utc:
40
+ raise ValueError("Expected datetime with timezone set to UTC")
41
+ return dt.isoformat(timespec="seconds").replace("+00:00", "Z")
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from logging import WARNING
19
- from typing import Optional, Tuple
19
+ from typing import Optional
20
20
 
21
21
  import numpy as np
22
22
 
@@ -125,7 +125,7 @@ def compute_adaptive_noise_params(
125
125
  noise_multiplier: float,
126
126
  num_sampled_clients: float,
127
127
  clipped_count_stddev: Optional[float],
128
- ) -> Tuple[float, float]:
128
+ ) -> tuple[float, float]:
129
129
  """Compute noising parameters for the adaptive clipping.
130
130
 
131
131
  Paper: https://arxiv.org/abs/1905.03871
flwr/common/dp.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
15
15
  """Building block functions for DP algorithms."""
16
16
 
17
17
 
18
- from typing import Tuple
19
-
20
18
  import numpy as np
21
19
 
22
20
  from flwr.common.logger import warn_deprecated_feature
@@ -41,7 +39,7 @@ def add_gaussian_noise(update: NDArrays, std_dev: float) -> NDArrays:
41
39
  return update_noised
42
40
 
43
41
 
44
- def clip_by_l2(update: NDArrays, threshold: float) -> Tuple[NDArrays, bool]:
42
+ def clip_by_l2(update: NDArrays, threshold: float) -> tuple[NDArrays, bool]:
45
43
  """Scales the update so thats its L2 norm is upper-bound to threshold."""
46
44
  warn_deprecated_feature("`clip_by_l2` method")
47
45
  update_norm = _get_update_norm(update)
@@ -19,7 +19,7 @@ import sys
19
19
  from signal import SIGINT, SIGTERM, signal
20
20
  from threading import Thread
21
21
  from types import FrameType
22
- from typing import List, Optional
22
+ from typing import Optional
23
23
 
24
24
  from grpc import Server
25
25
 
@@ -28,8 +28,8 @@ from flwr.common.telemetry import EventType, event
28
28
 
29
29
  def register_exit_handlers(
30
30
  event_type: EventType,
31
- grpc_servers: Optional[List[Server]] = None,
32
- bckg_threads: Optional[List[Thread]] = None,
31
+ grpc_servers: Optional[list[Server]] = None,
32
+ bckg_threads: Optional[list[Thread]] = None,
33
33
  ) -> None:
34
34
  """Register exit handlers for `SIGINT` and `SIGTERM` signals.
35
35
 
flwr/common/grpc.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -15,21 +15,33 @@
15
15
  """Utility functions for gRPC."""
16
16
 
17
17
 
18
- from logging import DEBUG
19
- from typing import Optional
18
+ import concurrent.futures
19
+ import sys
20
+ from collections.abc import Sequence
21
+ from logging import DEBUG, ERROR
22
+ from typing import Any, Callable, Optional
20
23
 
21
24
  import grpc
22
25
 
23
- from flwr.common.logger import log
26
+ from .address import is_port_in_use
27
+ from .logger import log
24
28
 
25
29
  GRPC_MAX_MESSAGE_LENGTH: int = 536_870_912 # == 512 * 1024 * 1024
26
30
 
31
+ INVALID_CERTIFICATES_ERR_MSG = """
32
+ When setting any of root_certificate, certificate, or private_key,
33
+ all of them need to be set.
34
+ """
35
+
36
+ AddServicerToServerFn = Callable[..., Any]
37
+
27
38
 
28
39
  def create_channel(
29
40
  server_address: str,
30
41
  insecure: bool,
31
42
  root_certificates: Optional[bytes] = None,
32
43
  max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
44
+ interceptors: Optional[Sequence[grpc.UnaryUnaryClientInterceptor]] = None,
33
45
  ) -> grpc.Channel:
34
46
  """Create a gRPC channel, either secure or insecure."""
35
47
  # Check for conflicting parameters
@@ -51,10 +63,157 @@ def create_channel(
51
63
  channel = grpc.insecure_channel(server_address, options=channel_options)
52
64
  log(DEBUG, "Opened insecure gRPC connection (no certificates were passed)")
53
65
  else:
54
- ssl_channel_credentials = grpc.ssl_channel_credentials(root_certificates)
66
+ try:
67
+ ssl_channel_credentials = grpc.ssl_channel_credentials(root_certificates)
68
+ except Exception as e:
69
+ raise ValueError(f"Failed to create SSL channel credentials: {e}") from e
55
70
  channel = grpc.secure_channel(
56
71
  server_address, ssl_channel_credentials, options=channel_options
57
72
  )
58
73
  log(DEBUG, "Opened secure gRPC connection using certificates")
59
74
 
75
+ if interceptors is not None:
76
+ channel = grpc.intercept_channel(channel, interceptors)
77
+
60
78
  return channel
79
+
80
+
81
+ def valid_certificates(certificates: tuple[bytes, bytes, bytes]) -> bool:
82
+ """Validate certificates tuple."""
83
+ is_valid = (
84
+ all(isinstance(certificate, bytes) for certificate in certificates)
85
+ and len(certificates) == 3
86
+ )
87
+
88
+ if not is_valid:
89
+ log(ERROR, INVALID_CERTIFICATES_ERR_MSG)
90
+
91
+ return is_valid
92
+
93
+
94
+ def generic_create_grpc_server( # pylint: disable=too-many-arguments,R0917
95
+ servicer_and_add_fn: tuple[Any, AddServicerToServerFn],
96
+ server_address: str,
97
+ max_concurrent_workers: int = 1000,
98
+ max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
99
+ keepalive_time_ms: int = 210000,
100
+ certificates: Optional[tuple[bytes, bytes, bytes]] = None,
101
+ interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None,
102
+ ) -> grpc.Server:
103
+ """Create a gRPC server with a single servicer.
104
+
105
+ Parameters
106
+ ----------
107
+ servicer_and_add_fn : tuple
108
+ A tuple holding a servicer implementation and a matching
109
+ add_Servicer_to_server function.
110
+ server_address : str
111
+ Server address in the form of HOST:PORT e.g. "[::]:8080"
112
+ max_concurrent_workers : int
113
+ Maximum number of clients the server can process before returning
114
+ RESOURCE_EXHAUSTED status (default: 1000)
115
+ max_message_length : int
116
+ Maximum message length that the server can send or receive.
117
+ Int valued in bytes. -1 means unlimited. (default: GRPC_MAX_MESSAGE_LENGTH)
118
+ keepalive_time_ms : int
119
+ Flower uses a default gRPC keepalive time of 210000ms (3 minutes 30 seconds)
120
+ because some cloud providers (for example, Azure) agressively clean up idle
121
+ TCP connections by terminating them after some time (4 minutes in the case
122
+ of Azure). Flower does not use application-level keepalive signals and relies
123
+ on the assumption that the transport layer will fail in cases where the
124
+ connection is no longer active. `keepalive_time_ms` can be used to customize
125
+ the keepalive interval for specific environments. The default Flower gRPC
126
+ keepalive of 210000 ms (3 minutes 30 seconds) ensures that Flower can keep
127
+ the long running streaming connection alive in most environments. The actual
128
+ gRPC default of this setting is 7200000 (2 hours), which results in dropped
129
+ connections in some cloud environments.
130
+
131
+ These settings are related to the issue described here:
132
+ - https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md
133
+ - https://github.com/grpc/grpc/blob/master/doc/keepalive.md
134
+ - https://grpc.io/docs/guides/performance/
135
+
136
+ Mobile Flower clients may choose to increase this value if their server
137
+ environment allows long-running idle TCP connections.
138
+ (default: 210000)
139
+ certificates : Tuple[bytes, bytes, bytes] (default: None)
140
+ Tuple containing root certificate, server certificate, and private key to
141
+ start a secure SSL-enabled server. The tuple is expected to have three bytes
142
+ elements in the following order:
143
+
144
+ * CA certificate.
145
+ * server certificate.
146
+ * server private key.
147
+ interceptors : Optional[Sequence[grpc.ServerInterceptor]] (default: None)
148
+ A list of gRPC interceptors.
149
+
150
+ Returns
151
+ -------
152
+ server : grpc.Server
153
+ A non-running instance of a gRPC server.
154
+ """
155
+ # Check if port is in use
156
+ if is_port_in_use(server_address):
157
+ sys.exit(f"Port in server address {server_address} is already in use.")
158
+
159
+ # Deconstruct tuple into servicer and function
160
+ servicer, add_servicer_to_server_fn = servicer_and_add_fn
161
+
162
+ # Possible options:
163
+ # https://github.com/grpc/grpc/blob/v1.43.x/include/grpc/impl/codegen/grpc_types.h
164
+ options = [
165
+ # Maximum number of concurrent incoming streams to allow on a http2
166
+ # connection. Int valued.
167
+ ("grpc.max_concurrent_streams", max(100, max_concurrent_workers)),
168
+ # Maximum message length that the channel can send.
169
+ # Int valued, bytes. -1 means unlimited.
170
+ ("grpc.max_send_message_length", max_message_length),
171
+ # Maximum message length that the channel can receive.
172
+ # Int valued, bytes. -1 means unlimited.
173
+ ("grpc.max_receive_message_length", max_message_length),
174
+ # The gRPC default for this setting is 7200000 (2 hours). Flower uses a
175
+ # customized default of 210000 (3 minutes and 30 seconds) to improve
176
+ # compatibility with popular cloud providers. Mobile Flower clients may
177
+ # choose to increase this value if their server environment allows
178
+ # long-running idle TCP connections.
179
+ ("grpc.keepalive_time_ms", keepalive_time_ms),
180
+ # Setting this to zero will allow sending unlimited keepalive pings in between
181
+ # sending actual data frames.
182
+ ("grpc.http2.max_pings_without_data", 0),
183
+ # Is it permissible to send keepalive pings from the client without
184
+ # any outstanding streams. More explanation here:
185
+ # https://github.com/adap/flower/pull/2197
186
+ ("grpc.keepalive_permit_without_calls", 0),
187
+ ]
188
+
189
+ server = grpc.server(
190
+ concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent_workers),
191
+ # Set the maximum number of concurrent RPCs this server will service before
192
+ # returning RESOURCE_EXHAUSTED status, or None to indicate no limit.
193
+ maximum_concurrent_rpcs=max_concurrent_workers,
194
+ options=options,
195
+ interceptors=interceptors,
196
+ )
197
+ add_servicer_to_server_fn(servicer, server)
198
+
199
+ if certificates is not None:
200
+ if not valid_certificates(certificates):
201
+ sys.exit(1)
202
+
203
+ root_certificate_b, certificate_b, private_key_b = certificates
204
+
205
+ server_credentials = grpc.ssl_server_credentials(
206
+ ((private_key_b, certificate_b),),
207
+ root_certificates=root_certificate_b,
208
+ # A boolean indicating whether or not to require clients to be
209
+ # authenticated. May only be True if root_certificates is not None.
210
+ # We are explicitly setting the current gRPC default to document
211
+ # the option. For further reference see:
212
+ # https://grpc.github.io/grpc/python/grpc.html#create-server-credentials
213
+ require_client_auth=False,
214
+ )
215
+ server.add_secure_port(server_address, server_credentials)
216
+ else:
217
+ server.add_insecure_port(server_address)
218
+
219
+ return server
flwr/common/logger.py CHANGED
@@ -15,10 +15,28 @@
15
15
  """Flower Logger."""
16
16
 
17
17
 
18
+ import json as _json
18
19
  import logging
20
+ import re
21
+ import sys
22
+ import threading
23
+ import time
24
+ from io import StringIO
19
25
  from logging import WARN, LogRecord
20
26
  from logging.handlers import HTTPHandler
21
- from typing import TYPE_CHECKING, Any, Dict, Optional, TextIO, Tuple
27
+ from queue import Empty, Queue
28
+ from typing import TYPE_CHECKING, Any, Optional, TextIO, Union
29
+
30
+ import grpc
31
+ import typer
32
+ from rich.console import Console
33
+
34
+ from flwr.proto.log_pb2 import PushLogsRequest # pylint: disable=E0611
35
+ from flwr.proto.node_pb2 import Node # pylint: disable=E0611
36
+ from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub # pylint: disable=E0611
37
+ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
38
+
39
+ from .constant import LOG_UPLOAD_INTERVAL
22
40
 
23
41
  # Create logger
24
42
  LOGGER_NAME = "flwr"
@@ -82,13 +100,20 @@ class ConsoleHandler(StreamHandler):
82
100
  return formatter.format(record)
83
101
 
84
102
 
85
- def update_console_handler(level: int, timestamps: bool, colored: bool) -> None:
103
+ def update_console_handler(
104
+ level: Optional[int] = None,
105
+ timestamps: Optional[bool] = None,
106
+ colored: Optional[bool] = None,
107
+ ) -> None:
86
108
  """Update the logging handler."""
87
109
  for handler in logging.getLogger(LOGGER_NAME).handlers:
88
110
  if isinstance(handler, ConsoleHandler):
89
- handler.setLevel(level)
90
- handler.timestamps = timestamps
91
- handler.colored = colored
111
+ if level is not None:
112
+ handler.setLevel(level)
113
+ if timestamps is not None:
114
+ handler.timestamps = timestamps
115
+ if colored is not None:
116
+ handler.colored = colored
92
117
 
93
118
 
94
119
  # Configure console logger
@@ -104,7 +129,7 @@ FLOWER_LOGGER.addHandler(console_handler)
104
129
  class CustomHTTPHandler(HTTPHandler):
105
130
  """Custom HTTPHandler which overrides the mapLogRecords method."""
106
131
 
107
- # pylint: disable=too-many-arguments,bad-option-value,R1725
132
+ # pylint: disable=too-many-arguments,bad-option-value,R1725,R0917
108
133
  def __init__(
109
134
  self,
110
135
  identifier: str,
@@ -112,12 +137,12 @@ class CustomHTTPHandler(HTTPHandler):
112
137
  url: str,
113
138
  method: str = "GET",
114
139
  secure: bool = False,
115
- credentials: Optional[Tuple[str, str]] = None,
140
+ credentials: Optional[tuple[str, str]] = None,
116
141
  ) -> None:
117
142
  super().__init__(host, url, method, secure, credentials)
118
143
  self.identifier = identifier
119
144
 
120
- def mapLogRecord(self, record: LogRecord) -> Dict[str, Any]:
145
+ def mapLogRecord(self, record: LogRecord) -> dict[str, Any]:
121
146
  """Filter for the properties to be send to the logserver."""
122
147
  record_dict = record.__dict__
123
148
  return {
@@ -164,13 +189,13 @@ logger = logging.getLogger(LOGGER_NAME) # pylint: disable=invalid-name
164
189
  log = logger.log # pylint: disable=invalid-name
165
190
 
166
191
 
167
- def warn_experimental_feature(name: str) -> None:
168
- """Warn the user when they use an experimental feature."""
192
+ def warn_preview_feature(name: str) -> None:
193
+ """Warn the user when they use a preview feature."""
169
194
  log(
170
195
  WARN,
171
- """EXPERIMENTAL FEATURE: %s
196
+ """PREVIEW FEATURE: %s
172
197
 
173
- This is an experimental feature. It could change significantly or be removed
198
+ This is a preview feature. It could change significantly or be removed
174
199
  entirely in future versions of Flower.
175
200
  """,
176
201
  name,
@@ -188,3 +213,196 @@ def warn_deprecated_feature(name: str) -> None:
188
213
  """,
189
214
  name,
190
215
  )
216
+
217
+
218
+ def warn_deprecated_feature_with_example(
219
+ deprecation_message: str, example_message: str, code_example: str
220
+ ) -> None:
221
+ """Warn if a feature is deprecated and show code example."""
222
+ log(
223
+ WARN,
224
+ """DEPRECATED FEATURE: %s
225
+
226
+ Check the following `FEATURE UPDATE` warning message for the preferred
227
+ new mechanism to use this feature in Flower.
228
+ """,
229
+ deprecation_message,
230
+ )
231
+ log(
232
+ WARN,
233
+ """FEATURE UPDATE: %s
234
+ ------------------------------------------------------------
235
+ %s
236
+ ------------------------------------------------------------
237
+ """,
238
+ example_message,
239
+ code_example,
240
+ )
241
+
242
+
243
+ def warn_unsupported_feature(name: str) -> None:
244
+ """Warn the user when they use an unsupported feature."""
245
+ log(
246
+ WARN,
247
+ """UNSUPPORTED FEATURE: %s
248
+
249
+ This is an unsupported feature. It will be removed
250
+ entirely in future versions of Flower.
251
+ """,
252
+ name,
253
+ )
254
+
255
+
256
+ def set_logger_propagation(
257
+ child_logger: logging.Logger, value: bool = True
258
+ ) -> logging.Logger:
259
+ """Set the logger propagation attribute.
260
+
261
+ Parameters
262
+ ----------
263
+ child_logger : logging.Logger
264
+ Child logger object
265
+ value : bool
266
+ Boolean setting for propagation. If True, both parent and child logger
267
+ display messages. Otherwise, only the child logger displays a message.
268
+ This False setting prevents duplicate logs in Colab notebooks.
269
+ Reference: https://stackoverflow.com/a/19561320
270
+
271
+ Returns
272
+ -------
273
+ logging.Logger
274
+ Child logger object with updated propagation setting
275
+ """
276
+ child_logger.propagate = value
277
+ if not child_logger.propagate:
278
+ child_logger.log(logging.DEBUG, "Logger propagate set to False")
279
+ return child_logger
280
+
281
+
282
+ def mirror_output_to_queue(log_queue: Queue[Optional[str]]) -> None:
283
+ """Mirror stdout and stderr output to the provided queue."""
284
+
285
+ def get_write_fn(stream: TextIO) -> Any:
286
+ original_write = stream.write
287
+
288
+ def fn(s: str) -> int:
289
+ ret = original_write(s)
290
+ stream.flush()
291
+ log_queue.put(s)
292
+ return ret
293
+
294
+ return fn
295
+
296
+ sys.stdout.write = get_write_fn(sys.stdout) # type: ignore[method-assign]
297
+ sys.stderr.write = get_write_fn(sys.stderr) # type: ignore[method-assign]
298
+ console_handler.stream = sys.stdout
299
+
300
+
301
+ def restore_output() -> None:
302
+ """Restore stdout and stderr.
303
+
304
+ This will stop mirroring output to queues.
305
+ """
306
+ sys.stdout = sys.__stdout__
307
+ sys.stderr = sys.__stderr__
308
+ console_handler.stream = sys.stdout
309
+
310
+
311
+ def redirect_output(output_buffer: StringIO) -> None:
312
+ """Redirect stdout and stderr to text I/O buffer."""
313
+ sys.stdout = output_buffer
314
+ sys.stderr = output_buffer
315
+ console_handler.stream = sys.stdout
316
+
317
+
318
+ def _log_uploader(
319
+ log_queue: Queue[Optional[str]], node_id: int, run_id: int, stub: ServerAppIoStub
320
+ ) -> None:
321
+ """Upload logs to the SuperLink."""
322
+ exit_flag = False
323
+ node = Node(node_id=node_id)
324
+ msgs: list[str] = []
325
+ while True:
326
+ # Fetch all messages from the queue
327
+ try:
328
+ while True:
329
+ msg = log_queue.get_nowait()
330
+ # Quit the loops if the returned message is `None`
331
+ # This is a signal that the run has finished
332
+ if msg is None:
333
+ exit_flag = True
334
+ break
335
+ msgs.append(msg)
336
+ except Empty:
337
+ pass
338
+
339
+ # Upload if any logs
340
+ if msgs:
341
+ req = PushLogsRequest(
342
+ node=node,
343
+ run_id=run_id,
344
+ logs=msgs,
345
+ )
346
+ try:
347
+ stub.PushLogs(req)
348
+ msgs.clear()
349
+ except grpc.RpcError as e:
350
+ # Ignore minor network errors
351
+ # pylint: disable-next=no-member
352
+ if e.code() != grpc.StatusCode.UNAVAILABLE:
353
+ raise e
354
+
355
+ if exit_flag:
356
+ break
357
+
358
+ time.sleep(LOG_UPLOAD_INTERVAL)
359
+
360
+
361
+ def start_log_uploader(
362
+ log_queue: Queue[Optional[str]],
363
+ node_id: int,
364
+ run_id: int,
365
+ stub: Union[ServerAppIoStub, SimulationIoStub],
366
+ ) -> threading.Thread:
367
+ """Start the log uploader thread and return it."""
368
+ thread = threading.Thread(
369
+ target=_log_uploader, args=(log_queue, node_id, run_id, stub)
370
+ )
371
+ thread.start()
372
+ return thread
373
+
374
+
375
+ def stop_log_uploader(
376
+ log_queue: Queue[Optional[str]], log_uploader: threading.Thread
377
+ ) -> None:
378
+ """Stop the log uploader thread."""
379
+ log_queue.put(None)
380
+ log_uploader.join()
381
+
382
+
383
+ def _remove_emojis(text: str) -> str:
384
+ """Remove emojis from the provided text."""
385
+ emoji_pattern = re.compile(
386
+ "["
387
+ "\U0001F600-\U0001F64F" # Emoticons
388
+ "\U0001F300-\U0001F5FF" # Symbols & Pictographs
389
+ "\U0001F680-\U0001F6FF" # Transport & Map Symbols
390
+ "\U0001F1E0-\U0001F1FF" # Flags
391
+ "\U00002702-\U000027B0" # Dingbats
392
+ "\U000024C2-\U0001F251"
393
+ "]+",
394
+ flags=re.UNICODE,
395
+ )
396
+ return emoji_pattern.sub(r"", text)
397
+
398
+
399
+ def print_json_error(msg: str, e: Union[typer.Exit, Exception]) -> None:
400
+ """Print error message as JSON."""
401
+ Console().print_json(
402
+ _json.dumps(
403
+ {
404
+ "success": False,
405
+ "error-message": _remove_emojis(str(msg) + "\n" + str(e)),
406
+ }
407
+ )
408
+ )