dex-python-sdk 0.2.10__tar.gz → 0.3.1__tar.gz

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 (62) hide show
  1. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/PKG-INFO +3 -2
  2. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/README.md +2 -1
  3. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/__init__.py +6 -2
  4. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_async_value_hydrator.py +28 -2
  5. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_async_worker_dispatcher.py +61 -8
  6. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_invocation_context.py +32 -28
  7. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_value_hydrator.py +28 -2
  8. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_worker_dispatcher.py +205 -6
  9. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/async_client.py +26 -1
  10. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/channel.py +2 -2
  11. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/client.py +26 -1
  12. dex_python_sdk-0.3.1/dex/dexpb/dex_pb2.py +495 -0
  13. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/dexpb/dex_pb2.pyi +124 -22
  14. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/flow.py +35 -4
  15. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/flow_options.py +69 -3
  16. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/runtime_errors.py +8 -2
  17. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/step.py +26 -2
  18. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/pyproject.toml +1 -1
  19. dex_python_sdk-0.2.10/dex/dexpb/dex_pb2.py +0 -477
  20. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/LEGACY_NOTICES.md +0 -0
  21. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/LICENSE +0 -0
  22. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_async_worker_service.py +0 -0
  23. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_grpc_errors.py +0 -0
  24. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_native.pyi +0 -0
  25. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_utils.py +0 -0
  26. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_value_mapper.py +0 -0
  27. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/_worker_service.py +0 -0
  28. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/async_worker.py +0 -0
  29. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/attribute.py +0 -0
  30. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/blob_cache.py +0 -0
  31. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/client_options.py +0 -0
  32. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/codec.py +0 -0
  33. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/condition.py +0 -0
  34. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/context.py +0 -0
  35. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/dexpb/__init__.py +0 -0
  36. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/dexpb/dex_pb2_grpc.py +0 -0
  37. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/flow_config.py +0 -0
  38. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/flow_info.py +0 -0
  39. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/flow_result.py +0 -0
  40. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/py.typed +0 -0
  41. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/step_execution.py +0 -0
  42. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/stream.py +0 -0
  43. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/subflow.py +0 -0
  44. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/timer.py +0 -0
  45. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/wait.py +0 -0
  46. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/worker.py +0 -0
  47. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/dex/worker_options.py +0 -0
  48. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/Cargo.lock +0 -0
  49. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/Cargo.toml +0 -0
  50. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  51. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  52. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  53. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  54. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  55. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  56. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  57. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  58. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  59. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  60. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  61. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  62. {dex_python_sdk-0.2.10 → dex_python_sdk-0.3.1}/sdk-rust/crates/dex-blob-cache-python/src/lib.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dex-python-sdk
3
- Version: 0.2.10
3
+ Version: 0.3.1
4
4
  Requires-Dist: grpcio>=1.83.0
5
5
  Requires-Dist: grpcio-status>=1.83.0
6
6
  Requires-Dist: protobuf>=7.35.1
@@ -49,7 +49,8 @@ def snapshot(self, context: dex.Context) -> dex.RPCResult[Snapshot]:
49
49
  Put an AttributeMap or ChannelMap directly in its plural load option when every
50
50
  current instance is needed. Use the singular instance options for the less common
51
51
  exact-instance case. A selected empty queue returns an empty tuple; reading an
52
- unselected map entry or pending-message snapshot raises **StateNotLoadedError**.
52
+ unselected map entry raises **AttributeMapNotLoadedError**. An unselected pending-message
53
+ snapshot raises **ChannelMessagesNotLoadedError**.
53
54
  Pending messages preserve FIFO order and include the server-assigned message ID.
54
55
  The snapshot does not change after the handler stages a publish or deletion.
55
56
 
@@ -33,7 +33,8 @@ def snapshot(self, context: dex.Context) -> dex.RPCResult[Snapshot]:
33
33
  Put an AttributeMap or ChannelMap directly in its plural load option when every
34
34
  current instance is needed. Use the singular instance options for the less common
35
35
  exact-instance case. A selected empty queue returns an empty tuple; reading an
