Skip to content

Commit 7442d1c

Browse files
authored
Merge dev to main for 1.2.5 release (#448)
1 parent 1105d8c commit 7442d1c

File tree

15 files changed

+315
-30
lines changed

15 files changed

+315
-30
lines changed

azure/durable_functions/models/DurableOrchestrationContext.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ def _get_function_name(self, name: FunctionBuilder,
683683
name = name._function._name
684684
return name
685685
else:
686-
if(trigger_type == OrchestrationTrigger):
686+
if (trigger_type == OrchestrationTrigger):
687687
trigger_type = "OrchestrationTrigger"
688688
else:
689689
trigger_type = "ActivityTrigger"

azure/durable_functions/models/Task.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ def __init__(self, id_: Union[int, str], actions: Union[List[Action], Action]):
5656
self.result: Any = None
5757
self.action_repr: Union[List[Action], Action] = actions
5858
self.is_played = False
59+
self._is_scheduled_flag = False
60+
61+
@property
62+
def _is_scheduled(self) -> bool:
63+
return self._is_scheduled_flag
64+
65+
def _set_is_scheduled(self, is_scheduled: bool):
66+
self._is_scheduled_flag = is_scheduled
5967

6068
@property
6169
def is_completed(self) -> bool:
@@ -158,7 +166,8 @@ def __init__(self, tasks: List[TaskBase], compound_action_constructor=None):
158166
if isinstance(action_repr, list):
159167
child_actions.extend(action_repr)
160168
else:
161-
child_actions.append(action_repr)
169+
if not task._is_scheduled:
170+
child_actions.append(action_repr)
162171
if compound_action_constructor is None:
163172
self.action_repr = child_actions
164173
else: # replay_schema is ReplaySchema.V2
@@ -176,6 +185,10 @@ def __init__(self, tasks: List[TaskBase], compound_action_constructor=None):
176185
if not (child.state is TaskState.RUNNING):
177186
self.handle_completion(child)
178187

188+
@property
189+
def _is_scheduled(self) -> bool:
190+
return all([child._is_scheduled for child in self.children])
191+
179192
def handle_completion(self, child: TaskBase):
180193
"""Manage sub-task completion events.
181194

azure/durable_functions/models/TaskOrchestrationExecutor.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from azure.durable_functions.models.Task import TaskBase, TaskState, AtomicTask
1+
from azure.durable_functions.models.Task import TaskBase, TaskState, AtomicTask, CompoundTask
22
from azure.durable_functions.models.OrchestratorState import OrchestratorState
33
from azure.durable_functions.models.DurableOrchestrationContext import DurableOrchestrationContext
44
from typing import Any, List, Optional, Union
@@ -229,7 +229,8 @@ def resume_user_code(self):
229229
task_succeeded = current_task.state is TaskState.SUCCEEDED
230230
new_task = self.generator.send(
231231
task_value) if task_succeeded else self.generator.throw(task_value)
232-
self.context._add_to_open_tasks(new_task)
232+
if isinstance(new_task, TaskBase) and not (new_task._is_scheduled):
233+
self.context._add_to_open_tasks(new_task)
233234
except StopIteration as stop_exception:
234235
# the orchestration returned,
235236
# flag it as such and capture its output
@@ -245,9 +246,17 @@ def resume_user_code(self):
245246
# user yielded the same task multiple times, continue executing code
246247
# until a new/not-previously-yielded task is encountered
247248
self.resume_user_code()
248-
else:
249+
elif not (self.current_task._is_scheduled):
249250
# new task is received. it needs to be resolved to a value
250251
self.context._add_to_actions(self.current_task.action_repr)
252+
self._mark_as_scheduled(self.current_task)
253+
254+
def _mark_as_scheduled(self, task: TaskBase):
255+
if isinstance(task, CompoundTask):
256+
for task in task.children:
257+
self._mark_as_scheduled(task)
258+
else:
259+
task._set_is_scheduled(True)
251260

252261
def get_orchestrator_state_str(self) -> str:
253262
"""Obtain a JSON-formatted string representing the orchestration's state.

samples-v2/blueprint/.funcignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.git*
2+
.vscode
3+
__azurite_db*__.json
4+
__blobstorage__
5+
__queuestorage__
6+
local.settings.json
7+
test
8+
.venv

samples-v2/blueprint/.gitignore

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
pip-wheel-metadata/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
.hypothesis/
51+
.pytest_cache/
52+
53+
# Translations
54+
*.mo
55+
*.pot
56+
57+
# Django stuff:
58+
*.log
59+
local_settings.py
60+
db.sqlite3
61+
62+
# Flask stuff:
63+
instance/
64+
.webassets-cache
65+
66+
# Scrapy stuff:
67+
.scrapy
68+
69+
# Sphinx documentation
70+
docs/_build/
71+
72+
# PyBuilder
73+
target/
74+
75+
# Jupyter Notebook
76+
.ipynb_checkpoints
77+
78+
# IPython
79+
profile_default/
80+
ipython_config.py
81+
82+
# pyenv
83+
.python-version
84+
85+
# pipenv
86+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
87+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
88+
# having no cross-platform support, pipenv may install dependencies that don’t work, or not
89+
# install all needed dependencies.
90+
#Pipfile.lock
91+
92+
# celery beat schedule file
93+
celerybeat-schedule
94+
95+
# SageMath parsed files
96+
*.sage.py
97+
98+
# Environments
99+
.env
100+
.venv
101+
env/
102+
venv/
103+
ENV/
104+
env.bak/
105+
venv.bak/
106+
107+
# Spyder project settings
108+
.spyderproject
109+
.spyproject
110+
111+
# Rope project settings
112+
.ropeproject
113+
114+
# mkdocs documentation
115+
/site
116+
117+
# mypy
118+
.mypy_cache/
119+
.dmypy.json
120+
dmypy.json
121+
122+
# Pyre type checker
123+
.pyre/
124+
125+
# Azure Functions artifacts
126+
bin
127+
obj
128+
appsettings.json
129+
local.settings.json
130+
131+
# Azurite artifacts
132+
__blobstorage__
133+
__queuestorage__
134+
__azurite_db*__.json
135+
.python_packages
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import logging
2+
import azure.functions as func
3+
import azure.durable_functions as df
4+
5+
# To learn more about blueprints in the Python prog model V2,
6+
# see: https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=asgi%2Capplication-level&pivots=python-mode-decorators#blueprints
7+
8+
# Note, the `func` namespace does not contain Durable Functions triggers and bindings, so to register blueprints of
9+
# DF we need to use the `df` package's version of blueprints.
10+
bp = df.Blueprint()
11+
12+
# We define a standard function-chaining DF pattern
13+
14+
@bp.route(route="startOrchestrator")
15+
@bp.durable_client_input(client_name="client")
16+
async def start_orchestrator(req: func.HttpRequest, client):
17+
instance_id = await client.start_new("my_orchestrator")
18+
19+
logging.info(f"Started orchestration with ID = '{instance_id}'.")
20+
return client.create_check_status_response(req, instance_id)
21+
22+
@bp.orchestration_trigger(context_name="context")
23+
def my_orchestrator(context: df.DurableOrchestrationContext):
24+
result1 = yield context.call_activity('say_hello', "Tokyo")
25+
result2 = yield context.call_activity('say_hello', "Seattle")
26+
result3 = yield context.call_activity('say_hello', "London")
27+
return [result1, result2, result3]
28+
29+
@bp.activity_trigger(input_name="city")
30+
def say_hello(city: str) -> str:
31+
return f"Hello {city}!"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import azure.functions as func
2+
import logging
3+
4+
from durable_blueprints import bp
5+
6+
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)
7+
app.register_functions(bp) # register the DF functions
8+
9+
# Define a simple HTTP trigger function, to show that you can also
10+
# register functions via the `app` object
11+
@app.route(route="HttpTrigger")
12+
def HttpTrigger(req: func.HttpRequest) -> func.HttpResponse:
13+
logging.info('Python HTTP trigger function processed a request.')
14+
15+
name = req.params.get('name')
16+
if not name:
17+
try:
18+
req_body = req.get_json()
19+
except ValueError:
20+
pass
21+
else:
22+
name = req_body.get('name')
23+
24+
if name:
25+
return func.HttpResponse(f"Hello, {name}. This HTTP triggered function executed successfully.")
26+
else:
27+
return func.HttpResponse(
28+
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
29+
status_code=200
30+
)

samples-v2/blueprint/host.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "2.0",
3+
"logging": {
4+
"applicationInsights": {
5+
"samplingSettings": {
6+
"isEnabled": true,
7+
"excludedTypes": "Request"
8+
}
9+
}
10+
},
11+
"extensionBundle": {
12+
"id": "Microsoft.Azure.Functions.ExtensionBundle",
13+
"version": "[4.*, 5.0.0)"
14+
}
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# DO NOT include azure-functions-worker in this file
2+
# The Python Worker is managed by Azure Functions platform
3+
# Manually managing azure-functions-worker may cause unexpected issues
4+
5+
azure-functions
6+
azure-functions-durable>=1.2.4

samples-v2/fan_in_fan_out/extensions.csproj

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)