flwr 1.19.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 (94) hide show
  1. flwr/cli/build.py +15 -5
  2. flwr/cli/new/new.py +12 -4
  3. flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
  4. flwr/cli/new/templates/app/README.md.tpl +5 -0
  5. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -3
  6. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
  7. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
  8. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
  9. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
  10. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
  11. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
  12. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
  13. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
  14. flwr/cli/run/run.py +45 -38
  15. flwr/cli/utils.py +12 -5
  16. flwr/client/grpc_adapter_client/connection.py +11 -4
  17. flwr/client/grpc_rere_client/connection.py +92 -117
  18. flwr/client/rest_client/connection.py +131 -164
  19. flwr/common/constant.py +3 -1
  20. flwr/common/exit/exit_code.py +16 -1
  21. flwr/common/grpc.py +12 -1
  22. flwr/common/{inflatable_grpc_utils.py → inflatable_protobuf_utils.py} +52 -10
  23. flwr/common/inflatable_utils.py +191 -24
  24. flwr/common/record/array.py +101 -22
  25. flwr/common/record/arraychunk.py +59 -0
  26. flwr/common/serde.py +0 -28
  27. flwr/compat/client/app.py +14 -31
  28. flwr/proto/appio_pb2.py +43 -0
  29. flwr/proto/appio_pb2.pyi +151 -0
  30. flwr/proto/appio_pb2_grpc.py +4 -0
  31. flwr/proto/appio_pb2_grpc.pyi +4 -0
  32. flwr/proto/clientappio_pb2.py +12 -19
  33. flwr/proto/clientappio_pb2.pyi +23 -101
  34. flwr/proto/clientappio_pb2_grpc.py +269 -28
  35. flwr/proto/clientappio_pb2_grpc.pyi +114 -20
  36. flwr/proto/fleet_pb2.py +12 -20
  37. flwr/proto/fleet_pb2.pyi +6 -36
  38. flwr/proto/serverappio_pb2.py +8 -31
  39. flwr/proto/serverappio_pb2.pyi +0 -152
  40. flwr/proto/serverappio_pb2_grpc.py +39 -38
  41. flwr/proto/serverappio_pb2_grpc.pyi +21 -20
  42. flwr/server/app.py +1 -1
  43. flwr/server/fleet_event_log_interceptor.py +4 -0
  44. flwr/server/grid/grpc_grid.py +91 -54
  45. flwr/server/serverapp/app.py +27 -17
  46. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +8 -0
  47. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
  48. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
  49. flwr/server/superlink/fleet/message_handler/message_handler.py +10 -16
  50. flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -2
  51. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -1
  52. flwr/server/superlink/serverappio/serverappio_servicer.py +35 -43
  53. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  54. flwr/server/superlink/simulation/simulationio_servicer.py +1 -1
  55. flwr/server/superlink/utils.py +0 -35
  56. flwr/simulation/app.py +8 -0
  57. flwr/simulation/run_simulation.py +17 -0
  58. flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
  59. flwr/supercore/grpc_health/__init__.py +22 -0
  60. flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
  61. flwr/supercore/license_plugin/__init__.py +22 -0
  62. flwr/supercore/license_plugin/license_plugin.py +26 -0
  63. flwr/supercore/object_store/in_memory_object_store.py +31 -31
  64. flwr/supercore/object_store/object_store.py +20 -42
  65. flwr/supercore/object_store/utils.py +43 -0
  66. flwr/supercore/scheduler/__init__.py +22 -0
  67. flwr/supercore/scheduler/plugin.py +71 -0
  68. flwr/supercore/utils.py +32 -0
  69. flwr/superexec/deployment.py +1 -2
  70. flwr/superexec/exec_event_log_interceptor.py +4 -0
  71. flwr/superexec/exec_grpc.py +18 -2
  72. flwr/superexec/exec_license_interceptor.py +82 -0
  73. flwr/superexec/exec_servicer.py +10 -1
  74. flwr/superexec/exec_user_auth_interceptor.py +10 -2
  75. flwr/superexec/executor.py +1 -1
  76. flwr/superexec/simulation.py +1 -2
  77. flwr/supernode/cli/flower_supernode.py +0 -7
  78. flwr/supernode/cli/flwr_clientapp.py +10 -3
  79. flwr/supernode/nodestate/in_memory_nodestate.py +11 -2
  80. flwr/supernode/nodestate/nodestate.py +15 -0
  81. flwr/supernode/runtime/run_clientapp.py +110 -33
  82. flwr/supernode/scheduler/__init__.py +22 -0
  83. flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
  84. flwr/supernode/servicer/clientappio/__init__.py +1 -3
  85. flwr/supernode/servicer/clientappio/clientappio_servicer.py +223 -164
  86. flwr/supernode/start_client_internal.py +202 -104
  87. {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/METADATA +2 -1
  88. {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/RECORD +93 -78
  89. flwr/common/inflatable_rest_utils.py +0 -99
  90. /flwr/{server/superlink → supercore}/ffs/__init__.py +0 -0
  91. /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
  92. /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
  93. {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +0 -0
  94. {flwr-1.19.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +0 -0
@@ -15,16 +15,15 @@
15
15
  """ClientAppIo API servicer."""
16
16
 
17
17
 
18
- from dataclasses import dataclass
19
18
  from logging import DEBUG, ERROR
20
- from typing import Optional, cast
19
+ from typing import cast
21
20
 
22
21
  import grpc
23
22
 
24
- from flwr.common import Context, Message, typing
23
+ from flwr.common import Context
24
+ from flwr.common.inflatable import UnexpectedObjectContentError
25
25
  from flwr.common.logger import log
26
26
  from flwr.common.serde import (
27
- clientappstatus_to_proto,
28
27
  context_from_proto,
29
28
  context_to_proto,
30
29
  fab_to_proto,
@@ -36,209 +35,269 @@ from flwr.common.typing import Fab, Run
36
35
 
37
36
  # pylint: disable=E0611
38
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
+ )
39
48
  from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
40
- GetTokenRequest,
41
- GetTokenResponse,
42
- PullClientAppInputsRequest,
43
- PullClientAppInputsResponse,
44
- PushClientAppOutputsRequest,
45
- PushClientAppOutputsResponse,
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,
46
61
  )
62
+ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
47
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
48
69
 
49
- @dataclass
50
- class ClientAppInputs:
51
- """Specify the inputs to the ClientApp."""
52
70
 
53
- message: Message
54
- context: Context
55
- run: Run
56
- fab: Optional[Fab]
57
- token: int
71
+ # pylint: disable=C0103,W0613,W0201
72
+ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
73
+ """ClientAppIo API servicer."""
58
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
59
84
 
60
- @dataclass
61
- class ClientAppOutputs:
62
- """Specify the outputs from the ClientApp."""
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")
63
92
 
64
- message: Message
65
- context: Context
93
+ # Initialize state connection
94
+ state = self.state_factory.state()
66
95
 
96
+ # Get run IDs with pending messages
97
+ run_ids = state.get_run_ids_with_pending_messages()
67
98
 
68
- # pylint: disable=C0103,W0613,W0201
69
- class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
70
- """ClientAppIo API servicer."""
99
+ # Return run IDs
100
+ return GetRunIdsWithPendingMessagesResponse(run_ids=run_ids)
71
101
 
72
- def __init__(self) -> None:
73
- self.clientapp_input: Optional[ClientAppInputs] = None
74
- self.clientapp_output: Optional[ClientAppOutputs] = None
75
- self.token_returned: bool = False
76
- self.inputs_returned: bool = False
102
+ def RequestToken(
103
+ self, request: RequestTokenRequest, context: grpc.ServicerContext
104
+ ) -> RequestTokenResponse:
105
+ """Request token."""
106
+ log(DEBUG, "ClientAppIo.RequestToken")
77
107
 
78
- def GetToken(
79
- self, request: GetTokenRequest, context: grpc.ServicerContext
80
- ) -> GetTokenResponse:
81
- """Get token."""
82
- log(DEBUG, "ClientAppIo.GetToken")
108
+ # Initialize state connection
109
+ state = self.state_factory.state()
83
110
 
84
- # Fail if no ClientAppInputs are available
85
- if self.clientapp_input is None:
86
- context.abort(
87
- grpc.StatusCode.FAILED_PRECONDITION,
88
- "No inputs available.",
89
- )
90
- clientapp_input = cast(ClientAppInputs, self.clientapp_input)
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="")
91
118
 
92
- # Fail if token was already returned in a previous call
93
- if self.token_returned:
94
- context.abort(
95
- grpc.StatusCode.FAILED_PRECONDITION,
96
- "Token already returned. A token can be returned only once.",
97
- )
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")
98
127
 
99
- # If
100
- # - ClientAppInputs is set, and
101
- # - token hasn't been returned before,
102
- # return token
103
- self.token_returned = True
104
- return GetTokenResponse(token=clientapp_input.token)
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))
105
138
 
