flwr 1.18.0__py3-none-any.whl → 1.20.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 (174) hide show
  1. flwr/app/__init__.py +15 -0
  2. flwr/app/error.py +68 -0
  3. flwr/app/metadata.py +223 -0
  4. flwr/cli/build.py +94 -59
  5. flwr/cli/log.py +3 -3
  6. flwr/cli/login/login.py +3 -7
  7. flwr/cli/ls.py +15 -36
  8. flwr/cli/new/new.py +12 -4
  9. flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
  10. flwr/cli/new/templates/app/README.md.tpl +5 -0
  11. flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
  12. flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
  13. flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
  14. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +25 -17
  15. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
  16. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
  17. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
  18. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
  19. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
  20. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
  21. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
  22. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
  23. flwr/cli/run/run.py +48 -49
  24. flwr/cli/stop.py +2 -2
  25. flwr/cli/utils.py +38 -5
  26. flwr/client/__init__.py +2 -2
  27. flwr/client/client_app.py +1 -1
  28. flwr/client/clientapp/__init__.py +0 -7
  29. flwr/client/grpc_adapter_client/connection.py +15 -8
  30. flwr/client/grpc_rere_client/connection.py +142 -97
  31. flwr/client/grpc_rere_client/grpc_adapter.py +34 -6
  32. flwr/client/message_handler/message_handler.py +1 -1
  33. flwr/client/mod/comms_mods.py +36 -17
  34. flwr/client/rest_client/connection.py +176 -103
  35. flwr/clientapp/__init__.py +15 -0
  36. flwr/common/__init__.py +2 -2
  37. flwr/common/auth_plugin/__init__.py +2 -0
  38. flwr/common/auth_plugin/auth_plugin.py +29 -3
  39. flwr/common/constant.py +39 -8
  40. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  41. flwr/common/exit/exit_code.py +16 -1
  42. flwr/common/exit_handlers.py +30 -0
  43. flwr/common/grpc.py +12 -1
  44. flwr/common/heartbeat.py +165 -0
  45. flwr/common/inflatable.py +290 -0
  46. flwr/common/inflatable_protobuf_utils.py +141 -0
  47. flwr/common/inflatable_utils.py +508 -0
  48. flwr/common/message.py +110 -242
  49. flwr/common/record/__init__.py +2 -1
  50. flwr/common/record/array.py +402 -0
  51. flwr/common/record/arraychunk.py +59 -0
  52. flwr/common/record/arrayrecord.py +103 -225
  53. flwr/common/record/configrecord.py +59 -4
  54. flwr/common/record/conversion_utils.py +1 -1
  55. flwr/common/record/metricrecord.py +55 -4
  56. flwr/common/record/recorddict.py +69 -1
  57. flwr/common/recorddict_compat.py +2 -2
  58. flwr/common/retry_invoker.py +5 -1
  59. flwr/common/serde.py +59 -211
  60. flwr/common/serde_utils.py +175 -0
  61. flwr/common/typing.py +5 -3
  62. flwr/compat/__init__.py +15 -0
  63. flwr/compat/client/__init__.py +15 -0
  64. flwr/{client → compat/client}/app.py +28 -185
  65. flwr/compat/common/__init__.py +15 -0
  66. flwr/compat/server/__init__.py +15 -0
  67. flwr/compat/server/app.py +174 -0
  68. flwr/compat/simulation/__init__.py +15 -0
  69. flwr/proto/appio_pb2.py +43 -0
  70. flwr/proto/appio_pb2.pyi +151 -0
  71. flwr/proto/appio_pb2_grpc.py +4 -0
  72. flwr/proto/appio_pb2_grpc.pyi +4 -0
  73. flwr/proto/clientappio_pb2.py +12 -19
  74. flwr/proto/clientappio_pb2.pyi +23 -101
  75. flwr/proto/clientappio_pb2_grpc.py +269 -28
  76. flwr/proto/clientappio_pb2_grpc.pyi +114 -20
  77. flwr/proto/fleet_pb2.py +24 -27
  78. flwr/proto/fleet_pb2.pyi +19 -35
  79. flwr/proto/fleet_pb2_grpc.py +117 -13
  80. flwr/proto/fleet_pb2_grpc.pyi +47 -6
  81. flwr/proto/heartbeat_pb2.py +33 -0
  82. flwr/proto/heartbeat_pb2.pyi +66 -0
  83. flwr/proto/heartbeat_pb2_grpc.py +4 -0
  84. flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
  85. flwr/proto/message_pb2.py +28 -11
  86. flwr/proto/message_pb2.pyi +125 -0
  87. flwr/proto/recorddict_pb2.py +16 -28
  88. flwr/proto/recorddict_pb2.pyi +46 -64
  89. flwr/proto/run_pb2.py +24 -32
  90. flwr/proto/run_pb2.pyi +4 -52
  91. flwr/proto/serverappio_pb2.py +9 -23
  92. flwr/proto/serverappio_pb2.pyi +0 -110
  93. flwr/proto/serverappio_pb2_grpc.py +177 -72
  94. flwr/proto/serverappio_pb2_grpc.pyi +75 -33
  95. flwr/proto/simulationio_pb2.py +12 -11
  96. flwr/proto/simulationio_pb2_grpc.py +35 -0
  97. flwr/proto/simulationio_pb2_grpc.pyi +14 -0
  98. flwr/server/__init__.py +1 -1
  99. flwr/server/app.py +69 -187
  100. flwr/server/compat/app_utils.py +50 -28
  101. flwr/server/fleet_event_log_interceptor.py +6 -2
  102. flwr/server/grid/grpc_grid.py +148 -41
  103. flwr/server/grid/inmemory_grid.py +5 -4
  104. flwr/server/serverapp/app.py +45 -17
  105. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +21 -3
  106. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
  107. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
  108. flwr/server/superlink/fleet/message_handler/message_handler.py +130 -19
  109. flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -13
  110. flwr/server/superlink/fleet/vce/vce_api.py +6 -3
  111. flwr/server/superlink/linkstate/in_memory_linkstate.py +138 -43
  112. flwr/server/superlink/linkstate/linkstate.py +53 -20
  113. flwr/server/superlink/linkstate/sqlite_linkstate.py +149 -55
  114. flwr/server/superlink/linkstate/utils.py +33 -29
  115. flwr/server/superlink/serverappio/serverappio_grpc.py +4 -1
  116. flwr/server/superlink/serverappio/serverappio_servicer.py +230 -84
  117. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  118. flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
  119. flwr/server/superlink/utils.py +9 -2
  120. flwr/server/utils/validator.py +2 -2
  121. flwr/serverapp/__init__.py +15 -0
  122. flwr/simulation/app.py +25 -0
  123. flwr/simulation/run_simulation.py +17 -0
  124. flwr/supercore/__init__.py +15 -0
  125. flwr/{server/superlink → supercore}/ffs/__init__.py +2 -0
  126. flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
  127. flwr/supercore/grpc_health/__init__.py +22 -0
  128. flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
  129. flwr/supercore/license_plugin/__init__.py +22 -0
  130. flwr/supercore/license_plugin/license_plugin.py +26 -0
  131. flwr/supercore/object_store/__init__.py +24 -0
  132. flwr/supercore/object_store/in_memory_object_store.py +229 -0
  133. flwr/supercore/object_store/object_store.py +170 -0
  134. flwr/supercore/object_store/object_store_factory.py +44 -0
  135. flwr/supercore/object_store/utils.py +43 -0
  136. flwr/supercore/scheduler/__init__.py +22 -0
  137. flwr/supercore/scheduler/plugin.py +71 -0
  138. flwr/{client/nodestate/nodestate.py → supercore/utils.py} +14 -13
  139. flwr/superexec/deployment.py +7 -4
  140. flwr/superexec/exec_event_log_interceptor.py +8 -4
  141. flwr/superexec/exec_grpc.py +25 -5
  142. flwr/superexec/exec_license_interceptor.py +82 -0
  143. flwr/superexec/exec_servicer.py +135 -24
  144. flwr/superexec/exec_user_auth_interceptor.py +45 -8
  145. flwr/superexec/executor.py +5 -1
  146. flwr/superexec/simulation.py +8 -3
  147. flwr/superlink/__init__.py +15 -0
  148. flwr/{client/supernode → supernode}/__init__.py +0 -7
  149. flwr/supernode/cli/__init__.py +24 -0
  150. flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +3 -19
  151. flwr/supernode/cli/flwr_clientapp.py +88 -0
  152. flwr/supernode/nodestate/in_memory_nodestate.py +199 -0
  153. flwr/supernode/nodestate/nodestate.py +227 -0
  154. flwr/supernode/runtime/__init__.py +15 -0
  155. flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +135 -89
  156. flwr/supernode/scheduler/__init__.py +22 -0
  157. flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
  158. flwr/supernode/servicer/__init__.py +15 -0
  159. flwr/supernode/servicer/clientappio/__init__.py +22 -0
  160. flwr/supernode/servicer/clientappio/clientappio_servicer.py +303 -0
  161. flwr/supernode/start_client_internal.py +589 -0
  162. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/METADATA +6 -4
  163. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/RECORD +171 -123
  164. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +1 -1
  165. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +2 -2
  166. flwr/client/clientapp/clientappio_servicer.py +0 -244
  167. flwr/client/heartbeat.py +0 -74
  168. flwr/client/nodestate/in_memory_nodestate.py +0 -38
  169. /flwr/{client → compat/client}/grpc_client/__init__.py +0 -0
  170. /flwr/{client → compat/client}/grpc_client/connection.py +0 -0
  171. /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
  172. /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
  173. /flwr/{client → supernode}/nodestate/__init__.py +0 -0
  174. /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
