async-durable-execution-runner 2.0.0a1__py3-none-any.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.
- LICENSE +175 -0
- NOTICE +8 -0
- VERSION.py +5 -0
- async_durable_execution_runner/__about__.py +33 -0
- async_durable_execution_runner/__init__.py +23 -0
- async_durable_execution_runner/checkpoint/__init__.py +1 -0
- async_durable_execution_runner/checkpoint/processor.py +101 -0
- async_durable_execution_runner/checkpoint/processors/__init__.py +1 -0
- async_durable_execution_runner/checkpoint/processors/base.py +199 -0
- async_durable_execution_runner/checkpoint/processors/callback.py +89 -0
- async_durable_execution_runner/checkpoint/processors/context.py +59 -0
- async_durable_execution_runner/checkpoint/processors/execution.py +52 -0
- async_durable_execution_runner/checkpoint/processors/step.py +124 -0
- async_durable_execution_runner/checkpoint/processors/wait.py +95 -0
- async_durable_execution_runner/checkpoint/transformer.py +104 -0
- async_durable_execution_runner/checkpoint/validators/__init__.py +1 -0
- async_durable_execution_runner/checkpoint/validators/checkpoint.py +242 -0
- async_durable_execution_runner/checkpoint/validators/operations/__init__.py +1 -0
- async_durable_execution_runner/checkpoint/validators/operations/callback.py +45 -0
- async_durable_execution_runner/checkpoint/validators/operations/context.py +73 -0
- async_durable_execution_runner/checkpoint/validators/operations/execution.py +47 -0
- async_durable_execution_runner/checkpoint/validators/operations/invoke.py +56 -0
- async_durable_execution_runner/checkpoint/validators/operations/step.py +106 -0
- async_durable_execution_runner/checkpoint/validators/operations/wait.py +54 -0
- async_durable_execution_runner/checkpoint/validators/transitions.py +66 -0
- async_durable_execution_runner/cli.py +498 -0
- async_durable_execution_runner/client.py +50 -0
- async_durable_execution_runner/exceptions.py +288 -0
- async_durable_execution_runner/execution.py +444 -0
- async_durable_execution_runner/executor.py +1234 -0
- async_durable_execution_runner/invoker.py +340 -0
- async_durable_execution_runner/model.py +3296 -0
- async_durable_execution_runner/observer.py +144 -0
- async_durable_execution_runner/py.typed +1 -0
- async_durable_execution_runner/runner.py +1167 -0
- async_durable_execution_runner/scheduler.py +246 -0
- async_durable_execution_runner/stores/__init__.py +1 -0
- async_durable_execution_runner/stores/base.py +147 -0
- async_durable_execution_runner/stores/filesystem.py +79 -0
- async_durable_execution_runner/stores/memory.py +38 -0
- async_durable_execution_runner/stores/sqlite.py +273 -0
- async_durable_execution_runner/token.py +49 -0
- async_durable_execution_runner/web/__init__.py +1 -0
- async_durable_execution_runner/web/errors.py +8 -0
- async_durable_execution_runner/web/handlers.py +813 -0
- async_durable_execution_runner/web/models.py +266 -0
- async_durable_execution_runner/web/routes.py +692 -0
- async_durable_execution_runner/web/serialization.py +235 -0
- async_durable_execution_runner/web/server.py +243 -0
- async_durable_execution_runner-2.0.0a1.dist-info/METADATA +238 -0
- async_durable_execution_runner-2.0.0a1.dist-info/RECORD +55 -0
- async_durable_execution_runner-2.0.0a1.dist-info/WHEEL +4 -0
- async_durable_execution_runner-2.0.0a1.dist-info/entry_points.txt +2 -0
- async_durable_execution_runner-2.0.0a1.dist-info/licenses/LICENSE +175 -0
- async_durable_execution_runner-2.0.0a1.dist-info/licenses/NOTICE +1 -0
|
@@ -0,0 +1,3296 @@
|
|
|
1
|
+
"""Model classes for the web API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass, replace
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from async_durable_execution.execution import DurableExecutionInvocationOutput
|
|
12
|
+
|
|
13
|
+
# Import existing types from the main SDK - REUSE EVERYTHING POSSIBLE
|
|
14
|
+
from async_durable_execution.lambda_service import (
|
|
15
|
+
CallbackDetails,
|
|
16
|
+
CallbackOptions,
|
|
17
|
+
ChainedInvokeDetails,
|
|
18
|
+
ChainedInvokeOptions,
|
|
19
|
+
ContextDetails,
|
|
20
|
+
ContextOptions,
|
|
21
|
+
ErrorObject,
|
|
22
|
+
ExecutionDetails,
|
|
23
|
+
Operation,
|
|
24
|
+
OperationAction,
|
|
25
|
+
OperationStatus,
|
|
26
|
+
OperationSubType,
|
|
27
|
+
OperationType,
|
|
28
|
+
OperationUpdate,
|
|
29
|
+
StepDetails,
|
|
30
|
+
StepOptions,
|
|
31
|
+
TimestampConverter,
|
|
32
|
+
WaitDetails,
|
|
33
|
+
WaitOptions,
|
|
34
|
+
)
|
|
35
|
+
from async_durable_execution.types import (
|
|
36
|
+
LambdaContext as LambdaContextProtocol,
|
|
37
|
+
)
|
|
38
|
+
from dateutil.tz import UTC
|
|
39
|
+
|
|
40
|
+
from async_durable_execution_runner.exceptions import (
|
|
41
|
+
InvalidParameterValueException,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EventType(Enum):
|
|
46
|
+
"""Event types for durable execution events."""
|
|
47
|
+
|
|
48
|
+
EXECUTION_STARTED = "ExecutionStarted"
|
|
49
|
+
EXECUTION_SUCCEEDED = "ExecutionSucceeded"
|
|
50
|
+
EXECUTION_FAILED = "ExecutionFailed"
|
|
51
|
+
EXECUTION_TIMED_OUT = "ExecutionTimedOut"
|
|
52
|
+
EXECUTION_STOPPED = "ExecutionStopped"
|
|
53
|
+
CONTEXT_STARTED = "ContextStarted"
|
|
54
|
+
CONTEXT_SUCCEEDED = "ContextSucceeded"
|
|
55
|
+
CONTEXT_FAILED = "ContextFailed"
|
|
56
|
+
WAIT_STARTED = "WaitStarted"
|
|
57
|
+
WAIT_SUCCEEDED = "WaitSucceeded"
|
|
58
|
+
WAIT_CANCELLED = "WaitCancelled"
|
|
59
|
+
STEP_STARTED = "StepStarted"
|
|
60
|
+
STEP_SUCCEEDED = "StepSucceeded"
|
|
61
|
+
STEP_FAILED = "StepFailed"
|
|
62
|
+
CHAINED_INVOKE_STARTED = "ChainedInvokeStarted"
|
|
63
|
+
CHAINED_INVOKE_SUCCEEDED = "ChainedInvokeSucceeded"
|
|
64
|
+
CHAINED_INVOKE_FAILED = "ChainedInvokeFailed"
|
|
65
|
+
CHAINED_INVOKE_TIMED_OUT = "ChainedInvokeTimedOut"
|
|
66
|
+
CHAINED_INVOKE_STOPPED = "ChainedInvokeStopped"
|
|
67
|
+
CALLBACK_STARTED = "CallbackStarted"
|
|
68
|
+
CALLBACK_SUCCEEDED = "CallbackSucceeded"
|
|
69
|
+
CALLBACK_FAILED = "CallbackFailed"
|
|
70
|
+
CALLBACK_TIMED_OUT = "CallbackTimedOut"
|
|
71
|
+
INVOCATION_COMPLETED = "InvocationCompleted"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
TERMINAL_STATUSES: set[OperationStatus] = {
|
|
75
|
+
OperationStatus.SUCCEEDED,
|
|
76
|
+
OperationStatus.FAILED,
|
|
77
|
+
OperationStatus.TIMED_OUT,
|
|
78
|
+
OperationStatus.STOPPED,
|
|
79
|
+
OperationStatus.CANCELLED,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class LambdaContext(LambdaContextProtocol):
|
|
85
|
+
"""Lambda context for testing."""
|
|
86
|
+
|
|
87
|
+
aws_request_id: str
|
|
88
|
+
log_group_name: str | None = None
|
|
89
|
+
log_stream_name: str | None = None
|
|
90
|
+
function_name: str | None = None
|
|
91
|
+
memory_limit_in_mb: str | None = None
|
|
92
|
+
function_version: str | None = None
|
|
93
|
+
invoked_function_arn: str | None = None
|
|
94
|
+
tenant_id: str | None = None
|
|
95
|
+
client_context: dict | None = None
|
|
96
|
+
identity: dict | None = None
|
|
97
|
+
|
|
98
|
+
def get_remaining_time_in_millis(self) -> int:
|
|
99
|
+
return 900000 # 15 minutes default
|
|
100
|
+
|
|
101
|
+
def log(self, msg) -> None:
|
|
102
|
+
pass # No-op for testing
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# region web_api_models
|
|
106
|
+
# Web API specific models (not in Smithy but needed for web interface)
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class StartDurableExecutionInput:
|
|
109
|
+
"""Input for starting a durable execution via web API."""
|
|
110
|
+
|
|
111
|
+
account_id: str
|
|
112
|
+
function_name: str
|
|
113
|
+
function_qualifier: str
|
|
114
|
+
execution_name: str
|
|
115
|
+
execution_timeout_seconds: int
|
|
116
|
+
execution_retention_period_days: int
|
|
117
|
+
invocation_id: str | None = None
|
|
118
|
+
trace_fields: dict | None = None
|
|
119
|
+
tenant_id: str | None = None
|
|
120
|
+
input: str | None = None
|
|
121
|
+
lambda_endpoint: str | None = None # Endpoint for this specific execution
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_dict(cls, data: dict) -> StartDurableExecutionInput:
|
|
125
|
+
# Validate required fields and raise AWS-compliant exceptions
|
|
126
|
+
required_fields = [
|
|
127
|
+
"AccountId",
|
|
128
|
+
"FunctionName",
|
|
129
|
+
"FunctionQualifier",
|
|
130
|
+
"ExecutionName",
|
|
131
|
+
"ExecutionTimeoutSeconds",
|
|
132
|
+
"ExecutionRetentionPeriodDays",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
for field in required_fields:
|
|
136
|
+
if field not in data:
|
|
137
|
+
msg: str = f"Missing required field: {field}"
|
|
138
|
+
raise InvalidParameterValueException(msg)
|
|
139
|
+
|
|
140
|
+
return cls(
|
|
141
|
+
account_id=data["AccountId"],
|
|
142
|
+
function_name=data["FunctionName"],
|
|
143
|
+
function_qualifier=data["FunctionQualifier"],
|
|
144
|
+
execution_name=data["ExecutionName"],
|
|
145
|
+
execution_timeout_seconds=data["ExecutionTimeoutSeconds"],
|
|
146
|
+
execution_retention_period_days=data["ExecutionRetentionPeriodDays"],
|
|
147
|
+
invocation_id=data.get("InvocationId"),
|
|
148
|
+
trace_fields=data.get("TraceFields"),
|
|
149
|
+
tenant_id=data.get("TenantId"),
|
|
150
|
+
input=data.get("Input"),
|
|
151
|
+
lambda_endpoint=data.get("LambdaEndpoint", None),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def to_dict(self) -> dict[str, Any]:
|
|
155
|
+
result = {
|
|
156
|
+
"AccountId": self.account_id,
|
|
157
|
+
"FunctionName": self.function_name,
|
|
158
|
+
"FunctionQualifier": self.function_qualifier,
|
|
159
|
+
"ExecutionName": self.execution_name,
|
|
160
|
+
"ExecutionTimeoutSeconds": self.execution_timeout_seconds,
|
|
161
|
+
"ExecutionRetentionPeriodDays": self.execution_retention_period_days,
|
|
162
|
+
}
|
|
163
|
+
if self.invocation_id is not None:
|
|
164
|
+
result["InvocationId"] = self.invocation_id
|
|
165
|
+
if self.trace_fields is not None:
|
|
166
|
+
result["TraceFields"] = self.trace_fields
|
|
167
|
+
if self.tenant_id is not None:
|
|
168
|
+
result["TenantId"] = self.tenant_id
|
|
169
|
+
if self.input is not None:
|
|
170
|
+
result["Input"] = self.input
|
|
171
|
+
if self.lambda_endpoint is not None:
|
|
172
|
+
result["LambdaEndpoint"] = self.lambda_endpoint
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
def get_normalized_input(self):
|
|
176
|
+
"""
|
|
177
|
+
Normalize input string to be JSON deserializable.
|
|
178
|
+
Avoid double coding json input.
|
|
179
|
+
"""
|
|
180
|
+
# Try to parse once
|
|
181
|
+
try:
|
|
182
|
+
_ = json.loads(self.input)
|
|
183
|
+
return self.input
|
|
184
|
+
except (json.JSONDecodeError, TypeError):
|
|
185
|
+
# Not valid JSON, treat as plain string and encode it
|
|
186
|
+
return json.dumps(self.input)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(frozen=True)
|
|
190
|
+
class StartDurableExecutionOutput:
|
|
191
|
+
"""Output from starting a durable execution via web API."""
|
|
192
|
+
|
|
193
|
+
execution_arn: str | None = None
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def from_dict(cls, data: dict) -> StartDurableExecutionOutput:
|
|
197
|
+
return cls(execution_arn=data.get("ExecutionArn"))
|
|
198
|
+
|
|
199
|
+
def to_dict(self) -> dict[str, Any]:
|
|
200
|
+
result = {}
|
|
201
|
+
if self.execution_arn is not None:
|
|
202
|
+
result["ExecutionArn"] = self.execution_arn
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# endregion web_api_models
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# region smithy_api_models
|
|
210
|
+
# Smithy-based API models
|
|
211
|
+
@dataclass(frozen=True)
|
|
212
|
+
class GetDurableExecutionRequest:
|
|
213
|
+
"""Request to get durable execution details."""
|
|
214
|
+
|
|
215
|
+
durable_execution_arn: str
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def from_dict(cls, data: dict) -> GetDurableExecutionRequest:
|
|
219
|
+
return cls(durable_execution_arn=data["DurableExecutionArn"])
|
|
220
|
+
|
|
221
|
+
def to_dict(self) -> dict[str, Any]:
|
|
222
|
+
return {"DurableExecutionArn": self.durable_execution_arn}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass(frozen=True)
|
|
226
|
+
class GetDurableExecutionResponse:
|
|
227
|
+
"""Response containing durable execution details."""
|
|
228
|
+
|
|
229
|
+
durable_execution_arn: str
|
|
230
|
+
durable_execution_name: str
|
|
231
|
+
function_arn: str
|
|
232
|
+
status: str
|
|
233
|
+
start_timestamp: datetime.datetime
|
|
234
|
+
input_payload: str | None = None
|
|
235
|
+
result: str | None = None
|
|
236
|
+
error: ErrorObject | None = None
|
|
237
|
+
end_timestamp: datetime.datetime | None = None
|
|
238
|
+
version: str | None = None
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def from_dict(cls, data: dict) -> GetDurableExecutionResponse:
|
|
242
|
+
error = None
|
|
243
|
+
if error_data := data.get("Error"):
|
|
244
|
+
error = ErrorObject.from_dict(error_data)
|
|
245
|
+
|
|
246
|
+
return cls(
|
|
247
|
+
durable_execution_arn=data["DurableExecutionArn"],
|
|
248
|
+
durable_execution_name=data["DurableExecutionName"],
|
|
249
|
+
function_arn=data["FunctionArn"],
|
|
250
|
+
status=data["Status"],
|
|
251
|
+
start_timestamp=data["StartTimestamp"],
|
|
252
|
+
input_payload=data.get("InputPayload"),
|
|
253
|
+
result=data.get("Result"),
|
|
254
|
+
error=error,
|
|
255
|
+
end_timestamp=data.get("EndTimestamp"),
|
|
256
|
+
version=data.get("Version"),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def to_dict(self) -> dict[str, Any]:
|
|
260
|
+
result: dict[str, Any] = {
|
|
261
|
+
"DurableExecutionArn": self.durable_execution_arn,
|
|
262
|
+
"DurableExecutionName": self.durable_execution_name,
|
|
263
|
+
"FunctionArn": self.function_arn,
|
|
264
|
+
"Status": self.status,
|
|
265
|
+
"StartTimestamp": self.start_timestamp,
|
|
266
|
+
}
|
|
267
|
+
if self.input_payload is not None:
|
|
268
|
+
result["InputPayload"] = self.input_payload
|
|
269
|
+
if self.result is not None:
|
|
270
|
+
result["Result"] = self.result
|
|
271
|
+
if self.error is not None:
|
|
272
|
+
result["Error"] = self.error.to_dict()
|
|
273
|
+
if self.end_timestamp is not None:
|
|
274
|
+
result["EndTimestamp"] = self.end_timestamp
|
|
275
|
+
if self.end_timestamp is not None:
|
|
276
|
+
result["EndTimestamp"] = self.end_timestamp
|
|
277
|
+
if self.version is not None:
|
|
278
|
+
result["Version"] = self.version
|
|
279
|
+
return result
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass(frozen=True)
|
|
283
|
+
class Execution:
|
|
284
|
+
"""Execution summary structure from Smithy model."""
|
|
285
|
+
|
|
286
|
+
durable_execution_arn: str
|
|
287
|
+
durable_execution_name: str
|
|
288
|
+
function_arn: str
|
|
289
|
+
status: str
|
|
290
|
+
start_timestamp: datetime.datetime
|
|
291
|
+
end_timestamp: datetime.datetime | None = None
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def from_dict(cls, data: dict) -> Execution:
|
|
295
|
+
return cls(
|
|
296
|
+
durable_execution_arn=data["DurableExecutionArn"],
|
|
297
|
+
durable_execution_name=data["DurableExecutionName"],
|
|
298
|
+
function_arn=data.get(
|
|
299
|
+
"FunctionArn", ""
|
|
300
|
+
), # Make optional for backward compatibility
|
|
301
|
+
status=data["Status"],
|
|
302
|
+
start_timestamp=data["StartTimestamp"],
|
|
303
|
+
end_timestamp=data.get("EndTimestamp"),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def to_dict(self) -> dict[str, Any]:
|
|
307
|
+
result = {
|
|
308
|
+
"DurableExecutionArn": self.durable_execution_arn,
|
|
309
|
+
"DurableExecutionName": self.durable_execution_name,
|
|
310
|
+
"Status": self.status,
|
|
311
|
+
"StartTimestamp": self.start_timestamp,
|
|
312
|
+
}
|
|
313
|
+
if self.function_arn: # Only include if not empty
|
|
314
|
+
result["FunctionArn"] = self.function_arn
|
|
315
|
+
if self.end_timestamp is not None:
|
|
316
|
+
result["EndTimestamp"] = self.end_timestamp
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_execution(cls, execution, status: str) -> Execution:
|
|
321
|
+
"""Create ExecutionSummary from Execution object."""
|
|
322
|
+
|
|
323
|
+
execution_op = execution.get_operation_execution_started()
|
|
324
|
+
return cls(
|
|
325
|
+
durable_execution_arn=execution.durable_execution_arn,
|
|
326
|
+
durable_execution_name=execution.start_input.execution_name,
|
|
327
|
+
function_arn=f"arn:aws:lambda:us-east-1:123456789012:function:{execution.start_input.function_name}",
|
|
328
|
+
status=status,
|
|
329
|
+
start_timestamp=execution_op.start_timestamp
|
|
330
|
+
if execution_op.start_timestamp
|
|
331
|
+
else datetime.datetime.now(datetime.UTC),
|
|
332
|
+
end_timestamp=execution_op.end_timestamp
|
|
333
|
+
if execution_op.end_timestamp
|
|
334
|
+
else None,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass(frozen=True)
|
|
339
|
+
class ListDurableExecutionsRequest:
|
|
340
|
+
"""Request to list durable executions."""
|
|
341
|
+
|
|
342
|
+
function_name: str | None = None
|
|
343
|
+
function_version: str | None = None
|
|
344
|
+
durable_execution_name: str | None = None
|
|
345
|
+
status_filter: list[str] | None = None
|
|
346
|
+
started_after: str | None = None
|
|
347
|
+
started_before: str | None = None
|
|
348
|
+
marker: str | None = None
|
|
349
|
+
max_items: int = 0
|
|
350
|
+
reverse_order: bool | None = None
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def from_dict(cls, data: dict) -> ListDurableExecutionsRequest:
|
|
354
|
+
# Handle query parameters that may be lists
|
|
355
|
+
function_name = data.get("FunctionName")
|
|
356
|
+
if isinstance(function_name, list):
|
|
357
|
+
function_name = function_name[0] if function_name else None
|
|
358
|
+
|
|
359
|
+
function_version = data.get("FunctionVersion")
|
|
360
|
+
if isinstance(function_version, list):
|
|
361
|
+
function_version = function_version[0] if function_version else None
|
|
362
|
+
|
|
363
|
+
durable_execution_name = data.get("DurableExecutionName")
|
|
364
|
+
if isinstance(durable_execution_name, list):
|
|
365
|
+
durable_execution_name = (
|
|
366
|
+
durable_execution_name[0] if durable_execution_name else None
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
status_filter = data.get("StatusFilter")
|
|
370
|
+
if isinstance(status_filter, list):
|
|
371
|
+
status_filter = status_filter if status_filter else None
|
|
372
|
+
elif status_filter:
|
|
373
|
+
status_filter = [status_filter]
|
|
374
|
+
|
|
375
|
+
started_after = data.get("StartedAfter")
|
|
376
|
+
if isinstance(started_after, list):
|
|
377
|
+
started_after = started_after[0] if started_after else None
|
|
378
|
+
|
|
379
|
+
started_before = data.get("StartedBefore")
|
|
380
|
+
if isinstance(started_before, list):
|
|
381
|
+
started_before = started_before[0] if started_before else None
|
|
382
|
+
|
|
383
|
+
marker = data.get("Marker")
|
|
384
|
+
if isinstance(marker, list):
|
|
385
|
+
marker = marker[0] if marker else None
|
|
386
|
+
|
|
387
|
+
max_items = data.get("MaxItems", 0)
|
|
388
|
+
if isinstance(max_items, list):
|
|
389
|
+
max_items = int(max_items[0]) if max_items else 0
|
|
390
|
+
|
|
391
|
+
reverse_order = data.get("ReverseOrder")
|
|
392
|
+
if isinstance(reverse_order, list):
|
|
393
|
+
reverse_order = (
|
|
394
|
+
reverse_order[0].lower() in ("true", "1", "yes")
|
|
395
|
+
if reverse_order
|
|
396
|
+
else None
|
|
397
|
+
)
|
|
398
|
+
elif isinstance(reverse_order, str):
|
|
399
|
+
reverse_order = reverse_order.lower() in ("true", "1", "yes")
|
|
400
|
+
|
|
401
|
+
return cls(
|
|
402
|
+
function_name=function_name,
|
|
403
|
+
function_version=function_version,
|
|
404
|
+
durable_execution_name=durable_execution_name,
|
|
405
|
+
status_filter=status_filter,
|
|
406
|
+
started_after=started_after,
|
|
407
|
+
started_before=started_before,
|
|
408
|
+
marker=marker,
|
|
409
|
+
max_items=max_items,
|
|
410
|
+
reverse_order=reverse_order,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
def to_dict(self) -> dict[str, Any]:
|
|
414
|
+
result: dict[str, Any] = {}
|
|
415
|
+
if self.function_name is not None:
|
|
416
|
+
result["FunctionName"] = self.function_name
|
|
417
|
+
if self.function_version is not None:
|
|
418
|
+
result["FunctionVersion"] = self.function_version
|
|
419
|
+
if self.durable_execution_name is not None:
|
|
420
|
+
result["DurableExecutionName"] = self.durable_execution_name
|
|
421
|
+
if self.status_filter is not None:
|
|
422
|
+
result["StatusFilter"] = self.status_filter
|
|
423
|
+
if self.started_after is not None:
|
|
424
|
+
result["StartedAfter"] = self.started_after
|
|
425
|
+
if self.started_before is not None:
|
|
426
|
+
result["StartedBefore"] = self.started_before
|
|
427
|
+
if self.marker is not None:
|
|
428
|
+
result["Marker"] = self.marker
|
|
429
|
+
if self.max_items is not None:
|
|
430
|
+
result["MaxItems"] = self.max_items
|
|
431
|
+
if self.reverse_order is not None:
|
|
432
|
+
result["ReverseOrder"] = self.reverse_order
|
|
433
|
+
return result
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@dataclass(frozen=True)
|
|
437
|
+
class ListDurableExecutionsResponse:
|
|
438
|
+
"""Response containing list of durable executions."""
|
|
439
|
+
|
|
440
|
+
durable_executions: list[Execution]
|
|
441
|
+
next_marker: str | None = None
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def from_dict(cls, data: dict) -> ListDurableExecutionsResponse:
|
|
445
|
+
executions = [
|
|
446
|
+
Execution.from_dict(exec_data)
|
|
447
|
+
for exec_data in data.get("DurableExecutions", [])
|
|
448
|
+
]
|
|
449
|
+
return cls(
|
|
450
|
+
durable_executions=executions,
|
|
451
|
+
next_marker=data.get("NextMarker"),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def to_dict(self) -> dict[str, Any]:
|
|
455
|
+
result: dict[str, Any] = {
|
|
456
|
+
"DurableExecutions": [exe.to_dict() for exe in self.durable_executions]
|
|
457
|
+
}
|
|
458
|
+
if self.next_marker is not None:
|
|
459
|
+
result["NextMarker"] = self.next_marker
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@dataclass(frozen=True)
|
|
464
|
+
class StopDurableExecutionRequest:
|
|
465
|
+
"""Request to stop a durable execution."""
|
|
466
|
+
|
|
467
|
+
durable_execution_arn: str
|
|
468
|
+
error: ErrorObject | None = None
|
|
469
|
+
|
|
470
|
+
@classmethod
|
|
471
|
+
def from_dict(cls, data: dict) -> StopDurableExecutionRequest:
|
|
472
|
+
error = None
|
|
473
|
+
if error_data := data.get("Error"):
|
|
474
|
+
error = ErrorObject.from_dict(error_data)
|
|
475
|
+
|
|
476
|
+
return cls(
|
|
477
|
+
durable_execution_arn=data["DurableExecutionArn"],
|
|
478
|
+
error=error,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def to_dict(self) -> dict[str, Any]:
|
|
482
|
+
result: dict[str, Any] = {"DurableExecutionArn": self.durable_execution_arn}
|
|
483
|
+
if self.error is not None:
|
|
484
|
+
result["Error"] = self.error.to_dict()
|
|
485
|
+
return result
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@dataclass(frozen=True)
|
|
489
|
+
class StopDurableExecutionResponse:
|
|
490
|
+
"""Response from stopping a durable execution."""
|
|
491
|
+
|
|
492
|
+
stop_timestamp: datetime.datetime
|
|
493
|
+
|
|
494
|
+
@classmethod
|
|
495
|
+
def from_dict(cls, data: dict) -> StopDurableExecutionResponse:
|
|
496
|
+
return cls(stop_timestamp=data["StopTimestamp"])
|
|
497
|
+
|
|
498
|
+
def to_dict(self) -> dict[str, Any]:
|
|
499
|
+
return {"StopTimestamp": self.stop_timestamp}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@dataclass(frozen=True)
|
|
503
|
+
class GetDurableExecutionStateRequest:
|
|
504
|
+
"""Request to get durable execution state."""
|
|
505
|
+
|
|
506
|
+
durable_execution_arn: str
|
|
507
|
+
checkpoint_token: str
|
|
508
|
+
marker: str | None = None
|
|
509
|
+
max_items: int = 0
|
|
510
|
+
|
|
511
|
+
@classmethod
|
|
512
|
+
def from_dict(cls, data: dict) -> GetDurableExecutionStateRequest:
|
|
513
|
+
return cls(
|
|
514
|
+
durable_execution_arn=data["DurableExecutionArn"],
|
|
515
|
+
checkpoint_token=data["CheckpointToken"],
|
|
516
|
+
marker=data.get("Marker"),
|
|
517
|
+
max_items=data.get("MaxItems", 0),
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
def to_dict(self) -> dict[str, Any]:
|
|
521
|
+
result: dict[str, Any] = {
|
|
522
|
+
"DurableExecutionArn": self.durable_execution_arn,
|
|
523
|
+
"CheckpointToken": self.checkpoint_token,
|
|
524
|
+
}
|
|
525
|
+
if self.marker is not None:
|
|
526
|
+
result["Marker"] = self.marker
|
|
527
|
+
if self.max_items is not None:
|
|
528
|
+
result["MaxItems"] = self.max_items
|
|
529
|
+
return result
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@dataclass(frozen=True)
|
|
533
|
+
class GetDurableExecutionStateResponse:
|
|
534
|
+
"""Response containing durable execution state operations."""
|
|
535
|
+
|
|
536
|
+
operations: list[Operation]
|
|
537
|
+
next_marker: str | None = None
|
|
538
|
+
|
|
539
|
+
@classmethod
|
|
540
|
+
def from_dict(cls, data: dict) -> GetDurableExecutionStateResponse:
|
|
541
|
+
operations = [
|
|
542
|
+
Operation.from_dict(op_data) for op_data in data.get("Operations", [])
|
|
543
|
+
]
|
|
544
|
+
return cls(
|
|
545
|
+
operations=operations,
|
|
546
|
+
next_marker=data.get("NextMarker"),
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
def to_dict(self) -> dict[str, Any]:
|
|
550
|
+
result: dict[str, Any] = {
|
|
551
|
+
"Operations": [op.to_dict() for op in self.operations]
|
|
552
|
+
}
|
|
553
|
+
if self.next_marker is not None:
|
|
554
|
+
result["NextMarker"] = self.next_marker
|
|
555
|
+
return result
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
# endregion smithy_api_models
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# region event_structures
|
|
562
|
+
# Event-related structures from Smithy model
|
|
563
|
+
@dataclass(frozen=True)
|
|
564
|
+
class EventInput:
|
|
565
|
+
"""Event input structure."""
|
|
566
|
+
|
|
567
|
+
payload: str | None = None
|
|
568
|
+
truncated: bool = False
|
|
569
|
+
|
|
570
|
+
@classmethod
|
|
571
|
+
def from_dict(cls, data: dict) -> EventInput:
|
|
572
|
+
return cls(
|
|
573
|
+
payload=data.get("Payload"),
|
|
574
|
+
truncated=data.get("Truncated", False),
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
def to_dict(self) -> dict[str, Any]:
|
|
578
|
+
result: dict[str, Any] = {"Truncated": self.truncated}
|
|
579
|
+
if self.payload is not None:
|
|
580
|
+
result["Payload"] = self.payload
|
|
581
|
+
return result
|
|
582
|
+
|
|
583
|
+
@classmethod
|
|
584
|
+
def from_details(
|
|
585
|
+
cls,
|
|
586
|
+
details: ExecutionDetails,
|
|
587
|
+
include: bool = False, # noqa: FBT001, FBT002
|
|
588
|
+
) -> EventInput:
|
|
589
|
+
details_input: str | None = details.input_payload if details else None
|
|
590
|
+
payload: str | None = details_input if include else None
|
|
591
|
+
truncated: bool = not include
|
|
592
|
+
return cls(payload=payload, truncated=truncated)
|
|
593
|
+
|
|
594
|
+
@classmethod
|
|
595
|
+
def from_start_durable_execution_input(
|
|
596
|
+
cls,
|
|
597
|
+
start_durable_execution_input: StartDurableExecutionInput,
|
|
598
|
+
include: bool = False, # noqa: FBT001, FBT002
|
|
599
|
+
) -> EventInput:
|
|
600
|
+
input: str | None = start_durable_execution_input.input
|
|
601
|
+
truncated: bool = not include
|
|
602
|
+
return cls(input, truncated)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@dataclass(frozen=True)
|
|
606
|
+
class EventResult:
|
|
607
|
+
"""Event result structure."""
|
|
608
|
+
|
|
609
|
+
payload: str | None = None
|
|
610
|
+
truncated: bool = False
|
|
611
|
+
|
|
612
|
+
@classmethod
|
|
613
|
+
def from_dict(cls, data: dict) -> EventResult:
|
|
614
|
+
return cls(
|
|
615
|
+
payload=data.get("Payload"),
|
|
616
|
+
truncated=data.get("Truncated", False),
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
def to_dict(self) -> dict[str, Any]:
|
|
620
|
+
result: dict[str, Any] = {"Truncated": self.truncated}
|
|
621
|
+
if self.payload is not None:
|
|
622
|
+
result["Payload"] = self.payload
|
|
623
|
+
return result
|
|
624
|
+
|
|
625
|
+
@classmethod
|
|
626
|
+
def from_details(
|
|
627
|
+
cls,
|
|
628
|
+
details: CallbackDetails | StepDetails | ChainedInvokeDetails | ContextDetails,
|
|
629
|
+
include: bool = False, # noqa: FBT001, FBT002
|
|
630
|
+
) -> EventResult:
|
|
631
|
+
details_result: str | None = details.result if details else None
|
|
632
|
+
payload: str | None = details_result if include else None
|
|
633
|
+
truncated: bool = not include
|
|
634
|
+
return cls(payload=payload, truncated=truncated)
|
|
635
|
+
|
|
636
|
+
@classmethod
|
|
637
|
+
def from_durable_execution_invocation_output(
|
|
638
|
+
cls,
|
|
639
|
+
durable_execution_invocation_output: DurableExecutionInvocationOutput,
|
|
640
|
+
include: bool = False, # noqa: FBT001, FBT002
|
|
641
|
+
) -> EventResult:
|
|
642
|
+
truncated: bool = not include
|
|
643
|
+
return cls(durable_execution_invocation_output.result, truncated)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
@dataclass(frozen=True)
|
|
647
|
+
class EventError:
|
|
648
|
+
"""Event error structure."""
|
|
649
|
+
|
|
650
|
+
payload: ErrorObject | None = None
|
|
651
|
+
truncated: bool = False
|
|
652
|
+
|
|
653
|
+
@classmethod
|
|
654
|
+
def from_dict(cls, data: dict) -> EventError:
|
|
655
|
+
payload = None
|
|
656
|
+
if payload_data := data.get("Payload"):
|
|
657
|
+
payload = ErrorObject.from_dict(payload_data)
|
|
658
|
+
|
|
659
|
+
return cls(
|
|
660
|
+
payload=payload,
|
|
661
|
+
truncated=data.get("Truncated", False),
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
def to_dict(self) -> dict[str, Any]:
|
|
665
|
+
result: dict[str, Any] = {"Truncated": self.truncated}
|
|
666
|
+
if self.payload is not None:
|
|
667
|
+
result["Payload"] = self.payload.to_dict()
|
|
668
|
+
return result
|
|
669
|
+
|
|
670
|
+
@classmethod
|
|
671
|
+
def from_details(
|
|
672
|
+
cls,
|
|
673
|
+
details: CallbackDetails | StepDetails | ChainedInvokeDetails | ContextDetails,
|
|
674
|
+
include: bool = False, # noqa: FBT001, FBT002
|
|
675
|
+
) -> EventError:
|
|
676
|
+
error_object: ErrorObject | None = details.error if details else None
|
|
677
|
+
truncated: bool = not include
|
|
678
|
+
return cls(error_object, truncated)
|
|
679
|
+
|
|
680
|
+
@classmethod
|
|
681
|
+
def from_durable_execution_invocation_output(
|
|
682
|
+
cls,
|
|
683
|
+
durable_execution_invocation_output: DurableExecutionInvocationOutput,
|
|
684
|
+
include: bool = False, # noqa: FBT001, FBT002
|
|
685
|
+
) -> EventError:
|
|
686
|
+
truncated: bool = not include
|
|
687
|
+
return cls(durable_execution_invocation_output.error, truncated)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
@dataclass(frozen=True)
|
|
691
|
+
class RetryDetails:
|
|
692
|
+
"""Retry details structure."""
|
|
693
|
+
|
|
694
|
+
current_attempt: int = 0
|
|
695
|
+
next_attempt_delay_seconds: int | None = None
|
|
696
|
+
|
|
697
|
+
@classmethod
|
|
698
|
+
def from_dict(cls, data: dict) -> RetryDetails:
|
|
699
|
+
return cls(
|
|
700
|
+
current_attempt=data.get("CurrentAttempt", 0),
|
|
701
|
+
next_attempt_delay_seconds=data.get("NextAttemptDelaySeconds"),
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
def to_dict(self) -> dict[str, Any]:
|
|
705
|
+
result: dict[str, Any] = {"CurrentAttempt": self.current_attempt}
|
|
706
|
+
if self.next_attempt_delay_seconds is not None:
|
|
707
|
+
result["NextAttemptDelaySeconds"] = self.next_attempt_delay_seconds
|
|
708
|
+
return result
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
# Event detail structures
|
|
712
|
+
@dataclass(frozen=True)
|
|
713
|
+
class ExecutionStartedDetails:
|
|
714
|
+
"""Execution started event details."""
|
|
715
|
+
|
|
716
|
+
input: EventInput | None = None
|
|
717
|
+
execution_timeout: int | None = None
|
|
718
|
+
|
|
719
|
+
@classmethod
|
|
720
|
+
def from_dict(cls, data: dict) -> ExecutionStartedDetails:
|
|
721
|
+
input_data = None
|
|
722
|
+
if input_dict := data.get("Input"):
|
|
723
|
+
input_data = EventInput.from_dict(input_dict)
|
|
724
|
+
|
|
725
|
+
return cls(
|
|
726
|
+
input=input_data,
|
|
727
|
+
execution_timeout=data.get("ExecutionTimeout"),
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
def to_dict(self) -> dict[str, Any]:
|
|
731
|
+
result: dict[str, Any] = {}
|
|
732
|
+
if self.input is not None:
|
|
733
|
+
result["Input"] = self.input.to_dict()
|
|
734
|
+
if self.execution_timeout is not None:
|
|
735
|
+
result["ExecutionTimeout"] = self.execution_timeout
|
|
736
|
+
return result
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
@dataclass(frozen=True)
|
|
740
|
+
class ExecutionSucceededDetails:
|
|
741
|
+
"""Execution succeeded event details."""
|
|
742
|
+
|
|
743
|
+
result: EventResult | None = None
|
|
744
|
+
|
|
745
|
+
@classmethod
|
|
746
|
+
def from_dict(cls, data: dict) -> ExecutionSucceededDetails:
|
|
747
|
+
result_data = None
|
|
748
|
+
if result_dict := data.get("Result"):
|
|
749
|
+
result_data = EventResult.from_dict(result_dict)
|
|
750
|
+
|
|
751
|
+
return cls(result=result_data)
|
|
752
|
+
|
|
753
|
+
def to_dict(self) -> dict[str, Any]:
|
|
754
|
+
result: dict[str, Any] = {}
|
|
755
|
+
if self.result is not None:
|
|
756
|
+
result["Result"] = self.result.to_dict()
|
|
757
|
+
return result
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
@dataclass(frozen=True)
|
|
761
|
+
class ExecutionFailedDetails:
|
|
762
|
+
"""Execution failed event details."""
|
|
763
|
+
|
|
764
|
+
error: EventError | None = None
|
|
765
|
+
|
|
766
|
+
@classmethod
|
|
767
|
+
def from_dict(cls, data: dict) -> ExecutionFailedDetails:
|
|
768
|
+
error_data = None
|
|
769
|
+
if error_dict := data.get("Error"):
|
|
770
|
+
error_data = EventError.from_dict(error_dict)
|
|
771
|
+
|
|
772
|
+
return cls(error=error_data)
|
|
773
|
+
|
|
774
|
+
def to_dict(self) -> dict[str, Any]:
|
|
775
|
+
result: dict[str, Any] = {}
|
|
776
|
+
if self.error is not None:
|
|
777
|
+
result["Error"] = self.error.to_dict()
|
|
778
|
+
return result
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
@dataclass(frozen=True)
|
|
782
|
+
class ExecutionTimedOutDetails:
|
|
783
|
+
"""Execution timed out event details."""
|
|
784
|
+
|
|
785
|
+
error: EventError | None = None
|
|
786
|
+
|
|
787
|
+
@classmethod
|
|
788
|
+
def from_dict(cls, data: dict) -> ExecutionTimedOutDetails:
|
|
789
|
+
error_data = None
|
|
790
|
+
if error_dict := data.get("Error"):
|
|
791
|
+
error_data = EventError.from_dict(error_dict)
|
|
792
|
+
|
|
793
|
+
return cls(error=error_data)
|
|
794
|
+
|
|
795
|
+
def to_dict(self) -> dict[str, Any]:
|
|
796
|
+
result: dict[str, Any] = {}
|
|
797
|
+
if self.error is not None:
|
|
798
|
+
result["Error"] = self.error.to_dict()
|
|
799
|
+
return result
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
@dataclass(frozen=True)
|
|
803
|
+
class ExecutionStoppedDetails:
|
|
804
|
+
"""Execution stopped event details."""
|
|
805
|
+
|
|
806
|
+
error: EventError | None = None
|
|
807
|
+
|
|
808
|
+
@classmethod
|
|
809
|
+
def from_dict(cls, data: dict) -> ExecutionStoppedDetails:
|
|
810
|
+
error_data = None
|
|
811
|
+
if error_dict := data.get("Error"):
|
|
812
|
+
error_data = EventError.from_dict(error_dict)
|
|
813
|
+
|
|
814
|
+
return cls(error=error_data)
|
|
815
|
+
|
|
816
|
+
def to_dict(self) -> dict[str, Any]:
|
|
817
|
+
result: dict[str, Any] = {}
|
|
818
|
+
if self.error is not None:
|
|
819
|
+
result["Error"] = self.error.to_dict()
|
|
820
|
+
return result
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
@dataclass(frozen=True)
|
|
824
|
+
class ContextStartedDetails:
|
|
825
|
+
"""Context started event details."""
|
|
826
|
+
|
|
827
|
+
@classmethod
|
|
828
|
+
def from_dict(cls, data: dict) -> ContextStartedDetails: # noqa: ARG003
|
|
829
|
+
return cls()
|
|
830
|
+
|
|
831
|
+
def to_dict(self) -> dict[str, Any]:
|
|
832
|
+
return {}
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
@dataclass(frozen=True)
|
|
836
|
+
class ContextSucceededDetails:
|
|
837
|
+
"""Context succeeded event details."""
|
|
838
|
+
|
|
839
|
+
result: EventResult | None = None
|
|
840
|
+
|
|
841
|
+
@classmethod
|
|
842
|
+
def from_dict(cls, data: dict) -> ContextSucceededDetails:
|
|
843
|
+
result_data = None
|
|
844
|
+
if result_dict := data.get("Result"):
|
|
845
|
+
result_data = EventResult.from_dict(result_dict)
|
|
846
|
+
|
|
847
|
+
return cls(result=result_data)
|
|
848
|
+
|
|
849
|
+
def to_dict(self) -> dict[str, Any]:
|
|
850
|
+
result: dict[str, Any] = {}
|
|
851
|
+
if self.result is not None:
|
|
852
|
+
result["Result"] = self.result.to_dict()
|
|
853
|
+
return result
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
@dataclass(frozen=True)
|
|
857
|
+
class ContextFailedDetails:
|
|
858
|
+
"""Context failed event details."""
|
|
859
|
+
|
|
860
|
+
error: EventError | None = None
|
|
861
|
+
|
|
862
|
+
@classmethod
|
|
863
|
+
def from_dict(cls, data: dict) -> ContextFailedDetails:
|
|
864
|
+
error_data = None
|
|
865
|
+
if error_dict := data.get("Error"):
|
|
866
|
+
error_data = EventError.from_dict(error_dict)
|
|
867
|
+
|
|
868
|
+
return cls(error=error_data)
|
|
869
|
+
|
|
870
|
+
def to_dict(self) -> dict[str, Any]:
|
|
871
|
+
result: dict[str, Any] = {}
|
|
872
|
+
if self.error is not None:
|
|
873
|
+
result["Error"] = self.error.to_dict()
|
|
874
|
+
return result
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
@dataclass(frozen=True)
|
|
878
|
+
class WaitStartedDetails:
|
|
879
|
+
"""Wait started event details."""
|
|
880
|
+
|
|
881
|
+
duration: int | None = None
|
|
882
|
+
scheduled_end_timestamp: datetime.datetime | None = None
|
|
883
|
+
|
|
884
|
+
@classmethod
|
|
885
|
+
def from_dict(cls, data: dict) -> WaitStartedDetails:
|
|
886
|
+
return cls(
|
|
887
|
+
duration=data.get("Duration"),
|
|
888
|
+
scheduled_end_timestamp=data.get("ScheduledEndTimestamp"),
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def to_dict(self) -> dict[str, Any]:
|
|
892
|
+
result: dict[str, Any] = {}
|
|
893
|
+
if self.duration is not None:
|
|
894
|
+
result["Duration"] = self.duration
|
|
895
|
+
if self.scheduled_end_timestamp is not None:
|
|
896
|
+
result["ScheduledEndTimestamp"] = self.scheduled_end_timestamp
|
|
897
|
+
return result
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
@dataclass(frozen=True)
|
|
901
|
+
class WaitSucceededDetails:
|
|
902
|
+
"""Wait succeeded event details."""
|
|
903
|
+
|
|
904
|
+
duration: int | None = None
|
|
905
|
+
|
|
906
|
+
@classmethod
|
|
907
|
+
def from_dict(cls, data: dict) -> WaitSucceededDetails:
|
|
908
|
+
return cls(duration=data.get("Duration"))
|
|
909
|
+
|
|
910
|
+
def to_dict(self) -> dict[str, Any]:
|
|
911
|
+
result: dict[str, Any] = {}
|
|
912
|
+
if self.duration is not None:
|
|
913
|
+
result["Duration"] = self.duration
|
|
914
|
+
return result
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
@dataclass(frozen=True)
|
|
918
|
+
class WaitCancelledDetails:
|
|
919
|
+
"""Wait cancelled event details."""
|
|
920
|
+
|
|
921
|
+
error: EventError | None = None
|
|
922
|
+
|
|
923
|
+
@classmethod
|
|
924
|
+
def from_dict(cls, data: dict) -> WaitCancelledDetails:
|
|
925
|
+
error_data = None
|
|
926
|
+
if error_dict := data.get("Error"):
|
|
927
|
+
error_data = EventError.from_dict(error_dict)
|
|
928
|
+
|
|
929
|
+
return cls(error=error_data)
|
|
930
|
+
|
|
931
|
+
def to_dict(self) -> dict[str, Any]:
|
|
932
|
+
result: dict[str, Any] = {}
|
|
933
|
+
if self.error is not None:
|
|
934
|
+
result["Error"] = self.error.to_dict()
|
|
935
|
+
return result
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
@dataclass(frozen=True)
|
|
939
|
+
class StepStartedDetails:
|
|
940
|
+
"""Step started event details."""
|
|
941
|
+
|
|
942
|
+
@classmethod
|
|
943
|
+
def from_dict(cls, data: dict) -> StepStartedDetails: # noqa: ARG003
|
|
944
|
+
return cls()
|
|
945
|
+
|
|
946
|
+
def to_dict(self) -> dict[str, Any]:
|
|
947
|
+
return {}
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
@dataclass(frozen=True)
|
|
951
|
+
class StepSucceededDetails:
|
|
952
|
+
"""Step succeeded event details."""
|
|
953
|
+
|
|
954
|
+
result: EventResult | None = None
|
|
955
|
+
retry_details: RetryDetails | None = None
|
|
956
|
+
|
|
957
|
+
@classmethod
|
|
958
|
+
def from_dict(cls, data: dict) -> StepSucceededDetails:
|
|
959
|
+
result_data = None
|
|
960
|
+
if result_dict := data.get("Result"):
|
|
961
|
+
result_data = EventResult.from_dict(result_dict)
|
|
962
|
+
|
|
963
|
+
retry_details_data = None
|
|
964
|
+
if retry_dict := data.get("RetryDetails"):
|
|
965
|
+
retry_details_data = RetryDetails.from_dict(retry_dict)
|
|
966
|
+
|
|
967
|
+
return cls(result=result_data, retry_details=retry_details_data)
|
|
968
|
+
|
|
969
|
+
def to_dict(self) -> dict[str, Any]:
|
|
970
|
+
result: dict[str, Any] = {}
|
|
971
|
+
if self.result is not None:
|
|
972
|
+
result["Result"] = self.result.to_dict()
|
|
973
|
+
if self.retry_details is not None:
|
|
974
|
+
result["RetryDetails"] = self.retry_details.to_dict()
|
|
975
|
+
return result
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
@dataclass(frozen=True)
|
|
979
|
+
class StepFailedDetails:
|
|
980
|
+
"""Step failed event details."""
|
|
981
|
+
|
|
982
|
+
error: EventError | None = None
|
|
983
|
+
retry_details: RetryDetails | None = None
|
|
984
|
+
|
|
985
|
+
@classmethod
|
|
986
|
+
def from_dict(cls, data: dict) -> StepFailedDetails:
|
|
987
|
+
error_data = None
|
|
988
|
+
if error_dict := data.get("Error"):
|
|
989
|
+
error_data = EventError.from_dict(error_dict)
|
|
990
|
+
|
|
991
|
+
retry_details_data = None
|
|
992
|
+
if retry_dict := data.get("RetryDetails"):
|
|
993
|
+
retry_details_data = RetryDetails.from_dict(retry_dict)
|
|
994
|
+
|
|
995
|
+
return cls(error=error_data, retry_details=retry_details_data)
|
|
996
|
+
|
|
997
|
+
def to_dict(self) -> dict[str, Any]:
|
|
998
|
+
result: dict[str, Any] = {}
|
|
999
|
+
if self.error is not None:
|
|
1000
|
+
result["Error"] = self.error.to_dict()
|
|
1001
|
+
if self.retry_details is not None:
|
|
1002
|
+
result["RetryDetails"] = self.retry_details.to_dict()
|
|
1003
|
+
return result
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
@dataclass(frozen=True)
|
|
1007
|
+
class ChainedInvokePendingDetails:
|
|
1008
|
+
"""Chained Invoke Pending event details."""
|
|
1009
|
+
|
|
1010
|
+
input: EventInput | None = None
|
|
1011
|
+
function_name: str | None = None
|
|
1012
|
+
|
|
1013
|
+
@classmethod
|
|
1014
|
+
def from_dict(cls, data: dict) -> ChainedInvokePendingDetails:
|
|
1015
|
+
input_data = None
|
|
1016
|
+
if input_dict := data.get("Input"):
|
|
1017
|
+
input_data = EventInput.from_dict(input_dict)
|
|
1018
|
+
|
|
1019
|
+
return cls(
|
|
1020
|
+
input=input_data,
|
|
1021
|
+
function_name=data.get("FunctionName"),
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1025
|
+
result: dict[str, Any] = {}
|
|
1026
|
+
if self.input is not None:
|
|
1027
|
+
result["Input"] = self.input.to_dict()
|
|
1028
|
+
if self.function_name is not None:
|
|
1029
|
+
result["FunctionName"] = self.function_name
|
|
1030
|
+
return result
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
@dataclass(frozen=True)
|
|
1034
|
+
class ChainedInvokeStartedDetails:
|
|
1035
|
+
"""Chained invoke started event details."""
|
|
1036
|
+
|
|
1037
|
+
durable_execution_arn: str | None = None
|
|
1038
|
+
|
|
1039
|
+
@classmethod
|
|
1040
|
+
def from_dict(cls, data: dict) -> ChainedInvokeStartedDetails:
|
|
1041
|
+
return cls(
|
|
1042
|
+
durable_execution_arn=data.get("DurableExecutionArn"),
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1046
|
+
result: dict[str, Any] = {}
|
|
1047
|
+
if self.durable_execution_arn is not None:
|
|
1048
|
+
result["DurableExecutionArn"] = self.durable_execution_arn
|
|
1049
|
+
return result
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
@dataclass(frozen=True)
|
|
1053
|
+
class ChainedInvokeSucceededDetails:
|
|
1054
|
+
"""Chained invoke succeeded event details."""
|
|
1055
|
+
|
|
1056
|
+
result: EventResult | None = None
|
|
1057
|
+
|
|
1058
|
+
@classmethod
|
|
1059
|
+
def from_dict(cls, data: dict) -> ChainedInvokeSucceededDetails:
|
|
1060
|
+
result_data = None
|
|
1061
|
+
if result_dict := data.get("Result"):
|
|
1062
|
+
result_data = EventResult.from_dict(result_dict)
|
|
1063
|
+
|
|
1064
|
+
return cls(result=result_data)
|
|
1065
|
+
|
|
1066
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1067
|
+
result: dict[str, Any] = {}
|
|
1068
|
+
if self.result is not None:
|
|
1069
|
+
result["Result"] = self.result.to_dict()
|
|
1070
|
+
return result
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
@dataclass(frozen=True)
|
|
1074
|
+
class ChainedInvokeFailedDetails:
|
|
1075
|
+
"""Chained invoke failed event details."""
|
|
1076
|
+
|
|
1077
|
+
error: EventError | None = None
|
|
1078
|
+
|
|
1079
|
+
@classmethod
|
|
1080
|
+
def from_dict(cls, data: dict) -> ChainedInvokeFailedDetails:
|
|
1081
|
+
error_data = None
|
|
1082
|
+
if error_dict := data.get("Error"):
|
|
1083
|
+
error_data = EventError.from_dict(error_dict)
|
|
1084
|
+
|
|
1085
|
+
return cls(error=error_data)
|
|
1086
|
+
|
|
1087
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1088
|
+
result: dict[str, Any] = {}
|
|
1089
|
+
if self.error is not None:
|
|
1090
|
+
result["Error"] = self.error.to_dict()
|
|
1091
|
+
return result
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
@dataclass(frozen=True)
|
|
1095
|
+
class ChainedInvokeTimedOutDetails:
|
|
1096
|
+
"""Chained invoke timed out event details."""
|
|
1097
|
+
|
|
1098
|
+
error: EventError | None = None
|
|
1099
|
+
|
|
1100
|
+
@classmethod
|
|
1101
|
+
def from_dict(cls, data: dict) -> ChainedInvokeTimedOutDetails:
|
|
1102
|
+
error_data = None
|
|
1103
|
+
if error_dict := data.get("Error"):
|
|
1104
|
+
error_data = EventError.from_dict(error_dict)
|
|
1105
|
+
|
|
1106
|
+
return cls(error=error_data)
|
|
1107
|
+
|
|
1108
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1109
|
+
result: dict[str, Any] = {}
|
|
1110
|
+
if self.error is not None:
|
|
1111
|
+
result["Error"] = self.error.to_dict()
|
|
1112
|
+
return result
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
@dataclass(frozen=True)
|
|
1116
|
+
class ChainedInvokeStoppedDetails:
|
|
1117
|
+
"""Chained invoke stopped event details."""
|
|
1118
|
+
|
|
1119
|
+
error: EventError | None = None
|
|
1120
|
+
|
|
1121
|
+
@classmethod
|
|
1122
|
+
def from_dict(cls, data: dict) -> ChainedInvokeStoppedDetails:
|
|
1123
|
+
error_data = None
|
|
1124
|
+
if error_dict := data.get("Error"):
|
|
1125
|
+
error_data = EventError.from_dict(error_dict)
|
|
1126
|
+
|
|
1127
|
+
return cls(error=error_data)
|
|
1128
|
+
|
|
1129
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1130
|
+
result: dict[str, Any] = {}
|
|
1131
|
+
if self.error is not None:
|
|
1132
|
+
result["Error"] = self.error.to_dict()
|
|
1133
|
+
return result
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
@dataclass(frozen=True)
|
|
1137
|
+
class CallbackStartedDetails:
|
|
1138
|
+
"""Callback started event details."""
|
|
1139
|
+
|
|
1140
|
+
callback_id: str | None = None
|
|
1141
|
+
heartbeat_timeout: int | None = None
|
|
1142
|
+
timeout: int | None = None
|
|
1143
|
+
|
|
1144
|
+
@classmethod
|
|
1145
|
+
def from_dict(cls, data: dict) -> CallbackStartedDetails:
|
|
1146
|
+
return cls(
|
|
1147
|
+
callback_id=data.get("CallbackId"),
|
|
1148
|
+
heartbeat_timeout=data.get("HeartbeatTimeout"),
|
|
1149
|
+
timeout=data.get("Timeout"),
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1153
|
+
result: dict[str, Any] = {}
|
|
1154
|
+
if self.callback_id is not None:
|
|
1155
|
+
result["CallbackId"] = self.callback_id
|
|
1156
|
+
if self.heartbeat_timeout is not None:
|
|
1157
|
+
result["HeartbeatTimeout"] = self.heartbeat_timeout
|
|
1158
|
+
if self.timeout is not None:
|
|
1159
|
+
result["Timeout"] = self.timeout
|
|
1160
|
+
return result
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
@dataclass(frozen=True)
|
|
1164
|
+
class CallbackSucceededDetails:
|
|
1165
|
+
"""Callback succeeded event details."""
|
|
1166
|
+
|
|
1167
|
+
result: EventResult | None = None
|
|
1168
|
+
|
|
1169
|
+
@classmethod
|
|
1170
|
+
def from_dict(cls, data: dict) -> CallbackSucceededDetails:
|
|
1171
|
+
result_data = None
|
|
1172
|
+
if result_dict := data.get("Result"):
|
|
1173
|
+
result_data = EventResult.from_dict(result_dict)
|
|
1174
|
+
|
|
1175
|
+
return cls(result=result_data)
|
|
1176
|
+
|
|
1177
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1178
|
+
result: dict[str, Any] = {}
|
|
1179
|
+
if self.result is not None:
|
|
1180
|
+
result["Result"] = self.result.to_dict()
|
|
1181
|
+
return result
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
@dataclass(frozen=True)
|
|
1185
|
+
class CallbackFailedDetails:
|
|
1186
|
+
"""Callback failed event details."""
|
|
1187
|
+
|
|
1188
|
+
error: EventError | None = None
|
|
1189
|
+
|
|
1190
|
+
@classmethod
|
|
1191
|
+
def from_dict(cls, data: dict) -> CallbackFailedDetails:
|
|
1192
|
+
error_data = None
|
|
1193
|
+
if error_dict := data.get("Error"):
|
|
1194
|
+
error_data = EventError.from_dict(error_dict)
|
|
1195
|
+
|
|
1196
|
+
return cls(error=error_data)
|
|
1197
|
+
|
|
1198
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1199
|
+
result: dict[str, Any] = {}
|
|
1200
|
+
if self.error is not None:
|
|
1201
|
+
result["Error"] = self.error.to_dict()
|
|
1202
|
+
return result
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
@dataclass(frozen=True)
|
|
1206
|
+
class CallbackTimedOutDetails:
|
|
1207
|
+
"""Callback timed out event details."""
|
|
1208
|
+
|
|
1209
|
+
error: EventError | None = None
|
|
1210
|
+
|
|
1211
|
+
@classmethod
|
|
1212
|
+
def from_dict(cls, data: dict) -> CallbackTimedOutDetails:
|
|
1213
|
+
error_data = None
|
|
1214
|
+
if error_dict := data.get("Error"):
|
|
1215
|
+
error_data = EventError.from_dict(error_dict)
|
|
1216
|
+
|
|
1217
|
+
return cls(error=error_data)
|
|
1218
|
+
|
|
1219
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1220
|
+
result: dict[str, Any] = {}
|
|
1221
|
+
if self.error is not None:
|
|
1222
|
+
result["Error"] = self.error.to_dict()
|
|
1223
|
+
return result
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
@dataclass(frozen=True)
|
|
1227
|
+
class InvocationCompletedDetails:
|
|
1228
|
+
"""Invocation completed event details."""
|
|
1229
|
+
|
|
1230
|
+
start_timestamp: datetime.datetime
|
|
1231
|
+
end_timestamp: datetime.datetime
|
|
1232
|
+
request_id: str
|
|
1233
|
+
|
|
1234
|
+
@classmethod
|
|
1235
|
+
def from_dict(cls, data: dict) -> InvocationCompletedDetails:
|
|
1236
|
+
return cls(
|
|
1237
|
+
start_timestamp=data["StartTimestamp"],
|
|
1238
|
+
end_timestamp=data["EndTimestamp"],
|
|
1239
|
+
request_id=data["RequestId"],
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
@classmethod
|
|
1243
|
+
def from_json_dict(cls, data: dict) -> InvocationCompletedDetails:
|
|
1244
|
+
"""Deserialize from JSON dict with Unix millisecond timestamps."""
|
|
1245
|
+
start_ts: datetime.datetime | None = TimestampConverter.from_unix_millis(
|
|
1246
|
+
data["StartTimestamp"]
|
|
1247
|
+
) # type: ignore[arg-type]
|
|
1248
|
+
end_ts: datetime.datetime | None = TimestampConverter.from_unix_millis(
|
|
1249
|
+
data["EndTimestamp"]
|
|
1250
|
+
) # type: ignore[arg-type]
|
|
1251
|
+
|
|
1252
|
+
if start_ts is None or end_ts is None:
|
|
1253
|
+
raise InvalidParameterValueException(
|
|
1254
|
+
"StartTimestamp and EndTimestamp cannot be null"
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
return cls(
|
|
1258
|
+
start_timestamp=start_ts,
|
|
1259
|
+
end_timestamp=end_ts,
|
|
1260
|
+
request_id=data["RequestId"],
|
|
1261
|
+
)
|
|
1262
|
+
|
|
1263
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1264
|
+
return {
|
|
1265
|
+
"StartTimestamp": self.start_timestamp,
|
|
1266
|
+
"EndTimestamp": self.end_timestamp,
|
|
1267
|
+
"RequestId": self.request_id,
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
def to_json_dict(self) -> dict[str, Any]:
|
|
1271
|
+
"""Convert to JSON-serializable dict with Unix millisecond timestamps."""
|
|
1272
|
+
return {
|
|
1273
|
+
"StartTimestamp": TimestampConverter.to_unix_millis(self.start_timestamp),
|
|
1274
|
+
"EndTimestamp": TimestampConverter.to_unix_millis(self.end_timestamp),
|
|
1275
|
+
"RequestId": self.request_id,
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
# endregion event_structures
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
@dataclass(frozen=True)
|
|
1283
|
+
class EventCreationContext:
|
|
1284
|
+
operation: Operation
|
|
1285
|
+
event_id: int
|
|
1286
|
+
durable_execution_arn: str
|
|
1287
|
+
start_durable_execution_input: StartDurableExecutionInput
|
|
1288
|
+
durable_execution_invocation_output: DurableExecutionInvocationOutput | None = None
|
|
1289
|
+
operation_update: OperationUpdate | None = None
|
|
1290
|
+
include_execution_data: bool = False # noqa: FBT001, FBT002
|
|
1291
|
+
|
|
1292
|
+
@classmethod
|
|
1293
|
+
def create(
|
|
1294
|
+
cls,
|
|
1295
|
+
operation: Operation,
|
|
1296
|
+
event_id: int,
|
|
1297
|
+
durable_execution_arn: str,
|
|
1298
|
+
start_input: StartDurableExecutionInput,
|
|
1299
|
+
result: DurableExecutionInvocationOutput | None = None,
|
|
1300
|
+
operation_update: OperationUpdate | None = None,
|
|
1301
|
+
include_execution_data: bool = False, # noqa: FBT001, FBT002
|
|
1302
|
+
) -> EventCreationContext:
|
|
1303
|
+
return cls(
|
|
1304
|
+
operation=operation,
|
|
1305
|
+
event_id=event_id,
|
|
1306
|
+
durable_execution_arn=durable_execution_arn,
|
|
1307
|
+
start_durable_execution_input=start_input,
|
|
1308
|
+
durable_execution_invocation_output=result,
|
|
1309
|
+
operation_update=operation_update,
|
|
1310
|
+
include_execution_data=include_execution_data,
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
@property
|
|
1314
|
+
def sub_type(self) -> str | None:
|
|
1315
|
+
return self.operation.sub_type.value if self.operation.sub_type else None
|
|
1316
|
+
|
|
1317
|
+
def get_retry_details(self) -> RetryDetails | None:
|
|
1318
|
+
if not self.operation.step_details or not self.operation_update:
|
|
1319
|
+
return None
|
|
1320
|
+
|
|
1321
|
+
delay = 0
|
|
1322
|
+
if (
|
|
1323
|
+
self.operation_update.operation_type == OperationType.STEP
|
|
1324
|
+
and self.operation_update.step_options
|
|
1325
|
+
):
|
|
1326
|
+
delay = self.operation_update.step_options.next_attempt_delay_seconds
|
|
1327
|
+
|
|
1328
|
+
return RetryDetails(
|
|
1329
|
+
current_attempt=self.operation.step_details.attempt,
|
|
1330
|
+
next_attempt_delay_seconds=delay,
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
@property
|
|
1334
|
+
def start_timestamp(self) -> datetime.datetime:
|
|
1335
|
+
return (
|
|
1336
|
+
self.operation.start_timestamp
|
|
1337
|
+
if self.operation.start_timestamp is not None
|
|
1338
|
+
else datetime.datetime.now(UTC)
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
@property
|
|
1342
|
+
def end_timestamp(self) -> datetime.datetime:
|
|
1343
|
+
return (
|
|
1344
|
+
self.operation.end_timestamp
|
|
1345
|
+
if self.operation.end_timestamp is not None
|
|
1346
|
+
else datetime.datetime.now(UTC)
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
# region event_class
|
|
1351
|
+
@dataclass(frozen=True)
|
|
1352
|
+
class Event:
|
|
1353
|
+
"""Event structure from Smithy model."""
|
|
1354
|
+
|
|
1355
|
+
event_type: str
|
|
1356
|
+
event_timestamp: datetime.datetime
|
|
1357
|
+
sub_type: str | None = None
|
|
1358
|
+
event_id: int = 1
|
|
1359
|
+
operation_id: str | None = None
|
|
1360
|
+
name: str | None = None
|
|
1361
|
+
parent_id: str | None = None
|
|
1362
|
+
execution_started_details: ExecutionStartedDetails | None = None
|
|
1363
|
+
execution_succeeded_details: ExecutionSucceededDetails | None = None
|
|
1364
|
+
execution_failed_details: ExecutionFailedDetails | None = None
|
|
1365
|
+
execution_timed_out_details: ExecutionTimedOutDetails | None = None
|
|
1366
|
+
execution_stopped_details: ExecutionStoppedDetails | None = None
|
|
1367
|
+
context_started_details: ContextStartedDetails | None = None
|
|
1368
|
+
context_succeeded_details: ContextSucceededDetails | None = None
|
|
1369
|
+
context_failed_details: ContextFailedDetails | None = None
|
|
1370
|
+
wait_started_details: WaitStartedDetails | None = None
|
|
1371
|
+
wait_succeeded_details: WaitSucceededDetails | None = None
|
|
1372
|
+
wait_cancelled_details: WaitCancelledDetails | None = None
|
|
1373
|
+
step_started_details: StepStartedDetails | None = None
|
|
1374
|
+
step_succeeded_details: StepSucceededDetails | None = None
|
|
1375
|
+
step_failed_details: StepFailedDetails | None = None
|
|
1376
|
+
chained_invoke_pending_details: ChainedInvokePendingDetails | None = None
|
|
1377
|
+
chained_invoke_started_details: ChainedInvokeStartedDetails | None = None
|
|
1378
|
+
chained_invoke_succeeded_details: ChainedInvokeSucceededDetails | None = None
|
|
1379
|
+
chained_invoke_failed_details: ChainedInvokeFailedDetails | None = None
|
|
1380
|
+
chained_invoke_timed_out_details: ChainedInvokeTimedOutDetails | None = None
|
|
1381
|
+
chained_invoke_stopped_details: ChainedInvokeStoppedDetails | None = None
|
|
1382
|
+
callback_started_details: CallbackStartedDetails | None = None
|
|
1383
|
+
callback_succeeded_details: CallbackSucceededDetails | None = None
|
|
1384
|
+
callback_failed_details: CallbackFailedDetails | None = None
|
|
1385
|
+
callback_timed_out_details: CallbackTimedOutDetails | None = None
|
|
1386
|
+
invocation_completed_details: InvocationCompletedDetails | None = None
|
|
1387
|
+
|
|
1388
|
+
@classmethod
|
|
1389
|
+
def from_dict(cls, data: dict) -> Event:
|
|
1390
|
+
# Parse all the detail structures
|
|
1391
|
+
execution_started_details = None
|
|
1392
|
+
if details_data := data.get("ExecutionStartedDetails"):
|
|
1393
|
+
execution_started_details = ExecutionStartedDetails.from_dict(details_data)
|
|
1394
|
+
|
|
1395
|
+
execution_succeeded_details = None
|
|
1396
|
+
if details_data := data.get("ExecutionSucceededDetails"):
|
|
1397
|
+
execution_succeeded_details = ExecutionSucceededDetails.from_dict(
|
|
1398
|
+
details_data
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
execution_failed_details = None
|
|
1402
|
+
if details_data := data.get("ExecutionFailedDetails"):
|
|
1403
|
+
execution_failed_details = ExecutionFailedDetails.from_dict(details_data)
|
|
1404
|
+
|
|
1405
|
+
execution_timed_out_details = None
|
|
1406
|
+
if details_data := data.get("ExecutionTimedOutDetails"):
|
|
1407
|
+
execution_timed_out_details = ExecutionTimedOutDetails.from_dict(
|
|
1408
|
+
details_data
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
execution_stopped_details = None
|
|
1412
|
+
if details_data := data.get("ExecutionStoppedDetails"):
|
|
1413
|
+
execution_stopped_details = ExecutionStoppedDetails.from_dict(details_data)
|
|
1414
|
+
|
|
1415
|
+
context_started_details = None
|
|
1416
|
+
if details_data := data.get("ContextStartedDetails"):
|
|
1417
|
+
context_started_details = ContextStartedDetails.from_dict(details_data)
|
|
1418
|
+
|
|
1419
|
+
context_succeeded_details = None
|
|
1420
|
+
if details_data := data.get("ContextSucceededDetails"):
|
|
1421
|
+
context_succeeded_details = ContextSucceededDetails.from_dict(details_data)
|
|
1422
|
+
|
|
1423
|
+
context_failed_details = None
|
|
1424
|
+
if details_data := data.get("ContextFailedDetails"):
|
|
1425
|
+
context_failed_details = ContextFailedDetails.from_dict(details_data)
|
|
1426
|
+
|
|
1427
|
+
wait_started_details = None
|
|
1428
|
+
if details_data := data.get("WaitStartedDetails"):
|
|
1429
|
+
wait_started_details = WaitStartedDetails.from_dict(details_data)
|
|
1430
|
+
|
|
1431
|
+
wait_succeeded_details = None
|
|
1432
|
+
if details_data := data.get("WaitSucceededDetails"):
|
|
1433
|
+
wait_succeeded_details = WaitSucceededDetails.from_dict(details_data)
|
|
1434
|
+
|
|
1435
|
+
wait_cancelled_details = None
|
|
1436
|
+
if details_data := data.get("WaitCancelledDetails"):
|
|
1437
|
+
wait_cancelled_details = WaitCancelledDetails.from_dict(details_data)
|
|
1438
|
+
|
|
1439
|
+
step_started_details = None
|
|
1440
|
+
if details_data := data.get("StepStartedDetails"):
|
|
1441
|
+
step_started_details = StepStartedDetails.from_dict(details_data)
|
|
1442
|
+
|
|
1443
|
+
step_succeeded_details = None
|
|
1444
|
+
if details_data := data.get("StepSucceededDetails"):
|
|
1445
|
+
step_succeeded_details = StepSucceededDetails.from_dict(details_data)
|
|
1446
|
+
|
|
1447
|
+
step_failed_details = None
|
|
1448
|
+
if details_data := data.get("StepFailedDetails"):
|
|
1449
|
+
step_failed_details = StepFailedDetails.from_dict(details_data)
|
|
1450
|
+
|
|
1451
|
+
chained_invoke_pending_details = None
|
|
1452
|
+
if details_data := data.get("ChainedInvokePendingDetails"):
|
|
1453
|
+
chained_invoke_pending_details = ChainedInvokePendingDetails.from_dict(
|
|
1454
|
+
details_data
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
chained_invoke_started_details = None
|
|
1458
|
+
if details_data := data.get("ChainedInvokeStartedDetails"):
|
|
1459
|
+
chained_invoke_started_details = ChainedInvokeStartedDetails.from_dict(
|
|
1460
|
+
details_data
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
chained_invoke_succeeded_details = None
|
|
1464
|
+
if details_data := data.get("ChainedInvokeSucceededDetails"):
|
|
1465
|
+
chained_invoke_succeeded_details = ChainedInvokeSucceededDetails.from_dict(
|
|
1466
|
+
details_data
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
chained_invoke_failed_details = None
|
|
1470
|
+
if details_data := data.get("ChainedInvokeFailedDetails"):
|
|
1471
|
+
chained_invoke_failed_details = ChainedInvokeFailedDetails.from_dict(
|
|
1472
|
+
details_data
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1475
|
+
chained_invoke_timed_out_details = None
|
|
1476
|
+
if details_data := data.get("ChainedInvokeTimedOutDetails"):
|
|
1477
|
+
chained_invoke_timed_out_details = ChainedInvokeTimedOutDetails.from_dict(
|
|
1478
|
+
details_data
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
chained_invoke_stopped_details = None
|
|
1482
|
+
if details_data := data.get("ChainedInvokeStoppedDetails"):
|
|
1483
|
+
chained_invoke_stopped_details = ChainedInvokeStoppedDetails.from_dict(
|
|
1484
|
+
details_data
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
callback_started_details = None
|
|
1488
|
+
if details_data := data.get("CallbackStartedDetails"):
|
|
1489
|
+
callback_started_details = CallbackStartedDetails.from_dict(details_data)
|
|
1490
|
+
|
|
1491
|
+
callback_succeeded_details = None
|
|
1492
|
+
if details_data := data.get("CallbackSucceededDetails"):
|
|
1493
|
+
callback_succeeded_details = CallbackSucceededDetails.from_dict(
|
|
1494
|
+
details_data
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
callback_failed_details = None
|
|
1498
|
+
if details_data := data.get("CallbackFailedDetails"):
|
|
1499
|
+
callback_failed_details = CallbackFailedDetails.from_dict(details_data)
|
|
1500
|
+
|
|
1501
|
+
callback_timed_out_details = None
|
|
1502
|
+
if details_data := data.get("CallbackTimedOutDetails"):
|
|
1503
|
+
callback_timed_out_details = CallbackTimedOutDetails.from_dict(details_data)
|
|
1504
|
+
|
|
1505
|
+
invocation_completed_details = None
|
|
1506
|
+
if details_data := data.get("InvocationCompletedDetails"):
|
|
1507
|
+
invocation_completed_details = InvocationCompletedDetails.from_dict(
|
|
1508
|
+
details_data
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1511
|
+
return cls(
|
|
1512
|
+
event_type=data["EventType"],
|
|
1513
|
+
event_timestamp=data["EventTimestamp"],
|
|
1514
|
+
sub_type=data.get("SubType"),
|
|
1515
|
+
event_id=data.get("EventId", 1),
|
|
1516
|
+
operation_id=data.get("Id"),
|
|
1517
|
+
name=data.get("Name"),
|
|
1518
|
+
parent_id=data.get("ParentId"),
|
|
1519
|
+
execution_started_details=execution_started_details,
|
|
1520
|
+
execution_succeeded_details=execution_succeeded_details,
|
|
1521
|
+
execution_failed_details=execution_failed_details,
|
|
1522
|
+
execution_timed_out_details=execution_timed_out_details,
|
|
1523
|
+
execution_stopped_details=execution_stopped_details,
|
|
1524
|
+
context_started_details=context_started_details,
|
|
1525
|
+
context_succeeded_details=context_succeeded_details,
|
|
1526
|
+
context_failed_details=context_failed_details,
|
|
1527
|
+
wait_started_details=wait_started_details,
|
|
1528
|
+
wait_succeeded_details=wait_succeeded_details,
|
|
1529
|
+
wait_cancelled_details=wait_cancelled_details,
|
|
1530
|
+
step_started_details=step_started_details,
|
|
1531
|
+
step_succeeded_details=step_succeeded_details,
|
|
1532
|
+
step_failed_details=step_failed_details,
|
|
1533
|
+
chained_invoke_pending_details=chained_invoke_pending_details,
|
|
1534
|
+
chained_invoke_started_details=chained_invoke_started_details,
|
|
1535
|
+
chained_invoke_succeeded_details=chained_invoke_succeeded_details,
|
|
1536
|
+
chained_invoke_failed_details=chained_invoke_failed_details,
|
|
1537
|
+
chained_invoke_timed_out_details=chained_invoke_timed_out_details,
|
|
1538
|
+
chained_invoke_stopped_details=chained_invoke_stopped_details,
|
|
1539
|
+
callback_started_details=callback_started_details,
|
|
1540
|
+
callback_succeeded_details=callback_succeeded_details,
|
|
1541
|
+
callback_failed_details=callback_failed_details,
|
|
1542
|
+
callback_timed_out_details=callback_timed_out_details,
|
|
1543
|
+
invocation_completed_details=invocation_completed_details,
|
|
1544
|
+
)
|
|
1545
|
+
|
|
1546
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1547
|
+
result: dict[str, Any] = {
|
|
1548
|
+
"EventType": self.event_type,
|
|
1549
|
+
"EventTimestamp": self.event_timestamp,
|
|
1550
|
+
"EventId": self.event_id,
|
|
1551
|
+
}
|
|
1552
|
+
if self.sub_type is not None:
|
|
1553
|
+
result["SubType"] = self.sub_type
|
|
1554
|
+
if self.operation_id is not None:
|
|
1555
|
+
result["Id"] = self.operation_id
|
|
1556
|
+
if self.name is not None:
|
|
1557
|
+
result["Name"] = self.name
|
|
1558
|
+
if self.parent_id is not None:
|
|
1559
|
+
result["ParentId"] = self.parent_id
|
|
1560
|
+
if self.execution_started_details is not None:
|
|
1561
|
+
result["ExecutionStartedDetails"] = self.execution_started_details.to_dict()
|
|
1562
|
+
if self.execution_succeeded_details is not None:
|
|
1563
|
+
result["ExecutionSucceededDetails"] = (
|
|
1564
|
+
self.execution_succeeded_details.to_dict()
|
|
1565
|
+
)
|
|
1566
|
+
if self.execution_failed_details is not None:
|
|
1567
|
+
result["ExecutionFailedDetails"] = self.execution_failed_details.to_dict()
|
|
1568
|
+
if self.execution_timed_out_details is not None:
|
|
1569
|
+
result["ExecutionTimedOutDetails"] = (
|
|
1570
|
+
self.execution_timed_out_details.to_dict()
|
|
1571
|
+
)
|
|
1572
|
+
if self.execution_stopped_details is not None:
|
|
1573
|
+
result["ExecutionStoppedDetails"] = self.execution_stopped_details.to_dict()
|
|
1574
|
+
if self.context_started_details is not None:
|
|
1575
|
+
result["ContextStartedDetails"] = self.context_started_details.to_dict()
|
|
1576
|
+
if self.context_succeeded_details is not None:
|
|
1577
|
+
result["ContextSucceededDetails"] = self.context_succeeded_details.to_dict()
|
|
1578
|
+
if self.context_failed_details is not None:
|
|
1579
|
+
result["ContextFailedDetails"] = self.context_failed_details.to_dict()
|
|
1580
|
+
if self.wait_started_details is not None:
|
|
1581
|
+
result["WaitStartedDetails"] = self.wait_started_details.to_dict()
|
|
1582
|
+
if self.wait_succeeded_details is not None:
|
|
1583
|
+
result["WaitSucceededDetails"] = self.wait_succeeded_details.to_dict()
|
|
1584
|
+
if self.wait_cancelled_details is not None:
|
|
1585
|
+
result["WaitCancelledDetails"] = self.wait_cancelled_details.to_dict()
|
|
1586
|
+
if self.step_started_details is not None:
|
|
1587
|
+
result["StepStartedDetails"] = self.step_started_details.to_dict()
|
|
1588
|
+
if self.step_succeeded_details is not None:
|
|
1589
|
+
result["StepSucceededDetails"] = self.step_succeeded_details.to_dict()
|
|
1590
|
+
if self.step_failed_details is not None:
|
|
1591
|
+
result["StepFailedDetails"] = self.step_failed_details.to_dict()
|
|
1592
|
+
if self.chained_invoke_pending_details is not None:
|
|
1593
|
+
result["ChainedInvokePendingDetails"] = (
|
|
1594
|
+
self.chained_invoke_pending_details.to_dict()
|
|
1595
|
+
)
|
|
1596
|
+
if self.chained_invoke_started_details is not None:
|
|
1597
|
+
result["ChainedInvokeStartedDetails"] = (
|
|
1598
|
+
self.chained_invoke_started_details.to_dict()
|
|
1599
|
+
)
|
|
1600
|
+
if self.chained_invoke_succeeded_details is not None:
|
|
1601
|
+
result["ChainedInvokeSucceededDetails"] = (
|
|
1602
|
+
self.chained_invoke_succeeded_details.to_dict()
|
|
1603
|
+
)
|
|
1604
|
+
if self.chained_invoke_failed_details is not None:
|
|
1605
|
+
result["ChainedInvokeFailedDetails"] = (
|
|
1606
|
+
self.chained_invoke_failed_details.to_dict()
|
|
1607
|
+
)
|
|
1608
|
+
if self.chained_invoke_timed_out_details is not None:
|
|
1609
|
+
result["ChainedInvokeTimedOutDetails"] = (
|
|
1610
|
+
self.chained_invoke_timed_out_details.to_dict()
|
|
1611
|
+
)
|
|
1612
|
+
if self.chained_invoke_stopped_details is not None:
|
|
1613
|
+
result["ChainedInvokeStoppedDetails"] = (
|
|
1614
|
+
self.chained_invoke_stopped_details.to_dict()
|
|
1615
|
+
)
|
|
1616
|
+
if self.callback_started_details is not None:
|
|
1617
|
+
result["CallbackStartedDetails"] = self.callback_started_details.to_dict()
|
|
1618
|
+
if self.callback_succeeded_details is not None:
|
|
1619
|
+
result["CallbackSucceededDetails"] = (
|
|
1620
|
+
self.callback_succeeded_details.to_dict()
|
|
1621
|
+
)
|
|
1622
|
+
if self.callback_failed_details is not None:
|
|
1623
|
+
result["CallbackFailedDetails"] = self.callback_failed_details.to_dict()
|
|
1624
|
+
if self.callback_timed_out_details is not None:
|
|
1625
|
+
result["CallbackTimedOutDetails"] = (
|
|
1626
|
+
self.callback_timed_out_details.to_dict()
|
|
1627
|
+
)
|
|
1628
|
+
if self.invocation_completed_details is not None:
|
|
1629
|
+
result["InvocationCompletedDetails"] = (
|
|
1630
|
+
self.invocation_completed_details.to_dict()
|
|
1631
|
+
)
|
|
1632
|
+
return result
|
|
1633
|
+
|
|
1634
|
+
# region execution
|
|
1635
|
+
@classmethod
|
|
1636
|
+
def create_execution_event_started(cls, context: EventCreationContext) -> Event:
|
|
1637
|
+
execution_details: ExecutionDetails | None = context.operation.execution_details
|
|
1638
|
+
event_input: EventInput | None = (
|
|
1639
|
+
EventInput.from_details(execution_details, context.include_execution_data)
|
|
1640
|
+
if execution_details
|
|
1641
|
+
else None
|
|
1642
|
+
)
|
|
1643
|
+
execution_timeout: int | None = (
|
|
1644
|
+
context.start_durable_execution_input.execution_timeout_seconds
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
return cls(
|
|
1648
|
+
event_type=EventType.EXECUTION_STARTED.value,
|
|
1649
|
+
event_timestamp=context.start_timestamp,
|
|
1650
|
+
sub_type=context.sub_type,
|
|
1651
|
+
event_id=context.event_id,
|
|
1652
|
+
operation_id=context.operation.operation_id,
|
|
1653
|
+
name=context.operation.name,
|
|
1654
|
+
parent_id=context.operation.parent_id,
|
|
1655
|
+
execution_started_details=ExecutionStartedDetails(
|
|
1656
|
+
input=event_input,
|
|
1657
|
+
execution_timeout=execution_timeout,
|
|
1658
|
+
),
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
@classmethod
|
|
1662
|
+
def create_execution_event_succeeded(cls, context: EventCreationContext) -> Event:
|
|
1663
|
+
result: EventResult | None = (
|
|
1664
|
+
EventResult.from_durable_execution_invocation_output(
|
|
1665
|
+
context.durable_execution_invocation_output,
|
|
1666
|
+
context.include_execution_data,
|
|
1667
|
+
)
|
|
1668
|
+
if context.durable_execution_invocation_output
|
|
1669
|
+
else None
|
|
1670
|
+
)
|
|
1671
|
+
return cls(
|
|
1672
|
+
event_type=EventType.EXECUTION_SUCCEEDED.value,
|
|
1673
|
+
event_timestamp=context.end_timestamp,
|
|
1674
|
+
sub_type=context.sub_type,
|
|
1675
|
+
event_id=context.event_id,
|
|
1676
|
+
operation_id=context.operation.operation_id,
|
|
1677
|
+
name=context.operation.name,
|
|
1678
|
+
parent_id=context.operation.parent_id,
|
|
1679
|
+
execution_succeeded_details=ExecutionSucceededDetails(result=result),
|
|
1680
|
+
)
|
|
1681
|
+
|
|
1682
|
+
@classmethod
|
|
1683
|
+
def create_execution_event_failed(cls, context: EventCreationContext) -> Event:
|
|
1684
|
+
error: EventError | None = (
|
|
1685
|
+
EventError.from_durable_execution_invocation_output(
|
|
1686
|
+
context.durable_execution_invocation_output,
|
|
1687
|
+
include=context.include_execution_data,
|
|
1688
|
+
)
|
|
1689
|
+
if context.durable_execution_invocation_output
|
|
1690
|
+
else None
|
|
1691
|
+
)
|
|
1692
|
+
return cls(
|
|
1693
|
+
event_type=EventType.EXECUTION_FAILED.value,
|
|
1694
|
+
event_timestamp=context.end_timestamp,
|
|
1695
|
+
sub_type=context.sub_type,
|
|
1696
|
+
event_id=context.event_id,
|
|
1697
|
+
operation_id=context.operation.operation_id,
|
|
1698
|
+
name=context.operation.name,
|
|
1699
|
+
parent_id=context.operation.parent_id,
|
|
1700
|
+
execution_failed_details=ExecutionFailedDetails(error=error),
|
|
1701
|
+
)
|
|
1702
|
+
|
|
1703
|
+
@classmethod
|
|
1704
|
+
def create_execution_event_timed_out(cls, context: EventCreationContext) -> Event:
|
|
1705
|
+
error: EventError | None = (
|
|
1706
|
+
EventError.from_durable_execution_invocation_output(
|
|
1707
|
+
context.durable_execution_invocation_output,
|
|
1708
|
+
include=context.include_execution_data,
|
|
1709
|
+
)
|
|
1710
|
+
if context.durable_execution_invocation_output
|
|
1711
|
+
else None
|
|
1712
|
+
)
|
|
1713
|
+
return cls(
|
|
1714
|
+
event_type=EventType.EXECUTION_TIMED_OUT.value,
|
|
1715
|
+
event_timestamp=context.end_timestamp,
|
|
1716
|
+
sub_type=context.sub_type,
|
|
1717
|
+
event_id=context.event_id,
|
|
1718
|
+
operation_id=context.operation.operation_id,
|
|
1719
|
+
name=context.operation.name,
|
|
1720
|
+
parent_id=context.operation.parent_id,
|
|
1721
|
+
execution_timed_out_details=ExecutionTimedOutDetails(error=error),
|
|
1722
|
+
)
|
|
1723
|
+
|
|
1724
|
+
@classmethod
|
|
1725
|
+
def create_execution_event_stopped(cls, context: EventCreationContext) -> Event:
|
|
1726
|
+
error: EventError | None = (
|
|
1727
|
+
EventError.from_durable_execution_invocation_output(
|
|
1728
|
+
context.durable_execution_invocation_output,
|
|
1729
|
+
include=context.include_execution_data,
|
|
1730
|
+
)
|
|
1731
|
+
if context.durable_execution_invocation_output
|
|
1732
|
+
else None
|
|
1733
|
+
)
|
|
1734
|
+
return cls(
|
|
1735
|
+
event_type=EventType.EXECUTION_STOPPED.value,
|
|
1736
|
+
event_timestamp=context.end_timestamp,
|
|
1737
|
+
sub_type=context.sub_type,
|
|
1738
|
+
event_id=context.event_id,
|
|
1739
|
+
operation_id=context.operation.operation_id,
|
|
1740
|
+
name=context.operation.name,
|
|
1741
|
+
parent_id=context.operation.parent_id,
|
|
1742
|
+
execution_stopped_details=ExecutionStoppedDetails(error=error),
|
|
1743
|
+
)
|
|
1744
|
+
|
|
1745
|
+
@classmethod
|
|
1746
|
+
def create_execution_event(cls, context: EventCreationContext) -> Event:
|
|
1747
|
+
"""Create execution event based on action."""
|
|
1748
|
+
match context.operation.status:
|
|
1749
|
+
case OperationStatus.STARTED:
|
|
1750
|
+
return cls.create_execution_event_started(context)
|
|
1751
|
+
case OperationStatus.SUCCEEDED:
|
|
1752
|
+
return cls.create_execution_event_succeeded(context)
|
|
1753
|
+
case OperationStatus.FAILED:
|
|
1754
|
+
return cls.create_execution_event_failed(context)
|
|
1755
|
+
case OperationStatus.TIMED_OUT:
|
|
1756
|
+
return cls.create_execution_event_timed_out(context)
|
|
1757
|
+
case OperationStatus.STOPPED:
|
|
1758
|
+
return cls.create_execution_event_stopped(context)
|
|
1759
|
+
case _:
|
|
1760
|
+
msg = f"Operation status {context.operation.status} is not valid for execution operations. Valid statuses are: STARTED, SUCCEEDED, FAILED, TIMED_OUT, STOPPED"
|
|
1761
|
+
raise InvalidParameterValueException(msg)
|
|
1762
|
+
|
|
1763
|
+
# endregion execution
|
|
1764
|
+
|
|
1765
|
+
# region context
|
|
1766
|
+
@classmethod
|
|
1767
|
+
def create_context_event_started(cls, context: EventCreationContext) -> Event:
|
|
1768
|
+
return cls(
|
|
1769
|
+
event_type=EventType.CONTEXT_STARTED.value,
|
|
1770
|
+
event_timestamp=context.start_timestamp,
|
|
1771
|
+
sub_type=context.sub_type,
|
|
1772
|
+
event_id=context.event_id,
|
|
1773
|
+
operation_id=context.operation.operation_id,
|
|
1774
|
+
name=context.operation.name,
|
|
1775
|
+
parent_id=context.operation.parent_id,
|
|
1776
|
+
context_started_details=ContextStartedDetails(),
|
|
1777
|
+
)
|
|
1778
|
+
|
|
1779
|
+
@classmethod
|
|
1780
|
+
def create_context_event_succeeded(cls, context: EventCreationContext) -> Event:
|
|
1781
|
+
context_details: ContextDetails | None = context.operation.context_details
|
|
1782
|
+
event_result: EventResult | None = (
|
|
1783
|
+
EventResult.from_details(context_details, context.include_execution_data)
|
|
1784
|
+
if context_details
|
|
1785
|
+
else None
|
|
1786
|
+
)
|
|
1787
|
+
return cls(
|
|
1788
|
+
event_type=EventType.CONTEXT_SUCCEEDED.value,
|
|
1789
|
+
event_timestamp=context.end_timestamp,
|
|
1790
|
+
sub_type=context.sub_type,
|
|
1791
|
+
event_id=context.event_id,
|
|
1792
|
+
operation_id=context.operation.operation_id,
|
|
1793
|
+
name=context.operation.name,
|
|
1794
|
+
parent_id=context.operation.parent_id,
|
|
1795
|
+
context_succeeded_details=ContextSucceededDetails(result=event_result),
|
|
1796
|
+
)
|
|
1797
|
+
|
|
1798
|
+
@classmethod
|
|
1799
|
+
def create_context_event_failed(cls, context: EventCreationContext) -> Event:
|
|
1800
|
+
context_details: ContextDetails | None = context.operation.context_details
|
|
1801
|
+
event_error: EventError | None = (
|
|
1802
|
+
EventError.from_details(context_details) if context_details else None
|
|
1803
|
+
)
|
|
1804
|
+
return cls(
|
|
1805
|
+
event_type=EventType.CONTEXT_FAILED.value,
|
|
1806
|
+
event_timestamp=context.end_timestamp,
|
|
1807
|
+
sub_type=context.sub_type,
|
|
1808
|
+
event_id=context.event_id,
|
|
1809
|
+
operation_id=context.operation.operation_id,
|
|
1810
|
+
name=context.operation.name,
|
|
1811
|
+
parent_id=context.operation.parent_id,
|
|
1812
|
+
context_failed_details=ContextFailedDetails(error=event_error),
|
|
1813
|
+
)
|
|
1814
|
+
|
|
1815
|
+
@classmethod
|
|
1816
|
+
def create_context_event(cls, context: EventCreationContext) -> Event:
|
|
1817
|
+
"""Create context event based on action."""
|
|
1818
|
+
match context.operation.status:
|
|
1819
|
+
case OperationStatus.STARTED:
|
|
1820
|
+
return cls.create_context_event_started(context)
|
|
1821
|
+
case OperationStatus.SUCCEEDED:
|
|
1822
|
+
return cls.create_context_event_succeeded(context)
|
|
1823
|
+
case OperationStatus.FAILED:
|
|
1824
|
+
return cls.create_context_event_failed(context)
|
|
1825
|
+
case _:
|
|
1826
|
+
msg = (
|
|
1827
|
+
f"Operation status {context.operation.status} is not valid for context operations. "
|
|
1828
|
+
f"Valid statuses are: STARTED, SUCCEEDED, FAILED"
|
|
1829
|
+
)
|
|
1830
|
+
raise InvalidParameterValueException(msg)
|
|
1831
|
+
|
|
1832
|
+
# endregion context
|
|
1833
|
+
|
|
1834
|
+
# region wait
|
|
1835
|
+
@classmethod
|
|
1836
|
+
def create_wait_event_started(cls, context: EventCreationContext) -> Event:
|
|
1837
|
+
wait_details: WaitDetails | None = context.operation.wait_details
|
|
1838
|
+
scheduled_end_timestamp: datetime.datetime | None = (
|
|
1839
|
+
wait_details.scheduled_end_timestamp if wait_details else None
|
|
1840
|
+
)
|
|
1841
|
+
duration: int | None = None
|
|
1842
|
+
if (
|
|
1843
|
+
wait_details
|
|
1844
|
+
and wait_details.scheduled_end_timestamp
|
|
1845
|
+
and context.operation.start_timestamp
|
|
1846
|
+
):
|
|
1847
|
+
duration = round(
|
|
1848
|
+
(
|
|
1849
|
+
wait_details.scheduled_end_timestamp
|
|
1850
|
+
- context.operation.start_timestamp
|
|
1851
|
+
).total_seconds()
|
|
1852
|
+
)
|
|
1853
|
+
return cls(
|
|
1854
|
+
event_type=EventType.WAIT_STARTED.value,
|
|
1855
|
+
event_timestamp=context.start_timestamp,
|
|
1856
|
+
sub_type=context.sub_type,
|
|
1857
|
+
event_id=context.event_id,
|
|
1858
|
+
operation_id=context.operation.operation_id,
|
|
1859
|
+
name=context.operation.name,
|
|
1860
|
+
parent_id=context.operation.parent_id,
|
|
1861
|
+
wait_started_details=WaitStartedDetails(
|
|
1862
|
+
duration=duration,
|
|
1863
|
+
scheduled_end_timestamp=scheduled_end_timestamp,
|
|
1864
|
+
),
|
|
1865
|
+
)
|
|
1866
|
+
|
|
1867
|
+
@classmethod
|
|
1868
|
+
def create_wait_event_succeeded(cls, context: EventCreationContext) -> Event:
|
|
1869
|
+
wait_details: WaitDetails | None = context.operation.wait_details
|
|
1870
|
+
duration: int | None = None
|
|
1871
|
+
if (
|
|
1872
|
+
wait_details
|
|
1873
|
+
and wait_details.scheduled_end_timestamp
|
|
1874
|
+
and context.operation.start_timestamp
|
|
1875
|
+
):
|
|
1876
|
+
duration = round(
|
|
1877
|
+
(
|
|
1878
|
+
wait_details.scheduled_end_timestamp - context.start_timestamp
|
|
1879
|
+
).total_seconds()
|
|
1880
|
+
)
|
|
1881
|
+
return cls(
|
|
1882
|
+
event_type=EventType.WAIT_SUCCEEDED.value,
|
|
1883
|
+
event_timestamp=context.end_timestamp,
|
|
1884
|
+
sub_type=context.sub_type,
|
|
1885
|
+
event_id=context.event_id,
|
|
1886
|
+
operation_id=context.operation.operation_id,
|
|
1887
|
+
name=context.operation.name,
|
|
1888
|
+
parent_id=context.operation.parent_id,
|
|
1889
|
+
wait_succeeded_details=WaitSucceededDetails(duration=duration),
|
|
1890
|
+
)
|
|
1891
|
+
|
|
1892
|
+
@classmethod
|
|
1893
|
+
def create_wait_event_cancelled(cls, context: EventCreationContext) -> Event:
|
|
1894
|
+
error: EventError | None = None
|
|
1895
|
+
if (
|
|
1896
|
+
context.operation_update
|
|
1897
|
+
and context.operation_update.operation_type == OperationType.WAIT
|
|
1898
|
+
and context.operation_update.action == OperationAction.CANCEL
|
|
1899
|
+
):
|
|
1900
|
+
error = EventError(
|
|
1901
|
+
context.operation_update.error, not context.include_execution_data
|
|
1902
|
+
)
|
|
1903
|
+
return cls(
|
|
1904
|
+
event_type=EventType.WAIT_CANCELLED.value,
|
|
1905
|
+
event_timestamp=context.end_timestamp,
|
|
1906
|
+
sub_type=context.sub_type,
|
|
1907
|
+
event_id=context.event_id,
|
|
1908
|
+
operation_id=context.operation.operation_id,
|
|
1909
|
+
name=context.operation.name,
|
|
1910
|
+
parent_id=context.operation.parent_id,
|
|
1911
|
+
wait_cancelled_details=WaitCancelledDetails(error=error),
|
|
1912
|
+
)
|
|
1913
|
+
|
|
1914
|
+
@classmethod
|
|
1915
|
+
def create_wait_event(cls, context: EventCreationContext) -> Event:
|
|
1916
|
+
"""Create wait event based on action."""
|
|
1917
|
+
match context.operation.status:
|
|
1918
|
+
case OperationStatus.STARTED:
|
|
1919
|
+
return cls.create_wait_event_started(context)
|
|
1920
|
+
case OperationStatus.SUCCEEDED:
|
|
1921
|
+
return cls.create_wait_event_succeeded(context)
|
|
1922
|
+
case OperationStatus.CANCELLED:
|
|
1923
|
+
return cls.create_wait_event_cancelled(context)
|
|
1924
|
+
case _:
|
|
1925
|
+
msg = (
|
|
1926
|
+
f"Operation status {context.operation.status} is not valid for wait operations. "
|
|
1927
|
+
f"Valid statuses are: STARTED, SUCCEEDED, CANCELLED"
|
|
1928
|
+
)
|
|
1929
|
+
raise InvalidParameterValueException(msg)
|
|
1930
|
+
|
|
1931
|
+
# endregion wait
|
|
1932
|
+
|
|
1933
|
+
# region step
|
|
1934
|
+
@classmethod
|
|
1935
|
+
def create_step_event_started(cls, context: EventCreationContext) -> Event:
|
|
1936
|
+
return cls(
|
|
1937
|
+
event_type=EventType.STEP_STARTED.value,
|
|
1938
|
+
event_timestamp=context.start_timestamp,
|
|
1939
|
+
sub_type=context.sub_type,
|
|
1940
|
+
event_id=context.event_id,
|
|
1941
|
+
operation_id=context.operation.operation_id,
|
|
1942
|
+
name=context.operation.name,
|
|
1943
|
+
parent_id=context.operation.parent_id,
|
|
1944
|
+
step_started_details=StepStartedDetails(),
|
|
1945
|
+
)
|
|
1946
|
+
|
|
1947
|
+
@classmethod
|
|
1948
|
+
def create_step_event_succeeded(cls, context: EventCreationContext) -> Event:
|
|
1949
|
+
step_details: StepDetails | None = context.operation.step_details
|
|
1950
|
+
event_result: EventResult | None = (
|
|
1951
|
+
EventResult.from_details(step_details, context.include_execution_data)
|
|
1952
|
+
if step_details
|
|
1953
|
+
else None
|
|
1954
|
+
)
|
|
1955
|
+
return cls(
|
|
1956
|
+
event_type=EventType.STEP_SUCCEEDED.value,
|
|
1957
|
+
event_timestamp=context.end_timestamp,
|
|
1958
|
+
sub_type=context.sub_type,
|
|
1959
|
+
event_id=context.event_id,
|
|
1960
|
+
operation_id=context.operation.operation_id,
|
|
1961
|
+
name=context.operation.name,
|
|
1962
|
+
parent_id=context.operation.parent_id,
|
|
1963
|
+
step_succeeded_details=StepSucceededDetails(
|
|
1964
|
+
result=event_result,
|
|
1965
|
+
retry_details=context.get_retry_details(),
|
|
1966
|
+
),
|
|
1967
|
+
)
|
|
1968
|
+
|
|
1969
|
+
@classmethod
|
|
1970
|
+
def create_step_event_failed(cls, context: EventCreationContext) -> Event:
|
|
1971
|
+
step_details: StepDetails | None = context.operation.step_details
|
|
1972
|
+
event_error: EventError | None = (
|
|
1973
|
+
EventError.from_details(
|
|
1974
|
+
step_details, include=context.include_execution_data
|
|
1975
|
+
)
|
|
1976
|
+
if step_details
|
|
1977
|
+
else None
|
|
1978
|
+
)
|
|
1979
|
+
return cls(
|
|
1980
|
+
event_type=EventType.STEP_FAILED.value,
|
|
1981
|
+
event_timestamp=context.end_timestamp,
|
|
1982
|
+
sub_type=context.sub_type,
|
|
1983
|
+
event_id=context.event_id,
|
|
1984
|
+
operation_id=context.operation.operation_id,
|
|
1985
|
+
name=context.operation.name,
|
|
1986
|
+
parent_id=context.operation.parent_id,
|
|
1987
|
+
step_failed_details=StepFailedDetails(
|
|
1988
|
+
error=event_error,
|
|
1989
|
+
retry_details=context.get_retry_details(),
|
|
1990
|
+
),
|
|
1991
|
+
)
|
|
1992
|
+
|
|
1993
|
+
@classmethod
|
|
1994
|
+
def create_step_event(cls, context: EventCreationContext) -> Event:
|
|
1995
|
+
"""Create step event based on action."""
|
|
1996
|
+
match context.operation.status:
|
|
1997
|
+
case OperationStatus.STARTED:
|
|
1998
|
+
return cls.create_step_event_started(context)
|
|
1999
|
+
case OperationStatus.SUCCEEDED:
|
|
2000
|
+
return cls.create_step_event_succeeded(context)
|
|
2001
|
+
case OperationStatus.FAILED:
|
|
2002
|
+
return cls.create_step_event_failed(context)
|
|
2003
|
+
case _:
|
|
2004
|
+
msg = (
|
|
2005
|
+
f"Operation status {context.operation.status} is not valid for step operations. "
|
|
2006
|
+
f"Valid statuses are: STARTED, SUCCEEDED, FAILED"
|
|
2007
|
+
)
|
|
2008
|
+
raise InvalidParameterValueException(msg)
|
|
2009
|
+
|
|
2010
|
+
# endregion step
|
|
2011
|
+
|
|
2012
|
+
# region chained_invoke
|
|
2013
|
+
@classmethod
|
|
2014
|
+
def create_chained_invoke_event_pending(
|
|
2015
|
+
cls, context: EventCreationContext
|
|
2016
|
+
) -> Event:
|
|
2017
|
+
input: EventInput = EventInput.from_start_durable_execution_input(
|
|
2018
|
+
context.start_durable_execution_input, context.include_execution_data
|
|
2019
|
+
)
|
|
2020
|
+
return cls(
|
|
2021
|
+
event_type=EventType.CHAINED_INVOKE_STARTED.value,
|
|
2022
|
+
event_timestamp=context.start_timestamp,
|
|
2023
|
+
sub_type=context.sub_type,
|
|
2024
|
+
event_id=context.event_id,
|
|
2025
|
+
operation_id=context.operation.operation_id,
|
|
2026
|
+
name=context.operation.name,
|
|
2027
|
+
parent_id=context.operation.parent_id,
|
|
2028
|
+
chained_invoke_pending_details=ChainedInvokePendingDetails(
|
|
2029
|
+
input=input,
|
|
2030
|
+
function_name=context.start_durable_execution_input.function_name,
|
|
2031
|
+
),
|
|
2032
|
+
)
|
|
2033
|
+
|
|
2034
|
+
@classmethod
|
|
2035
|
+
def create_chained_invoke_event_started(
|
|
2036
|
+
cls, context: EventCreationContext
|
|
2037
|
+
) -> Event:
|
|
2038
|
+
return cls(
|
|
2039
|
+
event_type=EventType.CHAINED_INVOKE_STARTED.value,
|
|
2040
|
+
event_timestamp=context.start_timestamp,
|
|
2041
|
+
sub_type=context.sub_type,
|
|
2042
|
+
event_id=context.event_id,
|
|
2043
|
+
operation_id=context.operation.operation_id,
|
|
2044
|
+
name=context.operation.name,
|
|
2045
|
+
parent_id=context.operation.parent_id,
|
|
2046
|
+
chained_invoke_started_details=ChainedInvokeStartedDetails(
|
|
2047
|
+
durable_execution_arn=context.durable_execution_arn
|
|
2048
|
+
),
|
|
2049
|
+
)
|
|
2050
|
+
|
|
2051
|
+
@classmethod
|
|
2052
|
+
def create_chained_invoke_event_succeeded(
|
|
2053
|
+
cls, context: EventCreationContext
|
|
2054
|
+
) -> Event:
|
|
2055
|
+
chained_invoke_details: ChainedInvokeDetails | None = (
|
|
2056
|
+
context.operation.chained_invoke_details
|
|
2057
|
+
)
|
|
2058
|
+
event_result: EventResult | None = (
|
|
2059
|
+
EventResult.from_details(
|
|
2060
|
+
chained_invoke_details, context.include_execution_data
|
|
2061
|
+
)
|
|
2062
|
+
if chained_invoke_details
|
|
2063
|
+
else None
|
|
2064
|
+
)
|
|
2065
|
+
return cls(
|
|
2066
|
+
event_type=EventType.CHAINED_INVOKE_SUCCEEDED.value,
|
|
2067
|
+
event_timestamp=context.end_timestamp,
|
|
2068
|
+
sub_type=context.sub_type,
|
|
2069
|
+
event_id=context.event_id,
|
|
2070
|
+
operation_id=context.operation.operation_id,
|
|
2071
|
+
name=context.operation.name,
|
|
2072
|
+
parent_id=context.operation.parent_id,
|
|
2073
|
+
chained_invoke_succeeded_details=ChainedInvokeSucceededDetails(
|
|
2074
|
+
result=event_result
|
|
2075
|
+
),
|
|
2076
|
+
)
|
|
2077
|
+
|
|
2078
|
+
@classmethod
|
|
2079
|
+
def create_chained_invoke_event_failed(cls, context: EventCreationContext) -> Event:
|
|
2080
|
+
chained_invoke_details: ChainedInvokeDetails | None = (
|
|
2081
|
+
context.operation.chained_invoke_details
|
|
2082
|
+
)
|
|
2083
|
+
event_error: EventError | None = (
|
|
2084
|
+
EventError.from_details(
|
|
2085
|
+
chained_invoke_details, include=context.include_execution_data
|
|
2086
|
+
)
|
|
2087
|
+
if chained_invoke_details
|
|
2088
|
+
else None
|
|
2089
|
+
)
|
|
2090
|
+
return cls(
|
|
2091
|
+
event_type=EventType.CHAINED_INVOKE_FAILED.value,
|
|
2092
|
+
event_timestamp=context.end_timestamp,
|
|
2093
|
+
sub_type=context.sub_type,
|
|
2094
|
+
event_id=context.event_id,
|
|
2095
|
+
operation_id=context.operation.operation_id,
|
|
2096
|
+
name=context.operation.name,
|
|
2097
|
+
parent_id=context.operation.parent_id,
|
|
2098
|
+
chained_invoke_failed_details=ChainedInvokeFailedDetails(error=event_error),
|
|
2099
|
+
)
|
|
2100
|
+
|
|
2101
|
+
@classmethod
|
|
2102
|
+
def create_chained_invoke_event_timed_out(
|
|
2103
|
+
cls, context: EventCreationContext
|
|
2104
|
+
) -> Event:
|
|
2105
|
+
chained_invoke_details: ChainedInvokeDetails | None = (
|
|
2106
|
+
context.operation.chained_invoke_details
|
|
2107
|
+
)
|
|
2108
|
+
event_error: EventError | None = (
|
|
2109
|
+
EventError.from_details(
|
|
2110
|
+
chained_invoke_details, include=context.include_execution_data
|
|
2111
|
+
)
|
|
2112
|
+
if chained_invoke_details
|
|
2113
|
+
else None
|
|
2114
|
+
)
|
|
2115
|
+
return cls(
|
|
2116
|
+
event_type=EventType.CHAINED_INVOKE_TIMED_OUT.value,
|
|
2117
|
+
event_timestamp=context.end_timestamp,
|
|
2118
|
+
sub_type=context.sub_type,
|
|
2119
|
+
event_id=context.event_id,
|
|
2120
|
+
operation_id=context.operation.operation_id,
|
|
2121
|
+
name=context.operation.name,
|
|
2122
|
+
parent_id=context.operation.parent_id,
|
|
2123
|
+
chained_invoke_timed_out_details=ChainedInvokeTimedOutDetails(
|
|
2124
|
+
error=event_error
|
|
2125
|
+
),
|
|
2126
|
+
)
|
|
2127
|
+
|
|
2128
|
+
@classmethod
|
|
2129
|
+
def create_chained_invoke_event_stopped(
|
|
2130
|
+
cls, context: EventCreationContext
|
|
2131
|
+
) -> Event:
|
|
2132
|
+
chained_invoke_details: ChainedInvokeDetails | None = (
|
|
2133
|
+
context.operation.chained_invoke_details
|
|
2134
|
+
)
|
|
2135
|
+
event_error: EventError | None = (
|
|
2136
|
+
EventError.from_details(
|
|
2137
|
+
chained_invoke_details, include=context.include_execution_data
|
|
2138
|
+
)
|
|
2139
|
+
if chained_invoke_details
|
|
2140
|
+
else None
|
|
2141
|
+
)
|
|
2142
|
+
return cls(
|
|
2143
|
+
event_type=EventType.CHAINED_INVOKE_STOPPED.value,
|
|
2144
|
+
event_timestamp=context.end_timestamp,
|
|
2145
|
+
sub_type=context.sub_type,
|
|
2146
|
+
event_id=context.event_id,
|
|
2147
|
+
operation_id=context.operation.operation_id,
|
|
2148
|
+
name=context.operation.name,
|
|
2149
|
+
parent_id=context.operation.parent_id,
|
|
2150
|
+
chained_invoke_stopped_details=ChainedInvokeStoppedDetails(
|
|
2151
|
+
error=event_error
|
|
2152
|
+
),
|
|
2153
|
+
)
|
|
2154
|
+
|
|
2155
|
+
@classmethod
|
|
2156
|
+
def create_chained_invoke_event(cls, context: EventCreationContext) -> Event:
|
|
2157
|
+
"""Create chained invoke event based on action."""
|
|
2158
|
+
match context.operation.status:
|
|
2159
|
+
case OperationStatus.PENDING:
|
|
2160
|
+
return cls.create_chained_invoke_event_pending(context)
|
|
2161
|
+
case OperationStatus.STARTED:
|
|
2162
|
+
return cls.create_chained_invoke_event_started(context)
|
|
2163
|
+
case OperationStatus.SUCCEEDED:
|
|
2164
|
+
return cls.create_chained_invoke_event_succeeded(context)
|
|
2165
|
+
case OperationStatus.FAILED:
|
|
2166
|
+
return cls.create_chained_invoke_event_failed(context)
|
|
2167
|
+
case OperationStatus.TIMED_OUT:
|
|
2168
|
+
return cls.create_chained_invoke_event_timed_out(context)
|
|
2169
|
+
case OperationStatus.STOPPED:
|
|
2170
|
+
return cls.create_chained_invoke_event_stopped(context)
|
|
2171
|
+
case _:
|
|
2172
|
+
msg = (
|
|
2173
|
+
f"Operation status {context.operation.status} is not valid for chained invoke operations. Valid statuses are: "
|
|
2174
|
+
f"STARTED, SUCCEEDED, FAILED, TIMED_OUT, STOPPED"
|
|
2175
|
+
)
|
|
2176
|
+
raise InvalidParameterValueException(msg)
|
|
2177
|
+
|
|
2178
|
+
# endregion chained_invoke
|
|
2179
|
+
|
|
2180
|
+
# region callback
|
|
2181
|
+
@classmethod
|
|
2182
|
+
def create_callback_event_started(cls, context: EventCreationContext) -> Event:
|
|
2183
|
+
callback_details: CallbackDetails | None = context.operation.callback_details
|
|
2184
|
+
callback_id: str | None = (
|
|
2185
|
+
callback_details.callback_id if callback_details else None
|
|
2186
|
+
)
|
|
2187
|
+
callback_options: CallbackOptions | None = (
|
|
2188
|
+
context.operation_update.callback_options
|
|
2189
|
+
if context.operation_update
|
|
2190
|
+
else None
|
|
2191
|
+
)
|
|
2192
|
+
timeout: int | None = (
|
|
2193
|
+
callback_options.timeout_seconds if callback_options else None
|
|
2194
|
+
)
|
|
2195
|
+
heartbeat_timeout: int | None = (
|
|
2196
|
+
callback_options.heartbeat_timeout_seconds if callback_options else None
|
|
2197
|
+
)
|
|
2198
|
+
return cls(
|
|
2199
|
+
event_type=EventType.CALLBACK_STARTED.value,
|
|
2200
|
+
event_timestamp=context.start_timestamp,
|
|
2201
|
+
sub_type=context.sub_type,
|
|
2202
|
+
event_id=context.event_id,
|
|
2203
|
+
operation_id=context.operation.operation_id,
|
|
2204
|
+
name=context.operation.name,
|
|
2205
|
+
parent_id=context.operation.parent_id,
|
|
2206
|
+
callback_started_details=CallbackStartedDetails(
|
|
2207
|
+
callback_id=callback_id,
|
|
2208
|
+
timeout=timeout,
|
|
2209
|
+
heartbeat_timeout=heartbeat_timeout,
|
|
2210
|
+
),
|
|
2211
|
+
)
|
|
2212
|
+
|
|
2213
|
+
@classmethod
|
|
2214
|
+
def create_callback_event_succeeded(cls, context: EventCreationContext) -> Event:
|
|
2215
|
+
callback_details: CallbackDetails | None = context.operation.callback_details
|
|
2216
|
+
event_result: EventResult | None = (
|
|
2217
|
+
EventResult.from_details(callback_details, context.include_execution_data)
|
|
2218
|
+
if callback_details
|
|
2219
|
+
else None
|
|
2220
|
+
)
|
|
2221
|
+
return cls(
|
|
2222
|
+
event_type=EventType.CALLBACK_SUCCEEDED.value,
|
|
2223
|
+
event_timestamp=context.end_timestamp,
|
|
2224
|
+
sub_type=context.sub_type,
|
|
2225
|
+
event_id=context.event_id,
|
|
2226
|
+
operation_id=context.operation.operation_id,
|
|
2227
|
+
name=context.operation.name,
|
|
2228
|
+
parent_id=context.operation.parent_id,
|
|
2229
|
+
callback_succeeded_details=CallbackSucceededDetails(result=event_result),
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
@classmethod
|
|
2233
|
+
def create_callback_event_failed(cls, context: EventCreationContext) -> Event:
|
|
2234
|
+
callback_details: CallbackDetails | None = context.operation.callback_details
|
|
2235
|
+
event_error: EventError | None = (
|
|
2236
|
+
EventError.from_details(callback_details) if callback_details else None
|
|
2237
|
+
)
|
|
2238
|
+
return cls(
|
|
2239
|
+
event_type=EventType.CALLBACK_FAILED.value,
|
|
2240
|
+
event_timestamp=context.end_timestamp,
|
|
2241
|
+
sub_type=context.sub_type,
|
|
2242
|
+
event_id=context.event_id,
|
|
2243
|
+
operation_id=context.operation.operation_id,
|
|
2244
|
+
name=context.operation.name,
|
|
2245
|
+
parent_id=context.operation.parent_id,
|
|
2246
|
+
callback_failed_details=CallbackFailedDetails(error=event_error),
|
|
2247
|
+
)
|
|
2248
|
+
|
|
2249
|
+
@classmethod
|
|
2250
|
+
def create_callback_event_timed_out(cls, context: EventCreationContext) -> Event:
|
|
2251
|
+
callback_details: CallbackDetails | None = context.operation.callback_details
|
|
2252
|
+
event_error: EventError | None = (
|
|
2253
|
+
EventError.from_details(callback_details) if callback_details else None
|
|
2254
|
+
)
|
|
2255
|
+
return cls(
|
|
2256
|
+
event_type=EventType.CALLBACK_TIMED_OUT.value,
|
|
2257
|
+
event_timestamp=context.end_timestamp,
|
|
2258
|
+
sub_type=context.sub_type,
|
|
2259
|
+
event_id=context.event_id,
|
|
2260
|
+
operation_id=context.operation.operation_id,
|
|
2261
|
+
name=context.operation.name,
|
|
2262
|
+
parent_id=context.operation.parent_id,
|
|
2263
|
+
callback_timed_out_details=CallbackTimedOutDetails(error=event_error),
|
|
2264
|
+
)
|
|
2265
|
+
|
|
2266
|
+
@classmethod
|
|
2267
|
+
def create_callback_event(cls, context: EventCreationContext) -> Event:
|
|
2268
|
+
"""Create callback event based on action."""
|
|
2269
|
+
match context.operation.status:
|
|
2270
|
+
case OperationStatus.STARTED:
|
|
2271
|
+
return cls.create_callback_event_started(context)
|
|
2272
|
+
case OperationStatus.SUCCEEDED:
|
|
2273
|
+
return cls.create_callback_event_succeeded(context)
|
|
2274
|
+
case OperationStatus.FAILED:
|
|
2275
|
+
return cls.create_callback_event_failed(context)
|
|
2276
|
+
case OperationStatus.TIMED_OUT:
|
|
2277
|
+
return cls.create_callback_event_timed_out(context)
|
|
2278
|
+
case _:
|
|
2279
|
+
msg = (
|
|
2280
|
+
f"Operation status {context.operation.status} is not valid for callback operations. "
|
|
2281
|
+
f"Valid statuses are: STARTED, SUCCEEDED, FAILED, TIMED_OUT"
|
|
2282
|
+
)
|
|
2283
|
+
raise InvalidParameterValueException(msg)
|
|
2284
|
+
|
|
2285
|
+
# endregion callback
|
|
2286
|
+
|
|
2287
|
+
# region invocation_completed
|
|
2288
|
+
@classmethod
|
|
2289
|
+
def create_invocation_completed(
|
|
2290
|
+
cls,
|
|
2291
|
+
event_id: int,
|
|
2292
|
+
event_timestamp: datetime.datetime,
|
|
2293
|
+
start_timestamp: datetime.datetime,
|
|
2294
|
+
end_timestamp: datetime.datetime,
|
|
2295
|
+
request_id: str,
|
|
2296
|
+
) -> Event:
|
|
2297
|
+
"""Create invocation completed event."""
|
|
2298
|
+
return cls(
|
|
2299
|
+
event_type=EventType.INVOCATION_COMPLETED.value,
|
|
2300
|
+
event_timestamp=event_timestamp,
|
|
2301
|
+
event_id=event_id,
|
|
2302
|
+
invocation_completed_details=InvocationCompletedDetails(
|
|
2303
|
+
start_timestamp=start_timestamp,
|
|
2304
|
+
end_timestamp=end_timestamp,
|
|
2305
|
+
request_id=request_id,
|
|
2306
|
+
),
|
|
2307
|
+
)
|
|
2308
|
+
|
|
2309
|
+
# endregion invocation_completed
|
|
2310
|
+
|
|
2311
|
+
@classmethod
|
|
2312
|
+
def create_event_started(cls, context: EventCreationContext) -> Event:
|
|
2313
|
+
"""Convert operation to started event."""
|
|
2314
|
+
if context.operation.start_timestamp is None:
|
|
2315
|
+
msg: str = "Operation start timestamp cannot be None when converting to started event"
|
|
2316
|
+
raise InvalidParameterValueException(msg)
|
|
2317
|
+
|
|
2318
|
+
match context.operation.operation_type:
|
|
2319
|
+
case OperationType.EXECUTION:
|
|
2320
|
+
return cls.create_execution_event_started(context)
|
|
2321
|
+
case OperationType.CONTEXT:
|
|
2322
|
+
return cls.create_context_event_started(context)
|
|
2323
|
+
case OperationType.WAIT:
|
|
2324
|
+
return cls.create_wait_event_started(context)
|
|
2325
|
+
case OperationType.STEP:
|
|
2326
|
+
return cls.create_step_event_started(context)
|
|
2327
|
+
case OperationType.CHAINED_INVOKE:
|
|
2328
|
+
return cls.create_chained_invoke_event_started(context)
|
|
2329
|
+
case OperationType.CALLBACK:
|
|
2330
|
+
return cls.create_callback_event_started(context)
|
|
2331
|
+
case _:
|
|
2332
|
+
msg = f"Unknown operation type: {context.operation.operation_type}"
|
|
2333
|
+
raise InvalidParameterValueException(msg)
|
|
2334
|
+
|
|
2335
|
+
@classmethod
|
|
2336
|
+
def from_event_with_id(cls, event: Event, event_id: int) -> Event:
|
|
2337
|
+
"""Create a new Event from an existing event with updated event_id."""
|
|
2338
|
+
return cls(
|
|
2339
|
+
event_type=event.event_type,
|
|
2340
|
+
event_timestamp=event.event_timestamp,
|
|
2341
|
+
sub_type=event.sub_type,
|
|
2342
|
+
event_id=event_id,
|
|
2343
|
+
operation_id=event.operation_id,
|
|
2344
|
+
name=event.name,
|
|
2345
|
+
parent_id=event.parent_id,
|
|
2346
|
+
execution_started_details=event.execution_started_details,
|
|
2347
|
+
execution_succeeded_details=event.execution_succeeded_details,
|
|
2348
|
+
execution_failed_details=event.execution_failed_details,
|
|
2349
|
+
execution_timed_out_details=event.execution_timed_out_details,
|
|
2350
|
+
execution_stopped_details=event.execution_stopped_details,
|
|
2351
|
+
context_started_details=event.context_started_details,
|
|
2352
|
+
context_succeeded_details=event.context_succeeded_details,
|
|
2353
|
+
context_failed_details=event.context_failed_details,
|
|
2354
|
+
wait_started_details=event.wait_started_details,
|
|
2355
|
+
wait_succeeded_details=event.wait_succeeded_details,
|
|
2356
|
+
wait_cancelled_details=event.wait_cancelled_details,
|
|
2357
|
+
step_started_details=event.step_started_details,
|
|
2358
|
+
step_succeeded_details=event.step_succeeded_details,
|
|
2359
|
+
step_failed_details=event.step_failed_details,
|
|
2360
|
+
chained_invoke_pending_details=event.chained_invoke_pending_details,
|
|
2361
|
+
chained_invoke_started_details=event.chained_invoke_started_details,
|
|
2362
|
+
chained_invoke_succeeded_details=event.chained_invoke_succeeded_details,
|
|
2363
|
+
chained_invoke_failed_details=event.chained_invoke_failed_details,
|
|
2364
|
+
chained_invoke_timed_out_details=event.chained_invoke_timed_out_details,
|
|
2365
|
+
chained_invoke_stopped_details=event.chained_invoke_stopped_details,
|
|
2366
|
+
callback_started_details=event.callback_started_details,
|
|
2367
|
+
callback_succeeded_details=event.callback_succeeded_details,
|
|
2368
|
+
callback_failed_details=event.callback_failed_details,
|
|
2369
|
+
callback_timed_out_details=event.callback_timed_out_details,
|
|
2370
|
+
)
|
|
2371
|
+
|
|
2372
|
+
@classmethod
|
|
2373
|
+
def create_event_terminated(cls, context: EventCreationContext) -> Event:
|
|
2374
|
+
"""Convert operation to finished event."""
|
|
2375
|
+
operation: Operation = context.operation
|
|
2376
|
+
if operation.end_timestamp is None:
|
|
2377
|
+
msg: str = "Operation end timestamp cannot be None when converting to finished event"
|
|
2378
|
+
raise InvalidParameterValueException(msg)
|
|
2379
|
+
|
|
2380
|
+
if operation.status not in TERMINAL_STATUSES:
|
|
2381
|
+
msg = f"Operation status must be one of SUCCEEDED, FAILED, TIMED_OUT, STOPPED, or CANCELLED. Got: {operation.status}"
|
|
2382
|
+
raise InvalidParameterValueException(msg)
|
|
2383
|
+
|
|
2384
|
+
match operation.operation_type:
|
|
2385
|
+
case OperationType.EXECUTION:
|
|
2386
|
+
return cls.create_execution_event(context)
|
|
2387
|
+
case OperationType.CONTEXT:
|
|
2388
|
+
return cls.create_context_event(context)
|
|
2389
|
+
case OperationType.WAIT:
|
|
2390
|
+
return cls.create_wait_event(context)
|
|
2391
|
+
case OperationType.STEP:
|
|
2392
|
+
return cls.create_step_event(context)
|
|
2393
|
+
case OperationType.CHAINED_INVOKE:
|
|
2394
|
+
return cls.create_chained_invoke_event(context)
|
|
2395
|
+
case OperationType.CALLBACK:
|
|
2396
|
+
return cls.create_callback_event(context)
|
|
2397
|
+
case _:
|
|
2398
|
+
msg = f"Unknown operation type: {operation.operation_type}"
|
|
2399
|
+
raise InvalidParameterValueException(msg)
|
|
2400
|
+
|
|
2401
|
+
|
|
2402
|
+
# endregion event_class
|
|
2403
|
+
|
|
2404
|
+
|
|
2405
|
+
# region history_models
|
|
2406
|
+
@dataclass(frozen=True)
|
|
2407
|
+
class HistoryEventTypeConfig:
|
|
2408
|
+
"""Configuration for how to process a specific event type."""
|
|
2409
|
+
|
|
2410
|
+
operation_type: OperationType | None
|
|
2411
|
+
operation_status: OperationStatus | None
|
|
2412
|
+
is_start_event: bool
|
|
2413
|
+
is_end_event: bool
|
|
2414
|
+
has_result: bool # Whether this event type contains result/error data
|
|
2415
|
+
|
|
2416
|
+
|
|
2417
|
+
# Mapping of event types to their processing configuration
|
|
2418
|
+
# This matches the TypeScript historyEventTypes constant
|
|
2419
|
+
HISTORY_EVENT_TYPES: dict[str, HistoryEventTypeConfig] = {
|
|
2420
|
+
"ExecutionStarted": HistoryEventTypeConfig(
|
|
2421
|
+
operation_type=OperationType.EXECUTION,
|
|
2422
|
+
operation_status=OperationStatus.STARTED,
|
|
2423
|
+
is_start_event=True,
|
|
2424
|
+
is_end_event=False,
|
|
2425
|
+
has_result=False,
|
|
2426
|
+
),
|
|
2427
|
+
"ExecutionFailed": HistoryEventTypeConfig(
|
|
2428
|
+
operation_type=OperationType.EXECUTION,
|
|
2429
|
+
operation_status=OperationStatus.FAILED,
|
|
2430
|
+
is_start_event=False,
|
|
2431
|
+
is_end_event=True,
|
|
2432
|
+
has_result=False,
|
|
2433
|
+
),
|
|
2434
|
+
"ExecutionStopped": HistoryEventTypeConfig(
|
|
2435
|
+
operation_type=OperationType.EXECUTION,
|
|
2436
|
+
operation_status=OperationStatus.STOPPED,
|
|
2437
|
+
is_start_event=False,
|
|
2438
|
+
is_end_event=True,
|
|
2439
|
+
has_result=False,
|
|
2440
|
+
),
|
|
2441
|
+
"ExecutionSucceeded": HistoryEventTypeConfig(
|
|
2442
|
+
operation_type=OperationType.EXECUTION,
|
|
2443
|
+
operation_status=OperationStatus.SUCCEEDED,
|
|
2444
|
+
is_start_event=False,
|
|
2445
|
+
is_end_event=True,
|
|
2446
|
+
has_result=False,
|
|
2447
|
+
),
|
|
2448
|
+
"ExecutionTimedOut": HistoryEventTypeConfig(
|
|
2449
|
+
operation_type=OperationType.EXECUTION,
|
|
2450
|
+
operation_status=OperationStatus.TIMED_OUT,
|
|
2451
|
+
is_start_event=False,
|
|
2452
|
+
is_end_event=True,
|
|
2453
|
+
has_result=False,
|
|
2454
|
+
),
|
|
2455
|
+
"CallbackStarted": HistoryEventTypeConfig(
|
|
2456
|
+
operation_type=OperationType.CALLBACK,
|
|
2457
|
+
operation_status=OperationStatus.STARTED,
|
|
2458
|
+
is_start_event=True,
|
|
2459
|
+
is_end_event=False,
|
|
2460
|
+
has_result=False,
|
|
2461
|
+
),
|
|
2462
|
+
"CallbackFailed": HistoryEventTypeConfig(
|
|
2463
|
+
operation_type=OperationType.CALLBACK,
|
|
2464
|
+
operation_status=OperationStatus.FAILED,
|
|
2465
|
+
is_start_event=False,
|
|
2466
|
+
is_end_event=True,
|
|
2467
|
+
has_result=True,
|
|
2468
|
+
),
|
|
2469
|
+
"CallbackSucceeded": HistoryEventTypeConfig(
|
|
2470
|
+
operation_type=OperationType.CALLBACK,
|
|
2471
|
+
operation_status=OperationStatus.SUCCEEDED,
|
|
2472
|
+
is_start_event=False,
|
|
2473
|
+
is_end_event=True,
|
|
2474
|
+
has_result=True,
|
|
2475
|
+
),
|
|
2476
|
+
"CallbackTimedOut": HistoryEventTypeConfig(
|
|
2477
|
+
operation_type=OperationType.CALLBACK,
|
|
2478
|
+
operation_status=OperationStatus.TIMED_OUT,
|
|
2479
|
+
is_start_event=False,
|
|
2480
|
+
is_end_event=True,
|
|
2481
|
+
has_result=True,
|
|
2482
|
+
),
|
|
2483
|
+
"ContextStarted": HistoryEventTypeConfig(
|
|
2484
|
+
operation_type=OperationType.CONTEXT,
|
|
2485
|
+
operation_status=OperationStatus.STARTED,
|
|
2486
|
+
is_start_event=True,
|
|
2487
|
+
is_end_event=False,
|
|
2488
|
+
has_result=False,
|
|
2489
|
+
),
|
|
2490
|
+
"ContextFailed": HistoryEventTypeConfig(
|
|
2491
|
+
operation_type=OperationType.CONTEXT,
|
|
2492
|
+
operation_status=OperationStatus.FAILED,
|
|
2493
|
+
is_start_event=False,
|
|
2494
|
+
is_end_event=True,
|
|
2495
|
+
has_result=True,
|
|
2496
|
+
),
|
|
2497
|
+
"ContextSucceeded": HistoryEventTypeConfig(
|
|
2498
|
+
operation_type=OperationType.CONTEXT,
|
|
2499
|
+
operation_status=OperationStatus.SUCCEEDED,
|
|
2500
|
+
is_start_event=False,
|
|
2501
|
+
is_end_event=True,
|
|
2502
|
+
has_result=True,
|
|
2503
|
+
),
|
|
2504
|
+
"ChainedInvokeStarted": HistoryEventTypeConfig(
|
|
2505
|
+
operation_type=OperationType.CHAINED_INVOKE,
|
|
2506
|
+
operation_status=OperationStatus.STARTED,
|
|
2507
|
+
is_start_event=True,
|
|
2508
|
+
is_end_event=False,
|
|
2509
|
+
has_result=False,
|
|
2510
|
+
),
|
|
2511
|
+
"ChainedInvokeFailed": HistoryEventTypeConfig(
|
|
2512
|
+
operation_type=OperationType.CHAINED_INVOKE,
|
|
2513
|
+
operation_status=OperationStatus.FAILED,
|
|
2514
|
+
is_start_event=False,
|
|
2515
|
+
is_end_event=True,
|
|
2516
|
+
has_result=True,
|
|
2517
|
+
),
|
|
2518
|
+
"ChainedInvokeSucceeded": HistoryEventTypeConfig(
|
|
2519
|
+
operation_type=OperationType.CHAINED_INVOKE,
|
|
2520
|
+
operation_status=OperationStatus.SUCCEEDED,
|
|
2521
|
+
is_start_event=False,
|
|
2522
|
+
is_end_event=True,
|
|
2523
|
+
has_result=True,
|
|
2524
|
+
),
|
|
2525
|
+
"ChainedInvokeTimedOut": HistoryEventTypeConfig(
|
|
2526
|
+
operation_type=OperationType.CHAINED_INVOKE,
|
|
2527
|
+
operation_status=OperationStatus.TIMED_OUT,
|
|
2528
|
+
is_start_event=False,
|
|
2529
|
+
is_end_event=True,
|
|
2530
|
+
has_result=True,
|
|
2531
|
+
),
|
|
2532
|
+
"ChainedInvokeCancelled": HistoryEventTypeConfig(
|
|
2533
|
+
operation_type=OperationType.CHAINED_INVOKE,
|
|
2534
|
+
operation_status=OperationStatus.CANCELLED,
|
|
2535
|
+
is_start_event=False,
|
|
2536
|
+
is_end_event=True,
|
|
2537
|
+
has_result=True,
|
|
2538
|
+
),
|
|
2539
|
+
"StepStarted": HistoryEventTypeConfig(
|
|
2540
|
+
operation_type=OperationType.STEP,
|
|
2541
|
+
operation_status=OperationStatus.STARTED,
|
|
2542
|
+
is_start_event=True,
|
|
2543
|
+
is_end_event=False,
|
|
2544
|
+
has_result=False,
|
|
2545
|
+
),
|
|
2546
|
+
"StepFailed": HistoryEventTypeConfig(
|
|
2547
|
+
operation_type=OperationType.STEP,
|
|
2548
|
+
operation_status=OperationStatus.FAILED,
|
|
2549
|
+
is_start_event=False,
|
|
2550
|
+
is_end_event=True,
|
|
2551
|
+
has_result=True,
|
|
2552
|
+
),
|
|
2553
|
+
"StepSucceeded": HistoryEventTypeConfig(
|
|
2554
|
+
operation_type=OperationType.STEP,
|
|
2555
|
+
operation_status=OperationStatus.SUCCEEDED,
|
|
2556
|
+
is_start_event=False,
|
|
2557
|
+
is_end_event=True,
|
|
2558
|
+
has_result=True,
|
|
2559
|
+
),
|
|
2560
|
+
"WaitStarted": HistoryEventTypeConfig(
|
|
2561
|
+
operation_type=OperationType.WAIT,
|
|
2562
|
+
operation_status=OperationStatus.STARTED,
|
|
2563
|
+
is_start_event=True,
|
|
2564
|
+
is_end_event=False,
|
|
2565
|
+
has_result=True,
|
|
2566
|
+
),
|
|
2567
|
+
"WaitSucceeded": HistoryEventTypeConfig(
|
|
2568
|
+
operation_type=OperationType.WAIT,
|
|
2569
|
+
operation_status=OperationStatus.SUCCEEDED,
|
|
2570
|
+
is_start_event=False,
|
|
2571
|
+
is_end_event=True,
|
|
2572
|
+
has_result=True,
|
|
2573
|
+
),
|
|
2574
|
+
"WaitCancelled": HistoryEventTypeConfig(
|
|
2575
|
+
operation_type=OperationType.WAIT,
|
|
2576
|
+
operation_status=OperationStatus.CANCELLED,
|
|
2577
|
+
is_start_event=False,
|
|
2578
|
+
is_end_event=True,
|
|
2579
|
+
has_result=True,
|
|
2580
|
+
),
|
|
2581
|
+
# TODO: add support for populating invocation information from InvocationCompleted event
|
|
2582
|
+
"InvocationCompleted": HistoryEventTypeConfig(
|
|
2583
|
+
operation_type=None,
|
|
2584
|
+
operation_status=None,
|
|
2585
|
+
is_start_event=False,
|
|
2586
|
+
is_end_event=False,
|
|
2587
|
+
has_result=True,
|
|
2588
|
+
),
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
|
|
2592
|
+
def events_to_operations(events: list[Event]) -> list[Operation]:
|
|
2593
|
+
"""Convert a list of history events into operations.
|
|
2594
|
+
|
|
2595
|
+
This function processes raw history events and groups them by operation ID,
|
|
2596
|
+
creating comprehensive operation objects following the TypeScript pattern from
|
|
2597
|
+
aws-durable-execution-sdk-js-testing.
|
|
2598
|
+
|
|
2599
|
+
Multiple events for the same operation_id are merged together, with each event
|
|
2600
|
+
contributing its specific fields (e.g., CallbackStarted provides callback_id,
|
|
2601
|
+
CallbackSucceeded provides result).
|
|
2602
|
+
|
|
2603
|
+
Args:
|
|
2604
|
+
events: List of history events to process
|
|
2605
|
+
|
|
2606
|
+
Returns:
|
|
2607
|
+
List of operations, one per unique operation ID
|
|
2608
|
+
|
|
2609
|
+
Raises:
|
|
2610
|
+
InvalidParameterValueException: When required fields are missing from an event
|
|
2611
|
+
|
|
2612
|
+
Note:
|
|
2613
|
+
InvocationCompleted events are currently skipped as they don't represent
|
|
2614
|
+
operations. Future enhancement: populate invocation information from these
|
|
2615
|
+
events (TODO).
|
|
2616
|
+
"""
|
|
2617
|
+
operations_map: dict[str, Operation] = {}
|
|
2618
|
+
|
|
2619
|
+
for event in events:
|
|
2620
|
+
if not event.event_type:
|
|
2621
|
+
msg = "Missing required 'event_type' field in event"
|
|
2622
|
+
raise InvalidParameterValueException(msg)
|
|
2623
|
+
|
|
2624
|
+
# Get event type configuration
|
|
2625
|
+
event_config: HistoryEventTypeConfig | None = HISTORY_EVENT_TYPES.get(
|
|
2626
|
+
event.event_type
|
|
2627
|
+
)
|
|
2628
|
+
if not event_config:
|
|
2629
|
+
msg = f"Unknown event type: {event.event_type}"
|
|
2630
|
+
raise InvalidParameterValueException(msg)
|
|
2631
|
+
|
|
2632
|
+
# TODO: add support for populating invocation information from InvocationCompleted event
|
|
2633
|
+
if event.event_type == "InvocationCompleted":
|
|
2634
|
+
continue
|
|
2635
|
+
|
|
2636
|
+
if not event.operation_id:
|
|
2637
|
+
msg = f"Missing required 'operation_id' field in event {event.event_id}"
|
|
2638
|
+
raise InvalidParameterValueException(msg)
|
|
2639
|
+
|
|
2640
|
+
# Get previous operation if it exists
|
|
2641
|
+
previous_operation: Operation | None = operations_map.get(event.operation_id)
|
|
2642
|
+
|
|
2643
|
+
# Get operation type and status from configuration
|
|
2644
|
+
operation_type: OperationType = (
|
|
2645
|
+
event_config.operation_type or OperationType.EXECUTION
|
|
2646
|
+
)
|
|
2647
|
+
status: OperationStatus = (
|
|
2648
|
+
event_config.operation_status or OperationStatus.PENDING
|
|
2649
|
+
)
|
|
2650
|
+
|
|
2651
|
+
# Parse sub_type
|
|
2652
|
+
sub_type: OperationSubType | None = None
|
|
2653
|
+
if event.sub_type:
|
|
2654
|
+
try:
|
|
2655
|
+
sub_type = OperationSubType(event.sub_type)
|
|
2656
|
+
except ValueError as e:
|
|
2657
|
+
raise InvalidParameterValueException(str(e)) from e
|
|
2658
|
+
|
|
2659
|
+
# Create base operation
|
|
2660
|
+
operation = Operation(
|
|
2661
|
+
operation_id=event.operation_id,
|
|
2662
|
+
operation_type=operation_type,
|
|
2663
|
+
status=status,
|
|
2664
|
+
name=event.name,
|
|
2665
|
+
parent_id=event.parent_id,
|
|
2666
|
+
sub_type=sub_type,
|
|
2667
|
+
start_timestamp=datetime.datetime.now(tz=datetime.timezone.utc),
|
|
2668
|
+
)
|
|
2669
|
+
|
|
2670
|
+
# Merge with previous operation if it exists
|
|
2671
|
+
# Most fields are immutable, so they get preserved from previous events
|
|
2672
|
+
if previous_operation:
|
|
2673
|
+
operation = replace(
|
|
2674
|
+
operation,
|
|
2675
|
+
name=operation.name or previous_operation.name,
|
|
2676
|
+
parent_id=operation.parent_id or previous_operation.parent_id,
|
|
2677
|
+
sub_type=operation.sub_type or previous_operation.sub_type,
|
|
2678
|
+
start_timestamp=previous_operation.start_timestamp,
|
|
2679
|
+
end_timestamp=previous_operation.end_timestamp,
|
|
2680
|
+
execution_details=previous_operation.execution_details,
|
|
2681
|
+
context_details=previous_operation.context_details,
|
|
2682
|
+
step_details=previous_operation.step_details,
|
|
2683
|
+
wait_details=previous_operation.wait_details,
|
|
2684
|
+
callback_details=previous_operation.callback_details,
|
|
2685
|
+
chained_invoke_details=previous_operation.chained_invoke_details,
|
|
2686
|
+
)
|
|
2687
|
+
|
|
2688
|
+
# Set timestamps based on event configuration
|
|
2689
|
+
if event_config.is_start_event:
|
|
2690
|
+
operation = replace(operation, start_timestamp=event.event_timestamp)
|
|
2691
|
+
if event_config.is_end_event:
|
|
2692
|
+
operation = replace(operation, end_timestamp=event.event_timestamp)
|
|
2693
|
+
|
|
2694
|
+
# Add operation-specific details incrementally
|
|
2695
|
+
# Each event type contributes only the fields it has
|
|
2696
|
+
|
|
2697
|
+
# EXECUTION details
|
|
2698
|
+
if (
|
|
2699
|
+
operation_type == OperationType.EXECUTION
|
|
2700
|
+
and event.execution_started_details
|
|
2701
|
+
and event.execution_started_details.input
|
|
2702
|
+
):
|
|
2703
|
+
operation = replace(
|
|
2704
|
+
operation,
|
|
2705
|
+
execution_details=ExecutionDetails(
|
|
2706
|
+
input_payload=event.execution_started_details.input.payload
|
|
2707
|
+
),
|
|
2708
|
+
)
|
|
2709
|
+
|
|
2710
|
+
# CALLBACK details - merge callback_id, result, and error from different events
|
|
2711
|
+
if operation_type == OperationType.CALLBACK:
|
|
2712
|
+
existing_cb: CallbackDetails | None = operation.callback_details
|
|
2713
|
+
callback_id: str = existing_cb.callback_id if existing_cb else ""
|
|
2714
|
+
result: str | None = existing_cb.result if existing_cb else None
|
|
2715
|
+
error: ErrorObject | None = existing_cb.error if existing_cb else None
|
|
2716
|
+
|
|
2717
|
+
# CallbackStarted provides callback_id
|
|
2718
|
+
if event.callback_started_details:
|
|
2719
|
+
callback_id = event.callback_started_details.callback_id or callback_id
|
|
2720
|
+
|
|
2721
|
+
# CallbackSucceeded provides result
|
|
2722
|
+
if (
|
|
2723
|
+
event.callback_succeeded_details
|
|
2724
|
+
and event.callback_succeeded_details.result
|
|
2725
|
+
):
|
|
2726
|
+
result = event.callback_succeeded_details.result.payload
|
|
2727
|
+
|
|
2728
|
+
# CallbackFailed provides error
|
|
2729
|
+
if event.callback_failed_details and event.callback_failed_details.error:
|
|
2730
|
+
error = event.callback_failed_details.error.payload
|
|
2731
|
+
|
|
2732
|
+
# CallbackTimedOut provides error
|
|
2733
|
+
if (
|
|
2734
|
+
event.callback_timed_out_details
|
|
2735
|
+
and event.callback_timed_out_details.error
|
|
2736
|
+
):
|
|
2737
|
+
error = event.callback_timed_out_details.error.payload
|
|
2738
|
+
|
|
2739
|
+
operation = replace(
|
|
2740
|
+
operation,
|
|
2741
|
+
callback_details=CallbackDetails(
|
|
2742
|
+
callback_id=callback_id,
|
|
2743
|
+
result=result,
|
|
2744
|
+
error=error,
|
|
2745
|
+
),
|
|
2746
|
+
)
|
|
2747
|
+
|
|
2748
|
+
# STEP details - only update if this event type has result data
|
|
2749
|
+
if operation_type == OperationType.STEP and event_config.has_result:
|
|
2750
|
+
existing_step: StepDetails | None = operation.step_details
|
|
2751
|
+
result_val: str | None = existing_step.result if existing_step else None
|
|
2752
|
+
error_val: ErrorObject | None = (
|
|
2753
|
+
existing_step.error if existing_step else None
|
|
2754
|
+
)
|
|
2755
|
+
attempt: int = existing_step.attempt if existing_step else 0
|
|
2756
|
+
next_attempt_ts: datetime.datetime | None = (
|
|
2757
|
+
existing_step.next_attempt_timestamp if existing_step else None
|
|
2758
|
+
)
|
|
2759
|
+
|
|
2760
|
+
# StepSucceeded provides result
|
|
2761
|
+
if event.step_succeeded_details:
|
|
2762
|
+
if event.step_succeeded_details.result:
|
|
2763
|
+
result_val = event.step_succeeded_details.result.payload
|
|
2764
|
+
if event.step_succeeded_details.retry_details:
|
|
2765
|
+
attempt = event.step_succeeded_details.retry_details.current_attempt
|
|
2766
|
+
|
|
2767
|
+
# StepFailed provides error and retry details
|
|
2768
|
+
if event.step_failed_details:
|
|
2769
|
+
if event.step_failed_details.error:
|
|
2770
|
+
error_val = event.step_failed_details.error.payload
|
|
2771
|
+
if event.step_failed_details.retry_details:
|
|
2772
|
+
attempt = event.step_failed_details.retry_details.current_attempt
|
|
2773
|
+
if (
|
|
2774
|
+
event.step_failed_details.retry_details.next_attempt_delay_seconds
|
|
2775
|
+
is not None
|
|
2776
|
+
):
|
|
2777
|
+
next_attempt_ts = event.event_timestamp + datetime.timedelta(
|
|
2778
|
+
seconds=event.step_failed_details.retry_details.next_attempt_delay_seconds
|
|
2779
|
+
)
|
|
2780
|
+
|
|
2781
|
+
operation = replace(
|
|
2782
|
+
operation,
|
|
2783
|
+
step_details=StepDetails(
|
|
2784
|
+
result=result_val,
|
|
2785
|
+
error=error_val,
|
|
2786
|
+
attempt=attempt,
|
|
2787
|
+
next_attempt_timestamp=next_attempt_ts,
|
|
2788
|
+
),
|
|
2789
|
+
)
|
|
2790
|
+
|
|
2791
|
+
# WAIT details
|
|
2792
|
+
if operation_type == OperationType.WAIT and event.wait_started_details:
|
|
2793
|
+
operation = replace(
|
|
2794
|
+
operation,
|
|
2795
|
+
wait_details=WaitDetails(
|
|
2796
|
+
scheduled_end_timestamp=event.wait_started_details.scheduled_end_timestamp
|
|
2797
|
+
),
|
|
2798
|
+
)
|
|
2799
|
+
|
|
2800
|
+
# CONTEXT details - only update if this event type has result data (matching TypeScript hasResult)
|
|
2801
|
+
if operation_type == OperationType.CONTEXT and event_config.has_result:
|
|
2802
|
+
if (
|
|
2803
|
+
event.context_succeeded_details
|
|
2804
|
+
and event.context_succeeded_details.result
|
|
2805
|
+
):
|
|
2806
|
+
operation = replace(
|
|
2807
|
+
operation,
|
|
2808
|
+
context_details=ContextDetails(
|
|
2809
|
+
result=event.context_succeeded_details.result.payload,
|
|
2810
|
+
error=None,
|
|
2811
|
+
),
|
|
2812
|
+
)
|
|
2813
|
+
elif event.context_failed_details and event.context_failed_details.error:
|
|
2814
|
+
operation = replace(
|
|
2815
|
+
operation,
|
|
2816
|
+
context_details=ContextDetails(
|
|
2817
|
+
result=None,
|
|
2818
|
+
error=event.context_failed_details.error.payload,
|
|
2819
|
+
),
|
|
2820
|
+
)
|
|
2821
|
+
|
|
2822
|
+
# CHAINED_INVOKE details - only update if this event type has result data (matching TypeScript hasResult)
|
|
2823
|
+
if operation_type == OperationType.CHAINED_INVOKE and event_config.has_result:
|
|
2824
|
+
if (
|
|
2825
|
+
event.chained_invoke_succeeded_details
|
|
2826
|
+
and event.chained_invoke_succeeded_details.result
|
|
2827
|
+
):
|
|
2828
|
+
operation = replace(
|
|
2829
|
+
operation,
|
|
2830
|
+
chained_invoke_details=ChainedInvokeDetails(
|
|
2831
|
+
result=event.chained_invoke_succeeded_details.result.payload,
|
|
2832
|
+
error=None,
|
|
2833
|
+
),
|
|
2834
|
+
)
|
|
2835
|
+
elif (
|
|
2836
|
+
event.chained_invoke_failed_details
|
|
2837
|
+
and event.chained_invoke_failed_details.error
|
|
2838
|
+
):
|
|
2839
|
+
operation = replace(
|
|
2840
|
+
operation,
|
|
2841
|
+
chained_invoke_details=ChainedInvokeDetails(
|
|
2842
|
+
result=None,
|
|
2843
|
+
error=event.chained_invoke_failed_details.error.payload,
|
|
2844
|
+
),
|
|
2845
|
+
)
|
|
2846
|
+
|
|
2847
|
+
# Store in map
|
|
2848
|
+
operations_map[event.operation_id] = operation
|
|
2849
|
+
|
|
2850
|
+
return list(operations_map.values())
|
|
2851
|
+
|
|
2852
|
+
|
|
2853
|
+
@dataclass(frozen=True)
|
|
2854
|
+
class GetDurableExecutionHistoryRequest:
|
|
2855
|
+
"""Request to get durable execution history."""
|
|
2856
|
+
|
|
2857
|
+
durable_execution_arn: str
|
|
2858
|
+
include_execution_data: bool | None = None
|
|
2859
|
+
reverse_order: bool | None = None
|
|
2860
|
+
marker: str | None = None
|
|
2861
|
+
max_items: int = 0
|
|
2862
|
+
|
|
2863
|
+
@classmethod
|
|
2864
|
+
def from_dict(cls, data: dict) -> GetDurableExecutionHistoryRequest:
|
|
2865
|
+
return cls(
|
|
2866
|
+
durable_execution_arn=data["DurableExecutionArn"],
|
|
2867
|
+
include_execution_data=data.get("IncludeExecutionData"),
|
|
2868
|
+
reverse_order=data.get("ReverseOrder"),
|
|
2869
|
+
marker=data.get("Marker"),
|
|
2870
|
+
max_items=data.get("MaxItems", 0),
|
|
2871
|
+
)
|
|
2872
|
+
|
|
2873
|
+
def to_dict(self) -> dict[str, Any]:
|
|
2874
|
+
result: dict[str, Any] = {"DurableExecutionArn": self.durable_execution_arn}
|
|
2875
|
+
if self.include_execution_data is not None:
|
|
2876
|
+
result["IncludeExecutionData"] = self.include_execution_data
|
|
2877
|
+
if self.reverse_order is not None:
|
|
2878
|
+
result["ReverseOrder"] = self.reverse_order
|
|
2879
|
+
if self.marker is not None:
|
|
2880
|
+
result["Marker"] = self.marker
|
|
2881
|
+
if self.max_items is not None:
|
|
2882
|
+
result["MaxItems"] = self.max_items
|
|
2883
|
+
return result
|
|
2884
|
+
|
|
2885
|
+
|
|
2886
|
+
@dataclass(frozen=True)
|
|
2887
|
+
class GetDurableExecutionHistoryResponse:
|
|
2888
|
+
"""Response containing durable execution history events."""
|
|
2889
|
+
|
|
2890
|
+
events: list[Event]
|
|
2891
|
+
next_marker: str | None = None
|
|
2892
|
+
|
|
2893
|
+
@classmethod
|
|
2894
|
+
def from_dict(cls, data: dict) -> GetDurableExecutionHistoryResponse:
|
|
2895
|
+
events = [Event.from_dict(event_data) for event_data in data.get("Events", [])]
|
|
2896
|
+
return cls(
|
|
2897
|
+
events=events,
|
|
2898
|
+
next_marker=data.get("NextMarker"),
|
|
2899
|
+
)
|
|
2900
|
+
|
|
2901
|
+
def to_dict(self) -> dict[str, Any]:
|
|
2902
|
+
result: dict[str, Any] = {"Events": [event.to_dict() for event in self.events]}
|
|
2903
|
+
if self.next_marker is not None:
|
|
2904
|
+
result["NextMarker"] = self.next_marker
|
|
2905
|
+
return result
|
|
2906
|
+
|
|
2907
|
+
|
|
2908
|
+
@dataclass(frozen=True)
|
|
2909
|
+
class ListDurableExecutionsByFunctionRequest:
|
|
2910
|
+
"""Request to list durable executions by function."""
|
|
2911
|
+
|
|
2912
|
+
function_name: str
|
|
2913
|
+
qualifier: str | None = None
|
|
2914
|
+
durable_execution_name: str | None = None
|
|
2915
|
+
status_filter: list[str] | None = None
|
|
2916
|
+
started_after: str | None = None
|
|
2917
|
+
started_before: str | None = None
|
|
2918
|
+
marker: str | None = None
|
|
2919
|
+
max_items: int = 0
|
|
2920
|
+
reverse_order: bool | None = None
|
|
2921
|
+
|
|
2922
|
+
@classmethod
|
|
2923
|
+
def from_dict(cls, data: dict) -> ListDurableExecutionsByFunctionRequest:
|
|
2924
|
+
# Handle query parameters that may be lists
|
|
2925
|
+
function_name = data.get("FunctionName")
|
|
2926
|
+
if isinstance(function_name, list):
|
|
2927
|
+
function_name = function_name[0] if function_name else ""
|
|
2928
|
+
elif not function_name:
|
|
2929
|
+
function_name = ""
|
|
2930
|
+
|
|
2931
|
+
qualifier = data.get("Qualifier") or data.get("functionVersion")
|
|
2932
|
+
if isinstance(qualifier, list):
|
|
2933
|
+
qualifier = qualifier[0] if qualifier else None
|
|
2934
|
+
|
|
2935
|
+
durable_execution_name = data.get("DurableExecutionName") or data.get(
|
|
2936
|
+
"executionName"
|
|
2937
|
+
)
|
|
2938
|
+
if isinstance(durable_execution_name, list):
|
|
2939
|
+
durable_execution_name = (
|
|
2940
|
+
durable_execution_name[0] if durable_execution_name else None
|
|
2941
|
+
)
|
|
2942
|
+
|
|
2943
|
+
status_filter = data.get("StatusFilter") or data.get("statusFilter")
|
|
2944
|
+
if isinstance(status_filter, list):
|
|
2945
|
+
status_filter = status_filter if status_filter else None
|
|
2946
|
+
elif status_filter:
|
|
2947
|
+
status_filter = [status_filter]
|
|
2948
|
+
|
|
2949
|
+
started_after = data.get("StartedAfter") or data.get("startedAfter")
|
|
2950
|
+
if isinstance(started_after, list):
|
|
2951
|
+
started_after = started_after[0] if started_after else None
|
|
2952
|
+
|
|
2953
|
+
started_before = data.get("StartedBefore") or data.get("startedBefore")
|
|
2954
|
+
if isinstance(started_before, list):
|
|
2955
|
+
started_before = started_before[0] if started_before else None
|
|
2956
|
+
|
|
2957
|
+
marker = data.get("Marker") or data.get("marker")
|
|
2958
|
+
if isinstance(marker, list):
|
|
2959
|
+
marker = marker[0] if marker else None
|
|
2960
|
+
|
|
2961
|
+
max_items = data.get("MaxItems") or data.get("maxItems", 0)
|
|
2962
|
+
if isinstance(max_items, list):
|
|
2963
|
+
max_items = int(max_items[0]) if max_items else 0
|
|
2964
|
+
|
|
2965
|
+
reverse_order = data.get("ReverseOrder") or data.get("reverseOrder")
|
|
2966
|
+
if isinstance(reverse_order, list):
|
|
2967
|
+
reverse_order = (
|
|
2968
|
+
reverse_order[0].lower() in ("true", "1", "yes")
|
|
2969
|
+
if reverse_order
|
|
2970
|
+
else None
|
|
2971
|
+
)
|
|
2972
|
+
elif isinstance(reverse_order, str):
|
|
2973
|
+
reverse_order = reverse_order.lower() in ("true", "1", "yes")
|
|
2974
|
+
|
|
2975
|
+
return cls(
|
|
2976
|
+
function_name=function_name,
|
|
2977
|
+
qualifier=qualifier,
|
|
2978
|
+
durable_execution_name=durable_execution_name,
|
|
2979
|
+
status_filter=status_filter,
|
|
2980
|
+
started_after=started_after,
|
|
2981
|
+
started_before=started_before,
|
|
2982
|
+
marker=marker,
|
|
2983
|
+
max_items=max_items,
|
|
2984
|
+
reverse_order=reverse_order,
|
|
2985
|
+
)
|
|
2986
|
+
|
|
2987
|
+
def to_dict(self) -> dict[str, Any]:
|
|
2988
|
+
result: dict[str, Any] = {"FunctionName": self.function_name}
|
|
2989
|
+
if self.qualifier is not None:
|
|
2990
|
+
result["Qualifier"] = self.qualifier
|
|
2991
|
+
if self.durable_execution_name is not None:
|
|
2992
|
+
result["DurableExecutionName"] = self.durable_execution_name
|
|
2993
|
+
if self.status_filter is not None:
|
|
2994
|
+
result["StatusFilter"] = self.status_filter
|
|
2995
|
+
if self.started_after is not None:
|
|
2996
|
+
result["StartedAfter"] = self.started_after
|
|
2997
|
+
if self.started_before is not None:
|
|
2998
|
+
result["StartedBefore"] = self.started_before
|
|
2999
|
+
if self.marker is not None:
|
|
3000
|
+
result["Marker"] = self.marker
|
|
3001
|
+
if self.max_items is not None:
|
|
3002
|
+
result["MaxItems"] = self.max_items
|
|
3003
|
+
if self.reverse_order is not None:
|
|
3004
|
+
result["ReverseOrder"] = self.reverse_order
|
|
3005
|
+
return result
|
|
3006
|
+
|
|
3007
|
+
|
|
3008
|
+
@dataclass(frozen=True)
|
|
3009
|
+
class ListDurableExecutionsByFunctionResponse:
|
|
3010
|
+
"""Response containing list of durable executions by function."""
|
|
3011
|
+
|
|
3012
|
+
durable_executions: list[Execution]
|
|
3013
|
+
next_marker: str | None = None
|
|
3014
|
+
|
|
3015
|
+
@classmethod
|
|
3016
|
+
def from_dict(cls, data: dict) -> ListDurableExecutionsByFunctionResponse:
|
|
3017
|
+
executions = [
|
|
3018
|
+
Execution.from_dict(exec_data)
|
|
3019
|
+
for exec_data in data.get("DurableExecutions", [])
|
|
3020
|
+
]
|
|
3021
|
+
return cls(
|
|
3022
|
+
durable_executions=executions,
|
|
3023
|
+
next_marker=data.get("NextMarker"),
|
|
3024
|
+
)
|
|
3025
|
+
|
|
3026
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3027
|
+
result: dict[str, Any] = {
|
|
3028
|
+
"DurableExecutions": [exe.to_dict() for exe in self.durable_executions]
|
|
3029
|
+
}
|
|
3030
|
+
if self.next_marker is not None:
|
|
3031
|
+
result["NextMarker"] = self.next_marker
|
|
3032
|
+
return result
|
|
3033
|
+
|
|
3034
|
+
|
|
3035
|
+
# endregion history_models
|
|
3036
|
+
|
|
3037
|
+
|
|
3038
|
+
# region callback_models
|
|
3039
|
+
# Callback-related models
|
|
3040
|
+
@dataclass(frozen=True)
|
|
3041
|
+
class SendDurableExecutionCallbackSuccessRequest:
|
|
3042
|
+
"""Request to send callback success."""
|
|
3043
|
+
|
|
3044
|
+
callback_id: str
|
|
3045
|
+
result: bytes | None = None
|
|
3046
|
+
|
|
3047
|
+
@classmethod
|
|
3048
|
+
def from_dict(cls, data: dict) -> SendDurableExecutionCallbackSuccessRequest:
|
|
3049
|
+
return cls(
|
|
3050
|
+
callback_id=data["CallbackId"],
|
|
3051
|
+
result=data.get("Result"),
|
|
3052
|
+
)
|
|
3053
|
+
|
|
3054
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3055
|
+
result: dict[str, Any] = {"CallbackId": self.callback_id}
|
|
3056
|
+
if self.result is not None:
|
|
3057
|
+
result["Result"] = self.result
|
|
3058
|
+
return result
|
|
3059
|
+
|
|
3060
|
+
|
|
3061
|
+
@dataclass(frozen=True)
|
|
3062
|
+
class SendDurableExecutionCallbackSuccessResponse:
|
|
3063
|
+
"""Response from sending callback success."""
|
|
3064
|
+
|
|
3065
|
+
|
|
3066
|
+
@dataclass(frozen=True)
|
|
3067
|
+
class SendDurableExecutionCallbackFailureRequest:
|
|
3068
|
+
"""Request to send callback failure."""
|
|
3069
|
+
|
|
3070
|
+
callback_id: str
|
|
3071
|
+
error: ErrorObject | None = None
|
|
3072
|
+
|
|
3073
|
+
@classmethod
|
|
3074
|
+
def from_dict(
|
|
3075
|
+
cls, data: dict, callback_id: str
|
|
3076
|
+
) -> SendDurableExecutionCallbackFailureRequest:
|
|
3077
|
+
error = ErrorObject.from_dict(data) if data else None
|
|
3078
|
+
|
|
3079
|
+
return cls(
|
|
3080
|
+
callback_id=callback_id,
|
|
3081
|
+
error=error,
|
|
3082
|
+
)
|
|
3083
|
+
|
|
3084
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3085
|
+
result: dict[str, Any] = {"CallbackId": self.callback_id}
|
|
3086
|
+
if self.error is not None:
|
|
3087
|
+
result["Error"] = self.error.to_dict()
|
|
3088
|
+
return result
|
|
3089
|
+
|
|
3090
|
+
|
|
3091
|
+
@dataclass(frozen=True)
|
|
3092
|
+
class SendDurableExecutionCallbackFailureResponse:
|
|
3093
|
+
"""Response from sending callback failure."""
|
|
3094
|
+
|
|
3095
|
+
|
|
3096
|
+
@dataclass(frozen=True)
|
|
3097
|
+
class SendDurableExecutionCallbackHeartbeatRequest:
|
|
3098
|
+
"""Request to send callback heartbeat."""
|
|
3099
|
+
|
|
3100
|
+
callback_id: str
|
|
3101
|
+
|
|
3102
|
+
@classmethod
|
|
3103
|
+
def from_dict(cls, data: dict) -> SendDurableExecutionCallbackHeartbeatRequest:
|
|
3104
|
+
return cls(callback_id=data["CallbackId"])
|
|
3105
|
+
|
|
3106
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3107
|
+
return {"CallbackId": self.callback_id}
|
|
3108
|
+
|
|
3109
|
+
|
|
3110
|
+
@dataclass(frozen=True)
|
|
3111
|
+
class SendDurableExecutionCallbackHeartbeatResponse:
|
|
3112
|
+
"""Response from sending callback heartbeat."""
|
|
3113
|
+
|
|
3114
|
+
|
|
3115
|
+
# endregion callback_models
|
|
3116
|
+
|
|
3117
|
+
|
|
3118
|
+
# region checkpoint_models
|
|
3119
|
+
# Checkpoint-related models
|
|
3120
|
+
@dataclass(frozen=True)
|
|
3121
|
+
class CheckpointUpdatedExecutionState:
|
|
3122
|
+
"""Updated execution state from checkpoint."""
|
|
3123
|
+
|
|
3124
|
+
operations: list[Operation]
|
|
3125
|
+
next_marker: str | None = None
|
|
3126
|
+
|
|
3127
|
+
@classmethod
|
|
3128
|
+
def from_dict(cls, data: dict) -> CheckpointUpdatedExecutionState:
|
|
3129
|
+
operations = [
|
|
3130
|
+
Operation.from_dict(op_data) for op_data in data.get("Operations", [])
|
|
3131
|
+
]
|
|
3132
|
+
return cls(
|
|
3133
|
+
operations=operations,
|
|
3134
|
+
next_marker=data.get("NextMarker"),
|
|
3135
|
+
)
|
|
3136
|
+
|
|
3137
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3138
|
+
result: dict[str, Any] = {
|
|
3139
|
+
"Operations": [op.to_dict() for op in self.operations]
|
|
3140
|
+
}
|
|
3141
|
+
if self.next_marker is not None:
|
|
3142
|
+
result["NextMarker"] = self.next_marker
|
|
3143
|
+
return result
|
|
3144
|
+
|
|
3145
|
+
|
|
3146
|
+
@dataclass(frozen=True)
|
|
3147
|
+
class CheckpointDurableExecutionRequest:
|
|
3148
|
+
"""Request to checkpoint a durable execution."""
|
|
3149
|
+
|
|
3150
|
+
durable_execution_arn: str
|
|
3151
|
+
checkpoint_token: str
|
|
3152
|
+
updates: list[OperationUpdate] | None = None
|
|
3153
|
+
client_token: str | None = None
|
|
3154
|
+
|
|
3155
|
+
@classmethod
|
|
3156
|
+
def from_dict(
|
|
3157
|
+
cls, data: dict, durable_execution_arn: str
|
|
3158
|
+
) -> CheckpointDurableExecutionRequest:
|
|
3159
|
+
updates = None
|
|
3160
|
+
if updates_data := data.get("Updates"):
|
|
3161
|
+
updates = []
|
|
3162
|
+
for update_data in updates_data:
|
|
3163
|
+
# Map dictionary fields to OperationUpdate constructor parameters
|
|
3164
|
+
operation_update = OperationUpdate(
|
|
3165
|
+
operation_id=update_data["Id"],
|
|
3166
|
+
operation_type=OperationType(update_data["Type"]),
|
|
3167
|
+
action=OperationAction(update_data["Action"]),
|
|
3168
|
+
parent_id=update_data.get("ParentId"),
|
|
3169
|
+
name=update_data.get("Name"),
|
|
3170
|
+
sub_type=OperationSubType(update_data["SubType"])
|
|
3171
|
+
if update_data.get("SubType")
|
|
3172
|
+
else None,
|
|
3173
|
+
payload=update_data.get("Payload"),
|
|
3174
|
+
error=ErrorObject.from_dict(update_data["Error"])
|
|
3175
|
+
if update_data.get("Error")
|
|
3176
|
+
else None,
|
|
3177
|
+
context_options=ContextOptions.from_dict(
|
|
3178
|
+
update_data["ContextOptions"]
|
|
3179
|
+
)
|
|
3180
|
+
if update_data.get("ContextOptions")
|
|
3181
|
+
else None,
|
|
3182
|
+
step_options=StepOptions.from_dict(update_data["StepOptions"])
|
|
3183
|
+
if update_data.get("StepOptions")
|
|
3184
|
+
else None,
|
|
3185
|
+
wait_options=WaitOptions.from_dict(update_data["WaitOptions"])
|
|
3186
|
+
if update_data.get("WaitOptions")
|
|
3187
|
+
else None,
|
|
3188
|
+
callback_options=CallbackOptions.from_dict(
|
|
3189
|
+
update_data["CallbackOptions"]
|
|
3190
|
+
)
|
|
3191
|
+
if update_data.get("CallbackOptions")
|
|
3192
|
+
else None,
|
|
3193
|
+
chained_invoke_options=ChainedInvokeOptions.from_dict(
|
|
3194
|
+
update_data["ChainedInvokeOptions"]
|
|
3195
|
+
)
|
|
3196
|
+
if update_data.get("ChainedInvokeOptions")
|
|
3197
|
+
else None,
|
|
3198
|
+
)
|
|
3199
|
+
updates.append(operation_update)
|
|
3200
|
+
|
|
3201
|
+
return cls(
|
|
3202
|
+
durable_execution_arn=durable_execution_arn,
|
|
3203
|
+
checkpoint_token=data["CheckpointToken"],
|
|
3204
|
+
updates=updates,
|
|
3205
|
+
client_token=data.get("ClientToken"),
|
|
3206
|
+
)
|
|
3207
|
+
|
|
3208
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3209
|
+
result: dict[str, Any] = {
|
|
3210
|
+
"DurableExecutionArn": self.durable_execution_arn,
|
|
3211
|
+
"CheckpointToken": self.checkpoint_token,
|
|
3212
|
+
}
|
|
3213
|
+
if self.updates is not None:
|
|
3214
|
+
result["Updates"] = [update.to_dict() for update in self.updates]
|
|
3215
|
+
if self.client_token is not None:
|
|
3216
|
+
result["ClientToken"] = self.client_token
|
|
3217
|
+
return result
|
|
3218
|
+
|
|
3219
|
+
|
|
3220
|
+
@dataclass(frozen=True)
|
|
3221
|
+
class CheckpointDurableExecutionResponse:
|
|
3222
|
+
"""Response from checkpointing a durable execution."""
|
|
3223
|
+
|
|
3224
|
+
checkpoint_token: str
|
|
3225
|
+
new_execution_state: CheckpointUpdatedExecutionState | None = None
|
|
3226
|
+
|
|
3227
|
+
@classmethod
|
|
3228
|
+
def from_dict(cls, data: dict) -> CheckpointDurableExecutionResponse:
|
|
3229
|
+
new_execution_state = None
|
|
3230
|
+
if state_data := data.get("NewExecutionState"):
|
|
3231
|
+
new_execution_state = CheckpointUpdatedExecutionState.from_dict(state_data)
|
|
3232
|
+
|
|
3233
|
+
return cls(
|
|
3234
|
+
checkpoint_token=data["CheckpointToken"],
|
|
3235
|
+
new_execution_state=new_execution_state,
|
|
3236
|
+
)
|
|
3237
|
+
|
|
3238
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3239
|
+
result: dict[str, Any] = {"CheckpointToken": self.checkpoint_token}
|
|
3240
|
+
if self.new_execution_state is not None:
|
|
3241
|
+
result["NewExecutionState"] = self.new_execution_state.to_dict()
|
|
3242
|
+
return result
|
|
3243
|
+
|
|
3244
|
+
|
|
3245
|
+
# endregion checkpoint_models
|
|
3246
|
+
|
|
3247
|
+
|
|
3248
|
+
# region error_models
|
|
3249
|
+
# Error response structure for consistent error handling
|
|
3250
|
+
@dataclass(frozen=True)
|
|
3251
|
+
class ErrorResponse:
|
|
3252
|
+
"""Structured error response for web service operations."""
|
|
3253
|
+
|
|
3254
|
+
error_type: str
|
|
3255
|
+
error_message: str
|
|
3256
|
+
error_code: str | None = None
|
|
3257
|
+
request_id: str | None = None
|
|
3258
|
+
|
|
3259
|
+
@classmethod
|
|
3260
|
+
def from_dict(cls, data: dict) -> ErrorResponse:
|
|
3261
|
+
"""Create ErrorResponse from dictionary.
|
|
3262
|
+
|
|
3263
|
+
Args:
|
|
3264
|
+
data: Dictionary containing error data
|
|
3265
|
+
|
|
3266
|
+
Returns:
|
|
3267
|
+
ErrorResponse: The error response object
|
|
3268
|
+
"""
|
|
3269
|
+
error_data = data.get("error", data) # Support both nested and flat structures
|
|
3270
|
+
return cls(
|
|
3271
|
+
error_type=error_data["type"],
|
|
3272
|
+
error_message=error_data["message"],
|
|
3273
|
+
error_code=error_data.get("code"),
|
|
3274
|
+
request_id=error_data.get("requestId"),
|
|
3275
|
+
)
|
|
3276
|
+
|
|
3277
|
+
def to_dict(self) -> dict[str, Any]:
|
|
3278
|
+
"""Convert ErrorResponse to dictionary.
|
|
3279
|
+
|
|
3280
|
+
Returns:
|
|
3281
|
+
dict: Dictionary representation of the error response
|
|
3282
|
+
"""
|
|
3283
|
+
error_data: dict[str, Any] = {
|
|
3284
|
+
"type": self.error_type,
|
|
3285
|
+
"message": self.error_message,
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
if self.error_code is not None:
|
|
3289
|
+
error_data["code"] = self.error_code
|
|
3290
|
+
if self.request_id is not None:
|
|
3291
|
+
error_data["requestId"] = self.request_id
|
|
3292
|
+
|
|
3293
|
+
return {"error": error_data}
|
|
3294
|
+
|
|
3295
|
+
|
|
3296
|
+
# endregion error_models
|