Skip to content

Commit 808ddb3

Browse files
committed
Add version parameter to start_new method for orchestration instances
- Introduced optional `version` parameter in `DurableOrchestrationClient.start_new()` - Updated related methods and documentation to support versioning - Modified sample function app to accept version from query parameters - Added unit test for URL construction with version
1 parent 44c259a commit 808ddb3

File tree

5 files changed

+135
-6
lines changed

5 files changed

+135
-6
lines changed

CHANGES_SUMMARY.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Version Override Feature - Changes Summary
2+
3+
## Overview
4+
Added the ability to pass a version parameter to `client.start_new()` to override the default version specified in `host.json`.
5+
6+
## Changes Made
7+
8+
### 1. DurableOrchestrationClient.py
9+
**Location**: `azure/durable_functions/models/DurableOrchestrationClient.py`
10+
11+
#### Added `version` parameter to `start_new` method:
12+
- Added optional `version` parameter (line 52)
13+
- Updated docstring to document the new parameter
14+
- Passed version to `_get_start_new_url` helper method
15+
16+
#### Updated `_get_start_new_url` helper method:
17+
- Added optional `version` parameter (line 646)
18+
- Appends version as query parameter when provided: `?version={version}`
19+
20+
### 2. Sample Function App
21+
**Location**: `samples-v2/orchestration_versioning/function_app.py`
22+
23+
Updated `http_start` function to:
24+
- Read version from query parameter: `req.params.get('version')`
25+
- Pass version to `client.start_new()` when provided
26+
- Log the version being used
27+
28+
### 3. Sample README
29+
**Location**: `samples-v2/orchestration_versioning/README.md`
30+
31+
Added new section "Overriding Version Programmatically" that documents:
32+
- How to pass version programmatically: `client.start_new("my_orchestrator", version="1.5")`
33+
- How to pass version via HTTP query parameter: `?version=2.0`
34+
- Use cases for explicit version override
35+
36+
### 4. Unit Tests
37+
**Location**: `tests/models/test_DurableOrchestrationClient.py`
38+
39+
Added `test_get_start_new_url_with_version` test to verify:
40+
- URL construction with version query parameter
41+
- Correct format: `orchestrators/{functionName}/{instanceId}?version={version}`
42+
43+
## Usage Examples
44+
45+
### Programmatic Usage
46+
```python
47+
# Start orchestration with specific version
48+
instance_id = await client.start_new("my_orchestrator", version="2.0")
49+
50+
# Start with version and custom instance ID
51+
instance_id = await client.start_new(
52+
"my_orchestrator",
53+
instance_id="custom-id-123",
54+
version="1.5"
55+
)
56+
57+
# Start with version and input
58+
instance_id = await client.start_new(
59+
"my_orchestrator",
60+
client_input={"key": "value"},
61+
version="3.0"
62+
)
63+
```
64+
65+
### HTTP Query Parameter Usage
66+
```bash
67+
# Start orchestration with version 2.0
68+
curl http://localhost:7071/api/orchestrators/my_orchestrator?version=2.0
69+
70+
# Start orchestration with version 1.0
71+
curl http://localhost:7071/api/orchestrators/my_orchestrator?version=1.0
72+
```
73+
74+
## Benefits
75+
76+
1. **Testing**: Test new versions without modifying `host.json`
77+
2. **Gradual Rollout**: Control which requests use which version
78+
3. **Multi-Version Support**: Run multiple versions simultaneously
79+
4. **Flexibility**: Override version on a per-request basis
80+
81+
## Backward Compatibility
82+
83+
This change is fully backward compatible:
84+
- The `version` parameter is optional
85+
- When not provided, behavior remains unchanged (uses `defaultVersion` from `host.json`)
86+
- Existing code continues to work without modification

azure/durable_functions/models/DurableOrchestrationClient.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ def __init__(self, context: str):
4848
async def start_new(self,
4949
orchestration_function_name: str,
5050
instance_id: Optional[str] = None,
51-
client_input: Optional[Any] = None) -> str:
51+
client_input: Optional[Any] = None,
52+
version: Optional[str] = None) -> str:
5253
"""Start a new instance of the specified orchestrator function.
5354
5455
If an orchestration instance with the specified ID already exists, the
@@ -63,14 +64,17 @@ async def start_new(self,
6364
the Durable Functions extension will generate a random GUID (recommended).
6465
client_input : Optional[Any]
6566
JSON-serializable input value for the orchestrator function.
67+
version : Optional[str]
68+
The version to assign to the orchestration instance. If not specified,
69+
the defaultVersion from host.json will be used.
6670
6771
Returns
6872
-------
6973
str
7074
The ID of the new orchestration instance if successful, None if not.
7175
"""
7276
request_url = self._get_start_new_url(
73-
instance_id=instance_id, orchestration_function_name=orchestration_function_name)
77+
instance_id=instance_id, orchestration_function_name=orchestration_function_name, version=version)
7478

