dex-python-sdk 0.12.0__tar.gz → 0.13.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 (66) hide show
  1. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/PKG-INFO +35 -6
  2. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/README.md +34 -5
  3. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/__init__.py +4 -2
  4. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_grpc_errors.py +4 -6
  5. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/async_client.py +99 -32
  6. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/client.py +99 -32
  7. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/dexpb/dex_pb2.py +248 -248
  8. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/dexpb/dex_pb2.pyi +14 -10
  9. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/dexpb/dex_pb2_grpc.py +12 -12
  10. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/flow.py +63 -0
  11. dex_python_sdk-0.13.0/dex/rpc_invoke_options.py +42 -0
  12. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/runtime_errors.py +4 -4
  13. dex_python_sdk-0.13.0/dex/wait_options.py +112 -0
  14. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/pyproject.toml +1 -1
  15. dex_python_sdk-0.12.0/dex/wait_options.py +0 -84
  16. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/LEGACY_NOTICES.md +0 -0
  17. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/LICENSE +0 -0
  18. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_async_value_hydrator.py +0 -0
  19. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_async_worker_dispatcher.py +0 -0
  20. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_async_worker_service.py +0 -0
  21. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_invocation_context.py +0 -0
  22. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_native.pyi +0 -0
  23. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_server_protocol.py +0 -0
  24. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_utils.py +0 -0
  25. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_value_hydrator.py +0 -0
  26. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_value_mapper.py +0 -0
  27. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_worker_dispatcher.py +0 -0
  28. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/_worker_service.py +0 -0
  29. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/async_worker.py +0 -0
  30. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/attribute.py +0 -0
  31. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/attribute_match.py +0 -0
  32. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/blob_cache.py +0 -0
  33. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/channel.py +0 -0
  34. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/client_options.py +0 -0
  35. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/codec.py +0 -0
  36. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/condition.py +0 -0
  37. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/context.py +0 -0
  38. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/dexpb/__init__.py +0 -0
  39. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/flow_config.py +0 -0
  40. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/flow_info.py +0 -0
  41. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/flow_options.py +0 -0
  42. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/flow_result.py +0 -0
  43. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/py.typed +0 -0
  44. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/step.py +0 -0
  45. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/step_execution.py +0 -0
  46. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/stream.py +0 -0
  47. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/subflow.py +0 -0
  48. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/timer.py +0 -0
  49. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/wait.py +0 -0
  50. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/worker.py +0 -0
  51. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/dex/worker_options.py +0 -0
  52. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/Cargo.lock +0 -0
  53. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/Cargo.toml +0 -0
  54. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  55. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  56. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  57. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  58. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  59. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  60. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  61. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  62. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  63. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  64. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  65. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  66. {dex_python_sdk-0.12.0 → dex_python_sdk-0.13.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.12.0
3
+ Version: 0.13.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
@@ -58,6 +58,30 @@ atomic commit and Channel deletion validation. Attribute locks add isolation
58
58
  only among cooperating Steps and RPCs using the same lock. Write-only publish
59
59
  and delete operations do not require loading.
60
60
 
61
+ ### Invocation-time map instances
62
+
63
+ The `@rpc` decorator remains the registration-time contract for timeout,
64
+ transactionality, locks, and fixed state loads. Pass frozen `RPCInvokeOptions`
65
+ through the keyword-only `options` argument when the caller chooses an exact map
66
+ instance at runtime:
67
+
68
+ ```python
69
+ options = dex.RPCInvokeOptions(
70
+ lock_attribute_map_instances=(profiles.lock(partition_name),),
71
+ load_attribute_map_instances=(profiles.load(partition_name),),
72
+ )
73
+ result = client.invoke_rpc(
74
+ directory.upsert_customer_profile,
75
+ flow_id,
76
+ profile,
77
+ options=options,
78
+ )
79
+ ```
80
+
81
+ `Client` and `AsyncClient` union, sort, and deduplicate these selections with the
82
+ registered ones. Locking and loading are independent; read-modify-write handlers
83
+ need both for the same instance.
84
+
61
85
  ## Step and timeout-handler state loading
62
86
 
63
87
  `StepOptions` provides the same five selections independently for `wait_for` and
@@ -363,11 +387,16 @@ sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
363
387
 
364
388
  Request IDs are optional for both durable waits. When omitted, the server
365
389
  derives a stable ID from the Step execution or Attribute condition. Reuse an
366
- override only for the same wait. `maximum_wait_time` is optional. Leave it at
367
- zero for normal use and bound one response with the caller deadline. Positive
368
- values are an exceptional safety valve: short budgets can add many Temporal
369
- Update events to Workflow history. If a positive value is truly needed, prefer
370
- at least one minute. A positive expiry raises `WaitHandlerTimeoutError`.
390
+ override only for the same wait. `request_timeout` is the total SDK call budget
391
+ across transparent transport reattachments. Zero waits indefinitely.
392
+ Temporal permits 10 in-flight Updates and 2,000 total Updates in History per
393
+ Workflow Execution. A caller timeout or cancellation does not finish an
394
+ accepted durable wait, so an abandoned handler can retain one in-flight slot.
395
+ Set `internal_handler_timeout` only when abandoned waits can approach that
396
+ limit. An active call transparently starts another generation, which counts
397
+ toward the 2,000-Update history limit. Prefer a value comfortably longer than
398
+ normal request timeouts and reconnect gaps. Zero disables timed rollover.
399
+ Request expiry raises `RequestTimeoutError`.
371
400
 
372
401
  `Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
373
402
  `FlowResult` after hydrating every output-bearing completion. Use
@@ -42,6 +42,30 @@ atomic commit and Channel deletion validation. Attribute locks add isolation
42
42
  only among cooperating Steps and RPCs using the same lock. Write-only publish
43
43
  and delete operations do not require loading.
44
44
 
45
+ ### Invocation-time map instances
46
+
47
+ The `@rpc` decorator remains the registration-time contract for timeout,
48
+ transactionality, locks, and fixed state loads. Pass frozen `RPCInvokeOptions`
49
+ through the keyword-only `options` argument when the caller chooses an exact map
50
+ instance at runtime:
51
+
52
+ ```python
53
+ options = dex.RPCInvokeOptions(
54
+ lock_attribute_map_instances=(profiles.lock(partition_name),),
55
+ load_attribute_map_instances=(profiles.load(partition_name),),
56
+ )
57
+ result = client.invoke_rpc(
58
+ directory.upsert_customer_profile,
59
+ flow_id,
60
+ profile,
61
+ options=options,
62
+ )
63
+ ```
64
+
65
+ `Client` and `AsyncClient` union, sort, and deduplicate these selections with the
66
+ registered ones. Locking and loading are independent; read-modify-write handlers
67
+ need both for the same instance.
68
+
45
69
  ## Step and timeout-handler state loading
46
70
 
47
71
  `StepOptions` provides the same five selections independently for `wait_for` and
@@ -347,11 +371,16 @@ sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
347
371
 
348
372
  Request IDs are optional for both durable waits. When omitted, the server
349
373
  derives a stable ID from the Step execution or Attribute condition. Reuse an
350
- override only for the same wait. `maximum_wait_time` is optional. Leave it at
351
- zero for normal use and bound one response with the caller deadline. Positive
352
- values are an exceptional safety valve: short budgets can add many Temporal
353
- Update events to Workflow history. If a positive value is truly needed, prefer
354
- at least one minute. A positive expiry raises `WaitHandlerTimeoutError`.
374
+ override only for the same wait. `request_timeout` is the total SDK call budget
375
+ across transparent transport reattachments. Zero waits indefinitely.
376
+ Temporal permits 10 in-flight Updates and 2,000 total Updates in History per
377
+ Workflow Execution. A caller timeout or cancellation does not finish an
378
+ accepted durable wait, so an abandoned handler can retain one in-flight slot.
379
+ Set `internal_handler_timeout` only when abandoned waits can approach that
380
+ limit. An active call transparently starts another generation, which counts
381
+ toward the 2,000-Update history limit. Prefer a value comfortably longer than
382
+ normal request timeouts and reconnect gaps. Zero disables timed rollover.
383
+ Request expiry raises `RequestTimeoutError`.
355
384
 
356
385
  `Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
357
386
  `FlowResult` after hydrating every output-bearing completion. Use
@@ -74,11 +74,12 @@ from dex.runtime_errors import (
74
74
  FlowNotFoundError,
75
75
  InvalidStepResultError,
76
76
  LongPollTimeoutError,
77
+ RequestTimeoutError,
77
78
  RpcLockConflictError,
78
79
  ValueMappingError,
79
- WaitHandlerTimeoutError,
80
80
  WorkerInvocationError,
81
81
  )
82
+ from dex.rpc_invoke_options import RPCInvokeOptions
82
83
  from dex.step import (
83
84
  RetryPolicy,
84
85
  Step,
@@ -166,9 +167,10 @@ __all__ = [
166
167
  "IndexType",
167
168
  "JsonCodec",
168
169
  "LongPollTimeoutError",
169
- "WaitHandlerTimeoutError",
170
+ "RequestTimeoutError",
170
171
  "PersistenceSchema",
171
172
  "RPCResult",
173
+ "RPCInvokeOptions",
172
174
  "RecoveryErrorInfo",
173
175
  "RetryAfterError",
174
176
  "RpcLockConflictError",
@@ -26,8 +26,8 @@ from dex.runtime_errors import (
26
26
  FlowNotActiveError,
27
27
  FlowNotFoundError,
28
28
  LongPollTimeoutError,
29
+ RequestTimeoutError,
29
30
  RpcLockConflictError,
30
- WaitHandlerTimeoutError,
31
31
  WorkerInvocationError,
32
32
  )
33
33
 
@@ -144,8 +144,8 @@ def translate_rpc_error(
144
144
  return LongPollTimeoutError(*parameters)
145
145
  if sub_status is ErrorSubStatus.CHANNEL_MESSAGE_NOT_FOUND:
146
146
  return ChannelMessageNotFoundError(*parameters)
147
- if sub_status is ErrorSubStatus.WAIT_HANDLER_TIMEOUT:
148
- return WaitHandlerTimeoutError(*parameters)
147
+ if sub_status is ErrorSubStatus.REQUEST_TIMEOUT:
148
+ return RequestTimeoutError(*parameters)
149
149
  return DexServiceError(*parameters)
150
150
 
151
151
 
@@ -249,8 +249,6 @@ def _map_sub_status(value: int) -> ErrorSubStatus:
249
249
  int(
250
250
  pb.ERROR_SUB_STATUS_CHANNEL_MESSAGE_NOT_FOUND
251
251
  ): ErrorSubStatus.CHANNEL_MESSAGE_NOT_FOUND,
252
- int(
253
- pb.ERROR_SUB_STATUS_WAIT_HANDLER_TIME_OUT
254
- ): ErrorSubStatus.WAIT_HANDLER_TIMEOUT,
252
+ int(pb.ERROR_SUB_STATUS_REQUEST_TIMEOUT): ErrorSubStatus.REQUEST_TIMEOUT,
255
253
  }
256
254
  return statuses.get(value, ErrorSubStatus.UNCATEGORIZED)
@@ -45,14 +45,16 @@ from dex.flow_options import (
45
45
  _resolve_flow_timeout_policy,
46
46
  )
47
47
  from dex.flow_result import FlowResult, flow_result_from_proto
48
- from dex.runtime_errors import FlowErrorType, LongPollTimeoutError
48
+ from dex.runtime_errors import DexServiceError, FlowErrorType, LongPollTimeoutError
49
+ from dex.rpc_invoke_options import RPCInvokeOptions
49
50
  from dex.step import RetryPolicy, StepDurability
50
51
  from dex.step_execution import StepExecutionId, TimerId
51
52
  from dex.stream import Stream, StreamMessage, StreamMessagesPage
52
53
  from dex.wait_options import (
53
54
  WaitForAttributeOptions,
54
55
  WaitForStepCompletionOptions,
55
- _ClientWaitBudget,
56
+ _ClientRequestBudget,
57
+ _duration_seconds32,
56
58
  )
57
59
 
58
60
  InputT = TypeVar("InputT")
@@ -213,6 +215,7 @@ class AsyncClient:
213
215
  input: InputT,
214
216
  *,
215
217
  run_id: str = "",
218
+ options: RPCInvokeOptions = RPCInvokeOptions(),
216
219
  ) -> OutputT: ...
217
220
 
218
221
  @overload
@@ -222,6 +225,7 @@ class AsyncClient:
222
225
  flow_id: str,
223
226
  *,
224
227
  run_id: str = "",
228
+ options: RPCInvokeOptions = RPCInvokeOptions(),
225
229
  ) -> OutputT: ...
226
230
 
227
231
  @overload
@@ -232,6 +236,7 @@ class AsyncClient:
232
236
  input: InputT,
233
237
  *,
234
238
  run_id: str = "",
239
+ options: RPCInvokeOptions = RPCInvokeOptions(),
235
240
  ) -> None: ...