106
139
  def PullClientAppInputs(
107
- self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
108
- ) -> PullClientAppInputsResponse:
140
+ self, request: PullAppInputsRequest, context: grpc.ServicerContext
141
+ ) -> PullAppInputsResponse:
109
142
  """Pull Message, Context, and Run."""
110
143
  log(DEBUG, "ClientAppIo.PullClientAppInputs")
111
144
 
112
- # Fail if no ClientAppInputs are available
113
- if self.clientapp_input is None:
114
- context.abort(
115
- grpc.StatusCode.FAILED_PRECONDITION,
116
- "No inputs available.",
117
- )
118
- clientapp_input = cast(ClientAppInputs, self.clientapp_input)
145
+ # Initialize state and ffs connection
146
+ state = self.state_factory.state()
147
+ ffs = self.ffs_factory.ffs()
119
148
 
120
- # Fail if token wasn't returned in a previous call
121
- if not self.token_returned:
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):
122
152
  context.abort(
123
- grpc.StatusCode.FAILED_PRECONDITION,
124
- "Token hasn't been returned."
125
- "Token must be returned before can be returned only once.",
153
+ grpc.StatusCode.PERMISSION_DENIED,
154
+ "Invalid token.",
126
155
  )
156
+ raise RuntimeError("This line should never be reached.")
127
157
 
