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.
- 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 +249 -249
- azure/durable_functions/decorators/metadata.py +109 -109
- azure/durable_functions/entity.py +125 -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 +781 -711
- azure/durable_functions/models/DurableOrchestrationContext.py +722 -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 -29
- azure/durable_functions/models/OrchestratorState.py +117 -116
- azure/durable_functions/models/PurgeHistoryResult.py +33 -33
- azure/durable_functions/models/ReplaySchema.py +8 -8
- azure/durable_functions/models/RetryOptions.py +69 -69
- azure/durable_functions/models/RpcManagementOptions.py +86 -86
- azure/durable_functions/models/Task.py +426 -426
- azure/durable_functions/models/TaskOrchestrationExecutor.py +346 -333
- azure/durable_functions/models/TokenSource.py +56 -56
- azure/durable_functions/models/__init__.py +24 -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 +76 -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 -25
- 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 +69 -69
- azure/durable_functions/models/utils/json_utils.py +56 -56
- azure/durable_functions/orchestrator.py +71 -71
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/LICENSE +21 -21
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/METADATA +58 -58
- azure_functions_durable-1.2.10.dist-info/RECORD +100 -0
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.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 -56
- tests/models/test_DurableOrchestrationClient.py +730 -612
- 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 +395 -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 -801
- 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_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.8.data/data/_manifest/bsi.json +0 -1
- azure_functions_durable-1.2.8.data/data/_manifest/manifest.cat +0 -0
- azure_functions_durable-1.2.8.data/data/_manifest/manifest.spdx.json +0 -12845
- azure_functions_durable-1.2.8.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
- azure_functions_durable-1.2.8.dist-info/RECORD +0 -102
- {azure_functions_durable-1.2.8.dist-info → azure_functions_durable-1.2.10.dist-info}/top_level.txt +0 -0
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
from azure.durable_functions.constants import ORCHESTRATION_TRIGGER, \
|
|
6
|
-
ACTIVITY_TRIGGER, ENTITY_TRIGGER, DURABLE_CLIENT
|
|
7
|
-
from azure.functions.decorators.core import Trigger, InputBinding
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class OrchestrationTrigger(Trigger):
|
|
11
|
-
"""OrchestrationTrigger.
|
|
12
|
-
|
|
13
|
-
Trigger representing an Orchestration Function.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
@staticmethod
|
|
17
|
-
def get_binding_name() -> str:
|
|
18
|
-
"""Get the name of this trigger, as a string.
|
|
19
|
-
|
|
20
|
-
Returns
|
|
21
|
-
-------
|
|
22
|
-
str
|
|
23
|
-
The string representation of this trigger.
|
|
24
|
-
"""
|
|
25
|
-
return ORCHESTRATION_TRIGGER
|
|
26
|
-
|
|
27
|
-
def __init__(self,
|
|
28
|
-
name: str,
|
|
29
|
-
orchestration: Optional[str] = None,
|
|
30
|
-
) -> None:
|
|
31
|
-
self.orchestration = orchestration
|
|
32
|
-
super().__init__(name=name)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class ActivityTrigger(Trigger):
|
|
36
|
-
"""ActivityTrigger.
|
|
37
|
-
|
|
38
|
-
Trigger representing a Durable Functions Activity.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
@staticmethod
|
|
42
|
-
def get_binding_name() -> str:
|
|
43
|
-
"""Get the name of this trigger, as a string.
|
|
44
|
-
|
|
45
|
-
Returns
|
|
46
|
-
-------
|
|
47
|
-
str
|
|
48
|
-
The string representation of this trigger.
|
|
49
|
-
"""
|
|
50
|
-
return ACTIVITY_TRIGGER
|
|
51
|
-
|
|
52
|
-
def __init__(self,
|
|
53
|
-
name: str,
|
|
54
|
-
activity: Optional[str] = None,
|
|
55
|
-
) -> None:
|
|
56
|
-
self.activity = activity
|
|
57
|
-
super().__init__(name=name)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class EntityTrigger(Trigger):
|
|
61
|
-
"""EntityTrigger.
|
|
62
|
-
|
|
63
|
-
Trigger representing an Entity Function.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
@staticmethod
|
|
67
|
-
def get_binding_name() -> str:
|
|
68
|
-
"""Get the name of this trigger, as a string.
|
|
69
|
-
|
|
70
|
-
Returns
|
|
71
|
-
-------
|
|
72
|
-
str
|
|
73
|
-
The string representation of this trigger.
|
|
74
|
-
"""
|
|
75
|
-
return ENTITY_TRIGGER
|
|
76
|
-
|
|
77
|
-
def __init__(self,
|
|
78
|
-
name: str,
|
|
79
|
-
entity_name: Optional[str] = None,
|
|
80
|
-
) -> None:
|
|
81
|
-
self.entity_name = entity_name
|
|
82
|
-
super().__init__(name=name)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class DurableClient(InputBinding):
|
|
86
|
-
"""DurableClient.
|
|
87
|
-
|
|
88
|
-
Binding representing a Durable-client object.
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
@staticmethod
|
|
92
|
-
def get_binding_name() -> str:
|
|
93
|
-
"""Get the name of this Binding, as a string.
|
|
94
|
-
|
|
95
|
-
Returns
|
|
96
|
-
-------
|
|
97
|
-
str
|
|
98
|
-
The string representation of this binding.
|
|
99
|
-
"""
|
|
100
|
-
return DURABLE_CLIENT
|
|
101
|
-
|
|
102
|
-
def __init__(self,
|
|
103
|
-
name: str,
|
|
104
|
-
task_hub: Optional[str] = None,
|
|
105
|
-
connection_name: Optional[str] = None
|
|
106
|
-
) -> None:
|
|
107
|
-
self.task_hub = task_hub
|
|
108
|
-
self.connection_name = connection_name
|
|
109
|
-
super().__init__(name=name)
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from azure.durable_functions.constants import ORCHESTRATION_TRIGGER, \
|
|
6
|
+
ACTIVITY_TRIGGER, ENTITY_TRIGGER, DURABLE_CLIENT
|
|
7
|
+
from azure.functions.decorators.core import Trigger, InputBinding
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrchestrationTrigger(Trigger):
|
|
11
|
+
"""OrchestrationTrigger.
|
|
12
|
+
|
|
13
|
+
Trigger representing an Orchestration Function.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_binding_name() -> str:
|
|
18
|
+
"""Get the name of this trigger, as a string.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
str
|
|
23
|
+
The string representation of this trigger.
|
|
24
|
+
"""
|
|
25
|
+
return ORCHESTRATION_TRIGGER
|
|
26
|
+
|
|
27
|
+
def __init__(self,
|
|
28
|
+
name: str,
|
|
29
|
+
orchestration: Optional[str] = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
self.orchestration = orchestration
|
|
32
|
+
super().__init__(name=name)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ActivityTrigger(Trigger):
|
|
36
|
+
"""ActivityTrigger.
|
|
37
|
+
|
|
38
|
+
Trigger representing a Durable Functions Activity.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def get_binding_name() -> str:
|
|
43
|
+
"""Get the name of this trigger, as a string.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
str
|
|
48
|
+
The string representation of this trigger.
|
|
49
|
+
"""
|
|
50
|
+
return ACTIVITY_TRIGGER
|
|
51
|
+
|
|
52
|
+
def __init__(self,
|
|
53
|
+
name: str,
|
|
54
|
+
activity: Optional[str] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
self.activity = activity
|
|
57
|
+
super().__init__(name=name)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class EntityTrigger(Trigger):
|
|
61
|
+
"""EntityTrigger.
|
|
62
|
+
|
|
63
|
+
Trigger representing an Entity Function.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def get_binding_name() -> str:
|
|
68
|
+
"""Get the name of this trigger, as a string.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
str
|
|
73
|
+
The string representation of this trigger.
|
|
74
|
+
"""
|
|
75
|
+
return ENTITY_TRIGGER
|
|
76
|
+
|
|
77
|
+
def __init__(self,
|
|
78
|
+
name: str,
|
|
79
|
+
entity_name: Optional[str] = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
self.entity_name = entity_name
|
|
82
|
+
super().__init__(name=name)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DurableClient(InputBinding):
|
|
86
|
+
"""DurableClient.
|
|
87
|
+
|
|
88
|
+
Binding representing a Durable-client object.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def get_binding_name() -> str:
|
|
93
|
+
"""Get the name of this Binding, as a string.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
str
|
|
98
|
+
The string representation of this binding.
|
|
99
|
+
"""
|
|
100
|
+
return DURABLE_CLIENT
|
|
101
|
+
|
|
102
|
+
def __init__(self,
|
|
103
|
+
name: str,
|
|
104
|
+
task_hub: Optional[str] = None,
|
|
105
|
+
connection_name: Optional[str] = None
|
|
106
|
+
) -> None:
|
|
107
|
+
self.task_hub = task_hub
|
|
108
|
+
self.connection_name = connection_name
|
|
109
|
+
super().__init__(name=name)
|
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
from .models import DurableEntityContext
|
|
2
|
-
from .models.entities import OperationResult, EntityState
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from typing import Callable, Any, List, Dict
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class InternalEntityException(Exception):
|
|
8
|
-
"""Framework-internal Exception class (for internal use only)."""
|
|
9
|
-
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Entity:
|
|
14
|
-
"""Durable Entity Class.
|
|
15
|
-
|
|
16
|
-
Responsible for executing the user-defined entity function.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, entity_func: Callable[[DurableEntityContext], None]):
|
|
20
|
-
"""Create a new entity for the user-defined entity.
|
|
21
|
-
|
|
22
|
-
Responsible for executing the user-defined entity function
|
|
23
|
-
|
|
24
|
-
Parameters
|
|
25
|
-
----------
|
|
26
|
-
entity_func: Callable[[DurableEntityContext], Generator[Any, Any, Any]]
|
|
27
|
-
The user defined entity function
|
|
28
|
-
"""
|
|
29
|
-
self.fn: Callable[[DurableEntityContext], None] = entity_func
|
|
30
|
-
|
|
31
|
-
def handle(self, context: DurableEntityContext, batch: List[Dict[str, Any]]) -> str:
|
|
32
|
-
"""Handle the execution of the user-defined entity function.
|
|
33
|
-
|
|
34
|
-
Loops over the batch, which serves to specify inputs to the entity,
|
|
35
|
-
and collects results and generates a final state, which are returned.
|
|
36
|
-
|
|
37
|
-
Parameters
|
|
38
|
-
----------
|
|
39
|
-
context: DurableEntityContext
|
|
40
|
-
The entity context of the entity, which the user interacts with as their Durable API
|
|
41
|
-
|
|
42
|
-
Returns
|
|
43
|
-
-------
|
|
44
|
-
str
|
|
45
|
-
A JSON-formatted string representing the output state, results, and exceptions for the
|
|
46
|
-
entity execution.
|
|
47
|
-
"""
|
|
48
|
-
response = EntityState(results=[], signals=[])
|
|
49
|
-
for operation_data in batch:
|
|
50
|
-
result: Any = None
|
|
51
|
-
is_error: bool = False
|
|
52
|
-
start_time: datetime = datetime.now()
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
# populate context
|
|
56
|
-
operation = operation_data["name"]
|
|
57
|
-
if operation is None:
|
|
58
|
-
message = "Durable Functions Internal Error:"\
|
|
59
|
-
"Entity operation was missing a name field"
|
|
60
|
-
raise InternalEntityException(message)
|
|
61
|
-
context._operation = operation
|
|
62
|
-
context._input = operation_data["input"]
|
|
63
|
-
self.fn(context)
|
|
64
|
-
result = context._result
|
|
65
|
-
|
|
66
|
-
except InternalEntityException as e:
|
|
67
|
-
raise e
|
|
68
|
-
|
|
69
|
-
except Exception as e:
|
|
70
|
-
is_error = True
|
|
71
|
-
result = str(e)
|
|
72
|
-
|
|
73
|
-
duration: int = self._elapsed_milliseconds_since(start_time)
|
|
74
|
-
operation_result = OperationResult(
|
|
75
|
-
is_error=is_error,
|
|
76
|
-
duration=duration,
|
|
77
|
-
result=result
|
|
78
|
-
)
|
|
79
|
-
response.results.append(operation_result)
|
|
80
|
-
|
|
81
|
-
response.state = context._state
|
|
82
|
-
response.entity_exists = context._exists
|
|
83
|
-
return response.to_json_string()
|
|
84
|
-
|
|
85
|
-
@classmethod
|
|
86
|
-
def create(cls, fn: Callable[[DurableEntityContext], None]) -> Callable[[Any], str]:
|
|
87
|
-
"""Create an instance of the entity class.
|
|
88
|
-
|
|
89
|
-
Parameters
|
|
90
|
-
----------
|
|
91
|
-
fn (Callable[[DurableEntityContext], None]): [description]
|
|
92
|
-
|
|
93
|
-
Returns
|
|
94
|
-
-------
|
|
95
|
-
Callable[[Any], str]
|
|
96
|
-
Handle function of the newly created entity client
|
|
97
|
-
"""
|
|
98
|
-
def handle(context) -> str:
|
|
99
|
-
# It is not clear when the context JSON would be found
|
|
100
|
-
# inside a "body"-key, but this pattern matches the
|
|
101
|
-
# orchestrator implementation, so we keep it for safety.
|
|
102
|
-
context_body = getattr(context, "body", None)
|
|
103
|
-
if context_body is None:
|
|
104
|
-
context_body = context
|
|
105
|
-
ctx, batch = DurableEntityContext.from_json(context_body)
|
|
106
|
-
return Entity(fn).handle(ctx, batch)
|
|
107
|
-
return handle
|
|
108
|
-
|
|
109
|
-
def _elapsed_milliseconds_since(self, start_time: datetime) -> int:
|
|
110
|
-
"""Calculate the elapsed time, in milliseconds, from the start_time to the present.
|
|
111
|
-
|
|
112
|
-
Parameters
|
|
113
|
-
----------
|
|
114
|
-
start_time: datetime
|
|
115
|
-
The timestamp of when the entity began processing a batched request.
|
|
116
|
-
|
|
117
|
-
Returns
|
|
118
|
-
-------
|
|
119
|
-
int
|
|
120
|
-
The time, in millseconds, from start_time to now
|
|
121
|
-
"""
|
|
122
|
-
end_time = datetime.now()
|
|
123
|
-
time_diff = end_time - start_time
|
|
124
|
-
elapsed_time = int(time_diff.total_seconds() * 1000)
|
|
125
|
-
return elapsed_time
|
|
1
|
+
from .models import DurableEntityContext
|
|
2
|
+
from .models.entities import OperationResult, EntityState
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Callable, Any, List, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InternalEntityException(Exception):
|
|
8
|
+
"""Framework-internal Exception class (for internal use only)."""
|
|
9
|
+
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Entity:
|
|
14
|
+
"""Durable Entity Class.
|
|
15
|
+
|
|
16
|
+
Responsible for executing the user-defined entity function.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, entity_func: Callable[[DurableEntityContext], None]):
|
|
20
|
+
"""Create a new entity for the user-defined entity.
|
|
21
|
+
|
|
22
|
+
Responsible for executing the user-defined entity function
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
entity_func: Callable[[DurableEntityContext], Generator[Any, Any, Any]]
|
|
27
|
+
The user defined entity function
|
|
28
|
+
"""
|
|
29
|
+
self.fn: Callable[[DurableEntityContext], None] = entity_func
|
|
30
|
+
|
|
31
|
+
def handle(self, context: DurableEntityContext, batch: List[Dict[str, Any]]) -> str:
|
|
32
|
+
"""Handle the execution of the user-defined entity function.
|
|
33
|
+
|
|
34
|
+
Loops over the batch, which serves to specify inputs to the entity,
|
|
35
|
+
and collects results and generates a final state, which are returned.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
context: DurableEntityContext
|
|
40
|
+
The entity context of the entity, which the user interacts with as their Durable API
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
str
|
|
45
|
+
A JSON-formatted string representing the output state, results, and exceptions for the
|
|
46
|
+
entity execution.
|
|
47
|
+
"""
|
|
48
|
+
response = EntityState(results=[], signals=[])
|
|
49
|
+
for operation_data in batch:
|
|
50
|
+
result: Any = None
|
|
51
|
+
is_error: bool = False
|
|
52
|
+
start_time: datetime = datetime.now()
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
# populate context
|
|
56
|
+
operation = operation_data["name"]
|
|
57
|
+
if operation is None:
|
|
58
|
+
message = "Durable Functions Internal Error:"\
|
|
59
|
+
"Entity operation was missing a name field"
|
|
60
|
+
raise InternalEntityException(message)
|
|
61
|
+
context._operation = operation
|
|
62
|
+
context._input = operation_data["input"]
|
|
63
|
+
self.fn(context)
|
|
64
|
+
result = context._result
|
|
65
|
+
|
|
66
|
+
except InternalEntityException as e:
|
|
67
|
+
raise e
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
is_error = True
|
|
71
|
+
result = str(e)
|
|
72
|
+
|
|
73
|
+
duration: int = self._elapsed_milliseconds_since(start_time)
|
|
74
|
+
operation_result = OperationResult(
|
|
75
|
+
is_error=is_error,
|
|
76
|
+
duration=duration,
|
|
77
|
+
result=result
|
|
78
|
+
)
|
|
79
|
+
response.results.append(operation_result)
|
|
80
|
+
|
|
81
|
+
response.state = context._state
|
|
82
|
+
response.entity_exists = context._exists
|
|
83
|
+
return response.to_json_string()
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def create(cls, fn: Callable[[DurableEntityContext], None]) -> Callable[[Any], str]:
|
|
87
|
+
"""Create an instance of the entity class.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
fn (Callable[[DurableEntityContext], None]): [description]
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
Callable[[Any], str]
|
|
96
|
+
Handle function of the newly created entity client
|
|
97
|
+
"""
|
|
98
|
+
def handle(context) -> str:
|
|
99
|
+
# It is not clear when the context JSON would be found
|
|
100
|
+
# inside a "body"-key, but this pattern matches the
|
|
101
|
+
# orchestrator implementation, so we keep it for safety.
|
|
102
|
+
context_body = getattr(context, "body", None)
|
|
103
|
+
if context_body is None:
|
|
104
|
+
context_body = context
|
|
105
|
+
ctx, batch = DurableEntityContext.from_json(context_body)
|
|
106
|
+
return Entity(fn).handle(ctx, batch)
|
|
107
|
+
return handle
|
|
108
|
+
|
|
109
|
+
def _elapsed_milliseconds_since(self, start_time: datetime) -> int:
|
|
110
|
+
"""Calculate the elapsed time, in milliseconds, from the start_time to the present.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
start_time: datetime
|
|
115
|
+
The timestamp of when the entity began processing a batched request.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
int
|
|
120
|
+
The time, in millseconds, from start_time to now
|
|
121
|
+
"""
|
|
122
|
+
end_time = datetime.now()
|
|
123
|
+
time_diff = end_time - start_time
|
|
124
|
+
elapsed_time = int(time_diff.total_seconds() * 1000)
|
|
125
|
+
return elapsed_time
|