dex-python-sdk 0.5.0__tar.gz → 0.7.0__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 (63) hide show
  1. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/PKG-INFO +28 -9
  2. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/README.md +27 -8
  3. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/__init__.py +8 -3
  4. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_grpc_errors.py +38 -11
  5. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/async_client.py +90 -440
  6. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/client.py +91 -440
  7. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/dexpb/dex_pb2.py +22 -22
  8. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/dexpb/dex_pb2.pyi +2 -0
  9. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/dexpb/dex_pb2_grpc.py +18 -18
  10. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/runtime_errors.py +8 -0
  11. dex_python_sdk-0.7.0/dex/wait_options.py +76 -0
  12. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/pyproject.toml +1 -1
  13. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/Cargo.lock +6 -6
  14. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/Cargo.toml +3 -3
  15. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/LEGACY_NOTICES.md +0 -0
  16. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/LICENSE +0 -0
  17. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_async_value_hydrator.py +0 -0
  18. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_async_worker_dispatcher.py +0 -0
  19. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_async_worker_service.py +0 -0
  20. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_invocation_context.py +0 -0
  21. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_native.pyi +0 -0
  22. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_utils.py +0 -0
  23. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_value_hydrator.py +0 -0
  24. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_value_mapper.py +0 -0
  25. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_worker_dispatcher.py +0 -0
  26. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/_worker_service.py +0 -0
  27. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/async_worker.py +0 -0
  28. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/attribute.py +0 -0
  29. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/attribute_match.py +0 -0
  30. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/blob_cache.py +0 -0
  31. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/channel.py +0 -0
  32. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/client_options.py +0 -0
  33. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/codec.py +0 -0
  34. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/condition.py +0 -0
  35. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/context.py +0 -0
  36. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/dexpb/__init__.py +0 -0
  37. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/flow.py +0 -0
  38. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/flow_config.py +0 -0
  39. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/flow_info.py +0 -0
  40. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/flow_options.py +0 -0
  41. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/flow_result.py +0 -0
  42. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/py.typed +0 -0
  43. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/step.py +0 -0
  44. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/step_execution.py +0 -0
  45. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/stream.py +0 -0
  46. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/subflow.py +0 -0
  47. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/timer.py +0 -0
  48. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/wait.py +0 -0
  49. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/worker.py +0 -0
  50. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/dex/worker_options.py +0 -0
  51. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  52. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  53. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  54. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  55. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  56. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  57. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  58. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  59. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  60. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  61. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  62. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  63. {dex_python_sdk-0.5.0 → dex_python_sdk-0.7.0}/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.5.0
3
+ Version: 0.7.0
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
@@ -17,13 +17,12 @@ Project-URL: Repository, https://github.com/superdurable/dex
17
17
 
18
18
  # Dex SDK for Python
19
19
 
20
- ## Pending Channel messages
20
+ ## Flow state I/O
21
21
 
22
- `Client.get_channel_messages(flow_id, channel)` returns typed `ChannelMessage`
23
- envelopes in FIFO order. Each envelope contains the decoded value and the UUIDv7
24
- assigned by Dex. `Client.delete_channel_message` deletes only a still-pending
25
- message and raises `ChannelMessageNotFoundError` after consumption or another
26
- deletion.
22
+ Applications read and write Flow state through typed RPCs. Client and AsyncClient
23
+ do not expose direct Attribute reads or writes, Channel publication, or
24
+ pending-message mutation. This keeps each external state transition behind a
25
+ Flow-owned method.
27
26
 
28
27
  RPC handlers can stage `channel.delete(context, message_id)`. Declare the RPC as
29
28
  `@rpc(is_transactional=True)` when a missing message must abort its other writes.
@@ -279,6 +278,15 @@ message = client.read_stream(flow_id, progress)
279
278
  print(message.source)