236
241
 
237
242
  @overload
@@ -241,6 +246,7 @@ class AsyncClient:
241
246
  flow_id: str,
242
247
  *,
243
248
  run_id: str = "",
249
+ options: RPCInvokeOptions = RPCInvokeOptions(),
244
250
  ) -> None: ...
245
251
 
246
252
  async def invoke_rpc(
@@ -250,6 +256,7 @@ class AsyncClient:
250
256
  input: object = None,
251
257
  *,
252
258
  run_id: str = "",
259
+ options: RPCInvokeOptions = RPCInvokeOptions(),
253
260
  ) -> Any:
254
261
  """Invoke a registered RPC and await its typed result.
255
262
 
@@ -266,6 +273,7 @@ class AsyncClient:
266
273
  input: The annotated input, or ``None`` for an input-free RPC.
267
274
  run_id: Optional exact run. With ``""``, the server resolves the current
268
275
  execution.
276
+ options: Additive runtime AttributeMap locks and exact map-instance loads.
269
277
 
270
278
  Returns:
271
279
  The decoded ``RPCResult.output``, or ``None`` for a no-output RPC.
@@ -278,7 +286,12 @@ class AsyncClient:
278
286
  DexServiceError: If the selected path requires an active execution or the
279
287
  service call otherwise fails.
280
288
  """
