From 483cad0c31a1e7933a763976d93cdbaa92fee5db Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Wed, 29 Jul 2026 14:21:20 -0600 Subject: [PATCH 1/3] Mark azure-functions-durable as typed Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3775188c-77e9-48a2-9c79-76e7bfdc25f9 --- azure-functions-durable/CHANGELOG.md | 2 + .../azure/durable_functions/py.typed | 0 azure-functions-durable/pyproject.toml | 3 ++ noxfile.py | 48 +++++++++++++++++-- .../typing/import_package.py | 8 ++++ .../typing/pyrightconfig.json | 7 +++ 6 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 azure-functions-durable/azure/durable_functions/py.typed create mode 100644 tests/azure-functions-durable/typing/import_package.py create mode 100644 tests/azure-functions-durable/typing/pyrightconfig.json diff --git a/azure-functions-durable/CHANGELOG.md b/azure-functions-durable/CHANGELOG.md index e9c022e6..fea8d4dc 100644 --- a/azure-functions-durable/CHANGELOG.md +++ b/azure-functions-durable/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ADDED +- Added PEP 561 type information so strict type checkers recognize +`azure.durable_functions` as a typed package. - Added `azure.durable_functions.testing.execute_entity()` for unit testing function-style and class-based entities without a Functions host or Durable Task backend. The helper returns the operation result, resulting state, and typed diff --git a/azure-functions-durable/azure/durable_functions/py.typed b/azure-functions-durable/azure/durable_functions/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/azure-functions-durable/pyproject.toml b/azure-functions-durable/pyproject.toml index fb675b4c..62698a04 100644 --- a/azure-functions-durable/pyproject.toml +++ b/azure-functions-durable/pyproject.toml @@ -39,5 +39,8 @@ changelog = "https://github.com/microsoft/durabletask-python/blob/main/azure-fun [tool.setuptools.packages.find] include = ["azure.durable_functions", "azure.durable_functions.*"] +[tool.setuptools.package-data] +"azure.durable_functions" = ["py.typed"] + [tool.pytest.ini_options] minversion = "6.0" diff --git a/noxfile.py b/noxfile.py index 075c71cc..0b40212d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -27,8 +27,10 @@ import shutil import socket import subprocess +import tarfile import time import uuid +import zipfile from pathlib import Path from typing import Sequence @@ -311,16 +313,56 @@ def typecheck_core(session: nox.Session) -> None: @nox.session(python=["3.13"]) def typecheck_functions(session: nox.Session) -> None: - """Run strict pyright checks for azure-functions-durable.""" + """Run strict pyright checks for azure-functions-durable and its wheel.""" session.install("-r", "requirements.txt") - _install_packages(session, editable=True) - session.install("pyright") + session.install("-e", str(REPO_ROOT)) + session.install("build", "pyright") + artifact_dir = Path(session.create_tmp()) / "dist" + if artifact_dir.exists(): + shutil.rmtree(artifact_dir) + session.run( + "python", + "-m", + "build", + "--outdir", + str(artifact_dir), + str(AZURE_FUNCTIONS_DURABLE), + ) + + wheels = list(artifact_dir.glob("azure_functions_durable-*.whl")) + sdists = list(artifact_dir.glob("azure_functions_durable-*.tar.gz")) + if len(wheels) != 1 or len(sdists) != 1: + session.error("Expected exactly one wheel and one source distribution") + wheel = wheels[0] + sdist = sdists[0] + marker = "azure/durable_functions/py.typed" + with zipfile.ZipFile(wheel) as archive: + if marker not in archive.namelist(): + session.error(f"{marker} is missing from {wheel.name}") + with tarfile.open(sdist, "r:gz") as archive: + if not any(name.endswith(f"/{marker}") for name in archive.getnames()): + session.error(f"{marker} is missing from {sdist.name}") + + session.install(str(wheel)) session.run( "pyright", "-p", "azure-functions-durable/pyrightconfig.json", *session.posargs, ) + python_path = session.run( + "python", + "-c", + "import sys; print(sys.executable)", + silent=True, + ).strip() + session.run( + "pyright", + "--pythonpath", + python_path, + "-p", + "tests/azure-functions-durable/typing/pyrightconfig.json", + ) @nox.session(python=PYTHON_VERSIONS) diff --git a/tests/azure-functions-durable/typing/import_package.py b/tests/azure-functions-durable/typing/import_package.py new file mode 100644 index 00000000..3beb1350 --- /dev/null +++ b/tests/azure-functions-durable/typing/import_package.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import azure.durable_functions as df + + +def create_app() -> df.DFApp: + return df.DFApp() diff --git a/tests/azure-functions-durable/typing/pyrightconfig.json b/tests/azure-functions-durable/typing/pyrightconfig.json new file mode 100644 index 00000000..68700dda --- /dev/null +++ b/tests/azure-functions-durable/typing/pyrightconfig.json @@ -0,0 +1,7 @@ +{ + "include": [ + "import_package.py" + ], + "pythonVersion": "3.13", + "typeCheckingMode": "strict" +} From a87cd287d9b1ec4db183e622654487a8ef33ea16 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Wed, 29 Jul 2026 14:27:21 -0600 Subject: [PATCH 2/3] Force reinstall packaged typing artifact Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3775188c-77e9-48a2-9c79-76e7bfdc25f9 --- noxfile.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/noxfile.py b/noxfile.py index 0b40212d..29ce7de9 100644 --- a/noxfile.py +++ b/noxfile.py @@ -344,6 +344,15 @@ def typecheck_functions(session: nox.Session) -> None: session.error(f"{marker} is missing from {sdist.name}") session.install(str(wheel)) + session.run( + "python", + "-m", + "pip", + "install", + "--force-reinstall", + "--no-deps", + str(wheel), + ) session.run( "pyright", "-p", From 3fcd7089b2339bdc5d5511933c994bc2a6c50d4a Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Thu, 30 Jul 2026 10:07:46 -0600 Subject: [PATCH 3/3] Validate packaged core-backed types Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3775188c-77e9-48a2-9c79-76e7bfdc25f9 --- .github/workflows/typecheck.yml | 3 ++ noxfile.py | 50 ++++++++++++++----- .../typing/import_package.py | 13 +++++ 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 010f1524..83eca624 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -53,3 +53,6 @@ jobs: - name: Run pyright (strict, Python 3.13) run: nox -s typecheck_functions + + - name: Check packaged types (strict, Python 3.13) + run: nox -s typecheck_functions_package diff --git a/noxfile.py b/noxfile.py index 29ce7de9..99a3e1e5 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,6 +12,7 @@ nox -s lint nox -s typecheck_core + nox -s typecheck_functions_package nox -s core_tests-3.10 nox -s azuremanaged_tests-3.10 nox -s functions_unit-3.13 @@ -313,13 +314,29 @@ def typecheck_core(session: nox.Session) -> None: @nox.session(python=["3.13"]) def typecheck_functions(session: nox.Session) -> None: - """Run strict pyright checks for azure-functions-durable and its wheel.""" + """Run strict pyright checks for azure-functions-durable.""" session.install("-r", "requirements.txt") - session.install("-e", str(REPO_ROOT)) + _install_packages(session, editable=True) + session.install("pyright") + session.run( + "pyright", + "-p", + "azure-functions-durable/pyrightconfig.json", + *session.posargs, + ) + + +@nox.session(python=["3.13"]) +def typecheck_functions_package(session: nox.Session) -> None: + """Check the installed Functions and core wheels with strict Pyright.""" session.install("build", "pyright") - artifact_dir = Path(session.create_tmp()) / "dist" + temp_dir = Path(session.create_tmp()) + artifact_dir = temp_dir / "functions-dist" + core_artifact_dir = temp_dir / "core-dist" if artifact_dir.exists(): shutil.rmtree(artifact_dir) + if core_artifact_dir.exists(): + shutil.rmtree(core_artifact_dir) session.run( "python", "-m", @@ -328,13 +345,26 @@ def typecheck_functions(session: nox.Session) -> None: str(artifact_dir), str(AZURE_FUNCTIONS_DURABLE), ) + session.run( + "python", + "-m", + "build", + "--wheel", + "--outdir", + str(core_artifact_dir), + str(REPO_ROOT), + ) wheels = list(artifact_dir.glob("azure_functions_durable-*.whl")) sdists = list(artifact_dir.glob("azure_functions_durable-*.tar.gz")) - if len(wheels) != 1 or len(sdists) != 1: - session.error("Expected exactly one wheel and one source distribution") + core_wheels = list(core_artifact_dir.glob("durabletask-*.whl")) + if len(wheels) != 1 or len(sdists) != 1 or len(core_wheels) != 1: + session.error( + "Expected one provider wheel, provider source distribution, " + "and core wheel") wheel = wheels[0] sdist = sdists[0] + core_wheel = core_wheels[0] marker = "azure/durable_functions/py.typed" with zipfile.ZipFile(wheel) as archive: if marker not in archive.namelist(): @@ -343,7 +373,7 @@ def typecheck_functions(session: nox.Session) -> None: if not any(name.endswith(f"/{marker}") for name in archive.getnames()): session.error(f"{marker} is missing from {sdist.name}") - session.install(str(wheel)) + session.install(str(core_wheel), str(wheel)) session.run( "python", "-m", @@ -351,14 +381,9 @@ def typecheck_functions(session: nox.Session) -> None: "install", "--force-reinstall", "--no-deps", + str(core_wheel), str(wheel), ) - session.run( - "pyright", - "-p", - "azure-functions-durable/pyrightconfig.json", - *session.posargs, - ) python_path = session.run( "python", "-c", @@ -510,6 +535,7 @@ def ci(session: nox.Session) -> None: session.notify("lint", ()) session.notify("typecheck_core", ()) session.notify("typecheck_functions", ()) + session.notify("typecheck_functions_package", ()) session.notify(f"core_tests-{python_version}", ()) session.notify(f"azuremanaged_tests-{python_version}", ()) session.notify("functions_unit-3.13", ()) diff --git a/tests/azure-functions-durable/typing/import_package.py b/tests/azure-functions-durable/typing/import_package.py index 3beb1350..fe91818c 100644 --- a/tests/azure-functions-durable/typing/import_package.py +++ b/tests/azure-functions-durable/typing/import_package.py @@ -1,8 +1,21 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +from datetime import timedelta + import azure.durable_functions as df def create_app() -> df.DFApp: return df.DFApp() + + +def create_retry_policy() -> df.RetryPolicy: + return df.RetryPolicy( + first_retry_interval=timedelta(seconds=1), + max_number_of_attempts=3, + ) + + +def activity_is_complete(context: df.DurableOrchestrationContext) -> bool: + return context.call_activity("activity").is_complete