From 256331b354bcb4576036258cd883e13b9adfcc07 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Thu, 25 Jun 2026 12:03:18 -0600 Subject: [PATCH 1/2] Add pyright type-checking to examples, fix discoveries --- examples/activity_sequence.py | 4 ++- examples/distributed-tracing/app.py | 10 ++++--- examples/entities/class_based_entity.py | 6 ++-- .../entities/class_based_entity_actions.py | 8 +++-- examples/entities/entity_locking.py | 6 ++-- examples/entities/function_based_entity.py | 6 ++-- .../entities/function_based_entity_actions.py | 8 +++-- examples/fanout_fanin.py | 8 +++-- examples/history_export/app.py | 4 ++- examples/human_interaction.py | 14 ++++++--- .../src/workflows.py | 10 ++++--- examples/large_payload/app.py | 4 ++- examples/sandboxes/main_app.py | 7 +++-- .../worker.py | 29 ++++++++++--------- examples/version_aware_orchestrator.py | 8 +++-- examples/work_item_filtering.py | 6 ++-- pyrightconfig.json | 3 +- 17 files changed, 90 insertions(+), 51 deletions(-) diff --git a/examples/activity_sequence.py b/examples/activity_sequence.py index b4b92ea7..d2c71e8a 100644 --- a/examples/activity_sequence.py +++ b/examples/activity_sequence.py @@ -2,6 +2,8 @@ that calls an activity function in a sequence and prints the outputs.""" import logging import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -17,7 +19,7 @@ def hello(ctx: task.ActivityContext, name: str) -> str: return f'Hello {name}!' -def sequence(ctx: task.OrchestrationContext, _): +def sequence(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, list[str]]: """Orchestrator function that calls the 'hello' activity function in a sequence""" # Create a replay-safe logger to avoid duplicate log messages during replay replay_logger = ctx.create_replay_safe_logger(logger) diff --git a/examples/distributed-tracing/app.py b/examples/distributed-tracing/app.py index 8cb09b89..86824e93 100644 --- a/examples/distributed-tracing/app.py +++ b/examples/distributed-tracing/app.py @@ -16,7 +16,9 @@ import os import time +from collections.abc import Generator from datetime import timedelta +from typing import Any from opentelemetry import trace from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter @@ -64,7 +66,7 @@ def get_weather(ctx: task.ActivityContext, city: str) -> str: return result -def summarize(ctx: task.ActivityContext, reports: list) -> str: +def summarize(ctx: task.ActivityContext, reports: list[str]) -> str: """Combine individual weather reports into a summary string.""" summary = " | ".join(reports) print(f" [Activity] summarize -> {summary}") @@ -75,9 +77,9 @@ def summarize(ctx: task.ActivityContext, reports: list) -> str: # Sub-orchestration # --------------------------------------------------------------------------- -def collect_weather(ctx: task.OrchestrationContext, cities: list): +def collect_weather(ctx: task.OrchestrationContext, cities: list[str]) -> Generator[task.Task[Any], Any, list[str]]: """Sub-orchestration that collects weather for a list of cities.""" - results = [] + results: list[str] = [] for city in cities: weather = yield ctx.call_activity(get_weather, input=city) results.append(f"{city}: {weather}") @@ -88,7 +90,7 @@ def collect_weather(ctx: task.OrchestrationContext, cities: list): # Main orchestration # --------------------------------------------------------------------------- -def weather_report_orchestrator(ctx: task.OrchestrationContext, cities: list): +def weather_report_orchestrator(ctx: task.OrchestrationContext, cities: list[str]) -> Generator[task.Task[Any], Any, str]: """Top-level orchestration demonstrating timers, activities, and sub-orchestrations. Flow: diff --git a/examples/entities/class_based_entity.py b/examples/entities/class_based_entity.py index e1b581d5..9e7714b4 100644 --- a/examples/entities/class_based_entity.py +++ b/examples/entities/class_based_entity.py @@ -1,6 +1,8 @@ """End-to-end sample that demonstrates how to configure an orchestrator that calls an activity function in a sequence and prints the outputs.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -13,7 +15,7 @@ class Counter(entities.DurableEntity): def set(self, input: int): self.set_state(input) - def add(self, input: int): + def add(self, input: int | None = None): current_state = self.get_state(int, 0) new_state = current_state + (1 if input is None else input) self.set_state(new_state) @@ -23,7 +25,7 @@ def get(self): return self.get_state(int, 0) -def counter_orchestrator(ctx: task.OrchestrationContext, _): +def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]: """Orchestrator function that demonstrates the behavior of the counter entity""" entity_id = task.EntityInstanceId("Counter", "myCounter") diff --git a/examples/entities/class_based_entity_actions.py b/examples/entities/class_based_entity_actions.py index 5240bb75..754d35f0 100644 --- a/examples/entities/class_based_entity_actions.py +++ b/examples/entities/class_based_entity_actions.py @@ -1,6 +1,8 @@ """End-to-end sample that demonstrates how to configure an orchestrator that calls an activity function in a sequence and prints the outputs.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -13,7 +15,7 @@ class Counter(entities.DurableEntity): def set(self, input: int): self.set_state(input) - def add(self, input: int): + def add(self, input: int | None = None): current_state = self.get_state(int, 0) new_state = current_state + (1 if input is None else input) self.set_state(new_state) @@ -32,7 +34,7 @@ def get(self): return self.get_state(int, 0) -def counter_orchestrator(ctx: task.OrchestrationContext, _): +def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]: """Orchestrator function that demonstrates the behavior of the counter entity""" entity_id = task.EntityInstanceId("Counter", "myCounter") @@ -51,7 +53,7 @@ def counter_orchestrator(ctx: task.OrchestrationContext, _): return (yield ctx.call_entity(parent_entity_id, "get")) -def hello_orchestrator(ctx: task.OrchestrationContext, _): +def hello_orchestrator(ctx: task.OrchestrationContext, _: Any) -> str: return "Hello world!" diff --git a/examples/entities/entity_locking.py b/examples/entities/entity_locking.py index c6c1bda9..b1aedc7a 100644 --- a/examples/entities/entity_locking.py +++ b/examples/entities/entity_locking.py @@ -1,6 +1,8 @@ """End-to-end sample that demonstrates how to configure an orchestrator that calls an activity function in a sequence and prints the outputs.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -13,7 +15,7 @@ class Counter(entities.DurableEntity): def set(self, input: int): self.set_state(input) - def add(self, input: int): + def add(self, input: int | None = None): current_state = self.get_state(int, 0) new_state = current_state + (1 if input is None else input) self.set_state(new_state) @@ -23,7 +25,7 @@ def get(self): return self.get_state(int, 0) -def counter_orchestrator(ctx: task.OrchestrationContext, _): +def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]: """Orchestrator function that demonstrates the behavior of the counter entity""" entity_id = entities.EntityInstanceId("Counter", "myCounter") diff --git a/examples/entities/function_based_entity.py b/examples/entities/function_based_entity.py index b99c29b4..3dc6e612 100644 --- a/examples/entities/function_based_entity.py +++ b/examples/entities/function_based_entity.py @@ -1,6 +1,8 @@ """End-to-end sample that demonstrates how to configure an orchestrator that calls an activity function in a sequence and prints the outputs.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -9,7 +11,7 @@ from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker -def counter(ctx: entities.EntityContext, input: int) -> int | None: +def counter(ctx: entities.EntityContext, input: int | None = None) -> int | None: if ctx.operation == "set": ctx.set_state(input) elif ctx.operation == "add": @@ -23,7 +25,7 @@ def counter(ctx: entities.EntityContext, input: int) -> int | None: raise ValueError(f"Unknown operation '{ctx.operation}'") -def counter_orchestrator(ctx: task.OrchestrationContext, _): +def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]: """Orchestrator function that demonstrates the behavior of the counter entity""" entity_id = entities.EntityInstanceId("counter", "myCounter") diff --git a/examples/entities/function_based_entity_actions.py b/examples/entities/function_based_entity_actions.py index edcdbee2..37e3c0ef 100644 --- a/examples/entities/function_based_entity_actions.py +++ b/examples/entities/function_based_entity_actions.py @@ -1,6 +1,8 @@ """End-to-end sample that demonstrates how to configure an orchestrator that calls an activity function in a sequence and prints the outputs.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -9,7 +11,7 @@ from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker -def counter(ctx: entities.EntityContext, input: int) -> int | None: +def counter(ctx: entities.EntityContext, input: int | None = None) -> int | None: if ctx.operation == "set": ctx.set_state(input) elif ctx.operation == "add": @@ -30,7 +32,7 @@ def counter(ctx: entities.EntityContext, input: int) -> int | None: raise ValueError(f"Unknown operation '{ctx.operation}'") -def counter_orchestrator(ctx: task.OrchestrationContext, _): +def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]: """Orchestrator function that demonstrates the behavior of the counter entity""" entity_id = task.EntityInstanceId("counter", "myCounter") @@ -49,7 +51,7 @@ def counter_orchestrator(ctx: task.OrchestrationContext, _): return (yield ctx.call_entity(parent_entity_id, "get")) -def hello_orchestrator(ctx: task.OrchestrationContext, _): +def hello_orchestrator(ctx: task.OrchestrationContext, _: Any) -> str: return "Hello world!" diff --git a/examples/fanout_fanin.py b/examples/fanout_fanin.py index 0975d924..bb39bbd9 100644 --- a/examples/fanout_fanin.py +++ b/examples/fanout_fanin.py @@ -4,6 +4,8 @@ import os import random import time +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -12,7 +14,7 @@ from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker -def get_work_items(ctx: task.ActivityContext, _) -> list[str]: +def get_work_items(ctx: task.ActivityContext, _: Any) -> list[str]: """Activity function that returns a list of work items""" # return a random number of work items count = random.randint(2, 10) @@ -31,7 +33,7 @@ def process_work_item(ctx: task.ActivityContext, item: str) -> int: return random.randint(0, 10) -def orchestrator(ctx: task.OrchestrationContext, _): +def orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, dict[str, Any]]: """Orchestrator function that calls the 'get_work_items' and 'process_work_item' activity functions in parallel, waits for them all to complete, and prints an aggregate summary of the outputs""" @@ -39,7 +41,7 @@ def orchestrator(ctx: task.OrchestrationContext, _): work_items: list[str] = yield ctx.call_activity(get_work_items) # execute the work-items in parallel and wait for them all to return - tasks = [ctx.call_activity(process_work_item, input=item) for item in work_items] + tasks: list[task.Task[int]] = [ctx.call_activity(process_work_item, input=item) for item in work_items] results: list[int] = yield task.when_all(tasks) # return an aggregate summary of the results diff --git a/examples/history_export/app.py b/examples/history_export/app.py index fd6c365e..6a786f0f 100644 --- a/examples/history_export/app.py +++ b/examples/history_export/app.py @@ -19,7 +19,9 @@ import os import time +from collections.abc import Generator from datetime import datetime, timedelta, timezone +from typing import Any from durabletask import client, task, worker from durabletask.extensions.history_export import ( @@ -50,7 +52,7 @@ def square(_: task.ActivityContext, n: int) -> int: return n * n -def sample_orchestrator(ctx: task.OrchestrationContext, n: int): +def sample_orchestrator(ctx: task.OrchestrationContext, n: int) -> Generator[task.Task[Any], Any, int]: result = yield ctx.call_activity(square, input=n) return result diff --git a/examples/human_interaction.py b/examples/human_interaction.py index 69131006..b75a5041 100644 --- a/examples/human_interaction.py +++ b/examples/human_interaction.py @@ -6,9 +6,10 @@ import os import threading import time -from collections import namedtuple +from collections.abc import Generator from dataclasses import dataclass from datetime import timedelta +from typing import Any, NamedTuple from azure.identity import DefaultAzureCredential @@ -17,6 +18,11 @@ from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker +class Approval(NamedTuple): + """Represents an approval event payload""" + approver: str + + @dataclass class Order: """Represents a purchase order""" @@ -39,7 +45,7 @@ def place_order(_: task.ActivityContext, order: Order) -> None: print(f'*** Placing order: {order}') -def purchase_order_workflow(ctx: task.OrchestrationContext, order: Order): +def purchase_order_workflow(ctx: task.OrchestrationContext, order: Order) -> Generator[task.Task[Any], Any, str]: """Orchestrator function that represents a purchase order workflow""" # Orders under $1000 are auto-approved if order.Cost < 1000: @@ -87,7 +93,7 @@ def purchase_order_workflow(ctx: task.OrchestrationContext, order: Order): def prompt_for_approval(): input("Press [ENTER] to approve the order...\n") - approval_event = namedtuple("Approval", ["approver"])(args.approver) + approval_event = Approval(args.approver) c.raise_orchestration_event(instance_id, "approval_received", data=approval_event) # Prompt the user for approval on a background thread @@ -133,7 +139,7 @@ def prompt_for_approval(): def prompt_for_approval(): input("Press [ENTER] to approve the order...\n") - approval_event = namedtuple("Approval", ["approver"])(args.approver) + approval_event = Approval(args.approver) c.raise_orchestration_event(instance_id, "approval_received", data=approval_event) # Prompt the user for approval on a background thread diff --git a/examples/in_memory_backend_example/src/workflows.py b/examples/in_memory_backend_example/src/workflows.py index f5f973d9..eecbd2ca 100644 --- a/examples/in_memory_backend_example/src/workflows.py +++ b/examples/in_memory_backend_example/src/workflows.py @@ -16,8 +16,10 @@ access (``item["quantity"]``) for nested data. """ +from collections.abc import Generator from dataclasses import dataclass from datetime import timedelta +from typing import Any from durabletask import task @@ -50,7 +52,7 @@ class Order: # --------------------------------------------------------------------------- -def validate_order(ctx: task.ActivityContext, order) -> None: +def validate_order(ctx: task.ActivityContext, order: Any) -> None: """Validate that the order has items and all quantities/prices are valid. Raises ``ValueError`` on invalid input. @@ -66,7 +68,7 @@ def validate_order(ctx: task.ActivityContext, order) -> None: f"Invalid price for '{item['name']}': {item['unit_price']}") -def calculate_total(ctx: task.ActivityContext, items: list) -> float: +def calculate_total(ctx: task.ActivityContext, items: list[Any]) -> float: """Return the total cost for a list of item dicts.""" return sum(item["quantity"] * item["unit_price"] for item in items) @@ -95,7 +97,7 @@ def ship_item(ctx: task.ActivityContext, item_name: str) -> str: # --------------------------------------------------------------------------- -def process_order(ctx: task.OrchestrationContext, order): +def process_order(ctx: task.OrchestrationContext, order: Any) -> Generator[task.Task[Any], Any, dict[str, Any]]: """Process a complete order: validate, pay, ship items in parallel, confirm. Demonstrates: @@ -135,7 +137,7 @@ def process_order(ctx: task.OrchestrationContext, order): } -def order_with_approval(ctx: task.OrchestrationContext, order): +def order_with_approval(ctx: task.OrchestrationContext, order: Any) -> Generator[task.Task[Any], Any, dict[str, Any]]: """Order workflow that requires manager approval for high-value orders. Demonstrates: diff --git a/examples/large_payload/app.py b/examples/large_payload/app.py index afdda044..c4a8081d 100644 --- a/examples/large_payload/app.py +++ b/examples/large_payload/app.py @@ -24,6 +24,8 @@ """ import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -48,7 +50,7 @@ def summarize(ctx: task.ActivityContext, report: str) -> str: # --------------- Orchestrator --------------- -def large_payload_orchestrator(ctx: task.OrchestrationContext, num_records: int): +def large_payload_orchestrator(ctx: task.OrchestrationContext, num_records: int) -> Generator[task.Task[Any], Any, str]: """Orchestrator that generates a large report and then summarizes it. Both the report (activity output) and the orchestration input are diff --git a/examples/sandboxes/main_app.py b/examples/sandboxes/main_app.py index 41f6f609..41d575dd 100644 --- a/examples/sandboxes/main_app.py +++ b/examples/sandboxes/main_app.py @@ -1,6 +1,8 @@ """Declarer app for the Durable Task Scheduler sandbox activities sample.""" import os +from collections.abc import Generator +from typing import Any from azure.core.credentials import TokenCredential from azure.identity import DefaultAzureCredential @@ -9,6 +11,7 @@ from durabletask.azuremanaged.client import DurableTaskSchedulerClient from durabletask.azuremanaged.preview.sandboxes import SandboxActivitiesClient from durabletask.azuremanaged.preview.sandboxes import SandboxWorkerProfile +from durabletask.azuremanaged.preview.sandboxes import SandboxWorkerProfileOptions from durabletask.azuremanaged.preview.sandboxes import sandbox_worker_profile from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker @@ -17,7 +20,7 @@ WORKER_PROFILE_ID = "remote-hello-profile" -def hello_orchestrator(ctx: task.OrchestrationContext, name: str): +def hello_orchestrator(ctx: task.OrchestrationContext, name: str) -> Generator[task.Task[Any], Any, Any]: """Orchestrator that calls an activity executed by the remote worker image.""" return (yield ctx.call_activity(REMOTE_HELLO.name, input=name)) @@ -78,7 +81,7 @@ def _resolve_scheduler_connection() -> tuple[str, str, bool, TokenCredential | N class RemoteWorkerProfile(SandboxWorkerProfile): """Sandbox worker profile used by the sample remote activity.""" - def configure(self, options) -> None: + def configure(self, options: SandboxWorkerProfileOptions) -> None: options.image.image_ref = container_image options.image.managed_identity_client_id = image_pull_managed_identity_client_id options.scheduler_managed_identity_client_id = scheduler_managed_identity_client_id diff --git a/examples/sub-orchestrations-with-fan-out-fan-in/worker.py b/examples/sub-orchestrations-with-fan-out-fan-in/worker.py index 45620cda..6acae7c7 100644 --- a/examples/sub-orchestrations-with-fan-out-fan-in/worker.py +++ b/examples/sub-orchestrations-with-fan-out-fan-in/worker.py @@ -1,12 +1,15 @@ import os import random import time +from collections.abc import Generator +from typing import Any + from azure.identity import DefaultAzureCredential from durabletask import task from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker -def get_orders(ctx, _) -> list[str]: +def get_orders(ctx: task.ActivityContext, _: Any) -> list[str]: """Activity function that returns a list of work items""" # return a random number of work items count = random.randint(2, 10) @@ -14,7 +17,7 @@ def get_orders(ctx, _) -> list[str]: return [f'order {i}' for i in range(count)] -def check_and_update_inventory(ctx, order: str) -> str: +def check_and_update_inventory(ctx: task.ActivityContext, order: str) -> bool: """Activity function that checks inventory for a given order""" print(f'checking inventory for order: {order}') @@ -22,10 +25,10 @@ def check_and_update_inventory(ctx, order: str) -> str: time.sleep(random.random() * 2) # return a random boolean indicating if the item is in stock - return random.choices([True, False], weights=[9, 1]) + return random.choices([True, False], weights=[9, 1])[0] -def charge_payment(ctx, order: str) -> bool: +def charge_payment(ctx: task.ActivityContext, order: str) -> bool: """Activity function that charges payment for a given order""" print(f'charging payment for order: {order}') @@ -33,10 +36,10 @@ def charge_payment(ctx, order: str) -> bool: time.sleep(random.random() * 2) # return a random boolean indicating if the payment was successful - return random.choices([True, False], weights=[9, 1]) + return random.choices([True, False], weights=[9, 1])[0] -def ship_order(ctx, order: str) -> bool: +def ship_order(ctx: task.ActivityContext, order: str) -> bool: """Activity function that ships a given order""" print(f'shipping order: {order}') @@ -44,10 +47,10 @@ def ship_order(ctx, order: str) -> bool: time.sleep(random.random() * 2) # return a random boolean indicating if the shipping was successful - return random.choices([True, False], weights=[9, 1]) + return random.choices([True, False], weights=[9, 1])[0] -def notify_customer(ctx, order: str) -> bool: +def notify_customer(ctx: task.ActivityContext, order: str) -> bool: """Activity function that notifies the customer about the order status""" print(f'notifying customer about order: {order}') @@ -55,10 +58,10 @@ def notify_customer(ctx, order: str) -> bool: time.sleep(random.random() * 2) # return a random boolean indicating if the notification was successful - return random.choices([True, False], weights=[9, 1]) + return random.choices([True, False], weights=[9, 1])[0] -def process_order(ctx, order: str) -> dict: +def process_order(ctx: task.OrchestrationContext, order: str) -> Generator[task.Task[Any], Any, dict[str, Any]]: """Sub-orchestration function that processes a given order by performing all steps""" print(f'processing order: {order}') @@ -90,7 +93,7 @@ def process_order(ctx, order: str) -> dict: return {'order': order, 'status': 'completed'} -def orchestrator(ctx, _): +def orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, dict[str, Any]]: """Orchestrator function that calls the 'get_orders' and 'process_order' sub-orchestration functions in parallel, waits for them all to complete, and prints an aggregate summary of the outputs""" @@ -98,8 +101,8 @@ def orchestrator(ctx, _): orders: list[str] = yield ctx.call_activity('get_orders') # Execute the orders in parallel and wait for them all to return - tasks = [ctx.call_sub_orchestrator(process_order, input=order) for order in orders] - results: list[dict] = yield task.when_all(tasks) + tasks: list[task.Task[Any]] = [ctx.call_sub_orchestrator(process_order, input=order) for order in orders] + results: list[dict[str, Any]] = yield task.when_all(tasks) # Return an aggregate summary of the results return { diff --git a/examples/version_aware_orchestrator.py b/examples/version_aware_orchestrator.py index b0af11a4..ee6e9d72 100644 --- a/examples/version_aware_orchestrator.py +++ b/examples/version_aware_orchestrator.py @@ -2,6 +2,8 @@ that a dynamic number activity functions in parallel, waits for them all to complete, and prints an aggregate summary of the outputs.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -22,16 +24,16 @@ def activity_v2(ctx: task.ActivityContext, input: str) -> str: return "Success from activity v2" -def orchestrator(ctx: task.OrchestrationContext, _): +def orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, dict[str, Any]]: """Orchestrator function that checks the orchestration version and has version-aware behavior Use case: Updating an orchestrator with new logic while maintaining compatibility with previously started orchestrations""" if ctx.version == "1.0.0": # For version 1.0.0, we use the original logic - result: int = yield ctx.call_activity(activity_v1, input="input for v1") + result = yield ctx.call_activity(activity_v1, input="input for v1") elif ctx.version == "2.0.0": # For version 2.0.0, we use the updated logic - result: int = yield ctx.call_activity(activity_v2, input="input for v2") + result = yield ctx.call_activity(activity_v2, input="input for v2") else: raise ValueError(f"Unsupported version: {ctx.version}") return { diff --git a/examples/work_item_filtering.py b/examples/work_item_filtering.py index 8bccb7f9..5fef4aa4 100644 --- a/examples/work_item_filtering.py +++ b/examples/work_item_filtering.py @@ -1,6 +1,8 @@ """End-to-end sample that demonstrates how to use work item filters to control which orchestrations and activities a worker processes.""" import os +from collections.abc import Generator +from typing import Any from azure.identity import DefaultAzureCredential @@ -23,13 +25,13 @@ def farewell(ctx: task.ActivityContext, name: str) -> str: # --- Orchestrator definitions --- -def greeting_orchestrator(ctx: task.OrchestrationContext, name: str): +def greeting_orchestrator(ctx: task.OrchestrationContext, name: str) -> Generator[task.Task[Any], Any, str]: """Orchestrator that calls the greet activity.""" result = yield ctx.call_activity(greet, input=name) return result -def farewell_orchestrator(ctx: task.OrchestrationContext, name: str): +def farewell_orchestrator(ctx: task.OrchestrationContext, name: str) -> Generator[task.Task[Any], Any, str]: """Orchestrator that calls the farewell activity.""" result = yield ctx.call_activity(farewell, input=name) return result diff --git a/pyrightconfig.json b/pyrightconfig.json index 6df52eef..46a54dbd 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,7 +1,8 @@ { "include": [ "durabletask", - "durabletask-azuremanaged" + "durabletask-azuremanaged", + "examples" ], "exclude": [ "**/__pycache__", From 54f1d50cc32c6b12b2b48d66b806ff0c13ac9191 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Thu, 25 Jun 2026 12:13:01 -0600 Subject: [PATCH 2/2] Non-editable installs and examples requirements --- .github/workflows/typecheck.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index f10463ad..28dcddfc 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -30,8 +30,18 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install -e ".[azure-blob-payloads,opentelemetry]" - pip install -e ./durabletask-azuremanaged + # Install third-party dependencies declared by the examples so they + # type-check cleanly. Each example's requirements.txt is the single + # source of truth for its dependencies. + for req in examples/requirements.txt examples/*/requirements.txt; do + pip install -r "$req" + done + # Install the packages under test from local source last (non-editable + # so the durabletask / durabletask.azuremanaged namespace packages are + # physically merged in site-packages, which pyright can resolve). This + # overrides any PyPI copy pulled in by the example requirements above. + pip install --force-reinstall --no-deps . ./durabletask-azuremanaged + pip install ".[azure-blob-payloads,opentelemetry]" pip install pyright - name: Run pyright (strict, Python 3.10)