Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
99 changes: 73 additions & 26 deletions nexus_standalone_operations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ This sample demonstrates how to execute Nexus operations directly from client co
without wrapping them in a workflow. It shows both synchronous and asynchronous
(workflow-backed) operations, plus listing and counting operations.

The starter and worker connect to two different namespaces (a "caller" namespace and a "handler"
namespace) — this mirrors how Nexus is typically used to cross namespace boundaries. The client is
configured via the SDK's [environment configuration](https://docs.temporal.io/develop/environment-configuration)
support (`ClientConfig.load_client_connect_config()`), which reads `TEMPORAL_NAMESPACE`,
`TEMPORAL_ADDRESS`, etc. from the environment (and optionally profiles from `temporal.toml`).

### Temporal Python SDK support for Standalone Nexus Operations is at [Pre-release](https://docs.temporal.io/evaluate/development-production-features/release-stages#pre-release).

All APIs are experimental and may be subject to backwards-incompatible changes.

Standalone Nexus operations require a server version that supports this feature. Use the dev server build at https://github.com/temporalio/cli/releases/tag/v1.7.3-standalone-nexus-operations.
Standalone Nexus operations require a server version that supports this feature. Use the dev server build at https://github.com/temporalio/cli/releases/tag/v1.7.4-standalone-nexus-operations.

### Sample directory structure

Expand All @@ -16,38 +21,36 @@ Standalone Nexus operations require a server version that supports this feature.
- [worker.py](./worker.py) - Temporal worker that hosts the Nexus service
- [starter.py](./starter.py) - Client that executes standalone Nexus operations

## Run locally against a dev server

### Instructions
1. Start the [Temporal dev server build that supports standalone Nexus operations](https://docs.temporal.io/standalone-nexus-operation#temporal-cli-support) with the required namespaces pre-created:

Run the [Temporal dev server build that supports standalone Nexus operations](https://github.com/temporalio/cli/releases/tag/v1.7.3-standalone-nexus-operations).
(If you are going to run locally, you will want to start it in another terminal; this command is blocking and runs until it receives a SIGINT (Ctrl + C) command.)
```bash
./temporal server start-dev \
--namespace my-caller-namespace \
--namespace my-handler-namespace
```

Start a Temporal dev server with the dynamic config flags required for standalone Nexus operations:
2. Create a Nexus endpoint that routes to the handler namespace and the worker's task queue:

```bash
temporal server start-dev \
--dynamic-config-value "nexusoperation.enableStandalone=true" \
--dynamic-config-value "history.enableChasmCallbacks=true"
```
```bash
./temporal operator nexus endpoint create \
--name my-nexus-endpoint \
--target-namespace my-handler-namespace \
--target-task-queue nexus-handler-queue
```

Create the Nexus endpoint:
3. In a second terminal, start the worker in the handler namespace:

```
temporal operator nexus endpoint create \
--name nexus-standalone-operations-endpoint \
--target-namespace default \
--target-task-queue nexus-standalone-operations
```
```bash
TEMPORAL_NAMESPACE=my-handler-namespace uv run nexus_standalone_operations/worker.py
```

In one terminal, start the worker:
```
uv run nexus_standalone_operations/worker.py
```
4. In a third terminal, run the starter in the caller namespace:

In another terminal, run the starter:
```
uv run nexus_standalone_operations/starter.py
```
```bash
TEMPORAL_NAMESPACE=my-caller-namespace uv run nexus_standalone_operations/starter.py
```

### Expected output

Expand All @@ -66,4 +69,48 @@ Total Nexus operations: 2
```

If you run the starter code multiple times, you should see additional operations in the listing results, as more operations are run.
The same goes for the total number of operations.
The same goes for the total number of operations.

## Run against Temporal Cloud

1. Create two namespaces in Temporal Cloud (for example `my-caller-namespace.<account>` and
`my-handler-namespace.<account>`) and generate an API key (or mTLS cert) that can access both.

2. Create a Nexus endpoint that targets the handler namespace and the worker's task queue. See the
Temporal Cloud instructions at https://docs.temporal.io/nexus/registry#create-a-nexus-endpoint.
Use:
- Endpoint name: `my-nexus-endpoint`
- Target namespace: `my-handler-namespace.<account>`
- Target task queue: `nexus-handler-queue`
- Allowed caller namespaces: include `my-caller-namespace.<account>` (endpoints reject callers
that are not on this list)

3. Add two profiles to your [environment configuration file](https://docs.temporal.io/develop/environment-configuration),
one per namespace. Using API keys:

```toml
[profile.handler]
address = "<region>.<cloud>.api.temporal.io:7233"
namespace = "my-handler-namespace.<account>"
api_key = "<your-api-key>"

[profile.caller]
address = "<region>.<cloud>.api.temporal.io:7233"
namespace = "my-caller-namespace.<account>"
api_key = "<your-api-key>"
```

For mTLS instead of API keys, set `tls.client_cert_path` and `tls.client_key_path` on each profile
(see the [docs](https://docs.temporal.io/develop/environment-configuration) for the full schema).

4. Run the worker and starter in separate terminals, selecting the appropriate profile in each:

```bash
# terminal 1 (worker, handler namespace)
TEMPORAL_PROFILE=handler uv run nexus_standalone_operations/worker.py
```

```bash
# terminal 2 (starter, caller namespace)
TEMPORAL_PROFILE=caller uv run nexus_standalone_operations/starter.py
```
2 changes: 1 addition & 1 deletion nexus_standalone_operations/starter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
MyNexusService,
)

ENDPOINT_NAME = "nexus-standalone-operations-endpoint"
ENDPOINT_NAME = "my-nexus-endpoint"


async def main() -> None:
Expand Down
2 changes: 1 addition & 1 deletion nexus_standalone_operations/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

interrupt_event = asyncio.Event()

TASK_QUEUE = "nexus-standalone-operations"
TASK_QUEUE = "nexus-handler-queue"


async def main() -> None:
Expand Down
12 changes: 1 addition & 11 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,7 @@ async def env(request) -> AsyncGenerator[WorkflowEnvironment, None]:
env_type = request.config.getoption("--workflow-environment")
if env_type == "local":
env = await WorkflowEnvironment.start_local(
dev_server_download_version="v1.7.3-standalone-nexus-operations",
dev_server_extra_args=[
"--dynamic-config-value",
"frontend.enableExecuteMultiOperation=true",
"--dynamic-config-value",
"system.enableEagerWorkflowStart=true",
"--dynamic-config-value",
"nexusoperation.enableStandalone=true",
"--dynamic-config-value",
"history.enableChasmCallbacks=true",
],
dev_server_download_version="v1.7.4-standalone-nexus-operations",
)
Comment on lines 44 to 46

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep standalone Nexus feature gates enabled

Starting the local test server with only the download version drops the dynamic config values that enabled the pre-release standalone Nexus paths. The default poe test run uses this fixture for tests/nexus_standalone_operations/nexus_standalone_operations_test.py, which still executes and counts standalone Nexus operations; with nexusoperation.enableStandalone/CHASM callbacks not passed via the repeatable --dynamic-config-value start-dev flag, those RPCs are rejected by the v1.7.x standalone server. Please keep the SANO dev_server_extra_args when bumping to v1.7.4.

Useful? React with 👍 / 👎.

elif env_type == "time-skipping":
env = await WorkflowEnvironment.start_time_skipping()
Expand Down
Loading