Skip to content

Commit 774fccf

Browse files
authored
Merge branch 'main' into workflow-streams-can-fix
2 parents b66c2be + c37fa7e commit 774fccf

114 files changed

Lines changed: 7184 additions & 1464 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CHANGELOG.md merge=union

.github/scripts/install_release_package.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,41 @@
66
import importlib.metadata
77
import subprocess
88
import sys
9+
import time
910
from collections.abc import Sequence
1011

12+
RELEASE_INSTALL_ATTEMPTS = 31
13+
RELEASE_INSTALL_RETRY_SECONDS = 30
14+
1115

1216
def _pip_install(args: Sequence[str]) -> None:
1317
subprocess.check_call([sys.executable, "-m", "pip", "install", *args])
1418

1519

20+
def _pip_install_with_retries(args: Sequence[str]) -> None:
21+
for attempt in range(1, RELEASE_INSTALL_ATTEMPTS + 1):
22+
try:
23+
_pip_install(args)
24+
return
25+
except subprocess.CalledProcessError:
26+
if attempt == RELEASE_INSTALL_ATTEMPTS:
27+
raise
28+
print(
29+
"Package was not installable yet; retrying in "
30+
f"{RELEASE_INSTALL_RETRY_SECONDS}s "
31+
f"({attempt}/{RELEASE_INSTALL_ATTEMPTS})",
32+
flush=True,
33+
)
34+
time.sleep(RELEASE_INSTALL_RETRY_SECONDS)
35+
36+
1637
def install_package(args: argparse.Namespace) -> None:
1738
package = f"temporalio=={args.version}"
1839
if args.dependency_index_url:
19-
_pip_install(
40+
_pip_install_with_retries(
2041
[
2142
"--prefer-binary",
43+
"--no-cache-dir",
2244
"--index-url",
2345
args.index_url,
2446
"--no-deps",
@@ -37,7 +59,15 @@ def install_package(args: argparse.Namespace) -> None:
3759
]
3860
)
3961
else:
40-
_pip_install(["--prefer-binary", "--index-url", args.index_url, package])
62+
_pip_install_with_retries(
63+
[
64+
"--prefer-binary",
65+
"--no-cache-dir",
66+
"--index-url",
67+
args.index_url,
68+
package,
69+
]
70+
)
4171

4272
subprocess.check_call([sys.executable, "-m", "pip", "check"])
4373

.github/scripts/release_verify.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import ast
77
import pathlib
88
import re
9+
import subprocess
910
from collections.abc import Sequence
1011

1112
try:
@@ -108,6 +109,139 @@ def verify_dist(args: argparse.Namespace) -> None:
108109
print(f" {name}")
109110

110111

112+
def _git(args: Sequence[str], *, cwd: pathlib.Path | None = None) -> str:
113+
return subprocess.check_output(
114+
["git", *args],
115+
cwd=cwd,
116+
encoding="utf-8",
117+
stderr=subprocess.STDOUT,
118+
).strip()
119+
120+
121+
def _version_tuple(version: str) -> tuple[int, ...] | None:
122+
match = re.fullmatch(r"([0-9]+(?:\.[0-9]+)+)(?:[a-zA-Z0-9_.+-]+)?", version)
123+
if not match:
124+
return None
125+
return tuple(int(part) for part in match.group(1).split("."))
126+
127+
128+
def _previous_release_tag(version: str) -> str:
129+
current = _version_tuple(version)
130+
if current is None:
131+
raise RuntimeError(f"Cannot determine previous release for {version!r}")
132+
133+
candidates: list[tuple[int, ...]] = []
134+
for tag in _git(["tag"]).splitlines():
135+
tag_version = _version_tuple(tag)
136+
if tag_version is not None and tag_version < current:
137+
candidates.append(tag_version)
138+
if not candidates:
139+
raise RuntimeError(f"Could not find a previous release tag before {version!r}")
140+
return ".".join(str(part) for part in max(candidates))
141+
142+
143+
def _gitlink(rev: str, path: str) -> str:
144+
output = _git(["ls-tree", rev, path])
145+
parts = output.split()
146+
if len(parts) < 3 or parts[0] != "160000":
147+
raise RuntimeError(f"Could not find submodule gitlink {path!r} at {rev!r}")
148+
return parts[2]
149+
150+
151+
def _clean_commit_subject(subject: str) -> str:
152+
subject = subject.encode("ascii", "ignore").decode("ascii")
153+
subject = re.sub(r"\s+", " ", subject).strip()
154+
subject = re.sub(r"^:[a-z0-9_+-]+:\s*", "", subject)
155+
return subject.replace(" : ", ": ")
156+
157+
158+
def _link_sdk_core_prs(subject: str) -> str:
159+
return re.sub(
160+
r"\(#([0-9]+)\)",
161+
r"([#\1](https://github.com/temporalio/sdk-rust/pull/\1))",
162+
subject,
163+
)
164+
165+
166+
def _sdk_core_release_notes(version: str, path: str) -> list[str]:
167+
previous_tag = _previous_release_tag(version)
168+
previous_commit = _gitlink(previous_tag, path)
169+
current_commit = _gitlink("HEAD", path)
170+
if previous_commit == current_commit:
171+
return []
172+
173+
submodule_path = pathlib.Path(path)
174+
if not (submodule_path / ".git").exists():
175+
raise RuntimeError(
176+
f"Submodule {path!r} is not initialized; checkout with submodules"
177+
)
178+
179+
log_args = [
180+
"log",
181+
"--format=%H%x00%h%x00%s",
182+
"--reverse",
183+
f"{previous_commit}..{current_commit}",
184+
]
185+
try:
186+
log_output = _git(log_args, cwd=submodule_path)
187+
except subprocess.CalledProcessError:
188+
_git(["fetch", "--quiet", "origin", "main"], cwd=submodule_path)
189+
log_output = _git(log_args, cwd=submodule_path)
190+
if not log_output:
191+
return []
192+
193+
lines = ["### SDK Core", ""]
194+
for line in log_output.splitlines():
195+
full_hash, short_hash, subject = line.split("\0", 2)
196+
subject = _link_sdk_core_prs(_clean_commit_subject(subject))
197+
lines.append(
198+
f"- [`{short_hash}`](https://github.com/temporalio/sdk-rust/commit/"
199+
f"{full_hash}) {subject}"
200+
)
201+
return lines
202+
203+
204+
def changelog_notes(args: argparse.Namespace) -> None:
205+
changelog_path = pathlib.Path(args.changelog)
206+
lines = changelog_path.read_text(encoding="utf-8").splitlines()
207+
heading = re.compile(r"^## \[(?P<version>[^\]]+)\](?:\s+-\s+.*)?\s*$")
208+
209+
start = None
210+
for index, line in enumerate(lines):
211+
match = heading.match(line)
212+
if match and match.group("version") == args.version:
213+
start = index + 1
214+
break
215+
216+
if start is None:
217+
raise RuntimeError(
218+
f"Could not find changelog section for version {args.version!r}"
219+
)
220+
221+
end = len(lines)
222+
for index in range(start, len(lines)):
223+
if lines[index].startswith("## "):
224+
end = index
225+
break
226+
227+
section_lines = lines[start:end]
228+
while section_lines and not section_lines[0].strip():
229+
section_lines.pop(0)
230+
while section_lines and not section_lines[-1].strip():
231+
section_lines.pop()
232+
233+
if not section_lines:
234+
raise RuntimeError(f"Changelog section for {args.version!r} is empty")
235+
236+
note_lines = ["## Notable Changes", "", *section_lines]
237+
sdk_core_notes = _sdk_core_release_notes(args.version, args.sdk_core_path)
238+
if sdk_core_notes:
239+
note_lines.extend(["", *sdk_core_notes])
240+
241+
notes = "\n".join(note_lines) + "\n"
242+
pathlib.Path(args.output).write_text(notes, encoding="utf-8")
243+
244+
111245
def main(argv: Sequence[str] | None = None) -> None:
112246
parser = argparse.ArgumentParser()
113247
subparsers = parser.add_subparsers(required=True)
@@ -122,6 +256,15 @@ def main(argv: Sequence[str] | None = None) -> None:
122256
verify_parser.add_argument("--dist-dir", default="dist")
123257
verify_parser.set_defaults(func=verify_dist)
124258

259+
changelog_parser = subparsers.add_parser("changelog-notes")
260+
changelog_parser.add_argument("--version", required=True)
261+
changelog_parser.add_argument("--changelog", default="CHANGELOG.md")
262+
changelog_parser.add_argument("--output", required=True)
263+
changelog_parser.add_argument(
264+
"--sdk-core-path", default="temporalio/bridge/sdk-core"
265+
)
266+
changelog_parser.set_defaults(func=changelog_notes)
267+
125268
args = parser.parse_args(argv)
126269
args.func(args)
127270

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ jobs:
123123
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
124124
- run: uv tool install poethepoet
125125
- run: uv remove google-adk --optional google-adk
126+
- run: uv add --dev --python 3.10 "googleapis-common-protos==1.70.0"
126127
- run: uv add --python 3.10 "protobuf<4"
127128
- run: uv sync --all-extras
128129
- run: poe build-develop

.github/workflows/release-publish.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ jobs:
101101
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
102102
with:
103103
ref: ${{ github.sha }}
104+
fetch-depth: 0
105+
submodules: recursive
104106
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
105107
with:
106108
python-version: "3.11"
@@ -111,6 +113,12 @@ jobs:
111113
python .github/scripts/release_verify.py validate-version \
112114
--sha "$(git rev-parse HEAD)" \
113115
--github-output "$GITHUB_OUTPUT"
116+
- name: Validate changelog release notes
117+
run: |
118+
set -euo pipefail
119+
python .github/scripts/release_verify.py changelog-notes \
120+
--version "${{ steps.validate_versions.outputs.version }}" \
121+
--output /tmp/release-notes.md
114122
- name: Download and flatten artifacts
115123
env:
116124
GH_TOKEN: ${{ github.token }}
@@ -249,6 +257,19 @@ jobs:
249257
permissions:
250258
contents: write
251259
steps:
260+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
261+
with:
262+
ref: ${{ needs.verify_artifacts.outputs.release_sha }}
263+
fetch-depth: 0
264+
submodules: recursive
265+
- name: Build changelog release notes
266+
env:
267+
VERSION: ${{ needs.verify_artifacts.outputs.version }}
268+
run: |
269+
set -euo pipefail
270+
python3 .github/scripts/release_verify.py changelog-notes \
271+
--version "$VERSION" \
272+
--output release-notes.md
252273
- name: Create draft release with generated notes
253274
env:
254275
GH_TOKEN: ${{ github.token }}
@@ -261,4 +282,5 @@ jobs:
261282
--target "$RELEASE_SHA" \
262283
--title "$VERSION" \
263284
--draft \
285+
--notes-file release-notes.md \
264286
--generate-notes

CHANGELOG.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!--
2+
High-level release notes.
3+
Loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4+
5+
When your PR includes a user-facing change, add an entry below under the
6+
appropriate heading. Within each heading content can be free-form. Feel free
7+
to include examples, links to docs, or any other relevant information.
8+
9+
### Added — new features
10+
### Changed — changes in existing functionality
11+
### Deprecated — soon-to-be-removed features
12+
### Breaking Changes — removed or backwards-incompatible features
13+
### Fixed — notable bug fixes
14+
### Security — notable security fixes
15+
-->
16+
17+
# Changelog
18+
19+
## [Unreleased]
20+
21+
### Added
22+
23+
### Changed
24+
25+
- AWS Lambda worker `configure` parameter supports sync, async, and async
26+
generator style functions. This callback is invoked on the asyncio event
27+
loop.
28+
- Relaxed the protobuf dependency bounds to allow protobuf 7 where compatible
29+
with the selected optional dependencies.
30+
31+
### Deprecated
32+
33+
### Breaking Changes
34+
35+
- AWS Lambda worker `configure` parameter has been changed to be invoked
36+
per-invocation of the worker instead of only at startup. It is advised that
37+
any shared, heavy-weight operations are performed outside of the callback
38+
before `run_worker` is invoked.
39+
40+
### Fixed
41+
42+
### Security
43+
44+
## [1.29.0] - 2026-06-17
45+
46+
### Added
47+
48+
- Added experimental `temporalio.workflow.signal_with_start_workflow`, backed by
49+
generated system Nexus bindings for
50+
`WorkflowService.SignalWithStartWorkflowExecution`.
51+
- Added OpenAI Agents plugin support for `CustomTool` dispatch, including lazy
52+
tool discovery through `defer_loading`.
53+
54+
### Changed
55+
56+
- Client connections now use gzip transport-level gRPC compression by default.
57+
Pass `grpc_compression=GrpcCompression.NONE` to `Client.connect` or
58+
`CloudOperationsClient.connect` to disable it.
59+
60+
### Breaking Changes
61+
62+
- `StartWorkflowUpdateWithStartInput` now owns the authoritative
63+
`rpc_metadata` and `rpc_timeout` fields for
64+
`OutboundInterceptor.start_update_with_start_workflow`. These fields were
65+
removed from the nested update-with-start input objects, so custom
66+
interceptors that accessed them there should read or update the top-level
67+
fields instead.
68+
69+
### Fixed
70+
71+
- Fixed `breakpoint()` and `pdb.set_trace()` inside workflow code when a worker
72+
runs with `debug_mode=True` or `TEMPORAL_DEBUG=1`; sandboxed workflows without
73+
debug mode now get a clearer error pointing to `debug_mode=True`.
74+
- Fixed `start_update_with_start_workflow` interceptor handling so RPC metadata
75+
and timeouts are forwarded to the underlying `execute_multi_operation` call.
76+
- Fixed OpenAI Agents plugin streamed event serialization when pydantic had not
77+
yet built deferred schemas, and fixed terminal sandbox errors retrying
78+
forever.
79+
- Removed the lazy-connect lock from the per-RPC hot path. It was previously
80+
acquired on every RPC, putting an event-loop-bound primitive on the hot path;
81+
it is now skipped once the client is connected. This reduces the client's
82+
coupling to the event loop it connected on, which can help when reusing a
83+
single long-lived `Client` across event loops or threads (e.g. the
84+
dedicated-loop pattern used with gevent/gunicorn and synchronous services).
85+
Note this does not make a `Client` fully thread- or loop-agnostic; reusing one
86+
long-lived loop is still the recommended pattern.

CONTRIBUTING.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Contributing to the Temporal Python SDK
2+
3+
Thanks for your interest in contributing!
4+
5+
All contributors must complete the Temporal Contributor License Agreement (CLA) before changes
6+
can be merged. A link to the CLA will be posted in the PR.
7+
8+
See the [README](README.md) for build and development instructions.
9+
10+
## Changelog
11+
12+
User-facing changes are recorded in [`CHANGELOG.md`](CHANGELOG.md), loosely following the
13+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
14+
15+
If your PR includes a user-facing change (new feature, behavior change, deprecation, breaking
16+
change, notable bug fix, or security fix), add a short, high-level entry to the `## [Unreleased]`
17+
section at the top of `CHANGELOG.md` under the appropriate heading:
18+
Added, Changed, Deprecated, Breaking Changes, Fixed, or Security.
19+
20+
Keep entries high-level and written for users. The full commit log is appended at release time,
21+
so internal-only changes (refactors, tests, CI, docs) don't need an entry.

0 commit comments

Comments
 (0)