dex-python-sdk 0.1.2__tar.gz → 0.1.3__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.3}/PKG-INFO +15 -1
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/README.md +14 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/__init__.py +2 -2
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_invocation_context.py +33 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_worker_dispatcher.py +14 -12
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/async_client.py +96 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/async_worker.py +3 -3
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/attribute.py +22 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/channel.py +22 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/client.py +93 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/context.py +10 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/step.py +1 -1
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/pyproject.toml +1 -1
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/LEGACY_NOTICES.md +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/LICENSE +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_async_value_hydrator.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_async_worker_dispatcher.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_async_worker_service.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_grpc_errors.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_native.pyi +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_utils.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_value_hydrator.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_value_mapper.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/_worker_service.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/blob_cache.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/client_options.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/codec.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/condition.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/dexpb/__init__.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/dexpb/dex_pb2.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/dexpb/dex_pb2.pyi +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/dexpb/dex_pb2_grpc.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/flow.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/flow_config.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/flow_info.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/flow_options.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/py.typed +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/runtime_errors.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/step_execution.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/timer.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/wait.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/worker.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/dex/worker_options.py +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/Cargo.lock +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/Cargo.toml +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
- {dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/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.3
|
|
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
|
|
@@ -135,6 +135,20 @@ two handler calls. Timer and channel conditions determine how long a Step waits.
|
|
|
135
135
|
codec before Client or Worker startup. `Client` methods use these typed objects
|
|
136
136
|
instead of raw Flow, Step, or RPC strings.
|
|
137
137
|
|
|
138
|
+
### Waiting and map inspection
|
|
139
|
+
|
|
140
|
+
`Wait.all_of` and `Wait.any_of` may use unnamed Conditions. Every Condition in
|
|
141
|
+
`Wait.any_combination_of` must have a non-empty user ID; the same Condition
|
|
142
|
+
instance may appear in multiple combinations.
|
|
143
|
+
|
|
144
|
+
Both `Client` and `AsyncClient` provide `wait_for_attribute_equal` and
|
|
145
|
+
`wait_for_attribute_map_equal`. They target the current run and accept only
|
|
146
|
+
string, bool, int, or float wire values. JSON objects, bytes, and null fail
|
|
147
|
+
before transport. `AttributeMap.get_map_size/get_all_instance_keys` include
|
|
148
|
+
buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
149
|
+
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
150
|
+
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
151
|
+
|
|
138
152
|
### Errors
|
|
139
153
|
|
|
140
154
|
Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
|
|
@@ -119,6 +119,20 @@ two handler calls. Timer and channel conditions determine how long a Step waits.
|
|
|
119
119
|
codec before Client or Worker startup. `Client` methods use these typed objects
|
|
120
120
|
instead of raw Flow, Step, or RPC strings.
|
|
121
121
|
|
|
122
|
+
### Waiting and map inspection
|
|
123
|
+
|
|
124
|
+
`Wait.all_of` and `Wait.any_of` may use unnamed Conditions. Every Condition in
|
|
125
|
+
`Wait.any_combination_of` must have a non-empty user ID; the same Condition
|
|
126
|
+
instance may appear in multiple combinations.
|
|
127
|
+
|
|
128
|
+
Both `Client` and `AsyncClient` provide `wait_for_attribute_equal` and
|
|
129
|
+
`wait_for_attribute_map_equal`. They target the current run and accept only
|
|
130
|
+
string, bool, int, or float wire values. JSON objects, bytes, and null fail
|
|
131
|
+
before transport. `AttributeMap.get_map_size/get_all_instance_keys` include
|
|
132
|
+
buffered sets and deletes. The matching `ChannelMap` methods are RPC-only,
|
|
133
|
+
include buffered publishes, and omit empty instances. Keys are decoded and
|
|
134
|
+
sorted. Use `force_complete_if_channels_empty(...)` for conditional completion.
|
|
135
|
+
|
|
122
136
|
### Errors
|
|
123
137
|
|
|
124
138
|
Client calls raise concrete `DexServiceError` subclasses. Existing-Flow reads
|
|
@@ -78,7 +78,7 @@ from dex.step import (
|
|
|
78
78
|
WaitForFailurePolicy,
|
|
79
79
|
dead_end,
|
|
80
80
|
force_complete,
|
|
81
|
-
|
|
81
|
+
force_complete_if_channels_empty,
|
|
82
82
|
force_fail,
|
|
83
83
|
go_to,
|
|
84
84
|
go_to_multi,
|
|
@@ -163,7 +163,7 @@ __all__ = [
|
|
|
163
163
|
"WorkerTarget",
|
|
164
164
|
"dead_end",
|
|
165
165
|
"force_complete",
|
|
166
|
-
"
|
|
166
|
+
"force_complete_if_channels_empty",
|
|
167
167
|
"force_fail",
|
|
168
168
|
"graceful_complete",
|
|
169
169
|
"go_to",
|
|
@@ -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:
|
|
@@ -417,7 +416,6 @@ class _ConditionMapper:
|
|
|
417
416
|
self._flow = flow
|
|
418
417
|
self._ids: dict[int, str] = {}
|
|
419
418
|
self._used: set[str] = set()
|
|
420
|
-
self._next_id = 0
|
|
421
419
|
self.timers: list[pb.TimerCondition] = []
|
|
422
420
|
self.channels: list[pb.ChannelCondition] = []
|
|
423
421
|
|
|
@@ -427,18 +425,22 @@ class _ConditionMapper:
|
|
|
427
425
|
for condition in conditions:
|
|
428
426
|
self.add(condition)
|
|
429
427
|
|
|
430
|
-
def add(self, condition: Condition) -> str:
|
|
428
|
+
def add(self, condition: Condition, *, id_required: bool = False) -> str:
|
|
431
429
|
identity = id(condition)
|
|
432
430
|
existing = self._ids.get(identity)
|
|
433
431
|
if existing is not None:
|
|
434
432
|
return existing
|
|
435
|
-
condition_id = condition.condition_id
|
|
436
|
-
if
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
433
|
+
condition_id = condition.condition_id or ""
|
|
434
|
+
if id_required and not condition_id:
|
|
435
|
+
raise ValueError(
|
|
436
|
+
"any_combination_of requires every Condition to have an ID"
|
|
437
|
+
)
|
|
438
|
+
if condition.condition_id is not None and not condition_id:
|
|
439
|
+
raise ValueError("empty Condition ID")
|
|
440
|
+
if condition_id and condition_id in self._used:
|
|
441
|
+
raise ValueError("duplicate Condition ID")
|
|
442
|
+
if condition_id:
|
|
443
|
+
self._used.add(condition_id)
|
|
442
444
|
if isinstance(condition, TimerCondition):
|
|
443
445
|
seconds = condition.duration.total_seconds()
|
|
444
446
|
if not seconds.is_integer():
|
|
@@ -787,6 +787,102 @@ class AsyncClient:
|
|
|
787
787
|
"active",
|
|
788
788
|
)
|
|
789
789
|
|
|
790
|
+
async def wait_for_attribute_equal(
|
|
791
|
+
self,
|
|
792
|
+
flow_id: str,
|
|
793
|
+
attribute: Attribute[ValueT],
|
|
794
|
+
expected: ValueT,
|
|
795
|
+
timeout: timedelta,
|
|
796
|
+
) -> None:
|
|
797
|
+
"""Await a scalar Attribute in the current run equaling ``expected``.
|
|
798
|
+
|
|
799
|
+
The Client generates a request ID. JSON, bytes, and null values raise
|
|
800
|
+
``ValueError`` before transport. A remote expiry raises
|
|
801
|
+
``LongPollTimeoutError``.
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
flow_id: The non-empty active Flow ID.
|
|
805
|
+
attribute: The registered scalar Attribute to observe.
|
|
806
|
+
expected: The string, bool, int, or float value to await.
|
|
807
|
+
timeout: The non-negative server-side wait duration.
|
|
808
|
+
|
|
809
|
+
Raises:
|
|
810
|
+
ValueError: If an identifier, timeout, or expected value is invalid.
|
|
811
|
+
LongPollTimeoutError: If equality is not observed before ``timeout``.
|
|
812
|
+
FlowNotActiveError: If the Flow closes first.
|
|
813
|
+
DexServiceError: If FlowService cannot perform the wait.
|
|
814
|
+
"""
|
|
815
|
+
await self._wait_for_attribute_equal(
|
|
816
|
+
flow_id, attribute, None, expected, timeout
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
async def wait_for_attribute_map_equal(
|
|
820
|
+
self,
|
|
821
|
+
flow_id: str,
|
|
822
|
+
attribute: AttributeMap[ValueT],
|
|
823
|
+
instance: str,
|
|
824
|
+
expected: ValueT,
|
|
825
|
+
timeout: timedelta,
|
|
826
|
+
) -> None:
|
|
827
|
+
"""Await one scalar AttributeMap instance in the current run.
|
|
828
|
+
|
|
829
|
+
Scalar restrictions, request-ID generation, timeout behavior, and
|
|
830
|
+
service errors match :meth:`wait_for_attribute_equal`.
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
flow_id: The non-empty active Flow ID.
|
|
834
|
+
attribute: The registered AttributeMap to observe.
|
|
835
|
+
instance: The non-empty logical map key to observe.
|
|
836
|
+
expected: The string, bool, int, or float value to await.
|
|
837
|
+
timeout: The non-negative server-side wait duration.
|
|
838
|
+
|
|
839
|
+
Raises:
|
|
840
|
+
ValueError: If an identifier, timeout, or expected value is invalid.
|
|
841
|
+
LongPollTimeoutError: If equality is not observed before ``timeout``.
|
|
842
|
+
FlowNotActiveError: If the Flow closes first.
|
|
843
|
+
DexServiceError: If FlowService cannot perform the wait.
|
|
844
|
+
"""
|
|
845
|
+
await self._wait_for_attribute_equal(
|
|
846
|
+
flow_id, attribute, instance, expected, timeout
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
async def _wait_for_attribute_equal(
|
|
850
|
+
self,
|
|
851
|
+
flow_id: str,
|
|
852
|
+
attribute: Attribute[Any] | AttributeMap[Any],
|
|
853
|
+
instance: str | None,
|
|
854
|
+
expected: object,
|
|
855
|
+
timeout: timedelta,
|
|
856
|
+
) -> None:
|
|
857
|
+
encoded = self._values.encode(
|
|
858
|
+
expected,
|
|
859
|
+
self._values.codec(attribute.value_type),
|
|
860
|
+
)
|
|
861
|
+
if encoded.WhichOneof("kind") not in {
|
|
862
|
+
"string_value",
|
|
863
|
+
"bool_value",
|
|
864
|
+
"int_value",
|
|
865
|
+
"double_value",
|
|
866
|
+
}:
|
|
867
|
+
raise ValueError("wait_for_attribute_equal supports only scalar values")
|
|
868
|
+
await self._call(
|
|
869
|
+
self._service.WaitForAttribute,
|
|
870
|
+
pb.WaitForAttributeRequest(
|
|
871
|
+
flow_id=require_name(flow_id),
|
|
872
|
+
condition=pb.WaitForAttributeCondition(
|
|
873
|
+
equal=pb.WaitForAttributeEqual(
|
|
874
|
+
key=self._definition_name(attribute, instance),
|
|
875
|
+
value=encoded,
|
|
876
|
+
)
|
|
877
|
+
),
|
|
878
|
+
wait_time_seconds=self._seconds32(timeout),
|
|
879
|
+
request_id=str(uuid4()),
|
|
880
|
+
),
|
|
881
|
+
"wait_for_attribute_equal",
|
|
882
|
+
flow_id,
|
|
883
|
+
"active",
|
|
884
|
+
)
|
|
885
|
+
|
|
790
886
|
async def update_flow_config(self, flow_id: str, config: FlowConfig) -> None:
|
|
791
887
|
"""Replace mutable configuration for an active Flow.
|
|
792
888
|
|
|
@@ -135,13 +135,13 @@ class AsyncWorker:
|
|
|
135
135
|
)
|
|
136
136
|
except grpc.RpcError as failure:
|
|
137
137
|
self._state = "stopped"
|
|
138
|
-
await self._flow_channel.close()
|
|
138
|
+
await self._flow_channel.close(None)
|
|
139
139
|
self._stopped.set()
|
|
140
140
|
raise RuntimeError("cannot synchronize Attribute indexes") from failure
|
|
141
141
|
self._bound_port = self._server.add_insecure_port(self.options.bind_address)
|
|
142
142
|
if self._bound_port == 0:
|
|
143
143
|
self._state = "stopped"
|
|
144
|
-
await self._flow_channel.close()
|
|
144
|
+
await self._flow_channel.close(None)
|
|
145
145
|
self._stopped.set()
|
|
146
146
|
raise RuntimeError(
|
|
147
147
|
f"cannot bind Python AsyncWorker to {self.options.bind_address}"
|
|
@@ -164,7 +164,7 @@ class AsyncWorker:
|
|
|
164
164
|
return
|
|
165
165
|
self._state = "stopping"
|
|
166
166
|
await self._server.stop(grace=5)
|
|
167
|
-
await self._flow_channel.close()
|
|
167
|
+
await self._flow_channel.close(None)
|
|
168
168
|
if self._state != "closed":
|
|
169
169
|
self._state = "stopped"
|
|
170
170
|
self._stopped.set()
|
|
@@ -206,6 +206,28 @@ class AttributeMap(Generic[ValueT]):
|
|
|
206
206
|
"""
|
|
207
207
|
context._delete_attribute(cast(AttributeMap[object], self), instance)
|
|
208
208
|
|
|
209
|
+
def get_map_size(self, context: Context) -> int:
|
|
210
|
+
"""Return the number of existing instances, including buffered writes.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
context: The current Step or RPC Context.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The number of keys visible after decision-local writes and deletions.
|
|
217
|
+
"""
|
|
218
|
+
return len(self.get_all_instance_keys(context))
|
|
219
|
+
|
|
220
|
+
def get_all_instance_keys(self, context: Context) -> tuple[str, ...]:
|
|
221
|
+
"""Return decoded existing instance keys in ascending order.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
context: The current Step or RPC Context.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
An immutable tuple reflecting decision-local writes and deletions.
|
|
228
|
+
"""
|
|
229
|
+
return context._attribute_map_keys(cast(AttributeMap[object], self))
|
|
230
|
+
|
|
209
231
|
def lock(self, instance: str) -> AttributeLock:
|
|
210
232
|
"""Return a lock request for one map instance.
|
|
211
233
|
|
|
@@ -210,6 +210,28 @@ class ChannelMap(Generic[ValueT]):
|
|
|
210
210
|
"""
|
|
211
211
|
return context._channel_size(cast(ChannelMap[object], self), instance)
|
|
212
212
|
|
|
213
|
+
def get_map_size(self, context: Context) -> int:
|
|
214
|
+
"""Return the number of non-empty instances visible to the current RPC.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
context: The current RPC Context.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
The number of keys after including publications buffered by this RPC.
|
|
221
|
+
"""
|
|
222
|
+
return len(self.get_all_instance_keys(context))
|
|
223
|
+
|
|
224
|
+
def get_all_instance_keys(self, context: Context) -> tuple[str, ...]:
|
|
225
|
+
"""Return decoded non-empty instance keys in ascending order.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
context: The current RPC Context.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
An immutable tuple including publications buffered by this RPC.
|
|
232
|
+
"""
|
|
233
|
+
return context._channel_map_keys(cast(ChannelMap[object], self))
|
|
234
|
+
|
|
213
235
|
def results(self, context: Context, instance: str) -> Sequence[ValueT]:
|
|
214
236
|
"""Return values selected for one instance by the satisfied condition.
|
|
215
237
|
|
|
@@ -786,6 +786,99 @@ class Client:
|
|
|
786
786
|
"active",
|
|
787
787
|
)
|
|
788
788
|
|
|
789
|
+
def wait_for_attribute_equal(
|
|
790
|
+
self,
|
|
791
|
+
flow_id: str,
|
|
792
|
+
attribute: Attribute[ValueT],
|
|
793
|
+
expected: ValueT,
|
|
794
|
+
timeout: timedelta,
|
|
795
|
+
) -> None:
|
|
796
|
+
"""Wait for a scalar Attribute in the current run to equal ``expected``.
|
|
797
|
+
|
|
798
|
+
The Client generates a request ID and blocks for ``timeout``. JSON,
|
|
799
|
+
bytes, and null values raise ``ValueError`` before transport. A remote
|
|
800
|
+
expiry raises ``LongPollTimeoutError``.
|
|
801
|
+
|
|
802
|
+
Args:
|
|
803
|
+
flow_id: The non-empty active Flow ID.
|
|
804
|
+
attribute: The registered scalar Attribute to observe.
|
|
805
|
+
expected: The string, bool, int, or float value to await.
|
|
806
|
+
timeout: The non-negative server-side wait duration.
|
|
807
|
+
|
|
808
|
+
Raises:
|
|
809
|
+
ValueError: If an identifier, timeout, or expected value is invalid.
|
|
810
|
+
LongPollTimeoutError: If equality is not observed before ``timeout``.
|
|
811
|
+
FlowNotActiveError: If the Flow closes first.
|
|
812
|
+
DexServiceError: If FlowService cannot perform the wait.
|
|
813
|
+
"""
|
|
814
|
+
self._wait_for_attribute_equal(flow_id, attribute, None, expected, timeout)
|
|
815
|
+
|
|
816
|
+
def wait_for_attribute_map_equal(
|
|
817
|
+
self,
|
|
818
|
+
flow_id: str,
|
|
819
|
+
attribute: AttributeMap[ValueT],
|
|
820
|
+
instance: str,
|
|
821
|
+
expected: ValueT,
|
|
822
|
+
timeout: timedelta,
|
|
823
|
+
) -> None:
|
|
824
|
+
"""Wait for one scalar AttributeMap instance in the current run.
|
|
825
|
+
|
|
826
|
+
``instance`` is encoded as a map key. Scalar restrictions, request-ID
|
|
827
|
+
generation, timeout behavior, and service errors match
|
|
828
|
+
:meth:`wait_for_attribute_equal`.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
flow_id: The non-empty active Flow ID.
|
|
832
|
+
attribute: The registered AttributeMap to observe.
|
|
833
|
+
instance: The non-empty logical map key to observe.
|
|
834
|
+
expected: The string, bool, int, or float value to await.
|
|
835
|
+
timeout: The non-negative server-side wait duration.
|
|
836
|
+
|
|
837
|
+
Raises:
|
|
838
|
+
ValueError: If an identifier, timeout, or expected value is invalid.
|
|
839
|
+
LongPollTimeoutError: If equality is not observed before ``timeout``.
|
|
840
|
+
FlowNotActiveError: If the Flow closes first.
|
|
841
|
+
DexServiceError: If FlowService cannot perform the wait.
|
|
842
|
+
"""
|
|
843
|
+
self._wait_for_attribute_equal(flow_id, attribute, instance, expected, timeout)
|
|
844
|
+
|
|
845
|
+
def _wait_for_attribute_equal(
|
|
846
|
+
self,
|
|
847
|
+
flow_id: str,
|
|
848
|
+
attribute: Attribute[Any] | AttributeMap[Any],
|
|
849
|
+
instance: str | None,
|
|
850
|
+
expected: object,
|
|
851
|
+
timeout: timedelta,
|
|
852
|
+
) -> None:
|
|
853
|
+
encoded = self._values.encode(
|
|
854
|
+
expected,
|
|
855
|
+
self._values.codec(attribute.value_type),
|
|
856
|
+
)
|
|
857
|
+
if encoded.WhichOneof("kind") not in {
|
|
858
|
+
"string_value",
|
|
859
|
+
"bool_value",
|
|
860
|
+
"int_value",
|
|
861
|
+
"double_value",
|
|
862
|
+
}:
|
|
863
|
+
raise ValueError("wait_for_attribute_equal supports only scalar values")
|
|
864
|
+
self._call(
|
|
865
|
+
self._service.WaitForAttribute,
|
|
866
|
+
pb.WaitForAttributeRequest(
|
|
867
|
+
flow_id=require_name(flow_id),
|
|
868
|
+
condition=pb.WaitForAttributeCondition(
|
|
869
|
+
equal=pb.WaitForAttributeEqual(
|
|
870
|
+
key=self._definition_name(attribute, instance),
|
|
871
|
+
value=encoded,
|
|
872
|
+
)
|
|
873
|
+
),
|
|
874
|
+
wait_time_seconds=self._seconds32(timeout),
|
|
875
|
+
request_id=str(uuid4()),
|
|
876
|
+
),
|
|
877
|
+
"wait_for_attribute_equal",
|
|
878
|
+
flow_id,
|
|
879
|
+
"active",
|
|
880
|
+
)
|
|
881
|
+
|
|
789
882
|
def update_flow_config(self, flow_id: str, config: FlowConfig) -> None:
|
|
790
883
|
"""Replace mutable configuration for an active Flow.
|
|
791
884
|
|
|
@@ -149,6 +149,11 @@ class Context(Protocol):
|
|
|
149
149
|
instance: str | None,
|
|
150
150
|
) -> None: ...
|
|
151
151
|
|
|
152
|
+
def _attribute_map_keys(
|
|
153
|
+
self,
|
|
154
|
+
definition: AttributeMap[object],
|
|
155
|
+
) -> tuple[str, ...]: ...
|
|
156
|
+
|
|
152
157
|
def _publish_channel(
|
|
153
158
|
self,
|
|
154
159
|
definition: Channel[ValueT] | ChannelMap[ValueT],
|
|
@@ -167,3 +172,8 @@ class Context(Protocol):
|
|
|
167
172
|
definition: Channel[ValueT] | ChannelMap[ValueT],
|
|
168
173
|
instance: str | None,
|
|
169
174
|
) -> Sequence[ValueT]: ...
|
|
175
|
+
|
|
176
|
+
def _channel_map_keys(
|
|
177
|
+
self,
|
|
178
|
+
definition: ChannelMap[object],
|
|
179
|
+
) -> tuple[str, ...]: ...
|
|
@@ -383,7 +383,7 @@ def force_complete(output: object | None = None) -> StepDecision:
|
|
|
383
383
|
return StepDecision(DecisionKind.FORCE_COMPLETE, output=output)
|
|
384
384
|
|
|
385
385
|
|
|
386
|
-
def
|
|
386
|
+
def force_complete_if_channels_empty(
|
|
387
387
|
output: object,
|
|
388
388
|
fallback: StepMovement[Any],
|
|
389
389
|
*channels: Channel[Any] | ChannelMap[Any],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml
RENAMED
|
File without changes
|
|
File without changes
|
{dex_python_sdk-0.1.2 → dex_python_sdk-0.1.3}/sdk-rust/crates/dex-blob-cache-python/src/lib.rs
RENAMED
|
File without changes
|