dex-python-sdk 0.11.2__tar.gz → 0.12.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 (65) hide show
  1. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/PKG-INFO +11 -6
  2. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/README.md +10 -5
  3. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/__init__.py +2 -2
  4. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_grpc_errors.py +4 -6
  5. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/async_client.py +67 -28
  6. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/client.py +67 -28
  7. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/dexpb/dex_pb2.py +248 -248
  8. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/dexpb/dex_pb2.pyi +14 -10
  9. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/dexpb/dex_pb2_grpc.py +12 -12
  10. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/flow_options.py +1 -1
  11. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/runtime_errors.py +4 -4
  12. dex_python_sdk-0.12.1/dex/wait_options.py +112 -0
  13. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/pyproject.toml +1 -1
  14. dex_python_sdk-0.11.2/dex/wait_options.py +0 -84
  15. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/LEGACY_NOTICES.md +0 -0
  16. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/LICENSE +0 -0
  17. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_async_value_hydrator.py +0 -0
  18. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_async_worker_dispatcher.py +0 -0
  19. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_async_worker_service.py +0 -0
  20. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_invocation_context.py +0 -0
  21. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_native.pyi +0 -0
  22. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_server_protocol.py +0 -0
  23. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_utils.py +0 -0
  24. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_value_hydrator.py +0 -0
  25. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_value_mapper.py +0 -0
  26. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_worker_dispatcher.py +0 -0
  27. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/_worker_service.py +0 -0
  28. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/async_worker.py +0 -0
  29. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/attribute.py +0 -0
  30. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/attribute_match.py +0 -0
  31. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/blob_cache.py +0 -0
  32. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/channel.py +0 -0
  33. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/client_options.py +0 -0
  34. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/codec.py +0 -0
  35. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/condition.py +0 -0
  36. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/context.py +0 -0
  37. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/dexpb/__init__.py +0 -0
  38. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/flow.py +0 -0
  39. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/flow_config.py +0 -0
  40. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/flow_info.py +0 -0
  41. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/flow_result.py +0 -0
  42. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/py.typed +0 -0
  43. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/step.py +0 -0
  44. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/step_execution.py +0 -0
  45. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/stream.py +0 -0
  46. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/subflow.py +0 -0
  47. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/timer.py +0 -0
  48. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/wait.py +0 -0
  49. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/worker.py +0 -0
  50. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/dex/worker_options.py +0 -0
  51. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/Cargo.lock +0 -0
  52. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/Cargo.toml +0 -0
  53. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  54. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  55. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  56. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  57. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  58. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  59. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  60. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  61. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  62. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  63. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  64. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.1}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  65. {dex_python_sdk-0.11.2 → dex_python_sdk-0.12.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.11.2
3
+ Version: 0.12.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
@@ -363,11 +363,16 @@ sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
363
363
 
364
364
  Request IDs are optional for both durable waits. When omitted, the server
365
365
  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`.
366
+ override only for the same wait. `request_timeout` is the total SDK call budget
367
+ across transparent transport reattachments. Zero waits indefinitely.
368
+ Temporal permits 10 in-flight Updates and 2,000 total Updates in History per
369
+ Workflow Execution. A caller timeout or cancellation does not finish an
370
+ accepted durable wait, so an abandoned handler can retain one in-flight slot.
371
+ Set `internal_handler_timeout` only when abandoned waits can approach that
372
+ limit. An active call transparently starts another generation, which counts
373
+ toward the 2,000-Update history limit. Prefer a value comfortably longer than
374
+ normal request timeouts and reconnect gaps. Zero disables timed rollover.
375
+ Request expiry raises `RequestTimeoutError`.
371
376
 
372
377
  `Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
373
378
  `FlowResult` after hydrating every output-bearing completion. Use
@@ -347,11 +347,16 @@ sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
347
347
 
348
348
  Request IDs are optional for both durable waits. When omitted, the server
349
349
  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`.
350
+ override only for the same wait. `request_timeout` is the total SDK call budget
351
+ across transparent transport reattachments. Zero waits indefinitely.
352
+ Temporal permits 10 in-flight Updates and 2,000 total Updates in History per
353
+ Workflow Execution. A caller timeout or cancellation does not finish an
354
+ accepted durable wait, so an abandoned handler can retain one in-flight slot.
355
+ Set `internal_handler_timeout` only when abandoned waits can approach that
356
+ limit. An active call transparently starts another generation, which counts
357
+ toward the 2,000-Update history limit. Prefer a value comfortably longer than
358
+ normal request timeouts and reconnect gaps. Zero disables timed rollover.
359
+ Request expiry raises `RequestTimeoutError`.
355
360
 
356
361
  `Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
357
362
  `FlowResult` after hydrating every output-bearing completion. Use
@@ -74,9 +74,9 @@ 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
82
  from dex.step import (
@@ -166,7 +166,7 @@ __all__ = [
166
166
  "IndexType",
167
167
  "JsonCodec",
168
168
  "LongPollTimeoutError",
169
- "WaitHandlerTimeoutError",
169
+ "RequestTimeoutError",
170
170
  "PersistenceSchema",
171
171
  "RPCResult",
172
172
  "RecoveryErrorInfo",
@@ -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,15 @@ 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
49
  from dex.step import RetryPolicy, StepDurability
50
50
  from dex.step_execution import StepExecutionId, TimerId
51
51
  from dex.stream import Stream, StreamMessage, StreamMessagesPage
52
52
  from dex.wait_options import (
53
53
  WaitForAttributeOptions,
54
54
  WaitForStepCompletionOptions,
55
- _ClientWaitBudget,
55
+ _ClientRequestBudget,
56
+ _duration_seconds32,
56
57
  )
57
58
 
58
59
  InputT = TypeVar("InputT")
@@ -738,27 +739,33 @@ class AsyncClient:
738
739
  step_execution_id: StepExecutionId,
739
740
  options: WaitForStepCompletionOptions,
740
741
  ) -> None:
741
- """Await one Step execution's completion or its wait budget expiry.
742
+ """Await one Step execution completion or its request timeout expiry.
742
743
 
743
744
  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.
745
+ is supplied. Request timeout covers transparent transport reattachments.
746
+ Internal handler timeout controls Update generation rollover without ending
747
+ this request.
748
748
 
749
749
  Args:
750
750
  flow_id: The non-empty active Flow ID.
751
751
  step_execution_id: The Step type and positive execution number.
752
- options: The optional Request ID override and total wait budget.
752
+ options: The optional Request ID, request timeout, and internal handler
753
+ timeout controls.
753
754
 
754
755
  Raises:
755
- ValueError: If the Request ID or wait budget is invalid.
756
- WaitHandlerTimeoutError: If a positive wait budget expires first.
756
+ ValueError: If the Request ID or request timeout is invalid.
757
+ RequestTimeoutError: If a positive request timeout expires first.
757
758
  FlowNotActiveError: If the Flow closes first.
758
759
  DexServiceError: If FlowService cannot perform the wait.
759
760
  """
760
- wait_budget = _ClientWaitBudget(options.maximum_wait_time)
761
+ request_budget = _ClientRequestBudget(options.request_timeout)
762
+ internal_handler_timeout_seconds = _duration_seconds32(
763
+ options.internal_handler_timeout
764
+ )
761
765
  while True:
766
+ request_attempt = request_budget.next_attempt(
767
+ "wait_for_step_completion", flow_id
768
+ )
762
769
  try:
763
770
  await self._call(
764
771
  self._service.WaitForStepCompletion,
@@ -766,18 +773,29 @@ class AsyncClient:
766
773
  flow_id=require_name(flow_id),
767
774
  step_type=step_execution_id.step_type,
768
775
  step_execution_number=str(step_execution_id.number),
769
- wait_time_seconds=wait_budget.remaining_seconds(
770
- "wait_for_step_completion", flow_id
776
+ request_timeout_seconds=request_attempt.request_timeout_seconds,
777
+ internal_handler_timeout_seconds=(
778
+ internal_handler_timeout_seconds
771
779
  ),
772
780
  request_id=options.request_id,
773
781
  ),
774
782
  "wait_for_step_completion",
775
783
  flow_id,
776
784
  "active",
785
+ request_attempt.transport_timeout_seconds,
777
786
  )
778
787
  return
779
788
  except LongPollTimeoutError:
780
789
  continue
790
+ except DexServiceError as error:
791
+ if (
792
+ error.code is grpc.StatusCode.DEADLINE_EXCEEDED
793
+ and request_budget.has_expired()
794
+ ):
795
+ raise request_budget.timeout_error(
796
+ "wait_for_step_completion", flow_id
797
+ ) from error
798
+ raise
781
799
 
782
800
  @overload
783
801
  async def wait_for_attribute_match(
@@ -791,24 +809,23 @@ class AsyncClient:
791
809
 
792
810
  The Client returns the value observed by the successful wait. The server
793
811
  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.
812
+ Request timeout covers transparent transport reattachments. Internal handler
813
+ timeout controls Update generation rollover without ending this request.
798
814
  JSON, bytes, and null operands raise ``ValueError`` before transport.
799
815
 
800
816
  Args:
801
817
  flow_id: The non-empty active Flow ID.
802
818
  attribute: The registered singleton Attribute to observe.
803
819
  match: The scalar predicate to await.
804
- options: The optional Request ID override and total wait budget.
820
+ options: The optional Request ID, request timeout, and internal handler
821
+ timeout controls.
805
822
 
806
823
  Returns:
807
824
  The current Attribute value that satisfied ``match``.
808
825
 
809
826
  Raises:
810
827
  ValueError: If an identifier, option, or match operand is invalid.
811
- WaitHandlerTimeoutError: If a positive wait budget expires first.
828
+ RequestTimeoutError: If a positive request timeout expires first.
812
829
  FlowNotActiveError: If the Flow closes first.
813
830
  DexServiceError: If FlowService cannot perform the wait.
814
831
  """
@@ -825,7 +842,7 @@ class AsyncClient:
825
842
  ) -> ValueT:
826
843
  """Await one AttributeMap instance in the current run satisfying a match.
827
844
 
828
- Match restrictions, Request ID, handler-budget behavior, and service
845
+ Match restrictions, Request ID, request-timeout and handler-generation behavior, and service
829
846
  errors match :meth:`wait_for_attribute_match`.
830
847
 
831
848
  Args:
@@ -833,14 +850,14 @@ class AsyncClient:
833
850
  attribute: The registered AttributeMap to observe.
834
851
  instance: The map instance to observe. Slash is prohibited because it is a reserved character.
835
852
  match: The scalar predicate to await.
836
- options: The optional Request ID override and total handler wait budget.
853
+ options: The optional Request ID override and total handler request timeout.
837
854
 
838
855
  Returns:
839
856
  The current AttributeMap value that satisfied ``match``.
840
857
 
841
858
  Raises:
842
859
  ValueError: If an identifier, option, or match operand is invalid.
843
- WaitHandlerTimeoutError: If a positive wait budget expires first.
860
+ RequestTimeoutError: If a positive request timeout expires first.
844
861
  FlowNotActiveError: If the Flow closes first.
845
862
  DexServiceError: If FlowService cannot perform the wait.
846
863
  """
@@ -870,7 +887,7 @@ class AsyncClient:
870
887
  Raises:
871
888
  TypeError: If arguments do not match the Attribute definition.
872
889
  ValueError: If an identifier, option, or expected value is invalid.
873
- WaitHandlerTimeoutError: If a positive wait budget expires first.
890
+ RequestTimeoutError: If a positive request timeout expires first.
874
891
  FlowNotActiveError: If the Flow closes first.
875
892
  DexServiceError: If FlowService cannot perform the wait.
876
893
  """
@@ -895,8 +912,14 @@ class AsyncClient:
895
912
  self._values.codec(attribute.value_type),
896
913
  )
897
914
  encoded_match.key = self._definition_name(attribute, instance)
898
- wait_budget = _ClientWaitBudget(options.maximum_wait_time)
915
+ request_budget = _ClientRequestBudget(options.request_timeout)
916
+ internal_handler_timeout_seconds = _duration_seconds32(
917
+ options.internal_handler_timeout
918
+ )
899
919
  while True:
920
+ request_attempt = request_budget.next_attempt(
921
+ "wait_for_attribute_match", flow_id
922
+ )
900
923
  try:
901
924
  response = cast(
902
925
  pb.WaitForAttributeResponse,
@@ -905,14 +928,18 @@ class AsyncClient:
905
928
  pb.WaitForAttributeRequest(
906
929
  flow_id=require_name(flow_id),
907
930
  match=encoded_match,
908
- wait_time_seconds=wait_budget.remaining_seconds(
909
- "wait_for_attribute_match", flow_id
931
+ request_timeout_seconds=(
932
+ request_attempt.request_timeout_seconds
933
+ ),
934
+ internal_handler_timeout_seconds=(
935
+ internal_handler_timeout_seconds
910
936
  ),
911
937
  request_id=options.request_id,
912
938
  ),
913
939
  "wait_for_attribute_match",
914
940
  flow_id,
915
941
  "active",
942
+ request_attempt.transport_timeout_seconds,
916
943
  ),
917
944
  )
918
945
  if not response.HasField("matched_value"):
@@ -923,6 +950,15 @@ class AsyncClient:
923
950
  )
924
951
  except LongPollTimeoutError:
925
952
  continue
953
+ except DexServiceError as error:
954
+ if (
955
+ error.code is grpc.StatusCode.DEADLINE_EXCEEDED
956
+ and request_budget.has_expired()
957
+ ):
958
+ raise request_budget.timeout_error(
959
+ "wait_for_attribute_match", flow_id
960
+ ) from error
961
+ raise
926
962
 
927
963
  async def update_flow_config(self, flow_id: str, config: FlowConfig) -> None:
928
964
  """Replace mutable configuration for an active Flow.
@@ -1245,13 +1281,16 @@ class AsyncClient:
1245
1281
 
1246
1282
  @staticmethod
1247
1283
  async def _call(
1248
- method: Callable[[Any], Any],
1284
+ method: Callable[..., Any],
1249
1285
  request: Any,
1250
1286
  operation: str,
1251
1287
  flow_id: str | None,
1252
1288
  requirement: FlowTargetRequirement,
1289
+ timeout: float | None = None,
1253
1290
  ) -> Any:
1254
1291
  try:
1255
- return await method(request)
1292
+ if timeout is None:
1293
+ return await method(request)
1294
+ return await method(request, timeout=timeout)
1256
1295
  except grpc.RpcError as error:
1257
1296
  raise translate_rpc_error(error, operation, flow_id, requirement) from error
@@ -45,14 +45,15 @@ 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
49
  from dex.step import RetryPolicy, StepDurability
50
50
  from dex.step_execution import StepExecutionId, TimerId
51
51
  from dex.stream import Stream, StreamMessage, StreamMessagesPage
52
52
  from dex.wait_options import (
53
53
  WaitForAttributeOptions,
54
54
  WaitForStepCompletionOptions,
55
- _ClientWaitBudget,
55
+ _ClientRequestBudget,
56
+ _duration_seconds32,
56
57
  )
57
58
 
58
59
  InputT = TypeVar("InputT")
@@ -734,27 +735,33 @@ class Client:
734
735
  step_execution_id: StepExecutionId,
735
736
  options: WaitForStepCompletionOptions,
736
737
  ) -> None:
737
- """Block until one Step execution completes or its wait budget expires.
738
+ """Block until one Step execution completes or its request timeout expires.
738
739
 
739
740
  The server derives a stable Request ID from the Step execution when none
740
- is supplied. Leave the maximum wait time at zero for normal use. Positive
741
- values are exceptional because short budgets can add many Temporal Update
742
- events to Workflow history; prefer at least one minute when nonzero. A
743
- positive value bounds the caller-visible wait.
741
+ is supplied. Request timeout covers transparent transport reattachments.
742
+ Internal handler timeout controls Update generation rollover without ending
743
+ this request.
744
744
 
745
745
  Args:
746
746
  flow_id: The non-empty active Flow ID.
747
747
  step_execution_id: The Step type and positive execution number.
748
- options: The optional Request ID override and total wait budget.
748
+ options: The optional Request ID, request timeout, and internal handler
749
+ timeout controls.
749
750
 
750
751
  Raises:
751
- ValueError: If the Request ID or wait budget is invalid.
752
- WaitHandlerTimeoutError: If a positive wait budget expires first.
752
+ ValueError: If the Request ID or request timeout is invalid.
753
+ RequestTimeoutError: If a positive request timeout expires first.
753
754
  FlowNotActiveError: If the Flow closes first.
754
755
  DexServiceError: If FlowService cannot perform the wait.
