dex-python-sdk 0.6.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.
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/PKG-INFO +12 -1
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/README.md +11 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/__init__.py +8 -3
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_grpc_errors.py +38 -11
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/async_client.py +89 -65
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/client.py +90 -66
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/dexpb/dex_pb2.py +22 -22
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/dexpb/dex_pb2.pyi +2 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/dexpb/dex_pb2_grpc.py +18 -18
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/runtime_errors.py +8 -0
- dex_python_sdk-0.7.0/dex/wait_options.py +76 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/pyproject.toml +1 -1
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/Cargo.lock +6 -6
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/Cargo.toml +3 -3
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/LEGACY_NOTICES.md +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/LICENSE +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_async_value_hydrator.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_async_worker_dispatcher.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_async_worker_service.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_invocation_context.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_native.pyi +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_utils.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_value_hydrator.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_value_mapper.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_worker_dispatcher.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/_worker_service.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/async_worker.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/attribute.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/attribute_match.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/blob_cache.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/channel.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/client_options.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/codec.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/condition.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/context.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/dexpb/__init__.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/flow.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/flow_config.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/flow_info.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/flow_options.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/flow_result.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/py.typed +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/step.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/step_execution.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/stream.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/subflow.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/timer.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/wait.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/worker.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/dex/worker_options.py +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
- {dex_python_sdk-0.6.0 → dex_python_sdk-0.7.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
- {dex_python_sdk-0.6.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.
|
|
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
|
|
@@ -361,6 +361,17 @@ buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
|
361
361
|
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
362
362
|
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
363
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
|
+
|
|
364
375
|
`Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
|
|
365
376
|
`FlowResult` after hydrating every output-bearing completion. Use
|
|
366
377
|
`single_output` only when the Flow contract produces exactly one output:
|
|
@@ -345,6 +345,17 @@ buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
|
345
345
|
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
346
346
|
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
347
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
|
+
|
|
348
359
|
`Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
|
|
349
360
|
`FlowResult` after hydrating every output-bearing completion. Use
|
|
350
361
|
`single_output` only when the Flow contract produces exactly one output:
|
|
@@ -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
|
-
|
|
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 =
|
|
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=
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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)
|
|
@@ -45,10 +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
|
|
48
|
+
from dex.runtime_errors import 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
|
+
from dex.wait_options import (
|
|
53
|
+
WaitForAttributeOptions,
|
|
54
|
+
WaitForStepCompletionOptions,
|
|
55
|
+
_ClientWaitBudget,
|
|
56
|
+
)
|
|
52
57
|
|
|
53
58
|
InputT = TypeVar("InputT")
|
|
54
59
|
OutputT = TypeVar("OutputT")
|
|
@@ -723,33 +728,45 @@ class AsyncClient:
|
|
|
723
728
|
self,
|
|
724
729
|
flow_id: str,
|
|
725
730
|
step_execution_id: StepExecutionId,
|
|
726
|
-
|
|
731
|
+
options: WaitForStepCompletionOptions,
|
|
727
732
|
) -> None:
|
|
728
|
-
"""Await one Step execution's completion or
|
|
733
|
+
"""Await one Step execution's completion or its handler budget expiry.
|
|
734
|
+
|
|
735
|
+
The server derives a stable Request ID from the Step execution when none
|
|
736
|
+
is supplied. Transport long polls automatically reattach to the same wait.
|
|
729
737
|
|
|
730
738
|
Args:
|
|
731
739
|
flow_id: The non-empty active Flow ID.
|
|
732
740
|
step_execution_id: The Step type and positive execution number.
|
|
733
|
-
|
|
741
|
+
options: The optional Request ID override and total handler wait budget.
|
|
734
742
|
|
|
735
743
|
Raises:
|
|
736
|
-
|
|
744
|
+
ValueError: If the Request ID or handler wait budget is invalid.
|
|
745
|
+
WaitHandlerTimeoutError: If a positive handler budget expires first.
|
|
737
746
|
FlowNotActiveError: If the Flow closes first.
|
|
738
747
|
DexServiceError: If FlowService cannot perform the wait.
|
|
739
748
|
"""
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
749
|
+
wait_budget = _ClientWaitBudget(options.maximum_wait_time)
|
|
750
|
+
while True:
|
|
751
|
+
try:
|
|
752
|
+
await self._call(
|
|
753
|
+
self._service.WaitForStepCompletion,
|
|
754
|
+
pb.WaitForStepCompletionRequest(
|
|
755
|
+
flow_id=require_name(flow_id),
|
|
756
|
+
step_type=step_execution_id.step_type,
|
|
757
|
+
step_execution_number=str(step_execution_id.number),
|
|
758
|
+
wait_time_seconds=wait_budget.remaining_seconds(
|
|
759
|
+
"wait_for_step_completion", flow_id
|
|
760
|
+
),
|
|
761
|
+
request_id=options.request_id,
|
|
762
|
+
),
|
|
763
|
+
"wait_for_step_completion",
|
|
764
|
+
flow_id,
|
|
765
|
+
"active",
|
|
766
|
+
)
|
|
767
|
+
return
|
|
768
|
+
except LongPollTimeoutError:
|
|
769
|
+
continue
|
|
753
770
|
|
|
754
771
|
@overload
|
|
755
772
|
async def wait_for_attribute_match(
|
|
@@ -757,26 +774,26 @@ class AsyncClient:
|
|
|
757
774
|
flow_id: str,
|
|
758
775
|
attribute: Attribute[ValueT],
|
|
759
776
|
match: AttributeMatch[ValueT],
|
|
760
|
-
|
|
777
|
+
options: WaitForAttributeOptions,
|
|
761
778
|
) -> ValueT:
|
|
762
779
|
"""Await a singleton Attribute in the current run satisfying ``match``.
|
|
763
780
|
|
|
764
|
-
The Client returns the value observed by the successful wait.
|
|
765
|
-
|
|
766
|
-
|
|
781
|
+
The Client returns the value observed by the successful wait. The server
|
|
782
|
+
derives a stable Request ID from the condition when none is supplied.
|
|
783
|
+
JSON, bytes, and null operands raise ``ValueError`` before transport.
|
|
767
784
|
|
|
768
785
|
Args:
|
|
769
786
|
flow_id: The non-empty active Flow ID.
|
|
770
787
|
attribute: The registered singleton Attribute to observe.
|
|
771
788
|
match: The scalar predicate to await.
|
|
772
|
-
|
|
789
|
+
options: The optional Request ID override and total handler wait budget.
|
|
773
790
|
|
|
774
791
|
Returns:
|
|
775
792
|
The current Attribute value that satisfied ``match``.
|
|
776
793
|
|
|
777
794
|
Raises:
|
|
778
|
-
ValueError: If an identifier,
|
|
779
|
-
|
|
795
|
+
ValueError: If an identifier, option, or match operand is invalid.
|
|
796
|
+
WaitHandlerTimeoutError: If a positive handler budget expires first.
|
|
780
797
|
FlowNotActiveError: If the Flow closes first.
|
|
781
798
|
DexServiceError: If FlowService cannot perform the wait.
|
|
782
799
|
"""
|
|
@@ -789,11 +806,11 @@ class AsyncClient:
|
|
|
789
806
|
attribute: AttributeMap[ValueT],
|
|
790
807
|
instance: str,
|
|
791
808
|
match: AttributeMatch[ValueT],
|
|
792
|
-
|
|
809
|
+
options: WaitForAttributeOptions,
|
|
793
810
|
) -> ValueT:
|
|
794
811
|
"""Await one AttributeMap instance in the current run satisfying a match.
|
|
795
812
|
|
|
796
|
-
Match restrictions,
|
|
813
|
+
Match restrictions, Request ID, handler-budget behavior, and service
|
|
797
814
|
errors match :meth:`wait_for_attribute_match`.
|
|
798
815
|
|
|
799
816
|
Args:
|
|
@@ -801,14 +818,14 @@ class AsyncClient:
|
|
|
801
818
|
attribute: The registered AttributeMap to observe.
|
|
802
819
|
instance: The map instance to observe. Slash is prohibited because it is a reserved character.
|
|
803
820
|
match: The scalar predicate to await.
|
|
804
|
-
|
|
821
|
+
options: The optional Request ID override and total handler wait budget.
|
|
805
822
|
|
|
806
823
|
Returns:
|
|
807
824
|
The current AttributeMap value that satisfied ``match``.
|
|
808
825
|
|
|
809
826
|
Raises:
|
|
810
|
-
ValueError: If an identifier,
|
|
811
|
-
|
|
827
|
+
ValueError: If an identifier, option, or match operand is invalid.
|
|
828
|
+
WaitHandlerTimeoutError: If a positive handler budget expires first.
|
|
812
829
|
FlowNotActiveError: If the Flow closes first.
|
|
813
830
|
DexServiceError: If FlowService cannot perform the wait.
|
|
814
831
|
"""
|
|
@@ -824,12 +841,12 @@ class AsyncClient:
|
|
|
824
841
|
"""Await a singleton Attribute or AttributeMap instance satisfying a match.
|
|
825
842
|
|
|
826
843
|
Singleton form is ``wait_for_attribute_match(flow_id, attribute, match,
|
|
827
|
-
|
|
844
|
+
options)``; map form adds ``instance`` before ``match``.
|
|
828
845
|
|
|
829
846
|
Args:
|
|
830
847
|
flow_id: The non-empty active Flow ID.
|
|
831
848
|
attribute: The registered Attribute or AttributeMap to observe.
|
|
832
|
-
*args: Positional match and
|
|
849
|
+
*args: Positional match and options, optionally preceded by a map instance.
|
|
833
850
|
**kwargs: The same arguments supplied by name.
|
|
834
851
|
|
|
835
852
|
Returns:
|
|
@@ -837,16 +854,16 @@ class AsyncClient:
|
|
|
837
854
|
|
|
838
855
|
Raises:
|
|
839
856
|
TypeError: If arguments do not match the Attribute definition.
|
|
840
|
-
ValueError: If an identifier,
|
|
841
|
-
|
|
857
|
+
ValueError: If an identifier, option, or expected value is invalid.
|
|
858
|
+
WaitHandlerTimeoutError: If a positive handler budget expires first.
|
|
842
859
|
FlowNotActiveError: If the Flow closes first.
|
|
843
860
|
DexServiceError: If FlowService cannot perform the wait.
|
|
844
861
|
"""
|
|
845
|
-
instance, match,
|
|
862
|
+
instance, match, options = self._attribute_wait_arguments(
|
|
846
863
|
attribute, args, kwargs
|
|
847
864
|
)
|
|
848
865
|
return await self._wait_for_attribute_match(
|
|
849
|
-
flow_id, attribute, instance, match,
|
|
866
|
+
flow_id, attribute, instance, match, options
|
|
850
867
|
)
|
|
851
868
|
|
|
852
869
|
async def _wait_for_attribute_match(
|
|
@@ -855,7 +872,7 @@ class AsyncClient:
|
|
|
855
872
|
attribute: Attribute[Any] | AttributeMap[Any],
|
|
856
873
|
instance: str | None,
|
|
857
874
|
match: object,
|
|
858
|
-
|
|
875
|
+
options: WaitForAttributeOptions,
|
|
859
876
|
) -> Any:
|
|
860
877
|
encoded_match = _encode_attribute_match(
|
|
861
878
|
cast(AttributeMatch[Any], match),
|
|
@@ -863,27 +880,34 @@ class AsyncClient:
|
|
|
863
880
|
self._values.codec(attribute.value_type),
|
|
864
881
|
)
|
|
865
882
|
encoded_match.key = self._definition_name(attribute, instance)
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
883
|
+
wait_budget = _ClientWaitBudget(options.maximum_wait_time)
|
|
884
|
+
while True:
|
|
885
|
+
try:
|
|
886
|
+
response = cast(
|
|
887
|
+
pb.WaitForAttributeResponse,
|
|
888
|
+
await self._call(
|
|
889
|
+
self._service.WaitForAttribute,
|
|
890
|
+
pb.WaitForAttributeRequest(
|
|
891
|
+
flow_id=require_name(flow_id),
|
|
892
|
+
match=encoded_match,
|
|
893
|
+
wait_time_seconds=wait_budget.remaining_seconds(
|
|
894
|
+
"wait_for_attribute_match", flow_id
|
|
895
|
+
),
|
|
896
|
+
request_id=options.request_id,
|
|
897
|
+
),
|
|
898
|
+
"wait_for_attribute_match",
|
|
899
|
+
flow_id,
|
|
900
|
+
"active",
|
|
901
|
+
),
|
|
902
|
+
)
|
|
903
|
+
if not response.HasField("matched_value"):
|
|
904
|
+
raise ValueError("wait_for_attribute_match response is incomplete")
|
|
905
|
+
return self._values.decode(
|
|
906
|
+
response.matched_value,
|
|
907
|
+
self._values.codec(attribute.value_type),
|
|
908
|
+
)
|
|
909
|
+
except LongPollTimeoutError:
|
|
910
|
+
continue
|
|
887
911
|
|
|
888
912
|
async def update_flow_config(self, flow_id: str, config: FlowConfig) -> None:
|
|
889
913
|
"""Replace mutable configuration for an active Flow.
|
|
@@ -1108,12 +1132,12 @@ class AsyncClient:
|
|
|
1108
1132
|
definition: Attribute[Any] | AttributeMap[Any],
|
|
1109
1133
|
args: tuple[object, ...],
|
|
1110
1134
|
kwargs: dict[str, object],
|
|
1111
|
-
) -> tuple[str | None, object,
|
|
1135
|
+
) -> tuple[str | None, object, WaitForAttributeOptions]:
|
|
1112
1136
|
parameter_names: tuple[str, ...]
|
|
1113
1137
|
if isinstance(definition, Attribute):
|
|
1114
|
-
parameter_names = ("match", "
|
|
1138
|
+
parameter_names = ("match", "options")
|
|
1115
1139
|
elif isinstance(definition, AttributeMap):
|
|
1116
|
-
parameter_names = ("instance", "match", "
|
|
1140
|
+
parameter_names = ("instance", "match", "options")
|
|
1117
1141
|
else:
|
|
1118
1142
|
raise TypeError("wait_for_attribute_match received invalid arguments")
|
|
1119
1143
|
if len(args) > len(parameter_names):
|
|
@@ -1125,15 +1149,15 @@ class AsyncClient:
|
|
|
1125
1149
|
arguments[name] = value
|
|
1126
1150
|
if set(arguments) != set(parameter_names):
|
|
1127
1151
|
raise TypeError("wait_for_attribute_match received invalid arguments")
|
|
1128
|
-
|
|
1129
|
-
if not isinstance(
|
|
1152
|
+
options = arguments["options"]
|
|
1153
|
+
if not isinstance(options, WaitForAttributeOptions):
|
|
1130
1154
|
raise TypeError("wait_for_attribute_match received invalid arguments")
|
|
1131
1155
|
if isinstance(definition, Attribute):
|
|
1132
|
-
return None, arguments["match"],
|
|
1156
|
+
return None, arguments["match"], options
|
|
1133
1157
|
instance = arguments["instance"]
|
|
1134
1158
|
if not isinstance(instance, str):
|
|
1135
1159
|
raise TypeError("wait_for_attribute_match received invalid arguments")
|
|
1136
|
-
return instance, arguments["match"],
|
|
1160
|
+
return instance, arguments["match"], options
|
|
1137
1161
|
|
|
1138
1162
|
@staticmethod
|
|
1139
1163
|
def _resolve_timeout_policy(
|