azure-functions-durable 1.2.9__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. azure/durable_functions/__init__.py +81 -81
  2. azure/durable_functions/constants.py +9 -9
  3. azure/durable_functions/decorators/__init__.py +3 -3
  4. azure/durable_functions/decorators/durable_app.py +260 -249
  5. azure/durable_functions/decorators/metadata.py +109 -109
  6. azure/durable_functions/entity.py +129 -125
  7. azure/durable_functions/models/DurableEntityContext.py +201 -201
  8. azure/durable_functions/models/DurableHttpRequest.py +58 -58
  9. azure/durable_functions/models/DurableOrchestrationBindings.py +66 -66
  10. azure/durable_functions/models/DurableOrchestrationClient.py +812 -781
  11. azure/durable_functions/models/DurableOrchestrationContext.py +761 -707
  12. azure/durable_functions/models/DurableOrchestrationStatus.py +156 -156
  13. azure/durable_functions/models/EntityStateResponse.py +23 -23
  14. azure/durable_functions/models/FunctionContext.py +7 -7
  15. azure/durable_functions/models/OrchestrationRuntimeStatus.py +32 -32
  16. azure/durable_functions/models/OrchestratorState.py +117 -116
  17. azure/durable_functions/models/PurgeHistoryResult.py +33 -33
  18. azure/durable_functions/models/ReplaySchema.py +9 -8
  19. azure/durable_functions/models/RetryOptions.py +69 -69
  20. azure/durable_functions/models/RpcManagementOptions.py +86 -86
  21. azure/durable_functions/models/Task.py +540 -426
  22. azure/durable_functions/models/TaskOrchestrationExecutor.py +352 -336
  23. azure/durable_functions/models/TokenSource.py +56 -56
  24. azure/durable_functions/models/__init__.py +26 -24
  25. azure/durable_functions/models/actions/Action.py +23 -23
  26. azure/durable_functions/models/actions/ActionType.py +18 -18
  27. azure/durable_functions/models/actions/CallActivityAction.py +41 -41
  28. azure/durable_functions/models/actions/CallActivityWithRetryAction.py +45 -45
  29. azure/durable_functions/models/actions/CallEntityAction.py +46 -46
  30. azure/durable_functions/models/actions/CallHttpAction.py +35 -35
  31. azure/durable_functions/models/actions/CallSubOrchestratorAction.py +40 -40
  32. azure/durable_functions/models/actions/CallSubOrchestratorWithRetryAction.py +44 -44
  33. azure/durable_functions/models/actions/CompoundAction.py +35 -35
  34. azure/durable_functions/models/actions/ContinueAsNewAction.py +36 -36
  35. azure/durable_functions/models/actions/CreateTimerAction.py +48 -48
  36. azure/durable_functions/models/actions/NoOpAction.py +35 -35
  37. azure/durable_functions/models/actions/SignalEntityAction.py +47 -47
  38. azure/durable_functions/models/actions/WaitForExternalEventAction.py +63 -63
  39. azure/durable_functions/models/actions/WhenAllAction.py +14 -14
  40. azure/durable_functions/models/actions/WhenAnyAction.py +14 -14
  41. azure/durable_functions/models/actions/__init__.py +24 -24
  42. azure/durable_functions/models/entities/EntityState.py +74 -74
  43. azure/durable_functions/models/entities/OperationResult.py +94 -76
  44. azure/durable_functions/models/entities/RequestMessage.py +53 -53
  45. azure/durable_functions/models/entities/ResponseMessage.py +48 -48
  46. azure/durable_functions/models/entities/Signal.py +62 -62
  47. azure/durable_functions/models/entities/__init__.py +17 -17
  48. azure/durable_functions/models/history/HistoryEvent.py +92 -92
  49. azure/durable_functions/models/history/HistoryEventType.py +27 -27
  50. azure/durable_functions/models/history/__init__.py +8 -8
  51. azure/durable_functions/models/utils/__init__.py +7 -7
  52. azure/durable_functions/models/utils/entity_utils.py +103 -91
  53. azure/durable_functions/models/utils/http_utils.py +80 -69
  54. azure/durable_functions/models/utils/json_utils.py +96 -56
  55. azure/durable_functions/orchestrator.py +73 -71
  56. azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +42 -0
  57. azure/durable_functions/testing/__init__.py +6 -0
  58. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/LICENSE +21 -21
  59. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/METADATA +59 -58
  60. azure_functions_durable-1.3.0.dist-info/RECORD +103 -0
  61. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/WHEEL +1 -1
  62. tests/models/test_DecoratorMetadata.py +135 -135
  63. tests/models/test_Decorators.py +107 -107
  64. tests/models/test_DurableOrchestrationBindings.py +68 -68
  65. tests/models/test_DurableOrchestrationClient.py +730 -730
  66. tests/models/test_DurableOrchestrationContext.py +102 -102
  67. tests/models/test_DurableOrchestrationStatus.py +59 -59
  68. tests/models/test_OrchestrationState.py +28 -28
  69. tests/models/test_RpcManagementOptions.py +79 -79
  70. tests/models/test_TokenSource.py +10 -10
  71. tests/orchestrator/models/OrchestrationInstance.py +18 -18
  72. tests/orchestrator/orchestrator_test_utils.py +130 -130
  73. tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
  74. tests/orchestrator/test_call_http.py +235 -176
  75. tests/orchestrator/test_continue_as_new.py +67 -67
  76. tests/orchestrator/test_create_timer.py +126 -126
  77. tests/orchestrator/test_entity.py +397 -395
  78. tests/orchestrator/test_external_event.py +53 -53
  79. tests/orchestrator/test_fan_out_fan_in.py +175 -175
  80. tests/orchestrator/test_is_replaying_flag.py +101 -101
  81. tests/orchestrator/test_retries.py +308 -308
  82. tests/orchestrator/test_sequential_orchestrator.py +841 -841
  83. tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
  84. tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
  85. tests/orchestrator/test_serialization.py +30 -30
  86. tests/orchestrator/test_sub_orchestrator.py +95 -95
  87. tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
  88. tests/orchestrator/test_task_any.py +60 -60
  89. tests/tasks/tasks_test_utils.py +17 -17
  90. tests/tasks/test_long_timers.py +70 -0
  91. tests/tasks/test_new_uuid.py +34 -34
  92. tests/test_utils/ContextBuilder.py +174 -174
  93. tests/test_utils/EntityContextBuilder.py +56 -56
  94. tests/test_utils/constants.py +1 -1
  95. tests/test_utils/json_utils.py +30 -30
  96. tests/test_utils/testClasses.py +56 -56
  97. tests/utils/__init__.py +1 -0
  98. tests/utils/test_entity_utils.py +24 -0
  99. azure_functions_durable-1.2.9.data/data/_manifest/bsi.json +0 -1
  100. azure_functions_durable-1.2.9.data/data/_manifest/manifest.cat +0 -0
  101. azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json +0 -11985
  102. azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
  103. azure_functions_durable-1.2.9.dist-info/RECORD +0 -102
  104. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/top_level.txt +0 -0