281
- _, rpc = self.registry._rpc_for_method(rpc_method)
289
+ registered_flow, rpc = self.registry._rpc_for_method(rpc_method)
290
+ (
291
+ invocation_locks,
292
+ invocation_attribute_map_loads,
293
+ invocation_channel_map_loads,
294
+ ) = self.registry._resolve_rpc_invoke_options(registered_flow, options)
282
295
  encoded_input = (
283
296
  self._values.encode(input, rpc.input_codec)
284
297
  if rpc.input_codec is not None
@@ -299,12 +312,28 @@ class AsyncClient:
299
312
  rpc_name=rpc.name,
300
313
  input=encoded_input,
301
314
  timeout_seconds=timeout,
302
- lock_attribute_keys=rpc.locks,
315
+ lock_attribute_keys=tuple(
316
+ sorted(set(rpc.locks + invocation_locks))
317
+ ),
303
318
  request_id=str(uuid4()),
304
319
  is_transactional=rpc.options.is_transactional,
305
- load_attribute_map_instances=rpc.load_attribute_map_instances,
320
+ load_attribute_map_instances=tuple(
321
+ sorted(
322
+ set(
323
+ rpc.load_attribute_map_instances
324
+ + invocation_attribute_map_loads
325
+ )
326
+ )
327
+ ),
306
328
  load_channel_names=rpc.load_channel_names,
307
- load_channel_map_instances=rpc.load_channel_map_instances,
329
+ load_channel_map_instances=tuple(
330
+ sorted(
331
+ set(
332
+ rpc.load_channel_map_instances
333
+ + invocation_channel_map_loads
334
+ )
335
+ )
336
+ ),
308
337
  ),
