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
@@ -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.
@@ -132,6 +132,7 @@ class DeploymentEngine(Executor):
132
132
  self,
133
133
  fab: Fab,
134
134
  override_config: UserConfig,
135
+ flwr_aid: Optional[str],
135
136
  ) -> int:
136
137
  fab_hash = self.ffs.put(fab.content, {})
137
138
  if fab_hash != fab.hash_str:
@@ -141,7 +142,7 @@ class DeploymentEngine(Executor):
141
142
  fab_id, fab_version = get_fab_metadata(fab.content)
142
143
 
143
144
  run_id = self.linkstate.create_run(
144
- fab_id, fab_version, fab_hash, override_config, ConfigRecord()
145
+ fab_id, fab_version, fab_hash, override_config, ConfigRecord(), flwr_aid
145
146
  )
146
147
  return run_id
147
148
 
@@ -161,6 +162,7 @@ class DeploymentEngine(Executor):
161
162
  fab_file: bytes,
162
163
  override_config: UserConfig,
163
164
  federation_options: ConfigRecord,
165
+ flwr_aid: Optional[str],
164
166
  ) -> Optional[int]:
165
167
  """Start run using the Flower Deployment Engine."""
166
168
  run_id = None
@@ -168,7 +170,9 @@ class DeploymentEngine(Executor):
168
170
 
169
171
  # Call SuperLink to create run
170
172
  run_id = self._create_run(
171
- Fab(hashlib.sha256(fab_file).hexdigest(), fab_file), override_config
173
+ Fab(hashlib.sha256(fab_file).hexdigest(), fab_file),
174
+ override_config,
175
+ flwr_aid,
172
176
  )
173
177
 
174
178
  # Register context for the Run
@@ -24,7 +24,7 @@ from google.protobuf.message import Message as GrpcMessage
24
24
  from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
25
25
  from flwr.common.typing import LogEntry
26
26
 
27
- from .exec_user_auth_interceptor import shared_user_info
27
+ from .exec_user_auth_interceptor import shared_account_info
28
28
 
29
29
 
30
30
  class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
@@ -62,7 +62,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
62
62
  log_entry = self.log_plugin.compose_log_before_event(
63
63
  request=request,
64
64
  context=context,
65
- user_info=shared_user_info.get(),
65
+ account_info=shared_account_info.get(),
66
66
  method_name=method_name,
67
67
  )
68
68
  self.log_plugin.write_log(log_entry)
@@ -81,7 +81,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
81
81
  log_entry = self.log_plugin.compose_log_after_event(
82
82
  request=request,
83
83
  context=context,
84
- user_info=shared_user_info.get(),
84
+ account_info=shared_account_info.get(),
85
85
  method_name=method_name,
86
86
  response=unary_response or error,
87
87
  )
@@ -111,7 +111,7 @@ class ExecEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
111
111
  log_entry = self.log_plugin.compose_log_after_event(
112
112
  request=request,
113
113
  context=context,
114
- user_info=shared_user_info.get(),
114
+ account_info=shared_account_info.get(),
115
115
  method_name=method_name,
116
116
  response=stream_response or error,
117
117
  )
@@ -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.
@@ -21,7 +21,7 @@ from typing import Optional
21
21
  import grpc
22
22
 
23
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
- from flwr.common.auth_plugin import ExecAuthPlugin
24
+ from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
25
25
  from flwr.common.event_log_plugin import EventLogWriterPlugin
26
26
  from flwr.common.grpc import generic_create_grpc_server
27
27
  from flwr.common.logger import log
@@ -29,6 +29,7 @@ from flwr.common.typing import UserConfig
29
29
  from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
30
30
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
31
31
  from flwr.server.superlink.linkstate import LinkStateFactory
32
+ from flwr.supercore.object_store import ObjectStoreFactory
32
33
  from flwr.superexec.exec_event_log_interceptor import ExecEventLogInterceptor
33
34
  from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
34
35
 
