aws-durable-execution-sdk-python 1.1.0__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/PKG-INFO +1 -1
  2. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/__about__.py +1 -1
  3. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/execution.py +35 -0
  4. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/lambda_service.py +97 -3
  5. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/state.py +6 -6
  6. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/execution_test.py +514 -0
  7. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/lambda_service_test.py +618 -0
  8. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/CODEOWNERS +0 -0
  9. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  10. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  11. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
  12. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  13. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/dependabot.yml +0 -0
  14. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/ci.yml +0 -0
  15. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/integration-tests.yml +0 -0
  16. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/lintcommit.js +0 -0
  17. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/pypi-publish.yml +0 -0
  18. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/scorecard.yml +0 -0
  19. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/sync-package.yml +0 -0
  20. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.github/workflows/test-parser.yml +0 -0
  21. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/.gitignore +0 -0
  22. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  23. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/CONTRIBUTING.md +0 -0
  24. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/LICENSE +0 -0
  25. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/NOTICE +0 -0
  26. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/README.md +0 -0
  27. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/advanced/error-handling.md +0 -0
  28. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/advanced/serialization.md +0 -0
  29. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/advanced/testing-modes.md +0 -0
  30. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/api-reference/.gitkeep +0 -0
  31. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/best-practices.md +0 -0
  32. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/callbacks.md +0 -0
  33. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/child-contexts.md +0 -0
  34. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/invoke.md +0 -0
  35. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/logger.md +0 -0
  36. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/map.md +0 -0
  37. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/parallel.md +0 -0
  38. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/steps.md +0 -0
  39. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/core/wait.md +0 -0
  40. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/getting-started.md +0 -0
  41. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/index.md +0 -0
  42. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/testing-patterns/.gitkeep +0 -0
  43. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/testing-patterns/basic-tests.md +0 -0
  44. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/testing-patterns/complex-workflows.md +0 -0
  45. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/docs/testing-patterns/stores.md +0 -0
  46. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/ops/__tests__/test_parse_sdk_branch.py +0 -0
  47. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/ops/ci-checks.sh +0 -0
  48. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/ops/parse_sdk_branch.py +0 -0
  49. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/pyproject.toml +0 -0
  50. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/.gitignore +0 -0
  51. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/__init__.py +0 -0
  52. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/concurrency/__init__.py +0 -0
  53. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/concurrency/executor.py +0 -0
  54. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/concurrency/models.py +0 -0
  55. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/config.py +0 -0
  56. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/context.py +0 -0
  57. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/exceptions.py +0 -0
  58. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/identifier.py +0 -0
  59. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/logger.py +0 -0
  60. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/__init__.py +0 -0
  61. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/base.py +0 -0
  62. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/callback.py +0 -0
  63. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/child.py +0 -0
  64. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/invoke.py +0 -0
  65. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/map.py +0 -0
  66. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/parallel.py +0 -0
  67. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/step.py +0 -0
  68. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/wait.py +0 -0
  69. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/operation/wait_for_condition.py +0 -0
  70. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/py.typed +0 -0
  71. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/retries.py +0 -0
  72. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/serdes.py +0 -0
  73. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/suspend.py +0 -0
  74. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/threading.py +0 -0
  75. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/types.py +0 -0
  76. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/src/aws_durable_execution_sdk_python/waits.py +0 -0
  77. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/__init__.py +0 -0
  78. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/concurrency_test.py +0 -0
  79. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/config_test.py +0 -0
  80. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/context_test.py +0 -0
  81. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/durable_executions_python_language_sdk_test.py +0 -0
  82. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/e2e/__init__.py +0 -0
  83. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/e2e/checkpoint_response_int_test.py +0 -0
  84. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/e2e/execution_int_test.py +0 -0
  85. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/exceptions_test.py +0 -0
  86. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/logger_test.py +0 -0
  87. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/__init__.py +0 -0
  88. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/base_test.py +0 -0
  89. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/callback_test.py +0 -0
  90. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/child_test.py +0 -0
  91. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/invoke_test.py +0 -0
  92. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/map_test.py +0 -0
  93. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/parallel_test.py +0 -0
  94. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/step_test.py +0 -0
  95. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/wait_for_condition_test.py +0 -0
  96. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/operation/wait_test.py +0 -0
  97. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/retries_test.py +0 -0
  98. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/serdes_test.py +0 -0
  99. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/state_test.py +0 -0
  100. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/suspend_test.py +0 -0
  101. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/test_helpers.py +0 -0
  102. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/threading_test.py +0 -0
  103. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/types_test.py +0 -0
  104. {aws_durable_execution_sdk_python-1.1.0 → aws_durable_execution_sdk_python-1.1.1}/tests/waits_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-durable-execution-sdk-python
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: AWS Durable Execution SDK for Python
5
5
  Project-URL: Documentation, https://github.com/aws/aws-durable-execution-sdk-python#readme
