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/object_ref.py CHANGED
@@ -17,8 +17,14 @@
17
17
 
18
18
  import ast
19
19
  import importlib
20
+ import sys
20
21
  from importlib.util import find_spec
21
- from typing import Any, Optional, Tuple, Type
22
+ from logging import WARN
23
+ from pathlib import Path
24
+ from threading import Lock
25
+ from typing import Any, Optional, Union
26
+
27
+ from .logger import log
22
28
 
23
29
  OBJECT_REF_HELP_STR = """
24
30
  \n\nThe object reference string should have the form <module>:<attribute>. Valid
@@ -28,21 +34,42 @@ attribute.
28
34
  """
29
35
 
30
36
 
37
+ _current_sys_path: Optional[str] = None
38
+ _import_lock = Lock()
39
+
40
+
31
41
  def validate(
32
42
  module_attribute_str: str,
33
- ) -> Tuple[bool, Optional[str]]:
43
+ check_module: bool = True,
44
+ project_dir: Optional[Union[str, Path]] = None,
45
+ ) -> tuple[bool, Optional[str]]:
34
46
  """Validate object reference.
35
47
 
36
- The object reference string should have the form <module>:<attribute>. Valid
37
- examples include `client:app` and `project.package.module:wrapper.app`. It must
38
- refer to a module on the PYTHONPATH and the module needs to have the specified
39
- attribute.
48
+ Parameters
49
+ ----------
50
+ module_attribute_str : str
51
+ The reference to the object. It should have the form `<module>:<attribute>`.
52
+ Valid examples include `client:app` and `project.package.module:wrapper.app`.
53
+ It must refer to a module on the PYTHONPATH or in the provided `project_dir`
54
+ and the module needs to have the specified attribute.
55
+ check_module : bool (default: True)
56
+ Flag indicating whether to verify the existence of the module and the
57
+ specified attribute within it.
58
+ project_dir : Optional[Union[str, Path]] (default: None)
59
+ The directory containing the module. If None, the current working directory
60
+ is used. If `check_module` is True, the `project_dir` will be temporarily
61
+ inserted into the system path and then removed after the validation is complete.
40
62
 
41
63
  Returns
42
64
  -------
43
65
  Tuple[bool, Optional[str]]
44
66
  A boolean indicating whether an object reference is valid and
45
67
  the reason why it might not be.
68
+
69
+ Note
70
+ ----
71
+ This function will temporarily modify `sys.path` by inserting the provided
72
+ `project_dir`, which will be removed after the validation is complete.
46
73
  """
47
74
  module_str, _, attributes_str = module_attribute_str.partition(":")
48
75
  if not module_str:
@@ -56,15 +83,29 @@ def validate(
56
83
  f"Missing attribute in {module_attribute_str}{OBJECT_REF_HELP_STR}",
57
84
  )
58
85
 
59
- # Load module
60
- module = find_spec(module_str)
61
- if module and module.origin:
62
- if not _find_attribute_in_module(module.origin, attributes_str):
63
- return (
64
- False,
65
- f"Unable to find attribute {attributes_str} in module {module_str}"
66
- f"{OBJECT_REF_HELP_STR}",
67
- )
86
+ if check_module:
87
+ if project_dir is None:
88
+ project_dir = Path.cwd()
89
+ project_dir = Path(project_dir).absolute()
90
+ # Set the system path
91
+ sys.path.insert(0, str(project_dir))
92
+
93
+ # Load module
94
+ module = find_spec(module_str)
95
+
96
+ # Unset the system path
97
+ sys.path.remove(str(project_dir))
98
+
99
+ # Check if the module and the attribute exist
100
+ if module and module.origin:
101
+ if not _find_attribute_in_module(module.origin, attributes_str):
102
+ return (
103
+ False,
104
+ f"Unable to find attribute {attributes_str} in module {module_str}"
105
+ f"{OBJECT_REF_HELP_STR}",
106
+ )
107
+ return (True, None)
108
+ else:
68
109
  return (True, None)
69
110
 