@@ -1,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,129 @@
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, timezone
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(timezone.utc)
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
+ execution_start_time_ms=int(start_time.timestamp() * 1000),
78
+ result=result
79
+ )
80
+ response.results.append(operation_result)
81
+
82
+ response.state = context._state
83
+ response.entity_exists = context._exists
84
+ return response.to_json_string()
85
+
86
+ @classmethod
87
+ def create(cls, fn: Callable[[DurableEntityContext], None]) -> Callable[[Any], str]:
88
+ """Create an instance of the entity class.
89
+
90
+ Parameters
91
+ ----------
92
+ fn (Callable[[DurableEntityContext], None]): [description]
93
+
94
+ Returns
95
+ -------
96
+ Callable[[Any], str]
97
+ Handle function of the newly created entity client
98
+ """
99
+ def handle(context) -> str:
100
+ # It is not clear when the context JSON would be found
101
+ # inside a "body"-key, but this pattern matches the
102
+ # orchestrator implementation, so we keep it for safety.
103
+ context_body = getattr(context, "body", None)
104
+ if context_body is None:
105
+ context_body = context
106
+ ctx, batch = DurableEntityContext.from_json(context_body)
107
+ return Entity(fn).handle(ctx, batch)
108
+
109
+ handle.entity_function = fn
110
+
111
+ return handle
112
+
113
+ def _elapsed_milliseconds_since(self, start_time: datetime) -> int:
114
+ """Calculate the elapsed time, in milliseconds, from the start_time to the present.
115
+
116
+ Parameters
117
+ ----------
118
+ start_time: datetime
119
+ The timestamp of when the entity began processing a batched request.
120
+
121
+ Returns
122
+ -------
123
+ int
124
+ The time, in millseconds, from start_time to now
125
+ """
126
+ end_time = datetime.now(timezone.utc)
127
+ time_diff = end_time - start_time
128
+ elapsed_time = int(time_diff.total_seconds() * 1000)
129
+ return elapsed_time