-
Notifications
You must be signed in to change notification settings - Fork 20
Andystaples/add functions support #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7ff1525
552a2dd
af0e3c2
86ee081
3497148
57de878
18145f8
9965ba4
209443e
cc005ae
bf6d6f2
0a03bd1
1176d03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| name: Durable Task Scheduler SDK (durabletask-azurefunctions) Dev Release | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["Durable Task Scheduler SDK (durabletask-azurefunctions)"] | ||
| types: | ||
| - completed | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| publish-dev: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Extract version from tag | ||
| run: echo "VERSION=${GITHUB_REF#refs/tags/azurefunctions-v}" >> $GITHUB_ENV # Extract version from the tag | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.14" # Adjust Python version as needed | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install build twine | ||
|
|
||
| - name: Append dev to version in pyproject.toml | ||
| working-directory: durabletask-azurefunctions | ||
| run: | | ||
| sed -i 's/^version = "\(.*\)"/version = "\1.dev${{ github.run_number }}"/' pyproject.toml | ||
|
|
||
| - name: Build package from directory durabletask-azurefunctions | ||
| working-directory: durabletask-azurefunctions | ||
| run: | | ||
| python -m build | ||
|
|
||
| - name: Check package | ||
| working-directory: durabletask-azurefunctions | ||
| run: | | ||
| twine check dist/* | ||
|
|
||
| - name: Publish package to PyPI | ||
| env: | ||
| TWINE_USERNAME: __token__ | ||
| TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_AZUREFUNCTIONS }} # Store your PyPI API token in GitHub Secrets | ||
| working-directory: durabletask-azurefunctions | ||
| run: | | ||
| twine upload dist/* | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||||||||||||||||
| name: Durable Task Scheduler SDK (durabletask-azurefunctions) Experimental Release | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||
| branches-ignore: | ||||||||||||||||||||||||||||||||
| - main | ||||||||||||||||||||||||||||||||
| - release/* | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||
| publish-experimental: | ||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: Checkout code | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Extract version from tag | ||||||||||||||||||||||||||||||||
| run: echo "VERSION=${GITHUB_REF#refs/tags/azurefunctions-v}" >> $GITHUB_ENV # Extract version from the tag | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Set up Python | ||||||||||||||||||||||||||||||||
| uses: actions/setup-python@v5 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| python-version: "3.14" # Adjust Python version as needed | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Install dependencies | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| python -m pip install --upgrade pip | ||||||||||||||||||||||||||||||||
| pip install build twine | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Change the version in pyproject.toml to 0.0.0dev{github.run_number} | ||||||||||||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| sed -i 's/^version = ".*"/version = "0.0.0.dev${{ github.run_number }}"/' pyproject.toml | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Build package from directory durabletask-azurefunctions | ||||||||||||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| python -m build | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Check package | ||||||||||||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| twine check dist/* | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Publish package to PyPI | ||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||
| TWINE_USERNAME: __token__ | ||||||||||||||||||||||||||||||||
| TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_AZUREFUNCTIONS }} # Store your PyPI API token in GitHub Secrets | ||||||||||||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| twine upload dist/* | ||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+50
Check warningCode scanning / CodeQL Workflow does not contain permissions Medium
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Copilot AutofixAI 11 minutes ago To fix the problem, an explicit permissions:
contents: readIf the workflow ever needs to write release or package information back to the repository, the permissions should specify only those specific privileges. For now, since all steps run locally and publish externally to PyPI (using Twine and a PyPI token), only Only one edit is needed: insert the
Suggested changeset
1
.github/workflows/durabletask-azurefunctions-experimental.yml
Copilot is powered by AI and may make mistakes. Always verify output.
Positive FeedbackNegative Feedback
Refresh and try again.
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,126 @@ | |||||||||||||||||||||||
| name: Durable Task Scheduler SDK (durabletask-azurefunctions) | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| on: | |||||||||||||||||||||||
| push: | |||||||||||||||||||||||
| branches: | |||||||||||||||||||||||
| - "main" | |||||||||||||||||||||||
| tags: | |||||||||||||||||||||||
| - "azurefunctions-v*" # Only run for tags starting with "azurefunctions-v" | |||||||||||||||||||||||
| pull_request: | |||||||||||||||||||||||
| branches: | |||||||||||||||||||||||
| - "main" | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| jobs: | |||||||||||||||||||||||
| lint: | |||||||||||||||||||||||
| runs-on: ubuntu-latest | |||||||||||||||||||||||
| steps: | |||||||||||||||||||||||
| - uses: actions/checkout@v4 | |||||||||||||||||||||||
| - name: Set up Python 3.14 | |||||||||||||||||||||||
| uses: actions/setup-python@v5 | |||||||||||||||||||||||
| with: | |||||||||||||||||||||||
| python-version: 3.14 | |||||||||||||||||||||||
| - name: Install dependencies | |||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| python -m pip install --upgrade pip | |||||||||||||||||||||||
| pip install setuptools wheel tox | |||||||||||||||||||||||
| pip install flake8 | |||||||||||||||||||||||
| - name: Run flake8 Linter | |||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | |||||||||||||||||||||||
| run: flake8 . | |||||||||||||||||||||||
| - name: Run flake8 Linter | |||||||||||||||||||||||
| working-directory: tests/durabletask-azurefunctions | |||||||||||||||||||||||
| run: flake8 . | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| run-docker-tests: | |||||||||||||||||||||||
|
Comment on lines
+15
to
+35
Check warningCode scanning / CodeQL Workflow does not contain permissions Medium
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
|
|||||||||||||||||||||||
| strategy: | |||||||||||||||||||||||
| fail-fast: false | |||||||||||||||||||||||
| matrix: | |||||||||||||||||||||||
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |||||||||||||||||||||||
| env: | |||||||||||||||||||||||
| EMULATOR_VERSION: "latest" | |||||||||||||||||||||||
| needs: lint | |||||||||||||||||||||||
| runs-on: ubuntu-latest | |||||||||||||||||||||||
| steps: | |||||||||||||||||||||||
| - name: Checkout repository | |||||||||||||||||||||||
| uses: actions/checkout@v4 | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Pull Docker image | |||||||||||||||||||||||
| run: docker pull mcr.microsoft.com/dts/dts-emulator:$EMULATOR_VERSION | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Run Docker container | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| docker run --name dtsemulator -d -p 8080:8080 mcr.microsoft.com/dts/dts-emulator:$EMULATOR_VERSION | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Wait for container to be ready | |||||||||||||||||||||||
| run: sleep 10 # Adjust if your service needs more time to start | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Set environment variables | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| echo "TASKHUB=default" >> $GITHUB_ENV | |||||||||||||||||||||||
| echo "ENDPOINT=http://localhost:8080" >> $GITHUB_ENV | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Install durabletask dependencies | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| python -m pip install --upgrade pip | |||||||||||||||||||||||
| pip install flake8 pytest | |||||||||||||||||||||||
| pip install -r requirements.txt | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Install durabletask-azurefunctions dependencies | |||||||||||||||||||||||
| working-directory: examples | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| python -m pip install --upgrade pip | |||||||||||||||||||||||
| pip install -r requirements.txt | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Install durabletask-azurefunctions locally | |||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| pip install . --no-deps --force-reinstall | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Install durabletask locally | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| pip install . --no-deps --force-reinstall | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Run the tests | |||||||||||||||||||||||
| working-directory: tests/durabletask-azurefunctions | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| pytest -m "dts" --verbose | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| publish-release: | |||||||||||||||||||||||
| if: startsWith(github.ref, 'refs/tags/azurefunctions-v') # Only run if a matching tag is pushed | |||||||||||||||||||||||
| needs: run-docker-tests | |||||||||||||||||||||||
| runs-on: ubuntu-latest | |||||||||||||||||||||||
| steps: | |||||||||||||||||||||||
| - name: Checkout code | |||||||||||||||||||||||
| uses: actions/checkout@v4 | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Extract version from tag | |||||||||||||||||||||||
| run: echo "VERSION=${GITHUB_REF#refs/tags/azurefunctions-v}" >> $GITHUB_ENV # Extract version from the tag | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Set up Python | |||||||||||||||||||||||
| uses: actions/setup-python@v5 | |||||||||||||||||||||||
| with: | |||||||||||||||||||||||
| python-version: "3.14" # Adjust Python version as needed | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Install dependencies | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| python -m pip install --upgrade pip | |||||||||||||||||||||||
| pip install build twine | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Build package from directory durabletask-azurefunctions | |||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| python -m build | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Check package | |||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| twine check dist/* | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| - name: Publish package to PyPI | |||||||||||||||||||||||
| env: | |||||||||||||||||||||||
| TWINE_USERNAME: __token__ | |||||||||||||||||||||||
| TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_AZUREFUNCTIONS }} # Store your PyPI API token in GitHub Secrets | |||||||||||||||||||||||
| working-directory: durabletask-azurefunctions | |||||||||||||||||||||||
| run: | | |||||||||||||||||||||||
| twine upload dist/* | |||||||||||||||||||||||
|
Comment on lines
+90
to
+126
Check warningCode scanning / CodeQL Workflow does not contain permissions Medium
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Copilot AutofixAI 11 minutes ago To fix this issue, explicitly set a We should therefore add the following block near the top of the workflow (to affect all jobs, unless overridden): permissions:
contents: readAlternatively, it could be added inside the This change is made at the root of the YAML file, directly after the No other code changes or external dependencies are required.
Suggested changeset
1
.github/workflows/durabletask-azurefunctions.yml
Copilot is powered by AI and may make mistakes. Always verify output.
Positive FeedbackNegative Feedback
Refresh and try again.
|
|||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this project will be documented in this file. | ||
|
|
||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
|
||
| ## v0.1.0 | ||
|
|
||
| - Initial implementation |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. | ||
|
|
||
| from durabletask.azurefunctions.decorators.durable_app import Blueprint, DFApp | ||
| from durabletask.azurefunctions.client import DurableFunctionsClient | ||
|
|
||
| __all__ = ["Blueprint", "DFApp", "DurableFunctionsClient"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. | ||
|
|
||
| import json | ||
|
|
||
| from datetime import timedelta | ||
| from typing import Any, Optional | ||
| import azure.functions as func | ||
| from urllib.parse import urlparse, quote | ||
|
|
||
| from durabletask.entities import EntityInstanceId | ||
| from durabletask.client import TaskHubGrpcClient | ||
| from durabletask.azurefunctions.internal.azurefunctions_grpc_interceptor import AzureFunctionsDefaultClientInterceptorImpl | ||
| from durabletask.azurefunctions.http import HttpManagementPayload | ||
|
|
||
|
|
||
| # Client class used for Durable Functions | ||
| class DurableFunctionsClient(TaskHubGrpcClient): | ||
| """A gRPC client passed to Durable Functions durable client bindings. | ||
|
|
||
| Connects to the Durable Functions runtime using gRPC and provides methods | ||
| for creating and managing Durable orchestrations, interacting with Durable entities, | ||
| and creating HTTP management payloads and check status responses for use with Durable Functions invocations. | ||
| """ | ||
| taskHubName: str | ||
| connectionName: str | ||
| creationUrls: dict[str, str] | ||
| managementUrls: dict[str, str] | ||
|
Comment on lines
+27
to
+28
|
||
| baseUrl: str | ||
| requiredQueryStringParameters: str | ||
| rpcBaseUrl: str | ||
| httpBaseUrl: str | ||
| maxGrpcMessageSizeInBytes: int | ||
| grpcHttpClientTimeout: timedelta | ||
|
|
||
| def __init__(self, client_as_string: str): | ||
| """Initializes a DurableFunctionsClient instance from a JSON string. | ||
|
|
||
| This string will be provided by the Durable Functions host extension upon invocation of the client trigger. | ||
|
|
||
| Args: | ||
| client_as_string (str): A JSON string containing the Durable Functions client configuration. | ||
|
|
||
| Raises: | ||
| json.JSONDecodeError: If the provided string is not valid JSON. | ||
| """ | ||
| client = json.loads(client_as_string) | ||
|
|
||
| self.taskHubName = client.get("taskHubName", "") | ||
| self.connectionName = client.get("connectionName", "") | ||
| self.creationUrls = client.get("creationUrls", {}) | ||
| self.managementUrls = client.get("managementUrls", {}) | ||
| self.baseUrl = client.get("baseUrl", "") | ||
| self.requiredQueryStringParameters = client.get("requiredQueryStringParameters", "") | ||
| self.rpcBaseUrl = client.get("rpcBaseUrl", "") | ||
| self.httpBaseUrl = client.get("httpBaseUrl", "") | ||
| self.maxGrpcMessageSizeInBytes = client.get("maxGrpcMessageSizeInBytes", 0) | ||
| # TODO: convert the string value back to timedelta - annoying regex? | ||
| self.grpcHttpClientTimeout = client.get("grpcHttpClientTimeout", timedelta(seconds=30)) | ||
andystaples marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| interceptors = [AzureFunctionsDefaultClientInterceptorImpl(self.taskHubName, self.requiredQueryStringParameters)] | ||
|
|
||
| # We pass in None for the metadata so we don't construct an additional interceptor in the parent class | ||
| # Since the parent class doesn't use anything metadata for anything else, we can set it as None | ||
| super().__init__( | ||
| host_address=self.rpcBaseUrl, | ||
| secure_channel=False, | ||
| metadata=None, | ||
| interceptors=interceptors) | ||
|
|
||
| def create_check_status_response(self, request: func.HttpRequest, instance_id: str) -> func.HttpResponse: | ||
| """Creates an HTTP response for checking the status of a Durable Function instance. | ||
|
|
||
| Args: | ||
| request (func.HttpRequest): The incoming HTTP request. | ||
| instance_id (str): The ID of the Durable Function instance. | ||
| """ | ||
| location_url = self._get_instance_status_url(request, instance_id) | ||
| return func.HttpResponse( | ||
| body=str(self._get_client_response_links(request, instance_id)), | ||
| status_code=501, | ||
| headers={ | ||
| 'content-type': 'application/json', | ||
| 'Location': location_url, | ||
| }, | ||
| ) | ||
|
|
||
| def create_http_management_payload(self, request: func.HttpRequest, instance_id: str) -> HttpManagementPayload: | ||
| """Creates an HTTP management payload for a Durable Function instance. | ||
|
|
||
| Args: | ||
| instance_id (str): The ID of the Durable Function instance. | ||
| """ | ||
| return self._get_client_response_links(request, instance_id) | ||
|
|
||
| def _get_client_response_links(self, request: func.HttpRequest, instance_id: str) -> HttpManagementPayload: | ||
| instance_status_url = self._get_instance_status_url(request, instance_id) | ||
| return HttpManagementPayload(instance_id, instance_status_url, self.requiredQueryStringParameters) | ||
|
|
||
| @staticmethod | ||
| def _get_instance_status_url(request: func.HttpRequest, instance_id: str) -> str: | ||
| request_url = urlparse(request.url) | ||
| location_url = f"{request_url.scheme}://{request_url.netloc}" | ||
| encoded_instance_id = quote(instance_id) | ||
| location_url = location_url + "/runtime/webhooks/durabletask/instances/" + encoded_instance_id | ||
| return location_url | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. | ||
|
|
||
| """Constants used to determine the local running context.""" | ||
andystaples marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ORCHESTRATION_TRIGGER = "orchestrationTrigger" | ||
| ACTIVITY_TRIGGER = "activityTrigger" | ||
| ENTITY_TRIGGER = "entityTrigger" | ||
| DURABLE_CLIENT = "durableClient" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Copilot Autofix
AI 11 minutes ago
The recommended fix is to explicitly add a
permissions:block to the workflow. This block should be placed at the top level of the workflow (directly after thename:field), thereby applying to all jobs unless jobs override it. The safest minimum ispermissions: contents: read, which will suffice unless more elevated privileges are required (not evidenced in any of the given steps). No additional imports, methods, or new dependencies are needed; this is a YAML configuration change. Edit.github/workflows/durabletask-azurefunctions-dev.ymlto add:immediately after the
name:field at line 1, before theon:trigger.