128
- # Fail if token isn't matching
129
- if request.token != clientapp_input.token:
130
- context.abort(
131
- grpc.StatusCode.INVALID_ARGUMENT,
132
- "Mismatch between ClientApp and SuperNode token",
133
- )
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
134
162
 
135
- # Success
136
- self.inputs_returned = True
137
- return PullClientAppInputsResponse(
138
- message=message_to_proto(clientapp_input.message),
139
- context=context_to_proto(clientapp_input.context),
140
- run=run_to_proto(clientapp_input.run),
141
- fab=fab_to_proto(clientapp_input.fab) if clientapp_input.fab else None,
163
+ return PullAppInputsResponse(
164
+ context=context_to_proto(context),
165
+ run=run_to_proto(run),
166
+ fab=fab_to_proto(fab),
142
167
  )
143
168
 
144
169
  def PushClientAppOutputs(
145
- self, request: PushClientAppOutputsRequest, context: grpc.ServicerContext
146
- ) -> PushClientAppOutputsResponse:
170
+ self, request: PushAppOutputsRequest, context: grpc.ServicerContext
171
+ ) -> PushAppOutputsResponse:
147
172
  """Push Message and Context."""
148
173
  log(DEBUG, "ClientAppIo.PushClientAppOutputs")
149
174
 
150
- # Fail if no ClientAppInputs are available
151
- if not self.clientapp_input:
152
- context.abort(
153
- grpc.StatusCode.FAILED_PRECONDITION,
154
- "No inputs available.",
155
- )
156
- clientapp_input = cast(ClientAppInputs, self.clientapp_input)
175
+ # Initialize state connection
176
+ state = self.state_factory.state()
157
177
 
158
- # Fail if token wasn't returned in a previous call
159
- if not self.token_returned:
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):
160
181
  context.abort(
161
- grpc.StatusCode.FAILED_PRECONDITION,
162
- "Token hasn't been returned."
163
- "Token must be returned before can be returned only once.",
182
+ grpc.StatusCode.PERMISSION_DENIED,
183
+ "Invalid token.",
164
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))
165
189
 
