azure-functions-durable 1.2.9__py3-none-any.whl → 1.3.0__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.
Files changed (104) hide show
  1. azure/durable_functions/__init__.py +81 -81
  2. azure/durable_functions/constants.py +9 -9
  3. azure/durable_functions/decorators/__init__.py +3 -3
  4. azure/durable_functions/decorators/durable_app.py +260 -249
  5. azure/durable_functions/decorators/metadata.py +109 -109
  6. azure/durable_functions/entity.py +129 -125
  7. azure/durable_functions/models/DurableEntityContext.py +201 -201
  8. azure/durable_functions/models/DurableHttpRequest.py +58 -58
  9. azure/durable_functions/models/DurableOrchestrationBindings.py +66 -66
  10. azure/durable_functions/models/DurableOrchestrationClient.py +812 -781
  11. azure/durable_functions/models/DurableOrchestrationContext.py +761 -707
  12. azure/durable_functions/models/DurableOrchestrationStatus.py +156 -156
  13. azure/durable_functions/models/EntityStateResponse.py +23 -23
  14. azure/durable_functions/models/FunctionContext.py +7 -7
  15. azure/durable_functions/models/OrchestrationRuntimeStatus.py +32 -32
  16. azure/durable_functions/models/OrchestratorState.py +117 -116
  17. azure/durable_functions/models/PurgeHistoryResult.py +33 -33
  18. azure/durable_functions/models/ReplaySchema.py +9 -8
  19. azure/durable_functions/models/RetryOptions.py +69 -69
  20. azure/durable_functions/models/RpcManagementOptions.py +86 -86
  21. azure/durable_functions/models/Task.py +540 -426
  22. azure/durable_functions/models/TaskOrchestrationExecutor.py +352 -336
  23. azure/durable_functions/models/TokenSource.py +56 -56
  24. azure/durable_functions/models/__init__.py +26 -24
  25. azure/durable_functions/models/actions/Action.py +23 -23
  26. azure/durable_functions/models/actions/ActionType.py +18 -18
  27. azure/durable_functions/models/actions/CallActivityAction.py +41 -41
  28. azure/durable_functions/models/actions/CallActivityWithRetryAction.py +45 -45
  29. azure/durable_functions/models/actions/CallEntityAction.py +46 -46
  30. azure/durable_functions/models/actions/CallHttpAction.py +35 -35
  31. azure/durable_functions/models/actions/CallSubOrchestratorAction.py +40 -40
  32. azure/durable_functions/models/actions/CallSubOrchestratorWithRetryAction.py +44 -44
  33. azure/durable_functions/models/actions/CompoundAction.py +35 -35
  34. azure/durable_functions/models/actions/ContinueAsNewAction.py +36 -36
  35. azure/durable_functions/models/actions/CreateTimerAction.py +48 -48
  36. azure/durable_functions/models/actions/NoOpAction.py +35 -35
  37. azure/durable_functions/models/actions/SignalEntityAction.py +47 -47
  38. azure/durable_functions/models/actions/WaitForExternalEventAction.py +63 -63
  39. azure/durable_functions/models/actions/WhenAllAction.py +14 -14
  40. azure/durable_functions/models/actions/WhenAnyAction.py +14 -14
  41. azure/durable_functions/models/actions/__init__.py +24 -24
  42. azure/durable_functions/models/entities/EntityState.py +74 -74
  43. azure/durable_functions/models/entities/OperationResult.py +94 -76
  44. azure/durable_functions/models/entities/RequestMessage.py +53 -53
  45. azure/durable_functions/models/entities/ResponseMessage.py +48 -48
  46. azure/durable_functions/models/entities/Signal.py +62 -62
  47. azure/durable_functions/models/entities/__init__.py +17 -17
  48. azure/durable_functions/models/history/HistoryEvent.py +92 -92
  49. azure/durable_functions/models/history/HistoryEventType.py +27 -27
  50. azure/durable_functions/models/history/__init__.py +8 -8
  51. azure/durable_functions/models/utils/__init__.py +7 -7
  52. azure/durable_functions/models/utils/entity_utils.py +103 -91
  53. azure/durable_functions/models/utils/http_utils.py +80 -69
  54. azure/durable_functions/models/utils/json_utils.py +96 -56
  55. azure/durable_functions/orchestrator.py +73 -71
  56. azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +42 -0
  57. azure/durable_functions/testing/__init__.py +6 -0
  58. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/LICENSE +21 -21
  59. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/METADATA +59 -58
  60. azure_functions_durable-1.3.0.dist-info/RECORD +103 -0
  61. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/WHEEL +1 -1
  62. tests/models/test_DecoratorMetadata.py +135 -135
  63. tests/models/test_Decorators.py +107 -107
  64. tests/models/test_DurableOrchestrationBindings.py +68 -68
  65. tests/models/test_DurableOrchestrationClient.py +730 -730
  66. tests/models/test_DurableOrchestrationContext.py +102 -102
  67. tests/models/test_DurableOrchestrationStatus.py +59 -59
  68. tests/models/test_OrchestrationState.py +28 -28
  69. tests/models/test_RpcManagementOptions.py +79 -79
  70. tests/models/test_TokenSource.py +10 -10
  71. tests/orchestrator/models/OrchestrationInstance.py +18 -18
  72. tests/orchestrator/orchestrator_test_utils.py +130 -130
  73. tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
  74. tests/orchestrator/test_call_http.py +235 -176
  75. tests/orchestrator/test_continue_as_new.py +67 -67
  76. tests/orchestrator/test_create_timer.py +126 -126
  77. tests/orchestrator/test_entity.py +397 -395
  78. tests/orchestrator/test_external_event.py +53 -53
  79. tests/orchestrator/test_fan_out_fan_in.py +175 -175
  80. tests/orchestrator/test_is_replaying_flag.py +101 -101
  81. tests/orchestrator/test_retries.py +308 -308
  82. tests/orchestrator/test_sequential_orchestrator.py +841 -841
  83. tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
  84. tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
  85. tests/orchestrator/test_serialization.py +30 -30
  86. tests/orchestrator/test_sub_orchestrator.py +95 -95
  87. tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
  88. tests/orchestrator/test_task_any.py +60 -60
  89. tests/tasks/tasks_test_utils.py +17 -17
  90. tests/tasks/test_long_timers.py +70 -0
  91. tests/tasks/test_new_uuid.py +34 -34
  92. tests/test_utils/ContextBuilder.py +174 -174
  93. tests/test_utils/EntityContextBuilder.py +56 -56
  94. tests/test_utils/constants.py +1 -1
  95. tests/test_utils/json_utils.py +30 -30
  96. tests/test_utils/testClasses.py +56 -56
  97. tests/utils/__init__.py +1 -0
  98. tests/utils/test_entity_utils.py +24 -0
  99. azure_functions_durable-1.2.9.data/data/_manifest/bsi.json +0 -1
  100. azure_functions_durable-1.2.9.data/data/_manifest/manifest.cat +0 -0
  101. azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json +0 -11985
  102. azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
  103. azure_functions_durable-1.2.9.dist-info/RECORD +0 -102
  104. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,17 @@