280
279
  ```
281
280
 
281
+ `Client.read_stream` and `AsyncClient.read_stream` move forward one message at
282
+ a time and can long-poll. Their `list_stream_messages` methods return one
283
+ non-blocking newest-first retained page. Pass the registered `Stream`, a
284
+ positive page size, and an empty token for the first page. Pass
285
+ `StreamMessagesPage.next_page_token` unchanged to read older messages until it
286
+ is empty. The server caps page size at 1000 by default. A trimmed anchor returns
287
+ an empty page, and newer concurrent writes do not enter an existing older-page
288
+ chain.
289
+
282
290
  ### Canceling Step executions
283
291
 
284
292
  A successful Step can cancel queued or active executions while continuing with
@@ -353,6 +361,17 @@ buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
353
361
  include buffered publishes, and omit empty instances. Keys are decoded and
354
362
  sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
355
363
 
364
+ Request IDs are optional for both durable waits. When omitted, the server
365
+ derives a namespaced stable ID from the Step execution or Attribute condition,
366
+ such as `wait-for-attribute:myInt>10`. Reuse an override only for the same
367
+ logical wait. The Client automatically reattaches transport long polls. If an
368
+ earlier Update with that ID exhausted its handler budget, the server appends an
369
+ increasing `-N` suffix and starts a new Update. `maximum_wait_time` is optional
370
+ and is the total handler budget across reattachments; zero waits indefinitely.
371
+ A positive budget expiry raises `WaitHandlerTimeoutError`. An abandoned infinite
372
+ wait remains accepted and counts against Temporal's in-flight Update limit until
373
+ it completes or the Flow closes.
374
+
356
375
  `Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
357
376
  `FlowResult` after hydrating every output-bearing completion. Use
358
377
  `single_output` only when the Flow contract produces exactly one output:
@@ -392,14 +411,14 @@ SubFlow.
392
411
  ### Errors
393
412
 
394
413
  Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
395
- (`get_attribute`, `describe_flow`, `wait_for_flow`, and `time_travel`) raise
414
+ (`describe_flow`, `wait_for_flow`, and `time_travel`) raise
396
415
  `FlowNotFoundError` when the Flow does not exist. Mutations, RPCs, timer/Step
397
416
  waits, config updates, and continue-as-new triggers raise
398
417
  `FlowNotActiveError` when no running Flow can accept the operation.
399
418
 
400
419
  ```python
401
420
  try:
402
- client.publish(flow_id, orders.approved, order_id)
421
+ client.invoke_rpc(orders.update_order, order_id, update)
403
422
  except dex.FlowNotActiveError:
404
423
  # The Flow is missing or already closed.
405
424
  pass
@@ -1,13 +1,12 @@
1
1
 
2
2
  # Dex SDK for Python
3
3
 
4
- ## Pending Channel messages
4
+ ## Flow state I/O
5
5
 
6
- `Client.get_channel_messages(flow_id, channel)` returns typed `ChannelMessage`
7
- envelopes in FIFO order. Each envelope contains the decoded value and the UUIDv7
8
- assigned by Dex. `Client.delete_channel_message` deletes only a still-pending
9
- message and raises `ChannelMessageNotFoundError` after consumption or another
10
- deletion.
6
+ Applications read and write Flow state through typed RPCs. Client and AsyncClient
7
+ do not expose direct Attribute reads or writes, Channel publication, or
8
+ pending-message mutation. This keeps each external state transition behind a
9
+ Flow-owned method.
11
10
 
12
11
  RPC handlers can stage `channel.delete(context, message_id)`. Declare the RPC as
13
12
  `@rpc(is_transactional=True)` when a missing message must abort its other writes.
@@ -263,6 +262,15 @@ message = client.read_stream(flow_id, progress)
263
262
  print(message.source)
264
263
  ```
265
264
 
265
+ `Client.read_stream` and `AsyncClient.read_stream` move forward one message at
266
+ a time and can long-poll. Their `list_stream_messages` methods return one
267
+ non-blocking newest-first retained page. Pass the registered `Stream`, a
268
+ positive page size, and an empty token for the first page. Pass
269
+ `StreamMessagesPage.next_page_token` unchanged to read older messages until it
270
+ is empty. The server caps page size at 1000 by default. A trimmed anchor returns
271
+ an empty page, and newer concurrent writes do not enter an existing older-page
272
+ chain.
273
+
266
274
  ### Canceling Step executions
267
275
 
268
276
  A successful Step can cancel queued or active executions while continuing with
@@ -337,6 +345,17 @@ buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
337
345
  include buffered publishes, and omit empty instances. Keys are decoded and
338
346
  sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
339
347
 
348
+ Request IDs are optional for both durable waits. When omitted, the server
349
+ derives a namespaced stable ID from the Step execution or Attribute condition,
350
+ such as `wait-for-attribute:myInt>10`. Reuse an override only for the same
351
+ logical wait. The Client automatically reattaches transport long polls. If an
352
+ earlier Update with that ID exhausted its handler budget, the server appends an
353
+ increasing `-N` suffix and starts a new Update. `maximum_wait_time` is optional
354
+ and is the total handler budget across reattachments; zero waits indefinitely.
355
+ A positive budget expiry raises `WaitHandlerTimeoutError`. An abandoned infinite
356
+ wait remains accepted and counts against Temporal's in-flight Update limit until
357
+ it completes or the Flow closes.
358
+
340
359
  `Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
341
360
  `FlowResult` after hydrating every output-bearing completion. Use
342
361
  `single_output` only when the Flow contract produces exactly one output:
@@ -376,14 +395,14 @@ SubFlow.
376
395
  ### Errors
377
396
 
378
397
  Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
379
- (`get_attribute`, `describe_flow`, `wait_for_flow`, and `time_travel`) raise
398
+ (`describe_flow`, `wait_for_flow`, and `time_travel`) raise
380
399
  `FlowNotFoundError` when the Flow does not exist. Mutations, RPCs, timer/Step
381
400
  waits, config updates, and continue-as-new triggers raise
382
401
  `FlowNotActiveError` when no running Flow can accept the operation.
383
402
 
384
403
  ```python
385
404
  try:
386
- client.publish(flow_id, orders.approved, order_id)
405
+ client.invoke_rpc(orders.update_order, order_id, update)
387
406
  except dex.FlowNotActiveError:
388
407
  # The Flow is missing or already closed.
389
408
  pass
@@ -62,10 +62,11 @@ from dex.flow_options import (
62
62
  )
63
63
  from dex.flow_result import FlowResult, StepCompletion
64
64
  from dex.runtime_errors import (
65
- DexServiceError,
66
- ErrorSubStatus,
67
65
  AttributeMapNotLoadedError,
66
+ ChannelMessageNotFoundError,
68
67
  ChannelMessagesNotLoadedError,
68
+ DexServiceError,
69
+ ErrorSubStatus,
69
70
  FlowAlreadyStartedError,
70
71
  FlowDefinitionError,
71
72
  FlowErrorType,
@@ -73,9 +74,9 @@ from dex.runtime_errors import (
73
74
  FlowNotFoundError,
74
75
  InvalidStepResultError,
75
76
  LongPollTimeoutError,
76
- ChannelMessageNotFoundError,
77
77
  RpcLockConflictError,
78
78
  ValueMappingError,
79
+ WaitHandlerTimeoutError,
79
80
  WorkerInvocationError,
80
81
  )
81
82
  from dex.step import (
@@ -108,6 +109,7 @@ from dex.stream import (
108
109
  from dex.subflow import SubFlow
109
110
  from dex.timer import Timer
110
111
  from dex.wait import Wait
112
+ from dex.wait_options import WaitForAttributeOptions, WaitForStepCompletionOptions
111
113
  from dex.worker import Worker
112
114
  from dex.worker_options import WorkerOptions, WorkerTarget
113
115
 
@@ -164,6 +166,7 @@ __all__ = [
164
166
  "IndexType",
165
167
  "JsonCodec",
166
168
  "LongPollTimeoutError",
169
+ "WaitHandlerTimeoutError",
167
170
  "PersistenceSchema",
168
171
  "RPCResult",
169
172
  "RecoveryErrorInfo",
@@ -205,6 +208,8 @@ __all__ = [
205
208
  "WorkerInvocationError",
206
209
  "WorkerOptions",
207
210
  "WorkerTarget",
211
+ "WaitForAttributeOptions",
212
+ "WaitForStepCompletionOptions",
208
213
  "dead_end",
209
214
  "force_complete",
210
215
  "force_complete_if_channels_empty",
@@ -27,12 +27,17 @@ from dex.runtime_errors import (
27
27
  FlowNotFoundError,
28
28
  LongPollTimeoutError,
29
29
  RpcLockConflictError,
30
+ WaitHandlerTimeoutError,
30
31
  WorkerInvocationError,
31
32
  )
32
33
 
33
34
  FlowTargetRequirement = Literal["none", "existing", "active"]
34
35
 
35
- MAX_WORKER_STACK_TRACE_BYTES = 16 * 1024
36
+ MAX_WORKER_ERROR_DETAIL_BYTES = 1024
37
+ MAX_WORKER_ERROR_TYPE_BYTES = 256
38
+ MAX_WORKER_STACK_TRACE_BYTES = 4 * 1024
39
+ _ERROR_DETAIL_TRUNCATION_MARKER = b"\n... error detail truncated by Dex Python SDK ..."
40
+ _ERROR_TYPE_TRUNCATION_MARKER = b"\n... error type truncated by Dex Python SDK ..."
36
41
  _STACK_TRACE_TRUNCATION_MARKER = b"\n... stack trace truncated by Dex Python SDK ..."
37
42
 
38
43
 
@@ -139,6 +144,8 @@ def translate_rpc_error(
139
144
  return LongPollTimeoutError(*parameters)
140
145
  if sub_status is ErrorSubStatus.CHANNEL_MESSAGE_NOT_FOUND:
141
146
  return ChannelMessageNotFoundError(*parameters)
147
+ if sub_status is ErrorSubStatus.WAIT_HANDLER_TIMEOUT:
148
+ return WaitHandlerTimeoutError(*parameters)
142
149
  return DexServiceError(*parameters)
143
150
 
144
151
 
@@ -179,11 +186,19 @@ def _worker_error_status(error: BaseException) -> status_pb2.Status:
179
186
  retry_after_error = error
180
187
  reported = error.cause
181
188
 
182
- message = str(reported) or type(reported).__name__
189
+ message = _truncate_worker_failure_field(
190
+ str(reported) or type(reported).__name__,
191
+ MAX_WORKER_ERROR_DETAIL_BYTES,
192
+ _ERROR_DETAIL_TRUNCATION_MARKER,
193
+ )
183
194
  stack_trace_source = error if retry_after_error is not None else reported
184
195
  worker_error = pb.WorkerErrorResponse(
185
196
  detail=message,
186
- error_type=f"{type(reported).__module__}.{type(reported).__qualname__}",
197
+ error_type=_truncate_worker_failure_field(
198
+ f"{type(reported).__module__}.{type(reported).__qualname__}",
199
+ MAX_WORKER_ERROR_TYPE_BYTES,
200
+ _ERROR_TYPE_TRUNCATION_MARKER,
201
+ ),
187
202
  stack_trace=_worker_stack_trace(stack_trace_source),
188
203
  )
189
204
  if retry_after_error is not None:
@@ -201,16 +216,25 @@ def _worker_stack_trace(error: BaseException) -> str:
201
216
  if error.__traceback__ is None:
202
217
  return ""
203
218
  lines = traceback.format_exception(type(error), error, error.__traceback__)
204
- encoded = "".join(lines).encode()
205
- if len(encoded) <= MAX_WORKER_STACK_TRACE_BYTES:
206
- return encoded.decode()
207
- prefix_length = MAX_WORKER_STACK_TRACE_BYTES - len(_STACK_TRACE_TRUNCATION_MARKER)
219
+ return _truncate_worker_failure_field(
220
+ "".join(lines),
221
+ MAX_WORKER_STACK_TRACE_BYTES,
222
+ _STACK_TRACE_TRUNCATION_MARKER,
223
+ )
224
+
225
+
226
+ def _truncate_worker_failure_field(
227
+ value: str,
228
+ maximum_bytes: int,
229
+ truncation_marker: bytes,
230
+ ) -> str:
231
+ encoded = value.encode()
232
+ if len(encoded) <= maximum_bytes:
233
+ return value
234
+ prefix_length = maximum_bytes - len(truncation_marker)
208
235
  while prefix_length > 0 and (encoded[prefix_length] & 0xC0) == 0x80:
209
236
  prefix_length -= 1
210
- return (
211
- encoded[:prefix_length].decode(errors="replace")
212
- + _STACK_TRACE_TRUNCATION_MARKER.decode()
213
- )
237
+ return encoded[:prefix_length].decode() + truncation_marker.decode()
214
238
 
215
239
 
216
240
  def _map_sub_status(value: int) -> ErrorSubStatus:
@@ -225,5 +249,8 @@ def _map_sub_status(value: int) -> ErrorSubStatus:
225
249
  int(
226
250
  pb.ERROR_SUB_STATUS_CHANNEL_MESSAGE_NOT_FOUND
227
251
  ): ErrorSubStatus.CHANNEL_MESSAGE_NOT_FOUND,
252
+ int(
253
+ pb.ERROR_SUB_STATUS_WAIT_HANDLER_TIME_OUT
254
+ ): ErrorSubStatus.WAIT_HANDLER_TIMEOUT,
228
255
  }
229
256
  return statuses.get(value, ErrorSubStatus.UNCATEGORIZED)