7579
trace_parent, trace_state = DurableOrchestrationClient._get_current_activity_context()
7680

@@ -639,10 +643,15 @@ def _parse_purge_instance_history_response(
639643
raise Exception(result)
640644

641645
def _get_start_new_url(
642-
self, instance_id: Optional[str], orchestration_function_name: str) -> str:
646+
self, instance_id: Optional[str], orchestration_function_name: str,
647+
version: Optional[str] = None) -> str:
643648
instance_path = f'/{instance_id}' if instance_id is not None else ''
644649
request_url = f'{self._orchestration_bindings.rpc_base_url}orchestrators/' \
645650
f'{orchestration_function_name}{instance_path}'
651+
652+
if version is not None:
653+
request_url += f'?version={version}'
654+
646655
return request_url
647656

648657
def _get_raise_event_url(

samples-v2/orchestration_versioning/README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ What happens to *existing orchestration instances* that were started *before* th
3333
6. Trigger the external event.
3434
7. Observe that the orchestration output.
3535

36-
```
36+
```text
3737
Orchestration version: 1.0
3838
Suborchestration version: 2.0
3939
Hello from A!
@@ -42,3 +42,18 @@ Hello from A!
4242
Note that the value returned by `context.version` is permanently associated with the orchestrator instance and is not impacted by the `defaultVersion` change. As a result, the orchestrator follows the old execution path to guarantee deterministic replay behavior.
4343

4444
However, the suborchestration version is `2.0` because this suborchestration was created *after* the `defaultVersion` change.
45+
46+
## Overriding Version Programmatically
47+
48+
In addition to using `defaultVersion` in `host.json`, you can also specify a version explicitly when starting an orchestration using the `version` parameter of `client.start_new()`:
49+
50+
```python
51+
# Start with a specific version
52+
instance_id = await client.start_new("my_orchestrator", version="1.5")
53+
```
54+
55+
When a version is explicitly provided, it takes precedence over the `defaultVersion` in `host.json`. This allows you to:
56+
57+
- Test new orchestration versions without changing configuration
58+
- Run multiple versions simultaneously
59+
- Gradually roll out new versions by controlling which requests use which version

samples-v2/orchestration_versioning/function_app.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
88
@myApp.durable_client_input(client_name="client")
99
async def http_start(req: func.HttpRequest, client):
1010
function_name = req.route_params.get('functionName')
11-
instance_id = await client.start_new(function_name)
1211

13-
logging.info(f"Started orchestration with ID = '{instance_id}'.")
12+
# Get optional version from query parameter
13+
version = req.params.get('version')
14+
15+
if version:
16+
instance_id = await client.start_new(function_name, version=version)
17+
logging.info(f"Started orchestration with ID = '{instance_id}' and version = '{version}'.")
18+
else:
19+
instance_id = await client.start_new(function_name)
20+
logging.info(f"Started orchestration with ID = '{instance_id}'.")
21+
1422
return client.create_check_status_response(req, instance_id)
1523

1624
@myApp.orchestration_trigger(context_name="context")

tests/models/test_DurableOrchestrationClient.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ def test_get_start_new_url(binding_string):
8282
assert expected_url == start_new_url
8383

8484

85+
def test_get_start_new_url_with_version(binding_string):
86+
client = DurableOrchestrationClient(binding_string)
87+
instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
88+
function_name = "my_function"
89+
version = "2.0"
90+
start_new_url = client._get_start_new_url(instance_id, function_name, version)
91+
expected_url = replace_stand_in_bits(
92+
f"{RPC_BASE_URL}orchestrators/{function_name}/{instance_id}?version={version}")
93+
assert expected_url == start_new_url
94+
95+
8596
def test_get_input_returns_none_when_none_supplied():
8697
result = DurableOrchestrationClient._get_json_input(None)
8798
assert result is None

0 commit comments

Comments
 (0)