1
- def assert_tasks_equal(task1, task2):
2
- assert task1.is_completed == task2.is_completed
3
- assert task1.is_faulted == task2.is_faulted
4
- assert task1.result == task2.result
5
- assert task1.timestamp == task2.timestamp
6
- assert task1.id == task2.id
7
- assert task1.action == task2.action
8
- assert str(task1.exception) == str(task2.exception)
9
-
10
-
11
- def assert_taskset_equal(taskset1, taskset2):
12
- assert taskset1.is_completed == taskset2.is_completed
13
- assert taskset1.is_faulted == taskset2.is_faulted
14
- assert taskset1.result == taskset2.result
15
- assert taskset1.actions == taskset2.actions
16
- assert taskset1.timestamp == taskset2.timestamp
17
- assert str(taskset1.exception) == str(taskset2.exception)
1
+ def assert_tasks_equal(task1, task2):
2
+ assert task1.is_completed == task2.is_completed
3
+ assert task1.is_faulted == task2.is_faulted
4
+ assert task1.result == task2.result
5
+ assert task1.timestamp == task2.timestamp
6
+ assert task1.id == task2.id
7
+ assert task1.action == task2.action
8
+ assert str(task1.exception) == str(task2.exception)
9
+
10
+
11
+ def assert_taskset_equal(taskset1, taskset2):
12
+ assert taskset1.is_completed == taskset2.is_completed
13
+ assert taskset1.is_faulted == taskset2.is_faulted
14
+ assert taskset1.result == taskset2.result
15
+ assert taskset1.actions == taskset2.actions
16
+ assert taskset1.timestamp == taskset2.timestamp
17
+ assert str(taskset1.exception) == str(taskset2.exception)
@@ -0,0 +1,70 @@
1
+ import datetime
2
+
3
+ import pytest
4
+ from azure.durable_functions.models.DurableOrchestrationContext import DurableOrchestrationContext
5
+ from azure.durable_functions.models.Task import LongTimerTask, TaskState, TimerTask
6
+ from azure.durable_functions.models.actions.CreateTimerAction import CreateTimerAction
7
+
8
+
9
+ @pytest.fixture
10
+ def starting_context_v3():
11
+ context = DurableOrchestrationContext.from_json(
12
+ '{"history":[{"EventType":12,"EventId":-1,"IsPlayed":false,'
13
+ '"Timestamp":"'
14
+ f'{datetime.datetime.now(datetime.timezone.utc).isoformat()}'
15
+ '"}, {"OrchestrationInstance":{'
16
+ '"InstanceId":"48d0f95957504c2fa579e810a390b938", '
17
+ '"ExecutionId":"fd183ee02e4b4fd18c95b773cfb5452b"},"EventType":0,'
18
+ '"ParentInstance":null, '
19
+ '"Name":"DurableOrchestratorTrigger","Version":"","Input":"null",'
20
+ '"Tags":null,"EventId":-1,"IsPlayed":false, '
21
+ '"Timestamp":"'
22
+ f'{datetime.datetime.now(datetime.timezone.utc).isoformat()}'
23
+ '"}],"input":null,'
24
+ '"instanceId":"48d0f95957504c2fa579e810a390b938", '
25
+ '"upperSchemaVersion": 2, '
26
+ '"upperSchemaVersionNew": 3, '
27
+ '"isReplaying":false,"parentInstanceId":null, '
28
+ '"maximumShortTimerDuration":"0.16:00:00", '
29
+ '"longRunningTimerIntervalDuration":"0.08:00:00" } ')
30
+ return context
31
+
32
+
33
+ def test_durable_context_creates_correct_timer(starting_context_v3):
34
+ timer = starting_context_v3.create_timer(datetime.datetime.now(datetime.timezone.utc) +
35
+ datetime.timedelta(minutes=30))
36
+ assert isinstance(timer, TimerTask)
37
+
38
+ timer2 = starting_context_v3.create_timer(datetime.datetime.now(datetime.timezone.utc) +
39
+ datetime.timedelta(days=1))
40
+ assert isinstance(timer2, LongTimerTask)
41
+
42
+ def test_long_timer_fires_appropriately(starting_context_v3):
43
+ starting_time = starting_context_v3.current_utc_datetime
44
+ final_fire_time = starting_time + datetime.timedelta(hours=20)
45
+ long_timer_action = CreateTimerAction(final_fire_time)
46
+ long_timer = LongTimerTask(None, long_timer_action, starting_context_v3)
47
+ assert long_timer.action.fire_at == final_fire_time
48
+ assert long_timer.action == long_timer_action
49
+
50
+ # Check the first "inner" timer and simulate firing it
51
+ short_timer = long_timer.pending_tasks.pop()
52
+ assert short_timer.action_repr.fire_at == starting_time + datetime.timedelta(hours=8)
53
+ # This happens when the task is reconstructed during replay, doing it manually for the test
54
+ long_timer._orchestration_context.current_utc_datetime = short_timer.action_repr.fire_at
55
+ short_timer.state = TaskState.SUCCEEDED
56
+ long_timer.try_set_value(short_timer)
57
+
58
+ assert long_timer.state == TaskState.RUNNING
59
+
60
+ # Check the scond "inner" timer and simulate firing it. This one should be set to the final
61
+ # fire time, the remaining time (12 hours) is less than the max long timer duration (16 hours)
62
+ short_timer = long_timer.pending_tasks.pop()
63
+ assert short_timer.action_repr.fire_at == final_fire_time
64
+ long_timer._orchestration_context.current_utc_datetime = short_timer.action_repr.fire_at
65
+ short_timer.state = TaskState.SUCCEEDED
66
+ long_timer.try_set_value(short_timer)
67
+
68
+ # Ensure the LongTimerTask finished
69
+ assert len(long_timer.pending_tasks) == 0
70
+ assert long_timer.state == TaskState.SUCCEEDED
@@ -1,35 +1,35 @@
1
- from uuid import uuid1
2
- from typing import List, Any, Dict
3
- from azure.durable_functions.models.DurableOrchestrationContext import DurableOrchestrationContext
4
- from azure.durable_functions.constants import DATETIME_STRING_FORMAT
5
-
6
-
7
- def history_list() -> List[Dict[Any, Any]]:
8
- history = [{'EventType': 12, 'EventId': -1, 'IsPlayed': False,
9
- 'Timestamp': '2019-12-08T23:18:41.3240927Z'}, {
10
- 'OrchestrationInstance': {'InstanceId': '48d0f95957504c2fa579e810a390b938',
11
- 'ExecutionId': 'fd183ee02e4b4fd18c95b773cfb5452b'},
12
- 'EventType': 0, 'ParentInstance': None, 'Name': 'DurableOrchestratorTrigger',
13
- 'Version': '', 'Input': 'null', 'Tags': None, 'EventId': -1, 'IsPlayed': False,
14
- 'Timestamp': '2019-12-08T23:18:39.756132Z'}]
15
- return history
16
-
17
-
18
- def test_new_uuid():
19
- instance_id = str(uuid1())
20
- history = history_list()
21
- context1 = DurableOrchestrationContext(history, instance_id, False, None)
22
-
23
- result1a = context1.new_uuid()
24
- result1b = context1.new_uuid()
25
-
26
- context2 = DurableOrchestrationContext(history, instance_id, False, None)
27
-
28
- result2a = context2.new_uuid()
29
- result2b = context2.new_uuid()
30
-
31
- assert result1a == result2a
32
- assert result1b == result2b
33
-
34
- assert result1a != result1b
1
+ from uuid import uuid1
2
+ from typing import List, Any, Dict
3
+ from azure.durable_functions.models.DurableOrchestrationContext import DurableOrchestrationContext
4
+ from azure.durable_functions.constants import DATETIME_STRING_FORMAT
5
+
6
+
7
+ def history_list() -> List[Dict[Any, Any]]:
8
+ history = [{'EventType': 12, 'EventId': -1, 'IsPlayed': False,
9
+ 'Timestamp': '2019-12-08T23:18:41.3240927Z'}, {
10
+ 'OrchestrationInstance': {'InstanceId': '48d0f95957504c2fa579e810a390b938',
11
+ 'ExecutionId': 'fd183ee02e4b4fd18c95b773cfb5452b'},
12
+ 'EventType': 0, 'ParentInstance': None, 'Name': 'DurableOrchestratorTrigger',
13
+ 'Version': '', 'Input': 'null', 'Tags': None, 'EventId': -1, 'IsPlayed': False,
14
+ 'Timestamp': '2019-12-08T23:18:39.756132Z'}]
15
+ return history
16
+
17
+
18
+ def test_new_uuid():
19
+ instance_id = str(uuid1())
20
+ history = history_list()
21
+ context1 = DurableOrchestrationContext(history, instance_id, False, None)
22
+
23
+ result1a = context1.new_uuid()
24
+ result1b = context1.new_uuid()
25
+
26
+ context2 = DurableOrchestrationContext(history, instance_id, False, None)
27
+
28
+ result2a = context2.new_uuid()
29
+ result2b = context2.new_uuid()
30
+
31
+ assert result1a == result2a
32
+ assert result1b == result2b
33
+
34
+ assert result1a != result1b
35
35
  assert result2a != result2b
