Skip to content

Commit 2bb6db7

Browse files
Promote dev to main for 1.0.1 release (#298)
Co-authored-by: Martijn Lentink <[email protected]>
1 parent f92dd38 commit 2bb6db7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+458
-58
lines changed

azure/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Base module for the Python Durable functions."""
22
from pkgutil import extend_path
3-
__path__ = extend_path(__path__, __name__)
3+
__path__ = extend_path(__path__, __name__) # type: ignore

azure/durable_functions/models/DurableOrchestrationClient.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .PurgeHistoryResult import PurgeHistoryResult
1111
from .DurableOrchestrationStatus import DurableOrchestrationStatus
12+
from .EntityStateResponse import EntityStateResponse
1213
from .RpcManagementOptions import RpcManagementOptions
1314
from .OrchestrationRuntimeStatus import OrchestrationRuntimeStatus
1415
from ..models.DurableOrchestrationBindings import DurableOrchestrationBindings
@@ -132,6 +133,56 @@ def create_http_management_payload(self, instance_id: str) -> Dict[str, str]:
132133
"""
133134
return self.get_client_response_links(None, instance_id)
134135

136+
async def read_entity_state(
137+
self,
138+
entityId: EntityId,
139+
task_hub_name: Optional[str] = None,
140+
connection_name: Optional[str] = None,
141+
) -> EntityStateResponse:
142+
"""Read the state of the entity.
143+
144+
Parameters
145+
----------
146+
entityId : EntityId
147+
The EntityId of the targeted entity.
148+
task_hub_name : Optional[str]
149+
The task hub name of the target entity.
150+
connection_name : Optional[str]
151+
The name of the connection string associated with [task_hub_name].
152+
153+
Raises
154+
------
155+
Exception:
156+
When an unexpected status code is returned
157+
158+
Returns
159+
-------
160+
EntityStateResponse
161+
container object representing the state of the entity
162+
"""
163+
options = RpcManagementOptions(
164+
connection_name=connection_name,
165+
task_hub_name=task_hub_name,
166+
entity_Id=entityId,
167+
)
168+
169+
request_url = options.to_url(self._orchestration_bindings.rpc_base_url)
170+
response = await self._get_async_request(request_url)
171+
172+
switch_statement = {
173+
200: lambda: EntityStateResponse(True, response[1]),
174+
404: lambda: EntityStateResponse(False),
175+
}
176+
177+
result = switch_statement.get(response[0])
178+
179+
if not result:
180+
raise Exception(
181+
f"The operation failed with an unexpected status code {response[0]}"
182+
)
183+
184+
return result()
185+
135186
def get_client_response_links(
136187
self,
137188
request: Optional[func.HttpRequest], instance_id: str) -> Dict[str, str]:
@@ -440,6 +491,8 @@ async def wait_for_completion_or_create_check_status_response(
440491
lambda: self._create_http_response(200, status.to_json()),
441492
OrchestrationRuntimeStatus.Failed:
442493
lambda: self._create_http_response(500, status.to_json()),
494+
None:
495+
None
443496
}
444497

445498
result = switch_statement.get(status.runtime_status)
@@ -456,6 +509,7 @@ async def wait_for_completion_or_create_check_status_response(
456509
await sleep(sleep_time)
457510
else:
458511
return self.create_check_status_response(request, instance_id)
512+
return self.create_check_status_response(request, instance_id)
459513

460514
async def signal_entity(self, entityId: EntityId, operation_name: str,
461515
operation_input: Optional[Any] = None,
@@ -640,6 +694,7 @@ async def rewind(self,
640694

641695
response = await self._post_async_request(request_url, None)
642696
status: int = response[0]
697+
ex_msg: str = ""
643698
if status == 200 or status == 202:
644699
return
645700
elif status == 404:
@@ -648,6 +703,9 @@ async def rewind(self,
648703
elif status == 410:
649704
ex_msg = "The rewind operation is only supported on failed orchestration instances."
650705
raise Exception(ex_msg)
651-
else:
706+
elif isinstance(response[1], str):
652707
ex_msg = response[1]
653708
raise Exception(ex_msg)
709+
else:
710+
ex_msg = "Received unexpected payload from the durable-extension: " + str(response)
711+
raise Exception(ex_msg)

azure/durable_functions/models/DurableOrchestrationContext.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from azure.durable_functions.models.ReplaySchema import ReplaySchema
12
import json
23
import datetime
4+
import inspect
35
from typing import List, Any, Dict, Optional
46
from uuid import UUID, uuid5, NAMESPACE_URL
7+
from datetime import timezone
58

69
from .RetryOptions import RetryOptions
710
from .TaskSet import TaskSet
@@ -28,7 +31,7 @@ class DurableOrchestrationContext:
2831
# parameter names are as defined by JSON schema and do not conform to PEP8 naming conventions
2932
def __init__(self,
3033
history: List[Dict[Any, Any]], instanceId: str, isReplaying: bool,
31-
parentInstanceId: str, input: Any = None, **kwargs):
34+
parentInstanceId: str, input: Any = None, upperSchemaVersion: int = 0, **kwargs):
3235
self._histories: List[HistoryEvent] = [HistoryEvent(**he) for he in history]
3336
self._instance_id: str = instanceId
3437
self._is_replaying: bool = isReplaying
@@ -43,8 +46,11 @@ def __init__(self,
4346
self._current_utc_datetime: datetime.datetime = \
4447
self.decision_started_event.timestamp
4548
self._new_uuid_counter = 0
46-
self.actions: List[List[Action]] = []
4749
self._function_context: FunctionContext = FunctionContext(**kwargs)
50+
self._replay_schema = ReplaySchema(upperSchemaVersion)
51+
self.actions: List[List[Action]] = []
52+
if self._replay_schema == ReplaySchema.V2:
53+
self.actions.append([])
4854

4955
# make _input always a string
5056
# (consistent with Python Functions generic trigger/input bindings)
@@ -238,7 +244,7 @@ def task_all(self, activities: List[Task]) -> TaskSet:
238244
TaskSet
239245
The results of all activities.
240246
"""
241-
return task_all(tasks=activities)
247+
return task_all(tasks=activities, replay_schema=self._replay_schema)
242248

243249
def task_any(self, activities: List[Task]) -> TaskSet:
244250
"""Schedule the execution of all activities.
@@ -258,7 +264,7 @@ def task_any(self, activities: List[Task]) -> TaskSet:
258264
TaskSet
259265
The first [[Task]] instance to complete.
260266
"""
261-
return task_any(tasks=activities)
267+
return task_any(tasks=activities, replay_schema=self._replay_schema)
262268

263269
def set_custom_status(self, status: Any):
264270
"""Set the customized orchestration status for your orchestrator function.
@@ -459,3 +465,15 @@ def new_guid(self) -> UUID:
459465
self._new_uuid_counter += 1
460466
guid = uuid5(NAMESPACE_URL, guid_name)
461467
return guid
468+
469+
def _pretty_print_history(self) -> str:
470+
"""Get a pretty-printed version of the orchestration's internal history."""
471+
def history_to_string(event):
472+
json_dict = {}
473+
for key, val in inspect.getmembers(event):
474+
if not key.startswith('_') and not inspect.ismethod(val):
475+
if isinstance(val, datetime.date):
476+
val = val.replace(tzinfo=timezone.utc).timetuple()
477+
json_dict[key] = val
478+
return json.dumps(json_dict)
479+
return str(list(map(history_to_string, self._histories)))
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Any
2+
3+
4+
class EntityStateResponse:
5+
"""Entity state response object for [read_entity_state]."""
6+
7+
def __init__(self, entity_exists: bool, entity_state: Any = None) -> None:
8+
self._entity_exists = entity_exists
9+
self._entity_state = entity_state
10+
11+
@property
12+
def entity_exists(self) -> bool:
13+
"""Get the bool representing whether entity exists."""
14+
return self._entity_exists
15+
16+
@property
17+
def entity_state(self) -> Any:
18+
"""Get the state of the entity.
19+
20+
When [entity_exists] is False, this value will be None.
21+
Optional.
22+
"""
23+
return self._entity_state

azure/durable_functions/models/OrchestratorState.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
from typing import List, Any, Dict, Optional, Union
33

4+
from azure.durable_functions.models.ReplaySchema import ReplaySchema
5+
46
from .utils.json_utils import add_attrib
57
from azure.durable_functions.models.actions.Action import Action
68

@@ -16,13 +18,15 @@ def __init__(self,
1618
is_done: bool,
1719
actions: List[List[Action]],
1820
output: Any,
21+
replay_schema: ReplaySchema,
1922
error: str = None,
2023
custom_status: Any = None):
2124
self._is_done: bool = is_done
2225
self._actions: List[List[Action]] = actions
2326
self._output: Any = output
2427
self._error: Optional[str] = error
2528
self._custom_status: Any = custom_status
29+
self._replay_schema: ReplaySchema = replay_schema
2630

2731
@property
2832
def actions(self) -> List[List[Action]]:
@@ -66,6 +70,11 @@ def custom_status(self):
6670
"""Get the JSON-serializable value used by DurableOrchestrationContext.SetCustomStatus."""
6771
return self._custom_status
6872

73+
@property
74+
def schema_version(self):
75+
"""Get the Replay Schema represented in this OrchestratorState payload."""
76+
return self._replay_schema.value
77+
6978
def to_json(self) -> Dict[str, Union[str, int]]:
7079
"""Convert object into a json dictionary.
7180
@@ -76,6 +85,8 @@ def to_json(self) -> Dict[str, Union[str, int]]:
7685
"""
7786
json_dict: Dict[str, Union[str, int]] = {}
7887
add_attrib(json_dict, self, '_is_done', 'isDone')
88+
if self._replay_schema != ReplaySchema.V1:
89+
add_attrib(json_dict, self, 'schema_version', 'schemaVersion')
7990
self._add_actions(json_dict)
8091
if not (self._output is None):
8192
json_dict['output'] = self._output
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from enum import Enum
2+
3+
4+
class ReplaySchema(Enum):
5+
"""Enum representing the ReplaySchemas supported by this SDK version."""
6+
7+
V1 = 0
8+
V2 = 1

azure/durable_functions/models/Task.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self, is_completed, is_faulted, action,
2626
self._id = id_
2727
self._exception = exc
2828
self._is_played = is_played
29+
self._is_yielded: bool = False
2930

3031
@property
3132
def is_completed(self) -> bool:

azure/durable_functions/models/TaskSet.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(self, is_completed, actions, result, is_faulted=False,
2525
self._timestamp: datetime = timestamp
2626
self._exception = exception
2727
self._is_played = is_played
28+
self._is_yielded: bool = False
2829

2930
@property
3031
def is_completed(self) -> bool:

azure/durable_functions/models/actions/ActionType.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ class ActionType(IntEnum):
1414
CALL_ENTITY = 7
1515
CALL_HTTP: int = 8
1616
SIGNAL_ENTITY: int = 9
17+
WHEN_ANY = 11
18+
WHEN_ALL = 12
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import Dict, Union
2+
3+
from .Action import Action
4+
from ..utils.json_utils import add_attrib
5+
from typing import List
6+
from abc import abstractmethod
7+
8+
9+
class CompoundAction(Action):
10+
"""Defines the structure of the WhenAll Action object.
11+
12+
Provides the information needed by the durable extension to be able to invoke WhenAll tasks.
13+
"""
14+
15+
def __init__(self, compoundTasks: List[Action]):
16+
self.compound_actions = list(map(lambda x: x.to_json(), compoundTasks))
17+
18+
@property
19+
@abstractmethod
20+
def action_type(self) -> int:
21+
"""Get this object's action type as an integer."""
22+
...
23+
24+
def to_json(self) -> Dict[str, Union[str, int]]:
25+
"""Convert object into a json dictionary.
26+
27+
Returns
28+
-------
29+
Dict[str, Union[str, int]]
30+
The instance of the class converted into a json dictionary
31+
"""
32+
json_dict: Dict[str, Union[str, int]] = {}
33+
add_attrib(json_dict, self, 'action_type', 'actionType')
34+
add_attrib(json_dict, self, 'compound_actions', 'compoundActions')
35+
return json_dict

0 commit comments

Comments
 (0)