755
756
  """
756
- wait_budget = _ClientWaitBudget(options.maximum_wait_time)
757
+ request_budget = _ClientRequestBudget(options.request_timeout)
758
+ internal_handler_timeout_seconds = _duration_seconds32(
759
+ options.internal_handler_timeout
760
+ )
757
761
  while True:
762
+ request_attempt = request_budget.next_attempt(
763
+ "wait_for_step_completion", flow_id
764
+ )
758
765
  try:
759
766
  self._call(
760
767
  self._service.WaitForStepCompletion,
@@ -762,18 +769,29 @@ class Client:
762
769
  flow_id=require_name(flow_id),
763
770
  step_type=step_execution_id.step_type,
764
771
  step_execution_number=str(step_execution_id.number),
765
- wait_time_seconds=wait_budget.remaining_seconds(
766
- "wait_for_step_completion", flow_id
772
+ request_timeout_seconds=request_attempt.request_timeout_seconds,
773
+ internal_handler_timeout_seconds=(
774
+ internal_handler_timeout_seconds
767
775
  ),
768
776
  request_id=options.request_id,
769
777
  ),
770
778
  "wait_for_step_completion",
771
779
  flow_id,
772
780
  "active",
781
+ request_attempt.transport_timeout_seconds,
773
782
  )
774
783
  return
775
784
  except LongPollTimeoutError:
776
785
  continue
786
+ except DexServiceError as error:
787
+ if (
788
+ error.code is grpc.StatusCode.DEADLINE_EXCEEDED
789
+ and request_budget.has_expired()
790
+ ):
791
+ raise request_budget.timeout_error(
792
+ "wait_for_step_completion", flow_id
793
+ ) from error
794
+ raise
777
795
 
778
796
  @overload
779
797
  def wait_for_attribute_match(
@@ -787,24 +805,23 @@ class Client:
787
805
 
788
806
  The Client returns the value observed by the successful wait. The server
789
807
  derives a stable Request ID from the condition when none is supplied.
790
- Leave the maximum wait time at zero for normal use. Positive values are
791
- exceptional because short budgets can add many Temporal Update events to
792
- Workflow history; prefer at least one minute when nonzero. A positive value
793
- bounds the caller-visible wait.
808
+ Request timeout covers transparent transport reattachments. Internal handler
809
+ timeout controls Update generation rollover without ending this request.
794
810
  JSON, bytes, and null operands raise ``ValueError`` before transport.
795
811
 
796
812
  Args:
797
813
  flow_id: The non-empty active Flow ID.
798
814
  attribute: The registered singleton Attribute to observe.
799
815
  match: The scalar predicate to await.
800
- options: The optional Request ID override and total wait budget.
816
+ options: The optional Request ID, request timeout, and internal handler
817
+ timeout controls.
801
818
 
802
819
  Returns:
803
820
  The current Attribute value that satisfied ``match``.
804
821
 
805
822
  Raises:
806
823
  ValueError: If an identifier, option, or match operand is invalid.
807
- WaitHandlerTimeoutError: If a positive wait budget expires first.
824
+ RequestTimeoutError: If a positive request timeout expires first.
808
825
  FlowNotActiveError: If the Flow closes first.
809
826
  DexServiceError: If FlowService cannot perform the wait.
810
827
  """
