Skip to content

Fix circular import: do not import temporalio.client in temporalio.nexus #973

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 1 commit into
base: main
Choose a base branch
from
Open
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
18 changes: 2 additions & 16 deletions temporalio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import temporalio.common
import temporalio.converter
import temporalio.exceptions
import temporalio.nexus
import temporalio.runtime
import temporalio.service
import temporalio.workflow
Expand Down Expand Up @@ -7319,23 +7320,8 @@ def api_key(self, value: Optional[str]) -> None:
self.service_client.update_api_key(value)


@dataclass(frozen=True)
class NexusCallback:
"""Nexus callback to attach to events such as workflow completion.

.. warning::
This API is experimental and unstable.
"""

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

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


# Intended to become a union of callback types
Callback = NexusCallback
Callback = temporalio.nexus.NexusCallback


async def _encode_user_metadata(
Expand Down
1 change: 1 addition & 0 deletions temporalio/nexus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ._decorators import workflow_run_operation as workflow_run_operation
from ._operation_context import Info as Info
from ._operation_context import LoggerAdapter as LoggerAdapter
from ._operation_context import NexusCallback as NexusCallback
from ._operation_context import (
WorkflowRunOperationContext as WorkflowRunOperationContext,
)
Expand Down
14 changes: 4 additions & 10 deletions temporalio/nexus/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,10 @@
StartOperationContext,
)

from temporalio.nexus._operation_context import (
WorkflowRunOperationContext,
)
from temporalio.nexus._operation_handlers import (
WorkflowRunOperationHandler,
)
from temporalio.nexus._token import (
WorkflowHandle,
)
from temporalio.nexus._util import (
from ._operation_context import WorkflowRunOperationContext
from ._operation_handlers import WorkflowRunOperationHandler
from ._token import WorkflowHandle
from ._util import (
get_callable_name,
get_workflow_run_start_method_input_and_output_type_annotations,
set_operation_factory,
Expand Down
5 changes: 4 additions & 1 deletion temporalio/nexus/_link_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import urllib.parse
from typing import (
TYPE_CHECKING,
Any,
Optional,
)
Expand All @@ -12,7 +13,9 @@

import temporalio.api.common.v1
import temporalio.api.enums.v1
import temporalio.client

if TYPE_CHECKING:
import temporalio.client

logger = logging.getLogger(__name__)

Expand Down
22 changes: 20 additions & 2 deletions temporalio/nexus/_operation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from dataclasses import dataclass
from datetime import timedelta
from typing import (
TYPE_CHECKING,
Any,
Callable,
Optional,
Expand All @@ -19,7 +20,6 @@

import temporalio.api.common.v1
import temporalio.api.workflowservice.v1
import temporalio.client
import temporalio.common
from temporalio.nexus import _link_conversion
from temporalio.nexus._token import WorkflowHandle
Expand All @@ -32,6 +32,9 @@
SelfType,
)

if TYPE_CHECKING:
import temporalio.client
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is client still needed in type checking if the callback has moved here? Is there another thing?


# The Temporal Nexus worker always builds a nexusrpc StartOperationContext or
# CancelOperationContext and passes it as the first parameter to the nexusrpc operation
# handler. In addition, it sets one of the following context vars.
Expand Down Expand Up @@ -122,7 +125,7 @@ def _get_callbacks(
ctx = self.nexus_context
return (
[
temporalio.client.NexusCallback(
NexusCallback(
url=ctx.callback_url,
headers=ctx.callback_headers,
)
Expand Down Expand Up @@ -449,6 +452,21 @@ async def start_workflow(
return WorkflowHandle[ReturnType]._unsafe_from_client_workflow_handle(wf_handle)


@dataclass(frozen=True)
class NexusCallback:
"""Nexus callback to attach to events such as workflow completion.

.. warning::
This API is experimental and unstable.
"""

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

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


@dataclass(frozen=True)
class _TemporalCancelOperationContext:
"""Context for a Nexus cancel operation being handled by a Temporal Nexus Worker."""
Expand Down
14 changes: 6 additions & 8 deletions temporalio/nexus/_operation_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
StartOperationResultAsync,
)

from temporalio import client
from temporalio.nexus._operation_context import (
_temporal_cancel_operation_context,
)
Expand Down Expand Up @@ -73,15 +72,14 @@ async def start(
"""Start the operation, by starting a workflow and completing asynchronously."""
handle = await self._start(ctx, input)
if not isinstance(handle, WorkflowHandle):
if isinstance(handle, client.WorkflowHandle):
raise RuntimeError(
f"Expected {handle} to be a nexus.WorkflowHandle, but got a client.WorkflowHandle. "
f"You must use WorkflowRunOperationContext.start_workflow "
"to start a workflow that will deliver the result of the Nexus operation, "
"not client.Client.start_workflow."
)
raise RuntimeError(
f"Expected {handle} to be a nexus.WorkflowHandle, but got {type(handle)}. "
f"When using @workflow_run_operation you must use "
"WorkflowRunOperationContext.start_workflow() "
"to start a workflow that will deliver the result of the Nexus operation, "
"and you must return the nexus.WorkflowHandle that it returns. "
"It is not possible to use client.Client.start_workflow() and client.WorkflowHandle "
"for this purpose."
)
return StartOperationResultAsync(handle.to_token())

Expand Down
15 changes: 9 additions & 6 deletions temporalio/nexus/_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
import base64
import json
from dataclasses import dataclass
from typing import Any, Generic, Literal, Optional, Type
from typing import TYPE_CHECKING, Any, Generic, Literal, Optional

from nexusrpc import OutputT

from temporalio import client

OperationTokenType = Literal[1]
OPERATION_TOKEN_TYPE_WORKFLOW: OperationTokenType = 1

if TYPE_CHECKING:
import temporalio.client


@dataclass(frozen=True)
class WorkflowHandle(Generic[OutputT]):
Expand All @@ -32,8 +33,10 @@ class WorkflowHandle(Generic[OutputT]):
version: Optional[int] = None

def _to_client_workflow_handle(
self, client: client.Client, result_type: Optional[Type[OutputT]] = None
) -> client.WorkflowHandle[Any, OutputT]:
self,
client: temporalio.client.Client,
result_type: Optional[type[OutputT]] = None,
) -> temporalio.client.WorkflowHandle[Any, OutputT]:
"""Create a :py:class:`temporalio.client.WorkflowHandle` from the token."""
if client.namespace != self.namespace:
raise ValueError(
Expand All @@ -46,7 +49,7 @@ def _to_client_workflow_handle(
# handle type.
@classmethod
def _unsafe_from_client_workflow_handle(
cls, workflow_handle: client.WorkflowHandle[Any, OutputT]
cls, workflow_handle: temporalio.client.WorkflowHandle[Any, OutputT]
) -> WorkflowHandle[OutputT]:
"""Create a :py:class:`WorkflowHandle` from a :py:class:`temporalio.client.WorkflowHandle`.

Expand Down
Loading