Skip to content

Commit c115409

Browse files
authored
Add pyright type-checking to examples, fix discoveries (#159)
* Add pyright type-checking to examples, fix discoveries * Non-editable installs and examples requirements
1 parent 972d0cd commit c115409

18 files changed

Lines changed: 102 additions & 53 deletions

File tree

.github/workflows/typecheck.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,18 @@ jobs:
3030
run: |
3131
python -m pip install --upgrade pip
3232
pip install -r requirements.txt
33-
pip install -e ".[azure-blob-payloads,opentelemetry]"
34-
pip install -e ./durabletask-azuremanaged
33+
# Install third-party dependencies declared by the examples so they
34+
# type-check cleanly. Each example's requirements.txt is the single
35+
# source of truth for its dependencies.
36+
for req in examples/requirements.txt examples/*/requirements.txt; do
37+
pip install -r "$req"
38+
done
39+
# Install the packages under test from local source last (non-editable
40+
# so the durabletask / durabletask.azuremanaged namespace packages are
41+
# physically merged in site-packages, which pyright can resolve). This
42+
# overrides any PyPI copy pulled in by the example requirements above.
43+
pip install --force-reinstall --no-deps . ./durabletask-azuremanaged
44+
pip install ".[azure-blob-payloads,opentelemetry]"
3545
pip install pyright
3646
3747
- name: Run pyright (strict, Python 3.10)

examples/activity_sequence.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
that calls an activity function in a sequence and prints the outputs."""
66
import logging
77
import os
8+
from collections.abc import Generator
9+
from typing import Any
810

911
from azure.identity import DefaultAzureCredential
1012

@@ -20,7 +22,7 @@ def hello(ctx: task.ActivityContext, name: str) -> str:
2022
return f'Hello {name}!'
2123

2224

23-
def sequence(ctx: task.OrchestrationContext, _):
25+
def sequence(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, list[str]]:
2426
"""Orchestrator function that calls the 'hello' activity function in a sequence"""
2527
# Create a replay-safe logger to avoid duplicate log messages during replay
2628
replay_logger = ctx.create_replay_safe_logger(logger)

examples/distributed-tracing/app.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
import os
1818
import time
19+
from collections.abc import Generator
1920
from datetime import timedelta
21+
from typing import Any
2022

2123
from opentelemetry import trace
2224
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
@@ -64,7 +66,7 @@ def get_weather(ctx: task.ActivityContext, city: str) -> str:
6466
return result
6567

6668

67-
def summarize(ctx: task.ActivityContext, reports: list) -> str:
69+
def summarize(ctx: task.ActivityContext, reports: list[str]) -> str:
6870
"""Combine individual weather reports into a summary string."""
6971
summary = " | ".join(reports)
7072
print(f" [Activity] summarize -> {summary}")
@@ -75,9 +77,9 @@ def summarize(ctx: task.ActivityContext, reports: list) -> str:
7577
# Sub-orchestration
7678
# ---------------------------------------------------------------------------
7779

78-
def collect_weather(ctx: task.OrchestrationContext, cities: list):
80+
def collect_weather(ctx: task.OrchestrationContext, cities: list[str]) -> Generator[task.Task[Any], Any, list[str]]:
7981
"""Sub-orchestration that collects weather for a list of cities."""
80-
results = []
82+
results: list[str] = []
8183
for city in cities:
8284
weather = yield ctx.call_activity(get_weather, input=city)
8385
results.append(f"{city}: {weather}")
@@ -88,7 +90,7 @@ def collect_weather(ctx: task.OrchestrationContext, cities: list):
8890
# Main orchestration
8991
# ---------------------------------------------------------------------------
9092

91-
def weather_report_orchestrator(ctx: task.OrchestrationContext, cities: list):
93+
def weather_report_orchestrator(ctx: task.OrchestrationContext, cities: list[str]) -> Generator[task.Task[Any], Any, str]:
9294
"""Top-level orchestration demonstrating timers, activities, and sub-orchestrations.
9395
9496
Flow:

examples/entities/class_based_entity.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"""End-to-end sample that demonstrates how to configure an orchestrator
55
that calls an activity function in a sequence and prints the outputs."""
66
import os
7+
from collections.abc import Generator
8+
from typing import Any
79

810
from azure.identity import DefaultAzureCredential
911

@@ -16,7 +18,7 @@ class Counter(entities.DurableEntity):
1618
def set(self, input: int):
1719
self.set_state(input)
1820

19-
def add(self, input: int):
21+
def add(self, input: int | None = None):
2022
current_state = self.get_state(int, 0)
2123
new_state = current_state + (1 if input is None else input)
2224
self.set_state(new_state)
@@ -26,7 +28,7 @@ def get(self):
2628
return self.get_state(int, 0)
2729

2830

29-
def counter_orchestrator(ctx: task.OrchestrationContext, _):
31+
def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]:
3032
"""Orchestrator function that demonstrates the behavior of the counter entity"""
3133

3234
entity_id = task.EntityInstanceId("Counter", "myCounter")

examples/entities/class_based_entity_actions.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"""End-to-end sample that demonstrates how to configure an orchestrator
55
that calls an activity function in a sequence and prints the outputs."""
66
import os
7+
from collections.abc import Generator
8+
from typing import Any
79

810
from azure.identity import DefaultAzureCredential
911

@@ -16,7 +18,7 @@ class Counter(entities.DurableEntity):
1618
def set(self, input: int):
1719
self.set_state(input)
1820

19-
def add(self, input: int):
21+
def add(self, input: int | None = None):
2022
current_state = self.get_state(int, 0)
2123
new_state = current_state + (1 if input is None else input)
2224
self.set_state(new_state)
@@ -35,7 +37,7 @@ def get(self):
3537
return self.get_state(int, 0)
3638

3739

38-
def counter_orchestrator(ctx: task.OrchestrationContext, _):
40+
def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]:
3941
"""Orchestrator function that demonstrates the behavior of the counter entity"""
4042

4143
entity_id = task.EntityInstanceId("Counter", "myCounter")
@@ -54,7 +56,7 @@ def counter_orchestrator(ctx: task.OrchestrationContext, _):
5456
return (yield ctx.call_entity(parent_entity_id, "get"))
5557

5658

57-
def hello_orchestrator(ctx: task.OrchestrationContext, _):
59+
def hello_orchestrator(ctx: task.OrchestrationContext, _: Any) -> str:
5860
return "Hello world!"
5961

6062

examples/entities/entity_locking.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"""End-to-end sample that demonstrates how to configure an orchestrator
55
that calls an activity function in a sequence and prints the outputs."""
66
import os
7+
from collections.abc import Generator
8+
from typing import Any
79

810
from azure.identity import DefaultAzureCredential
911

@@ -16,7 +18,7 @@ class Counter(entities.DurableEntity):
1618
def set(self, input: int):
1719
self.set_state(input)
1820

19-
def add(self, input: int):
21+
def add(self, input: int | None = None):
2022
current_state = self.get_state(int, 0)
2123
new_state = current_state + (1 if input is None else input)
2224
self.set_state(new_state)
@@ -26,7 +28,7 @@ def get(self):
2628
return self.get_state(int, 0)
2729

2830

29-
def counter_orchestrator(ctx: task.OrchestrationContext, _):
31+
def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]:
3032
"""Orchestrator function that demonstrates the behavior of the counter entity"""
3133

3234
entity_id = entities.EntityInstanceId("Counter", "myCounter")

examples/entities/function_based_entity.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"""End-to-end sample that demonstrates how to configure an orchestrator
55
that calls an activity function in a sequence and prints the outputs."""
66
import os
7+
from collections.abc import Generator
8+
from typing import Any
79

810
from azure.identity import DefaultAzureCredential
911

@@ -12,7 +14,7 @@
1214
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
1315

1416

15-
def counter(ctx: entities.EntityContext, input: int) -> int | None:
17+
def counter(ctx: entities.EntityContext, input: int | None = None) -> int | None:
1618
if ctx.operation == "set":
1719
ctx.set_state(input)
1820
elif ctx.operation == "add":
@@ -26,7 +28,7 @@ def counter(ctx: entities.EntityContext, input: int) -> int | None:
2628
raise ValueError(f"Unknown operation '{ctx.operation}'")
2729

2830

29-
def counter_orchestrator(ctx: task.OrchestrationContext, _):
31+
def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]:
3032
"""Orchestrator function that demonstrates the behavior of the counter entity"""
3133

3234
entity_id = entities.EntityInstanceId("counter", "myCounter")

examples/entities/function_based_entity_actions.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"""End-to-end sample that demonstrates how to configure an orchestrator
55
that calls an activity function in a sequence and prints the outputs."""
66
import os
7+
from collections.abc import Generator
8+
from typing import Any
79

810
from azure.identity import DefaultAzureCredential
911

@@ -12,7 +14,7 @@
1214
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
1315

1416

15-
def counter(ctx: entities.EntityContext, input: int) -> int | None:
17+
def counter(ctx: entities.EntityContext, input: int | None = None) -> int | None:
1618
if ctx.operation == "set":
1719
ctx.set_state(input)
1820
elif ctx.operation == "add":
@@ -33,7 +35,7 @@ def counter(ctx: entities.EntityContext, input: int) -> int | None:
3335
raise ValueError(f"Unknown operation '{ctx.operation}'")
3436

3537

36-
def counter_orchestrator(ctx: task.OrchestrationContext, _):
38+
def counter_orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, Any]:
3739
"""Orchestrator function that demonstrates the behavior of the counter entity"""
3840

3941
entity_id = task.EntityInstanceId("counter", "myCounter")
@@ -52,7 +54,7 @@ def counter_orchestrator(ctx: task.OrchestrationContext, _):
5254
return (yield ctx.call_entity(parent_entity_id, "get"))
5355

5456

55-
def hello_orchestrator(ctx: task.OrchestrationContext, _):
57+
def hello_orchestrator(ctx: task.OrchestrationContext, _: Any) -> str:
5658
return "Hello world!"
5759

5860

examples/fanout_fanin.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import os
88
import random
99
import time
10+
from collections.abc import Generator
11+
from typing import Any
1012

1113
from azure.identity import DefaultAzureCredential
1214

@@ -15,7 +17,7 @@
1517
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
1618

1719

18-
def get_work_items(ctx: task.ActivityContext, _) -> list[str]:
20+
def get_work_items(ctx: task.ActivityContext, _: Any) -> list[str]:
1921
"""Activity function that returns a list of work items"""
2022
# return a random number of work items
2123
count = random.randint(2, 10)
@@ -34,15 +36,15 @@ def process_work_item(ctx: task.ActivityContext, item: str) -> int:
3436
return random.randint(0, 10)
3537

3638

37-
def orchestrator(ctx: task.OrchestrationContext, _):
39+
def orchestrator(ctx: task.OrchestrationContext, _: Any) -> Generator[task.Task[Any], Any, dict[str, Any]]:
3840
"""Orchestrator function that calls the 'get_work_items' and 'process_work_item'
3941
activity functions in parallel, waits for them all to complete, and prints
4042
an aggregate summary of the outputs"""
4143

4244
work_items: list[str] = yield ctx.call_activity(get_work_items)
4345

4446
# execute the work-items in parallel and wait for them all to return
45-
tasks = [ctx.call_activity(process_work_item, input=item) for item in work_items]
47+
tasks: list[task.Task[int]] = [ctx.call_activity(process_work_item, input=item) for item in work_items]
4648
results: list[int] = yield task.when_all(tasks)
4749

4850
# return an aggregate summary of the results

examples/history_export/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import os
2121
import time
22+
from collections.abc import Generator
2223
from datetime import datetime, timedelta, timezone
24+
from typing import Any
2325

2426
from durabletask import client, task, worker
2527
from durabletask.extensions.history_export import (
@@ -50,7 +52,7 @@ def square(_: task.ActivityContext, n: int) -> int:
5052
return n * n
5153

5254

53-
def sample_orchestrator(ctx: task.OrchestrationContext, n: int):
55+
def sample_orchestrator(ctx: task.OrchestrationContext, n: int) -> Generator[task.Task[Any], Any, int]:
5456
result = yield ctx.call_activity(square, input=n)
5557
return result
5658

0 commit comments

Comments
 (0)