@@ -0,0 +1,303 @@
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
+ """ClientAppIo API servicer."""
16
+
17
+
18
+ from logging import DEBUG, ERROR
19
+ from typing import cast
20
+
21
+ import grpc
22
+
23
+ from flwr.common import Context
24
+ from flwr.common.inflatable import UnexpectedObjectContentError
25
+ from flwr.common.logger import log
26
+ from flwr.common.serde import (
27
+ context_from_proto,
28
+ context_to_proto,
29
+ fab_to_proto,
30
+ message_from_proto,
31
+ message_to_proto,
32
+ run_to_proto,
33
+ )
34
+ from flwr.common.typing import Fab, Run
35
+
36
+ # pylint: disable=E0611
37
+ from flwr.proto import clientappio_pb2_grpc
38
+ from flwr.proto.appio_pb2 import ( # pylint: disable=E0401
39
+ PullAppInputsRequest,
40
+ PullAppInputsResponse,
41
+ PullAppMessagesRequest,
42
+ PullAppMessagesResponse,
43
+ PushAppMessagesRequest,
44
+ PushAppMessagesResponse,
45
+ PushAppOutputsRequest,
46
+ PushAppOutputsResponse,
47
+ )
48
+ from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
49
+ GetRunIdsWithPendingMessagesRequest,
50
+ GetRunIdsWithPendingMessagesResponse,
51
+ RequestTokenRequest,
52
+ RequestTokenResponse,
53
+ )
54
+ from flwr.proto.message_pb2 import (
55
+ ConfirmMessageReceivedRequest,
56
+ ConfirmMessageReceivedResponse,
57
+ PullObjectRequest,
58
+ PullObjectResponse,
59
+ PushObjectRequest,
60
+ PushObjectResponse,
61
+ )
62
+ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
63
+
64
+ # pylint: disable=E0601
65
+ from flwr.supercore.ffs import FfsFactory
66
+ from flwr.supercore.object_store import NoObjectInStoreError, ObjectStoreFactory
67
+ from flwr.supercore.object_store.utils import store_mapping_and_register_objects
68
+ from flwr.supernode.nodestate import NodeStateFactory
69
+
70
+
71
+ # pylint: disable=C0103,W0613,W0201
72
+ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
73
+ """ClientAppIo API servicer."""
74
+
75
+ def __init__(
76
+ self,
77
+ state_factory: NodeStateFactory,
78
+ ffs_factory: FfsFactory,
79
+ objectstore_factory: ObjectStoreFactory,
80
+ ) -> None:
81
+ self.state_factory = state_factory
82
+ self.ffs_factory = ffs_factory
83
+ self.objectstore_factory = objectstore_factory
84
+
85
+ def GetRunIdsWithPendingMessages(
86
+ self,
87
+ request: GetRunIdsWithPendingMessagesRequest,
88
+ context: grpc.ServicerContext,
89
+ ) -> GetRunIdsWithPendingMessagesResponse:
90
+ """Get run IDs with pending messages."""
91
+ log(DEBUG, "ClientAppIo.GetRunIdsWithPendingMessages")
92
+
93
+ # Initialize state connection
94
+ state = self.state_factory.state()
95
+
96
+ # Get run IDs with pending messages
97
+ run_ids = state.get_run_ids_with_pending_messages()
98
+
99
+ # Return run IDs
100
+ return GetRunIdsWithPendingMessagesResponse(run_ids=run_ids)
101
+
102
+ def RequestToken(
103
+ self, request: RequestTokenRequest, context: grpc.ServicerContext
104
+ ) -> RequestTokenResponse:
105
+ """Request token."""
106
+ log(DEBUG, "ClientAppIo.RequestToken")
107
+
108
+ # Initialize state connection
109
+ state = self.state_factory.state()
110
+
111
+ # Attempt to create a token for the provided run ID
112
+ try:
113
+ token = state.create_token(request.run_id)
114
+ except ValueError:
115
+ # Return an empty token if A token already exists for this run ID,
116
+ # indicating the run is in progress
117
+ return RequestTokenResponse(token="")
118
+
119
+ # Return the token
120
+ return RequestTokenResponse(token=token)
121
+
122
+ def GetRun(
123
+ self, request: GetRunRequest, context: grpc.ServicerContext
124
+ ) -> GetRunResponse:
125
+ """Get run information."""
126
+ log(DEBUG, "ClientAppIo.GetRun")
127
+
128
+ # Initialize state connection
129
+ state = self.state_factory.state()
130
+
131
+ # Retrieve run information
132
+ run = state.get_run(request.run_id)
133
+
134
+ if run is None:
135
+ return GetRunResponse()
136
+
137
+ return GetRunResponse(run=run_to_proto(run))
138
+
139
+ def PullClientAppInputs(
140
+ self, request: PullAppInputsRequest, context: grpc.ServicerContext
141
+ ) -> PullAppInputsResponse:
142
+ """Pull Message, Context, and Run."""
143
+ log(DEBUG, "ClientAppIo.PullClientAppInputs")
144
+
145
+ # Initialize state and ffs connection
146
+ state = self.state_factory.state()
147
+ ffs = self.ffs_factory.ffs()
148
+
149
+ # Validate the token
150
+ run_id = state.get_run_id_by_token(request.token)
151
+ if run_id is None or not state.verify_token(run_id, request.token):
152
+ context.abort(
153
+ grpc.StatusCode.PERMISSION_DENIED,
154
+ "Invalid token.",
155
+ )
156
+ raise RuntimeError("This line should never be reached.")
157
+
158
+ # Retrieve context, run and fab for this run
159
+ context = cast(Context, state.get_context(run_id))
160
+ run = cast(Run, state.get_run(run_id))
161
+ fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
162
+
163
+ return PullAppInputsResponse(
164
+ context=context_to_proto(context),
165
+ run=run_to_proto(run),
166
+ fab=fab_to_proto(fab),
167
+ )
168
+
169
+ def PushClientAppOutputs(
170
+ self, request: PushAppOutputsRequest, context: grpc.ServicerContext
171
+ ) -> PushAppOutputsResponse:
172
+ """Push Message and Context."""
173
+ log(DEBUG, "ClientAppIo.PushClientAppOutputs")
174
+
175
+ # Initialize state connection
176
+ state = self.state_factory.state()
177
+
178
+ # Validate the token
179
+ run_id = state.get_run_id_by_token(request.token)
180
+ if run_id is None or not state.verify_token(run_id, request.token):
181
+ context.abort(
182
+ grpc.StatusCode.PERMISSION_DENIED,
183
+ "Invalid token.",
184
+ )
185
+ raise RuntimeError("This line should never be reached.")
186
+
187
+ # Save the context to the state
188
+ state.store_context(context_from_proto(request.context))
189
+
190
+ # Remove the token to make the run eligible for processing
191
+ # A run associated with a token cannot be handled until its token is cleared
192
+ state.delete_token(run_id)
193
+
194
+ return PushAppOutputsResponse()
195
+
196
+ def PullMessage(
197
+ self, request: PullAppMessagesRequest, context: grpc.ServicerContext
198
+ ) -> PullAppMessagesResponse:
199
+ """Pull one Message."""
200
+ # Initialize state and store connection
201
+ state = self.state_factory.state()
202
+ store = self.objectstore_factory.store()
203
+
204
+ # Validate the token
205
+ run_id = state.get_run_id_by_token(request.token)
206
+ if run_id is None or not state.verify_token(run_id, request.token):
207
+ context.abort(
208
+ grpc.StatusCode.PERMISSION_DENIED,
209
+ "Invalid token.",
210
+ )
211
+ raise RuntimeError("This line should never be reached.")
212
+
213
+ # Retrieve message for this run
214
+ message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
215
+
216
+ # Retrieve the object tree for the message
217
+ object_tree = store.get_object_tree(message.metadata.message_id)
218
+
219
+ return PullAppMessagesResponse(
220
+ messages_list=[message_to_proto(message)],
221
+ message_object_trees=[object_tree],
222
+ )
223
+
224
+ def PushMessage(
225
+ self, request: PushAppMessagesRequest, context: grpc.ServicerContext
226
+ ) -> PushAppMessagesResponse:
227
+ """Push one Message."""
228
+ # Initialize state and store connection
229
+ state = self.state_factory.state()
230
+ store = self.objectstore_factory.store()
231
+
232
+ # Validate the token
233
+ run_id = state.get_run_id_by_token(request.token)
234
+ if run_id is None or not state.verify_token(run_id, request.token):
235
+ context.abort(
236
+ grpc.StatusCode.PERMISSION_DENIED,
237
+ "Invalid token.",
238
+ )
239
+ raise RuntimeError("This line should never be reached.")
240
+
241
+ # Save the message to the state
242
+ state.store_message(message_from_proto(request.messages_list[0]))
243
+
244
+ # Store Message object to descendants mapping and preregister objects
245
+ objects_to_push = store_mapping_and_register_objects(store, request=request)
246
+
247
+ return PushAppMessagesResponse(objects_to_push=objects_to_push)
248
+
249
+ def PushObject(
250
+ self, request: PushObjectRequest, context: grpc.ServicerContext
251
+ ) -> PushObjectResponse:
252
+ """Push an object to the ObjectStore."""
253
+ log(DEBUG, "ServerAppIoServicer.PushObject")
254
+
255
+ # Init state and store
256
+ store = self.objectstore_factory.store()
257
+
258
+ # Insert in store
259
+ stored = False
260
+ try:
261
+ store.put(request.object_id, request.object_content)
262
+ stored = True
263
+ except (NoObjectInStoreError, ValueError) as e:
264
+ log(ERROR, str(e))
265
+ except UnexpectedObjectContentError as e:
266
+ # Object content is not valid
267
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, str(e))
268
+
269
+ return PushObjectResponse(stored=stored)
270
+
271
+ def PullObject(
272
+ self, request: PullObjectRequest, context: grpc.ServicerContext
273
+ ) -> PullObjectResponse:
274
+ """Pull an object from the ObjectStore."""
275
+ log(DEBUG, "ServerAppIoServicer.PullObject")
276
+
277
+ # Init state and store
278
+ store = self.objectstore_factory.store()
279
+
280
+ # Fetch from store
281
+ content = store.get(request.object_id)
282
+ if content is not None:
283
+ object_available = content != b""
284
+ return PullObjectResponse(
285
+ object_found=True,
286
+ object_available=object_available,
287
+ object_content=content,
288
+ )
289
+ return PullObjectResponse(object_found=False, object_available=False)
290
+
291
+ def ConfirmMessageReceived(
292
+ self, request: ConfirmMessageReceivedRequest, context: grpc.ServicerContext
293
+ ) -> ConfirmMessageReceivedResponse:
294
+ """Confirm message received."""
295
+ log(DEBUG, "ServerAppIoServicer.ConfirmMessageReceived")
296
+
297
+ # Init state and store
298
+ store = self.objectstore_factory.store()
299
+
300
+ # Delete the message object
301
+ store.delete(request.message_object_id)
302
+
303
+ return ConfirmMessageReceivedResponse()