309
338
  "invoke_rpc",
310
339
  flow_id,
@@ -738,27 +767,33 @@ class AsyncClient:
738
767
  step_execution_id: StepExecutionId,
739
768
  options: WaitForStepCompletionOptions,
740
769
  ) -> None:
741
- """Await one Step execution's completion or its wait budget expiry.
770
+ """Await one Step execution completion or its request timeout expiry.
742
771
 
743
772
  The server derives a stable Request ID from the Step execution when none
744
- is supplied. Leave the maximum wait time at zero for normal use. Positive
745
- values are exceptional because short budgets can add many Temporal Update
746
- events to Workflow history; prefer at least one minute when nonzero. A
747
- positive value bounds the caller-visible wait.
773
+ is supplied. Request timeout covers transparent transport reattachments.
774
+ Internal handler timeout controls Update generation rollover without ending
775
+ this request.
748
776
 
749
777
  Args:
750
778
  flow_id: The non-empty active Flow ID.
751
779
  step_execution_id: The Step type and positive execution number.
752
- options: The optional Request ID override and total wait budget.
780
+ options: The optional Request ID, request timeout, and internal handler
781
+ timeout controls.
753
782
 
754
783
  Raises:
755
- ValueError: If the Request ID or wait budget is invalid.
756
- WaitHandlerTimeoutError: If a positive wait budget expires first.
784
+ ValueError: If the Request ID or request timeout is invalid.
785
+ RequestTimeoutError: If a positive request timeout expires first.
757
786
  FlowNotActiveError: If the Flow closes first.