@@ -1,174 +1,174 @@
1
- from azure.durable_functions.models.ReplaySchema import ReplaySchema
2
- import uuid
3
- import json
4
- from datetime import datetime, timedelta
5
- from typing import List, Dict, Any, Optional
6
-
7
- from .json_utils import add_attrib, convert_history_event_to_json_dict
8
- from azure.durable_functions.constants import DATETIME_STRING_FORMAT
9
- from tests.orchestrator.models.OrchestrationInstance \
10
- import OrchestrationInstance
11
- from azure.durable_functions.models.history.HistoryEvent import HistoryEvent
12
- from azure.durable_functions.models.history.HistoryEventType \
13
- import HistoryEventType
14
-
15
-
16
- class ContextBuilder:
17
- def __init__(self, name: str="", increase_time: bool = True, starting_time: Optional[datetime] = None, is_replaying=False, replay_schema: ReplaySchema = ReplaySchema.V1):
18
- self.increase_time = increase_time
19
- self.instance_id = uuid.uuid4()
20
- self.is_replaying: bool = False
21
- self.input_ = None
22
- self.parent_instance_id = None
23
- self.history_events: List[HistoryEvent] = []
24
-
25
- if starting_time is None:
26
- starting_time = datetime.now()
27
- self.current_datetime: datetime = starting_time
28
- self.upperSchemaVersion = replay_schema.value
29
-
30
- self.add_orchestrator_started_event()
31
- self.add_execution_started_event(name, is_played=is_replaying)
32
-
33
- def get_base_event(
34
- self, event_type: HistoryEventType, id_: int = -1,
35
- is_played: bool = False, timestamp=None) -> HistoryEvent:
36
- if self.increase_time:
37
- self.current_datetime = self.current_datetime + timedelta(seconds=1)
38
- if not timestamp:
39
- timestamp = self.current_datetime
40
- event = HistoryEvent(EventType=event_type, EventId=id_,
41
- IsPlayed=is_played,
42
- Timestamp=timestamp.strftime(DATETIME_STRING_FORMAT))
43
-
44
- return event
45
-
46
- def add_orchestrator_started_event(self):
47
- event = self.get_base_event(HistoryEventType.ORCHESTRATOR_STARTED)
48
- self.history_events.append(event)
49
-
50
- def add_orchestrator_completed_event(self):
51
- event = self.get_base_event(HistoryEventType.ORCHESTRATOR_COMPLETED)
52
- self.history_events.append(event)
53
-
54
- def add_sub_orchestrator_started_event(self, name: str, id_, input_=None):
55
- event = self.get_base_event(HistoryEventType.SUB_ORCHESTRATION_INSTANCE_CREATED,
56
- id_=id_)
57
- event.Name = name
58
- event.Input = input_
59
- self.history_events.append(event)
60
-
61
- def add_sub_orchestrator_completed_event(self, result, id_):
62
- event = self.get_base_event(HistoryEventType.SUB_ORCHESTRATION_INSTANCE_COMPLETED)
63
- event.Result = result
64
- event.TaskScheduledId = id_
65
- self.history_events.append(event)
66
-
67
- def add_sub_orchestrator_failed_event(self, id_, reason, details):
68
- event = self.get_base_event(HistoryEventType.SUB_ORCHESTRATION_INSTANCE_FAILED)
69
- event.Reason = reason
70
- event.Details = details
71
- event.TaskScheduledId = id_
72
- self.history_events.append(event)
73
-
74
- def add_event_sent_event(self, instance_id, event_id):
75
- event = self.get_base_event(HistoryEventType.EVENT_SENT)
76
- event.InstanceId = instance_id
77
- event._event_id = event_id
78
- event.Name = "op"
79
- event.Input = json.dumps({ "id": "0000" }) # usually provided by the extension
80
- self.history_events.append(event)
81
-
82
- def add_task_scheduled_event(
83
- self, name: str, id_: int, version: str = '', input_=None):
84
- event = self.get_base_event(HistoryEventType.TASK_SCHEDULED, id_=id_)
85
- event.Name = name
86
- event.Version = version
87
- event.Input_ = input_
88
- self.history_events.append(event)
89
-
90
- def add_task_completed_event(self, id_: int, result, is_played=False):
91
- event = self.get_base_event(HistoryEventType.TASK_COMPLETED, is_played=is_played)
92
- event.Result = result
93
- event.TaskScheduledId = id_
94
- self.history_events.append(event)
95
-
96
- def add_task_failed_event(self, id_: int, reason: str, details: str):
97
- event = self.get_base_event(HistoryEventType.TASK_FAILED)
98
- event.Reason = reason
99
- event.Details = details
100
- event.TaskScheduledId = id_
101
- self.history_events.append(event)
102
-
103
- def add_timer_created_event(self, id_: int, timestamp: str = None):
104
- fire_at = self.current_datetime.strftime(DATETIME_STRING_FORMAT)
105
- if timestamp is not None:
106
- fire_at = timestamp
107
- event = self.get_base_event(HistoryEventType.TIMER_CREATED, id_=id_)
108
- event.FireAt = fire_at
109
- self.history_events.append(event)
110
- return fire_at
111
-
112
- def add_timer_fired_event(self, id_: int, fire_at: str, is_played: bool = True):
113
- event = self.get_base_event(HistoryEventType.TIMER_FIRED, is_played=is_played)
114
- event.TimerId = id_
115
- event.FireAt = fire_at
116
- self.history_events.append(event)
117
-
118
- def add_execution_started_event(
119
- self, name: str, version: str = '', input_=None, is_played=True):
120
- event = self.get_base_event(HistoryEventType.EXECUTION_STARTED, is_played=is_played)
121
- event.orchestration_instance = OrchestrationInstance()
122
- self.instance_id = event.orchestration_instance.instance_id
123
- event.Name = name
124
- event.Version = version
125
- event.Input = input_
126
- self.history_events.append(event)
127
-
128
- def add_event_raised_event(self, name:str, id_: int, input_=None, timestamp=None, is_entity=False, is_error = False, literal_input=False):
129
- event = self.get_base_event(HistoryEventType.EVENT_RAISED, id_=id_, timestamp=timestamp)
130
- event.Name = name
131
- if is_entity:
132
- if is_error:
133
- event.Input = json.dumps({ "result": json.dumps(input_), "exceptionType": "True" })
134
- else:
135
- if literal_input:
136
- event.Input = json.dumps({ "result": input_ })
137
- else:
138
- event.Input = json.dumps({ "result": json.dumps(input_) })
139
- else:
140
- event.Input = input_
141
- # event.timestamp = timestamp
142
- self.history_events.append(event)
143
-
144
- def to_json(self, **kwargs) -> Dict[str, Any]:
145
- json_dict = {}
146
-
147
- add_attrib(json_dict, self, 'instance_id', 'instanceId')
148
- add_attrib(json_dict, self, 'parent_instance_id', 'parentInstanceId')
149
- add_attrib(json_dict, self, 'is_replaying', 'isReplaying')
150
- add_attrib(json_dict, self, 'input_', "input")
151
- add_attrib(json_dict, self, 'upperSchemaVersion', "upperSchemaVersion")
152
-
153
- history_list_as_dict = self.get_history_list_as_dict()
154
- json_dict['history'] = history_list_as_dict
155
-
156
- if kwargs is not None:
157
- for key, value in kwargs.items():
158
- json_dict[key] = value
159
-
160
- return json_dict
161
-
162
- def get_history_list_as_dict(self) -> List[Dict[str, Any]]:
163
- history_list = []
164
-
165
- for history_event in self.history_events:
166
- event_as_dict = convert_history_event_to_json_dict(history_event)
167
- history_list.append(event_as_dict)
168
-
169
- return history_list
170
-
171
- def to_json_string(self, **kwargs) -> str:
172
- json_dict = self.to_json(**kwargs)
173
-
174
- return json.dumps(json_dict)
1
+ from azure.durable_functions.models.ReplaySchema import ReplaySchema
2
+ import uuid
3
+ import json
4
+ from datetime import datetime, timedelta
5
+ from typing import List, Dict, Any, Optional
6
+
7
+ from .json_utils import add_attrib, convert_history_event_to_json_dict
8
+ from azure.durable_functions.constants import DATETIME_STRING_FORMAT
9
+ from tests.orchestrator.models.OrchestrationInstance \
10
+ import OrchestrationInstance
11
+ from azure.durable_functions.models.history.HistoryEvent import HistoryEvent
12
+ from azure.durable_functions.models.history.HistoryEventType \
13
+ import HistoryEventType
14
+
15
+
16
+ class ContextBuilder:
17
+ def __init__(self, name: str="", increase_time: bool = True, starting_time: Optional[datetime] = None, is_replaying=False, replay_schema: ReplaySchema = ReplaySchema.V1):
18
+ self.increase_time = increase_time
19
+ self.instance_id = uuid.uuid4()
20
+ self.is_replaying: bool = False
21
+ self.input_ = None
22
+ self.parent_instance_id = None
23
+ self.history_events: List[HistoryEvent] = []
24
+
25
+ if starting_time is None:
26
+ starting_time = datetime.now()
27
+ self.current_datetime: datetime = starting_time
28
+ self.upperSchemaVersion = replay_schema.value
29
+
30
+ self.add_orchestrator_started_event()
31
+ self.add_execution_started_event(name, is_played=is_replaying)
32
+
33
+ def get_base_event(
34
+ self, event_type: HistoryEventType, id_: int = -1,
35
+ is_played: bool = False, timestamp=None) -> HistoryEvent:
36
+ if self.increase_time:
37
+ self.current_datetime = self.current_datetime + timedelta(seconds=1)
38
+ if not timestamp:
39
+ timestamp = self.current_datetime
40
+ event = HistoryEvent(EventType=event_type, EventId=id_,
41
+ IsPlayed=is_played,
42
+ Timestamp=timestamp.strftime(DATETIME_STRING_FORMAT))
43
+
44
+ return event
45
+
46
+ def add_orchestrator_started_event(self):
47
+ event = self.get_base_event(HistoryEventType.ORCHESTRATOR_STARTED)
48
+ self.history_events.append(event)
49
+
50
+ def add_orchestrator_completed_event(self):
51
+ event = self.get_base_event(HistoryEventType.ORCHESTRATOR_COMPLETED)
52
+ self.history_events.append(event)
53
+
54
+ def add_sub_orchestrator_started_event(self, name: str, id_, input_=None):
55
+ event = self.get_base_event(HistoryEventType.SUB_ORCHESTRATION_INSTANCE_CREATED,
56
+ id_=id_)
57
+ event.Name = name
58
+ event.Input = input_
59
+ self.history_events.append(event)
60
+
61
+ def add_sub_orchestrator_completed_event(self, result, id_):
62
+ event = self.get_base_event(HistoryEventType.SUB_ORCHESTRATION_INSTANCE_COMPLETED)
63
+ event.Result = result
64
+ event.TaskScheduledId = id_
65
+ self.history_events.append(event)
66
+
67
+ def add_sub_orchestrator_failed_event(self, id_, reason, details):
68
+ event = self.get_base_event(HistoryEventType.SUB_ORCHESTRATION_INSTANCE_FAILED)
69
+ event.Reason = reason
70
+ event.Details = details
71
+ event.TaskScheduledId = id_
72
+ self.history_events.append(event)
73
+
74
+ def add_event_sent_event(self, instance_id, event_id):
75
+ event = self.get_base_event(HistoryEventType.EVENT_SENT)
76
+ event.InstanceId = instance_id
77
+ event._event_id = event_id
78
+ event.Name = "op"
79
+ event.Input = json.dumps({ "id": "0000" }) # usually provided by the extension
80
+ self.history_events.append(event)
81
+
82
+ def add_task_scheduled_event(
83
+ self, name: str, id_: int, version: str = '', input_=None):
84
+ event = self.get_base_event(HistoryEventType.TASK_SCHEDULED, id_=id_)
85
+ event.Name = name
86
+ event.Version = version
87
+ event.Input_ = input_
88
+ self.history_events.append(event)
89
+
90
+ def add_task_completed_event(self, id_: int, result, is_played=False):
91
+ event = self.get_base_event(HistoryEventType.TASK_COMPLETED, is_played=is_played)
92
+ event.Result = result
93
+ event.TaskScheduledId = id_
94
+ self.history_events.append(event)
95
+
96
+ def add_task_failed_event(self, id_: int, reason: str, details: str):
97
+ event = self.get_base_event(HistoryEventType.TASK_FAILED)
98
+ event.Reason = reason
99
+ event.Details = details
100
+ event.TaskScheduledId = id_
101
+ self.history_events.append(event)
102
+
103
+ def add_timer_created_event(self, id_: int, timestamp: str = None):
104
+ fire_at = self.current_datetime.strftime(DATETIME_STRING_FORMAT)
105
+ if timestamp is not None:
106
+ fire_at = timestamp
107
+ event = self.get_base_event(HistoryEventType.TIMER_CREATED, id_=id_)
108
+ event.FireAt = fire_at
109
+ self.history_events.append(event)
110
+ return fire_at
111
+
112
+ def add_timer_fired_event(self, id_: int, fire_at: str, is_played: bool = True):
113
+ event = self.get_base_event(HistoryEventType.TIMER_FIRED, is_played=is_played)
114
+ event.TimerId = id_
115
+ event.FireAt = fire_at
116
+ self.history_events.append(event)
117
+
118
+ def add_execution_started_event(
119
+ self, name: str, version: str = '', input_=None, is_played=True):
120
+ event = self.get_base_event(HistoryEventType.EXECUTION_STARTED, is_played=is_played)
121
+ event.orchestration_instance = OrchestrationInstance()
122
+ self.instance_id = event.orchestration_instance.instance_id
123
+ event.Name = name
124
+ event.Version = version
125
+ event.Input = input_
126
+ self.history_events.append(event)
127
+
128
+ def add_event_raised_event(self, name:str, id_: int, input_=None, timestamp=None, is_entity=False, is_error = False, literal_input=False):
129
+ event = self.get_base_event(HistoryEventType.EVENT_RAISED, id_=id_, timestamp=timestamp)
130
+ event.Name = name
131
+ if is_entity:
132
+ if is_error:
133
+ event.Input = json.dumps({ "result": json.dumps(input_), "exceptionType": "True" })
134
+ else:
135
+ if literal_input:
136
+ event.Input = json.dumps({ "result": input_ })
137
+ else:
138
+ event.Input = json.dumps({ "result": json.dumps(input_) })
139
+ else:
140
+ event.Input = input_
141
+ # event.timestamp = timestamp
142
+ self.history_events.append(event)
143
+
144
+ def to_json(self, **kwargs) -> Dict[str, Any]:
145
+ json_dict = {}
146
+
147
+ add_attrib(json_dict, self, 'instance_id', 'instanceId')
148
+ add_attrib(json_dict, self, 'parent_instance_id', 'parentInstanceId')
149
+ add_attrib(json_dict, self, 'is_replaying', 'isReplaying')
150
+ add_attrib(json_dict, self, 'input_', "input")
151
+ add_attrib(json_dict, self, 'upperSchemaVersion', "upperSchemaVersion")
152
+
153
+ history_list_as_dict = self.get_history_list_as_dict()
154
+ json_dict['history'] = history_list_as_dict
155
+
156
+ if kwargs is not None:
157
+ for key, value in kwargs.items():
158
+ json_dict[key] = value
159
+
160
+ return json_dict
161
+
162
+ def get_history_list_as_dict(self) -> List[Dict[str, Any]]:
163
+ history_list = []
164
+
165
+ for history_event in self.history_events:
166
+ event_as_dict = convert_history_event_to_json_dict(history_event)
167
+ history_list.append(event_as_dict)
168
+
169
+ return history_list
170
+
171
+ def to_json_string(self, **kwargs) -> str:
172
+ json_dict = self.to_json(**kwargs)
173
+
174
+ return json.dumps(json_dict)
@@ -1,57 +1,57 @@
1
- import json
2
- from typing import Any, List, Dict, Any
3
-
4
- class EntityContextBuilder():
5
- """Mock class for an EntityContext object, includes a batch field for convenience
6
- """
7
- def __init__(self,
8
- name: str = "",
9
- key: str = "",
10
- exists: bool = True,
11
- state: Any = None,
12
- batch: List[Dict[str, Any]] = []):
13
- """Construct an EntityContextBuilder
14
-
15
- Parameters
16
- ----------
17
- name: str:
18
- The name of the entity. Defaults to the empty string.
19
- key: str
20
- The key of the entity. Defaults to the empty string.
21
- exists: bool
22
- Boolean representing if the entity exists, defaults to True.
23
- state: Any
24
- The state of the entity, defaults ot None.
25
- batch: List[Dict[str, Any]]
26
- The upcoming batch of operations for the entity to perform.
27
- Note that the batch is not technically a part of the entity context
28
- and so it is here only for convenience. Defaults to the empty list.
29
- """
30
- self.name = name
31
- self.key = key
32
- self.exists = exists
33
- self.state = state
34
- self.batch = batch
35
-
36
- def to_json_string(self) -> str:
37
- """Generate a string-representation of the Entity input payload.
38
-
39
- The payload matches the current durable-extension entity-communication
40
- schema.
41
-
42
- Returns
43
- -------
44
- str:
45
- A JSON-formatted string for an EntityContext to load via `from_json`
46
- """
47
- context_json = {
48
- "self": {
49
- "name": self.name,
50
- "key": self.key
51
- },
52
- "state": self.state,
53
- "exists": self.exists,
54
- "batch": self.batch
55
- }
56
- json_string = json.dumps(context_json)
1
+ import json
2
+ from typing import Any, List, Dict, Any
3
+
4
+ class EntityContextBuilder():
5
+ """Mock class for an EntityContext object, includes a batch field for convenience
6
+ """
7
+ def __init__(self,
8
+ name: str = "",
9
+ key: str = "",
10
+ exists: bool = True,
11
+ state: Any = None,
12
+ batch: List[Dict[str, Any]] = []):
13
+ """Construct an EntityContextBuilder
14
+
15
+ Parameters
16
+ ----------
17
+ name: str:
18
+ The name of the entity. Defaults to the empty string.
19
+ key: str
20
+ The key of the entity. Defaults to the empty string.
21
+ exists: bool
22
+ Boolean representing if the entity exists, defaults to True.
23
+ state: Any
24
+ The state of the entity, defaults ot None.
25
+ batch: List[Dict[str, Any]]
26
+ The upcoming batch of operations for the entity to perform.
27
+ Note that the batch is not technically a part of the entity context
28
+ and so it is here only for convenience. Defaults to the empty list.
29
+ """
30
+ self.name = name
31
+ self.key = key
32
+ self.exists = exists
33
+ self.state = state
34
+ self.batch = batch
35
+
36
+ def to_json_string(self) -> str:
37
+ """Generate a string-representation of the Entity input payload.
38
+
39
+ The payload matches the current durable-extension entity-communication
40
+ schema.
41
+
42
+ Returns
43
+ -------
44
+ str:
45
+ A JSON-formatted string for an EntityContext to load via `from_json`
46
+ """
47
+ context_json = {
48
+ "self": {
49
+ "name": self.name,
50
+ "key": self.key
51
+ },
52
+ "state": self.state,
53
+ "exists": self.exists,
54
+ "batch": self.batch
55
+ }
56
+ json_string = json.dumps(context_json)
57
57
  return json_string
@@ -1 +1 @@
1
- RPC_BASE_URL = "http://127.0.0.1:17071/durabletask/"
1
+ RPC_BASE_URL = "http://127.0.0.1:17071/durabletask/"