@@ -42,9 +43,11 @@ def run_exec_api_grpc(
42
43
  executor: Executor,
43
44
  state_factory: LinkStateFactory,
44
45
  ffs_factory: FfsFactory,
46
+ objectstore_factory: ObjectStoreFactory,
45
47
  certificates: Optional[tuple[bytes, bytes, bytes]],
46
48
  config: UserConfig,
47
49
  auth_plugin: Optional[ExecAuthPlugin] = None,
50
+ authz_plugin: Optional[ExecAuthzPlugin] = None,
48
51
  event_log_plugin: Optional[EventLogWriterPlugin] = None,
49
52
  ) -> grpc.Server:
50
53
  """Run Exec API (gRPC, request-response)."""
@@ -53,12 +56,13 @@ def run_exec_api_grpc(
53
56
  exec_servicer: grpc.Server = ExecServicer(
54
57
  linkstate_factory=state_factory,
55
58
  ffs_factory=ffs_factory,
59
+ objectstore_factory=objectstore_factory,
56
60
  executor=executor,
57
61
  auth_plugin=auth_plugin,
58
62
  )
59
63
  interceptors: list[grpc.ServerInterceptor] = []
60
- if auth_plugin is not None:
61
- interceptors.append(ExecUserAuthInterceptor(auth_plugin))
64
+ if auth_plugin is not None and authz_plugin is not None:
65
+ interceptors.append(ExecUserAuthInterceptor(auth_plugin, authz_plugin))
62
66
  # Event log interceptor must be added after user auth interceptor
63
67
  if event_log_plugin is not None:
64
68
  interceptors.append(ExecEventLogInterceptor(event_log_plugin))
@@ -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.
@@ -18,21 +18,25 @@
18
18
  import time
19
19
  from collections.abc import Generator
20
20
  from logging import ERROR, INFO
21
- from typing import Any, Optional
22
- from uuid import UUID
21
+ from typing import Any, Optional, cast
23
22
 
24
23
  import grpc
25
24
 
26
25
  from flwr.common import now
27
26
  from flwr.common.auth_plugin import ExecAuthPlugin
28
- from flwr.common.constant import LOG_STREAM_INTERVAL, Status, SubStatus
27
+ from flwr.common.constant import (
28
+ LOG_STREAM_INTERVAL,
29
+ RUN_ID_NOT_FOUND_MESSAGE,
30
+ Status,
31
+ SubStatus,
32
+ )
29
33
  from flwr.common.logger import log
30
34
  from flwr.common.serde import (
31
35
  config_record_from_proto,
32
36
  run_to_proto,
33
37
  user_config_from_proto,
34
38
  )
35
- from flwr.common.typing import RunStatus
39
+ from flwr.common.typing import Run, RunStatus
36
40
  from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
37
41
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
38
42
  GetAuthTokensRequest,
@@ -50,22 +54,26 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
50
54
  )
51
55
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
52
56
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
57
+ from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
53
58
 
59
+ from .exec_user_auth_interceptor import shared_account_info
54
60
  from .executor import Executor
55
61
 
56
62
 
57
63
  class ExecServicer(exec_pb2_grpc.ExecServicer):
58
64
  """SuperExec API servicer."""
59
65
 
