Skip to content

Commit 009f2ea

Browse files
committed
Fix example bugs, improve docs, and add copilot instructions
- Fix entity method dispatch in worker.py to inspect method signature before passing entity_input, so class-based entity methods that take no input (e.g. get()) work without requiring a dummy parameter - Fix function_based_entity.py: if -> elif for set operation - Fix function_based_entity_actions.py: add missing 'add' operation - Fix sub-orchestration examples: use dynamic secure_channel based on endpoint instead of hardcoded True - Rewrite examples/README.md with proper formatting, virtual env setup instructions, and local dev install steps - Add .github/copilot-instructions.md with project conventions - Add .pymarkdown.json config and pymarkdownlnt to dev-requirements.txt - Add unit tests for entity executor method dispatch
1 parent 424ffa9 commit 009f2ea

17 files changed

+384
-99
lines changed

.github/copilot-instructions.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copilot Instructions for durabletask-python
2+
3+
## Project Overview
4+
5+
This is the Durable Task Python SDK, providing a client and worker for
6+
building durable orchestrations. The repo contains two packages:
7+
8+
- `durabletask` — core SDK (in `durabletask/`)
9+
- `durabletask.azuremanaged` — Azure Durable Task Scheduler provider (in `durabletask-azuremanaged/`)
10+
11+
## Language and Style
12+
13+
- Python 3.10+ is required.
14+
- Use type hints for all public API signatures.
15+
- Follow PEP 8 conventions.
16+
- Use `autopep8` for Python formatting.
17+
18+
## Markdown Style
19+
20+
Use GitHub-style callouts for notes, warnings, and tips in Markdown files:
21+
22+
```markdown
23+
> [!NOTE]
24+
> This is a note.
25+
26+
> [!WARNING]
27+
> This is a warning.
28+
29+
> [!TIP]
30+
> This is a tip.
31+
```
32+
33+
Do **not** use bold-text callouts like `**NOTE:**` or `> **Note:**`.
34+
35+
When providing shell commands in Markdown, include both Bash and
36+
PowerShell examples if the syntax differs between them. Common cases
37+
include multiline commands (Bash uses `\` for line continuation while
38+
PowerShell uses a backtick `` ` ``), environment variable syntax, and
39+
path separators. If a command is identical in both shells, a single
40+
example is sufficient.
41+
42+
## Markdown Linting
43+
44+
This repository uses [pymarkdownlnt](https://pypi.org/project/pymarkdownlnt/)
45+
for linting Markdown files. Configuration is in `.pymarkdown.json` at the
46+
repository root.
47+
48+
To lint a single file:
49+
50+
```bash
51+
pymarkdown -c .pymarkdown.json scan path/to/file.md
52+
```
53+
54+
To lint all Markdown files in the repository:
55+
56+
```bash
57+
pymarkdown -c .pymarkdown.json scan **/*.md
58+
```
59+
60+
Install the linter via the dev dependencies:
61+
62+
```bash
63+
pip install -r dev-requirements.txt
64+
```
65+
66+
## Building and Testing
67+
68+
Install the packages locally in editable mode:
69+
70+
```bash
71+
pip install -e . -e ./durabletask-azuremanaged
72+
```
73+
74+
Run tests with pytest:
75+
76+
```bash
77+
pytest
78+
```
79+
80+
## Project Structure
81+
82+
- `durabletask/` — core SDK source
83+
- `durabletask-azuremanaged/` — Azure managed provider source
84+
- `examples/` — example orchestrations (see `examples/README.md`)
85+
- `tests/` — test suite
86+
- `dev-requirements.txt` — development dependencies

.pymarkdown.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": {
3+
"md013": {
4+
"line_length": 100
5+
},
6+
"md014": {
7+
"enabled": false
8+
}
9+
}
10+
}

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
grpcio-tools
2+
pymarkdownlnt

durabletask/worker.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,7 @@ class _EntityExecutor:
19131913
def __init__(self, registry: _Registry, logger: logging.Logger):
19141914
self._registry = registry
19151915
self._logger = logger
1916+
self._entity_method_cache: dict[tuple[type, str], bool] = {}
19161917

