Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/ISSUE_TEMPLATE/----feature-request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: "\U0001F4A1 Feature request"
about: Suggest an idea for this project!
title: ''
labels: ''
assignees: ''

---

💡 **Feature description**
> What would you like to see, and why?
> What kinds of usage do you expect to see in this feature?

💭 **Describe alternatives you've considered**
> What are other ways to achieve this? Have you thought of alternative designs or solutions?
> Any existing workarounds? Why are they not sufficient? We'd like to know!

**Additional context**
> Add any other context or screenshots about the feature request here.
31 changes: 31 additions & 0 deletions .github/ISSUE_TEMPLATE/---bug-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: "\U0001F41B Bug report"
about: Did something not work out as expected? Let us know!
title: ''
labels: ''
assignees: ''

---

🐛 **Describe the bug**
> A clear and concise description of what the bug is.

🤔 **Expected behavior**
> What should have happened?

☕ **Steps to reproduce**
> What Durable Functions patterns are you using, if any?
> Any minimal reproducer we can use?
> Are you running this locally or on Azure?

⚡**If deployed to Azure**
> We have access to a lot of telemetry that can help with investigations. Please provide as much of the following information as you can to help us investigate!

- **Timeframe issue observed**:
- **Function App name**:
- **Function name(s)**:
- **Azure region**:
- **Orchestration instance ID(s)**:
- **Azure storage account name**:

> If you don't want to share your Function App or storage account name GitHub, please at least share the orchestration instance ID. Otherwise it's extremely difficult to look up information.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
|Branch|Status|
|---|---|
|main|[![Build Status](https://azfunc.visualstudio.com/Azure%20Functions%20Python/_apis/build/status/Azure%20Functions%20Durable%20Python?branchName=main)](https://azfunc.visualstudio.com/Azure%20Functions%20Python/_build/latest?definitionId=44&branchName=main)|
|dev|[![Build Status](https://azfunc.visualstudio.com/Azure%20Functions%20Python/_apis/build/status/Azure%20Functions%20Durable%20Python?branchName=dev)](https://azfunc.visualstudio.com/Azure%20Functions%20Python/_build/latest?definitionId=44&branchName=dev)|
|main|[![Build Status](https://azfunc.visualstudio.com/Azure%20Functions/_apis/build/status/Azure.azure-functions-durable-python?branchName=main)](https://azfunc.visualstudio.com/Azure%20Functions/_build/latest?definitionId=58&branchName=main)|
|dev|[![Build Status](https://azfunc.visualstudio.com/Azure%20Functions/_apis/build/status/Azure.azure-functions-durable-python?branchName=dev)](https://azfunc.visualstudio.com/Azure%20Functions/_build/latest?definitionId=58&branchName=dev)|

# Durable Functions for Python

Expand All @@ -27,4 +27,4 @@ Follow these instructions to get started with Durable Functions in Python:

## Tooling

* 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.
* 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.
3 changes: 1 addition & 2 deletions azure/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
"""Base module for the Python Durable functions."""
from pkgutil import extend_path
import typing
__path__: typing.Iterable[str] = extend_path(__path__, __name__)
__path__ = extend_path(__path__, __name__)
16 changes: 11 additions & 5 deletions azure/durable_functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@
from .entity import Entity
from .models.utils.entity_utils import EntityId
from .models.DurableOrchestrationClient import DurableOrchestrationClient
from .models.OrchestrationRuntimeStatus import OrchestrationRuntimeStatus
from .models.DurableOrchestrationContext import DurableOrchestrationContext
from .models.DurableEntityContext import DurableEntityContext
from .models.RetryOptions import RetryOptions
from .models.TokenSource import ManagedIdentityTokenSource
import json
from pathlib import Path
import sys
import warnings


def validate_extension_bundles():
"""Throw an exception if host.json contains bundle-range V1.
"""Raise a warning if host.json contains bundle-range V1.

Raises
Effects
------
Exception: Exception prompting the user to update to bundles V2
Warning: Warning prompting the user to update to bundles V2
"""
# No need to validate if we're running tests
if "pytest" in sys.modules:
Expand All @@ -46,10 +48,13 @@ def validate_extension_bundles():
# We do a best-effort attempt to detect bundles V1
# This is the string hard-coded into the bundles V1 template in VSCode
if version_range == "[1.*, 2.0.0)":
message = "Durable Functions for Python does not support Bundles V1."\
message = "Your application is currently configured to use Extension Bundles V1."\
" Durable Functions for Python works best with Bundles V2,"\
" which provides additional features like Durable Entities, better performance,"\
" and is actively being developed."\
" Please update to Bundles V2 in your `host.json`."\
" You can set extensionBundles version to be: [2.*, 3.0.0)"
raise Exception(message)
warnings.warn(message)


# Validate that users are not in extension bundles V1
Expand All @@ -63,5 +68,6 @@ def validate_extension_bundles():
'DurableEntityContext',
'DurableOrchestrationContext',
'ManagedIdentityTokenSource',
'OrchestrationRuntimeStatus',
'RetryOptions'
]
15 changes: 15 additions & 0 deletions azure/durable_functions/models/DurableOrchestrationContext.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import datetime
from typing import List, Any, Dict, Optional
from uuid import UUID, uuid5, NAMESPACE_URL

from .RetryOptions import RetryOptions
from .TaskSet import TaskSet
Expand Down Expand Up @@ -444,3 +445,17 @@ def continue_as_new(self, input_: Any):
The new starting input to the orchestrator.
"""
return continue_as_new(context=self, input_=input_)

def new_guid(self) -> UUID:
"""Generate a replay-safe GUID.

Returns
-------
UUID
A new globally-unique ID
"""
guid_name = f"{self.instance_id}_{self.current_utc_datetime}"\
f"_{self._new_uuid_counter}"
self._new_uuid_counter += 1
guid = uuid5(NAMESPACE_URL, guid_name)
return guid
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def run(self, *args, **kwargs):
'Operating System :: POSIX',
'Operating System :: MacOS :: MacOS X',
'Environment :: Web Environment',
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
],
license='MIT',
python_requires='>=3.6,<4',
Expand Down
32 changes: 32 additions & 0 deletions tests/orchestrator/test_sequential_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ def generator_function_with_serialization(context):

return outputs

def generator_function_new_guid(context):
"""Simple orchestrator that generates 3 GUIDs"""
outputs = []

output1 = context.new_guid()
output2 = context.new_guid()
output3 = context.new_guid()

outputs.append(str(output1))
outputs.append(str(output2))
outputs.append(str(output3))
return outputs


def base_expected_state(output=None) -> OrchestratorState:
return OrchestratorState(is_done=False, actions=[], output=output)
Expand Down Expand Up @@ -353,3 +366,22 @@ def test_utc_time_updates_correctly():
assert_valid_schema(result)
assert_orchestration_state_equals(expected, result)

def test_new_guid_orchestrator():
"""Tests that the new_guid API is replay-safe and produces new GUIDs every time"""
context_builder = ContextBuilder('test_guid_orchestrator')

# To test that the API is replay-safe, we generate two orchestrators
# with the same starting context
result1 = get_orchestration_state_result(
context_builder, generator_function_new_guid)
outputs1 = result1["output"]

result2 = get_orchestration_state_result(
context_builder, generator_function_new_guid)
outputs2 = result2["output"]

# All GUIDs should be unique
assert len(outputs1) == len(set(outputs1))
# The two GUID lists should be the same
assert outputs1 == outputs2