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
flwr/cli/utils.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -28,7 +28,12 @@ import typer
28
28
 
29
29
  from flwr.cli.cli_user_auth_interceptor import CliUserAuthInterceptor
30
30
  from flwr.common.auth_plugin import CliAuthPlugin
31
- from flwr.common.constant import AUTH_TYPE_JSON_KEY, CREDENTIALS_DIR, FLWR_DIR
31
+ from flwr.common.constant import (
32
+ AUTH_TYPE_JSON_KEY,
33
+ CREDENTIALS_DIR,
34
+ FLWR_DIR,
35
+ RUN_ID_NOT_FOUND_MESSAGE,
36
+ )
32
37
  from flwr.common.grpc import (
33
38
  GRPC_MAX_MESSAGE_LENGTH,
34
39
  create_channel,
@@ -220,17 +225,6 @@ def try_obtain_cli_auth_plugin(
220
225
  if not federation_config.get("enable-user-auth", False):
221
226
  return None
222
227
 
223
- # Check if TLS is enabled. If not, raise an error
224
- if federation_config.get("root-certificates") is None:
225
- typer.secho(
226
- "❌ User authentication requires TLS to be enabled. "
227
- "Please provide 'root-certificates' in the federation"
228
- " configuration.",
229
- fg=typer.colors.RED,
230
- bold=True,
231
- )
232
- raise typer.Exit(code=1)
233
-
234
228
  config_path = get_user_auth_config_path(root_dir, federation)
235
229
 
236
230
  # Get the auth type from the config if not provided
@@ -273,6 +267,16 @@ def init_channel(
273
267
  # Initialize the CLI-side user auth interceptor
274
268
  interceptors: list[grpc.UnaryUnaryClientInterceptor] = []
275
269
  if auth_plugin is not None:
270
+ # Check if TLS is enabled. If not, raise an error
271
+ if insecure:
272
+ typer.secho(
273
+ "❌ User authentication requires TLS to be enabled. "
274
+ "Remove `insecure = true` from the federation configuration.",
275
+ fg=typer.colors.RED,
276
+ bold=True,
277
+ )
278
+ raise typer.Exit(code=1)
279
+
276
280
  auth_plugin.load_tokens()
277
281
  interceptors.append(CliUserAuthInterceptor(auth_plugin))
278
282
 
@@ -289,11 +293,12 @@ def init_channel(
289
293
 
290
294
 
291
295
  @contextmanager
292
- def unauthenticated_exc_handler() -> Iterator[None]:
293
- """Context manager to handle gRPC UNAUTHENTICATED errors.
296
+ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
297
+ """Context manager to handle specific gRPC errors.
294
298
 
295
- It catches grpc.RpcError exceptions with UNAUTHENTICATED status, informs the user,
296
- and exits the application. All other exceptions will be allowed to escape.
299
+ It catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED, and
300
+ PERMISSION_DENIED statuses, informs the user, and exits the application. All other
301
+ exceptions will be allowed to escape.
297
302
  """
298
303
  try:
299
304
  yield
@@ -313,4 +318,24 @@ def unauthenticated_exc_handler() -> Iterator[None]:
313
318
  bold=True,
314
319
  )
315
320
  raise typer.Exit(code=1) from None
321
+ if e.code() == grpc.StatusCode.PERMISSION_DENIED:
322
+ typer.secho(
323
+ "❌ Authorization failed. Please contact your administrator"
324
+ " to check your permissions.",
325
+ fg=typer.colors.RED,
326
+ bold=True,
327
+ )
328
+ # pylint: disable=E1101
329
+ typer.secho(e.details(), fg=typer.colors.RED, bold=True)
330
+ raise typer.Exit(code=1) from None
331
+ if (
332
+ e.code() == grpc.StatusCode.NOT_FOUND
333
+ and e.details() == RUN_ID_NOT_FOUND_MESSAGE
334
+ ):
335
+ typer.secho(
336
+ "❌ Run ID not found.",
337
+ fg=typer.colors.RED,
338
+ bold=True,
339
+ )
340
+ raise typer.Exit(code=1) from None
316
341
  raise
flwr/client/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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,8 @@
15
15
  """Flower client."""
16
16
 
17
17
 
18
- from .app import start_client as start_client
19
- from .app import start_numpy_client as start_numpy_client
18
+ from ..compat.client.app import start_client as start_client # Deprecated
19
+ from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
20
20
  from .client import Client as Client
21
21
  from .client_app import ClientApp as ClientApp
22
22
  from .numpy_client import NumPyClient as NumPyClient
flwr/client/client.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
flwr/client/client_app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -20,6 +20,7 @@ from collections.abc import Iterator
20
20
  from contextlib import contextmanager
21
21
  from typing import Callable, Optional
22
22
 
23
+ from flwr.app.metadata import validate_message_type
23
24
  from flwr.client.client import Client
24
25
  from flwr.client.message_handler.message_handler import (
25
26
  handle_legacy_message_from_msgtype,
@@ -28,7 +29,6 @@ from flwr.client.mod.utils import make_ffn
28
29
  from flwr.client.typing import ClientFnExt, Mod
29
30
  from flwr.common import Context, Message, MessageType
30
31
  from flwr.common.logger import warn_deprecated_feature
31
- from flwr.common.message import validate_message_type
32
32
 
33
33
  from .typing import ClientAppCallable
34
34
 
@@ -95,16 +95,16 @@ class ClientApp:
95
95
 
96
96
  Examples
97
97
  --------
98
- Assuming a typical `Client` implementation named `FlowerClient`, you can wrap it in
99
- a `ClientApp` as follows:
100
-
101
- >>> class FlowerClient(NumPyClient):
102
- >>> # ...
103
- >>>
104
- >>> def client_fn(context: Context):
105
- >>> return FlowerClient().to_client()
106
- >>>
107
- >>> app = ClientApp(client_fn)
98
+ Assuming a typical ``Client`` implementation named ``FlowerClient``, you can wrap
99
+ it in a ``ClientApp`` as follows::
100
+
101
+ class FlowerClient(NumPyClient):
102
+ # ...
103
+
104
+ def client_fn(context: Context):
105
+ return FlowerClient().to_client()
106
+
107
+ app = ClientApp(client_fn)
108
108
  """
109
109
 
110
110
  def __init__(
@@ -181,39 +181,39 @@ class ClientApp:
181
181
 
182
182
  Examples
183
183
  --------
184
- Registering a train function:
185
-
186
- >>> app = ClientApp()
187
- >>>
188
- >>> @app.train()
189
- >>> def train(message: Message, context: Context) -> Message:
190
- >>> print("Executing default train function")
191
- >>> # Create and return an echo reply message
192
- >>> return Message(message.content, reply_to=message)
193
-
194
- Registering a train function with a custom action name:
195
-
196
- >>> app = ClientApp()
197
- >>>
198
- >>> # Messages with `message_type="train.custom_action"` will be
199
- >>> # routed to this function.
200
- >>> @app.train("custom_action")
201
- >>> def custom_action(message: Message, context: Context) -> Message:
202
- >>> print("Executing train function for custom action")
203
- >>> return Message(message.content, reply_to=message)
204
-
205
- Registering a train function with a function-specific Flower Mod:
206
-
207
- >>> from flwr.client.mod import message_size_mod
208
- >>>
209
- >>> app = ClientApp()
210
- >>>
211
- >>> # Using the `mods` argument to apply a function-specific mod.
212
- >>> @app.train(mods=[message_size_mod])
213
- >>> def train(message: Message, context: Context) -> Message:
214
- >>> print("Executing train function with message size mod")
215
- >>> # Create and return an echo reply message
216
- >>> return Message(message.content, reply_to=message)
184
+ Registering a train function::
185
+
186
+ app = ClientApp()
187
+
188
+ @app.train()
189
+ def train(message: Message, context: Context) -> Message:
190
+ print("Executing default train function")
191
+ # Create and return an echo reply message
192
+ return Message(message.content, reply_to=message)
193
+
194
+ Registering a train function with a custom action name::
195
+
196
+ app = ClientApp()
197
+
198
+ # Messages with `message_type="train.custom_action"` will be
199
+ # routed to this function.
200
+ @app.train("custom_action")
201
+ def custom_action(message: Message, context: Context) -> Message:
202
+ print("Executing train function for custom action")
203
+ return Message(message.content, reply_to=message)
204
+
205
+ Registering a train function with a function-specific Flower Mod::
206
+
207
+ from flwr.client.mod import message_size_mod
208
+
209
+ app = ClientApp()
210
+
211
+ # Using the `mods` argument to apply a function-specific mod.
212
+ @app.train(mods=[message_size_mod])
213
+ def train(message: Message, context: Context) -> Message:
214
+ print("Executing train function with message size mod")
215
+ # Create and return an echo reply message
216
+ return Message(message.content, reply_to=message)
217
217
  """
218
218
  return _get_decorator(self, MessageType.TRAIN, action, mods)
219
219
 
@@ -236,39 +236,39 @@ class ClientApp:
236
236
 
237
237
  Examples
238
238
  --------
239
- Registering an evaluate function:
240
-
241
- >>> app = ClientApp()
242
- >>>
243
- >>> @app.evaluate()
244
- >>> def evaluate(message: Message, context: Context) -> Message:
245
- >>> print("Executing default evaluate function")
246
- >>> # Create and return an echo reply message
247
- >>> return Message(message.content, reply_to=message)
248
-
249
- Registering an evaluate function with a custom action name:
250
-
251
- >>> app = ClientApp()
252
- >>>
253
- >>> # Messages with `message_type="evaluate.custom_action"` will be
254
- >>> # routed to this function.
255
- >>> @app.evaluate("custom_action")
256
- >>> def custom_action(message: Message, context: Context) -> Message:
257
- >>> print("Executing evaluate function for custom action")
258
- >>> return Message(message.content, reply_to=message)
259
-
260
- Registering an evaluate function with a function-specific Flower Mod:
261
-
262
- >>> from flwr.client.mod import message_size_mod
263
- >>>
264
- >>> app = ClientApp()
265
- >>>
266
- >>> # Using the `mods` argument to apply a function-specific mod.
267
- >>> @app.evaluate(mods=[message_size_mod])
268
- >>> def evaluate(message: Message, context: Context) -> Message:
269
- >>> print("Executing evaluate function with message size mod")
270
- >>> # Create and return an echo reply message
271
- >>> return Message(message.content, reply_to=message)
239
+ Registering an evaluate function::
240
+
241
+ app = ClientApp()
242
+
243
+ @app.evaluate()
244
+ def evaluate(message: Message, context: Context) -> Message:
245
+ print("Executing default evaluate function")
246
+ # Create and return an echo reply message
247
+ return Message(message.content, reply_to=message)
248
+
249
+ Registering an evaluate function with a custom action name::
250
+
251
+ app = ClientApp()
252
+
253
+ # Messages with `message_type="evaluate.custom_action"` will be
254
+ # routed to this function.
255
+ @app.evaluate("custom_action")
256
+ def custom_action(message: Message, context: Context) -> Message:
257
+ print("Executing evaluate function for custom action")
258
+ return Message(message.content, reply_to=message)
259
+
260
+ Registering an evaluate function with a function-specific Flower Mod::
261
+
262
+ from flwr.client.mod import message_size_mod
263
+
264
+ app = ClientApp()
265
+
266
+ # Using the `mods` argument to apply a function-specific mod.
267
+ @app.evaluate(mods=[message_size_mod])
268
+ def evaluate(message: Message, context: Context) -> Message:
269
+ print("Executing evaluate function with message size mod")
270
+ # Create and return an echo reply message
271
+ return Message(message.content, reply_to=message)
272
272
  """
273
273
  return _get_decorator(self, MessageType.EVALUATE, action, mods)
274
274
 
@@ -291,39 +291,39 @@ class ClientApp:
291
291
 
292
292
  Examples
293
293
  --------
294
- Registering a query function:
295
-
296
- >>> app = ClientApp()
297
- >>>
298
- >>> @app.query()
299
- >>> def query(message: Message, context: Context) -> Message:
300
- >>> print("Executing default query function")
301
- >>> # Create and return an echo reply message
302
- >>> return Message(message.content, reply_to=message)
303
-
304
- Registering a query function with a custom action name:
305
-
306
- >>> app = ClientApp()
307
- >>>
308
- >>> # Messages with `message_type="query.custom_action"` will be
309
- >>> # routed to this function.
310
- >>> @app.query("custom_action")
311
- >>> def custom_action(message: Message, context: Context) -> Message:
312
- >>> print("Executing query function for custom action")
313
- >>> return Message(message.content, reply_to=message)
314
-
315
- Registering a query function with a function-specific Flower Mod:
316
-
317
- >>> from flwr.client.mod import message_size_mod
318
- >>>
319
- >>> app = ClientApp()
320
- >>>
321
- >>> # Using the `mods` argument to apply a function-specific mod.
322
- >>> @app.query(mods=[message_size_mod])
323
- >>> def query(message: Message, context: Context) -> Message:
324
- >>> print("Executing query function with message size mod")
325
- >>> # Create and return an echo reply message
326
- >>> return Message(message.content, reply_to=message)
294
+ Registering a query function::
295
+
296
+ app = ClientApp()
297
+
298
+ @app.query()
299
+ def query(message: Message, context: Context) -> Message:
300
+ print("Executing default query function")
301
+ # Create and return an echo reply message
302
+ return Message(message.content, reply_to=message)
303
+
304
+ Registering a query function with a custom action name::
305
+
306
+ app = ClientApp()
307
+
308
+ # Messages with `message_type="query.custom_action"` will be
309
+ # routed to this function.
310
+ @app.query("custom_action")
311
+ def custom_action(message: Message, context: Context) -> Message:
312
+ print("Executing query function for custom action")
313
+ return Message(message.content, reply_to=message)
314
+
315
+ Registering a query function with a function-specific Flower Mod::
316
+
317
+ from flwr.client.mod import message_size_mod
318
+
319
+ app = ClientApp()
320
+
321
+ # Using the `mods` argument to apply a function-specific mod.
322
+ @app.query(mods=[message_size_mod])
323
+ def query(message: Message, context: Context) -> Message:
324
+ print("Executing query function with message size mod")
325
+ # Create and return an echo reply message
326
+ return Message(message.content, reply_to=message)
327
327
  """
328
328
  return _get_decorator(self, MessageType.QUERY, action, mods)
329
329
 
@@ -339,17 +339,19 @@ class ClientApp:
339
339
 
340
340
  Examples
341
341
  --------
342
- >>> app = ClientApp()
343
- >>>
344
- >>> @app.lifespan()
345
- >>> def lifespan(context: Context) -> None:
346
- >>> # Perform initialization tasks before the app starts
347
- >>> print("Initializing ClientApp")
348
- >>>
349
- >>> yield # ClientApp is running
350
- >>>
351
- >>> # Perform cleanup tasks after the app stops
352
- >>> print("Cleaning up ClientApp")
342
+ ::
343
+
344
+ app = ClientApp()
345
+
346
+ @app.lifespan()
347
+ def lifespan(context: Context) -> None:
348
+ # Perform initialization tasks before the app starts
349
+ print("Initializing ClientApp")
350
+
351
+ yield # ClientApp is running
352
+
353
+ # Perform cleanup tasks after the app stops
354
+ print("Cleaning up ClientApp")
353
355
  """
354
356
 
355
357
  def lifespan_decorator(
@@ -436,24 +438,24 @@ def _registration_error(fn_name: str) -> ValueError:
436
438
 
437
439
  Use the `ClientApp` with an existing `client_fn`:
438
440
 
439
- >>> class FlowerClient(NumPyClient):
440
- >>> # ...
441
- >>>
442
- >>> def client_fn(context: Context):
443
- >>> return FlowerClient().to_client()
444
- >>>
445
- >>> app = ClientApp(
446
- >>> client_fn=client_fn,
447
- >>> )
441
+ class FlowerClient(NumPyClient):
442
+ # ...
443
+
444
+ def client_fn(context: Context):
445
+ return FlowerClient().to_client()
446
+
447
+ app = ClientApp(
448
+ client_fn=client_fn,
449
+ )
448
450
 
449
451
  Use the `ClientApp` with a custom {fn_name} function:
450
452
 
451
- >>> app = ClientApp()
452
- >>>
453
- >>> @app.{fn_name}()
454
- >>> def {fn_name}(message: Message, context: Context) -> Message:
455
- >>> print("ClientApp {fn_name} running")
456
- >>> # Create and return an echo reply message
457
- >>> return Message(message.content, reply_to=message)
453
+ app = ClientApp()
454
+
455
+ @app.{fn_name}()
456
+ def {fn_name}(message: Message, context: Context) -> Message:
457
+ print("ClientApp {fn_name} running")
458
+ # Create and return an echo reply message
459
+ return Message(message.content, reply_to=message)
458
460
  """,
459
461
  )
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -13,10 +13,3 @@
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
15
  """Flower AppIO service."""
16
-
17
-
18
- from .app import flwr_clientapp as flwr_clientapp
19
-
20
- __all__ = [
21
- "flwr_clientapp",
22
- ]
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -45,10 +45,10 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
45
45
  tuple[
46
46
  Callable[[], Optional[Message]],
47
47
  Callable[[Message], None],
48
- Optional[Callable[[], Optional[int]]],
49
- Optional[Callable[[], None]],
50
- Optional[Callable[[int], Run]],
51
- Optional[Callable[[str, int], Fab]],
48
+ Callable[[], Optional[int]],
49
+ Callable[[], None],
50
+ Callable[[int], Run],
51
+ Callable[[str, int], Fab],
52
52
  ]
53
53
  ]:
54
54
  """Primitives for request/response-based interaction with a server via GrpcAdapter.
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2025 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.