Skip to content

Commit a30396b

Browse files
committed
Early draft integration of nex-gen into python
1 parent ee59538 commit a30396b

13 files changed

Lines changed: 2024 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,17 @@ format = [
9898
{ cmd = "cargo fmt", cwd = "temporalio/bridge" },
9999
]
100100
gen-docs = "uv run scripts/gen_docs.py"
101+
gen-nexus-system-api = "uv run scripts/gen_nexus_system_api.py"
101102
gen-protos = [
102103
{ cmd = "uv run scripts/gen_protos.py" },
104+
{ ref = "gen-nexus-system-api" },
103105
{ cmd = "uv run scripts/gen_payload_visitor.py" },
104106
{ cmd = "uv run scripts/gen_bridge_client.py" },
105107
{ ref = "format" },
106108
]
107109
gen-protos-docker = [
108110
{ cmd = "uv run scripts/gen_protos_docker.py" },
111+
{ ref = "gen-nexus-system-api" },
109112
{ cmd = "uv run scripts/gen_payload_visitor.py" },
110113
{ cmd = "uv run scripts/gen_bridge_client.py" },
111114
{ ref = "format" },
@@ -169,7 +172,7 @@ exclude = [
169172
[tool.pydocstyle]
170173
convention = "google"
171174
# https://github.com/PyCQA/pydocstyle/issues/363#issuecomment-625563088
172-
match_dir = "^(?!(docs|scripts|tests|api|proto|\\.)).*"
175+
match_dir = "^(?!(docs|scripts|tests|api|proto|_system|\\.)).*"
173176
add_ignore = [
174177
# We like to wrap at a certain number of chars, even long summary sentences.
175178
# https://github.com/PyCQA/pydocstyle/issues/184

scripts/_nexus/temporal-system.wit

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package temporal:nexus@1.0.0;
2+
3+
world system {
4+
export workflow-service;
5+
}
6+
7+
/// @nexus.endpoint "temporal-system"
8+
interface workflow-service {
9+
use nexus:temporal-types/model@1.0.0.{
10+
duration,
11+
memo,
12+
payloads,
13+
placeholder,
14+
priority,
15+
retry-policy,
16+
search-attributes,
17+
signal-function,
18+
task-queue,
19+
user-metadata,
20+
versioning-override,
21+
workflow-function,
22+
workflow-id-conflict-policy,
23+
workflow-id-reuse-policy,
24+
};
25+
26+
/// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest"
27+
record signal-with-start-workflow-execution-request {
28+
/// @nexus.proto-field "workflow_type"
29+
workflow: workflow-function,
30+
workflow-id: string,
31+
task-queue: task-queue,
32+
/// @nexus.proto-field "signal_name"
33+
signal: signal-function,
34+
workflow-execution-timeout: option<duration>,
35+
workflow-run-timeout: option<duration>,
36+
workflow-task-timeout: option<duration>,
37+
identity: option<string>,
38+
request-id: option<string>,
39+
workflow-id-reuse-policy: option<workflow-id-reuse-policy>,
40+
workflow-id-conflict-policy: option<workflow-id-conflict-policy>,
41+
retry-policy: option<retry-policy>,
42+
cron-schedule: option<string>,
43+
memo: option<memo>,
44+
search-attributes: option<search-attributes>,
45+
priority: option<priority>,
46+
versioning-override: option<versioning-override>,
47+
workflow-start-delay: option<duration>,
48+
user-metadata: option<user-metadata>,
49+
/// @nexus.source python="workflow_namespace" typescript="workflowNamespace"
50+
namespace: string,
51+
/// @nexus.omit
52+
control: placeholder,
53+
/// @nexus.omit
54+
header: placeholder,
55+
/// @nexus.omit
56+
links: placeholder,
57+
/// @nexus.omit
58+
time-skipping-config: placeholder,
59+
}
60+
61+
/// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse"
62+
record signal-with-start-workflow-execution-response {
63+
run-id: option<string>,
64+
started: option<bool>,
65+
/// @nexus.omit
66+
signal-link: placeholder,
67+
}
68+
69+
/// @nexus.output-transform
70+
/// python-type="workflow.ExternalWorkflowHandle[typing.Any]"
71+
/// python="workflow.get_external_workflow_handle(request.workflow_id, run_id=result.run_id)"
72+
/// @nexus.operation name="SignalWithStartWorkflowExecution"
73+
signal-with-start-workflow: func(
74+
request: signal-with-start-workflow-execution-request,
75+
) -> signal-with-start-workflow-execution-response;
76+
}

scripts/gen_nexus_system_api.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import os
2+
import shutil
3+
import subprocess
4+
import sys
5+
import tempfile
6+
from pathlib import Path
7+
8+
import gen_protos
9+
10+
base_dir = Path(__file__).parent.parent
11+
wit_path = base_dir / "scripts" / "_nexus" / "temporal-system.wit"
12+
output_dir = base_dir / "temporalio" / "nexus" / "_system" / "workflow_service"
13+
workflowservice_request_response_proto = (
14+
gen_protos.api_proto_dir
15+
/ "temporal"
16+
/ "api"
17+
/ "workflowservice"
18+
/ "v1"
19+
/ "request_response.proto"
20+
)
21+
22+
23+
def nexus_api_gen_command() -> list[str]:
24+
if bin_path := os.environ.get("NEXUS_API_GEN_BIN"):
25+
return [bin_path]
26+
27+
candidates = []
28+
if source_dir := os.environ.get("NEXUS_API_GEN_DIR"):
29+
candidates.append(Path(source_dir))
30+
candidates.extend(
31+
[
32+
base_dir / "tools" / "nexus-api-gen",
33+
base_dir.parent / "nexus-api-gen",
34+
]
35+
)
36+
for candidate in candidates:
37+
manifest_path = candidate / "Cargo.toml"
38+
if manifest_path.exists():
39+
return ["cargo", "run", "--manifest-path", str(manifest_path), "--"]
40+
41+
raise RuntimeError(
42+
"nexus-api-gen not found; set NEXUS_API_GEN_BIN or NEXUS_API_GEN_DIR"
43+
)
44+
45+
46+
def build_descriptor_set(descriptor_path: Path) -> None:
47+
subprocess.check_call(
48+
[
49+
sys.executable,
50+
"-mgrpc_tools.protoc",
51+
f"--proto_path={gen_protos.api_proto_dir}",
52+
f"--proto_path={gen_protos.proto_dir}",
53+
"--include_imports",
54+
f"--descriptor_set_out={descriptor_path}",
55+
str(workflowservice_request_response_proto),
56+
]
57+
)
58+
59+
60+
def strip_unsupported_pyright_comments() -> None:
61+
for path in output_dir.rglob("*.py"):
62+
content = path.read_text()
63+
content = content.replace("# pyright: reportAny=false\n", "")
64+
content = content.replace(
65+
"# pyright: reportAny=false, reportExplicitAny=false\n", ""
66+
)
67+
path.write_text(content)
68+
69+
70+
def generate_nexus_system_api() -> None:
71+
if not wit_path.exists():
72+
raise RuntimeError(f"missing WIT source: {wit_path}")
73+
74+
with tempfile.TemporaryDirectory(dir=base_dir) as temp_dir:
75+
descriptor_path = Path(temp_dir) / "temporal_api.bin"
76+
build_descriptor_set(descriptor_path)
77+
78+
shutil.rmtree(output_dir, ignore_errors=True)
79+
output_dir.parent.mkdir(parents=True, exist_ok=True)
80+
subprocess.check_call(
81+
[
82+
*nexus_api_gen_command(),
83+
"generate",
84+
"--lang",
85+
"python",
86+
"--input",
87+
str(wit_path),
88+
"--descriptors",
89+
str(descriptor_path),
90+
"--output",
91+
str(output_dir),
92+
]
93+
)
94+
95+
(output_dir.parent / "__init__.py").touch()
96+
strip_unsupported_pyright_comments()
97+
subprocess.check_call(
98+
[
99+
sys.executable,
100+
"-m",
101+
"ruff",
102+
"check",
103+
"--select",
104+
"I",
105+
"--fix",
106+
str(output_dir),
107+
]
108+
)
109+
subprocess.check_call(
110+
[
111+
sys.executable,
112+
"-m",
113+
"ruff",
114+
"format",
115+
str(output_dir),
116+
]
117+
)
118+
119+
120+
if __name__ == "__main__":
121+
print("Generating Nexus system API...", file=sys.stderr)
122+
generate_nexus_system_api()
123+
print("Done", file=sys.stderr)

temporalio/nexus/_system/__init__.py

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by nexus-api-gen. DO NOT EDIT!
2+
3+
from __future__ import annotations
4+
5+
from . import models
6+
from . import service as _service
7+
from .operations.signal_with_start_workflow import signal_with_start_workflow
8+
9+
__all__ = [
10+
"models",
11+
"signal_with_start_workflow",
12+
"__nexus_operation_registry__",
13+
]
14+
15+
16+
__nexus_operation_registry__ = {
17+
(
18+
"WorkflowService",
19+
"SignalWithStartWorkflowExecution",
20+
): _service.WorkflowService.signal_with_start_workflow,
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Generated by nexus-api-gen. DO NOT EDIT!
2+
3+
from __future__ import annotations
4+
5+
__all__ = []
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Generated by nexus-api-gen. DO NOT EDIT!
2+
3+
from __future__ import annotations
4+
5+
from .model_overrides import * # noqa: F401,F403

0 commit comments

Comments
 (0)