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.
- azure/durable_functions/__init__.py +81 -81
- azure/durable_functions/constants.py +9 -9
- azure/durable_functions/decorators/__init__.py +3 -3
- azure/durable_functions/decorators/durable_app.py +260 -249
- azure/durable_functions/decorators/metadata.py +109 -109
- azure/durable_functions/entity.py +129 -125
- azure/durable_functions/models/DurableEntityContext.py +201 -201
- azure/durable_functions/models/DurableHttpRequest.py +58 -58
- azure/durable_functions/models/DurableOrchestrationBindings.py +66 -66
- azure/durable_functions/models/DurableOrchestrationClient.py +812 -781
- azure/durable_functions/models/DurableOrchestrationContext.py +761 -707
- azure/durable_functions/models/DurableOrchestrationStatus.py +156 -156
- azure/durable_functions/models/EntityStateResponse.py +23 -23
- azure/durable_functions/models/FunctionContext.py +7 -7
- azure/durable_functions/models/OrchestrationRuntimeStatus.py +32 -32
- azure/durable_functions/models/OrchestratorState.py +117 -116
- azure/durable_functions/models/PurgeHistoryResult.py +33 -33
- azure/durable_functions/models/ReplaySchema.py +9 -8
- azure/durable_functions/models/RetryOptions.py +69 -69
- azure/durable_functions/models/RpcManagementOptions.py +86 -86
- azure/durable_functions/models/Task.py +540 -426
- azure/durable_functions/models/TaskOrchestrationExecutor.py +352 -336
- azure/durable_functions/models/TokenSource.py +56 -56
- azure/durable_functions/models/__init__.py +26 -24
- azure/durable_functions/models/actions/Action.py +23 -23
- azure/durable_functions/models/actions/ActionType.py +18 -18
- azure/durable_functions/models/actions/CallActivityAction.py +41 -41
- azure/durable_functions/models/actions/CallActivityWithRetryAction.py +45 -45
- azure/durable_functions/models/actions/CallEntityAction.py +46 -46
- azure/durable_functions/models/actions/CallHttpAction.py +35 -35
- azure/durable_functions/models/actions/CallSubOrchestratorAction.py +40 -40
- azure/durable_functions/models/actions/CallSubOrchestratorWithRetryAction.py +44 -44
- azure/durable_functions/models/actions/CompoundAction.py +35 -35
- azure/durable_functions/models/actions/ContinueAsNewAction.py +36 -36
- azure/durable_functions/models/actions/CreateTimerAction.py +48 -48
- azure/durable_functions/models/actions/NoOpAction.py +35 -35
- azure/durable_functions/models/actions/SignalEntityAction.py +47 -47
- azure/durable_functions/models/actions/WaitForExternalEventAction.py +63 -63
- azure/durable_functions/models/actions/WhenAllAction.py +14 -14
- azure/durable_functions/models/actions/WhenAnyAction.py +14 -14
- azure/durable_functions/models/actions/__init__.py +24 -24
- azure/durable_functions/models/entities/EntityState.py +74 -74
- azure/durable_functions/models/entities/OperationResult.py +94 -76
- azure/durable_functions/models/entities/RequestMessage.py +53 -53
- azure/durable_functions/models/entities/ResponseMessage.py +48 -48
- azure/durable_functions/models/entities/Signal.py +62 -62
- azure/durable_functions/models/entities/__init__.py +17 -17
- azure/durable_functions/models/history/HistoryEvent.py +92 -92
- azure/durable_functions/models/history/HistoryEventType.py +27 -27
- azure/durable_functions/models/history/__init__.py +8 -8
- azure/durable_functions/models/utils/__init__.py +7 -7
- azure/durable_functions/models/utils/entity_utils.py +103 -91
- azure/durable_functions/models/utils/http_utils.py +80 -69
- azure/durable_functions/models/utils/json_utils.py +96 -56
- azure/durable_functions/orchestrator.py +73 -71
- azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +42 -0
- azure/durable_functions/testing/__init__.py +6 -0
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/LICENSE +21 -21
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/METADATA +59 -58
- azure_functions_durable-1.3.0.dist-info/RECORD +103 -0
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/WHEEL +1 -1
- tests/models/test_DecoratorMetadata.py +135 -135
- tests/models/test_Decorators.py +107 -107
- tests/models/test_DurableOrchestrationBindings.py +68 -68
- tests/models/test_DurableOrchestrationClient.py +730 -730
- tests/models/test_DurableOrchestrationContext.py +102 -102
- tests/models/test_DurableOrchestrationStatus.py +59 -59
- tests/models/test_OrchestrationState.py +28 -28
- tests/models/test_RpcManagementOptions.py +79 -79
- tests/models/test_TokenSource.py +10 -10
- tests/orchestrator/models/OrchestrationInstance.py +18 -18
- tests/orchestrator/orchestrator_test_utils.py +130 -130
- tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
- tests/orchestrator/test_call_http.py +235 -176
- tests/orchestrator/test_continue_as_new.py +67 -67
- tests/orchestrator/test_create_timer.py +126 -126
- tests/orchestrator/test_entity.py +397 -395
- tests/orchestrator/test_external_event.py +53 -53
- tests/orchestrator/test_fan_out_fan_in.py +175 -175
- tests/orchestrator/test_is_replaying_flag.py +101 -101
- tests/orchestrator/test_retries.py +308 -308
- tests/orchestrator/test_sequential_orchestrator.py +841 -841
- tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
- tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
- tests/orchestrator/test_serialization.py +30 -30
- tests/orchestrator/test_sub_orchestrator.py +95 -95
- tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
- tests/orchestrator/test_task_any.py +60 -60
- tests/tasks/tasks_test_utils.py +17 -17
- tests/tasks/test_long_timers.py +70 -0
- tests/tasks/test_new_uuid.py +34 -34
- tests/test_utils/ContextBuilder.py +174 -174
- tests/test_utils/EntityContextBuilder.py +56 -56
- tests/test_utils/constants.py +1 -1
- tests/test_utils/json_utils.py +30 -30
- tests/test_utils/testClasses.py +56 -56
- tests/utils/__init__.py +1 -0
- tests/utils/test_entity_utils.py +24 -0
- azure_functions_durable-1.2.9.data/data/_manifest/bsi.json +0 -1
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.cat +0 -0
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json +0 -11985
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
- azure_functions_durable-1.2.9.dist-info/RECORD +0 -102
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,396 +1,398 @@
|
|
|
1
|
-
from azure.durable_functions.models.ReplaySchema import ReplaySchema
|
|
2
|
-
from .orchestrator_test_utils \
|
|
3
|
-
import assert_orchestration_state_equals, assert_results_are_equal, get_orchestration_state_result, assert_valid_schema, \
|
|
4
|
-
get_entity_state_result, assert_entity_state_equals
|
|
5
|
-
from tests.test_utils.ContextBuilder import ContextBuilder
|
|
6
|
-
from tests.test_utils.EntityContextBuilder import EntityContextBuilder
|
|
7
|
-
from azure.durable_functions.models.OrchestratorState import OrchestratorState
|
|
8
|
-
from azure.durable_functions.models.entities.EntityState import EntityState, OperationResult
|
|
9
|
-
from azure.durable_functions.models.actions.CallEntityAction \
|
|
10
|
-
import CallEntityAction
|
|
11
|
-
from azure.durable_functions.models.actions.SignalEntityAction \
|
|
12
|
-
import SignalEntityAction
|
|
13
|
-
from tests.test_utils.testClasses import SerializableClass
|
|
14
|
-
import azure.durable_functions as df
|
|
15
|
-
from typing import Any, Dict, List
|
|
16
|
-
import json
|
|
17
|
-
import azure.functions as func
|
|
18
|
-
|
|
19
|
-
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def generator_function_call_entity(context):
|
|
23
|
-
outputs = []
|
|
24
|
-
entityId = df.EntityId("Counter", "myCounter")
|
|
25
|
-
x = yield context.call_entity(entityId, "add", 3)
|
|
26
|
-
|
|
27
|
-
outputs.append(x)
|
|
28
|
-
return outputs
|
|
29
|
-
|
|
30
|
-
def generator_function_catch_entity_exception(context):
|
|
31
|
-
entityId = df.EntityId("Counter", "myCounter")
|
|
32
|
-
try:
|
|
33
|
-
yield context.call_entity(entityId, "add", 3)
|
|
34
|
-
return "No exception thrown"
|
|
35
|
-
except:
|
|
36
|
-
return "Exception thrown"
|
|
37
|
-
|
|
38
|
-
def generator_function_signal_entity(context):
|
|
39
|
-
outputs = []
|
|
40
|
-
entityId = df.EntityId("Counter", "myCounter")
|
|
41
|
-
context.signal_entity(entityId, "add", 3)
|
|
42
|
-
x = yield context.call_entity(entityId, "get")
|
|
43
|
-
|
|
44
|
-
outputs.append(x)
|
|
45
|
-
return outputs
|
|
46
|
-
|
|
47
|
-
def counter_entity_function(context):
|
|
48
|
-
"""A Counter Durable Entity.
|
|
49
|
-
|
|
50
|
-
A simple example of a Durable Entity that implements
|
|
51
|
-
a simple counter.
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
current_value = context.get_state(lambda: 0)
|
|
55
|
-
operation = context.operation_name
|
|
56
|
-
if operation == "add":
|
|
57
|
-
amount = context.get_input()
|
|
58
|
-
current_value += amount
|
|
59
|
-
elif operation == "reset":
|
|
60
|
-
current_value = 0
|
|
61
|
-
elif operation == "get":
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
result = f"The state is now: {current_value}"
|
|
65
|
-
context.set_state(current_value)
|
|
66
|
-
context.set_result(result)
|
|
67
|
-
|
|
68
|
-
@app.entity_trigger(context_name="context")
|
|
69
|
-
def counter_entity_function_with_pystein(context):
|
|
70
|
-
"""A Counter Durable Entity.
|
|
71
|
-
|
|
72
|
-
A simple example of a Durable Entity that implements
|
|
73
|
-
a simple counter.
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
current_value = context.get_state(lambda: 0)
|
|
77
|
-
operation = context.operation_name
|
|
78
|
-
if operation == "add":
|
|
79
|
-
amount = context.get_input()
|
|
80
|
-
current_value += amount
|
|
81
|
-
elif operation == "reset":
|
|
82
|
-
current_value = 0
|
|
83
|
-
elif operation == "get":
|
|
84
|
-
pass
|
|
85
|
-
|
|
86
|
-
result = f"The state is now: {current_value}"
|
|
87
|
-
context.set_state(current_value)
|
|
88
|
-
context.set_result(result)
|
|
89
|
-
|
|
90
|
-
def counter_entity_function_raises_exception(context):
|
|
91
|
-
raise Exception("boom!")
|
|
92
|
-
|
|
93
|
-
@app.entity_trigger(context_name="context")
|
|
94
|
-
def counter_entity_function_raises_exception_with_pystein(context):
|
|
95
|
-
raise Exception("boom!")
|
|
96
|
-
|
|
97
|
-
def test_entity_raises_exception():
|
|
98
|
-
# Create input batch
|
|
99
|
-
batch = []
|
|
100
|
-
add_to_batch(batch, name="get")
|
|
101
|
-
context_builder = EntityContextBuilder(batch=batch)
|
|
102
|
-
|
|
103
|
-
# Run the entity, get observed result
|
|
104
|
-
result = get_entity_state_result(
|
|
105
|
-
context_builder,
|
|
106
|
-
counter_entity_function_raises_exception,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Construct expected result
|
|
110
|
-
expected_state = entity_base_expected_state()
|
|
111
|
-
apply_operation(expected_state, result="boom!", state=None, is_error=True)
|
|
112
|
-
expected = expected_state.to_json()
|
|
113
|
-
|
|
114
|
-
# Ensure expectation matches observed behavior
|
|
115
|
-
#assert_valid_schema(result)
|
|
116
|
-
assert_entity_state_equals(expected, result)
|
|
117
|
-
|
|
118
|
-
def test_entity_raises_exception_with_pystein():
|
|
119
|
-
# Create input batch
|
|
120
|
-
batch = []
|
|
121
|
-
add_to_batch(batch, name="get")
|
|
122
|
-
context_builder = EntityContextBuilder(batch=batch)
|
|
123
|
-
|
|
124
|
-
# Run the entity, get observed result
|
|
125
|
-
result = get_entity_state_result(
|
|
126
|
-
context_builder,
|
|
127
|
-
counter_entity_function_raises_exception_with_pystein,
|
|
128
|
-
uses_pystein=True
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
# Construct expected result
|
|
132
|
-
expected_state = entity_base_expected_state()
|
|
133
|
-
apply_operation(expected_state, result="boom!", state=None, is_error=True)
|
|
134
|
-
expected = expected_state.to_json()
|
|
135
|
-
|
|
136
|
-
# Ensure expectation matches observed behavior
|
|
137
|
-
#assert_valid_schema(result)
|
|
138
|
-
assert_entity_state_equals(expected, result)
|
|
139
|
-
|
|
140
|
-
def test_entity_signal_then_call():
|
|
141
|
-
"""Tests that a simple counter entity outputs the correct value
|
|
142
|
-
after a sequence of operations. Mostly just a sanity check.
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
# Create input batch
|
|
146
|
-
batch = []
|
|
147
|
-
add_to_batch(batch, name="add", input_=3)
|
|
148
|
-
add_to_batch(batch, name="get")
|
|
149
|
-
context_builder = EntityContextBuilder(batch=batch)
|
|
150
|
-
|
|
151
|
-
# Run the entity, get observed result
|
|
152
|
-
result = get_entity_state_result(
|
|
153
|
-
context_builder,
|
|
154
|
-
counter_entity_function,
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# Construct expected result
|
|
158
|
-
expected_state = entity_base_expected_state()
|
|
159
|
-
apply_operation(expected_state, result="The state is now: 3", state=3)
|
|
160
|
-
expected = expected_state.to_json()
|
|
161
|
-
|
|
162
|
-
# Ensure expectation matches observed behavior
|
|
163
|
-
#assert_valid_schema(result)
|
|
164
|
-
assert_entity_state_equals(expected, result)
|
|
165
|
-
|
|
166
|
-
def test_entity_signal_then_call_with_pystein():
|
|
167
|
-
"""Tests that a simple counter entity outputs the correct value
|
|
168
|
-
after a sequence of operations. Mostly just a sanity check.
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
# Create input batch
|
|
172
|
-
batch = []
|
|
173
|
-
add_to_batch(batch, name="add", input_=3)
|
|
174
|
-
add_to_batch(batch, name="get")
|
|
175
|
-
context_builder = EntityContextBuilder(batch=batch)
|
|
176
|
-
|
|
177
|
-
# Run the entity, get observed result
|
|
178
|
-
result = get_entity_state_result(
|
|
179
|
-
context_builder,
|
|
180
|
-
counter_entity_function_with_pystein,
|
|
181
|
-
uses_pystein=True
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
# Construct expected result
|
|
185
|
-
expected_state = entity_base_expected_state()
|
|
186
|
-
apply_operation(expected_state, result="The state is now: 3", state=3)
|
|
187
|
-
expected = expected_state.to_json()
|
|
188
|
-
|
|
189
|
-
# Ensure expectation matches observed behavior
|
|
190
|
-
#assert_valid_schema(result)
|
|
191
|
-
assert_entity_state_equals(expected, result)
|
|
192
|
-
|
|
193
|
-
def apply_operation(entity_state: EntityState, result: Any, state: Any, is_error: bool = False):
|
|
194
|
-
"""Apply the effects of an operation to the expected entity state object
|
|
195
|
-
|
|
196
|
-
Parameters
|
|
197
|
-
----------
|
|
198
|
-
entity_state: EntityState
|
|
199
|
-
The expected entity state object
|
|
200
|
-
result: Any
|
|
201
|
-
The result of the latest operation
|
|
202
|
-
state: Any
|
|
203
|
-
The state right after the latest operation
|
|
204
|
-
is_error: bool
|
|
205
|
-
Whether or not the operation resulted in an exception. Defaults to False
|
|
206
|
-
"""
|
|
207
|
-
entity_state.state = state
|
|
208
|
-
|
|
209
|
-
# We cannot control duration, so default it to zero and avoid checking for it
|
|
210
|
-
# in later asserts
|
|
211
|
-
duration = 0
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
context_builder.
|
|
277
|
-
context_builder.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
expected_state
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
1
|
+
from azure.durable_functions.models.ReplaySchema import ReplaySchema
|
|
2
|
+
from .orchestrator_test_utils \
|
|
3
|
+
import assert_orchestration_state_equals, assert_results_are_equal, get_orchestration_state_result, assert_valid_schema, \
|
|
4
|
+
get_entity_state_result, assert_entity_state_equals
|
|
5
|
+
from tests.test_utils.ContextBuilder import ContextBuilder
|
|
6
|
+
from tests.test_utils.EntityContextBuilder import EntityContextBuilder
|
|
7
|
+
from azure.durable_functions.models.OrchestratorState import OrchestratorState
|
|
8
|
+
from azure.durable_functions.models.entities.EntityState import EntityState, OperationResult
|
|
9
|
+
from azure.durable_functions.models.actions.CallEntityAction \
|
|
10
|
+
import CallEntityAction
|
|
11
|
+
from azure.durable_functions.models.actions.SignalEntityAction \
|
|
12
|
+
import SignalEntityAction
|
|
13
|
+
from tests.test_utils.testClasses import SerializableClass
|
|
14
|
+
import azure.durable_functions as df
|
|
15
|
+
from typing import Any, Dict, List
|
|
16
|
+
import json
|
|
17
|
+
import azure.functions as func
|
|
18
|
+
|
|
19
|
+
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def generator_function_call_entity(context):
|
|
23
|
+
outputs = []
|
|
24
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
25
|
+
x = yield context.call_entity(entityId, "add", 3)
|
|
26
|
+
|
|
27
|
+
outputs.append(x)
|
|
28
|
+
return outputs
|
|
29
|
+
|
|
30
|
+
def generator_function_catch_entity_exception(context):
|
|
31
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
32
|
+
try:
|
|
33
|
+
yield context.call_entity(entityId, "add", 3)
|
|
34
|
+
return "No exception thrown"
|
|
35
|
+
except:
|
|
36
|
+
return "Exception thrown"
|
|
37
|
+
|
|
38
|
+
def generator_function_signal_entity(context):
|
|
39
|
+
outputs = []
|
|
40
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
41
|
+
context.signal_entity(entityId, "add", 3)
|
|
42
|
+
x = yield context.call_entity(entityId, "get")
|
|
43
|
+
|
|
44
|
+
outputs.append(x)
|
|
45
|
+
return outputs
|
|
46
|
+
|
|
47
|
+
def counter_entity_function(context):
|
|
48
|
+
"""A Counter Durable Entity.
|
|
49
|
+
|
|
50
|
+
A simple example of a Durable Entity that implements
|
|
51
|
+
a simple counter.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
current_value = context.get_state(lambda: 0)
|
|
55
|
+
operation = context.operation_name
|
|
56
|
+
if operation == "add":
|
|
57
|
+
amount = context.get_input()
|
|
58
|
+
current_value += amount
|
|
59
|
+
elif operation == "reset":
|
|
60
|
+
current_value = 0
|
|
61
|
+
elif operation == "get":
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
result = f"The state is now: {current_value}"
|
|
65
|
+
context.set_state(current_value)
|
|
66
|
+
context.set_result(result)
|
|
67
|
+
|
|
68
|
+
@app.entity_trigger(context_name="context")
|
|
69
|
+
def counter_entity_function_with_pystein(context):
|
|
70
|
+
"""A Counter Durable Entity.
|
|
71
|
+
|
|
72
|
+
A simple example of a Durable Entity that implements
|
|
73
|
+
a simple counter.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
current_value = context.get_state(lambda: 0)
|
|
77
|
+
operation = context.operation_name
|
|
78
|
+
if operation == "add":
|
|
79
|
+
amount = context.get_input()
|
|
80
|
+
current_value += amount
|
|
81
|
+
elif operation == "reset":
|
|
82
|
+
current_value = 0
|
|
83
|
+
elif operation == "get":
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
result = f"The state is now: {current_value}"
|
|
87
|
+
context.set_state(current_value)
|
|
88
|
+
context.set_result(result)
|
|
89
|
+
|
|
90
|
+
def counter_entity_function_raises_exception(context):
|
|
91
|
+
raise Exception("boom!")
|
|
92
|
+
|
|
93
|
+
@app.entity_trigger(context_name="context")
|
|
94
|
+
def counter_entity_function_raises_exception_with_pystein(context):
|
|
95
|
+
raise Exception("boom!")
|
|
96
|
+
|
|
97
|
+
def test_entity_raises_exception():
|
|
98
|
+
# Create input batch
|
|
99
|
+
batch = []
|
|
100
|
+
add_to_batch(batch, name="get")
|
|
101
|
+
context_builder = EntityContextBuilder(batch=batch)
|
|
102
|
+
|
|
103
|
+
# Run the entity, get observed result
|
|
104
|
+
result = get_entity_state_result(
|
|
105
|
+
context_builder,
|
|
106
|
+
counter_entity_function_raises_exception,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Construct expected result
|
|
110
|
+
expected_state = entity_base_expected_state()
|
|
111
|
+
apply_operation(expected_state, result="boom!", state=None, is_error=True)
|
|
112
|
+
expected = expected_state.to_json()
|
|
113
|
+
|
|
114
|
+
# Ensure expectation matches observed behavior
|
|
115
|
+
#assert_valid_schema(result)
|
|
116
|
+
assert_entity_state_equals(expected, result)
|
|
117
|
+
|
|
118
|
+
def test_entity_raises_exception_with_pystein():
|
|
119
|
+
# Create input batch
|
|
120
|
+
batch = []
|
|
121
|
+
add_to_batch(batch, name="get")
|
|
122
|
+
context_builder = EntityContextBuilder(batch=batch)
|
|
123
|
+
|
|
124
|
+
# Run the entity, get observed result
|
|
125
|
+
result = get_entity_state_result(
|
|
126
|
+
context_builder,
|
|
127
|
+
counter_entity_function_raises_exception_with_pystein,
|
|
128
|
+
uses_pystein=True
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Construct expected result
|
|
132
|
+
expected_state = entity_base_expected_state()
|
|
133
|
+
apply_operation(expected_state, result="boom!", state=None, is_error=True)
|
|
134
|
+
expected = expected_state.to_json()
|
|
135
|
+
|
|
136
|
+
# Ensure expectation matches observed behavior
|
|
137
|
+
#assert_valid_schema(result)
|
|
138
|
+
assert_entity_state_equals(expected, result)
|
|
139
|
+
|
|
140
|
+
def test_entity_signal_then_call():
|
|
141
|
+
"""Tests that a simple counter entity outputs the correct value
|
|
142
|
+
after a sequence of operations. Mostly just a sanity check.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# Create input batch
|
|
146
|
+
batch = []
|
|
147
|
+
add_to_batch(batch, name="add", input_=3)
|
|
148
|
+
add_to_batch(batch, name="get")
|
|
149
|
+
context_builder = EntityContextBuilder(batch=batch)
|
|
150
|
+
|
|
151
|
+
# Run the entity, get observed result
|
|
152
|
+
result = get_entity_state_result(
|
|
153
|
+
context_builder,
|
|
154
|
+
counter_entity_function,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Construct expected result
|
|
158
|
+
expected_state = entity_base_expected_state()
|
|
159
|
+
apply_operation(expected_state, result="The state is now: 3", state=3)
|
|
160
|
+
expected = expected_state.to_json()
|
|
161
|
+
|
|
162
|
+
# Ensure expectation matches observed behavior
|
|
163
|
+
#assert_valid_schema(result)
|
|
164
|
+
assert_entity_state_equals(expected, result)
|
|
165
|
+
|
|
166
|
+
def test_entity_signal_then_call_with_pystein():
|
|
167
|
+
"""Tests that a simple counter entity outputs the correct value
|
|
168
|
+
after a sequence of operations. Mostly just a sanity check.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
# Create input batch
|
|
172
|
+
batch = []
|
|
173
|
+
add_to_batch(batch, name="add", input_=3)
|
|
174
|
+
add_to_batch(batch, name="get")
|
|
175
|
+
context_builder = EntityContextBuilder(batch=batch)
|
|
176
|
+
|
|
177
|
+
# Run the entity, get observed result
|
|
178
|
+
result = get_entity_state_result(
|
|
179
|
+
context_builder,
|
|
180
|
+
counter_entity_function_with_pystein,
|
|
181
|
+
uses_pystein=True
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Construct expected result
|
|
185
|
+
expected_state = entity_base_expected_state()
|
|
186
|
+
apply_operation(expected_state, result="The state is now: 3", state=3)
|
|
187
|
+
expected = expected_state.to_json()
|
|
188
|
+
|
|
189
|
+
# Ensure expectation matches observed behavior
|
|
190
|
+
#assert_valid_schema(result)
|
|
191
|
+
assert_entity_state_equals(expected, result)
|
|
192
|
+
|
|
193
|
+
def apply_operation(entity_state: EntityState, result: Any, state: Any, is_error: bool = False):
|
|
194
|
+
"""Apply the effects of an operation to the expected entity state object
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
entity_state: EntityState
|
|
199
|
+
The expected entity state object
|
|
200
|
+
result: Any
|
|
201
|
+
The result of the latest operation
|
|
202
|
+
state: Any
|
|
203
|
+
The state right after the latest operation
|
|
204
|
+
is_error: bool
|
|
205
|
+
Whether or not the operation resulted in an exception. Defaults to False
|
|
206
|
+
"""
|
|
207
|
+
entity_state.state = state
|
|
208
|
+
|
|
209
|
+
# We cannot control duration, so default it to zero and avoid checking for it
|
|
210
|
+
# in later asserts
|
|
211
|
+
duration = 0
|
|
212
|
+
start_time = 0
|
|
213
|
+
operation_result = OperationResult(
|
|
214
|
+
is_error=is_error,
|
|
215
|
+
duration=duration,
|
|
216
|
+
execution_start_time_ms=start_time,
|
|
217
|
+
result=result
|
|
218
|
+
)
|
|
219
|
+
entity_state._results.append(operation_result)
|
|
220
|
+
|
|
221
|
+
def add_to_batch(batch: List[Dict[str, Any]], name: str, input_: Any=None):
|
|
222
|
+
"""Add new work item to the batch of entity operations.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
batch: List[Dict[str, Any]]
|
|
227
|
+
Current list of json-serialized entity work items
|
|
228
|
+
name: str
|
|
229
|
+
Name of the entity operation to be performed
|
|
230
|
+
input_: Optional[Any]:
|
|
231
|
+
Input to the operation. Defaults to None.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
--------
|
|
235
|
+
List[Dict[str, str]]:
|
|
236
|
+
Batch of json-serialized entity work items
|
|
237
|
+
"""
|
|
238
|
+
# It is key to serialize the input twice, as this is
|
|
239
|
+
# the extension behavior
|
|
240
|
+
packet = {
|
|
241
|
+
"name": name,
|
|
242
|
+
"input": json.dumps(json.dumps(input_))
|
|
243
|
+
}
|
|
244
|
+
batch.append(packet)
|
|
245
|
+
return batch
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def entity_base_expected_state() -> EntityState:
|
|
249
|
+
"""Get a base entity state.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
EntityState:
|
|
254
|
+
An EntityState with no results, no signals, a None state, and entity_exists set to True.
|
|
255
|
+
"""
|
|
256
|
+
return EntityState(results=[], signals=[], entity_exists=True, state=None)
|
|
257
|
+
|
|
258
|
+
def add_call_entity_action_for_entity(state: OrchestratorState, id_: df.EntityId, op: str, input_: Any):
|
|
259
|
+
action = CallEntityAction(entity_id=id_, operation=op, input_=input_)
|
|
260
|
+
state.actions.append([action])
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
|
|
264
|
+
return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema.value)
|
|
265
|
+
|
|
266
|
+
def add_call_entity_action(state: OrchestratorState, id_: df.EntityId, op: str, input_: Any):
|
|
267
|
+
action = CallEntityAction(entity_id=id_, operation=op, input_=input_)
|
|
268
|
+
state.actions.append([action])
|
|
269
|
+
|
|
270
|
+
def add_signal_entity_action(state: OrchestratorState, id_: df.EntityId, op: str, input_: Any):
|
|
271
|
+
action = SignalEntityAction(entity_id=id_, operation=op, input_=input_)
|
|
272
|
+
state.actions.append([action])
|
|
273
|
+
|
|
274
|
+
def add_call_entity_completed_events(
|
|
275
|
+
context_builder: ContextBuilder, op: str, instance_id=str, input_=None, event_id=0, is_error=False, literal_input=False):
|
|
276
|
+
context_builder.add_event_sent_event(instance_id, event_id)
|
|
277
|
+
context_builder.add_orchestrator_completed_event()
|
|
278
|
+
context_builder.add_orchestrator_started_event()
|
|
279
|
+
context_builder.add_event_raised_event(name="0000", id_=0, input_=input_, is_entity=True, is_error=is_error, literal_input=literal_input)
|
|
280
|
+
|
|
281
|
+
def test_call_entity_sent():
|
|
282
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
283
|
+
|
|
284
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
285
|
+
result = get_orchestration_state_result(
|
|
286
|
+
context_builder, generator_function_call_entity)
|
|
287
|
+
|
|
288
|
+
expected_state = base_expected_state()
|
|
289
|
+
add_call_entity_action(expected_state, entityId, "add", 3)
|
|
290
|
+
expected = expected_state.to_json()
|
|
291
|
+
|
|
292
|
+
#assert_valid_schema(result)
|
|
293
|
+
assert_orchestration_state_equals(expected, result)
|
|
294
|
+
|
|
295
|
+
def test_signal_entity_sent():
|
|
296
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
297
|
+
|
|
298
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
299
|
+
result = get_orchestration_state_result(
|
|
300
|
+
context_builder, generator_function_signal_entity)
|
|
301
|
+
|
|
302
|
+
expected_state = base_expected_state()
|
|
303
|
+
add_signal_entity_action(expected_state, entityId, "add", 3)
|
|
304
|
+
add_call_entity_action(expected_state, entityId, "get", None)
|
|
305
|
+
expected = expected_state.to_json()
|
|
306
|
+
|
|
307
|
+
#assert_valid_schema(result)
|
|
308
|
+
assert_orchestration_state_equals(expected, result)
|
|
309
|
+
|
|
310
|
+
def test_signal_entity_sent_and_response_received():
|
|
311
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
312
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
313
|
+
add_call_entity_completed_events(context_builder, "get", df.EntityId.get_scheduler_id(entityId), 3, 1)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
result = get_orchestration_state_result(
|
|
317
|
+
context_builder, generator_function_signal_entity)
|
|
318
|
+
|
|
319
|
+
expected_state = base_expected_state([3])
|
|
320
|
+
add_signal_entity_action(expected_state, entityId, "add", 3)
|
|
321
|
+
add_call_entity_action(expected_state, entityId, "get", None)
|
|
322
|
+
expected_state._is_done = True
|
|
323
|
+
expected = expected_state.to_json()
|
|
324
|
+
|
|
325
|
+
#assert_valid_schema(result)
|
|
326
|
+
assert_orchestration_state_equals(expected, result)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def test_call_entity_raised():
|
|
330
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
331
|
+
context_builder = ContextBuilder('test_simple_function')
|
|
332
|
+
add_call_entity_completed_events(context_builder, "add", df.EntityId.get_scheduler_id(entityId), 3, 0)
|
|
333
|
+
|
|
334
|
+
result = get_orchestration_state_result(
|
|
335
|
+
context_builder, generator_function_call_entity)
|
|
336
|
+
|
|
337
|
+
expected_state = base_expected_state(
|
|
338
|
+
[3]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
add_call_entity_action(expected_state, entityId, "add", 3)
|
|
342
|
+
expected_state._is_done = True
|
|
343
|
+
expected = expected_state.to_json()
|
|
344
|
+
|
|
345
|
+
#assert_valid_schema(result)
|
|
346
|
+
|
|
347
|
+
assert_orchestration_state_equals(expected, result)
|
|
348
|
+
|
|
349
|
+
def test_call_entity_catch_exception():
|
|
350
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
351
|
+
context_builder = ContextBuilder('catch exceptions')
|
|
352
|
+
add_call_entity_completed_events(
|
|
353
|
+
context_builder,
|
|
354
|
+
"add",
|
|
355
|
+
df.EntityId.get_scheduler_id(entityId),
|
|
356
|
+
input_="I am an error!",
|
|
357
|
+
event_id=0,
|
|
358
|
+
is_error=True
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
result = get_orchestration_state_result(
|
|
362
|
+
context_builder, generator_function_catch_entity_exception)
|
|
363
|
+
|
|
364
|
+
expected_state = base_expected_state(
|
|
365
|
+
"Exception thrown"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
add_call_entity_action(expected_state, entityId, "add", 3)
|
|
369
|
+
expected_state._is_done = True
|
|
370
|
+
expected = expected_state.to_json()
|
|
371
|
+
|
|
372
|
+
assert_orchestration_state_equals(expected, result)
|
|
373
|
+
|
|
374
|
+
def test_timeout_entity_catch_exception():
|
|
375
|
+
entityId = df.EntityId("Counter", "myCounter")
|
|
376
|
+
context_builder = ContextBuilder('catch timeout exceptions')
|
|
377
|
+
add_call_entity_completed_events(
|
|
378
|
+
context_builder,
|
|
379
|
+
"add",
|
|
380
|
+
df.EntityId.get_scheduler_id(entityId),
|
|
381
|
+
input_="Timeout value of 00:02:00 was exceeded by function: Functions.SlowEntity.",
|
|
382
|
+
event_id=0,
|
|
383
|
+
is_error=False,
|
|
384
|
+
literal_input=True
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
result = get_orchestration_state_result(
|
|
388
|
+
context_builder, generator_function_catch_entity_exception)
|
|
389
|
+
|
|
390
|
+
expected_state = base_expected_state(
|
|
391
|
+
"Exception thrown"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
add_call_entity_action(expected_state, entityId, "add", 3)
|
|
395
|
+
expected_state._is_done = True
|
|
396
|
+
expected = expected_state.to_json()
|
|
397
|
+
|
|
396
398
|
assert_orchestration_state_equals(expected, result)
|