dex-python-sdk 0.1.2__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.2 → dex_python_sdk-0.1.4}/PKG-INFO +49 -15
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/README.md +48 -14
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/__init__.py +7 -4
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_grpc_errors.py +3 -3
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_invocation_context.py +33 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_worker_dispatcher.py +34 -19
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/async_client.py +191 -36
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/async_worker.py +3 -3
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/attribute.py +22 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/channel.py +22 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/client.py +190 -37
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/codec.py +15 -13
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/context.py +10 -0
- dex_python_sdk-0.1.4/dex/dexpb/dex_pb2.py +409 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/dexpb/dex_pb2.pyi +113 -30
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/dexpb/dex_pb2_grpc.py +21 -18
- {dex_python_sdk-0.1.2 → 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.2 → dex_python_sdk-0.1.4}/dex/runtime_errors.py +9 -29
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/step.py +16 -7
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/pyproject.toml +1 -1
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/Cargo.toml +3 -1
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/Cargo.toml +1 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +2 -1
- dex_python_sdk-0.1.2/dex/dexpb/dex_pb2.py +0 -395
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/LEGACY_NOTICES.md +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/LICENSE +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_async_value_hydrator.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_async_worker_dispatcher.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_async_worker_service.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_native.pyi +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_utils.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_value_hydrator.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_value_mapper.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/_worker_service.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/blob_cache.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/client_options.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/condition.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/dexpb/__init__.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/flow.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/flow_config.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/flow_info.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/py.typed +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/step_execution.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/timer.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/wait.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/worker.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/dex/worker_options.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/Cargo.lock +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.4}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
- {dex_python_sdk-0.1.2 → 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,10 +131,49 @@ 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.
|
|
137
143
|
|
|
144
|
+
### Waiting and map inspection
|
|
145
|
+
|
|
146
|
+
`Wait.all_of` and `Wait.any_of` may use unnamed Conditions. Every Condition in
|
|
147
|
+
`Wait.any_combination_of` must have a non-empty user ID; the same Condition
|
|
148
|
+
instance may appear in multiple combinations.
|
|
149
|
+
|
|
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
|
|
153
|
+
before transport. `AttributeMap.get_map_size/get_all_instance_keys` include
|
|
154
|
+
buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
155
|
+
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
156
|
+
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
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
|
+
|
|
138
177
|
### Errors
|
|
139
178
|
|
|
140
179
|
Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
|
|
@@ -185,22 +224,17 @@ implemented. Python owns its gRPC transport; the native bridge is limited to
|
|
|
185
224
|
the shared BlobCache. Design notes:
|
|
186
225
|
[`docs/design/plan/python-sdk-async-apis.md`](../docs/design/plan/python-sdk-async-apis.md).
|
|
187
226
|
|
|
188
|
-
## Running
|
|
227
|
+
## Running Dex locally
|
|
189
228
|
|
|
190
|
-
|
|
191
|
-
See [dex README](https://github.com/superdurable/dex#using-docker-image--docker-compose)
|
|
229
|
+
Install and start the complete local environment with `dexcli`:
|
|
192
230
|
|
|
193
|
-
|
|
231
|
+
```bash
|
|
232
|
+
brew install superdurable/tap/dexcli
|
|
233
|
+
dexcli dev
|
|
234
|
+
```
|
|
194
235
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
- Open the project in VSCode.
|
|
198
|
-
```bash
|
|
199
|
-
cd dex-python-sdk
|
|
200
|
-
code .
|
|
201
|
-
```
|
|
202
|
-
- 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.
|
|
203
|
-
- 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.
|
|
204
238
|
|
|
205
239
|
## How To Contribute
|
|
206
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,10 +115,49 @@ 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.
|
|
121
127
|
|
|
128
|
+
### Waiting and map inspection
|
|
129
|
+
|
|
130
|
+
`Wait.all_of` and `Wait.any_of` may use unnamed Conditions. Every Condition in
|
|
131
|
+
`Wait.any_combination_of` must have a non-empty user ID; the same Condition
|
|
132
|
+
instance may appear in multiple combinations.
|
|
133
|
+
|
|
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
|
|
137
|
+
before transport. `AttributeMap.get_map_size/get_all_instance_keys` include
|
|
138
|
+
buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
139
|
+
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
140
|
+
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
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
|
+
|
|
122
161
|
### Errors
|
|
123
162
|
|
|
124
163
|
Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
|
|
@@ -169,22 +208,17 @@ implemented. Python owns its gRPC transport; the native bridge is limited to
|
|
|
169
208
|
the shared BlobCache. Design notes:
|
|
170
209
|
[`docs/design/plan/python-sdk-async-apis.md`](../docs/design/plan/python-sdk-async-apis.md).
|
|
171
210
|
|
|
172
|
-
## Running
|
|
211
|
+
## Running Dex locally
|
|
173
212
|
|
|
174
|
-
|
|
175
|
-
See [dex README](https://github.com/superdurable/dex#using-docker-image--docker-compose)
|
|
213
|
+
Install and start the complete local environment with `dexcli`:
|
|
176
214
|
|
|
177
|
-
|
|
215
|
+
```bash
|
|
216
|
+
brew install superdurable/tap/dexcli
|
|
217
|
+
dexcli dev
|
|
218
|
+
```
|
|
178
219
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- Open the project in VSCode.
|
|
182
|
-
```bash
|
|
183
|
-
cd dex-python-sdk
|
|
184
|
-
code .
|
|
185
|
-
```
|
|
186
|
-
- 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.
|
|
187
|
-
- 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.
|
|
188
222
|
|
|
189
223
|
## How To Contribute
|
|
190
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,
|
|
@@ -78,7 +79,7 @@ from dex.step import (
|
|
|
78
79
|
WaitForFailurePolicy,
|
|
79
80
|
dead_end,
|
|
80
81
|
force_complete,
|
|
81
|
-
|
|
82
|
+
force_complete_if_channels_empty,
|
|
82
83
|
force_fail,
|
|
83
84
|
go_to,
|
|
84
85
|
go_to_multi,
|
|
@@ -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",
|
|
@@ -163,7 +166,7 @@ __all__ = [
|
|
|
163
166
|
"WorkerTarget",
|
|
164
167
|
"dead_end",
|
|
165
168
|
"force_complete",
|
|
166
|
-
"
|
|
169
|
+
"force_complete_if_channels_empty",
|
|
167
170
|
"force_fail",
|
|
168
171
|
"graceful_complete",
|
|
169
172
|
"go_to",
|
|
@@ -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(
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
from enum import Enum
|
|
12
12
|
from typing import Any, Sequence, TypeVar, cast
|
|
13
|
+
from urllib.parse import unquote
|
|
13
14
|
|
|
14
15
|
from dex._utils import require_name
|
|
15
16
|
from dex._value_mapper import ValueMapper
|
|
@@ -204,6 +205,38 @@ class InvocationContext:
|
|
|
204
205
|
size=(current.size if current is not None else 0) + 1
|
|
205
206
|
)
|
|
206
207
|
|
|
208
|
+
def _attribute_map_keys(
|
|
209
|
+
self,
|
|
210
|
+
definition: AttributeMap[object],
|
|
211
|
+
) -> tuple[str, ...]:
|
|
212
|
+
self._require_registered(definition)
|
|
213
|
+
prefix = f"{definition.name}/"
|
|
214
|
+
physical_keys = {key for key in self._attributes if key.startswith(prefix)}
|
|
215
|
+
for key, write in self.attribute_writes.items():
|
|
216
|
+
if not key.startswith(prefix):
|
|
217
|
+
continue
|
|
218
|
+
if write.value.WhichOneof("kind") == "null_value":
|
|
219
|
+
physical_keys.discard(key)
|
|
220
|
+
else:
|
|
221
|
+
physical_keys.add(key)
|
|
222
|
+
return tuple(sorted(unquote(key[len(prefix) :]) for key in physical_keys))
|
|
223
|
+
|
|
224
|
+
def _channel_map_keys(
|
|
225
|
+
self,
|
|
226
|
+
definition: ChannelMap[object],
|
|
227
|
+
) -> tuple[str, ...]:
|
|
228
|
+
self._require_registered(definition)
|
|
229
|
+
if self._method is not InvocationMethod.RPC:
|
|
230
|
+
raise ValueError("ChannelMap introspection requires an RPC invocation")
|
|
231
|
+
prefix = f"{definition.name}/"
|
|
232
|
+
return tuple(
|
|
233
|
+
sorted(
|
|
234
|
+
unquote(key[len(prefix) :])
|
|
235
|
+
for key, info in self._channel_infos.items()
|
|
236
|
+
if key.startswith(prefix) and info.size > 0
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
207
240
|
def _channel_size(
|
|
208
241
|
self,
|
|
209
242
|
definition: Channel[object] | ChannelMap[object],
|
|
@@ -32,8 +32,6 @@ from dex.step import (
|
|
|
32
32
|
)
|
|
33
33
|
from dex.wait import Wait, WaitKind
|
|
34
34
|
|
|
35
|
-
_INTERNAL_CONDITION_PREFIX = "__dex_internal_condition_"
|
|
36
|
-
|
|
37
35
|
|
|
38
36
|
class WorkerDispatcher:
|
|
39
37
|
def __init__(
|
|
@@ -259,7 +257,8 @@ class WorkerDispatcher:
|
|
|
259
257
|
for combination in wait.combinations:
|
|
260
258
|
waiting.condition_combinations.add(
|
|
261
259
|
condition_ids=[
|
|
262
|
-
mapper.add(condition)
|
|
260
|
+
mapper.add(condition, id_required=True)
|
|
261
|
+
for condition in combination.conditions
|
|
263
262
|
]
|
|
264
263
|
)
|
|
265
264
|
else:
|
|
@@ -280,11 +279,19 @@ class WorkerDispatcher:
|
|
|
280
279
|
mapped.next_steps.extend(self._map_movements(flow, decision.movements))
|
|
281
280
|
elif decision.kind is DecisionKind.GRACEFUL_COMPLETE:
|
|
282
281
|
mapped.close_decision.CopyFrom(
|
|
283
|
-
self._close(
|
|
282
|
+
self._close(
|
|
283
|
+
pb.CLOSE_DECISION_TYPE_GRACEFUL_COMPLETE,
|
|
284
|
+
decision.output,
|
|
285
|
+
decision._has_output(),
|
|
286
|
+
)
|
|
284
287
|
)
|
|
285
288
|
elif decision.kind is DecisionKind.FORCE_COMPLETE:
|
|
286
289
|
mapped.close_decision.CopyFrom(
|
|
287
|
-
self._close(
|
|
290
|
+
self._close(
|
|
291
|
+
pb.CLOSE_DECISION_TYPE_FORCE_COMPLETE,
|
|
292
|
+
decision.output,
|
|
293
|
+
decision._has_output(),
|
|
294
|
+
)
|
|
288
295
|
)
|
|
289
296
|
elif decision.kind is DecisionKind.FORCE_FAIL:
|
|
290
297
|
mapped.close_decision.CopyFrom(
|
|
@@ -315,11 +322,16 @@ class WorkerDispatcher:
|
|
|
315
322
|
raise ValueError("unsupported StepDecision kind")
|
|
316
323
|
return mapped
|
|
317
324
|
|
|
318
|
-
def _close(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
323
335
|
|
|
324
336
|
def _map_movements(
|
|
325
337
|
self,
|
|
@@ -417,7 +429,6 @@ class _ConditionMapper:
|
|
|
417
429
|
self._flow = flow
|
|
418
430
|
self._ids: dict[int, str] = {}
|
|
419
431
|
self._used: set[str] = set()
|
|
420
|
-
self._next_id = 0
|
|
421
432
|
self.timers: list[pb.TimerCondition] = []
|
|
422
433
|
self.channels: list[pb.ChannelCondition] = []
|
|
423
434
|
|
|
@@ -427,18 +438,22 @@ class _ConditionMapper:
|
|
|
427
438
|
for condition in conditions:
|
|
428
439
|
self.add(condition)
|
|
429
440
|
|
|
430
|
-
def add(self, condition: Condition) -> str:
|
|
441
|
+
def add(self, condition: Condition, *, id_required: bool = False) -> str:
|
|
431
442
|
identity = id(condition)
|
|
432
443
|
existing = self._ids.get(identity)
|
|
433
444
|
if existing is not None:
|
|
434
445
|
return existing
|
|
435
|
-
condition_id = condition.condition_id
|
|
436
|
-
if
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
446
|
+
condition_id = condition.condition_id or ""
|
|
447
|
+
if id_required and not condition_id:
|
|
448
|
+
raise ValueError(
|
|
449
|
+
"any_combination_of requires every Condition to have an ID"
|
|
450
|
+
)
|
|
451
|
+
if condition.condition_id is not None and not condition_id:
|
|
452
|
+
raise ValueError("empty Condition ID")
|
|
453
|
+
if condition_id and condition_id in self._used:
|
|
454
|
+
raise ValueError("duplicate Condition ID")
|
|
455
|
+
if condition_id:
|
|
456
|
+
self._used.add(condition_id)
|
|
442
457
|
if isinstance(condition, TimerCondition):
|
|
443
458
|
seconds = condition.duration.total_seconds()
|
|
444
459
|
if not seconds.is_integer():
|