Skip to content

Commit 60c4633

Browse files
elena-kolevskacgillumberndverst
authored
Syncs with upstream (bumps min python version, regenerates protos with an older grpcio-tools version) (#6)
* Update version to 0.2b1, require Python 3.9+, and enhance GitHub Actions workflow (#1) (microsoft#35) - Bump version in `pyproject.toml` to 0.2b1 and update Python requirement to >=3.9. - Add `protobuf` dependency in `requirements.txt`. - Update GitHub Actions workflow to support Python versions 3.9 to 3.13 and upgrade action versions. - Refactor type hints in various files to use `Optional` and `list` instead of `Union` and `List`. - Improve handling of custom status in orchestration context and related functions. - Fix purge implementation to pass required parameters. Signed-off-by: Elena Kolevska <[email protected]> * Downgrade required `grpcio` and `protobuf` versions (microsoft#36) Signed-off-by: Elena Kolevska <[email protected]> --------- Signed-off-by: Elena Kolevska <[email protected]> Co-authored-by: Chris Gillum <[email protected]> Co-authored-by: Bernd Verst <[email protected]>
1 parent 4fc38e2 commit 60c4633

21 files changed

+529
-757
lines changed

.github/workflows/pr-validation.yml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
python-version: ["3.8", "3.9", "3.10", "3.11"]
20+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
2121

2222
steps:
2323
- uses: actions/checkout@v4
2424
- name: Set up Python ${{ matrix.python-version }}
25-
uses: actions/setup-python@v3
25+
uses: actions/setup-python@v5
2626
with:
2727
python-version: ${{ matrix.python-version }}
2828
- name: Install dependencies
@@ -36,6 +36,17 @@ jobs:
3636
- name: Pytest unit tests
3737
run: |
3838
pytest -m "not e2e" --verbose
39+
# Sidecar for running e2e tests requires Go SDK
40+
- name: Install Go SDK
41+
uses: actions/setup-go@v5
42+
with:
43+
go-version: 'stable'
44+
# Install and run the durabletask-go sidecar for running e2e tests
45+
- name: Pytest e2e tests
46+
run: |
47+
go install github.com/microsoft/durabletask-go@main
48+
durabletask-go --port 4001 &
49+
pytest -m "e2e" --verbose
3950
publish:
4051
needs: build
4152
if: startswith(github.ref, 'refs/tags/v')
@@ -59,4 +70,4 @@ jobs:
5970
TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_PASS }}
6071
run: |
6172
python -m build
62-
twine upload dist/*
73+
twine upload dist/*

.vscode/settings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"editor.defaultFormatter": "ms-python.autopep8",
44
"editor.formatOnSave": true,
55
"editor.codeActionsOnSave": {
6-
"source.organizeImports": true,
6+
"source.organizeImports": "explicit"
77
},
88
"editor.rulers": [
99
119
@@ -29,5 +29,6 @@
2929
"coverage.xml",
3030
"jacoco.xml",
3131
"coverage.cobertura.xml"
32-
]
32+
],
33+
"makefile.configureOnOpen": false
3334
}

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added `set_custom_status` orchestrator API ([#31](https://github.com/microsoft/durabletask-python/pull/31)) - contributed by [@famarting](https://github.com/famarting)
1313
- Added `purge_orchestration` client API ([#34](https://github.com/microsoft/durabletask-python/pull/34)) - contributed by [@famarting](https://github.com/famarting)
1414

15+
### Changes
16+
17+
- Protos are compiled with gRPC 1.62.3 / protobuf 3.25.X instead of the latest release. This ensures compatibility with a wider range of grpcio versions for better compatibility with other packages / libraries.
18+
1519
### Updates
1620

1721
- Updated `durabletask-protobuf` submodule reference to latest

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ install:
1111
python3 -m pip install .
1212

1313
gen-proto:
14-
# NOTE: There is currently a hand-edit that we make to the generated orchestrator_service_pb2.py file after it's generated to help resolve import problems.
15-
python3 -m grpc_tools.protoc --proto_path=./submodules/durabletask-protobuf/protos --python_out=./durabletask/internal --pyi_out=./durabletask/internal --grpc_python_out=./durabletask/internal orchestrator_service.proto
14+
cp ./submodules/durabletask-protobuf/protos/orchestrator_service.proto durabletask/internal/orchestrator_service.proto
15+
python3 -m grpc_tools.protoc --proto_path=. --python_out=. --pyi_out=. --grpc_python_out=. ./durabletask/internal/orchestrator_service.proto
16+
rm durabletask/internal/*.proto
1617

1718
.PHONY: init test-unit test-e2e gen-proto install

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ git submodule update --init
170170
Once the submodule is available, the corresponding source code can be regenerated using the following command from the project root:
171171

172172
```sh
173+
pip3 install -r dev-requirements.txt
173174
make gen-proto
174175
```
175176

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
grpcio-tools==1.62.3 # 1.62.X is the latest version before protobuf 1.26.X is used which has breaking changes for Python

durabletask/client.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from dataclasses import dataclass
77
from datetime import datetime
88
from enum import Enum
9-
from typing import Any, List, Tuple, TypeVar, Union
9+
from typing import Any, Optional, TypeVar, Union
1010

1111
import grpc
1212
from google.protobuf import wrappers_pb2
@@ -42,10 +42,10 @@ class OrchestrationState:
4242
runtime_status: OrchestrationStatus
4343
created_at: datetime
4444
last_updated_at: datetime
45-
serialized_input: Union[str, None]
46-
serialized_output: Union[str, None]
47-
serialized_custom_status: Union[str, None]
48-
failure_details: Union[task.FailureDetails, None]
45+
serialized_input: Optional[str]
46+
serialized_output: Optional[str]
47+
serialized_custom_status: Optional[str]
48+
failure_details: Optional[task.FailureDetails]
4949

5050
def raise_if_failed(self):
5151
if self.failure_details is not None:
@@ -64,7 +64,7 @@ def failure_details(self):
6464
return self._failure_details
6565

6666

67-
def new_orchestration_state(instance_id: str, res: pb.GetInstanceResponse) -> Union[OrchestrationState, None]:
67+
def new_orchestration_state(instance_id: str, res: pb.GetInstanceResponse) -> Optional[OrchestrationState]:
6868
if not res.exists:
6969
return None
7070

@@ -92,20 +92,20 @@ def new_orchestration_state(instance_id: str, res: pb.GetInstanceResponse) -> Un
9292
class TaskHubGrpcClient:
9393

9494
def __init__(self, *,
95-
host_address: Union[str, None] = None,
96-
metadata: Union[List[Tuple[str, str]], None] = None,
97-
log_handler = None,
98-
log_formatter: Union[logging.Formatter, None] = None,
95+
host_address: Optional[str] = None,
96+
metadata: Optional[list[tuple[str, str]]] = None,
97+
log_handler: Optional[logging.Handler] = None,
98+
log_formatter: Optional[logging.Formatter] = None,
9999
secure_channel: bool = False):
100100
channel = shared.get_grpc_channel(host_address, metadata, secure_channel=secure_channel)
101101
self._stub = stubs.TaskHubSidecarServiceStub(channel)
102102
self._logger = shared.get_logger("client", log_handler, log_formatter)
103103

104104
def schedule_new_orchestration(self, orchestrator: Union[task.Orchestrator[TInput, TOutput], str], *,
105-
input: Union[TInput, None] = None,
106-
instance_id: Union[str, None] = None,
107-
start_at: Union[datetime, None] = None,
108-
reuse_id_policy: Union[pb.OrchestrationIdReusePolicy, None] = None) -> str:
105+
input: Optional[TInput] = None,
106+
instance_id: Optional[str] = None,
107+
start_at: Optional[datetime] = None,
108+
reuse_id_policy: Optional[pb.OrchestrationIdReusePolicy] = None) -> str:
109109

110110
name = orchestrator if isinstance(orchestrator, str) else task.get_name(orchestrator)
111111

@@ -122,14 +122,14 @@ def schedule_new_orchestration(self, orchestrator: Union[task.Orchestrator[TInpu
122122
res: pb.CreateInstanceResponse = self._stub.StartInstance(req)
123123
return res.instanceId
124124

125-
def get_orchestration_state(self, instance_id: str, *, fetch_payloads: bool = True) -> Union[OrchestrationState, None]:
125+
def get_orchestration_state(self, instance_id: str, *, fetch_payloads: bool = True) -> Optional[OrchestrationState]:
126126
req = pb.GetInstanceRequest(instanceId=instance_id, getInputsAndOutputs=fetch_payloads)
127127
res: pb.GetInstanceResponse = self._stub.GetInstance(req)
128128
return new_orchestration_state(req.instanceId, res)
129129

130130
def wait_for_orchestration_start(self, instance_id: str, *,
131131
fetch_payloads: bool = False,
132-
timeout: int = 60) -> Union[OrchestrationState, None]:
132+
timeout: int = 60) -> Optional[OrchestrationState]:
133133
req = pb.GetInstanceRequest(instanceId=instance_id, getInputsAndOutputs=fetch_payloads)
134134
try:
135135
self._logger.info(f"Waiting up to {timeout}s for instance '{instance_id}' to start.")
@@ -144,7 +144,7 @@ def wait_for_orchestration_start(self, instance_id: str, *,
144144

145145
def wait_for_orchestration_completion(self, instance_id: str, *,
146146
fetch_payloads: bool = True,
147-
timeout: int = 60) -> Union[OrchestrationState, None]:
147+
timeout: int = 60) -> Optional[OrchestrationState]:
148148
req = pb.GetInstanceRequest(instanceId=instance_id, getInputsAndOutputs=fetch_payloads)
149149
try:
150150
self._logger.info(f"Waiting {timeout}s for instance '{instance_id}' to complete.")
@@ -170,7 +170,7 @@ def wait_for_orchestration_completion(self, instance_id: str, *,
170170
raise
171171

172172
def raise_orchestration_event(self, instance_id: str, event_name: str, *,
173-
data: Union[Any, None] = None):
173+
data: Optional[Any] = None):
174174
req = pb.RaiseEventRequest(
175175
instanceId=instance_id,
176176
name=event_name,
@@ -180,7 +180,7 @@ def raise_orchestration_event(self, instance_id: str, event_name: str, *,
180180
self._stub.RaiseEvent(req)
181181

182182
def terminate_orchestration(self, instance_id: str, *,
183-
output: Union[Any, None] = None,
183+
output: Optional[Any] = None,
184184
recursive: bool = True):
185185
req = pb.TerminateRequest(
186186
instanceId=instance_id,
@@ -203,4 +203,4 @@ def resume_orchestration(self, instance_id: str):
203203
def purge_orchestration(self, instance_id: str, recursive: bool = True):
204204
req = pb.PurgeInstancesRequest(instanceId=instance_id, recursive=recursive)
205205
self._logger.info(f"Purging instance '{instance_id}'.")
206-
self._stub.PurgeInstances()
206+
self._stub.PurgeInstances(req)

durabletask/internal/__init__.py

Whitespace-only changes.

durabletask/internal/grpc_interceptor.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# Licensed under the MIT License.
33

44
from collections import namedtuple
5-
from typing import List, Tuple
65

76
import grpc
87

@@ -26,7 +25,7 @@ class DefaultClientInterceptorImpl (
2625
StreamUnaryClientInterceptor and StreamStreamClientInterceptor from grpc to add an
2726
interceptor to add additional headers to all calls as needed."""
2827

29-
def __init__(self, metadata: List[Tuple[str, str]]):
28+
def __init__(self, metadata: list[tuple[str, str]]):
3029
super().__init__()
3130
self._metadata = metadata
3231

durabletask/internal/helpers.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import traceback
55
from datetime import datetime
6-
from typing import List, Union
6+
from typing import Optional
77

88
from google.protobuf import timestamp_pb2, wrappers_pb2
99

@@ -12,14 +12,14 @@
1212
# TODO: The new_xxx_event methods are only used by test code and should be moved elsewhere
1313

1414

15-
def new_orchestrator_started_event(timestamp: Union[datetime, None] = None) -> pb.HistoryEvent:
15+
def new_orchestrator_started_event(timestamp: Optional[datetime] = None) -> pb.HistoryEvent:
1616
ts = timestamp_pb2.Timestamp()
1717
if timestamp is not None:
1818
ts.FromDatetime(timestamp)
1919
return pb.HistoryEvent(eventId=-1, timestamp=ts, orchestratorStarted=pb.OrchestratorStartedEvent())
2020

2121

22-
def new_execution_started_event(name: str, instance_id: str, encoded_input: Union[str, None] = None) -> pb.HistoryEvent:
22+
def new_execution_started_event(name: str, instance_id: str, encoded_input: Optional[str] = None) -> pb.HistoryEvent:
2323
return pb.HistoryEvent(
2424
eventId=-1,
2525
timestamp=timestamp_pb2.Timestamp(),
@@ -49,15 +49,15 @@ def new_timer_fired_event(timer_id: int, fire_at: datetime) -> pb.HistoryEvent:
4949
)
5050

5151

52-
def new_task_scheduled_event(event_id: int, name: str, encoded_input: Union[str, None] = None) -> pb.HistoryEvent:
52+
def new_task_scheduled_event(event_id: int, name: str, encoded_input: Optional[str] = None) -> pb.HistoryEvent:
5353
return pb.HistoryEvent(
5454
eventId=event_id,
5555
timestamp=timestamp_pb2.Timestamp(),
5656
taskScheduled=pb.TaskScheduledEvent(name=name, input=get_string_value(encoded_input))
5757
)
5858

5959

60-
def new_task_completed_event(event_id: int, encoded_output: Union[str, None] = None) -> pb.HistoryEvent:
60+
def new_task_completed_event(event_id: int, encoded_output: Optional[str] = None) -> pb.HistoryEvent:
6161
return pb.HistoryEvent(
6262
eventId=-1,
6363
timestamp=timestamp_pb2.Timestamp(),
@@ -77,7 +77,7 @@ def new_sub_orchestration_created_event(
7777
event_id: int,
7878
name: str,
7979
instance_id: str,
80-
encoded_input: Union[str, None] = None) -> pb.HistoryEvent:
80+
encoded_input: Optional[str] = None) -> pb.HistoryEvent:
8181
return pb.HistoryEvent(
8282
eventId=event_id,
8383
timestamp=timestamp_pb2.Timestamp(),
@@ -88,7 +88,7 @@ def new_sub_orchestration_created_event(
8888
)
8989

9090

91-
def new_sub_orchestration_completed_event(event_id: int, encoded_output: Union[str, None] = None) -> pb.HistoryEvent:
91+
def new_sub_orchestration_completed_event(event_id: int, encoded_output: Optional[str] = None) -> pb.HistoryEvent:
9292
return pb.HistoryEvent(
9393
eventId=-1,
9494
timestamp=timestamp_pb2.Timestamp(),
@@ -116,7 +116,7 @@ def new_failure_details(ex: Exception) -> pb.TaskFailureDetails:
116116
)
117117

118118

119-
def new_event_raised_event(name: str, encoded_input: Union[str, None] = None) -> pb.HistoryEvent:
119+
def new_event_raised_event(name: str, encoded_input: Optional[str] = None) -> pb.HistoryEvent:
120120
return pb.HistoryEvent(
121121
eventId=-1,
122122
timestamp=timestamp_pb2.Timestamp(),
@@ -140,7 +140,7 @@ def new_resume_event() -> pb.HistoryEvent:
140140
)
141141

142142

143-
def new_terminated_event(*, encoded_output: Union[str, None] = None) -> pb.HistoryEvent:
143+
def new_terminated_event(*, encoded_output: Optional[str] = None) -> pb.HistoryEvent:
144144
return pb.HistoryEvent(
145145
eventId=-1,
146146
timestamp=timestamp_pb2.Timestamp(),
@@ -150,7 +150,7 @@ def new_terminated_event(*, encoded_output: Union[str, None] = None) -> pb.Histo
150150
)
151151

152152

153-
def get_string_value(val: Union[str, None]) -> Union[wrappers_pb2.StringValue, None]:
153+
def get_string_value(val: Optional[str]) -> Optional[wrappers_pb2.StringValue]:
154154
if val is None:
155155
return None
156156
else:
@@ -160,9 +160,9 @@ def get_string_value(val: Union[str, None]) -> Union[wrappers_pb2.StringValue, N
160160
def new_complete_orchestration_action(
161161
id: int,
162162
status: pb.OrchestrationStatus,
163-
result: Union[str, None] = None,
164-
failure_details: Union[pb.TaskFailureDetails, None] = None,
165-
carryover_events: Union[List[pb.HistoryEvent], None] = None) -> pb.OrchestratorAction:
163+
result: Optional[str] = None,
164+
failure_details: Optional[pb.TaskFailureDetails] = None,
165+
carryover_events: Optional[list[pb.HistoryEvent]] = None) -> pb.OrchestratorAction:
166166
completeOrchestrationAction = pb.CompleteOrchestrationAction(
167167
orchestrationStatus=status,
168168
result=get_string_value(result),
@@ -178,7 +178,7 @@ def new_create_timer_action(id: int, fire_at: datetime) -> pb.OrchestratorAction
178178
return pb.OrchestratorAction(id=id, createTimer=pb.CreateTimerAction(fireAt=timestamp))
179179

180180

181-
def new_schedule_task_action(id: int, name: str, encoded_input: Union[str, None]) -> pb.OrchestratorAction:
181+
def new_schedule_task_action(id: int, name: str, encoded_input: Optional[str]) -> pb.OrchestratorAction:
182182
return pb.OrchestratorAction(id=id, scheduleTask=pb.ScheduleTaskAction(
183183
name=name,
184184
input=get_string_value(encoded_input)
@@ -194,8 +194,8 @@ def new_timestamp(dt: datetime) -> timestamp_pb2.Timestamp:
194194
def new_create_sub_orchestration_action(
195195
id: int,
196196
name: str,
197-
instance_id: Union[str, None],
198-
encoded_input: Union[str, None]) -> pb.OrchestratorAction:
197+
instance_id: Optional[str],
198+
encoded_input: Optional[str]) -> pb.OrchestratorAction:
199199
return pb.OrchestratorAction(id=id, createSubOrchestration=pb.CreateSubOrchestrationAction(
200200
name=name,
201201
instanceId=instance_id,

durabletask/internal/orchestrator_service_pb2.py

Lines changed: 188 additions & 198 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)