@@ -822,7 +839,7 @@ class Client:
822
839
  """Wait for one AttributeMap instance in the current run to match.
823
840
 
824
841
  ``instance`` is encoded as a map key. Match restrictions, Request ID,
825
- handler-budget behavior, and service errors match
842
+ request-timeout and handler-generation behavior, and service errors match
826
843
  :meth:`wait_for_attribute_match`.
827
844
 
828
845
  Args:
@@ -830,14 +847,14 @@ class Client:
830
847
  attribute: The registered AttributeMap to observe.
831
848
  instance: The map instance to observe. Slash is prohibited because it is a reserved character.
832
849
  match: The scalar predicate to await.
833
- options: The optional Request ID override and total handler wait budget.
850
+ options: The optional Request ID override and total handler request timeout.
834
851
 
835
852
  Returns:
836
853
  The current AttributeMap value that satisfied ``match``.
837
854
 
838
855
  Raises:
839
856
  ValueError: If an identifier, option, or match operand is invalid.
840
- WaitHandlerTimeoutError: If a positive wait budget expires first.
857
+ RequestTimeoutError: If a positive request timeout expires first.
841
858
  FlowNotActiveError: If the Flow closes first.
842
859
  DexServiceError: If FlowService cannot perform the wait.
843
860
  """