36
- unselected map entry or pending-message snapshot raises **StateNotLoadedError**.
36
+ unselected map entry raises **AttributeMapNotLoadedError**. An unselected pending-message
37
+ snapshot raises **ChannelMessagesNotLoadedError**.
37
38
  Pending messages preserve FIFO order and include the server-assigned message ID.
38
39
  The snapshot does not change after the handler stages a publish or deletion.
39
40
 
@@ -47,6 +47,7 @@ from dex.flow_info import (
47
47
  SearchFlowsPage,
48
48
  )
49
49
  from dex.flow_options import (
50
+ FlowTimeoutHandlerOptions,
50
51
  FlowTimeoutPolicy,
51
52
  IdReusePolicy,
52
53
  StartFlowOptions,
@@ -62,6 +63,8 @@ from dex.flow_result import FlowResult, StepCompletion
62
63
  from dex.runtime_errors import (
63
64
  DexServiceError,
64
65
  ErrorSubStatus,
66
+ AttributeMapNotLoadedError,
67
+ ChannelMessagesNotLoadedError,
65
68
  FlowAlreadyStartedError,
66
69
  FlowDefinitionError,
67
70
  FlowErrorType,
@@ -71,7 +74,6 @@ from dex.runtime_errors import (
71
74
  LongPollTimeoutError,
72
75
  ChannelMessageNotFoundError,
73
76
  RpcLockConflictError,
74
- StateNotLoadedError,
75
77
  ValueMappingError,
76
78
  WorkerInvocationError,
77
79
  )
@@ -119,6 +121,7 @@ __all__ = [
119
121
  "AttributeLock",
120
122
  "AttributeMap",
121
123
  "AttributeMapLoad",
124
+ "AttributeMapNotLoadedError",
122
125
  "AsyncContext",
123
126
  "AsyncClient",
124
127
  "AsyncBufferedTextStream",
@@ -131,6 +134,7 @@ __all__ = [
131
134
  "ChannelMapLoad",
132
135
  "ChannelMessage",
133
136
  "ChannelMessageNotFoundError",
137
+ "ChannelMessagesNotLoadedError",
134
138
  "Client",
135
139
  "ClientOptions",
136
140
  "Codec",
@@ -149,6 +153,7 @@ __all__ = [
149
153
  "FlowNotActiveError",
150
154
  "FlowNotFoundError",
151
155
  "FlowStatus",
156
+ "FlowTimeoutHandlerOptions",
152
157
  "FlowTimeoutPolicy",
153
158
  "HealthInfo",
154
159
  "IdReusePolicy",
@@ -160,7 +165,6 @@ __all__ = [
160
165
  "RPCResult",
161
166
  "RetryAfterError",
162
167
  "RpcLockConflictError",
163
- "StateNotLoadedError",
164
168
  "Registry",
165
169
  "TimeTravelOptions",
166
170
  "TimeTravelStepMethod",
@@ -80,12 +80,25 @@ class AsyncValueHydrator:
80
80
  if has_heartbeat:
81
81
  values.append(request.context.last_heartbeat_value)
82
82
  values.extend(entry.value for entry in request.attributes)
83
+ channel_messages = [
84
+ message
85
+ for channel_values in request.loaded_channel_messages.values()
86
+ for message in channel_values.messages
87
+ ]
88
+ values.extend(message.value for message in channel_messages)
83
89
  hydrated = iter(await self.hydrate_all(values))
84
90
  result.step_input.CopyFrom(next(hydrated))
85
91
  if has_heartbeat:
86
92
  result.context.last_heartbeat_value.CopyFrom(next(hydrated))
87
- for entry, value in zip(result.attributes, hydrated):
88
- entry.value.CopyFrom(value)
93
+ for entry in result.attributes:
94
+ entry.value.CopyFrom(next(hydrated))
95
+ result_messages = [
96
+ message
97
+ for channel_values in result.loaded_channel_messages.values()
98
+ for message in channel_values.messages
99
+ ]
100
+ for message in result_messages:
101
+ message.value.CopyFrom(next(hydrated))
89
102
  return result
90
103
 
91
104
  async def execute_request(
@@ -101,6 +114,12 @@ class AsyncValueHydrator:
101
114
  values.append(request.context.last_heartbeat_value)
102
115
  values.extend(entry.value for entry in request.attributes)
103
116
  values.extend(entry.value for entry in request.step_exe_locals)
117
+ channel_messages = [
118
+ message
119
+ for channel_values in request.loaded_channel_messages.values()
120
+ for message in channel_values.messages
121
+ ]
122
+ values.extend(message.value for message in channel_messages)
104
123
  for channel_result in request.condition_results.channel_results:
105
124
  values.extend(channel_result.values)
106
125
  hydrated = iter(await self.hydrate_all(values))
@@ -112,6 +131,13 @@ class AsyncValueHydrator:
112
131
  entry.value.CopyFrom(next(hydrated))
113
132
  for entry in result.step_exe_locals:
114
133
  entry.value.CopyFrom(next(hydrated))
134
+ result_messages = [
135
+ message
136
+ for channel_values in result.loaded_channel_messages.values()
137
+ for message in channel_values.messages
138
+ ]
139
+ for message in result_messages:
140
+ message.value.CopyFrom(next(hydrated))
115
141
  for channel_result in result.condition_results.channel_results:
116
142
  for value in channel_result.values:
117
143
  value.CopyFrom(next(hydrated))
@@ -100,6 +100,11 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
100
100
  request.context,
101
101
  self._values,
102
102
  request.attributes,
103
+ channel_infos=dict(request.channel_infos),
104
+ loaded_channel_messages=dict(request.loaded_channel_messages),
105
+ loaded_attribute_map_instances=request.loaded_attribute_map_instances,
106
+ loaded_channel_names=request.loaded_channel_names,
107
+ loaded_channel_map_instances=request.loaded_channel_map_instances,
103
108
  is_active=is_active,
104
109
  output_emitter=emitter,
105
110
  )
@@ -126,6 +131,7 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
126
131
  upsert_step_exe_locals=list(context.local_writes.values()),
127
132
  record_events=context.events,
128
133
  publish_to_channel=context.publications,
134
+ delete_from_channel=context.channel_deletions,
129
135
  )
130
136
  waiting = self._map_wait(flow, wait)
131
137
  if waiting is not None:
@@ -153,8 +159,16 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
153
159
  is_active: Callable[[], bool] | None = None,
154
160
  ) -> AsyncGenerator[pb.InvokeExecuteMethodOutput, None]:
155
161
  if original.step_type == _TIMEOUT_HANDLER_STEP_TYPE:
156
- response = await self._invoke_timeout_handler_async(original)
157
- yield pb.InvokeExecuteMethodOutput(result=response)
162
+ emitter = _AsyncStepOutputEmitter()
163
+ handler = asyncio.create_task(
164
+ self._invoke_timeout_handler_async(original, emitter, is_active)
165
+ )
166
+ try:
167
+ async for output in self._drain_outputs(emitter, handler):
168
+ yield self._map_execute_output(output)
169
+ yield pb.InvokeExecuteMethodOutput(result=await handler)
170
+ finally:
171
+ await self._close_invocation(emitter, handler)
158
172
  return
159
173
  emitter = _AsyncStepOutputEmitter()
160
174
  handler = asyncio.create_task(
@@ -187,6 +201,11 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
187
201
  request.attributes,
188
202
  request.step_exe_locals,
189
203
  condition_results,
204
+ channel_infos=dict(request.channel_infos),
205
+ loaded_channel_messages=dict(request.loaded_channel_messages),
206
+ loaded_attribute_map_instances=request.loaded_attribute_map_instances,
207
+ loaded_channel_names=request.loaded_channel_names,
208
+ loaded_channel_map_instances=request.loaded_channel_map_instances,
190
209
  is_active=is_active,
191
210
  output_emitter=emitter,
192
211
  )
@@ -214,6 +233,7 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
214
233
  record_events=context.events,
215
234
  upsert_step_exe_locals=list(context.local_writes.values()),
216
235
  publish_to_channel=context.publications,
236
+ delete_from_channel=context.channel_deletions,
217
237
  )
218
238
  except InvalidStepResultError as error:
219
239
  failure = error
@@ -235,6 +255,8 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
235
255
  async def _invoke_timeout_handler_async(
236
256
  self,
237
257
  original: pb.InvokeExecuteMethodRequest,
258
+ emitter: _AsyncStepOutputEmitter,
259
+ is_active: Callable[[], bool] | None,
238
260
  ) -> pb.InvokeExecuteMethodResponse:
239
261
  request = await self._async_hydrator.execute_request(original)
240
262
  flow = self._registry._flow_by_type(request.flow_type)
@@ -260,24 +282,55 @@ class AsyncWorkerDispatcher(WorkerDispatcher):
260
282
  request.attributes,
261
283
  request.step_exe_locals,
262
284
  condition_results,
285
+ channel_infos=dict(request.channel_infos),
286
+ loaded_channel_messages=dict(request.loaded_channel_messages),
287
+ loaded_attribute_map_instances=request.loaded_attribute_map_instances,
288
+ loaded_channel_names=request.loaded_channel_names,
289
+ loaded_channel_map_instances=request.loaded_channel_map_instances,
290
+ is_active=is_active,
291
+ output_emitter=emitter,
263
292
  )
264
- decision: Any = flow.flow.handle_timeout(context)
265
- if isawaitable(decision):
266
- decision = await decision
293
+ response: pb.InvokeExecuteMethodResponse | None = None
294
+ failure: BaseException | None = None
295
+ cause: BaseException | None = None
267
296
  try:
297
+ decision: Any = flow.flow.handle_timeout(context)
298
+ if isgenerator(decision):
299
+ decision.close()
300
+ raise InvalidStepResultError(
301
+ flow.name,
302
+ _TIMEOUT_HANDLER_STEP_TYPE,
303
+ "execute",
304
+ "synchronous generators require Worker",
305
+ )
306
+ if isawaitable(decision):
307
+ decision = await decision
268
308
  if not isinstance(decision, StepDecision):
269
309
  raise TypeError("handle_timeout must return StepDecision")
270
- return pb.InvokeExecuteMethodResponse(
310
+ response = pb.InvokeExecuteMethodResponse(
271
311
  step_decision=self._map_decision(flow, decision),
272
312
  upsert_attributes=list(context.attribute_writes.values()),
273
313
  record_events=context.events,
274
314
  upsert_step_exe_locals=list(context.local_writes.values()),
275
315
  publish_to_channel=context.publications,
316
+ delete_from_channel=context.channel_deletions,
276
317
  )
318
+ except InvalidStepResultError as error:
319
+ failure = error
277
320
  except (TypeError, ValueError) as error:
278
- raise InvalidStepResultError(
321
+ failure = InvalidStepResultError(
279
322
  flow.name, _TIMEOUT_HANDLER_STEP_TYPE, "execute", str(error)
280
- ) from error
323
+ )
324
+ cause = error
325
+ except BaseException as error:
326
+ failure = error
327
+ combined = context._finalize_step_outputs(failure)
328
+ if combined is not None:
329
+ if cause is not None:
330
+ raise combined from cause
331
+ raise combined
332
+ assert response is not None
333
+ return response
281
334
 
282
335
  async def invoke_rpc( # type: ignore[override]
283
336
  self,
@@ -20,7 +20,10 @@ from dex.codec import Codec
20
20
  from dex.dexpb import dex_pb2 as pb
21
21
  from dex.flow import Registry, _RegisteredFlow
22
22
  from dex.flow_result import FlowResult, flow_result_from_proto
23
- from dex.runtime_errors import StateNotLoadedError
23
+ from dex.runtime_errors import (
24
+ AttributeMapNotLoadedError,
25
+ ChannelMessagesNotLoadedError,
26
+ )
24
27
  from dex.step import (
25
28
  _NO_HEARTBEAT_VALUE,
26
29
  StepOutput,
@@ -136,7 +139,12 @@ class InvocationContext:
136
139
 
137
140
  async def heartbeat(self, value: object = _NO_HEARTBEAT_VALUE) -> None:
138
141
  if (
139
- self._method not in (InvocationMethod.WAIT_FOR, InvocationMethod.EXECUTE)
142
+ self._method
143
+ not in (
144
+ InvocationMethod.WAIT_FOR,
145
+ InvocationMethod.EXECUTE,
146
+ InvocationMethod.TIMEOUT,
147
+ )
140
148
  or self._output_emitter is None
141
149
  ):
142
150
  raise ValueError("heartbeat requires an asynchronous Step Context")
@@ -223,7 +231,11 @@ class InvocationContext:
223
231
  definition: Stream[ValueT],
224
232
  value: ValueT,
225
233
  ) -> StepOutput | None:
226
- if self._method not in (InvocationMethod.WAIT_FOR, InvocationMethod.EXECUTE):
234
+ if self._method not in (
235
+ InvocationMethod.WAIT_FOR,
236
+ InvocationMethod.EXECUTE,
237
+ InvocationMethod.TIMEOUT,
238
+ ):
227
239
  raise ValueError("Stream writes require a Step Context")
228
240
  self._require_registered(definition)
229
241
  output = _StreamStepOutput(
@@ -242,7 +254,11 @@ class InvocationContext:
242
254
  return None
243
255
 
244
256
  def _prepare_buffered_stream(self, definition: Stream[object]) -> bool:
245
- if self._method not in (InvocationMethod.WAIT_FOR, InvocationMethod.EXECUTE):
257
+ if self._method not in (
258
+ InvocationMethod.WAIT_FOR,
259
+ InvocationMethod.EXECUTE,
260
+ InvocationMethod.TIMEOUT,
261
+ ):
246
262
  raise ValueError("Buffered Streams require a Step Context")
247
263
  self._require_registered(definition)
248
264
  return self._output_emitter is not None
@@ -354,11 +370,10 @@ class InvocationContext:
354
370
  ),
355
371
  )
356
372
  )
357
- if self._method is InvocationMethod.RPC:
358
- current = self._channel_infos.get(name)
359
- self._channel_infos[name] = pb.ChannelInfo(
360
- size=(current.size if current is not None else 0) + 1
361
- )
373
+ current = self._channel_infos.get(name)
374
+ self._channel_infos[name] = pb.ChannelInfo(
375
+ size=(current.size if current is not None else 0) + 1
376
+ )
362
377
 
363
378
  def _delete_channel_message(
364
379
  self,
@@ -366,8 +381,6 @@ class InvocationContext:
366
381
  instance: str | None,
367
382
  message_id: str,
368
383
  ) -> None:
369
- if self._method is not InvocationMethod.RPC:
370
- raise ValueError("Channel message deletion requires an RPC Context")
371
384
  self._require_registered(definition)
372
385
  name = self._physical_name(definition, instance)
373
386
  self.channel_deletions.append(
@@ -407,8 +420,6 @@ class InvocationContext:
407
420
  definition: ChannelMap[object],
408
421
  ) -> tuple[str, ...]:
409
422
  self._require_registered(definition)
410
- if self._method is not InvocationMethod.RPC:
411
- raise ValueError("ChannelMap introspection requires an RPC invocation")
412
423
  prefix = f"{definition.name}/"
413
424
  return tuple(
414
425
  sorted(
@@ -432,8 +443,6 @@ class InvocationContext:
432
443
  definition: Channel[ValueT] | ChannelMap[ValueT],
433
444
  instance: str | None,
434
445
  ) -> tuple[ChannelMessage[ValueT], ...]:
435
- if self._method is not InvocationMethod.RPC:
436
- raise ValueError("pending Channel messages require an RPC Context")
437
446
  self._require_registered(definition)
438
447
  if isinstance(definition, ChannelMap):
439
448
  channel_name = self._physical_name(definition, instance)
@@ -445,8 +454,8 @@ class InvocationContext:
445
454
  channel_name = self._physical_name(definition, instance)
446
455
  is_loaded = definition.name in self._loaded_channel_names
447
456
  if not is_loaded:
448
- raise StateNotLoadedError(
449
- f"Channel messages were not loaded for RPC: {definition.name}"
457
+ raise ChannelMessagesNotLoadedError(
458
+ f"Channel messages were not loaded for this invocation: {definition.name}"
450
459
  )
451
460
  values = self._loaded_channel_messages.get(channel_name)
452
461
  if values is None:
@@ -486,29 +495,24 @@ class InvocationContext:
486
495
  definition: Attribute[Any] | AttributeMap[Any],
487
496
  instance: str | None,
488
497
  ) -> None:
489
- if self._method is not InvocationMethod.RPC or not isinstance(
490
- definition, AttributeMap
491
- ):
498
+ if not isinstance(definition, AttributeMap):
492
499
  return
493
500
  physical_name = self._physical_name(definition, instance)
494
501
  if (
495
502
  f"{definition.name}/" not in self._loaded_attribute_map_instances
496
503
  and physical_name not in self._loaded_attribute_map_instances
497
504
  ):
498
- raise StateNotLoadedError(
499
- f"AttributeMap instance was not loaded for RPC: {physical_name}"
505
+ raise AttributeMapNotLoadedError(
506
+ f"AttributeMap instance was not loaded for this invocation: {physical_name}"
500
507
  )
501
508
 
502
509
  def _require_attribute_map_all_loaded(
503
510
  self,
504
511
  definition: AttributeMap[object],
505
512
  ) -> None:
506
- if (
507
- self._method is InvocationMethod.RPC
508
- and f"{definition.name}/" not in self._loaded_attribute_map_instances
509
- ):
510
- raise StateNotLoadedError(
511
- f"all AttributeMap instances were not loaded for RPC: {definition.name}"
513
+ if f"{definition.name}/" not in self._loaded_attribute_map_instances:
514
+ raise AttributeMapNotLoadedError(
515
+ f"all AttributeMap instances were not loaded for this invocation: {definition.name}"
512
516
  )
513
517
 
514
518
  def _require_registered(self, definition: _Definition) -> None:
@@ -80,12 +80,25 @@ class ValueHydrator:
80
80
  if has_heartbeat:
81
81
  values.append(request.context.last_heartbeat_value)
82
82
  values.extend(entry.value for entry in request.attributes)
83
+ channel_messages = [
84
+ message
85
+ for channel_values in request.loaded_channel_messages.values()
86
+ for message in channel_values.messages
87
+ ]
88
+ values.extend(message.value for message in channel_messages)
83
89
  hydrated = iter(self.hydrate_all(values))
84
90
  result.step_input.CopyFrom(next(hydrated))
85
91
  if has_heartbeat:
86
92
  result.context.last_heartbeat_value.CopyFrom(next(hydrated))
87
- for entry, value in zip(result.attributes, hydrated):
88
- entry.value.CopyFrom(value)
93
+ for entry in result.attributes:
94
+ entry.value.CopyFrom(next(hydrated))
95
+ result_messages = [
96
+ message
97
+ for channel_values in result.loaded_channel_messages.values()
98
+ for message in channel_values.messages
99
+ ]
100
+ for message in result_messages:
101
+ message.value.CopyFrom(next(hydrated))
89
102
  return result
90
103
 
91
104
  def execute_request(
@@ -101,6 +114,12 @@ class ValueHydrator:
101
114
  values.append(request.context.last_heartbeat_value)
102
115
  values.extend(entry.value for entry in request.attributes)
103
116
  values.extend(entry.value for entry in request.step_exe_locals)
117
+ channel_messages = [
118
+ message
119
+ for channel_values in request.loaded_channel_messages.values()
120
+ for message in channel_values.messages
121
+ ]
122
+ values.extend(message.value for message in channel_messages)
104
123
  for channel_result in request.condition_results.channel_results:
105
124
  values.extend(channel_result.values)
106
125
  for flow_result in request.condition_results.sub_flow_results:
@@ -116,6 +135,13 @@ class ValueHydrator:
116
135
  entry.value.CopyFrom(next(hydrated))
117
136
  for entry in result.step_exe_locals:
118
137
  entry.value.CopyFrom(next(hydrated))
138
+ result_messages = [
139
+ message
140
+ for channel_values in result.loaded_channel_messages.values()
141
+ for message in channel_values.messages
142
+ ]
143
+ for message in result_messages:
144
+ message.value.CopyFrom(next(hydrated))
119
145
  for channel_result in result.condition_results.channel_results:
120
146
  for value in channel_result.values:
121
147
  value.CopyFrom(next(hydrated))