758
787
  DexServiceError: If FlowService cannot perform the wait.
759
788
  """
760
- wait_budget = _ClientWaitBudget(options.maximum_wait_time)
789
+ request_budget = _ClientRequestBudget(options.request_timeout)
790
+ internal_handler_timeout_seconds = _duration_seconds32(
791
+ options.internal_handler_timeout
792
+ )
761
793
  while True:
794
+ request_attempt = request_budget.next_attempt(
795
+ "wait_for_step_completion", flow_id
796
+ )
762
797
  try:
763
798
  await self._call(
764
799
  self._service.WaitForStepCompletion,
@@ -766,18 +801,29 @@ class AsyncClient:
766
801
  flow_id=require_name(flow_id),
767
802
  step_type=step_execution_id.step_type,
768
803
  step_execution_number=str(step_execution_id.number),
769
- wait_time_seconds=wait_budget.remaining_seconds(
770
- "wait_for_step_completion", flow_id
804
+ request_timeout_seconds=request_attempt.request_timeout_seconds,
805
+ internal_handler_timeout_seconds=(
806
+ internal_handler_timeout_seconds
771
807
  ),
772
808
  request_id=options.request_id,
773
809
  ),
774
810
  "wait_for_step_completion",
775
811
  flow_id,
776
812
  "active",
813
+ request_attempt.transport_timeout_seconds,
777
814
  )
778
815
  return
779
816
  except LongPollTimeoutError:
780
817
  continue
818
+ except DexServiceError as error:
819
+ if (
820
+ error.code is grpc.StatusCode.DEADLINE_EXCEEDED
821
+ and request_budget.has_expired()
822
+ ):
823
+ raise request_budget.timeout_error(
824
+ "wait_for_step_completion", flow_id
825
+ ) from error
826
+ raise
781
827
 
782
828
  @overload
783
829
  async def wait_for_attribute_match(
@@ -791,24 +837,23 @@ class AsyncClient:
791
837
 
792
838
  The Client returns the value observed by the successful wait. The server
793
839
  derives a stable Request ID from the condition when none is supplied.
794
- Leave the maximum wait time at zero for normal use. Positive values are
795
- exceptional because short budgets can add many Temporal Update events to
796
- Workflow history; prefer at least one minute when nonzero. A positive value
797
- bounds the caller-visible wait.
840
+ Request timeout covers transparent transport reattachments. Internal handler
841
+ timeout controls Update generation rollover without ending this request.
798
842
  JSON, bytes, and null operands raise ``ValueError`` before transport.
799
843
 
800
844
  Args:
801
845
  flow_id: The non-empty active Flow ID.
802
846
  attribute: The registered singleton Attribute to observe.
803
847
  match: The scalar predicate to await.
804
- options: The optional Request ID override and total wait budget.
848
+ options: The optional Request ID, request timeout, and internal handler
849
+ timeout controls.
805
850
 
806
851
  Returns:
807
852
  The current Attribute value that satisfied ``match``.
808
853
 
809
854
  Raises:
810
855
  ValueError: If an identifier, option, or match operand is invalid.
811
- WaitHandlerTimeoutError: If a positive wait budget expires first.
856
+ RequestTimeoutError: If a positive request timeout expires first.
812
857
  FlowNotActiveError: If the Flow closes first.
813
858
  DexServiceError: If FlowService cannot perform the wait.
814
859
  """
@@ -825,7 +870,7 @@ class AsyncClient:
825
870
  ) -> ValueT:
826
871
  """Await one AttributeMap instance in the current run satisfying a match.
827
872
 
828
- Match restrictions, Request ID, handler-budget behavior, and service
873
+ Match restrictions, Request ID, request-timeout and handler-generation behavior, and service
829
874
  errors match :meth:`wait_for_attribute_match`.
830
875
 
831
876
  Args:
@@ -833,14 +878,14 @@ class AsyncClient:
833
878
  attribute: The registered AttributeMap to observe.
834
879
  instance: The map instance to observe. Slash is prohibited because it is a reserved character.
835
880
  match: The scalar predicate to await.
836
- options: The optional Request ID override and total handler wait budget.
881
+ options: The optional Request ID override and total handler request timeout.
837
882
 
838
883
  Returns:
839
884
  The current AttributeMap value that satisfied ``match``.
840
885
 
841
886
  Raises:
842
887
  ValueError: If an identifier, option, or match operand is invalid.
843
- WaitHandlerTimeoutError: If a positive wait budget expires first.
888
+ RequestTimeoutError: If a positive request timeout expires first.
844
889
  FlowNotActiveError: If the Flow closes first.
845
890
  DexServiceError: If FlowService cannot perform the wait.
846
891
  """
@@ -870,7 +915,7 @@ class AsyncClient:
870
915
  Raises:
871
916
  TypeError: If arguments do not match the Attribute definition.
872
917
  ValueError: If an identifier, option, or expected value is invalid.
873
- WaitHandlerTimeoutError: If a positive wait budget expires first.
918
+ RequestTimeoutError: If a positive request timeout expires first.
874
919
  FlowNotActiveError: If the Flow closes first.
875
920
  DexServiceError: If FlowService cannot perform the wait.
876
921
  """
@@ -895,8 +940,14 @@ class AsyncClient:
895
940
  self._values.codec(attribute.value_type),
896
941
  )
897
942
  encoded_match.key = self._definition_name(attribute, instance)
898
- wait_budget = _ClientWaitBudget(options.maximum_wait_time)
943
+ request_budget = _ClientRequestBudget(options.request_timeout)
944
+ internal_handler_timeout_seconds = _duration_seconds32(
945
+ options.internal_handler_timeout
946
+ )
899
947
  while True:
948
+ request_attempt = request_budget.next_attempt(
949
+ "wait_for_attribute_match", flow_id
950
+ )
900
951
  try:
901
952
  response = cast(
902
953
  pb.WaitForAttributeResponse,
@@ -905,14 +956,18 @@ class AsyncClient:
905
956
  pb.WaitForAttributeRequest(
906
957
  flow_id=require_name(flow_id),
907
958
  match=encoded_match,
908
- wait_time_seconds=wait_budget.remaining_seconds(
909
- "wait_for_attribute_match", flow_id
959
+ request_timeout_seconds=(
960
+ request_attempt.request_timeout_seconds
961
+ ),
962
+ internal_handler_timeout_seconds=(
963
+ internal_handler_timeout_seconds
910
964
  ),
911
965
  request_id=options.request_id,
912
966
  ),
913
967
  "wait_for_attribute_match",
914
968
  flow_id,
915
969
  "active",
970
+ request_attempt.transport_timeout_seconds,
916
971
  ),
917
972
  )
918
973
  if not response.HasField("matched_value"):
@@ -923,6 +978,15 @@ class AsyncClient:
923
978
  )
924
979
  except LongPollTimeoutError:
925
980
  continue
981
+ except DexServiceError as error:
982
+ if (
983
+ error.code is grpc.StatusCode.DEADLINE_EXCEEDED
984
+ and request_budget.has_expired()
985
+ ):
986
+ raise request_budget.timeout_error(
987
+ "wait_for_attribute_match", flow_id
988
+ ) from error
989
+ raise
926
990
 
927
991
  async def update_flow_config(self, flow_id: str, config: FlowConfig) -> None:
928
992
  """Replace mutable configuration for an active Flow.
@@ -1245,13 +1309,16 @@ class AsyncClient:
1245
1309
 
1246
1310
  @staticmethod
1247
1311
  async def _call(
1248
- method: Callable[[Any], Any],
1312
+ method: Callable[..., Any],
1249
1313
  request: Any,
1250
1314
  operation: str,
1251
1315
  flow_id: str | None,
1252
1316
  requirement: FlowTargetRequirement,
1317
+ timeout: float | None = None,
1253
1318
  ) -> Any:
1254
1319
  try:
1255
- return await method(request)
1320
+ if timeout is None:
1321
+ return await method(request)
1322
+ return await method(request, timeout=timeout)
1256
1323
  except grpc.RpcError as error:
1257
1324
  raise translate_rpc_error(error, operation, flow_id, requirement) from error