Skip to content

Implement Standalone Nexus Operations#1461

Draft
VegetarianOrc wants to merge 5 commits intomainfrom
amazzeo/sano
Draft

Implement Standalone Nexus Operations#1461
VegetarianOrc wants to merge 5 commits intomainfrom
amazzeo/sano

Conversation

@VegetarianOrc
Copy link
Copy Markdown
Contributor

@VegetarianOrc VegetarianOrc commented Apr 17, 2026

NOTE FOR REVIEWERS: This PR is pending the actual release of the API and Server and will not be merged until those are complete.

I recommend viewing this diff with the histogram diff algorithm to reduce noise and see the additions more clearly. You can view that by checking out the branch and running git diff --histogram origin/main

Add Standalone Nexus Operation Support

API Changes

  • Client.create_nexus_client() - Create a typed NexusClient from an endpoint & nexusrpc.Service
  • Client.list_nexus_operations() - List operations via visibility query
  • Client.count_nexus_operations() - Count operations via visibility query
  • Client.get_nexus_operation_handle() - Create a typed NexusOperationHandle to an existing operation
  • NexusClient.start_operation() - Start a Nexus Operation and return a NexusOperationHandle.
  • NexusClient.execute_operation() - Same as start_operation, but poll the handle for the final result.
  • NexusOperationHandle.result() / .describe() / .cancel() / .terminate() / property accessors - interact with an existing operation.
  • Supporting types like input types, enums, and errors added as appropriate.

Interceptor Support

The following methods have been added to OutboundInterceptor

  • start_nexus_operation()
  • describe_nexus_operation()
  • cancel_nexus_operation()
  • terminate_nexus_operation()
  • list_nexus_operations()
  • count_nexus_operations()

Testing

Added integration test suite tests/nexus/test_standalone_operations.py (858 lines) covering start, get result, describe, cancel, terminate, list, count, ID conflict/reuse policies, and interceptor integration

Misc Changes

  • Update ruff target version to py310 to match the minimum support Python version.

Copy link
Copy Markdown

@Evanthx Evanthx left a comment

Choose a reason for hiding this comment

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

Looks pretty good overall!

"""
@property
def provider(self) -> temporalio.api.compute.v1.provider_pb2.ComputeProvider:
"""Stores instructions for a worker control plane controller how to respond
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not quite grammatically correct

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a generated file. We'll need to file a PR in the api repo to address this.

Comment thread temporalio/client.py Outdated
)

# Convert outcome to error or value
match self._known_outcome:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there a guarantee that one of these two cases will match? What will ReturnType be if it is an unexpected outcome?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've changed this in the new setup with get_nexus_operation_result being on the OutboundInterceptor now, but to answer your question, the linter/type checkers ensure that a match statement is exhaustive. In this case self._known_outcome isn't affected by function input or anything so a mismatch runtime type compared to the exhaustive type checking is unlikely.

Comment thread temporalio/client.py
The result of the operation.

Raises:
NexusOperationFailureError: If the operation completed with a failure.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude tells me this exception is never used anywhere, but this comment says it can be raised?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a good catch! I was experimenting with what should be raised here when things like a cancellation or termination occur. But based on how the rest of the SDK functions I've updated it to properly raise this error as documented.

…n defaults for id_reuse_policy and id_conflict_policy. Update integration tests with better typing and new assertions for the newly interceptable get_nexus_operation_result
Comment thread temporalio/client.py
rpc_metadata=rpc_metadata,
rpc_timeout=rpc_timeout,
)
return await handle.result()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are type-level tests for workflow nexus operations in tests/nexus/test_type_errors.py. Since the overloads in the Python SDK are fairly involved, could consider extending those to cover SANO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great callout. I've added type tests that cover a variety of scenarios.

…ch start/execute operation. Fix hardcoded retryable=True in fallback error serialization. Add type tests and rename test/nexus/test_type_errors.py to ensure that the type test file properly executes nexus tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants