Skip to content

Nexus: worker, workflow-backed operations, and workflow caller #813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 85 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
edfa0e5
Nexus
dandavison Apr 19, 2025
768d93a
Nexus workflow caller
dandavison Jun 10, 2025
143b510
Nexus: squashed commit
dandavison Jun 12, 2025
cce402a
Option 1 for workflow_run_operation_handler
dandavison Jun 21, 2025
eb0a93e
Revert "Option 1 for workflow_run_operation_handler"
dandavison Jun 21, 2025
e47edfe
Adjust imports
dandavison Jun 21, 2025
585353e
Revert "Adjust imports"
dandavison Jun 21, 2025
d723c05
TODO
dandavison Jun 21, 2025
6da5ad5
Option 2 for workflow_run_operation_handler
dandavison Jun 21, 2025
c6c24ed
Failing test: first of two workflows incorrectly delivers result
dandavison Jun 22, 2025
d162d61
WIP: Option 3 for workflow_run_operation_handler
dandavison Jun 22, 2025
1b79dc4
TemporalNexusOperationContext should not be an ABC
dandavison Jun 22, 2025
eb86854
Remove unused output_type
dandavison Jun 22, 2025
7c1905b
Cleanup
dandavison Jun 22, 2025
5c8b4d4
Make WorkflowOperationToken generic, parameterized by output type
dandavison Jun 22, 2025
91a046a
Option 4 for WorkflowRunOperationHandler
dandavison Jun 22, 2025
9cdf219
Cleanup
dandavison Jun 22, 2025
a78b7f2
Refactor test
dandavison Jun 22, 2025
2b8b8de
Failing test: request ID is not used for non-backing workflow
dandavison Jun 22, 2025
eba4273
Bug fix: wire request_id through as top-level start_workflow param
dandavison Jun 22, 2025
9d72a97
Rename: TemporalOperationContext
dandavison Jun 22, 2025
f3fd617
Rename: cancel_operation
dandavison Jun 22, 2025
ef6971d
Cleanup
dandavison Jun 22, 2025
a59c44e
Do not allow Nexus operation to set client used for starting workflow
dandavison Jun 22, 2025
93c112b
Make task queue optional when starting workflows
dandavison Jun 22, 2025
b1f05dc
Add nexus_task_poller_behavior
dandavison Jun 22, 2025
e73271c
Handle PollShutdownError
dandavison Jun 22, 2025
5207abe
uv.lock
dandavison Jun 22, 2025
03c9cff
Respond to upstream: default to async
dandavison Jun 23, 2025
51c3786
Cleanup; changes from review comments
dandavison Jun 22, 2025
74640bc
Respond to upstream: handler factory instead of sync_operation_handler
dandavison Jun 23, 2025
25c8c19
Switch workflow_run_operation_handler to standard factory
dandavison Jun 23, 2025
b333023
Do not support passing client to cancel_operation
dandavison Jun 23, 2025
7c1ad67
RTU: bridge Rust
dandavison Jun 23, 2025
d45a7a9
Fix: make all methods `async def` on WorkflowRunOperationHandler
dandavison Jun 24, 2025
5016561
Get rid of TypeGuard
dandavison Jun 24, 2025
9021665
Support passing result_type when getting workflow handle from token
dandavison Jun 24, 2025
b49a60a
Implement fetch_result handler
dandavison Jun 24, 2025
d1fb476
Cleanup
dandavison Jun 24, 2025
9c41058
Tests: clean up type annotation warnings
dandavison Jun 24, 2025
f7cd556
Improve type annotation warnings
dandavison Jun 24, 2025
877248f
Cleanup
dandavison Jun 24, 2025
857684d
Import nexus.handler.logger in worker
dandavison Jun 24, 2025
380bf1c
Do not issue warnings when user is not using type annotations
dandavison Jun 24, 2025
300fc2e
Remove redundant validation
dandavison Jun 24, 2025
1d0425b
Respond to code review comments
dandavison Jun 24, 2025
ae82f0c
Don't swallow exceptions when encoding failures
dandavison Jun 24, 2025
6431f39
Catch BaseException at top-level in worker
dandavison Jun 24, 2025
9dfd799
Fail worker on broken executor
dandavison Jun 24, 2025
1c07502
Revert "Catch BaseException at top-level in worker"
dandavison Jun 24, 2025
3298358
Cleanup
dandavison Jun 24, 2025
dcdeb1a
Change context method name: .current() -> .get()
dandavison Jun 24, 2025
688e6c5
Rename: TemporalNexusOperationContext
dandavison Jun 24, 2025
ffc2369
Expose contextvar object directly
dandavison Jun 24, 2025
daf9ba8
Mark methods as private
dandavison Jun 24, 2025
96fe21e
Add run-time type check
dandavison Jun 24, 2025
fe988be
Make start_workflow a static function
dandavison Jun 24, 2025
e746462
Remove accidental exports
dandavison Jun 24, 2025
081645c
Docstrings
dandavison Jun 24, 2025
0000f4d
Comment, cleanup
dandavison Jun 24, 2025
3914524
TODO
dandavison Jun 24, 2025
cf86778
TODOs
dandavison Jun 24, 2025
886f5ed
Get rid of spurious type parameters
dandavison Jun 25, 2025
4403972
Add worker logging
dandavison Jun 25, 2025
3e49329
Type-level enforcement of the two ways to use WorkflowRunOperationHan…
dandavison Jun 25, 2025
d69a7cb
Respond to upstream: SyncOperation.from_callable
dandavison Jun 25, 2025
29101e7
-> WorkflowRunOperation.from_callable()
dandavison Jun 25, 2025
45a841f
TODO
dandavison Jun 25, 2025
1af01d3
Parameterize workflow_run_operation tests
dandavison Jun 25, 2025
1319732
Failing test case
dandavison Jun 25, 2025
320e1f1
Test: clean up imports
dandavison Jun 25, 2025
f8077c5
Respond to upstream: sync_operation_handler
dandavison Jun 25, 2025
f74784a
New workflow_run_operation_handler
dandavison Jun 25, 2025
8e0af0a
Delete reference to obsolete __nexus_service_metadata__
dandavison Jun 26, 2025
d9bef4e
TODO
dandavison Jun 26, 2025
6836d4b
Use get_callable_name utility
dandavison Jun 26, 2025
2a4b7cf
Fix test: 'not an async def` message changed
dandavison Jun 26, 2025
d45317e
Refactor
dandavison Jun 26, 2025
740a822
Reorganize: temporalio.nexus.handler -> temporalo.nexus
dandavison Jun 26, 2025
64e4b01
Fix signatures of start_method on workflow caller side
dandavison Jun 26, 2025
f0dba64
`from temporalio import nexus` everywhere
dandavison Jun 26, 2025
8c76a6f
Qualify client.WorkflowHandle in temporalio.nexus
dandavison Jun 26, 2025
97272ed
Fixup: no coverage for these
dandavison Jun 26, 2025
5ff174c
Rename: nexus.WorkflowHandle
dandavison Jun 26, 2025
8f4b524
nexus.WorkflowHandle.{to,from}_token()
dandavison Jun 26, 2025
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ informal introduction to the features and their implementation.
- [Heartbeating and Cancellation](#heartbeating-and-cancellation)
- [Worker Shutdown](#worker-shutdown)
- [Testing](#testing-1)
- [Nexus](#nexus)
Copy link
Member

Choose a reason for hiding this comment

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

I do not see this section

- [Workflow Replay](#workflow-replay)
- [Observability](#observability)
- [Metrics](#metrics)
Expand Down Expand Up @@ -1308,6 +1309,7 @@ affect calls activity code might make to functions on the `temporalio.activity`
* `cancel()` can be invoked to simulate a cancellation of the activity
* `worker_shutdown()` can be invoked to simulate a worker shutdown during execution of the activity


### Workflow Replay

Given a workflow's history, it can be replayed locally to check for things like non-determinism errors. For example,
Expand Down
13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ keywords = [
"workflow",
]
dependencies = [
"nexus-rpc",
"protobuf>=3.20,<6",
"python-dateutil>=2.8.2,<3 ; python_version < '3.11'",
"types-protobuf>=3.20",
Expand Down Expand Up @@ -41,7 +42,7 @@ dev = [
"psutil>=5.9.3,<6",
"pydocstyle>=6.3.0,<7",
"pydoctor>=24.11.1,<25",
"pyright==1.1.377",
"pyright==1.1.400",
"pytest~=7.4",
"pytest-asyncio>=0.21,<0.22",
"pytest-timeout~=2.2",
Expand All @@ -50,6 +51,8 @@ dev = [
"twine>=4.0.1,<5",
"ruff>=0.5.0,<0.6",
"maturin>=1.8.2",
"pytest-cov>=6.1.1",
"httpx>=0.28.1",
"pytest-pretty>=1.3.0",
]

Expand Down Expand Up @@ -159,6 +162,7 @@ exclude = [
"tests/worker/workflow_sandbox/testmodules/proto",
"temporalio/bridge/worker.py",
"temporalio/contrib/opentelemetry.py",
"temporalio/contrib/pydantic.py",
"temporalio/converter.py",
"temporalio/testing/_workflow.py",
"temporalio/worker/_activity.py",
Expand All @@ -170,6 +174,10 @@ exclude = [
"tests/api/test_grpc_stub.py",
"tests/conftest.py",
"tests/contrib/test_opentelemetry.py",
"tests/contrib/pydantic/models.py",
"tests/contrib/pydantic/models_2.py",
"tests/contrib/pydantic/test_pydantic.py",
"tests/contrib/pydantic/workflows.py",
"tests/test_converter.py",
"tests/test_service.py",
"tests/test_workflow.py",
Expand Down Expand Up @@ -204,3 +212,6 @@ exclude = [
[tool.uv]
# Prevent uv commands from building the package by default
package = false

[tool.uv.sources]
nexus-rpc = { path = "../nexus-sdk-python", editable = true }
Comment on lines +216 to +217
Copy link
Member

Choose a reason for hiding this comment

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

Let's make sure not to merge until this is proper dependency

32 changes: 31 additions & 1 deletion temporalio/bridge/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use temporal_sdk_core_api::worker::{
};
use temporal_sdk_core_api::Worker;
use temporal_sdk_core_protos::coresdk::workflow_completion::WorkflowActivationCompletion;
use temporal_sdk_core_protos::coresdk::{ActivityHeartbeat, ActivityTaskCompletion};
use temporal_sdk_core_protos::coresdk::{ActivityHeartbeat, ActivityTaskCompletion, nexus::NexusTaskCompletion};
use temporal_sdk_core_protos::temporal::api::history::v1::History;
use tokio::sync::mpsc::{channel, Sender};
use tokio_stream::wrappers::ReceiverStream;
Expand Down Expand Up @@ -60,6 +60,7 @@ pub struct WorkerConfig {
graceful_shutdown_period_millis: u64,
nondeterminism_as_workflow_fail: bool,
nondeterminism_as_workflow_fail_for_types: HashSet<String>,
nexus_task_poller_behavior: PollerBehavior,
}

#[derive(FromPyObject)]
Expand Down Expand Up @@ -569,6 +570,18 @@ impl WorkerRef {
})
}

fn poll_nexus_task<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
let worker = self.worker.as_ref().unwrap().clone();
self.runtime.future_into_py(py, async move {
let bytes = match worker.poll_nexus_task().await {
Ok(task) => task.encode_to_vec(),
Err(PollError::ShutDown) => return Err(PollShutdownError::new_err(())),
Err(err) => return Err(PyRuntimeError::new_err(format!("Poll failure: {}", err))),
};
Ok(bytes)
})
}

fn complete_workflow_activation<'p>(
&self,
py: Python<'p>,
Expand Down Expand Up @@ -603,6 +616,22 @@ impl WorkerRef {
})
}

fn complete_nexus_task<'p>(&self,
py: Python<'p>,
proto: &Bound<'_, PyBytes>,
) -> PyResult<Bound<'p, PyAny>> {
let worker = self.worker.as_ref().unwrap().clone();
let completion = NexusTaskCompletion::decode(proto.as_bytes())
.map_err(|err| PyValueError::new_err(format!("Invalid proto: {}", err)))?;
self.runtime.future_into_py(py, async move {
worker
.complete_nexus_task(completion)
.await
.context("Completion failure")
.map_err(Into::into)
})
}

fn record_activity_heartbeat(&self, proto: &Bound<'_, PyBytes>) -> PyResult<()> {
enter_sync!(self.runtime);
let heartbeat = ActivityHeartbeat::decode(proto.as_bytes())
Expand Down Expand Up @@ -700,6 +729,7 @@ fn convert_worker_config(
})
.collect::<HashMap<String, HashSet<WorkflowErrorType>>>(),
)
.nexus_task_poller_behavior(conf.nexus_task_poller_behavior)
.build()
.map_err(|err| PyValueError::new_err(format!("Invalid worker config: {}", err)))
}
Expand Down
18 changes: 17 additions & 1 deletion temporalio/bridge/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import temporalio.bridge.client
import temporalio.bridge.proto
import temporalio.bridge.proto.activity_task
import temporalio.bridge.proto.nexus
import temporalio.bridge.proto.workflow_activation
import temporalio.bridge.proto.workflow_completion
import temporalio.bridge.runtime
Expand All @@ -35,7 +36,7 @@
from temporalio.bridge.temporal_sdk_bridge import (
CustomSlotSupplier as BridgeCustomSlotSupplier,
)
from temporalio.bridge.temporal_sdk_bridge import PollShutdownError
from temporalio.bridge.temporal_sdk_bridge import PollShutdownError # type: ignore


@dataclass
Expand All @@ -60,6 +61,7 @@ class WorkerConfig:
graceful_shutdown_period_millis: int
nondeterminism_as_workflow_fail: bool
nondeterminism_as_workflow_fail_for_types: Set[str]
nexus_task_poller_behavior: PollerBehavior


@dataclass
Expand Down Expand Up @@ -216,6 +218,14 @@ async def poll_activity_task(
await self._ref.poll_activity_task()
)

async def poll_nexus_task(
self,
) -> temporalio.bridge.proto.nexus.NexusTask:
"""Poll for a nexus task."""
return temporalio.bridge.proto.nexus.NexusTask.FromString(
await self._ref.poll_nexus_task()
)

async def complete_workflow_activation(
self,
comp: temporalio.bridge.proto.workflow_completion.WorkflowActivationCompletion,
Expand All @@ -229,6 +239,12 @@ async def complete_activity_task(
"""Complete an activity task."""
await self._ref.complete_activity_task(comp.SerializeToString())

async def complete_nexus_task(
self, comp: temporalio.bridge.proto.nexus.NexusTaskCompletion
) -> None:
"""Complete a nexus task."""
await self._ref.complete_nexus_task(comp.SerializeToString())

def record_activity_heartbeat(
self, comp: temporalio.bridge.proto.ActivityHeartbeat
) -> None:
Expand Down
46 changes: 43 additions & 3 deletions temporalio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,15 @@ async def start_workflow(
rpc_metadata: Mapping[str, str] = {},
rpc_timeout: Optional[timedelta] = None,
request_eager_start: bool = False,
stack_level: int = 2,
priority: temporalio.common.Priority = temporalio.common.Priority.default,
versioning_override: Optional[temporalio.common.VersioningOverride] = None,
# The following options are deliberately not exposed in overloads
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# The following options are deliberately not exposed in overloads
# The following options are deliberately not exposed in overloads and are not subject to compatibility expectations

That way we can change these up as we need

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Taken

Copy link
Member

Choose a reason for hiding this comment

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

Unresolved (but pedantic, don't have to take if not wanted, I am just afraid of users using them)

nexus_completion_callbacks: Sequence[NexusCompletionCallback] = [],
workflow_event_links: Sequence[
temporalio.api.common.v1.Link.WorkflowEvent
] = [],
request_id: Optional[str] = None,
stack_level: int = 2,
) -> WorkflowHandle[Any, Any]:
"""Start a workflow and return its handle.

Expand Down Expand Up @@ -529,7 +535,6 @@ async def start_workflow(
name, result_type_from_type_hint = (
temporalio.workflow._Definition.get_name_and_result_type(workflow)
)

return await self._impl.start_workflow(
StartWorkflowInput(
workflow=name,
Expand Down Expand Up @@ -557,6 +562,9 @@ async def start_workflow(
rpc_timeout=rpc_timeout,
request_eager_start=request_eager_start,
priority=priority,
nexus_completion_callbacks=nexus_completion_callbacks,
workflow_event_links=workflow_event_links,
request_id=request_id,
)
)

Expand Down Expand Up @@ -5193,6 +5201,9 @@ class StartWorkflowInput:
rpc_timeout: Optional[timedelta]
request_eager_start: bool
priority: temporalio.common.Priority
nexus_completion_callbacks: Sequence[NexusCompletionCallback]
workflow_event_links: Sequence[temporalio.api.common.v1.Link.WorkflowEvent]
request_id: Optional[str]
Comment on lines +5204 to +5206
Copy link
Member

Choose a reason for hiding this comment

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

May want to mention here these are unstable/experimental

versioning_override: Optional[temporalio.common.VersioningOverride] = None


Expand Down Expand Up @@ -5807,8 +5818,26 @@ async def _build_start_workflow_execution_request(
self, input: StartWorkflowInput
) -> temporalio.api.workflowservice.v1.StartWorkflowExecutionRequest:
req = temporalio.api.workflowservice.v1.StartWorkflowExecutionRequest()
req.request_eager_execution = input.request_eager_start
await self._populate_start_workflow_execution_request(req, input)
# _populate_start_workflow_execution_request is used for both StartWorkflowInput
# and UpdateWithStartStartWorkflowInput. UpdateWithStartStartWorkflowInput does
# not have the following two fields so they are handled here.
req.request_eager_execution = input.request_eager_start
if input.request_id:
req.request_id = input.request_id

req.completion_callbacks.extend(
temporalio.api.common.v1.Callback(
nexus=temporalio.api.common.v1.Callback.Nexus(
url=callback.url, header=callback.header
)
)
for callback in input.nexus_completion_callbacks
)
req.links.extend(
temporalio.api.common.v1.Link(workflow_event=link)
for link in input.workflow_event_links
)
return req

async def _build_signal_with_start_workflow_execution_request(
Expand Down Expand Up @@ -7231,6 +7260,17 @@ def api_key(self, value: Optional[str]) -> None:
self.service_client.update_api_key(value)


@dataclass(frozen=True)
class NexusCompletionCallback:
Copy link
Member

Choose a reason for hiding this comment

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

May want to mention this is unstable/experimental and also not really for user use (I understand exposing because it's exposed in the interceptor)

"""Nexus callback to attach to events such as workflow completion."""

url: str
"""Callback URL."""

header: Mapping[str, str]
"""Header to attach to callback request."""


async def _encode_user_metadata(
converter: temporalio.converter.DataConverter,
summary: Optional[Union[str, temporalio.api.common.v1.Payload]],
Expand Down
2 changes: 1 addition & 1 deletion temporalio/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum, IntEnum
from enum import IntEnum
from typing import (
Any,
Callable,
Expand Down
26 changes: 26 additions & 0 deletions temporalio/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,12 @@ def _error_to_failure(
failure.child_workflow_execution_failure_info.retry_state = (
temporalio.api.enums.v1.RetryState.ValueType(error.retry_state or 0)
)
# TODO(nexus-prerelease): test coverage for this
elif isinstance(error, temporalio.exceptions.NexusOperationError):
Copy link
Member

Choose a reason for hiding this comment

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

For symmetry reasons, I suspect we also need to convert NexusHandlerError (in theory a failure conversion from a failure should be able to convert back to a failure)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to add test coverage per the comment, and for what you're saying. Maybe this is something to resolve when we merge the workflow caller.

failure.nexus_operation_execution_failure_info.SetInParent()
failure.nexus_operation_execution_failure_info.operation_token = (
error.operation_token
)

def from_failure(
self,
Expand Down Expand Up @@ -1006,6 +1012,26 @@ def from_failure(
if child_info.retry_state
else None,
)
elif failure.HasField("nexus_handler_failure_info"):
nexus_handler_failure_info = failure.nexus_handler_failure_info
err = temporalio.exceptions.NexusHandlerError(
failure.message or "Nexus handler error",
type=nexus_handler_failure_info.type,
retryable={
temporalio.api.enums.v1.NexusHandlerErrorRetryBehavior.NEXUS_HANDLER_ERROR_RETRY_BEHAVIOR_RETRYABLE: True,
temporalio.api.enums.v1.NexusHandlerErrorRetryBehavior.NEXUS_HANDLER_ERROR_RETRY_BEHAVIOR_NON_RETRYABLE: False,
}.get(nexus_handler_failure_info.retry_behavior),
)
elif failure.HasField("nexus_operation_execution_failure_info"):
nexus_op_failure_info = failure.nexus_operation_execution_failure_info
err = temporalio.exceptions.NexusOperationError(
failure.message or "Nexus operation error",
scheduled_event_id=nexus_op_failure_info.scheduled_event_id,
endpoint=nexus_op_failure_info.endpoint,
service=nexus_op_failure_info.service,
operation=nexus_op_failure_info.operation,
operation_token=nexus_op_failure_info.operation_token,
)
else:
err = temporalio.exceptions.FailureError(failure.message or "Failure error")
err._failure = failure
Expand Down
63 changes: 63 additions & 0 deletions temporalio/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,69 @@ def retry_state(self) -> Optional[RetryState]:
return self._retry_state


class NexusHandlerError(FailureError):
"""Error raised on Nexus handler failure."""

def __init__(
self,
message: str,
*,
type: str,
retryable: Optional[bool] = None,
):
"""Initialize a Nexus handler error."""
super().__init__(message)
self._type = type
self._retryable = retryable
Comment on lines +377 to +378
Copy link
Member

Choose a reason for hiding this comment

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

Users of this exception should be allowed to see these properties/attributes IMO, even if we don't think they ever will

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's resolve this when we review the workflow caller.

[In fact, let's consider using the same PR for that. Just for now, let's review handler/worker-relevant stuff only]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should the type be a str rather than the enum from nexusrpc?

Copy link
Member

Choose a reason for hiding this comment

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

IMO no it should not be an enum if it was chosen to not be in the API



class NexusOperationError(FailureError):
"""Error raised on Nexus operation failure."""

def __init__(
self,
message: str,
*,
scheduled_event_id: int,
endpoint: str,
service: str,
operation: str,
operation_token: str,
):
"""Initialize a Nexus operation error."""
super().__init__(message)
self._scheduled_event_id = scheduled_event_id
self._endpoint = endpoint
self._service = service
self._operation = operation
self._operation_token = operation_token

@property
def scheduled_event_id(self) -> int:
"""The NexusOperationScheduled event ID for the failed operation."""
return self._scheduled_event_id

@property
def endpoint(self) -> str:
"""The endpoint name for the failed operation."""
return self._endpoint

@property
def service(self) -> str:
"""The service name for the failed operation."""
return self._service

@property
def operation(self) -> str:
"""The name of the failed operation."""
return self._operation

@property
def operation_token(self) -> str:
"""The operation token returned by the failed operation."""
return self._operation_token


def is_cancelled_exception(exception: BaseException) -> bool:
"""Check whether the given exception is considered a cancellation exception
according to Temporal.
Expand Down
Loading