70
111
  return (
@@ -73,42 +114,136 @@ def validate(
73
114
  )
74
115
 
75
116
 
76
- def load_app(
117
+ def load_app( # pylint: disable= too-many-branches
77
118
  module_attribute_str: str,
78
- error_type: Type[Exception],
119
+ error_type: type[Exception],
120
+ project_dir: Optional[Union[str, Path]] = None,
79
121
  ) -> Any:
80
122
  """Return the object specified in a module attribute string.
81
123
 
82
- The module/attribute string should have the form <module>:<attribute>. Valid
83
- examples include `client:app` and `project.package.module:wrapper.app`. It must
84
- refer to a module on the PYTHONPATH, the module needs to have the specified
85
- attribute.
124
+ Parameters
125
+ ----------
126
+ module_attribute_str : str
127
+ The reference to the object. It should have the form `<module>:<attribute>`.
128
+ Valid examples include `client:app` and `project.package.module:wrapper.app`.
129
+ It must refer to a module on the PYTHONPATH or in the provided `project_dir`
130
+ and the module needs to have the specified attribute.
131
+ error_type : Type[Exception]
132
+ The type of exception to be raised if the provided `module_attribute_str` is
133
+ in an invalid format.
134
+ project_dir : Optional[Union[str, Path]], optional (default=None)
135
+ The directory containing the module. If None, the current working directory
136
+ is used. The `project_dir` will be inserted into the system path, and the
137
+ previously inserted `project_dir` will be removed.
138
+
139
+ Returns
140
+ -------
141
+ Any
142
+ The object specified by the module attribute string.
143
+
144
+ Note
145
+ ----
146
+ - This function will unload all modules in the previously provided `project_dir`,
147
+ if it is invoked again.
148
+ - This function will modify `sys.path` by inserting the provided `project_dir`
149
+ and removing the previously inserted `project_dir`.
86
150
  """
87
- valid, error_msg = validate(module_attribute_str)
88
- if not valid and error_msg:
89
- raise error_type(error_msg) from None
151
+ with _import_lock:
152
+ valid, error_msg = validate(module_attribute_str, check_module=False)
153
+ if not valid and error_msg:
154
+ raise error_type(error_msg) from None
90
155
 
91
- module_str, _, attributes_str = module_attribute_str.partition(":")
156
+ module_str, _, attributes_str = module_attribute_str.partition(":")
157
+
158
+ try:
159
+ # Initialize project path
160
+ if project_dir is None:
161
+ project_dir = Path.cwd()
162
+ project_dir = Path(project_dir).absolute()
163
+
164
+ # Unload modules if the project directory has changed
165
+ if _current_sys_path and _current_sys_path != str(project_dir):
166
+ _unload_modules(Path(_current_sys_path))
167
+
168
+ # Set the system path
169
+ _set_sys_path(project_dir)
170
+
171
+ # Import the module
172
+ if module_str not in sys.modules:
173
+ module = importlib.import_module(module_str)
174
+ # Hack: `tabnet` does not work with `importlib.reload`
175
+ elif "tabnet" in sys.modules:
176
+ log(
177
+ WARN,
178
+ "Cannot reload module `%s` from disk due to compatibility issues "
179
+ "with the `tabnet` library. The module will be loaded from the "
180
+ "cache instead. If you experience issues, consider restarting "
181
+ "the application.",
182
+ module_str,
183
+ )
184
+ module = sys.modules[module_str]
185
+ else:
186
+ module = sys.modules[module_str]
187
+ _reload_modules(project_dir)
188
+
189
+ except ModuleNotFoundError as err:
190
+ raise error_type(
191
+ f"Unable to load module {module_str}{OBJECT_REF_HELP_STR}",
192
+ ) from err
193
+
194
+ # Recursively load attribute
195
+ attribute = module
196
+ try:
197
+ for attribute_str in attributes_str.split("."):
198
+ attribute = getattr(attribute, attribute_str)
199
+ except AttributeError as err:
200
+ raise error_type(
201
+ f"Unable to load attribute {attributes_str} from module {module_str}"
202
+ f"{OBJECT_REF_HELP_STR}",
203
+ ) from err
204
+
205
+ return attribute
206
+
207
+
208
+ def _unload_modules(project_dir: Path) -> None:
209
+ """Unload modules from the project directory."""
210
+ dir_str = str(project_dir.absolute())
211
+ for name, m in list(sys.modules.items()):
212
+ path: Optional[str] = getattr(m, "__file__", None)
213
+ if path is not None and path.startswith(dir_str):
214
+ del sys.modules[name]
215
+
216
+
217
+ def _reload_modules(project_dir: Path) -> None:
218
+ """Reload modules from the project directory."""
219
+ dir_str = str(project_dir.absolute())
220
+ for m in list(sys.modules.values()):
221
+ path: Optional[str] = getattr(m, "__file__", None)
222
+ if path is not None and path.startswith(dir_str):
223
+ importlib.reload(m)
224
+
225
+
226
+ def _set_sys_path(directory: Optional[Union[str, Path]]) -> None:
227
+ """Set the system path."""
228
+ if directory is None:
229
+ directory = Path.cwd()
230
+ else:
231
+ directory = Path(directory).absolute()
232
+
233
+ # If the directory has already been added to `sys.path`, return
234
+ if str(directory) in sys.path:
235
+ return
236
+
237
+ # Remove the old path if it exists and is not `""`.
238
+ global _current_sys_path # pylint: disable=global-statement
239
+ if _current_sys_path is not None:
240
+ sys.path.remove(_current_sys_path)
241
+
242
+ # Add the new path to sys.path
243
+ sys.path.insert(0, str(directory))
92
244
 
93
- try:
94
- module = importlib.import_module(module_str)
95
- except ModuleNotFoundError:
96
- raise error_type(
97
- f"Unable to load module {module_str}{OBJECT_REF_HELP_STR}",
98
- ) from None
99
-
100
- # Recursively load attribute
101
- attribute = module
102
- try:
103
- for attribute_str in attributes_str.split("."):
104
- attribute = getattr(attribute, attribute_str)
105
- except AttributeError:
106
- raise error_type(
107
- f"Unable to load attribute {attributes_str} from module {module_str}"
108
- f"{OBJECT_REF_HELP_STR}",
109
- ) from None
110
-
111
- return attribute
245
+ # Update the current_sys_path
246
+ _current_sys_path = str(directory)
112
247
 
113
248
 
114
249
  def _find_attribute_in_module(file_path: str, attribute_name: str) -> bool:
flwr/common/pyproject.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Validates the project's name property."""
16
16
 
17
+
17
18
  import re
18
19
 
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Record APIs."""
16
16
 
17
+
17
18
  from .configsrecord import ConfigsRecord
18
19
  from .conversion_utils import array_from_numpy
19
20
  from .metricsrecord import MetricsRecord
@@ -22,9 +23,9 @@ from .recordset import RecordSet
22
23
 
23
24
  __all__ = [
24
25
  "Array",
25
- "array_from_numpy",
26
26
  "ConfigsRecord",
27
27
  "MetricsRecord",
28
28
  "ParametersRecord",
29
29
  "RecordSet",
30
+ "array_from_numpy",
30
31
  ]
@@ -15,7 +15,7 @@
15
15
  """ConfigsRecord."""
16
16
 
17
17
 
18
- from typing import Dict, List, Optional, get_args
18
+ from typing import Optional, get_args
19
19
 
20
20
  from flwr.common.typing import ConfigsRecordValues, ConfigsScalar
21
21
 
@@ -58,27 +58,61 @@ def _check_value(value: ConfigsRecordValues) -> None:
58
58
 
59
59
 
60
60
  class ConfigsRecord(TypedDict[str, ConfigsRecordValues]):
61
- """Configs record."""
61
+ """Configs record.
62
+
63
+ A :code:`ConfigsRecord` is a Python dictionary designed to ensure that
64
+ each key-value pair adheres to specified data types. A :code:`ConfigsRecord`
65
+ is one of the types of records that a
66
+ `flwr.common.RecordSet <flwr.common.RecordSet.html#recordset>`_ supports and
67
+ can therefore be used to construct :code:`common.Message` objects.
68
+
69
+ Parameters
70
+ ----------
71
+ configs_dict : Optional[Dict[str, ConfigsRecordValues]]
72
+ A dictionary that stores basic types (i.e. `str`, `int`, `float`, `bytes` as
73
+ defined in `ConfigsScalar`) and lists of such types (see
74
+ `ConfigsScalarList`).
75
+ keep_input : bool (default: True)
76
+ A boolean indicating whether config passed should be deleted from the input
77
+ dictionary immediately after adding them to the record. When set
78
+ to True, the data is duplicated in memory. If memory is a concern, set
79
+ it to False.
80
+
81
+ Examples
82
+ --------
83
+ The usage of a :code:`ConfigsRecord` is envisioned for sending configuration values
84
+ telling the target node how to perform a certain action (e.g. train/evaluate a model
85
+ ). You can use standard Python built-in types such as :code:`float`, :code:`str`
86
+ , :code:`bytes`. All types allowed are defined in
87
+ :code:`flwr.common.ConfigsRecordValues`. While lists are supported, we
88
+ encourage you to use a :code:`ParametersRecord` instead if these are of high
89
+ dimensionality.
90
+
91
+ Let's see some examples of how to construct a :code:`ConfigsRecord` from scratch:
92
+
93
+ >>> from flwr.common import ConfigsRecord
94
+ >>>
95
+ >>> # A `ConfigsRecord` is a specialized Python dictionary
96
+ >>> record = ConfigsRecord({"lr": 0.1, "batch-size": 128})
97
+ >>> # You can add more content to an existing record
98
+ >>> record["compute-average"] = True
99
+ >>> # It also supports lists
100
+ >>> record["loss-fn-coefficients"] = [0.4, 0.25, 0.35]
101
+ >>> # And string values (among other types)
102
+ >>> record["path-to-S3"] = "s3://bucket_name/folder1/fileA.json"
103
+
104
+ Just like the other types of records in a :code:`flwr.common.RecordSet`, types are
105
+ enforced. If you need to add a custom data structure or object, we recommend to
106
+ serialise it into bytes and save it as such (bytes are allowed in a
107
+ :code:`ConfigsRecord`)
108
+ """
62
109
 
63
110
  def __init__(
64
111
  self,
65
- configs_dict: Optional[Dict[str, ConfigsRecordValues]] = None,
112
+ configs_dict: Optional[dict[str, ConfigsRecordValues]] = None,
66
113
  keep_input: bool = True,
67
114
  ) -> None:
68
- """Construct a ConfigsRecord object.
69
-
70
- Parameters
71
- ----------
72
- configs_dict : Optional[Dict[str, ConfigsRecordValues]]
73
- A dictionary that stores basic types (i.e. `str`, `int`, `float`, `bytes` as
74
- defined in `ConfigsScalar`) and lists of such types (see
75
- `ConfigsScalarList`).
76
- keep_input : bool (default: True)
77
- A boolean indicating whether config passed should be deleted from the input
78
- dictionary immediately after adding them to the record. When set
79
- to True, the data is duplicated in memory. If memory is a concern, set
80
- it to False.
81
- """
115
+
82
116
  super().__init__(_check_key, _check_value)
83
117
  if configs_dict:
84
118
  for k in list(configs_dict.keys()):
@@ -94,6 +128,7 @@ class ConfigsRecord(TypedDict[str, ConfigsRecordValues]):
94
128
 
95
129
  def get_var_bytes(value: ConfigsScalar) -> int:
96
130
  """Return Bytes of value passed."""
131
+ var_bytes = 0
97
132
  if isinstance(value, bool):
98
133
  var_bytes = 1
99
134
  elif isinstance(value, (int, float)):
@@ -102,12 +137,17 @@ class ConfigsRecord(TypedDict[str, ConfigsRecordValues]):
102
137
  )
103
138
  if isinstance(value, (str, bytes)):
104
139
  var_bytes = len(value)
140
+ if var_bytes == 0:
141
+ raise ValueError(
142
+ "Config values must be either `bool`, `int`, `float`, "
143
+ "`str`, or `bytes`"
144
+ )
105
145
  return var_bytes
106
146
 
107
147
  num_bytes = 0
108
148
 
109
149
  for k, v in self.items():
110
- if isinstance(v, List):
150
+ if isinstance(v, list):
111
151
  if isinstance(v[0], (bytes, str)):
112
152
  # not all str are of equal length necessarily
113
153
  # for both the footprint of each element is 1 Byte
@@ -15,7 +15,7 @@
15
15
  """MetricsRecord."""
16
16
 
17
17
 
18
- from typing import Dict, List, Optional, get_args
18
+ from typing import Optional, get_args
19
19
 
20
20
  from flwr.common.typing import MetricsRecordValues, MetricsScalar
21
21
 
@@ -58,26 +58,66 @@ def _check_value(value: MetricsRecordValues) -> None:
58
58
 
59
59
 
60
60
  class MetricsRecord(TypedDict[str, MetricsRecordValues]):
61
- """Metrics record."""
61
+ """Metrics recod.
62
+
63
+ A :code:`MetricsRecord` is a Python dictionary designed to ensure that
64
+ each key-value pair adheres to specified data types. A :code:`MetricsRecord`
65
+ is one of the types of records that a
66
+ `flwr.common.RecordSet <flwr.common.RecordSet.html#recordset>`_ supports and
67
+ can therefore be used to construct :code:`common.Message` objects.
68
+
69
+ Parameters
70
+ ----------
71
+ metrics_dict : Optional[Dict[str, MetricsRecordValues]]
72
+ A dictionary that stores basic types (i.e. `int`, `float` as defined
73
+ in `MetricsScalar`) and list of such types (see `MetricsScalarList`).
74
+ keep_input : bool (default: True)
75
+ A boolean indicating whether metrics should be deleted from the input
76
+ dictionary immediately after adding them to the record. When set
77
+ to True, the data is duplicated in memory. If memory is a concern, set
78
+ it to False.
79
+
80
+ Examples
81
+ --------
82
+ The usage of a :code:`MetricsRecord` is envisioned for communicating results
83
+ obtained when a node performs an action. A few typical examples include:
84
+ communicating the training accuracy after a model is trained locally by a
85
+ :code:`ClientApp`, reporting the validation loss obtained at a :code:`ClientApp`,
86
+ or, more generally, the output of executing a query by the :code:`ClientApp`.
87
+ Common to these examples is that the output can be typically represented by
88
+ a single scalar (:code:`int`, :code:`float`) or list of scalars.
89
+
90
+ Let's see some examples of how to construct a :code:`MetricsRecord` from scratch:
91
+
92
+ >>> from flwr.common import MetricsRecord
93
+ >>>
94
+ >>> # A `MetricsRecord` is a specialized Python dictionary
95
+ >>> record = MetricsRecord({"accuracy": 0.94})
96
+ >>> # You can add more content to an existing record
97
+ >>> record["loss"] = 0.01
98
+ >>> # It also supports lists
99
+ >>> record["loss-historic"] = [0.9, 0.5, 0.01]
100
+
101
+ Since types are enforced, the types of the objects inserted are checked. For a
102
+ :code:`MetricsRecord`, value types allowed are those in defined in
103
+ :code:`flwr.common.MetricsRecordValues`. Similarly, only :code:`str` keys are
104
+ allowed.
105
+
106
+ >>> from flwr.common import MetricsRecord
107
+ >>>
108
+ >>> record = MetricsRecord() # an empty record
109
+ >>> # Add unsupported value
110
+ >>> record["something-unsupported"] = {'a': 123} # Will throw a `TypeError`
111
+
112
+ If you need a more versatily type of record try :code:`ConfigsRecord` or
113
+ :code:`ParametersRecord`.
114
+ """
62
115
 
63
116
  def __init__(
64
117
  self,
65
- metrics_dict: Optional[Dict[str, MetricsRecordValues]] = None,
118
+ metrics_dict: Optional[dict[str, MetricsRecordValues]] = None,
66
119
  keep_input: bool = True,
67
120
  ):
68
- """Construct a MetricsRecord object.
69
-
70
- Parameters
71
- ----------
72
- metrics_dict : Optional[Dict[str, MetricsRecordValues]]
73
- A dictionary that stores basic types (i.e. `int`, `float` as defined
74
- in `MetricsScalar`) and list of such types (see `MetricsScalarList`).
75
- keep_input : bool (default: True)
76
- A boolean indicating whether metrics should be deleted from the input
77
- dictionary immediately after adding them to the record. When set
78
- to True, the data is duplicated in memory. If memory is a concern, set
79
- it to False.
80
- """
81
121
  super().__init__(_check_key, _check_value)
82
122
  if metrics_dict:
83
123
  for k in list(metrics_dict.keys()):
@@ -90,7 +130,7 @@ class MetricsRecord(TypedDict[str, MetricsRecordValues]):
90
130
  num_bytes = 0
91
131
 
92
132
  for k, v in self.items():
93
- if isinstance(v, List):
133
+ if isinstance(v, list):
94
134
  # both int and float normally take 4 bytes
95
135
  # But MetricRecords are mapped to 64bit int/float
96
136
  # during protobuffing
@@ -14,9 +14,11 @@
14
14
  # ==============================================================================
15
15
  """ParametersRecord and Array."""
16
16
 
17
+
18
+ from collections import OrderedDict
17
19
  from dataclasses import dataclass
18
20
  from io import BytesIO
19
- from typing import List, Optional, OrderedDict, cast
21
+ from typing import Optional, cast
20
22
 
21
23
  import numpy as np
22
24
 
@@ -51,7 +53,7 @@ class Array:
51
53
  """
52
54
 
53
55
  dtype: str
54
- shape: List[int]
56
+ shape: list[int]
55
57
  stype: str
56
58
  data: bytes
57
59
 
@@ -82,13 +84,94 @@ def _check_value(value: Array) -> None:
82
84
  )
83
85
 
84
86
 
85
- @dataclass
86
87
  class ParametersRecord(TypedDict[str, Array]):
87
- """Parameters record.
88
+ r"""Parameters record.
88
89
 
89
90
  A dataclass storing named Arrays in order. This means that it holds entries as an
90
91
  OrderedDict[str, Array]. ParametersRecord objects can be viewed as an equivalent to
91
- PyTorch's state_dict, but holding serialised tensors instead.
92
+ PyTorch's state_dict, but holding serialised tensors instead. A
93
+ :code:`ParametersRecord` is one of the types of records that a
94
+ `flwr.common.RecordSet <flwr.common.RecordSet.html#recordset>`_ supports and
95
+ can therefore be used to construct :code:`common.Message` objects.
96
+
97
+ Parameters
98
+ ----------
99
+ array_dict : Optional[OrderedDict[str, Array]]
100
+ A dictionary that stores serialized array-like or tensor-like objects.
101
+ keep_input : bool (default: False)
102
+ A boolean indicating whether parameters should be deleted from the input
103
+ dictionary immediately after adding them to the record. If False, the
104
+ dictionary passed to `set_parameters()` will be empty once exiting from that
105
+ function. This is the desired behaviour when working with very large
106
+ models/tensors/arrays. However, if you plan to continue working with your
107
+ parameters after adding it to the record, set this flag to True. When set
108
+ to True, the data is duplicated in memory.
109
+
110
+ Examples
111
+ --------
112
+ The usage of :code:`ParametersRecord` is envisioned for storing data arrays (e.g.
113
+ parameters of a machine learning model). These first need to be serialized into
114
+ a :code:`flwr.common.Array` data structure.
115
+
116
+ Let's see some examples:
117
+
118
+ >>> import numpy as np
119
+ >>> from flwr.common import ParametersRecord
120
+ >>> from flwr.common import array_from_numpy
121
+ >>>
122
+ >>> # Let's create a simple NumPy array
123
+ >>> arr_np = np.random.randn(3, 3)
124
+ >>>
125
+ >>> # If we print it
126
+ >>> array([[-1.84242409, -1.01539537, -0.46528405],
127
+ >>> [ 0.32991896, 0.55540414, 0.44085534],
128
+ >>> [-0.10758364, 1.97619858, -0.37120501]])
129
+ >>>
130
+ >>> # Let's create an Array out of it
131
+ >>> arr = array_from_numpy(arr_np)
132
+ >>>
133
+ >>> # If we print it you'll see (note the binary data)
134
+ >>> Array(dtype='float64', shape=[3,3], stype='numpy.ndarray', data=b'@\x99\x18...')
135
+ >>>
136
+ >>> # Adding it to a ParametersRecord:
137
+ >>> p_record = ParametersRecord({"my_array": arr})
138
+
139
+ Now that the NumPy array is embedded into a :code:`ParametersRecord` it could be
140
+ sent if added as part of a :code:`common.Message` or it could be saved as a
141
+ persistent state of a :code:`ClientApp` via its context. Regardless of the usecase,
142
+ we will sooner or later want to recover the array in its original NumPy
143
+ representation. For the example above, where the array was serialized using the
144
+ built-in utility function, deserialization can be done as follows:
145
+
146
+ >>> # Use the Array's built-in method
147
+ >>> arr_np_d = arr.numpy()
148
+ >>>
149
+ >>> # If printed, it will show the exact same data as above:
150
+ >>> array([[-1.84242409, -1.01539537, -0.46528405],
151
+ >>> [ 0.32991896, 0.55540414, 0.44085534],
152
+ >>> [-0.10758364, 1.97619858, -0.37120501]])
153
+
154
+ If you need finer control on how your arrays are serialized and deserialized, you
155
+ can construct :code:`Array` objects directly like this:
156
+
157
+ >>> from flwr.common import Array
158
+ >>> # Serialize your array and construct Array object
159
+ >>> arr = Array(
160
+ >>> data=ndarray.tobytes(),
161
+ >>> dtype=str(ndarray.dtype),
162
+ >>> stype="", # Could be used in a deserialization function
163
+ >>> shape=list(ndarray.shape),
164
+ >>> )
165
+ >>>
166
+ >>> # Then you can deserialize it like this
167
+ >>> arr_np_d = np.frombuffer(
168
+ >>> buffer=array.data,
169
+ >>> dtype=array.dtype,
170
+ >>> ).reshape(array.shape)
171
+
172
+ Note that different arrays (e.g. from PyTorch, Tensorflow) might require different
173
+ serialization mechanism. Howerver, they often support a conversion to NumPy,
174
+ therefore allowing to use the same or similar steps as in the example above.
92
175
  """
93
176
 
94
177
  def __init__(
@@ -96,21 +179,6 @@ class ParametersRecord(TypedDict[str, Array]):
96
179
  array_dict: Optional[OrderedDict[str, Array]] = None,
97
180
  keep_input: bool = False,
98
181
  ) -> None:
99
- """Construct a ParametersRecord object.
100
-
101
- Parameters
102
- ----------
103
- array_dict : Optional[OrderedDict[str, Array]]
104
- A dictionary that stores serialized array-like or tensor-like objects.
105
- keep_input : bool (default: False)
106
- A boolean indicating whether parameters should be deleted from the input
107
- dictionary immediately after adding them to the record. If False, the
108
- dictionary passed to `set_parameters()` will be empty once exiting from that
109
- function. This is the desired behaviour when working with very large
110
- models/tensors/arrays. However, if you plan to continue working with your
111
- parameters after adding it to the record, set this flag to True. When set
112
- to True, the data is duplicated in memory.
113
- """
114
182
  super().__init__(_check_key, _check_value)
115
183
  if array_dict:
116
184
  for k in list(array_dict.keys()):