azure-functions-durable 1.2.8__py3-none-any.whl → 1.2.10__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 (101) 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 +249 -249
  5. azure/durable_functions/decorators/metadata.py +109 -109
  6. azure/durable_functions/entity.py +125 -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 +781 -711
  11. azure/durable_functions/models/DurableOrchestrationContext.py +722 -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 -29
  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 +8 -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 +426 -426
  22. azure/durable_functions/models/TaskOrchestrationExecutor.py +346 -333
  23. azure/durable_functions/models/TokenSource.py +56 -56
  24. azure/durable_functions/models/__init__.py +24 -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 +76 -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 -25
  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 +69 -69
  54. azure/durable_functions/models/utils/json_utils.py +56 -56
  55. azure/durable_functions/orchestrator.py +71 -71
  56. {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/LICENSE +21 -21
  57. {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/METADATA +58 -58
  58. azure_functions_durable-1.2.10.dist-info/RECORD +100 -0
  59. {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/WHEEL +1 -1
  60. tests/models/test_DecoratorMetadata.py +135 -135
  61. tests/models/test_Decorators.py +107 -107
  62. tests/models/test_DurableOrchestrationBindings.py +68 -56
  63. tests/models/test_DurableOrchestrationClient.py +730 -612
  64. tests/models/test_DurableOrchestrationContext.py +102 -102
  65. tests/models/test_DurableOrchestrationStatus.py +59 -59
  66. tests/models/test_OrchestrationState.py +28 -28
  67. tests/models/test_RpcManagementOptions.py +79 -79
  68. tests/models/test_TokenSource.py +10 -10
  69. tests/orchestrator/models/OrchestrationInstance.py +18 -18
  70. tests/orchestrator/orchestrator_test_utils.py +130 -130
  71. tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
  72. tests/orchestrator/test_call_http.py +235 -176
  73. tests/orchestrator/test_continue_as_new.py +67 -67
  74. tests/orchestrator/test_create_timer.py +126 -126
  75. tests/orchestrator/test_entity.py +395 -395
  76. tests/orchestrator/test_external_event.py +53 -53
  77. tests/orchestrator/test_fan_out_fan_in.py +175 -175
  78. tests/orchestrator/test_is_replaying_flag.py +101 -101
  79. tests/orchestrator/test_retries.py +308 -308
  80. tests/orchestrator/test_sequential_orchestrator.py +841 -801
  81. tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
  82. tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
  83. tests/orchestrator/test_serialization.py +30 -30
  84. tests/orchestrator/test_sub_orchestrator.py +95 -95
  85. tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
  86. tests/orchestrator/test_task_any.py +60 -60
  87. tests/tasks/tasks_test_utils.py +17 -17
  88. tests/tasks/test_new_uuid.py +34 -34
  89. tests/test_utils/ContextBuilder.py +174 -174
  90. tests/test_utils/EntityContextBuilder.py +56 -56
  91. tests/test_utils/constants.py +1 -1
  92. tests/test_utils/json_utils.py +30 -30
  93. tests/test_utils/testClasses.py +56 -56
  94. tests/utils/__init__.py +1 -0
  95. tests/utils/test_entity_utils.py +24 -0
  96. azure_functions_durable-1.2.8.data/data/_manifest/bsi.json +0 -1
  97. azure_functions_durable-1.2.8.data/data/_manifest/manifest.cat +0 -0
  98. azure_functions_durable-1.2.8.data/data/_manifest/manifest.spdx.json +0 -12845
  99. azure_functions_durable-1.2.8.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
  100. azure_functions_durable-1.2.8.dist-info/RECORD +0 -102
  101. {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/top_level.txt +0 -0
@@ -1,466 +1,466 @@
1
- from typing import List, Union
2
- from azure.durable_functions.models.ReplaySchema import ReplaySchema
3
- from .orchestrator_test_utils \
4
- import get_orchestration_state_result, assert_orchestration_state_equals, assert_valid_schema
5
- from tests.test_utils.ContextBuilder import ContextBuilder
6
- from azure.durable_functions.models.OrchestratorState import OrchestratorState
7
- from azure.durable_functions.models.RetryOptions import RetryOptions
8
- from azure.durable_functions.models.actions.CallActivityWithRetryAction \
9
- import CallActivityWithRetryAction
10
-
11
-
12
- RETRY_OPTIONS = RetryOptions(5000, 3)
13
-
14
-
15
- def generator_function(context):
16
- outputs = []
17
-
18
- retry_options = RETRY_OPTIONS
19
- task1 = yield context.call_activity_with_retry(
20
- "Hello", retry_options, "Tokyo")
21
- task2 = yield context.call_activity_with_retry(
22
- "Hello", retry_options, "Seattle")
23
- task3 = yield context.call_activity_with_retry(
24
- "Hello", retry_options, "London")
25
-
26
- outputs.append(task1)
27
- outputs.append(task2)
28
- outputs.append(task3)
29
-
30
- return outputs
31
-
32
- def generator_function_try_catch(context):
33
- outputs = []
34
-
35
- retry_options = RETRY_OPTIONS
36
- result = None
37
- try:
38
- result = yield context.call_activity_with_retry(
39
- "Hello", retry_options, "Tokyo")
40
- except:
41
- result = yield context.call_activity_with_retry(
42
- "Hello", retry_options, "Seattle")
43
- return result
44
-
45
- def generator_function_concurrent_retries(context):
46
- outputs = []
47
-
48
- retry_options = RETRY_OPTIONS
49
- task1 = context.call_activity_with_retry(
50
- "Hello", retry_options, "Tokyo")
51
- task2 = context.call_activity_with_retry(
52
- "Hello", retry_options, "Seattle")
53
- task3 = context.call_activity_with_retry(
54
- "Hello", retry_options, "London")
55
-
56
- outputs = yield context.task_all([task1, task2, task3])
57
-
58
- return outputs
59
-
60
- def generator_function_two_concurrent_retries_when_all(context):
61
- outputs = []
62
-
63
- retry_options = RETRY_OPTIONS
64
- task1 = context.call_activity_with_retry(
65
- "Hello", retry_options, "Tokyo")
66
- task2 = context.call_activity_with_retry(
67
- "Hello", retry_options, "Seattle")
68
-
69
- outputs = yield context.task_all([task1, task2])
70
-
71
- return outputs
72
-
73
- def generator_function_two_concurrent_retries_when_any(context):
74
- outputs = []
75
-
76
- retry_options = RETRY_OPTIONS
77
- task1 = context.call_activity_with_retry(
78
- "Hello", retry_options, "Tokyo")
79
- task2 = context.call_activity_with_retry(
80
- "Hello", retry_options, "Seattle")
81
-
82
- outputs = yield context.task_any([task1, task2])
83
-
84
- return outputs.result
85
-
86
-
87
- def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
88
- return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema.value)
89
-
90
-
91
- def add_hello_action(state: OrchestratorState, input_: Union[List[str], str]):
92
- retry_options = RETRY_OPTIONS
93
- actions = []
94
- inputs = input_
95
- if not isinstance(input_, list):
96
- inputs = [input_]
97
- for input_ in inputs:
98
- action = CallActivityWithRetryAction(
99
- function_name='Hello', retry_options=retry_options, input_=input_)
100
- actions.append(action)
101
- state._actions.append(actions)
102
-
103
-
104
- def add_hello_failed_events(
105
- context_builder: ContextBuilder, id_: int, reason: str, details: str):
106
- context_builder.add_task_scheduled_event(name='Hello', id_=id_)
107
- context_builder.add_orchestrator_completed_event()
108
- context_builder.add_orchestrator_started_event()
109
- context_builder.add_task_failed_event(
110
- id_=id_, reason=reason, details=details)
111
-
112
-
113
- def add_hello_completed_events(
114
- context_builder: ContextBuilder, id_: int, result: str):
115
- context_builder.add_task_scheduled_event(name='Hello', id_=id_)
116
- context_builder.add_orchestrator_completed_event()
117
- context_builder.add_orchestrator_started_event()
118
- context_builder.add_task_completed_event(id_=id_, result=result)
119
-
120
-
121
- def add_retry_timer_events(context_builder: ContextBuilder, id_: int):
122
- fire_at = context_builder.add_timer_created_event(id_)
123
- context_builder.add_orchestrator_completed_event()
124
- context_builder.add_orchestrator_started_event()
125
- context_builder.add_timer_fired_event(id_=id_, fire_at=fire_at)
126
-
127
- def add_two_retriable_events_completing_out_of_order(context_builder: ContextBuilder,
128
- failed_reason, failed_details):
129
- ## Schedule tasks
130
- context_builder.add_task_scheduled_event(name='Hello', id_=0) # Tokyo task
131
- context_builder.add_task_scheduled_event(name='Hello', id_=1) # Seattle task
132
-
133
- context_builder.add_orchestrator_completed_event()
134
- context_builder.add_orchestrator_started_event()
135
-
136
- ## Task failures and timer-scheduling
137
-
138
- # tasks fail "out of order"
139
- context_builder.add_task_failed_event(
140
- id_=1, reason=failed_reason, details=failed_details) # Seattle task
141
- fire_at_1 = context_builder.add_timer_created_event(2) # Seattle timer
142
-
143
- context_builder.add_orchestrator_completed_event()
144
- context_builder.add_orchestrator_started_event()
145
-
146
- context_builder.add_task_failed_event(
147
- id_=0, reason=failed_reason, details=failed_details) # Tokyo task
148
- fire_at_2 = context_builder.add_timer_created_event(3) # Tokyo timer
149
-
150
- context_builder.add_orchestrator_completed_event()
151
- context_builder.add_orchestrator_started_event()
152
-
153
- ## fire timers
154
- context_builder.add_timer_fired_event(id_=2, fire_at=fire_at_1) # Seattle timer
155
- context_builder.add_timer_fired_event(id_=3, fire_at=fire_at_2) # Tokyo timer
156
-
157
- ## Complete events
158
- context_builder.add_task_scheduled_event(name='Hello', id_=4) # Seattle task
159
- context_builder.add_task_scheduled_event(name='Hello', id_=5) # Tokyo task
160
-
161
- context_builder.add_orchestrator_completed_event()
162
- context_builder.add_orchestrator_started_event()
163
- context_builder.add_task_completed_event(id_=4, result="\"Hello Seattle!\"")
164
- context_builder.add_task_completed_event(id_=5, result="\"Hello Tokyo!\"")
165
-
166
-
167
- def test_initial_orchestration_state():
168
- context_builder = ContextBuilder('test_simple_function')
169
-
170
- result = get_orchestration_state_result(
171
- context_builder, generator_function)
172
-
173
- expected_state = base_expected_state()
174
- add_hello_action(expected_state, 'Tokyo')
175
- expected = expected_state.to_json()
176
-
177
- assert_valid_schema(result)
178
- assert_orchestration_state_equals(expected, result)
179
-
180
-
181
- def test_tokyo_state():
182
- context_builder = ContextBuilder('test_simple_function')
183
- add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
184
-
185
- result = get_orchestration_state_result(
186
- context_builder, generator_function)
187
-
188
- expected_state = base_expected_state()
189
- add_hello_action(expected_state, 'Tokyo')
190
- add_hello_action(expected_state, 'Seattle')
191
- expected = expected_state.to_json()
192
-
193
- assert_valid_schema(result)
194
- assert_orchestration_state_equals(expected, result)
195
-
196
-
197
- def test_failed_tokyo_with_retry():
198
- failed_reason = 'Reasons'
199
- failed_details = 'Stuff and Things'
200
- context_builder = ContextBuilder('test_simple_function')
201
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
202
-
203
- result = get_orchestration_state_result(
204
- context_builder, generator_function)
205
-
206
- expected_state = base_expected_state()
207
- add_hello_action(expected_state, 'Tokyo')
208
- expected = expected_state.to_json()
209
-
210
- assert_valid_schema(result)
211
- assert_orchestration_state_equals(expected, result)
212
-
213
-
214
- def test_failed_tokyo_with_timer_entry():
215
- failed_reason = 'Reasons'
216
- failed_details = 'Stuff and Things'
217
- context_builder = ContextBuilder('test_simple_function')
218
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
219
- add_retry_timer_events(context_builder, 1)
220
-
221
- result = get_orchestration_state_result(
222
- context_builder, generator_function)
223
-
224
- expected_state = base_expected_state()
225
- add_hello_action(expected_state, 'Tokyo')
226
- expected = expected_state.to_json()
227
-
228
- assert_valid_schema(result)
229
- assert_orchestration_state_equals(expected, result)
230
-
231
-
232
- def test_failed_tokyo_with_failed_retry():
233
- failed_reason = 'Reasons'
234
- failed_details = 'Stuff and Things'
235
- context_builder = ContextBuilder('test_simple_function')
236
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
237
- add_retry_timer_events(context_builder, 1)
238
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
239
-
240
- result = get_orchestration_state_result(
241
- context_builder, generator_function)
242
-
243
- expected_state = base_expected_state()
244
- add_hello_action(expected_state, 'Tokyo')
245
- expected = expected_state.to_json()
246
-
247
- assert_valid_schema(result)
248
- assert_orchestration_state_equals(expected, result)
249
-
250
-
251
- def test_failed_tokyo_with_failed_retry_timer_added():
252
- failed_reason = 'Reasons'
253
- failed_details = 'Stuff and Things'
254
- context_builder = ContextBuilder('test_simple_function')
255
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
256
- add_retry_timer_events(context_builder, 1)
257
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
258
- add_retry_timer_events(context_builder, 3)
259
-
260
- result = get_orchestration_state_result(
261
- context_builder, generator_function)
262
-
263
- expected_state = base_expected_state()
264
- add_hello_action(expected_state, 'Tokyo')
265
- expected = expected_state.to_json()
266
-
267
- assert_valid_schema(result)
268
- assert_orchestration_state_equals(expected, result)
269
-
270
-
271
- def test_successful_tokyo_with_failed_retry_timer_added():
272
- failed_reason = 'Reasons'
273
- failed_details = 'Stuff and Things'
274
- context_builder = ContextBuilder('test_simple_function')
275
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
276
- add_retry_timer_events(context_builder, 1)
277
- add_hello_completed_events(context_builder, 2, "\"Hello Tokyo!\"")
278
-
279
- result = get_orchestration_state_result(
280
- context_builder, generator_function)
281
-
282
- expected_state = base_expected_state()
283
- add_hello_action(expected_state, 'Tokyo')
284
- add_hello_action(expected_state, 'Seattle')
285
- expected = expected_state.to_json()
286
-
287
- assert_valid_schema(result)
288
- assert_orchestration_state_equals(expected, result)
289
-
290
-
291
- def test_failed_tokyo_hit_max_attempts():
292
- failed_reason = 'Reasons'
293
- failed_details = 'Stuff and Things'
294
- context_builder = ContextBuilder('test_simple_function')
295
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
296
- add_retry_timer_events(context_builder, 1)
297
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
298
- add_retry_timer_events(context_builder, 3)
299
- add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
300
- add_retry_timer_events(context_builder, 5)
301
-
302
- try:
303
- result = get_orchestration_state_result(
304
- context_builder, generator_function)
305
- # expected an exception
306
- assert False
307
- except Exception as e:
308
- error_label = "\n\n$OutOfProcData$:"
309
- error_str = str(e)
310
-
311
- expected_state = base_expected_state()
312
- add_hello_action(expected_state, 'Tokyo')
313
-
314
- error_msg = f'{failed_reason} \n {failed_details}'
315
- expected_state._error = error_msg
316
- state_str = expected_state.to_json_string()
317
-
318
- expected_error_str = f"{error_msg}{error_label}{state_str}"
319
- assert expected_error_str == error_str
320
-
321
- def test_failed_tokyo_hit_max_attempts_in_try_catch():
322
- # This test ensures that APIs can still be invoked after a failed CallActivityWithRetry invocation
323
- failed_reason = 'Reasons'
324
- failed_details = 'Stuff and Things'
325
- context_builder = ContextBuilder('test_simple_function')
326
-
327
- # events for first task: "Hello Tokyo"
328
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
329
- add_retry_timer_events(context_builder, 1)
330
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
331
- add_retry_timer_events(context_builder, 3)
332
- add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
333
- # we have an "extra" timer to wait for, due to legacy behavior in DTFx.
334
- add_retry_timer_events(context_builder, 5)
335
-
336
- # events to task in except block
337
- add_hello_completed_events(context_builder, 6, "\"Hello Seattle!\"")
338
-
339
- result = get_orchestration_state_result(
340
- context_builder, generator_function_try_catch)
341
-
342
- expected_state = base_expected_state()
343
- add_hello_action(expected_state, 'Tokyo')
344
- add_hello_action(expected_state, 'Seattle')
345
- expected_state._output = "Hello Seattle!"
346
- expected_state._is_done = True
347
- expected = expected_state.to_json()
348
-
349
- assert_valid_schema(result)
350
- assert_orchestration_state_equals(expected, result)
351
-
352
- def test_concurrent_retriable_results():
353
- failed_reason = 'Reasons'
354
- failed_details = 'Stuff and Things'
355
- context_builder = ContextBuilder('test_concurrent_retriable')
356
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
357
- add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
358
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
359
- add_retry_timer_events(context_builder, 3)
360
- add_retry_timer_events(context_builder, 4)
361
- add_retry_timer_events(context_builder, 5)
362
- add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
363
- add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
364
- add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
365
-
366
- result = get_orchestration_state_result(
367
- context_builder, generator_function_concurrent_retries)
368
-
369
- expected_state = base_expected_state()
370
- add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
371
- expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
372
- expected_state._is_done = True
373
- expected = expected_state.to_json()
374
-
375
- assert_valid_schema(result)
376
- assert_orchestration_state_equals(expected, result)
377
-
378
- def test_concurrent_retriable_results_unordered_arrival():
379
- failed_reason = 'Reasons'
380
- failed_details = 'Stuff and Things'
381
- context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
382
- add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
383
- add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
384
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
385
- add_retry_timer_events(context_builder, 3)
386
- add_retry_timer_events(context_builder, 4)
387
- add_retry_timer_events(context_builder, 5)
388
- # events arrive in non-sequential different order
389
- add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
390
- add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
391
- add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
392
-
393
- result = get_orchestration_state_result(
394
- context_builder, generator_function_concurrent_retries)
395
-
396
- expected_state = base_expected_state()
397
- add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
398
- expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
399
- expected_state._is_done = True
400
- expected = expected_state.to_json()
401
-
402
- assert_valid_schema(result)
403
- assert_orchestration_state_equals(expected, result)
404
-
405
- def test_concurrent_retriable_results_mixed_arrival():
406
- failed_reason = 'Reasons'
407
- failed_details = 'Stuff and Things'
408
- context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
409
- # one task succeeds, the other two fail at first, and succeed on retry
410
- add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
411
- add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
412
- add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
413
- add_retry_timer_events(context_builder, 3)
414
- add_retry_timer_events(context_builder, 4)
415
- add_hello_completed_events(context_builder, 6, "\"Hello London!\"")
416
- add_hello_completed_events(context_builder, 5, "\"Hello Seattle!\"")
417
-
418
- result = get_orchestration_state_result(
419
- context_builder, generator_function_concurrent_retries)
420
-
421
- expected_state = base_expected_state()
422
- add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
423
- expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
424
- expected_state._is_done = True
425
- expected = expected_state.to_json()
426
-
427
- assert_valid_schema(result)
428
- assert_orchestration_state_equals(expected, result)
429
-
430
- def test_concurrent_retriable_results_alternating_taskIDs_when_all():
431
- failed_reason = 'Reasons'
432
- failed_details = 'Stuff and Things'
433
- context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
434
-
435
- add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
436
-
437
- result = get_orchestration_state_result(
438
- context_builder, generator_function_two_concurrent_retries_when_all)
439
-
440
- expected_state = base_expected_state()
441
- add_hello_action(expected_state, ['Tokyo', 'Seattle'])
442
- expected_state._output = ["Hello Tokyo!", "Hello Seattle!"]
443
- expected_state._is_done = True
444
- expected = expected_state.to_json()
445
-
446
- assert_valid_schema(result)
447
- assert_orchestration_state_equals(expected, result)
448
-
449
- def test_concurrent_retriable_results_alternating_taskIDs_when_any():
450
- failed_reason = 'Reasons'
451
- failed_details = 'Stuff and Things'
452
- context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
453
-
454
- add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
455
-
456
- result = get_orchestration_state_result(
457
- context_builder, generator_function_two_concurrent_retries_when_any)
458
-
459
- expected_state = base_expected_state()
460
- add_hello_action(expected_state, ['Tokyo', 'Seattle'])
461
- expected_state._output = "Hello Seattle!"
462
- expected_state._is_done = True
463
- expected = expected_state.to_json()
464
-
465
- assert_valid_schema(result)
1
+ from typing import List, Union
2
+ from azure.durable_functions.models.ReplaySchema import ReplaySchema
3
+ from .orchestrator_test_utils \
4
+ import get_orchestration_state_result, assert_orchestration_state_equals, assert_valid_schema
5
+ from tests.test_utils.ContextBuilder import ContextBuilder
6
+ from azure.durable_functions.models.OrchestratorState import OrchestratorState
7
+ from azure.durable_functions.models.RetryOptions import RetryOptions
8
+ from azure.durable_functions.models.actions.CallActivityWithRetryAction \
9
+ import CallActivityWithRetryAction
10
+
11
+
12
+ RETRY_OPTIONS = RetryOptions(5000, 3)
13
+
14
+
15
+ def generator_function(context):
16
+ outputs = []
17
+
18
+ retry_options = RETRY_OPTIONS
19
+ task1 = yield context.call_activity_with_retry(
20
+ "Hello", retry_options, "Tokyo")
21
+ task2 = yield context.call_activity_with_retry(
22
+ "Hello", retry_options, "Seattle")
23
+ task3 = yield context.call_activity_with_retry(
24
+ "Hello", retry_options, "London")
25
+
26
+ outputs.append(task1)
27
+ outputs.append(task2)
28
+ outputs.append(task3)
29
+
30
+ return outputs
31
+
32
+ def generator_function_try_catch(context):
33
+ outputs = []
34
+
35
+ retry_options = RETRY_OPTIONS
36
+ result = None
37
+ try:
38
+ result = yield context.call_activity_with_retry(
39
+ "Hello", retry_options, "Tokyo")
40
+ except:
41
+ result = yield context.call_activity_with_retry(
42
+ "Hello", retry_options, "Seattle")
43
+ return result
44
+
45
+ def generator_function_concurrent_retries(context):
46
+ outputs = []
47
+
48
+ retry_options = RETRY_OPTIONS
49
+ task1 = context.call_activity_with_retry(
50
+ "Hello", retry_options, "Tokyo")
51
+ task2 = context.call_activity_with_retry(
52
+ "Hello", retry_options, "Seattle")
53
+ task3 = context.call_activity_with_retry(
54
+ "Hello", retry_options, "London")
55
+
56
+ outputs = yield context.task_all([task1, task2, task3])
57
+
58
+ return outputs
59
+
60
+ def generator_function_two_concurrent_retries_when_all(context):
61
+ outputs = []
62
+
63
+ retry_options = RETRY_OPTIONS
64
+ task1 = context.call_activity_with_retry(
65
+ "Hello", retry_options, "Tokyo")
66
+ task2 = context.call_activity_with_retry(
67
+ "Hello", retry_options, "Seattle")
68
+
69
+ outputs = yield context.task_all([task1, task2])
70
+
71
+ return outputs
72
+
73
+ def generator_function_two_concurrent_retries_when_any(context):
74
+ outputs = []
75
+
76
+ retry_options = RETRY_OPTIONS
77
+ task1 = context.call_activity_with_retry(
78
+ "Hello", retry_options, "Tokyo")
79
+ task2 = context.call_activity_with_retry(
80
+ "Hello", retry_options, "Seattle")
81
+
82
+ outputs = yield context.task_any([task1, task2])
83
+
84
+ return outputs.result
85
+
86
+
87
+ def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
88
+ return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema.value)
89
+
90
+
91
+ def add_hello_action(state: OrchestratorState, input_: Union[List[str], str]):
92
+ retry_options = RETRY_OPTIONS
93
+ actions = []
94
+ inputs = input_
95
+ if not isinstance(input_, list):
96
+ inputs = [input_]
97
+ for input_ in inputs:
98
+ action = CallActivityWithRetryAction(
99
+ function_name='Hello', retry_options=retry_options, input_=input_)
100
+ actions.append(action)
101
+ state._actions.append(actions)
102
+
103
+
104
+ def add_hello_failed_events(
105
+ context_builder: ContextBuilder, id_: int, reason: str, details: str):
106
+ context_builder.add_task_scheduled_event(name='Hello', id_=id_)
107
+ context_builder.add_orchestrator_completed_event()
108
+ context_builder.add_orchestrator_started_event()
109
+ context_builder.add_task_failed_event(
110
+ id_=id_, reason=reason, details=details)
111
+
112
+
113
+ def add_hello_completed_events(
114
+ context_builder: ContextBuilder, id_: int, result: str):
115
+ context_builder.add_task_scheduled_event(name='Hello', id_=id_)
116
+ context_builder.add_orchestrator_completed_event()
117
+ context_builder.add_orchestrator_started_event()
118
+ context_builder.add_task_completed_event(id_=id_, result=result)
119
+
120
+
121
+ def add_retry_timer_events(context_builder: ContextBuilder, id_: int):
122
+ fire_at = context_builder.add_timer_created_event(id_)
123
+ context_builder.add_orchestrator_completed_event()
124
+ context_builder.add_orchestrator_started_event()
125
+ context_builder.add_timer_fired_event(id_=id_, fire_at=fire_at)
126
+
127
+ def add_two_retriable_events_completing_out_of_order(context_builder: ContextBuilder,
128
+ failed_reason, failed_details):
129
+ ## Schedule tasks
130
+ context_builder.add_task_scheduled_event(name='Hello', id_=0) # Tokyo task
131
+ context_builder.add_task_scheduled_event(name='Hello', id_=1) # Seattle task
132
+
133
+ context_builder.add_orchestrator_completed_event()
134
+ context_builder.add_orchestrator_started_event()
135
+
136
+ ## Task failures and timer-scheduling
137
+
138
+ # tasks fail "out of order"
139
+ context_builder.add_task_failed_event(
140
+ id_=1, reason=failed_reason, details=failed_details) # Seattle task
141
+ fire_at_1 = context_builder.add_timer_created_event(2) # Seattle timer
142
+
143
+ context_builder.add_orchestrator_completed_event()
144
+ context_builder.add_orchestrator_started_event()
145
+
146
+ context_builder.add_task_failed_event(
147
+ id_=0, reason=failed_reason, details=failed_details) # Tokyo task
148
+ fire_at_2 = context_builder.add_timer_created_event(3) # Tokyo timer
149
+
150
+ context_builder.add_orchestrator_completed_event()
151
+ context_builder.add_orchestrator_started_event()
152
+
153
+ ## fire timers
154
+ context_builder.add_timer_fired_event(id_=2, fire_at=fire_at_1) # Seattle timer
155
+ context_builder.add_timer_fired_event(id_=3, fire_at=fire_at_2) # Tokyo timer
156
+
157
+ ## Complete events
158
+ context_builder.add_task_scheduled_event(name='Hello', id_=4) # Seattle task
159
+ context_builder.add_task_scheduled_event(name='Hello', id_=5) # Tokyo task
160
+
161
+ context_builder.add_orchestrator_completed_event()
162
+ context_builder.add_orchestrator_started_event()
163
+ context_builder.add_task_completed_event(id_=4, result="\"Hello Seattle!\"")
164
+ context_builder.add_task_completed_event(id_=5, result="\"Hello Tokyo!\"")
165
+
166
+
167
+ def test_initial_orchestration_state():
168
+ context_builder = ContextBuilder('test_simple_function')
169
+
170
+ result = get_orchestration_state_result(
171
+ context_builder, generator_function)
172
+
173
+ expected_state = base_expected_state()
174
+ add_hello_action(expected_state, 'Tokyo')
175
+ expected = expected_state.to_json()
176
+
177
+ assert_valid_schema(result)
178
+ assert_orchestration_state_equals(expected, result)
179
+
180
+
181
+ def test_tokyo_state():
182
+ context_builder = ContextBuilder('test_simple_function')
183
+ add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
184
+
185
+ result = get_orchestration_state_result(
186
+ context_builder, generator_function)
187
+
188
+ expected_state = base_expected_state()
189
+ add_hello_action(expected_state, 'Tokyo')
190
+ add_hello_action(expected_state, 'Seattle')
191
+ expected = expected_state.to_json()
192
+
193
+ assert_valid_schema(result)
194
+ assert_orchestration_state_equals(expected, result)
195
+
196
+
197
+ def test_failed_tokyo_with_retry():
198
+ failed_reason = 'Reasons'
199
+ failed_details = 'Stuff and Things'
200
+ context_builder = ContextBuilder('test_simple_function')
201
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
202
+
203
+ result = get_orchestration_state_result(
204
+ context_builder, generator_function)
205
+
206
+ expected_state = base_expected_state()
207
+ add_hello_action(expected_state, 'Tokyo')
208
+ expected = expected_state.to_json()
209
+
210
+ assert_valid_schema(result)
211
+ assert_orchestration_state_equals(expected, result)
212
+
213
+
214
+ def test_failed_tokyo_with_timer_entry():
215
+ failed_reason = 'Reasons'
216
+ failed_details = 'Stuff and Things'
217
+ context_builder = ContextBuilder('test_simple_function')
218
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
219
+ add_retry_timer_events(context_builder, 1)
220
+
221
+ result = get_orchestration_state_result(
222
+ context_builder, generator_function)
223
+
224
+ expected_state = base_expected_state()
225
+ add_hello_action(expected_state, 'Tokyo')
226
+ expected = expected_state.to_json()
227
+
228
+ assert_valid_schema(result)
229
+ assert_orchestration_state_equals(expected, result)
230
+
231
+
232
+ def test_failed_tokyo_with_failed_retry():
233
+ failed_reason = 'Reasons'
234
+ failed_details = 'Stuff and Things'
235
+ context_builder = ContextBuilder('test_simple_function')
236
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
237
+ add_retry_timer_events(context_builder, 1)
238
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
239
+
240
+ result = get_orchestration_state_result(
241
+ context_builder, generator_function)
242
+
243
+ expected_state = base_expected_state()
244
+ add_hello_action(expected_state, 'Tokyo')
245
+ expected = expected_state.to_json()
246
+
247
+ assert_valid_schema(result)
248
+ assert_orchestration_state_equals(expected, result)
249
+
250
+
251
+ def test_failed_tokyo_with_failed_retry_timer_added():
252
+ failed_reason = 'Reasons'
253
+ failed_details = 'Stuff and Things'
254
+ context_builder = ContextBuilder('test_simple_function')
255
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
256
+ add_retry_timer_events(context_builder, 1)
257
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
258
+ add_retry_timer_events(context_builder, 3)
259
+
260
+ result = get_orchestration_state_result(
261
+ context_builder, generator_function)
262
+
263
+ expected_state = base_expected_state()
264
+ add_hello_action(expected_state, 'Tokyo')
265
+ expected = expected_state.to_json()
266
+
267
+ assert_valid_schema(result)
268
+ assert_orchestration_state_equals(expected, result)
269
+
270
+
271
+ def test_successful_tokyo_with_failed_retry_timer_added():
272
+ failed_reason = 'Reasons'
273
+ failed_details = 'Stuff and Things'
274
+ context_builder = ContextBuilder('test_simple_function')
275
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
276
+ add_retry_timer_events(context_builder, 1)
277
+ add_hello_completed_events(context_builder, 2, "\"Hello Tokyo!\"")
278
+
279
+ result = get_orchestration_state_result(
280
+ context_builder, generator_function)
281
+
282
+ expected_state = base_expected_state()
283
+ add_hello_action(expected_state, 'Tokyo')
284
+ add_hello_action(expected_state, 'Seattle')
285
+ expected = expected_state.to_json()
286
+
287
+ assert_valid_schema(result)
288
+ assert_orchestration_state_equals(expected, result)
289
+
290
+
291
+ def test_failed_tokyo_hit_max_attempts():
292
+ failed_reason = 'Reasons'
293
+ failed_details = 'Stuff and Things'
294
+ context_builder = ContextBuilder('test_simple_function')
295
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
296
+ add_retry_timer_events(context_builder, 1)
297
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
298
+ add_retry_timer_events(context_builder, 3)
299
+ add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
300
+ add_retry_timer_events(context_builder, 5)
301
+
302
+ try:
303
+ result = get_orchestration_state_result(
304
+ context_builder, generator_function)
305
+ # expected an exception
306
+ assert False
307
+ except Exception as e:
308
+ error_label = "\n\n$OutOfProcData$:"
309
+ error_str = str(e)
310
+
311
+ expected_state = base_expected_state()
312
+ add_hello_action(expected_state, 'Tokyo')
313
+
314
+ error_msg = f'{failed_reason} \n {failed_details}'
315
+ expected_state._error = error_msg
316
+ state_str = expected_state.to_json_string()
317
+
318
+ expected_error_str = f"{error_msg}{error_label}{state_str}"
319
+ assert expected_error_str == error_str
320
+
321
+ def test_failed_tokyo_hit_max_attempts_in_try_catch():
322
+ # This test ensures that APIs can still be invoked after a failed CallActivityWithRetry invocation
323
+ failed_reason = 'Reasons'
324
+ failed_details = 'Stuff and Things'
325
+ context_builder = ContextBuilder('test_simple_function')
326
+
327
+ # events for first task: "Hello Tokyo"
328
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
329
+ add_retry_timer_events(context_builder, 1)
330
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
331
+ add_retry_timer_events(context_builder, 3)
332
+ add_hello_failed_events(context_builder, 4, failed_reason, failed_details)
333
+ # we have an "extra" timer to wait for, due to legacy behavior in DTFx.
334
+ add_retry_timer_events(context_builder, 5)
335
+
336
+ # events to task in except block
337
+ add_hello_completed_events(context_builder, 6, "\"Hello Seattle!\"")
338
+
339
+ result = get_orchestration_state_result(
340
+ context_builder, generator_function_try_catch)
341
+
342
+ expected_state = base_expected_state()
343
+ add_hello_action(expected_state, 'Tokyo')
344
+ add_hello_action(expected_state, 'Seattle')
345
+ expected_state._output = "Hello Seattle!"
346
+ expected_state._is_done = True
347
+ expected = expected_state.to_json()
348
+
349
+ assert_valid_schema(result)
350
+ assert_orchestration_state_equals(expected, result)
351
+
352
+ def test_concurrent_retriable_results():
353
+ failed_reason = 'Reasons'
354
+ failed_details = 'Stuff and Things'
355
+ context_builder = ContextBuilder('test_concurrent_retriable')
356
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
357
+ add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
358
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
359
+ add_retry_timer_events(context_builder, 3)
360
+ add_retry_timer_events(context_builder, 4)
361
+ add_retry_timer_events(context_builder, 5)
362
+ add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
363
+ add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
364
+ add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
365
+
366
+ result = get_orchestration_state_result(
367
+ context_builder, generator_function_concurrent_retries)
368
+
369
+ expected_state = base_expected_state()
370
+ add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
371
+ expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
372
+ expected_state._is_done = True
373
+ expected = expected_state.to_json()
374
+
375
+ assert_valid_schema(result)
376
+ assert_orchestration_state_equals(expected, result)
377
+
378
+ def test_concurrent_retriable_results_unordered_arrival():
379
+ failed_reason = 'Reasons'
380
+ failed_details = 'Stuff and Things'
381
+ context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
382
+ add_hello_failed_events(context_builder, 0, failed_reason, failed_details)
383
+ add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
384
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
385
+ add_retry_timer_events(context_builder, 3)
386
+ add_retry_timer_events(context_builder, 4)
387
+ add_retry_timer_events(context_builder, 5)
388
+ # events arrive in non-sequential different order
389
+ add_hello_completed_events(context_builder, 8, "\"Hello London!\"")
390
+ add_hello_completed_events(context_builder, 6, "\"Hello Tokyo!\"")
391
+ add_hello_completed_events(context_builder, 7, "\"Hello Seattle!\"")
392
+
393
+ result = get_orchestration_state_result(
394
+ context_builder, generator_function_concurrent_retries)
395
+
396
+ expected_state = base_expected_state()
397
+ add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
398
+ expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
399
+ expected_state._is_done = True
400
+ expected = expected_state.to_json()
401
+
402
+ assert_valid_schema(result)
403
+ assert_orchestration_state_equals(expected, result)
404
+
405
+ def test_concurrent_retriable_results_mixed_arrival():
406
+ failed_reason = 'Reasons'
407
+ failed_details = 'Stuff and Things'
408
+ context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
409
+ # one task succeeds, the other two fail at first, and succeed on retry
410
+ add_hello_failed_events(context_builder, 1, failed_reason, failed_details)
411
+ add_hello_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
412
+ add_hello_failed_events(context_builder, 2, failed_reason, failed_details)
413
+ add_retry_timer_events(context_builder, 3)
414
+ add_retry_timer_events(context_builder, 4)
415
+ add_hello_completed_events(context_builder, 6, "\"Hello London!\"")
416
+ add_hello_completed_events(context_builder, 5, "\"Hello Seattle!\"")
417
+
418
+ result = get_orchestration_state_result(
419
+ context_builder, generator_function_concurrent_retries)
420
+
421
+ expected_state = base_expected_state()
422
+ add_hello_action(expected_state, ['Tokyo', 'Seattle', 'London'])
423
+ expected_state._output = ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
424
+ expected_state._is_done = True
425
+ expected = expected_state.to_json()
426
+
427
+ assert_valid_schema(result)
428
+ assert_orchestration_state_equals(expected, result)
429
+
430
+ def test_concurrent_retriable_results_alternating_taskIDs_when_all():
431
+ failed_reason = 'Reasons'
432
+ failed_details = 'Stuff and Things'
433
+ context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
434
+
435
+ add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
436
+
437
+ result = get_orchestration_state_result(
438
+ context_builder, generator_function_two_concurrent_retries_when_all)
439
+
440
+ expected_state = base_expected_state()
441
+ add_hello_action(expected_state, ['Tokyo', 'Seattle'])
442
+ expected_state._output = ["Hello Tokyo!", "Hello Seattle!"]
443
+ expected_state._is_done = True
444
+ expected = expected_state.to_json()
445
+
446
+ assert_valid_schema(result)
447
+ assert_orchestration_state_equals(expected, result)
448
+
449
+ def test_concurrent_retriable_results_alternating_taskIDs_when_any():
450
+ failed_reason = 'Reasons'
451
+ failed_details = 'Stuff and Things'
452
+ context_builder = ContextBuilder('test_concurrent_retriable_unordered_results')
453
+
454
+ add_two_retriable_events_completing_out_of_order(context_builder, failed_reason, failed_details)
455
+
456
+ result = get_orchestration_state_result(
457
+ context_builder, generator_function_two_concurrent_retries_when_any)
458
+
459
+ expected_state = base_expected_state()
460
+ add_hello_action(expected_state, ['Tokyo', 'Seattle'])
461
+ expected_state._output = "Hello Seattle!"
462
+ expected_state._is_done = True
463
+ expected = expected_state.to_json()
464
+
465
+ assert_valid_schema(result)
466
466
  assert_orchestration_state_equals(expected, result)