6
6
  Project-URL: Issues, https://github.com/aws/aws-durable-execution-sdk-python/issues
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Amazon.com, Inc. or its affiliates.
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
- __version__ = "1.1.0"
4
+ __version__ = "1.1.1"
@@ -59,6 +59,16 @@ class InitialExecutionState:
59
59
  next_marker=input_dict.get("NextMarker", ""),
60
60
  )
61
61
 
62
+ @staticmethod
63
+ def from_json_dict(input_dict: MutableMapping[str, Any]) -> InitialExecutionState:
64
+ operations = []
65
+ if input_operations := input_dict.get("Operations"):
66
+ operations = [Operation.from_json_dict(op) for op in input_operations]
67
+ return InitialExecutionState(
68
+ operations=operations,
69
+ next_marker=input_dict.get("NextMarker", ""),
70
+ )
71
+
62
72
  def get_execution_operation(self) -> Operation | None:
63
73
  if not self.operations:
64
74
  # Due to payload size limitations we may have an empty operations list.
@@ -91,6 +101,12 @@ class InitialExecutionState:
91
101
  "NextMarker": self.next_marker,
92
102
  }
93
103
 
104
+ def to_json_dict(self) -> MutableMapping[str, Any]:
105
+ return {
106
+ "Operations": [op.to_json_dict() for op in self.operations],
107
+ "NextMarker": self.next_marker,
108
+ }
109
+
94
110
 
95
111
  @dataclass(frozen=True)
96
112
  class DurableExecutionInvocationInput:
@@ -110,6 +126,18 @@ class DurableExecutionInvocationInput:
110
126
  ),
111
127
  )
112
128
 
129
+ @staticmethod
130
+ def from_json_dict(
131
+ input_dict: MutableMapping[str, Any],
132
+ ) -> DurableExecutionInvocationInput:
133
+ return DurableExecutionInvocationInput(
134
+ durable_execution_arn=input_dict["DurableExecutionArn"],
135
+ checkpoint_token=input_dict["CheckpointToken"],
136
+ initial_execution_state=InitialExecutionState.from_json_dict(
137
+ input_dict.get("InitialExecutionState", {})
138
+ ),
139
+ )
140
+
113
141
  def to_dict(self) -> MutableMapping[str, Any]:
114
142
  return {
115
143
  "DurableExecutionArn": self.durable_execution_arn,
@@ -117,6 +145,13 @@ class DurableExecutionInvocationInput:
117
145
  "InitialExecutionState": self.initial_execution_state.to_dict(),
118
146
  }
119
147
 
148
+ def to_json_dict(self) -> MutableMapping[str, Any]:
149
+ return {
150
+ "DurableExecutionArn": self.durable_execution_arn,
151
+ "CheckpointToken": self.checkpoint_token,
152
+ "InitialExecutionState": self.initial_execution_state.to_json_dict(),
153
+ }
154
+
120
155
 
121
156
  @dataclass(frozen=True)
122
157
  class DurableExecutionInvocationInputWithClient(DurableExecutionInvocationInput):
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  import datetime
4
5
  import logging
5
6
  from dataclasses import dataclass, field
@@ -692,6 +693,24 @@ class OperationUpdate:
692
693
  # endregion wait
693
694
 
694
695
 
696
+ class TimestampConverter:
697
+ """Converter for datetime/Unix timestamp conversions."""
698
+
699
+ @staticmethod
700
+ def to_unix_millis(dt: datetime.datetime | None) -> int | None:
701
+ """Convert datetime to Unix timestamp in milliseconds."""
702
+ return int(dt.timestamp() * 1000) if dt else None
703
+
704
+ @staticmethod
705
+ def from_unix_millis(ms: int | None) -> datetime.datetime | None:
706
+ """Convert Unix timestamp in milliseconds to datetime."""
707
+ return (
708
+ datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.UTC)
709
+ if ms is not None
710
+ else None
711
+ )
712
+
713
+
695
714
  @dataclass(frozen=True)
696
715
  class Operation:
697
716
  """Represent the Operation type for GetDurableExecutionState and CheckpointDurableExecution."""
@@ -805,9 +824,11 @@ class Operation:
805
824
  step_dict["Error"] = self.step_details.error.to_dict()
806
825
  result["StepDetails"] = step_dict
807
826
  if self.wait_details:
808
- result["WaitDetails"] = {
809
- "ScheduledEndTimestamp": self.wait_details.scheduled_end_timestamp
810
- }
827
+ result["WaitDetails"] = (
828
+ {"ScheduledEndTimestamp": self.wait_details.scheduled_end_timestamp}
829
+ if self.wait_details.scheduled_end_timestamp
830
+ else {}
831
+ )
811
832
  if self.callback_details:
812
833
  callback_dict: MutableMapping[str, Any] = {
813
834
  "CallbackId": self.callback_details.callback_id
@@ -826,6 +847,79 @@ class Operation:
826
847
  result["ChainedInvokeDetails"] = invoke_dict
827
848
  return result
828
849
 
850
+ def to_json_dict(self) -> MutableMapping[str, Any]:
851
+ """Convert the Operation to a JSON-serializable dictionary.
852
+
853
+ Converts datetime objects to millisecond timestamps for JSON compatibility.
854
+
855
+ Returns:
856
+ A dictionary with JSON-serializable values
857
+ """
858
+ # Start with the regular to_dict output
859
+ result = self.to_dict()
860
+
861
+ # Convert datetime objects to millisecond timestamps
862
+ if ts := result.get("StartTimestamp"):
863
+ result["StartTimestamp"] = TimestampConverter.to_unix_millis(ts)
864
+
865
+ if ts := result.get("EndTimestamp"):
866
+ result["EndTimestamp"] = TimestampConverter.to_unix_millis(ts)
867
+
868
+ if (step_details := result.get("StepDetails")) and (
869
+ ts := step_details.get("NextAttemptTimestamp")
870
+ ):
871
+ result["StepDetails"]["NextAttemptTimestamp"] = (
872
+ TimestampConverter.to_unix_millis(ts)
873
+ )
874
+
875
+ if (wait_details := result.get("WaitDetails")) and (
876
+ ts := wait_details.get("ScheduledEndTimestamp")
877
+ ):
878
+ result["WaitDetails"]["ScheduledEndTimestamp"] = (
879
+ TimestampConverter.to_unix_millis(ts)
880
+ )
881
+
882
+ return result
883
+
884
+ @classmethod
885
+ def from_json_dict(cls, data: MutableMapping[str, Any]) -> Operation:
886
+ """Create an Operation from a JSON-serializable dictionary.
887
+
888
+ Converts millisecond timestamps back to datetime objects.
889
+
890
+ Args:
891
+ data: Dictionary with JSON-serializable values (millisecond timestamps)
892
+
893
+ Returns:
894
+ An Operation instance with datetime objects
895
+ """
896
+ # Make a copy to avoid modifying the original data
897
+ data_copy = copy.deepcopy(data)
898
+
899
+ # Convert millisecond timestamps back to datetime objects
900
+ if ms := data_copy.get("StartTimestamp"):
901
+ data_copy["StartTimestamp"] = TimestampConverter.from_unix_millis(ms)
902
+
903
+ if ms := data_copy.get("EndTimestamp"):
904
+ data_copy["EndTimestamp"] = TimestampConverter.from_unix_millis(ms)
905
+
906
+ if (step_details := data_copy.get("StepDetails")) and (
907
+ ms := step_details.get("NextAttemptTimestamp")
908
+ ):
909
+ step_details["NextAttemptTimestamp"] = TimestampConverter.from_unix_millis(
910
+ ms
911
+ )
912
+
913
+ if (wait_details := data_copy.get("WaitDetails")) and (
914
+ ms := wait_details.get("ScheduledEndTimestamp")
915
+ ):
916
+ wait_details["ScheduledEndTimestamp"] = TimestampConverter.from_unix_millis(
917
+ ms
918
+ )
919
+
920
+ # Use the existing from_dict method with the converted data
921
+ return cls.from_dict(data_copy)
922
+
829
923
 
830
924
  @dataclass(frozen=True)
831
925
  class CheckpointUpdatedExecutionState:
@@ -613,20 +613,20 @@ class ExecutionState:
613
613
 
614
614
  logger.debug("Checkpoint batch processed successfully")
615
615
 
616
- # Signal completion for any synchronous operations
617
- for queued_op in batch:
618
- if queued_op.completion_event is not None:
619
- queued_op.completion_event.set()
620
-
621
616
  # Update local token for next iteration
622
617
  current_checkpoint_token = output.checkpoint_token
623
618
 
624
- # Fetch new operations from the API
619
+ # Fetch new operations from the API before unblocking sync waiters
625
620
  self.fetch_paginated_operations(
626
621
  output.new_execution_state.operations,
627
622
  output.checkpoint_token,
628
623
  output.new_execution_state.next_marker,
629
624
  )
625
+
626
+ # Signal completion for any synchronous operations
627
+ for queued_op in batch:
628
+ if queued_op.completion_event is not None:
629
+ queued_op.completion_event.set()
630
630
  except Exception as e:
631
631
  # Checkpoint failed - wake all blocked threads so they can raise error
632
632
  # Drain both queues and signal all completion events