dex-python-sdk 0.0.2__cp311-abi3-win_amd64.whl
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/__init__.py +146 -0
- dex/_grpc_errors.py +73 -0
- dex/_invocation_context.py +269 -0
- dex/_native.pyd +0 -0
- dex/_native.pyi +20 -0
- dex/_utils.py +18 -0
- dex/_value_hydrator.py +223 -0
- dex/_value_mapper.py +174 -0
- dex/_worker_dispatcher.py +451 -0
- dex/_worker_service.py +60 -0
- dex/attribute.py +87 -0
- dex/blob_cache.py +73 -0
- dex/channel.py +165 -0
- dex/client.py +715 -0
- dex/client_options.py +19 -0
- dex/codec.py +286 -0
- dex/command_request.py +120 -0
- dex/command_results.py +107 -0
- dex/communication.py +136 -0
- dex/communication_schema.py +54 -0
- dex/condition.py +74 -0
- dex/context.py +86 -0
- dex/data_attributes.py +70 -0
- dex/dexpb/__init__.py +1 -0
- dex/dexpb/dex_pb2.py +381 -0
- dex/dexpb/dex_pb2.pyi +1734 -0
- dex/dexpb/dex_pb2_grpc.py +1298 -0
- dex/errors.py +109 -0
- dex/flow.py +456 -0
- dex/flow_config.py +29 -0
- dex/flow_info.py +51 -0
- dex/flow_options.py +122 -0
- dex/object_encoder.py +799 -0
- dex/persistence.py +89 -0
- dex/persistence_options.py +12 -0
- dex/persistence_schema.py +51 -0
- dex/py.typed +1 -0
- dex/registry.py +204 -0
- dex/reset_workflow_type_and_options.py +67 -0
- dex/rpc.py +93 -0
- dex/runtime_errors.py +81 -0
- dex/search_attributes.py +184 -0
- dex/state_decision.py +153 -0
- dex/state_execution_locals.py +66 -0
- dex/state_movement.py +115 -0
- dex/state_schema.py +48 -0
- dex/step.py +194 -0
- dex/step_execution.py +42 -0
- dex/stop_workflow_options.py +18 -0
- dex/tests/__init__.py +80 -0
- dex/tests/dex-service-env/.env +7 -0
- dex/tests/dex-service-env/docker-compose-init.sh +44 -0
- dex/tests/dex-service-env/docker-compose.yml +97 -0
- dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
- dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +9 -0
- dex/tests/dex-service-env/dynamicconfig/docker.yaml +2 -0
- dex/tests/test_abnormal_exit_workflow.py +43 -0
- dex/tests/test_basic_workflow.py +70 -0
- dex/tests/test_conditional_complete.py +50 -0
- dex/tests/test_describe_workflow.py +40 -0
- dex/tests/test_empty_data_decodes_properly.py +74 -0
- dex/tests/test_internal_channel.py +28 -0
- dex/tests/test_internal_channel_with_no_prefix_channel.py +41 -0
- dex/tests/test_persistence_data_attributes.py +62 -0
- dex/tests/test_persistence_search_attributes.py +127 -0
- dex/tests/test_persistence_state_execution_locals.py +38 -0
- dex/tests/test_rpc.py +64 -0
- dex/tests/test_rpc_with_memo.py +195 -0
- dex/tests/test_rpc_with_memo_duplicate_java_tests.py +117 -0
- dex/tests/test_signal.py +51 -0
- dex/tests/test_skip_wait_until.py +76 -0
- dex/tests/test_state_failure_recovery.py +28 -0
- dex/tests/test_timer.py +35 -0
- dex/tests/test_wait_for_state_execution_completion.py +53 -0
- dex/tests/test_workflow_errors.py +87 -0
- dex/tests/test_workflow_state_options.py +118 -0
- dex/tests/test_workflow_state_options_override.py +44 -0
- dex/tests/worker_server.py +64 -0
- dex/tests/workflows/abnormal_exit_workflow.py +42 -0
- dex/tests/workflows/basic_workflow.py +62 -0
- dex/tests/workflows/conditional_complete_workflow.py +95 -0
- dex/tests/workflows/describe_workflow.py +46 -0
- dex/tests/workflows/empty_data_workflow.py +45 -0
- dex/tests/workflows/internal_channel_workflow.py +129 -0
- dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +100 -0
- dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +276 -0
- dex/tests/workflows/persistence_data_attributes_workflow.py +98 -0
- dex/tests/workflows/persistence_search_attributes_workflow.py +159 -0
- dex/tests/workflows/persistence_state_execution_local_workflow.py +63 -0
- dex/tests/workflows/recovery_workflow.py +82 -0
- dex/tests/workflows/rpc_memo_workflow.py +231 -0
- dex/tests/workflows/rpc_workflow.py +117 -0
- dex/tests/workflows/state_options_override_workflow.py +93 -0
- dex/tests/workflows/state_options_workflow.py +84 -0
- dex/tests/workflows/timer_workflow.py +46 -0
- dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +70 -0
- dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +71 -0
- dex/tests/workflows/wait_internal_channel_workflow.py +47 -0
- dex/tests/workflows/wait_signal_workflow.py +147 -0
- dex/timer.py +21 -0
- dex/type_store.py +99 -0
- dex/unregistered_client.py +585 -0
- dex/utils/__init__.py +3 -0
- dex/utils/dex_typing.py +25 -0
- dex/utils/persistence_utils.py +32 -0
- dex/wait.py +49 -0
- dex/worker.py +121 -0
- dex/worker_options.py +22 -0
- dex/worker_service.py +432 -0
- dex/workflow.py +79 -0
- dex/workflow_context.py +44 -0
- dex/workflow_info.py +16 -0
- dex/workflow_options.py +74 -0
- dex/workflow_state.py +123 -0
- dex/workflow_state_options.py +154 -0
- dex_python_sdk-0.0.2.dist-info/METADATA +202 -0
- dex_python_sdk-0.0.2.dist-info/RECORD +121 -0
- dex_python_sdk-0.0.2.dist-info/WHEEL +4 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LEGACY_NOTICES.md +61 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LICENSE +192 -0
- dex_python_sdk-0.0.2.dist-info/sboms/dex-blob-cache-python.cyclonedx.json +2406 -0
dex/__init__.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
from dex.attribute import (
|
|
12
|
+
Attribute,
|
|
13
|
+
AttributeIndex,
|
|
14
|
+
AttributeLock,
|
|
15
|
+
AttributeMap,
|
|
16
|
+
IndexType,
|
|
17
|
+
)
|
|
18
|
+
from dex.blob_cache import BlobCache, BlobCacheConfig, open_blob_cache
|
|
19
|
+
from dex.channel import Channel, ChannelMap
|
|
20
|
+
from dex.client import Client
|
|
21
|
+
from dex.client_options import ClientOptions
|
|
22
|
+
from dex.codec import (
|
|
23
|
+
BOOL,
|
|
24
|
+
BYTES,
|
|
25
|
+
DOUBLE,
|
|
26
|
+
INT64,
|
|
27
|
+
STRING,
|
|
28
|
+
Codec,
|
|
29
|
+
CodecRegistry,
|
|
30
|
+
JsonCodec,
|
|
31
|
+
Value,
|
|
32
|
+
WireKind,
|
|
33
|
+
)
|
|
34
|
+
from dex.condition import ConditionCombination
|
|
35
|
+
from dex.context import Context
|
|
36
|
+
from dex.flow import Flow, PersistenceSchema, Registry, RPCResult, rpc
|
|
37
|
+
from dex.flow_config import ActiveStepSearchMode, FlowConfig
|
|
38
|
+
from dex.flow_info import FlowInfo, FlowStatus, HealthInfo, SearchFlowEntry
|
|
39
|
+
from dex.flow_options import (
|
|
40
|
+
IdReusePolicy,
|
|
41
|
+
ResetFlowOptions,
|
|
42
|
+
ResetType,
|
|
43
|
+
StartFlowOptions,
|
|
44
|
+
StopFlowOptions,
|
|
45
|
+
StopType,
|
|
46
|
+
)
|
|
47
|
+
from dex.runtime_errors import (
|
|
48
|
+
DexException,
|
|
49
|
+
ErrorSubStatus,
|
|
50
|
+
FlowErrorType,
|
|
51
|
+
FlowUncompletedError,
|
|
52
|
+
LongPollTimeoutError,
|
|
53
|
+
)
|
|
54
|
+
from dex.step import (
|
|
55
|
+
RetryPolicy,
|
|
56
|
+
Step,
|
|
57
|
+
StepDecision,
|
|
58
|
+
StepDurability,
|
|
59
|
+
StepList,
|
|
60
|
+
StepMovement,
|
|
61
|
+
StepOptions,
|
|
62
|
+
WaitForFailurePolicy,
|
|
63
|
+
dead_end,
|
|
64
|
+
force_complete,
|
|
65
|
+
force_complete_when_channels_empty,
|
|
66
|
+
force_fail,
|
|
67
|
+
go_to,
|
|
68
|
+
go_to_multi,
|
|
69
|
+
graceful_complete,
|
|
70
|
+
)
|
|
71
|
+
from dex.step_execution import StepExecutionId, TimerId
|
|
72
|
+
from dex.timer import Timer
|
|
73
|
+
from dex.wait import Wait
|
|
74
|
+
from dex.worker import Worker
|
|
75
|
+
from dex.worker_options import WorkerOptions, WorkerTarget
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
"BOOL",
|
|
79
|
+
"BYTES",
|
|
80
|
+
"DOUBLE",
|
|
81
|
+
"INT64",
|
|
82
|
+
"STRING",
|
|
83
|
+
"ActiveStepSearchMode",
|
|
84
|
+
"Attribute",
|
|
85
|
+
"AttributeIndex",
|
|
86
|
+
"AttributeLock",
|
|
87
|
+
"AttributeMap",
|
|
88
|
+
"BlobCache",
|
|
89
|
+
"BlobCacheConfig",
|
|
90
|
+
"Channel",
|
|
91
|
+
"ChannelMap",
|
|
92
|
+
"Client",
|
|
93
|
+
"ClientOptions",
|
|
94
|
+
"Codec",
|
|
95
|
+
"CodecRegistry",
|
|
96
|
+
"ConditionCombination",
|
|
97
|
+
"Context",
|
|
98
|
+
"DexException",
|
|
99
|
+
"ErrorSubStatus",
|
|
100
|
+
"Flow",
|
|
101
|
+
"FlowConfig",
|
|
102
|
+
"FlowErrorType",
|
|
103
|
+
"FlowInfo",
|
|
104
|
+
"FlowStatus",
|
|
105
|
+
"FlowUncompletedError",
|
|
106
|
+
"HealthInfo",
|
|
107
|
+
"IdReusePolicy",
|
|
108
|
+
"IndexType",
|
|
109
|
+
"JsonCodec",
|
|
110
|
+
"LongPollTimeoutError",
|
|
111
|
+
"PersistenceSchema",
|
|
112
|
+
"RPCResult",
|
|
113
|
+
"Registry",
|
|
114
|
+
"ResetFlowOptions",
|
|
115
|
+
"ResetType",
|
|
116
|
+
"RetryPolicy",
|
|
117
|
+
"SearchFlowEntry",
|
|
118
|
+
"StartFlowOptions",
|
|
119
|
+
"StepExecutionId",
|
|
120
|
+
"StepDecision",
|
|
121
|
+
"Step",
|
|
122
|
+
"StepList",
|
|
123
|
+
"StepDurability",
|
|
124
|
+
"StepMovement",
|
|
125
|
+
"StepOptions",
|
|
126
|
+
"StopFlowOptions",
|
|
127
|
+
"StopType",
|
|
128
|
+
"Timer",
|
|
129
|
+
"TimerId",
|
|
130
|
+
"Value",
|
|
131
|
+
"Wait",
|
|
132
|
+
"WaitForFailurePolicy",
|
|
133
|
+
"WireKind",
|
|
134
|
+
"Worker",
|
|
135
|
+
"WorkerOptions",
|
|
136
|
+
"WorkerTarget",
|
|
137
|
+
"dead_end",
|
|
138
|
+
"force_complete",
|
|
139
|
+
"force_complete_when_channels_empty",
|
|
140
|
+
"force_fail",
|
|
141
|
+
"graceful_complete",
|
|
142
|
+
"go_to",
|
|
143
|
+
"go_to_multi",
|
|
144
|
+
"open_blob_cache",
|
|
145
|
+
"rpc",
|
|
146
|
+
]
|
dex/_grpc_errors.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Super Durable Source License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
import grpc
|
|
14
|
+
from google.protobuf import any_pb2
|
|
15
|
+
from google.rpc import status_pb2
|
|
16
|
+
from grpc_status import rpc_status
|
|
17
|
+
|
|
18
|
+
from dex.dexpb import dex_pb2 as pb
|
|
19
|
+
from dex.runtime_errors import DexException, ErrorSubStatus
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def translate_rpc_error(error: grpc.RpcError) -> DexException:
|
|
23
|
+
details: pb.ErrorResponse | None = None
|
|
24
|
+
status = rpc_status.from_call(cast(grpc.Call, error))
|
|
25
|
+
if status is not None:
|
|
26
|
+
for packed in status.details:
|
|
27
|
+
candidate = pb.ErrorResponse()
|
|
28
|
+
if packed.Is(candidate.DESCRIPTOR):
|
|
29
|
+
packed.Unpack(candidate)
|
|
30
|
+
details = candidate
|
|
31
|
+
break
|
|
32
|
+
detail = (
|
|
33
|
+
details.detail if details is not None and details.detail else error.details()
|
|
34
|
+
)
|
|
35
|
+
return DexException(
|
|
36
|
+
error.code(),
|
|
37
|
+
_map_sub_status(details.sub_status) if details is not None else None,
|
|
38
|
+
detail,
|
|
39
|
+
details.original_worker_error_type if details is not None else "",
|
|
40
|
+
details.original_worker_error_detail if details is not None else "",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def abort_worker_error(
|
|
45
|
+
context: grpc.ServicerContext,
|
|
46
|
+
error: BaseException,
|
|
47
|
+
) -> None:
|
|
48
|
+
message = str(error) or type(error).__name__
|
|
49
|
+
worker_error = pb.WorkerErrorResponse(
|
|
50
|
+
detail=message,
|
|
51
|
+
error_type=f"{type(error).__module__}.{type(error).__qualname__}",
|
|
52
|
+
)
|
|
53
|
+
packed = any_pb2.Any()
|
|
54
|
+
packed.Pack(worker_error)
|
|
55
|
+
status = status_pb2.Status(
|
|
56
|
+
code=grpc.StatusCode.UNKNOWN.value[0],
|
|
57
|
+
message=message,
|
|
58
|
+
details=[packed],
|
|
59
|
+
)
|
|
60
|
+
context.abort_with_status(rpc_status.to_status(status))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _map_sub_status(value: int) -> ErrorSubStatus | None:
|
|
64
|
+
statuses: dict[int, ErrorSubStatus] = {
|
|
65
|
+
int(pb.ERROR_SUB_STATUS_UNCATEGORIZED): ErrorSubStatus.UNCATEGORIZED,
|
|
66
|
+
int(
|
|
67
|
+
pb.ERROR_SUB_STATUS_FLOW_ALREADY_STARTED
|
|
68
|
+
): ErrorSubStatus.FLOW_ALREADY_STARTED,
|
|
69
|
+
int(pb.ERROR_SUB_STATUS_FLOW_NOT_EXISTS): ErrorSubStatus.FLOW_NOT_EXISTS,
|
|
70
|
+
int(pb.ERROR_SUB_STATUS_WORKER_API_ERROR): ErrorSubStatus.WORKER_API_ERROR,
|
|
71
|
+
int(pb.ERROR_SUB_STATUS_LONG_POLL_TIME_OUT): ErrorSubStatus.LONG_POLL_TIMEOUT,
|
|
72
|
+
}
|
|
73
|
+
return statuses.get(value)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Super Durable Source License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Sequence, TypeVar, cast
|
|
13
|
+
|
|
14
|
+
from dex._utils import require_name
|
|
15
|
+
from dex._value_mapper import ValueMapper
|
|
16
|
+
from dex.attribute import Attribute, AttributeMap
|
|
17
|
+
from dex.channel import Channel, ChannelMap
|
|
18
|
+
from dex.codec import Codec
|
|
19
|
+
from dex.dexpb import dex_pb2 as pb
|
|
20
|
+
from dex.flow import Registry, _RegisteredFlow
|
|
21
|
+
|
|
22
|
+
ValueT = TypeVar("ValueT")
|
|
23
|
+
_Definition = Attribute[Any] | AttributeMap[Any] | Channel[Any] | ChannelMap[Any]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InvocationMethod(Enum):
|
|
27
|
+
WAIT_FOR = "wait_for"
|
|
28
|
+
EXECUTE = "execute"
|
|
29
|
+
RPC = "rpc"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class InvocationContext:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
method: InvocationMethod,
|
|
36
|
+
flow: _RegisteredFlow,
|
|
37
|
+
metadata: pb.Context,
|
|
38
|
+
values: ValueMapper,
|
|
39
|
+
attributes: Sequence[pb.KV],
|
|
40
|
+
locals: Sequence[pb.KV] = (),
|
|
41
|
+
condition_results: pb.ConditionResults | None = None,
|
|
42
|
+
channel_infos: dict[str, pb.ChannelInfo] | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
self._method = method
|
|
45
|
+
self._flow = flow
|
|
46
|
+
self._metadata = metadata
|
|
47
|
+
self._values = values
|
|
48
|
+
self._attributes = self._map_values("Attribute", attributes)
|
|
49
|
+
self._locals = self._map_values("step-execution local", locals)
|
|
50
|
+
self._condition_results = condition_results
|
|
51
|
+
self._channel_infos = dict(channel_infos or {})
|
|
52
|
+
self.attribute_writes: dict[str, pb.AttributeWrite] = {}
|
|
53
|
+
self.local_writes: dict[str, pb.KV] = {}
|
|
54
|
+
self.events: list[pb.KV] = []
|
|
55
|
+
self.publications: list[pb.ChannelMessage] = []
|
|
56
|
+
self._event_names: set[str] = set()
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def flow_id(self) -> str:
|
|
60
|
+
return self._metadata.flow_id
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def run_id(self) -> str:
|
|
64
|
+
return self._metadata.run_id
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def step_execution_id(self) -> str:
|
|
68
|
+
return self._metadata.step_execution_id
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def from_step_execution_id(self) -> str:
|
|
72
|
+
return self._metadata.from_step_execution_id
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def attempt(self) -> int:
|
|
76
|
+
return self._metadata.attempt
|
|
77
|
+
|
|
78
|
+
def has_timer_fired(self, index: int | None = None) -> bool:
|
|
79
|
+
if self._condition_results is None:
|
|
80
|
+
return False
|
|
81
|
+
results = self._condition_results.timer_results
|
|
82
|
+
if index is None:
|
|
83
|
+
return any(
|
|
84
|
+
result.condition_status == pb.CONDITION_STATUS_COMPLETED
|
|
85
|
+
for result in results
|
|
86
|
+
)
|
|
87
|
+
return (
|
|
88
|
+
0 <= index < len(results)
|
|
89
|
+
and results[index].condition_status == pb.CONDITION_STATUS_COMPLETED
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def wait_for_method_failed(self) -> bool:
|
|
93
|
+
return bool(
|
|
94
|
+
self._condition_results is not None
|
|
95
|
+
and self._condition_results.wait_for_failed
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def set_step_execution_local(self, key: str, value: object) -> None:
|
|
99
|
+
require_name(key)
|
|
100
|
+
self.local_writes[key] = pb.KV(
|
|
101
|
+
key=key,
|
|
102
|
+
value=self._values.encode_dynamic(value),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def get_step_execution_local(
|
|
106
|
+
self,
|
|
107
|
+
key: str,
|
|
108
|
+
value_type: type[ValueT],
|
|
109
|
+
) -> ValueT | None:
|
|
110
|
+
require_name(key)
|
|
111
|
+
entry = self.local_writes.get(key)
|
|
112
|
+
value = entry.value if entry is not None else self._locals.get(key)
|
|
113
|
+
if value is None:
|
|
114
|
+
return None
|
|
115
|
+
return cast(
|
|
116
|
+
ValueT,
|
|
117
|
+
self._values.decode(value, self._flow_codec(value_type)),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def record_event(self, name: str, value: object) -> None:
|
|
121
|
+
require_name(name)
|
|
122
|
+
if name in self._event_names:
|
|
123
|
+
raise ValueError(f"event was already recorded: {name}")
|
|
124
|
+
self._event_names.add(name)
|
|
125
|
+
self.events.append(pb.KV(key=name, value=self._values.encode_dynamic(value)))
|
|
126
|
+
|
|
127
|
+
def _get_attribute(
|
|
128
|
+
self,
|
|
129
|
+
definition: Attribute[ValueT] | AttributeMap[ValueT],
|
|
130
|
+
instance: str | None,
|
|
131
|
+
) -> ValueT:
|
|
132
|
+
self._require_registered(definition)
|
|
133
|
+
key = self._physical_name(definition, instance)
|
|
134
|
+
write = self.attribute_writes.get(key)
|
|
135
|
+
if write is not None:
|
|
136
|
+
if write.value.WhichOneof("kind") == "null_value":
|
|
137
|
+
return cast(ValueT, self._default_value(definition.value_type))
|
|
138
|
+
value: pb.Value | None = write.value
|
|
139
|
+
else:
|
|
140
|
+
value = self._attributes.get(key)
|
|
141
|
+
if value is None:
|
|
142
|
+
return cast(ValueT, self._default_value(definition.value_type))
|
|
143
|
+
codec = self._flow_codec(definition.value_type)
|
|
144
|
+
return cast(ValueT, self._values.decode(value, codec))
|
|
145
|
+
|
|
146
|
+
def _set_attribute(
|
|
147
|
+
self,
|
|
148
|
+
definition: Attribute[ValueT] | AttributeMap[ValueT],
|
|
149
|
+
instance: str | None,
|
|
150
|
+
value: ValueT,
|
|
151
|
+
) -> None:
|
|
152
|
+
self._require_registered(definition)
|
|
153
|
+
key = self._physical_name(definition, instance)
|
|
154
|
+
write = pb.AttributeWrite(
|
|
155
|
+
key=key,
|
|
156
|
+
value=self._values.encode(value, self._flow_codec(definition.value_type)),
|
|
157
|
+
)
|
|
158
|
+
index = self._values.index_config(
|
|
159
|
+
definition.index,
|
|
160
|
+
isinstance(definition, AttributeMap),
|
|
161
|
+
)
|
|
162
|
+
if index is not None:
|
|
163
|
+
write.index_config.CopyFrom(index)
|
|
164
|
+
self.attribute_writes[key] = write
|
|
165
|
+
|
|
166
|
+
def _delete_attribute(
|
|
167
|
+
self,
|
|
168
|
+
definition: Attribute[object] | AttributeMap[object],
|
|
169
|
+
instance: str | None,
|
|
170
|
+
) -> None:
|
|
171
|
+
self._require_registered(definition)
|
|
172
|
+
key = self._physical_name(definition, instance)
|
|
173
|
+
write = pb.AttributeWrite(key=key, value=self._values.deletion())
|
|
174
|
+
index = self._values.index_config(
|
|
175
|
+
definition.index,
|
|
176
|
+
isinstance(definition, AttributeMap),
|
|
177
|
+
)
|
|
178
|
+
if index is not None:
|
|
179
|
+
write.index_config.CopyFrom(index)
|
|
180
|
+
self.attribute_writes[key] = write
|
|
181
|
+
|
|
182
|
+
def _publish_channel(
|
|
183
|
+
self,
|
|
184
|
+
definition: Channel[ValueT] | ChannelMap[ValueT],
|
|
185
|
+
instance: str | None,
|
|
186
|
+
value: ValueT,
|
|
187
|
+
) -> None:
|
|
188
|
+
self._require_registered(definition)
|
|
189
|
+
name = self._physical_name(definition, instance)
|
|
190
|
+
self.publications.append(
|
|
191
|
+
pb.ChannelMessage(
|
|
192
|
+
channel_name=name,
|
|
193
|
+
value=self._values.encode(
|
|
194
|
+
value,
|
|
195
|
+
self._flow_codec(definition.value_type),
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
if self._method is InvocationMethod.RPC:
|
|
200
|
+
current = self._channel_infos.get(name)
|
|
201
|
+
self._channel_infos[name] = pb.ChannelInfo(
|
|
202
|
+
size=(current.size if current is not None else 0) + 1
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def _channel_size(
|
|
206
|
+
self,
|
|
207
|
+
definition: Channel[object] | ChannelMap[object],
|
|
208
|
+
instance: str | None,
|
|
209
|
+
) -> int:
|
|
210
|
+
self._require_registered(definition)
|
|
211
|
+
info = self._channel_infos.get(self._physical_name(definition, instance))
|
|
212
|
+
return info.size if info is not None else 0
|
|
213
|
+
|
|
214
|
+
def _channel_results(
|
|
215
|
+
self,
|
|
216
|
+
definition: Channel[ValueT] | ChannelMap[ValueT],
|
|
217
|
+
instance: str | None,
|
|
218
|
+
) -> Sequence[ValueT]:
|
|
219
|
+
self._require_registered(definition)
|
|
220
|
+
if self._condition_results is None:
|
|
221
|
+
return ()
|
|
222
|
+
name = self._physical_name(definition, instance)
|
|
223
|
+
codec = self._flow_codec(definition.value_type)
|
|
224
|
+
return tuple(
|
|
225
|
+
cast(ValueT, self._values.decode(value, codec))
|
|
226
|
+
for result in self._condition_results.channel_results
|
|
227
|
+
if result.channel_name == name
|
|
228
|
+
and result.condition_status == pb.CONDITION_STATUS_COMPLETED
|
|
229
|
+
for value in result.values
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _flow_codec(self, value_type: object) -> Codec[Any]:
|
|
233
|
+
return self._values.codec(value_type)
|
|
234
|
+
|
|
235
|
+
def _require_registered(self, definition: _Definition) -> None:
|
|
236
|
+
registered = self._flow.persistence.get(definition.name)
|
|
237
|
+
if registered is not definition:
|
|
238
|
+
raise ValueError(
|
|
239
|
+
f"persistence definition does not belong to Flow: {definition.name}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def _physical_name(definition: _Definition, instance: str | None) -> str:
|
|
244
|
+
if isinstance(definition, (AttributeMap, ChannelMap)):
|
|
245
|
+
if instance is None:
|
|
246
|
+
raise ValueError("dynamic definition requires an instance")
|
|
247
|
+
return Registry.physical_name(definition.name, instance)
|
|
248
|
+
if instance is not None:
|
|
249
|
+
raise ValueError("static definition cannot use an instance")
|
|
250
|
+
return definition.name
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def _map_values(kind: str, entries: Sequence[pb.KV]) -> dict[str, pb.Value]:
|
|
254
|
+
values: dict[str, pb.Value] = {}
|
|
255
|
+
for entry in entries:
|
|
256
|
+
if not entry.key or not entry.HasField("value") or entry.key in values:
|
|
257
|
+
raise ValueError(f"invalid or duplicate {kind}")
|
|
258
|
+
values[entry.key] = entry.value
|
|
259
|
+
return values
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def _default_value(value_type: object) -> object | None:
|
|
263
|
+
if value_type is bool:
|
|
264
|
+
return False
|
|
265
|
+
if value_type is int:
|
|
266
|
+
return 0
|
|
267
|
+
if value_type is float:
|
|
268
|
+
return 0.0
|
|
269
|
+
return None
|
dex/_native.pyd
ADDED
|
Binary file
|
dex/_native.pyi
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Super Durable Source License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
|
|
8
|
+
|
|
9
|
+
class NativeBlobCache:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
directory: str,
|
|
13
|
+
max_bytes: int,
|
|
14
|
+
frequency_counters: int,
|
|
15
|
+
) -> None: ...
|
|
16
|
+
def get(self, blob_id: str) -> bytes | None: ...
|
|
17
|
+
def put(self, blob_id: str, payload: bytes) -> bool: ...
|
|
18
|
+
def delete(self, blob_id: str) -> None: ...
|
|
19
|
+
def delete_all(self) -> None: ...
|
|
20
|
+
def close(self) -> None: ...
|
dex/_utils.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Super Durable Source License 1.0.
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# See the LICENSE file in the repository root.
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def require_name(name: str) -> str:
|
|
11
|
+
if not name.strip():
|
|
12
|
+
raise ValueError("durable name is required")
|
|
13
|
+
return name
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def validate_condition_id(condition_id: str | None) -> None:
|
|
17
|
+
if condition_id is not None and not condition_id:
|
|
18
|
+
raise ValueError("condition ID must not be empty")
|