166
- # Fail if inputs weren't delivered in a previous call
167
- if not self.inputs_returned:
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):
168
207
  context.abort(
169
- grpc.StatusCode.FAILED_PRECONDITION,
170
- "Inputs haven't been delivered."
171
- "Inputs must be delivered before can be returned only once.",
208
+ grpc.StatusCode.PERMISSION_DENIED,
209
+ "Invalid token.",
172
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)
173
218
 
174
- # Fail if token isn't matching
175
- if request.token != clientapp_input.token:
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):
176
235
  context.abort(
177
- grpc.StatusCode.INVALID_ARGUMENT,
178
- "Mismatch between ClientApp and SuperNode token",
236
+ grpc.StatusCode.PERMISSION_DENIED,
237
+ "Invalid token.",
179
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)
180
248
 
181
- # Preconditions met
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
182
260
  try:
183
- # Update Message and Context
184
- self.clientapp_output = ClientAppOutputs(
185
- message=message_from_proto(request.message),
186
- context=context_from_proto(request.context),
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,
187
288
  )
289
+ return PullObjectResponse(object_found=False, object_available=False)
188
290
 
189
- # Set status
190
- code = typing.ClientAppOutputCode.SUCCESS
191
- status = typing.ClientAppOutputStatus(code=code, message="Success")
192
- except Exception as e: # pylint: disable=broad-exception-caught
193
- log(ERROR, "ClientApp failed to push message to SuperNode, %s", e)
194
- code = typing.ClientAppOutputCode.UNKNOWN_ERROR
195
- status = typing.ClientAppOutputStatus(code=code, message="Unkonwn error")
291
+ def ConfirmMessageReceived(
292
+ self, request: ConfirmMessageReceivedRequest, context: grpc.ServicerContext
293
+ ) -> ConfirmMessageReceivedResponse:
294
+ """Confirm message received."""
295
+ log(DEBUG, "ServerAppIoServicer.ConfirmMessageReceived")
196
296
 
197
- # Return status to ClientApp process
198
- proto_status = clientappstatus_to_proto(status=status)
199
- return PushClientAppOutputsResponse(status=proto_status)
297
+ # Init state and store
298
+ store = self.objectstore_factory.store()
200
299
 
201
- def set_inputs(
202
- self, clientapp_input: ClientAppInputs, token_returned: bool
203
- ) -> None:
204
- """Set ClientApp inputs.
205
-
206
- Parameters
207
- ----------
208
- clientapp_input : ClientAppInputs
209
- The inputs to the ClientApp.
210
- token_returned : bool
211
- A boolean indicating if the token has been returned.
212
- Set to `True` when passing the token to `flwr-clientap`
213
- and `False` otherwise.
214
- """
215
- if (
216
- self.clientapp_input is not None
217
- or self.clientapp_output is not None
218
- or self.token_returned
219
- ):
220
- raise ValueError(
221
- "ClientAppInputs and ClientAppOutputs must not be set before "
222
- "calling `set_inputs`."
223
- )
224
- log(DEBUG, "ClientAppInputs set (token: %s)", clientapp_input.token)
225
- self.clientapp_input = clientapp_input
226
- self.token_returned = token_returned
227
-
228
- def has_outputs(self) -> bool:
229
- """Check if ClientAppOutputs are available."""
230
- return self.clientapp_output is not None
231
-
232
- def get_outputs(self) -> ClientAppOutputs:
233
- """Get ClientApp outputs."""
234
- if self.clientapp_output is None:
235
- raise ValueError("ClientAppOutputs not set before calling `get_outputs`.")
236
-
237
- # Set outputs to a local variable and clear state
238
- output: ClientAppOutputs = self.clientapp_output
239
- self.clientapp_input = None
240
- self.clientapp_output = None
241
- self.token_returned = False
242
- self.inputs_returned = False
243
-
244
- return output
300
+ # Delete the message object
301
+ store.delete(request.message_object_id)
302
+
303
+ return ConfirmMessageReceivedResponse()