Skip to content

Commit 9358286

Browse files
authored
Merge branch 'dev' into master
2 parents fdd8278 + ceb9de8 commit 9358286

File tree

146 files changed

+2521
-1567
lines changed

Some content is hidden

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

146 files changed

+2521
-1567
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Note: Conda based environments are not yet supported in Azure Functions.
7070
### Setting up durable-py debugging
7171

7272

73-
1. Git clone your fork and use any starter sample from this [folder] in your fork (https://github.com/Azure/azure-functions-durable-python/tree/dev/samples/) and open this folder in your VS Code editor.
73+
1. Git clone your fork and use any starter sample from this [folder](https://github.com/Azure/azure-functions-durable-python/tree/dev/samples/) in your fork and open this folder in your VS Code editor.
7474

7575
2. Initialize this folder as an Azure Functions project using the VS Code Extension using these [instructions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code?pivots=programming-language-python). This step will create a Python virtual environment if one doesn't exist already.
7676

README.md

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,68 +5,26 @@
55

66
# Durable Functions for Python
77

8-
The `azure-functions-durable` [pip](https://pypi.org/project/azure-functions-durable/) package allows you to write [Durable Functions](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview) for [Python](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python). Durable Functions is an extension of [Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview) that lets you write stateful functions and workflows in a serverless environment. The extension manages state, checkpoints, and restarts for you. Durable Functions' advantages include:
8+
[Durable Functions](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview) is an extension of [Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview) that lets you write stateful functions in a serverless compute environment. The extension lets you define stateful workflows by writing orchestrator functions and stateful entities by writing entity functions using the Azure Functions programming model. Behind the scenes, the extension manages state, checkpoints, and restarts for you, allowing you to focus on your business logic.
9+
10+
🐍 Find us on PyPi [here](https://pypi.org/project/azure-functions-durable/) 🐍
911

10-
* Define workflows in code. No JSON schemas or designers are needed.
11-
* Call other functions synchronously and asynchronously. Output from called functions can be saved to local variables.
12-
* Automatically checkpoint progress whenever the function schedules async work. Local state is never lost if the process recycles or the VM reboots.
1312

1413
You can find more information at the following links:
1514

1615
* [Azure Functions overview](https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview)
1716
* [Azure Functions Python developers guide](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-python)
1817
* [Durable Functions overview](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=python)
18+
* [Core concepts and features overview](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-types-features-overview).
1919

20-
A durable function, or _orchestration_, is a solution made up of different types of Azure Functions:
21-
22-
* **Activity:** the functions and tasks being orchestrated by your workflow.
23-
* **Orchestrator:** a function that describes the way and order actions are executed in code.
24-
* **Client:** the entry point for creating an instance of a durable orchestration.
25-
26-
Durable Functions' function types and features are documented in-depth [here.](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-types-features-overview)
27-
28-
## Current limitations
29-
30-
Python support for Durable Functions is currently in public preview. The following are the current known limitations.
31-
32-
### Functionality
33-
34-
* Sub-orchestrations are not yet supported (planned [#62](https://github.com/Azure/azure-functions-durable-python/issues/62))
35-
* Durable Entities are not yet supported (not yet planned [#96](https://github.com/Azure/azure-functions-durable-python/issues/96))
36-
37-
### Tooling
38-
39-
* Python Durable Functions requires [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local) version 3.0.2630 or higher.
20+
> Durable Functions expects certain programming constraints to be followed. Please read the documentation linked above for more information.
4021
4122
## Getting Started
4223

4324
Follow these instructions to get started with Durable Functions in Python:
4425

4526
**🚀 [Python Durable Functions quickstart](https://docs.microsoft.com/azure/azure-functions/durable/quickstart-python-vscode)**
4627

47-
## Samples
48-
49-
Take a look at this project's [samples directory](./samples/):
50-
51-
* [Function Chaining](./samples/function_chaining)
52-
* [Fan-out/Fan-in - Simple](./samples/fan_out_fan_in)
53-
* [Fan-out/Fan-in - TensorFlow](./samples/fan_out_fan_in_tensorflow)
54-
* [External Events - Human Interaction & Timeouts](./samples/external_events)
55-
56-
### Orchestrator example
57-
58-
```python
59-
import azure.durable_functions as df
60-
61-
62-
def orchestrator_function(context: df.DurableOrchestrationContext):
63-
task1 = yield context.call_activity("DurableActivity", "One")
64-
task2 = yield context.call_activity("DurableActivity", "Two")
65-
task3 = yield context.call_activity("DurableActivity", "Three")
66-
67-
outputs = [task1, task2, task3]
68-
return outputs
69-
28+
## Tooling
7029

71-
main = df.Orchestrator.create(orchestrator_function)
72-
```
30+
* Python Durable Functions requires [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local) version 3.0.2630 or higher.

azure/durable_functions/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
Exposes the different API components intended for public consumption
44
"""
55
from .orchestrator import Orchestrator
6+
from .entity import Entity
7+
from .models.utils.entity_utils import EntityId
68
from .models.DurableOrchestrationClient import DurableOrchestrationClient
79
from .models.DurableOrchestrationContext import DurableOrchestrationContext
10+
from .models.DurableEntityContext import DurableEntityContext
811
from .models.RetryOptions import RetryOptions
912
from .models.TokenSource import ManagedIdentityTokenSource
1013

1114
__all__ = [
1215
'Orchestrator',
16+
'Entity',
17+
'EntityId',
1318
'DurableOrchestrationClient',
19+
'DurableEntityContext',
1420
'DurableOrchestrationContext',
1521
'ManagedIdentityTokenSource',
1622
'RetryOptions'

azure/durable_functions/entity.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +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

0 commit comments

Comments
 (0)