60
- def __init__(
66
+ def __init__( # pylint: disable=R0913, R0917
61
67
  self,
62
68
  linkstate_factory: LinkStateFactory,
63
69
  ffs_factory: FfsFactory,
70
+ objectstore_factory: ObjectStoreFactory,
64
71
  executor: Executor,
65
72
  auth_plugin: Optional[ExecAuthPlugin] = None,
66
73
  ) -> None:
67
74
  self.linkstate_factory = linkstate_factory
68
75
  self.ffs_factory = ffs_factory
76
+ self.objectstore_factory = objectstore_factory
69
77
  self.executor = executor
70
78
  self.executor.initialize(linkstate_factory, ffs_factory)
71
79
  self.auth_plugin = auth_plugin
@@ -76,10 +84,12 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
76
84
  """Create run ID."""
77
85
  log(INFO, "ExecServicer.StartRun")
78
86
 
87
+ flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
79
88
  run_id = self.executor.start_run(
80
89
  request.fab.content,
81
90
  user_config_from_proto(request.override_config),
82
91
  config_record_from_proto(request.federation_options),
92
+ flwr_aid,
83
93
  )
84
94
 
85
95
  if run_id is None:
@@ -95,12 +105,20 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
95
105
  log(INFO, "ExecServicer.StreamLogs")
96
106
  state = self.linkstate_factory.state()
97
107
 
98
- # Retrieve run ID
108
+ # Retrieve run ID and run
99
109
  run_id = request.run_id
110
+ run = state.get_run(run_id)
100
111
 
101
112
  # Exit if `run_id` not found
102
- if not state.get_run(run_id):
103
- context.abort(grpc.StatusCode.NOT_FOUND, "Run ID not found")
113
+ if not run:
114
+ context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
115
+
116
+ # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
117
+ if self.auth_plugin:
118
+ flwr_aid = shared_account_info.get().flwr_aid
119
+ _check_flwr_aid_in_run(
120
+ flwr_aid=flwr_aid, run=cast(Run, run), context=context
121
+ )
104
122
 
105
123
  after_timestamp = request.after_timestamp + 1e-6
106
124
  while context.is_active():
@@ -119,7 +137,10 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
119
137
  # is returned at this point and the server ends the stream.
120
138
  run_status = state.get_run_status({run_id})[run_id]
121
139
  if run_status.status == Status.FINISHED:
122
- log(INFO, "All logs for run ID `%s` returned", request.run_id)
140
+ log(INFO, "All logs for run ID `%s` returned", run_id)
141
+
142
+ # Delete objects of the run from the object store
143
+ self.objectstore_factory.store().delete_objects_in_run(run_id)
123
144
  break
124
145
 
125
146
  time.sleep(LOG_STREAM_INTERVAL) # Sleep briefly to avoid busy waiting
@@ -131,11 +152,44 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
131
152
  log(INFO, "ExecServicer.List")
132
153
  state = self.linkstate_factory.state()
133
154
 
134
- # Handle `flwr ls --runs`
155
+ # Build a set of run IDs for `flwr ls --runs`
135
156
  if not request.HasField("run_id"):
136
- return _create_list_runs_response(state.get_run_ids(), state)
137
- # Handle `flwr ls --run-id <run_id>`
138
- return _create_list_runs_response({request.run_id}, state)
157
+ if self.auth_plugin:
158
+ # If no `run_id` is specified and user auth is enabled,
159
+ # return run IDs for the authenticated user
160
+ flwr_aid = shared_account_info.get().flwr_aid
161
+ if flwr_aid is None:
162
+ context.abort(
163
+ grpc.StatusCode.PERMISSION_DENIED,
164
+ "️⛔️ User authentication is enabled, but `flwr_aid` is None",
165
+ )
166
+ run_ids = state.get_run_ids(flwr_aid=flwr_aid)
167
+ else:
168
+ # If no `run_id` is specified and no user auth is enabled,
169
+ # return all run IDs
170
+ run_ids = state.get_run_ids(None)
171
+ # Build a set of run IDs for `flwr ls --run-id <run_id>`
172
+ else:
173
+ # Retrieve run ID and run
174
+ run_id = request.run_id
175
+ run = state.get_run(run_id)
176
+
177
+ # Exit if `run_id` not found
178
+ if not run:
179
+ context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
180
+
181
+ # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
182
+ if self.auth_plugin:
183
+ flwr_aid = shared_account_info.get().flwr_aid
184
+ _check_flwr_aid_in_run(
185
+ flwr_aid=flwr_aid, run=cast(Run, run), context=context
186
+ )
187
+
188
+ run_ids = {run_id}
189
+
190
+ # Init the object store
191
+ store = self.objectstore_factory.store()
192
+ return _create_list_runs_response(run_ids, state, store)
139
193
 
140
194
  def StopRun(
141
195
  self, request: StopRunRequest, context: grpc.ServicerContext
@@ -144,30 +198,42 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
144
198
  log(INFO, "ExecServicer.StopRun")
145
199
  state = self.linkstate_factory.state()
146
200
 
201
+ # Retrieve run ID and run
202
+ run_id = request.run_id
203
+ run = state.get_run(run_id)
204
+
147
205
  # Exit if `run_id` not found
148
- if not state.get_run(request.run_id):
149
- context.abort(
150
- grpc.StatusCode.NOT_FOUND, f"Run ID {request.run_id} not found"
206
+ if not run:
207
+ context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
208
+
209
+ # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
210
+ if self.auth_plugin:
211
+ flwr_aid = shared_account_info.get().flwr_aid
212
+ _check_flwr_aid_in_run(
213
+ flwr_aid=flwr_aid, run=cast(Run, run), context=context
151
214
  )
152
215
 
153
- run_status = state.get_run_status({request.run_id})[request.run_id]
216
+ run_status = state.get_run_status({run_id})[run_id]
154
217
  if run_status.status == Status.FINISHED:
155
218
  context.abort(
156
219
  grpc.StatusCode.FAILED_PRECONDITION,
157
- f"Run ID {request.run_id} is already finished",
220
+ f"Run ID {run_id} is already finished",
158
221
  )
159
222
 
160
223
  update_success = state.update_run_status(
161
- run_id=request.run_id,
224
+ run_id=run_id,
162
225
  new_status=RunStatus(Status.FINISHED, SubStatus.STOPPED, ""),
163
226
  )
164
227
 
165
228
  if update_success:
166
- message_ids: set[UUID] = state.get_message_ids_from_run_id(request.run_id)
229
+ message_ids: set[str] = state.get_message_ids_from_run_id(run_id)
167
230
 
168
231
  # Delete Messages and their replies for the `run_id`
169
232
  state.delete_messages(message_ids)
170
233
 
234
+ # Delete objects of the run from the object store
235
+ self.objectstore_factory.store().delete_objects_in_run(run_id)
236
+
171
237
  return StopRunResponse(success=update_success)
172
238
 
173
239
  def GetLoginDetails(
@@ -222,10 +288,46 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
222
288
  )
223
289
 
224
290
 
225
- def _create_list_runs_response(run_ids: set[int], state: LinkState) -> ListRunsResponse:
291
+ def _create_list_runs_response(
292
+ run_ids: set[int], state: LinkState, store: ObjectStore
293
+ ) -> ListRunsResponse:
226
294
  """Create response for `flwr ls --runs` and `flwr ls --run-id <run_id>`."""
227
- run_dict = {run_id: state.get_run(run_id) for run_id in run_ids}
295
+ run_dict = {run_id: run for run_id in run_ids if (run := state.get_run(run_id))}
296
+
297
+ # Delete objects of finished runs from the object store
298
+ for run_id, run in run_dict.items():
299
+ if run.status.status == Status.FINISHED:
300
+ store.delete_objects_in_run(run_id)
301
+
228
302
  return ListRunsResponse(
229
- run_dict={run_id: run_to_proto(run) for run_id, run in run_dict.items() if run},
303
+ run_dict={run_id: run_to_proto(run) for run_id, run in run_dict.items()},
230
304
  now=now().isoformat(),
231
305
  )
306
+
307
+
308
+ def _check_flwr_aid_in_run(
309
+ flwr_aid: Optional[str], run: Run, context: grpc.ServicerContext
310
+ ) -> None:
311
+ """Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
312
+ # `flwr_aid` must not be None. Abort if it is None.
313
+ if flwr_aid is None:
314
+ context.abort(
315
+ grpc.StatusCode.PERMISSION_DENIED,
316
+ "️⛔️ User authentication is enabled, but `flwr_aid` is None",
317
+ )
318
+
319
+ # `run.flwr_aid` must not be an empty string. Abort if it is empty.
320
+ run_flwr_aid = run.flwr_aid
321
+ if not run_flwr_aid:
322
+ context.abort(
323
+ grpc.StatusCode.PERMISSION_DENIED,
324
+ "⛔️ User authentication is enabled, but the run is not associated "
325
+ "with a `flwr_aid`.",
326
+ )
327
+
328
+ # Exit if `flwr_aid` does not match the run's `flwr_aid`
329
+ if run_flwr_aid != flwr_aid:
330
+ context.abort(
331
+ grpc.StatusCode.PERMISSION_DENIED,
332
+ "⛔️ Run ID does not belong to the user",
333
+ )
@@ -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.
@@ -16,12 +16,12 @@
16
16
 
17
17
 
18
18
  import contextvars
19
- from typing import Any, Callable, Union, cast
19
+ from typing import Any, Callable, Union
20
20
 
21
21
  import grpc
22
22
 
23
- from flwr.common.auth_plugin import ExecAuthPlugin
24
- from flwr.common.typing import UserInfo
23
+ from flwr.common.auth_plugin import ExecAuthPlugin, ExecAuthzPlugin
24
+ from flwr.common.typing import AccountInfo
25
25
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
26
26
  GetAuthTokensRequest,
27
27
  GetAuthTokensResponse,
@@ -45,8 +45,8 @@ Response = Union[
45
45
  ]
46
46
 
47
47
 
48
- shared_user_info: contextvars.ContextVar[UserInfo] = contextvars.ContextVar(
49
- "user_info", default=UserInfo(user_id=None, user_name=None)
48
+ shared_account_info: contextvars.ContextVar[AccountInfo] = contextvars.ContextVar(
49
+ "account_info", default=AccountInfo(flwr_aid=None, account_name=None)
50
50
  )
51
51
 
52
52
 
@@ -56,8 +56,10 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
56
56
  def __init__(
57
57
  self,
58
58
  auth_plugin: ExecAuthPlugin,
59
+ authz_plugin: ExecAuthzPlugin,
59
60
  ):
60
61
  self.auth_plugin = auth_plugin
62
+ self.authz_plugin = authz_plugin
61
63
 
62
64
  def intercept_service(
63
65
  self,
@@ -91,17 +93,44 @@ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
91
93
  return call(request, context) # type: ignore
92
94
 
93
95
  # For other requests, check if the user is authenticated
94
- valid_tokens, user_info = self.auth_plugin.validate_tokens_in_metadata(
96
+ valid_tokens, account_info = self.auth_plugin.validate_tokens_in_metadata(
95
97
  metadata
96
98
  )
97
99
  if valid_tokens:
100
+ if account_info is None:
101
+ context.abort(
102
+ grpc.StatusCode.UNAUTHENTICATED,
103
+ "Tokens validated, but user info not found",
104
+ )
105
+ raise grpc.RpcError()
98
106
  # Store user info in contextvars for authenticated users
99
- shared_user_info.set(cast(UserInfo, user_info))
107
+ shared_account_info.set(account_info)
108
+ # Check if the user is authorized
109
+ if not self.authz_plugin.verify_user_authorization(account_info):
110
+ context.abort(
111
+ grpc.StatusCode.PERMISSION_DENIED, "User not authorized"
112
+ )
113
+ raise grpc.RpcError()
100
114
  return call(request, context) # type: ignore
101
115
 
102
116
  # If the user is not authenticated, refresh tokens
103
- tokens = self.auth_plugin.refresh_tokens(context.invocation_metadata())
117
+ tokens, account_info = self.auth_plugin.refresh_tokens(metadata)
104
118
  if tokens is not None:
119
+ if account_info is None:
120
+ context.abort(
121
+ grpc.StatusCode.UNAUTHENTICATED,
122
+ "Tokens refreshed, but user info not found",
123
+ )
124
+ raise grpc.RpcError()
125
+ # Store user info in contextvars for authenticated users
126
+ shared_account_info.set(account_info)
127
+ # Check if the user is authorized
128
+ if not self.authz_plugin.verify_user_authorization(account_info):
129
+ context.abort(
130
+ grpc.StatusCode.PERMISSION_DENIED, "User not authorized"
131
+ )
132
+ raise grpc.RpcError()
133
+
105
134
  context.send_initial_metadata(tokens)
106
135
  return call(request, context) # type: ignore
107
136
 
@@ -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.
@@ -74,6 +74,7 @@ class Executor(ABC):
74
74
  fab_file: bytes,
75
75
  override_config: UserConfig,
76
76
  federation_options: ConfigRecord,
77
+ flwr_aid: Optional[str],
77
78
  ) -> Optional[int]:
78
79
  """Start a run using the given Flower FAB ID and version.
79
80
 
@@ -88,6 +89,9 @@ class Executor(ABC):
88
89
  The config overrides dict sent by the user (using `flwr run`).
89
90
  federation_options: ConfigRecord
90
91
  The federation options sent by the user (using `flwr run`).
92
+ flwr_aid : Optional[str]
93
+ The Flower Account ID of the user starting the run, if authentication is
94
+ enabled.
91
95
 
92
96
  Returns
93
97
  -------
@@ -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.
@@ -77,6 +77,7 @@ class SimulationEngine(Executor):
77
77
  fab_file: bytes,
78
78
  override_config: UserConfig,
79
79
  federation_options: ConfigRecord,
80
+ flwr_aid: Optional[str],
80
81
  ) -> Optional[int]:
81
82
  """Start run using the Flower Simulation Engine."""
82
83
  try:
@@ -96,7 +97,12 @@ class SimulationEngine(Executor):
96
97
  fab_id, fab_version = get_fab_metadata(fab.content)
97
98
 
98
99
  run_id = self.linkstate.create_run(
99
- fab_id, fab_version, fab_hash, override_config, federation_options
100
+ fab_id,
101
+ fab_version,
102
+ fab_hash,
103
+ override_config,
104
+ federation_options,
105
+ flwr_aid,
100
106
  )
101
107
 
102
108
  # Create an empty context for the Run
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower SuperLink."""
@@ -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 SuperNode."""
16
-
17
-
18
- from .app import run_supernode as run_supernode
19
-
20
- __all__ = [
21
- "run_supernode",
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.
@@ -12,20 +12,13 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Abstract base class NodeState."""
15
+ """Flower command line interface for SuperNode."""
16
16
 
17
17
 
18
- import abc
19
- from typing import Optional
18
+ from .flower_supernode import flower_supernode
19
+ from .flwr_clientapp import flwr_clientapp
20
20
 
21
-
22
- class NodeState(abc.ABC):
23
- """Abstract NodeState."""
24
-
25
- @abc.abstractmethod
26
- def set_node_id(self, node_id: Optional[int]) -> None:
27
- """Set the node ID."""
28
-
29
- @abc.abstractmethod
30
- def get_node_id(self) -> int:
31
- """Get the node ID."""
21
+ __all__ = [
22
+ "flower_supernode",
23
+ "flwr_clientapp",
24
+ ]
@@ -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.
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower SuperNode."""
15
+ """`flower-supernode` command."""
16
16
 
17
17
 
18
18
  import argparse
@@ -42,12 +42,10 @@ from flwr.common.constant import (
42
42
  from flwr.common.exit import ExitCode, flwr_exit
43
43
  from flwr.common.exit_handlers import register_exit_handlers
44
44
  from flwr.common.logger import log
45
+ from flwr.supernode.start_client_internal import start_client_internal
45
46
 
46
- from ..app import start_client_internal
47
- from ..clientapp.utils import get_load_client_app_fn
48
47
 
49
-
50
- def run_supernode() -> None:
48
+ def flower_supernode() -> None:
51
49
  """Run Flower SuperNode."""
52
50
  args = _parse_args_run_supernode().parse_args()
53
51
 
@@ -64,12 +62,6 @@ def run_supernode() -> None:
64
62
  )
65
63
 
66
64
  root_certificates = try_obtain_root_certificates(args, args.superlink)
67
- load_fn = get_load_client_app_fn(
68
- default_app_ref="",
69
- app_path=None,
70
- flwr_dir=args.flwr_dir,
71
- multi_app=True,
72
- )
73
65
  authentication_keys = _try_setup_client_authentication(args)
74
66
 
75
67
  log(DEBUG, "Isolation mode: %s", args.isolation)
@@ -82,7 +74,6 @@ def run_supernode() -> None:
82
74
 
83
75
  start_client_internal(
84
76
  server_address=args.superlink,
85
- load_client_app_fn=load_fn,
86
77
  transport=args.transport,
87
78
  root_certificates=root_certificates,
88
79
  insecure=args.insecure,