Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ccd0e97
Pointed Core at a commit that vendors the Stream API.
moetemp Nov 13, 2026
b874f86
Taught the generator scripts to build the Stream package.
moetemp Nov 13, 2026
02645de
Generated the Stream protos, bindings, visitor and Workflow exports.
moetemp Nov 13, 2026
7f2eca0
Added the support functions that the generated Stream code calls.
moetemp Nov 13, 2026
20815db
Routed a client's Stream operations to a Stream Store.
moetemp Nov 13, 2026
a92a582
Ran Workflow Stream operations in a Local Activity with a Store.
moetemp Nov 13, 2026
0c0f2e1
Gave an Activity's info the Workflow Task of a Local Activity.
moetemp Nov 13, 2026
e4c2b32
Wrote a Workflow's Stream append as its Local Activity's Producer.
moetemp Nov 13, 2026
a644cf5
Pointed Core at the commit with the Stream crate.
moetemp Nov 13, 2026
1f154d7
Bound the Stream runtime in the bridge behind a `streams` feature.
moetemp Nov 13, 2026
1f0993a
Raised one `StreamError` for every failed Stream call.
moetemp Nov 13, 2026
43654eb
Added a memory Stream Store and moved the Stream tests to it.
moetemp Nov 13, 2026
7337df1
Gave a Workflow's Stream create the chain's first run id.
moetemp Nov 13, 2026
7c89321
Moved Core to the head of the Stream crate.
moetemp Nov 13, 2026
e83221c
Kept the linters out of the Core submodule.
moetemp Nov 13, 2026
7f4efeb
Bound the Redis Stream runtime in the bridge.
moetemp Nov 13, 2026
307a5cd
Added the Redis Stream Store with its tests.
moetemp Nov 13, 2026
6b1cf02
Started a Redis in CI for the Redis Stream Store tests.
moetemp Nov 13, 2026
4c3e86b
Kept the base of the bridge Stores next to `StreamStore`.
moetemp Nov 13, 2026
8911d75
Turned on Streams in a FIPS build.
moetemp Nov 13, 2026
01a154f
Pointed the Core submodule at the fork that has the Stream crate.
moetemp Nov 13, 2026
e70dff4
Gave the Redis Stream Store a cargo feature of its own.
moetemp Nov 13, 2026
41a3c2b
Sent a Workflow's Stream append with a sequence, so its retry writes …
moetemp Nov 13, 2026
57d1473
Moved Core to the API files with the sequence parameter of append.
moetemp Nov 13, 2026
2bf62e7
Moved the Redis Store pin to the commit that follows the new Core.
moetemp Nov 13, 2026
5bc79ea
Regenerated the Stream code for the sequence parameter of append.
moetemp Nov 13, 2026
b097176
Refused an append and a close from Workflow code on a Store.
moetemp Nov 13, 2026
f491efb
Merged the branch of the generated Stream code.
moetemp Nov 13, 2026
1c836c8
Moved the Worker side of a Stream Store into one module.
moetemp Nov 13, 2026
f1fdc70
Made the call of a Stream Store private.
moetemp Nov 13, 2026
ed82555
Said why stream_stores is a list, and named the Redis step in CI.
moetemp Nov 13, 2026
9fa615e
Took the Stream Stores of a client as one config with a selector.
moetemp Nov 13, 2026
fa62ab2
Let WorkflowEnvironment take a Stream config for its client.
moetemp Nov 13, 2026
f1629bb
Let a Worker serve every Stream Store of its client.
moetemp Nov 13, 2026
dfa007b
Moved the options of the Stream Local Activity to the Stream config.
moetemp Nov 13, 2026
2d86f5b
Read the ref of a Stream call from its request object.
moetemp Nov 13, 2026
d0fba10
Added BufferedAppender, which batches the appends of one Stream.
moetemp Nov 13, 2026
51a7d0b
Moved Core to the API files with the Workflow Stream marks.
moetemp Nov 13, 2026
ea1523d
Merged the regenerated Stream code.
moetemp Nov 13, 2026
2c8b049
Followed the Workflow Stream that has no append, close or read.
moetemp Nov 13, 2026
336dbed
Let a module of the Stream Workflow package be the first import.
moetemp Nov 13, 2026
56c9937
Let the user's Activity interceptors see the Stream Local Activity.
moetemp Nov 13, 2026
63180a3
Raised an error for a Stream call when the client has no Store.
moetemp Nov 13, 2026
6427a7f
Gave StreamStore the five Stream operations as methods.
moetemp Nov 13, 2026
e2d3bf8
Moved the Worker side of a Stream call into the Store.
moetemp Nov 13, 2026
419fc75
Moved Core to the Stream crate that has no Python script.
moetemp Nov 13, 2026
a69aef6
Dropped the lint excludes for the Core submodule.
moetemp Nov 13, 2026
138f338
Removed a stale sentence from the StreamStore docstring.
moetemp Nov 13, 2026
51fe79d
Merged the generated-code branch, which sits on the Workflow Task info.
moetemp Nov 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/actions/install-redis/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Install Redis
description: Install and start a Redis server for the tests of the Redis Stream Store.
runs:
using: composite
steps:
- if: runner.os == 'Linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends redis-server
- if: runner.os == 'macOS'
shell: bash
run: brew install redis
# Redis has no official Windows build, so the Redis Store tests skip there. The port is not the
# default one, since the Linux package starts its own server on 6379.
- if: runner.os != 'Windows'
shell: bash
run: |
redis-server --port 6380 --save "" --appendonly no --daemonize yes
for _ in $(seq 1 50); do
redis-cli -p 6380 ping && break
sleep 0.1
done
redis-cli -p 6380 ping
echo "STREAMS_REDIS_URL=redis://127.0.0.1:6380" >> "$GITHUB_ENV"
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ jobs:
if: ${{ matrix.clippyLinter }}
- run: poe build-develop
- run: poe lint
- name: Install and start Redis for the Redis Stream Store tests
uses: ./.github/actions/install-redis
- run: mkdir junit-xml
- run: poe test ${{matrix.pytestExtraArgs}} -s --junit-xml=junit-xml/${{ matrix.python }}--${{ matrix.os }}.xml
timeout-minutes: 15
Expand Down
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "sdk-core"]
path = temporalio/bridge/sdk-core
url = https://github.com/temporalio/sdk-rust.git
url = https://github.com/moetemp/sdk-rust.git
17 changes: 12 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ dev = [
]