@@ -867,7 +884,7 @@ class Client:
867
884
  Raises:
868
885
  TypeError: If arguments do not match the Attribute definition.
869
886
  ValueError: If an identifier, option, or expected value is invalid.
870
- WaitHandlerTimeoutError: If a positive wait budget expires first.
887
+ RequestTimeoutError: If a positive request timeout expires first.
871
888
  FlowNotActiveError: If the Flow closes first.
872
889
  DexServiceError: If FlowService cannot perform the wait.
873
890
  """
@@ -892,8 +909,14 @@ class Client:
892
909
  self._values.codec(attribute.value_type),
893
910
  )
894
911
  encoded_match.key = self._definition_name(attribute, instance)
895
- wait_budget = _ClientWaitBudget(options.maximum_wait_time)
912
+ request_budget = _ClientRequestBudget(options.request_timeout)
913
+ internal_handler_timeout_seconds = _duration_seconds32(
914
+ options.internal_handler_timeout
915
+ )
896
916
  while True:
917
+ request_attempt = request_budget.next_attempt(
918
+ "wait_for_attribute_match", flow_id
919
+ )
897
920
  try:
898
921
  response = cast(
899
922
  pb.WaitForAttributeResponse,
@@ -902,14 +925,18 @@ class Client:
902
925
  pb.WaitForAttributeRequest(
903
926
  flow_id=require_name(flow_id),
904
927
  match=encoded_match,
905
- wait_time_seconds=wait_budget.remaining_seconds(
906
- "wait_for_attribute_match", flow_id
928
+ request_timeout_seconds=(
929
+ request_attempt.request_timeout_seconds
930
+ ),
931
+ internal_handler_timeout_seconds=(
932
+ internal_handler_timeout_seconds
907
933
  ),
908
934
  request_id=options.request_id,
909
935
  ),
910
936
  "wait_for_attribute_match",
911
937
  flow_id,
912
938
  "active",
939
+ request_attempt.transport_timeout_seconds,
913
940
  ),
914
941
  )
915
942
  if not response.HasField("matched_value"):
@@ -920,6 +947,15 @@ class Client:
920
947
  )
921
948
  except LongPollTimeoutError:
922
949
  continue
950
+ except DexServiceError as error:
951
+ if (
952
+ error.code is grpc.StatusCode.DEADLINE_EXCEEDED
953
+ and request_budget.has_expired()
954
+ ):
955
+ raise request_budget.timeout_error(
956
+ "wait_for_attribute_match", flow_id
957
+ ) from error
958
+ raise
923
959
 
924
960
  def update_flow_config(self, flow_id: str, config: FlowConfig) -> None:
925
961
  """Replace mutable configuration for an active Flow.
@@ -1242,13 +1278,16 @@ class Client:
1242
1278
 
1243
1279
  @staticmethod
1244
1280
  def _call(
1245
- method: Callable[[Any], Any],
1281
+ method: Callable[..., Any],
1246
1282
  request: Any,
1247
1283
  operation: str,
1248
1284
  flow_id: str | None,
1249
1285
  requirement: FlowTargetRequirement,
1286
+ timeout: float | None = None,
1250
1287
  ) -> Any:
1251
1288
  try:
1252
- return method(request)
1289
+ if timeout is None:
1290
+ return method(request)
1291
+ return method(request, timeout=timeout)
1253
1292
  except grpc.RpcError as error:
1254
1293
  raise translate_rpc_error(error, operation, flow_id, requirement) from error