dex-python-sdk 0.1.3__tar.gz → 0.1.4__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.1.3 → dex_python_sdk-0.1.4}/PKG-INFO +38 -18
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/README.md +37 -17
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/__init__.py +5 -2
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_grpc_errors.py +3 -3
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_worker_dispatcher.py +20 -7
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/async_client.py +104 -45
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/client.py +104 -44
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/codec.py +15 -13
- dex_python_sdk-0.1.4/dex/dexpb/dex_pb2.py +409 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/dexpb/dex_pb2.pyi +113 -30
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/dexpb/dex_pb2_grpc.py +21 -18
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/flow_options.py +3 -5
- dex_python_sdk-0.1.4/dex/flow_result.py +127 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/runtime_errors.py +9 -29
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/step.py +15 -6
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/pyproject.toml +1 -1
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/Cargo.toml +3 -1
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/Cargo.toml +1 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +2 -1
- dex_python_sdk-0.1.3/dex/dexpb/dex_pb2.py +0 -395
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/LEGACY_NOTICES.md +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/LICENSE +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_async_value_hydrator.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_async_worker_dispatcher.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_async_worker_service.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_invocation_context.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_native.pyi +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_utils.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_value_hydrator.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_value_mapper.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/_worker_service.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/async_worker.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/attribute.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/blob_cache.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/channel.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/client_options.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/condition.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/context.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/dexpb/__init__.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/flow.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/flow_config.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/flow_info.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/py.typed +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/step_execution.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/timer.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/wait.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/worker.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/dex/worker_options.py +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/Cargo.lock +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
- {dex_python_sdk-0.1.3 → dex_python_sdk-0.1.4}/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.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
@@ -71,7 +71,7 @@ registry = dex.Registry((flow,))
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Registry derives codecs from declared Python types and handler annotations.
|
|
74
|
-
Built-in
|
|
74
|
+
Built-in primitive types and dataclasses need no codec arguments. Register an
|
|
75
75
|
explicit codec only for a custom encoding or a type Registry cannot derive.
|
|
76
76
|
`PersistenceSchema.of(...)` accepts attributes and channels together and
|
|
77
77
|
partitions them by definition type.
|
|
@@ -131,6 +131,12 @@ Applications implement two generic interfaces from [`dex`](dex/):
|
|
|
131
131
|
`StepOptions.wait_for_method_timeout` and `execute_method_timeout` bound the
|
|
132
132
|
two handler calls. Timer and channel conditions determine how long a Step waits.
|
|
133
133
|
|
|
134
|
+
`wait_for_retry` and `execute_retry` limit one logical handler execution. With
|
|
135
|
+
`StepDurability.ASYNC`, local and fallback regular activities share maximum
|
|
136
|
+
attempts, total duration, and 1-based attempt numbers. Fallback starts
|
|
137
|
+
immediately; later regular retries continue the backoff sequence at the
|
|
138
|
+
cumulative attempt.
|
|
139
|
+
|
|
134
140
|
`Registry` validates every Flow, Step, RPC signature, durable name, lock, and
|
|
135
141
|
codec before Client or Worker startup. `Client` methods use these typed objects
|
|
136
142
|
instead of raw Flow, Step, or RPC strings.
|
|
@@ -141,14 +147,33 @@ instead of raw Flow, Step, or RPC strings.
|
|
|
141
147
|
`Wait.any_combination_of` must have a non-empty user ID; the same Condition
|
|
142
148
|
instance may appear in multiple combinations.
|
|
143
149
|
|
|
144
|
-
Both `Client` and `AsyncClient` provide
|
|
145
|
-
`
|
|
146
|
-
string, bool, int, or float wire values. JSON objects, bytes, and null fail
|
|
150
|
+
Both `Client` and `AsyncClient` provide singleton and AttributeMap-instance
|
|
151
|
+
overloads of `wait_for_attribute_equal`. They target the current run and accept
|
|
152
|
+
only string, bool, int, or float wire values. JSON objects, bytes, and null fail
|
|
147
153
|
before transport. `AttributeMap.get_map_size/get_all_instance_keys` include
|
|
148
154
|
buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
149
155
|
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
150
156
|
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
151
157
|
|
|
158
|
+
`Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
|
|
159
|
+
`WaitForFlowResult` after hydrating every output-bearing completion. Use
|
|
160
|
+
`single_output` only when the Flow contract produces exactly one output:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
output = client.wait_for_flow(flow_id).single_output(OrderResult)
|
|
164
|
+
|
|
165
|
+
result = client.wait_for_flow(flow_id)
|
|
166
|
+
for completion in result.completions:
|
|
167
|
+
if completion.step_execution_id == expected_execution_id:
|
|
168
|
+
output = completion.decode(OrderResult)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`completions` is an immutable tuple in server collection order. Parallel branch
|
|
172
|
+
order is not deterministic, so select by `step_type` or `step_execution_id`.
|
|
173
|
+
No-output Flows return an empty tuple; `single_output` raises `ValueError` for
|
|
174
|
+
zero or multiple completions. `FlowUncompletedError.completions` uses the same
|
|
175
|
+
hydrated model for partial outputs.
|
|
176
|
+
|
|
152
177
|
### Errors
|
|
153
178
|
|
|
154
179
|
Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
|
|
@@ -199,22 +224,17 @@ implemented. Python owns its gRPC transport; the native bridge is limited to
|
|
|
199
224
|
the shared BlobCache. Design notes:
|
|
200
225
|
[`docs/design/plan/python-sdk-async-apis.md`](../docs/design/plan/python-sdk-async-apis.md).
|
|
201
226
|
|
|
202
|
-
## Running
|
|
227
|
+
## Running Dex locally
|
|
203
228
|
|
|
204
|
-
|
|
205
|
-
See [dex README](https://github.com/superdurable/dex#using-docker-image--docker-compose)
|
|
229
|
+
Install and start the complete local environment with `dexcli`:
|
|
206
230
|
|
|
207
|
-
|
|
231
|
+
```bash
|
|
232
|
+
brew install superdurable/tap/dexcli
|
|
233
|
+
dexcli dev
|
|
234
|
+
```
|
|
208
235
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
- Open the project in VSCode.
|
|
212
|
-
```bash
|
|
213
|
-
cd dex-python-sdk
|
|
214
|
-
code .
|
|
215
|
-
```
|
|
216
|
-
- Launch the Remote-Containers: Reopen in Container command from Command Palette (Ctrl + Shift + P). You can also click in the bottom left corner to access the remote container menu.
|
|
217
|
-
- Once the dev container starts, dex-server will be listening on port 8801.
|
|
236
|
+
Dex Server listens on `127.0.0.1:8801`. See the
|
|
237
|
+
[CLI README](../cli/README.md) for endpoints and persistence options.
|
|
218
238
|
|
|
219
239
|
## How To Contribute
|
|
220
240
|
|
|
@@ -55,7 +55,7 @@ registry = dex.Registry((flow,))
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Registry derives codecs from declared Python types and handler annotations.
|
|
58
|
-
Built-in
|
|
58
|
+
Built-in primitive types and dataclasses need no codec arguments. Register an
|
|
59
59
|
explicit codec only for a custom encoding or a type Registry cannot derive.
|
|
60
60
|
`PersistenceSchema.of(...)` accepts attributes and channels together and
|
|
61
61
|
partitions them by definition type.
|
|
@@ -115,6 +115,12 @@ Applications implement two generic interfaces from [`dex`](dex/):
|
|
|
115
115
|
`StepOptions.wait_for_method_timeout` and `execute_method_timeout` bound the
|
|
116
116
|
two handler calls. Timer and channel conditions determine how long a Step waits.
|
|
117
117
|
|
|
118
|
+
`wait_for_retry` and `execute_retry` limit one logical handler execution. With
|
|
119
|
+
`StepDurability.ASYNC`, local and fallback regular activities share maximum
|
|
120
|
+
attempts, total duration, and 1-based attempt numbers. Fallback starts
|
|
121
|
+
immediately; later regular retries continue the backoff sequence at the
|
|
122
|
+
cumulative attempt.
|
|
123
|
+
|
|
118
124
|
`Registry` validates every Flow, Step, RPC signature, durable name, lock, and
|
|
119
125
|
codec before Client or Worker startup. `Client` methods use these typed objects
|
|
120
126
|
instead of raw Flow, Step, or RPC strings.
|
|
@@ -125,14 +131,33 @@ instead of raw Flow, Step, or RPC strings.
|
|
|
125
131
|
`Wait.any_combination_of` must have a non-empty user ID; the same Condition
|
|
126
132
|
instance may appear in multiple combinations.
|
|
127
133
|
|
|
128
|
-
Both `Client` and `AsyncClient` provide
|
|
129
|
-
`
|
|
130
|
-
string, bool, int, or float wire values. JSON objects, bytes, and null fail
|
|
134
|
+
Both `Client` and `AsyncClient` provide singleton and AttributeMap-instance
|
|
135
|
+
overloads of `wait_for_attribute_equal`. They target the current run and accept
|
|
136
|
+
only string, bool, int, or float wire values. JSON objects, bytes, and null fail
|
|
131
137
|
before transport. `AttributeMap.get_map_size/get_all_instance_keys` include
|
|
132
138
|
buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
133
139
|
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
134
140
|
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
135
141
|
|
|
142
|
+
`Client.wait_for_flow` and `AsyncClient.wait_for_flow` return a
|
|
143
|
+
`WaitForFlowResult` after hydrating every output-bearing completion. Use
|
|
144
|
+
`single_output` only when the Flow contract produces exactly one output:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
output = client.wait_for_flow(flow_id).single_output(OrderResult)
|
|
148
|
+
|
|
149
|
+
result = client.wait_for_flow(flow_id)
|
|
150
|
+
for completion in result.completions:
|
|
151
|
+
if completion.step_execution_id == expected_execution_id:
|
|
152
|
+
output = completion.decode(OrderResult)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`completions` is an immutable tuple in server collection order. Parallel branch
|
|
156
|
+
order is not deterministic, so select by `step_type` or `step_execution_id`.
|
|
157
|
+
No-output Flows return an empty tuple; `single_output` raises `ValueError` for
|
|
158
|
+
zero or multiple completions. `FlowUncompletedError.completions` uses the same
|
|
159
|
+
hydrated model for partial outputs.
|
|
160
|
+
|
|
136
161
|
### Errors
|
|
137
162
|
|
|
138
163
|
Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
|
|
@@ -183,22 +208,17 @@ implemented. Python owns its gRPC transport; the native bridge is limited to
|
|
|
183
208
|
the shared BlobCache. Design notes:
|
|
184
209
|
[`docs/design/plan/python-sdk-async-apis.md`](../docs/design/plan/python-sdk-async-apis.md).
|
|
185
210
|
|
|
186
|
-
## Running
|
|
211
|
+
## Running Dex locally
|
|
187
212
|
|
|
188
|
-
|
|
189
|
-
See [dex README](https://github.com/superdurable/dex#using-docker-image--docker-compose)
|
|
213
|
+
Install and start the complete local environment with `dexcli`:
|
|
190
214
|
|
|
191
|
-
|
|
215
|
+
```bash
|
|
216
|
+
brew install superdurable/tap/dexcli
|
|
217
|
+
dexcli dev
|
|
218
|
+
```
|
|
192
219
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
- Open the project in VSCode.
|
|
196
|
-
```bash
|
|
197
|
-
cd dex-python-sdk
|
|
198
|
-
code .
|
|
199
|
-
```
|
|
200
|
-
- Launch the Remote-Containers: Reopen in Container command from Command Palette (Ctrl + Shift + P). You can also click in the bottom left corner to access the remote container menu.
|
|
201
|
-
- Once the dev container starts, dex-server will be listening on port 8801.
|
|
220
|
+
Dex Server listens on `127.0.0.1:8801`. See the
|
|
221
|
+
[CLI README](../cli/README.md) for endpoints and persistence options.
|
|
202
222
|
|
|
203
223
|
## How To Contribute
|
|
204
224
|
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
# Legacy Materials remain under their original licenses.
|
|
9
9
|
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
10
|
|
|
11
|
+
from dex.async_client import AsyncClient
|
|
12
|
+
from dex.async_worker import AsyncWorker
|
|
11
13
|
from dex.attribute import (
|
|
12
14
|
Attribute,
|
|
13
15
|
AttributeIndex,
|
|
@@ -17,8 +19,6 @@ from dex.attribute import (
|
|
|
17
19
|
)
|
|
18
20
|
from dex.blob_cache import BlobCache, BlobCacheConfig, open_blob_cache
|
|
19
21
|
from dex.channel import Channel, ChannelMap
|
|
20
|
-
from dex.async_client import AsyncClient
|
|
21
|
-
from dex.async_worker import AsyncWorker
|
|
22
22
|
from dex.client import Client
|
|
23
23
|
from dex.client_options import ClientOptions
|
|
24
24
|
from dex.codec import (
|
|
@@ -52,6 +52,7 @@ from dex.flow_options import (
|
|
|
52
52
|
StopFlowOptions,
|
|
53
53
|
StopType,
|
|
54
54
|
)
|
|
55
|
+
from dex.flow_result import StepCompletion, WaitForFlowResult
|
|
55
56
|
from dex.runtime_errors import (
|
|
56
57
|
DexServiceError,
|
|
57
58
|
ErrorSubStatus,
|
|
@@ -140,6 +141,7 @@ __all__ = [
|
|
|
140
141
|
"RetryPolicy",
|
|
141
142
|
"SearchFlowEntry",
|
|
142
143
|
"SearchFlowsPage",
|
|
144
|
+
"StepCompletion",
|
|
143
145
|
"StartFlowOptions",
|
|
144
146
|
"StepExecutionId",
|
|
145
147
|
"StepDecision",
|
|
@@ -156,6 +158,7 @@ __all__ = [
|
|
|
156
158
|
"ValueMappingError",
|
|
157
159
|
"Wait",
|
|
158
160
|
"WaitForFailurePolicy",
|
|
161
|
+
"WaitForFlowResult",
|
|
159
162
|
"WireKind",
|
|
160
163
|
"Worker",
|
|
161
164
|
"WorkerInvocationError",
|
|
@@ -36,12 +36,12 @@ def translate_rpc_error(
|
|
|
36
36
|
flow_id: str | None,
|
|
37
37
|
requirement: FlowTargetRequirement,
|
|
38
38
|
) -> DexServiceError:
|
|
39
|
-
details: pb.
|
|
39
|
+
details: pb.ServiceErrorResponse | None = None
|
|
40
40
|
try:
|
|
41
41
|
status = rpc_status.from_call(cast(grpc.Call, error))
|
|
42
42
|
if status is not None:
|
|
43
43
|
for packed in status.details:
|
|
44
|
-
candidate = pb.
|
|
44
|
+
candidate = pb.ServiceErrorResponse()
|
|
45
45
|
if packed.Is(candidate.DESCRIPTOR):
|
|
46
46
|
packed.Unpack(candidate)
|
|
47
47
|
details = candidate
|
|
@@ -87,7 +87,7 @@ def translate_rpc_error(
|
|
|
87
87
|
return DexServiceError(*parameters)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
def _worker_code(details: pb.
|
|
90
|
+
def _worker_code(details: pb.ServiceErrorResponse | None) -> grpc.StatusCode | None:
|
|
91
91
|
if details is None or details.original_worker_error_status == 0:
|
|
92
92
|
return None
|
|
93
93
|
return next(
|
|
@@ -279,11 +279,19 @@ class WorkerDispatcher:
|
|
|
279
279
|
mapped.next_steps.extend(self._map_movements(flow, decision.movements))
|
|
280
280
|
elif decision.kind is DecisionKind.GRACEFUL_COMPLETE:
|
|
281
281
|
mapped.close_decision.CopyFrom(
|
|
282
|
-
self._close(
|
|
282
|
+
self._close(
|
|
283
|
+
pb.CLOSE_DECISION_TYPE_GRACEFUL_COMPLETE,
|
|
284
|
+
decision.output,
|
|
285
|
+
decision._has_output(),
|
|
286
|
+
)
|
|
283
287
|
)
|
|
284
288
|
elif decision.kind is DecisionKind.FORCE_COMPLETE:
|
|
285
289
|
mapped.close_decision.CopyFrom(
|
|
286
|
-
self._close(
|
|
290
|
+
self._close(
|
|
291
|
+
pb.CLOSE_DECISION_TYPE_FORCE_COMPLETE,
|
|
292
|
+
decision.output,
|
|
293
|
+
decision._has_output(),
|
|
294
|
+
)
|
|
287
295
|
)
|
|
288
296
|
elif decision.kind is DecisionKind.FORCE_FAIL:
|
|
289
297
|
mapped.close_decision.CopyFrom(
|
|
@@ -314,11 +322,16 @@ class WorkerDispatcher:
|
|
|
314
322
|
raise ValueError("unsupported StepDecision kind")
|
|
315
323
|
return mapped
|
|
316
324
|
|
|
317
|
-
def _close(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
325
|
+
def _close(
|
|
326
|
+
self,
|
|
327
|
+
close_type: int,
|
|
328
|
+
output: object,
|
|
329
|
+
has_output: bool = True,
|
|
330
|
+
) -> pb.CloseDecision:
|
|
331
|
+
close = pb.CloseDecision(close_decision_type=cast(Any, close_type))
|
|
332
|
+
if has_output:
|
|
333
|
+
close.close_input.CopyFrom(self._values.encode_dynamic(output))
|
|
334
|
+
return close
|
|
322
335
|
|
|
323
336
|
def _map_movements(
|
|
324
337
|
self,
|
|
@@ -40,10 +40,8 @@ from dex.flow_options import (
|
|
|
40
40
|
StopFlowOptions,
|
|
41
41
|
StopType,
|
|
42
42
|
)
|
|
43
|
-
from dex.
|
|
44
|
-
|
|
45
|
-
FlowUncompletedError,
|
|
46
|
-
)
|
|
43
|
+
from dex.flow_result import StepCompletion, WaitForFlowResult
|
|
44
|
+
from dex.runtime_errors import FlowErrorType, FlowUncompletedError
|
|
47
45
|
from dex.step import RetryPolicy, StepDurability
|
|
48
46
|
from dex.step_execution import StepExecutionId, TimerId
|
|
49
47
|
|
|
@@ -67,7 +65,7 @@ class AsyncClient:
|
|
|
67
65
|
Examples:
|
|
68
66
|
>>> async with AsyncClient(registry, cache) as client:
|
|
69
67
|
... run_id = await client.start_flow(orders, "order-42", input)
|
|
70
|
-
... result = await client.wait_for_flow("order-42"
|
|
68
|
+
... result = (await client.wait_for_flow("order-42")).single_output(OrderResult)
|
|
71
69
|
"""
|
|
72
70
|
|
|
73
71
|
def __init__(
|
|
@@ -500,54 +498,44 @@ class AsyncClient:
|
|
|
500
498
|
"active",
|
|
501
499
|
)
|
|
502
500
|
|
|
503
|
-
@overload
|
|
504
|
-
async def wait_for_flow(self, flow_id: str) -> None: ...
|
|
505
|
-
|
|
506
|
-
@overload
|
|
507
|
-
async def wait_for_flow(
|
|
508
|
-
self,
|
|
509
|
-
flow_id: str,
|
|
510
|
-
output_type: type[OutputT],
|
|
511
|
-
timeout: timedelta | None = None,
|
|
512
|
-
) -> OutputT: ...
|
|
513
|
-
|
|
514
501
|
async def wait_for_flow(
|
|
515
502
|
self,
|
|
516
503
|
flow_id: str,
|
|
517
|
-
output_type: type[Any] | None = None,
|
|
518
504
|
timeout: timedelta | None = None,
|
|
519
|
-
) ->
|
|
520
|
-
"""Await Flow closure and
|
|
505
|
+
) -> WaitForFlowResult:
|
|
506
|
+
"""Await Flow closure and return all output-bearing completions.
|
|
521
507
|
|
|
522
508
|
``timeout`` is a server-side long-poll duration, not a local task deadline.
|
|
523
|
-
A LongPollTimeoutError is retryable by awaiting this method again.
|
|
509
|
+
A LongPollTimeoutError is retryable by awaiting this method again. Parallel
|
|
510
|
+
completion order is not deterministic; select results by Step identity.
|
|
524
511
|
|
|
525
512
|
Args:
|
|
526
513
|
flow_id: The non-empty target Flow ID.
|
|
527
|
-
output_type: Optional Python type for the latest completed Step output.
|
|
528
514
|
timeout: Optional non-negative server-side wait duration.
|
|
529
515
|
|
|
530
516
|
Returns:
|
|
531
|
-
|
|
517
|
+
An immutable snapshot of every output-bearing Step completion.
|
|
532
518
|
|
|
533
519
|
Raises:
|
|
534
520
|
LongPollTimeoutError: If the Flow remains open when the wait expires.
|
|
535
521
|
FlowUncompletedError: If the Flow closes without successful completion.
|
|
536
522
|
FlowNotFoundError: If ``flow_id`` does not exist.
|
|
537
|
-
ValueMappingError: If a
|
|
523
|
+
ValueMappingError: If Dex returns a malformed or incompatible hydrated value.
|
|
538
524
|
DexServiceError: If FlowService cannot perform the wait.
|
|
539
525
|
"""
|
|
540
526
|
response = await self._wait_for_flow_response(flow_id, timeout)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
527
|
+
hydrated = await self._hydrator.step_outputs(list(response.results))
|
|
528
|
+
return WaitForFlowResult(
|
|
529
|
+
[
|
|
530
|
+
StepCompletion(
|
|
531
|
+
result,
|
|
532
|
+
lambda value, output_type: self._values.decode(
|
|
533
|
+
value, self._values.codec(output_type)
|
|
534
|
+
),
|
|
549
535
|
)
|
|
550
|
-
|
|
536
|
+
for result in hydrated
|
|
537
|
+
]
|
|
538
|
+
)
|
|
551
539
|
|
|
552
540
|
async def stop_flow(
|
|
553
541
|
self,
|
|
@@ -700,8 +688,7 @@ class AsyncClient:
|
|
|
700
688
|
ResetType.STEP_EXECUTION_ID: pb.FLOW_RESET_TYPE_STEP_EXECUTION_ID,
|
|
701
689
|
}[options.type],
|
|
702
690
|
reason=options.reason or "",
|
|
703
|
-
|
|
704
|
-
skip_locking_rpc_reapply=options.skip_locking_rpc_reapply,
|
|
691
|
+
skip_writes_reapply=options.skip_writes_reapply,
|
|
705
692
|
)
|
|
706
693
|
if options.history_event_id is not None:
|
|
707
694
|
request.history_event_id = options.history_event_id
|
|
@@ -787,6 +774,7 @@ class AsyncClient:
|
|
|
787
774
|
"active",
|
|
788
775
|
)
|
|
789
776
|
|
|
777
|
+
@overload
|
|
790
778
|
async def wait_for_attribute_equal(
|
|
791
779
|
self,
|
|
792
780
|
flow_id: str,
|
|
@@ -794,7 +782,7 @@ class AsyncClient:
|
|
|
794
782
|
expected: ValueT,
|
|
795
783
|
timeout: timedelta,
|
|
796
784
|
) -> None:
|
|
797
|
-
"""Await a
|
|
785
|
+
"""Await a singleton Attribute in the current run equaling ``expected``.
|
|
798
786
|
|
|
799
787
|
The Client generates a request ID. JSON, bytes, and null values raise
|
|
800
788
|
``ValueError`` before transport. A remote expiry raises
|
|
@@ -802,7 +790,7 @@ class AsyncClient:
|
|
|
802
790
|
|
|
803
791
|
Args:
|
|
804
792
|
flow_id: The non-empty active Flow ID.
|
|
805
|
-
attribute: The registered
|
|
793
|
+
attribute: The registered singleton Attribute to observe.
|
|
806
794
|
expected: The string, bool, int, or float value to await.
|
|
807
795
|
timeout: The non-negative server-side wait duration.
|
|
808
796
|
|
|
@@ -812,11 +800,10 @@ class AsyncClient:
|
|
|
812
800
|
FlowNotActiveError: If the Flow closes first.
|
|
813
801
|
DexServiceError: If FlowService cannot perform the wait.
|
|
814
802
|
"""
|
|
815
|
-
|
|
816
|
-
flow_id, attribute, None, expected, timeout
|
|
817
|
-
)
|
|
803
|
+
...
|
|
818
804
|
|
|
819
|
-
|
|
805
|
+
@overload
|
|
806
|
+
async def wait_for_attribute_equal(
|
|
820
807
|
self,
|
|
821
808
|
flow_id: str,
|
|
822
809
|
attribute: AttributeMap[ValueT],
|
|
@@ -824,9 +811,9 @@ class AsyncClient:
|
|
|
824
811
|
expected: ValueT,
|
|
825
812
|
timeout: timedelta,
|
|
826
813
|
) -> None:
|
|
827
|
-
"""Await one
|
|
814
|
+
"""Await one AttributeMap instance in the current run.
|
|
828
815
|
|
|
829
|
-
|
|
816
|
+
Primitive-value restrictions, request-ID generation, timeout behavior, and
|
|
830
817
|
service errors match :meth:`wait_for_attribute_equal`.
|
|
831
818
|
|
|
832
819
|
Args:
|
|
@@ -842,6 +829,36 @@ class AsyncClient:
|
|
|
842
829
|
FlowNotActiveError: If the Flow closes first.
|
|
843
830
|
DexServiceError: If FlowService cannot perform the wait.
|
|
844
831
|
"""
|
|
832
|
+
...
|
|
833
|
+
|
|
834
|
+
async def wait_for_attribute_equal(
|
|
835
|
+
self,
|
|
836
|
+
flow_id: str,
|
|
837
|
+
attribute: Attribute[Any] | AttributeMap[Any],
|
|
838
|
+
*args: object,
|
|
839
|
+
**kwargs: object,
|
|
840
|
+
) -> None:
|
|
841
|
+
"""Await a singleton Attribute or AttributeMap instance equaling a value.
|
|
842
|
+
|
|
843
|
+
Singleton form is ``wait_for_attribute_equal(flow_id, attribute, expected,
|
|
844
|
+
timeout)``; map form adds ``instance`` before ``expected``.
|
|
845
|
+
|
|
846
|
+
Args:
|
|
847
|
+
flow_id: The non-empty active Flow ID.
|
|
848
|
+
attribute: The registered Attribute or AttributeMap to observe.
|
|
849
|
+
*args: Positional expected value and timeout, optionally preceded by a map instance.
|
|
850
|
+
**kwargs: The same arguments supplied by name.
|
|
851
|
+
|
|
852
|
+
Raises:
|
|
853
|
+
TypeError: If arguments do not match the Attribute definition.
|
|
854
|
+
ValueError: If an identifier, timeout, or expected value is invalid.
|
|
855
|
+
LongPollTimeoutError: If equality is not observed before the timeout.
|
|
856
|
+
FlowNotActiveError: If the Flow closes first.
|
|
857
|
+
DexServiceError: If FlowService cannot perform the wait.
|
|
858
|
+
"""
|
|
859
|
+
instance, expected, timeout = self._attribute_wait_arguments(
|
|
860
|
+
attribute, args, kwargs
|
|
861
|
+
)
|
|
845
862
|
await self._wait_for_attribute_equal(
|
|
846
863
|
flow_id, attribute, instance, expected, timeout
|
|
847
864
|
)
|
|
@@ -864,7 +881,9 @@ class AsyncClient:
|
|
|
864
881
|
"int_value",
|
|
865
882
|
"double_value",
|
|
866
883
|
}:
|
|
867
|
-
raise ValueError(
|
|
884
|
+
raise ValueError(
|
|
885
|
+
"wait_for_attribute_equal supports only string, boolean, or number values"
|
|
886
|
+
)
|
|
868
887
|
await self._call(
|
|
869
888
|
self._service.WaitForAttribute,
|
|
870
889
|
pb.WaitForAttributeRequest(
|
|
@@ -981,13 +1000,21 @@ class AsyncClient:
|
|
|
981
1000
|
if response.flow_status != pb.FLOW_STATUS_COMPLETED:
|
|
982
1001
|
info = await self.describe_flow(flow_id)
|
|
983
1002
|
results = await self._hydrator.step_outputs(list(response.results))
|
|
1003
|
+
completions = [
|
|
1004
|
+
StepCompletion(
|
|
1005
|
+
result,
|
|
1006
|
+
lambda value, output_type: self._values.decode(
|
|
1007
|
+
value, self._values.codec(output_type)
|
|
1008
|
+
),
|
|
1009
|
+
)
|
|
1010
|
+
for result in results
|
|
1011
|
+
]
|
|
984
1012
|
raise FlowUncompletedError(
|
|
985
1013
|
info.run_id,
|
|
986
1014
|
self._map_flow_status(response.flow_status),
|
|
987
1015
|
self._map_flow_error_type(response.error_type),
|
|
988
1016
|
response.error_message or None,
|
|
989
|
-
|
|
990
|
-
self._values,
|
|
1017
|
+
completions,
|
|
991
1018
|
)
|
|
992
1019
|
return response
|
|
993
1020
|
|
|
@@ -1126,6 +1153,38 @@ class AsyncClient:
|
|
|
1126
1153
|
return args[0], args[1]
|
|
1127
1154
|
raise TypeError("set_attribute received invalid arguments")
|
|
1128
1155
|
|
|
1156
|
+
@staticmethod
|
|
1157
|
+
def _attribute_wait_arguments(
|
|
1158
|
+
definition: Attribute[Any] | AttributeMap[Any],
|
|
1159
|
+
args: tuple[object, ...],
|
|
1160
|
+
kwargs: dict[str, object],
|
|
1161
|
+
) -> tuple[str | None, object, timedelta]:
|
|
1162
|
+
parameter_names: tuple[str, ...]
|
|
1163
|
+
if isinstance(definition, Attribute):
|
|
1164
|
+
parameter_names = ("expected", "timeout")
|
|
1165
|
+
elif isinstance(definition, AttributeMap):
|
|
1166
|
+
parameter_names = ("instance", "expected", "timeout")
|
|
1167
|
+
else:
|
|
1168
|
+
raise TypeError("wait_for_attribute_equal received invalid arguments")
|
|
1169
|
+
if len(args) > len(parameter_names):
|
|
1170
|
+
raise TypeError("wait_for_attribute_equal received invalid arguments")
|
|
1171
|
+
arguments = dict(zip(parameter_names, args))
|
|
1172
|
+
for name, value in kwargs.items():
|
|
1173
|
+
if name not in parameter_names or name in arguments:
|
|
1174
|
+
raise TypeError("wait_for_attribute_equal received invalid arguments")
|
|
1175
|
+
arguments[name] = value
|
|
1176
|
+
if set(arguments) != set(parameter_names):
|
|
1177
|
+
raise TypeError("wait_for_attribute_equal received invalid arguments")
|
|
1178
|
+
timeout = arguments["timeout"]
|
|
1179
|
+
if not isinstance(timeout, timedelta):
|
|
1180
|
+
raise TypeError("wait_for_attribute_equal received invalid arguments")
|
|
1181
|
+
if isinstance(definition, Attribute):
|
|
1182
|
+
return None, arguments["expected"], timeout
|
|
1183
|
+
instance = arguments["instance"]
|
|
1184
|
+
if not isinstance(instance, str):
|
|
1185
|
+
raise TypeError("wait_for_attribute_equal received invalid arguments")
|
|
1186
|
+
return instance, arguments["expected"], timeout
|
|
1187
|
+
|
|
1129
1188
|
@staticmethod
|
|
1130
1189
|
def _seconds32(duration: timedelta) -> int:
|
|
1131
1190
|
seconds = duration.total_seconds()
|