[tool.poe.tasks]
build-develop = "uv run maturin develop --uv"
build-develop-with-release = { cmd = "uv run maturin develop --release --uv" }
build-develop = "uv run maturin develop --uv --features streams-redis"
build-develop-with-release = { cmd = "uv run maturin develop --release --uv --features streams-redis" }
# FIPS builds: swap the rustls stack onto aws-lc-rs FIPS mode (aws-lc-fips-sys), eliminating `ring`.
build-develop-fips = { cmd = "uv run maturin develop --uv --no-default-features --features fips", env = { TEMPORALIO_FIPS = "1" } }
build-wheel-fips = { cmd = "uv run maturin build --release --no-default-features --features fips", env = { TEMPORALIO_FIPS = "1" } }
Expand Down Expand Up @@ -153,18 +153,20 @@ filterwarnings = [
before-all = "pip install protoc-wheel-0"
build = "cp311-win_amd64 cp311-manylinux_x86_64 cp311-manylinux_aarch64 cp311-musllinux_x86_64 cp311-musllinux_aarch64 cp311-macosx_x86_64 cp311-macosx_arm64"
build-verbosity = 1
# The official wheels link the Stream runtime.
environment = { MATURIN_PEP517_ARGS = "--features streams-redis" }

[tool.cibuildwheel.macos]
environment = { MACOSX_DEPLOYMENT_TARGET = "10.12" }
environment = { MACOSX_DEPLOYMENT_TARGET = "10.12", MATURIN_PEP517_ARGS = "--features streams-redis" }

[tool.cibuildwheel.linux]
before-all = "sh scripts/cibuildwheel_before_all_linux.sh"
before-build = "sh scripts/cibuildwheel_before_build_linux.sh"
environment = { PATH = "$PATH:$HOME/.cargo/bin", CARGO_NET_GIT_FETCH_WITH_CLI = "true" }
environment = { PATH = "$PATH:$HOME/.cargo/bin", CARGO_NET_GIT_FETCH_WITH_CLI = "true", MATURIN_PEP517_ARGS = "--features streams-redis" }

[[tool.cibuildwheel.overrides]]
select = "*musllinux*"
environment = { PATH = "$PATH:$HOME/.cargo/bin", CARGO_NET_GIT_FETCH_WITH_CLI = "true", RUSTFLAGS = "-C target-feature=-crt-static" }
environment = { PATH = "$PATH:$HOME/.cargo/bin", CARGO_NET_GIT_FETCH_WITH_CLI = "true", RUSTFLAGS = "-C target-feature=-crt-static", MATURIN_PEP517_ARGS = "--features streams-redis" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like quite a bit of shenanigans involved here, I'll ignore the pyproject until we're further along in core.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Most of it is there so the dev build and the wheels link the Stream runtime while it's behind a cargo feature. The rest keeps the linters out of the Core submodule.

[tool.mypy]
ignore_missing_imports = true
Expand All @@ -173,12 +175,17 @@ exclude = [
'temporalio/api',
'temporalio/bridge/proto',
'temporalio/nexus/system/workflow_service',
'temporalio/nexus/system/stream_service',
]

[[tool.mypy.overrides]]
module = "temporalio.nexus.system.workflow_service.*"
ignore_errors = true

[[tool.mypy.overrides]]
module = "temporalio.nexus.system.stream_service.*"
ignore_errors = true

[tool.pydocstyle]
convention = "google"
# https://github.com/PyCQA/pydocstyle/issues/363#issuecomment-625563088
Expand Down
80 changes: 65 additions & 15 deletions scripts/gen_nexus_system_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@
)
wit_deps_dir = wit_input_dir / "deps"
workflow_output_dir = base_dir / "temporalio" / "nexus" / "system" / "workflow_service"
stream_output_dir = base_dir / "temporalio" / "nexus" / "system" / "stream_service"
notification_output_dir = base_dir / "temporalio" / "nexus" / "notifications"
workflow_init_path = base_dir / "temporalio" / "workflow" / "__init__.py"
NEX_GEN_VERSION = "0.2.8"
# The Stream package needs a newer nexgen, which rejects the other WITs. One pin is enough
# again once the api WITs and a nexgen release agree. No release has nexgen commit 9653a0f5,
# the `main` of nexgen, yet, so NEX_GEN_STREAM_BIN points at a build of it.
NEX_GEN_STREAM_VERSION = "0.2.12"
proto_files = [
gen_protos.api_proto_dir
/ "temporal"
Expand All @@ -36,10 +41,22 @@
gen_protos.api_proto_dir
/ "temporal"
/ "api"
/ "streamservice"
/ "v1"
/ "request_response.proto",
gen_protos.api_proto_dir
/ "temporal"
/ "api"
/ "notificationservice"
/ "v1"
/ "request_response.proto",
]
# The Workflow functions of these generated packages are exported by temporalio.workflow.
# They are in import order, so that sorting the imports keeps them between the markers.
workflow_export_packages = [
("temporalio.nexus.system.stream_service.workflow", stream_output_dir / "workflow"),
("temporalio.nexus.system.workflow_service", workflow_output_dir),
]


def nex_gen_command() -> list[str]:
Expand All @@ -66,6 +83,20 @@ def nex_gen_command() -> list[str]:
return ["nexgen"]


def nex_gen_stream_command() -> list[str]:
bin_path = os.environ.get("NEX_GEN_STREAM_BIN")
if not bin_path:
raise RuntimeError(
"NEX_GEN_STREAM_BIN must point at a nexgen build of commit 9653a0f5"
)
version = subprocess.check_output([bin_path, "--version"], text=True).strip()
if version != f"nexgen {NEX_GEN_STREAM_VERSION}":
raise RuntimeError(
f"NEX_GEN_STREAM_BIN is {version}, not nexgen {NEX_GEN_STREAM_VERSION}"
)
return [bin_path]


def build_descriptor_set(descriptor_path: Path) -> None:
subprocess.check_call(
[
Expand All @@ -85,14 +116,15 @@ def generate_package(
output_dir: Path,
descriptor_path: Path,
*,
native_api: bool,
flags: list[str],
) -> None:
args = [*command, "python", str(wit_input_dir / wit_name), str(wit_deps_dir)]
if native_api:
args.append("--native-api")
subprocess.check_call(
[
*args,
*command,
"python",
str(wit_input_dir / wit_name),
str(wit_deps_dir),
*flags,
"--system-nexus",
"--support-package",
"temporalio.nexus.system._support",
Expand Down Expand Up @@ -132,17 +164,26 @@ def replace_marker_block(


def generate_workflow_exports() -> None:
exports = generated_exports(workflow_output_dir / "__init__.py")
package_exports = [
(module, generated_exports(package_dir / "__init__.py"))
for module, package_dir in workflow_export_packages
]
import_block = [
"# BEGIN GENERATED NEXUS SYSTEM EXPORTS\n",
"from temporalio.nexus.system.workflow_service import (\n",
*[f" {export},\n" for export in exports],
")\n",
*[
line
for module, exports in package_exports
for line in [
f"from {module} import (\n",
*[f" {export},\n" for export in exports],
")\n",
]
],
"# END GENERATED NEXUS SYSTEM EXPORTS\n",
]
all_block = [
" # BEGIN GENERATED NEXUS SYSTEM __ALL__\n",
*[f' "{export}",\n' for export in exports],
*[f' "{export}",\n' for _, exports in package_exports for export in exports],
" # END GENERATED NEXUS SYSTEM __ALL__\n",
]
content = workflow_init_path.read_text()
Expand All @@ -162,6 +203,7 @@ def generate_workflow_exports() -> None:
def generate_nexus_system_api() -> None:
required_paths = [
wit_input_dir / "workflow-service.wit",
wit_input_dir / "stream-service.wit",
wit_input_dir / "notification-service.wit",
wit_deps_dir,
*proto_files,
Expand All @@ -173,23 +215,31 @@ def generate_nexus_system_api() -> None:
descriptor_path = Path(temp_dir) / "temporal_api.bin"
build_descriptor_set(descriptor_path)
command = nex_gen_command()
for wit_name, output_dir, native_api in (
("workflow-service.wit", workflow_output_dir, True),
("notification-service.wit", notification_output_dir, False),
stream_command = nex_gen_stream_command()
for wit_name, output_dir, package_command, flags in (
("workflow-service.wit", workflow_output_dir, command, ["--native-api"]),
(
"stream-service.wit",
stream_output_dir,
stream_command,
["--targets", "workflow,client"],
),
("notification-service.wit", notification_output_dir, command, []),
):
shutil.rmtree(output_dir, ignore_errors=True)
output_dir.parent.mkdir(parents=True, exist_ok=True)
generate_package(
command,
package_command,
wit_name,
output_dir,
descriptor_path,
native_api=native_api,
flags=flags,
)
workflow_output_dir.parent.joinpath("__init__.py").touch()
generate_workflow_exports()
format_paths = [
str(workflow_output_dir),
str(stream_output_dir),
str(notification_output_dir),
str(workflow_init_path),
]
Expand Down
34 changes: 22 additions & 12 deletions scripts/gen_payload_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,30 @@


def discover_system_nexus_roots() -> list[Descriptor]:
module_path = base_dir / "temporalio" / "nexus" / "system" / "workflow_service"
spec = spec_from_file_location(
"temporalio_nexus_system_workflow_service",
module_path / "__init__.py",
submodule_search_locations=[str(module_path)],
)
if spec is None or spec.loader is None:
raise RuntimeError(f"Cannot load generated system service from {module_path}")
module = module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
services: list[type] = []
system_path = base_dir / "temporalio" / "nexus" / "system"
for module_path in sorted(p.parent for p in system_path.glob("*/services.py")):
spec = spec_from_file_location(
f"temporalio_nexus_system_{module_path.name}",
module_path / "__init__.py",
submodule_search_locations=[str(module_path)],
)
if spec is None or spec.loader is None:
raise RuntimeError(
f"Cannot load generated system service from {module_path}"
)
module = module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
services.extend(
value
for value in vars(module.services).values()
if isinstance(value, type) and nexusrpc.get_service_definition(value)
)

annotations = [a for service in services for a in get_type_hints(service).values()]
roots: list[Descriptor] = []
for annotation in get_type_hints(module._services.WorkflowService).values():
for annotation in annotations:
if get_origin(annotation) is not nexusrpc.Operation:
continue
for operation_type in get_args(annotation):
Expand Down
Loading