19171918
def execute(
19181919
self,
@@ -1948,7 +1949,16 @@ def execute(
19481949
raise TypeError(f"Entity operation '{operation}' is not callable")
19491950
# Execute the entity method
19501951
entity_instance._initialize_entity_context(ctx)
1951-
entity_output = method(entity_input)
1952+
cache_key = (type(entity_instance), operation)
1953+
expects_input = self._entity_method_cache.get(cache_key)
1954+
if expects_input is None:
1955+
sig = inspect.signature(method)
1956+
expects_input = len(sig.parameters) > 0
1957+
self._entity_method_cache[cache_key] = expects_input
1958+
if expects_input:
1959+
entity_output = method(entity_input)
1960+
else:
1961+
entity_output = method()
19521962
else:
19531963
# Execute the entity function
19541964
entity_output = fn(ctx, entity_input)

examples/README.md

Lines changed: 141 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,176 @@
11
# Examples
22

3-
This directory contains examples of how to author durable orchestrations using the Durable Task Python SDK in conjunction with the Durable Task Scheduler (DTS).
3+
This directory contains examples of how to author durable orchestrations
4+
using the Durable Task Python SDK in conjunction with the
5+
Durable Task Scheduler (DTS).
46

57
## Prerequisites
8+
69
If using a deployed Durable Task Scheduler:
7-
- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli)
8-
- [`az durabletask` CLI extension](https://learn.microsoft.com/en-us/cli/azure/durabletask?view=azure-cli-latest)
10+
11+
- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli)
12+
- [`az durabletask` CLI extension](https://learn.microsoft.com/cli/azure/durabletask?view=azure-cli-latest)
913

1014
## Running the Examples
15+
1116
There are two separate ways to run an example:
1217

1318
- Using the Emulator (recommended for learning and development)
14-
- Using a deployed Scheduler and Taskhub in Azure
19+
- Using a deployed Scheduler and Taskhub in Azure
1520

1621
### Running with the Emulator
17-
We recommend using the emulator for learning and development as it's faster to set up and doesn't require any Azure resources. The emulator simulates a scheduler and taskhub, packaged into an easy-to-use Docker container.
22+
23+
We recommend using the emulator for learning and development as it's
24+
faster to set up and doesn't require any Azure resources. The emulator
25+
simulates a scheduler and taskhub, packaged into an easy-to-use
26+
Docker container.
1827

1928
1. Install Docker: If it is not already installed.
2029

21-
2. Pull the Docker Image for the Emulator:
22-
```bash
23-
docker pull mcr.microsoft.com/dts/dts-emulator:v0.0.6
24-
```
30+
1. Pull the Docker Image for the Emulator:
2531

26-
3. Run the Emulator: Wait a few seconds for the container to be ready.
27-
```bash
28-
docker run --name dtsemulator -d -p 8080:8080 mcr.microsoft.com/dts/dts-emulator:v0.0.6
29-
```
32+
```bash
33+
docker pull mcr.microsoft.com/dts/dts-emulator:latest
34+
```
3035

31-
4. Install the Required Packages:
32-
```bash
33-
pip install -r requirements.txt
34-
```
36+
1. Run the Emulator: Wait a few seconds for the container to be ready.
37+
38+
```bash
39+
docker run --name dtsemulator -d -p 8080:8080 mcr.microsoft.com/dts/dts-emulator:latest
40+
```
41+
42+
1. Create a Python virtual environment (recommended):
43+
44+
```bash
45+
python -m venv .venv
46+
```
3547

36-
Note: The example code has been updated to use the default emulator settings automatically (endpoint: http://localhost:8080, taskhub: default). You don't need to set any environment variables.
48+
Activate the virtual environment:
49+
50+
Bash:
51+
52+
```bash
53+
source .venv/bin/activate
54+
```
55+
56+
PowerShell:
57+
58+
```powershell
59+
.\.venv\Scripts\Activate.ps1
60+
```
61+
62+
1. Install the Required Packages:
63+
64+
```bash
65+
pip install -r requirements.txt
66+
```
67+
68+
If you are running from a local clone of the repository, install the
69+
local packages in editable mode instead (run this from the repository
70+
root, not the `examples/` directory):
71+
72+
```bash
73+
pip install -e . -e ./durabletask-azuremanaged
74+
```
75+
76+
> [!NOTE]
77+
> The example code uses the default emulator settings
78+
> automatically (endpoint: `http://localhost:8080`, taskhub: `default`).
79+
> You don't need to set any environment variables.
3780
3881
### Running with a Deployed Scheduler and Taskhub Resource in Azure
39-
For production scenarios or when you're ready to deploy to Azure, you can create a taskhub using the Azure CLI:
82+
83+
For production scenarios or when you're ready to deploy to Azure, you
84+
can create a taskhub using the Azure CLI:
4085

4186
1. Create a Scheduler:
42-
```bash
43-
az durabletask scheduler create --resource-group <testrg> --name <testscheduler> --location <eastus> --ip-allowlist "[0.0.0.0/0]" --sku-capacity 1 --sku-name "Dedicated" --tags "{'myattribute':'myvalue'}"
44-
```
4587

46-
2. Create Your Taskhub:
47-
```bash
48-
az durabletask taskhub create --resource-group <testrg> --scheduler-name <testscheduler> --name <testtaskhub>
49-
```
88+
Bash:
5089

51-
3. Retrieve the Endpoint for the Scheduler: Locate the taskhub in the Azure portal to find the endpoint.
90+
```bash
91+
az durabletask scheduler create \
92+
--resource-group <testrg> \
93+
--name <testscheduler> \
94+
--location <eastus> \
95+
--ip-allowlist "[0.0.0.0/0]" \
96+
--sku-capacity 1 \
97+
--sku-name "Dedicated" \
98+
--tags "{'myattribute':'myvalue'}"
99+
```
52100

53-
4. Set the Environment Variables:
54-
Bash:
55-
```bash
56-
export TASKHUB=<taskhubname>
57-
export ENDPOINT=<taskhubEndpoint>
58-
```
59-
Powershell:
60-
```powershell
61-
$env:TASKHUB = "<taskhubname>"
62-
$env:ENDPOINT = "<taskhubEndpoint>"
63-
```
101+
PowerShell:
64102

65-
5. Install the Required Packages:
66-
```bash
67-
pip install -r requirements.txt
68-
```
103+
```powershell
104+
az durabletask scheduler create `
105+
--resource-group <testrg> `
106+
--name <testscheduler> `
107+
--location <eastus> `
108+
--ip-allowlist "[0.0.0.0/0]" `
109+
--sku-capacity 1 `
110+
--sku-name "Dedicated" `
111+
--tags "{'myattribute':'myvalue'}"
112+
```
113+
114+
1. Create Your Taskhub:
115+
116+
Bash:
117+
118+
```bash
119+
az durabletask taskhub create \
120+
--resource-group <testrg> \
121+
--scheduler-name <testscheduler> \
122+
--name <testtaskhub>
123+
```
124+
125+
PowerShell:
126+
127+
```powershell
128+
az durabletask taskhub create `
129+
--resource-group <testrg> `
130+
--scheduler-name <testscheduler> `
131+
--name <testtaskhub>
132+
```
133+
134+
1. Retrieve the Endpoint for the Scheduler: Locate the taskhub in the
135+
Azure portal to find the endpoint.
136+
137+
1. Set the Environment Variables:
138+
139+
Bash:
140+
141+
```bash
142+
export TASKHUB=<taskhubname>
143+
export ENDPOINT=<taskhubEndpoint>
144+
```
145+
146+
PowerShell:
147+
148+
```powershell
149+
$env:TASKHUB = "<taskhubname>"
150+
$env:ENDPOINT = "<taskhubEndpoint>"
151+
```
152+
153+
1. Install the Required Packages:
154+
155+
```bash
156+
pip install -r requirements.txt
157+
```
158+
159+
### Executing the Examples
69160

70-
### Running the Examples
71161
You can now execute any of the examples in this directory using Python:
72162

73163
```bash
74-
python3 example_file.py
164+
python activity_sequence.py
75165
```
76166

77-
### Review Orchestration History and Status in the Durable Task Scheduler Dashboard
78-
To access the Durable Task Scheduler Dashboard, follow these steps:
167+
### Review Orchestration History and Status
79168

80-
- **Using the Emulator**: By default, the dashboard runs on portal 8082. Navigate to http://localhost:8082 and click on the default task hub.
169+
To access the Durable Task Scheduler Dashboard, follow these steps:
81170

82-
- **Using a Deployed Scheduler**: Navigate to the Scheduler resource. Then, go to the Task Hub subresource that you are using and click on the dashboard URL in the top right corner.
171+
- **Using the Emulator**: By default, the dashboard runs on port 8082.
172+
Navigate to <http://localhost:8082> and click on the default task hub.
83173

84-
```sh
85-
python3 activity_sequence.py
86-
```
174+
- **Using a Deployed Scheduler**: Navigate to the Scheduler resource.
175+
Then, go to the Task Hub subresource that you are using and click on
176+
the dashboard URL in the top right corner.

examples/activity_sequence.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@ def sequence(ctx: task.OrchestrationContext, _):
3333
print(f"Using endpoint: {endpoint}")
3434

3535
# Set credential to None for emulator, or DefaultAzureCredential for Azure
36-
credential = None if endpoint == "http://localhost:8080" else DefaultAzureCredential()
37-
38-
# configure and start the worker - use secure_channel=False for emulator
39-
secure_channel = endpoint != "http://localhost:8080"
36+
secure_channel = endpoint.startswith("https://")
37+
credential = DefaultAzureCredential() if secure_channel else None
4038
with DurableTaskSchedulerWorker(host_address=endpoint, secure_channel=secure_channel,
4139
taskhub=taskhub_name, token_credential=credential) as w:
4240
w.add_orchestrator(sequence)

examples/entities/class_based_entity.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def set(self, input: int):
1515

1616
def add(self, input: int):
1717
current_state = self.get_state(int, 0)
18-
new_state = current_state + (input or 1)
18+
new_state = current_state + (1 if input is None else input)
1919
self.set_state(new_state)
2020
return new_state
2121

@@ -44,10 +44,8 @@ def counter_orchestrator(ctx: task.OrchestrationContext, _):
4444
print(f"Using endpoint: {endpoint}")
4545

4646
# Set credential to None for emulator, or DefaultAzureCredential for Azure
47-
credential = None if endpoint == "http://localhost:8080" else DefaultAzureCredential()
48-
49-
# configure and start the worker - use secure_channel=False for emulator
50-
secure_channel = endpoint != "http://localhost:8080"
47+
secure_channel = endpoint.startswith("https://")
48+
credential = DefaultAzureCredential() if secure_channel else None
5149
with DurableTaskSchedulerWorker(host_address=endpoint, secure_channel=secure_channel,
5250
taskhub=taskhub_name, token_credential=credential) as w:
5351
w.add_orchestrator(counter_orchestrator)

0 commit comments

Comments
 (0)