-
Notifications
You must be signed in to change notification settings - Fork 103
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
base: main
Are you sure you want to change the base?
Changes from all commits
27a7c6a
afe910b
0d7f148
e76c1c3
68388d4
5586e86
ca4ef14
b66ab54
ef4d558
d126fa9
03663a2
fdd24b7
1086fff
8f5c2a0
60237ca
f51fb79
042e25d
edb08d2
fbb79af
b462369
1a6878b
90612fc
f3cc7a3
ce6c92d
f887ee3
f4f04c2
b42f40b
1fe9614
5149f9f
af08872
5b9ca3b
2043d9f
2db1ddf
d9a0a9d
3226439
8e25cda
ebeca69
31b52f5
c69cc27
de1822b
7241466
fc9959c
d51eacc
d029d55
b40e085
25e02d2
080ef38
9be016f
1448ddc
857f106
b03ab20
3ab9cea
92afcb2
c79c53e
9780c46
68e2bde
35a4864
12a4acf
43c000f
e8f5c91
7355cf2
a10eb4d
923ffd0
ea0acc7
2ffef81
e8d18e0
7c19d97
61912ff
32a06c1
f933e6f
873f62b
6f6e53b
fea7c3c
97ac04c
e779252
e87aa31
5a31fba
f1aca05
cac301c
004c6df
2f76778
f972f99
cfb2bdc
c27536b
2d02295
c90a9af
f113556
acbba79
5f41bfc
58f75c1
c69be1f
99a5f55
9327bee
bc6230c
f0b664f
160cd9a
7540596
8889838
5b821a2
208989d
38fd17a
47ceb4a
59b47cb
35c32ab
2e2cb90
c539c57
fd10067
d0c1eca
7422438
63a72e7
58f6977
16b540c
e5c774c
a6bf7cb
5480eb4
122442d
5bb7e42
d4812c3
34b3bcb
696ebb4
8d190ac
bdc40d9
993bfd3
8b3af4d
3b92183
cebb622
da0eb60
a547276
8b153e1
8818432
42094c4
38e2056
1bcbb79
e54f98c
036a02e
9824a23
4b49b45
d69d22b
5df2903
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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", | ||
|
@@ -44,7 +45,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", | ||
|
@@ -53,6 +54,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", | ||
] | ||
|
||
|
@@ -162,6 +165,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", | ||
|
@@ -173,6 +177,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", | ||
|
@@ -208,3 +216,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
+220
to
+221
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make sure not to merge until this is proper dependency |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -464,9 +464,17 @@ 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 should not be considered part of the public API. They | ||
# are deliberately not exposed in overloads, and are not subject to any | ||
# backwards compatibility guarantees. | ||
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. | ||
|
||
|
@@ -529,7 +537,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, | ||
|
@@ -557,6 +564,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, | ||
) | ||
) | ||
|
||
|
@@ -5193,6 +5203,10 @@ class StartWorkflowInput: | |
rpc_timeout: Optional[timedelta] | ||
request_eager_start: bool | ||
priority: temporalio.common.Priority | ||
# The following options are experimental and unstable. | ||
nexus_completion_callbacks: Sequence[NexusCompletionCallback] | ||
workflow_event_links: Sequence[temporalio.api.common.v1.Link.WorkflowEvent] | ||
request_id: Optional[str] | ||
Comment on lines
+5207
to
+5209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May want to mention here these are unstable/experimental There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Done |
||
versioning_override: Optional[temporalio.common.VersioningOverride] = None | ||
|
||
|
||
|
@@ -5807,8 +5821,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( | ||
|
@@ -7231,6 +7263,21 @@ def api_key(self, value: Optional[str]) -> None: | |
self.service_client.update_api_key(value) | ||
|
||
|
||
@dataclass(frozen=True) | ||
class NexusCompletionCallback: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done @dataclass(frozen=True)
class NexusCompletionCallback:
"""Nexus callback to attach to events such as workflow completion.
.. warning::
This option is experimental and unstable.
""" |
||
"""Nexus callback to attach to events such as workflow completion. | ||
|
||
.. warning:: | ||
This option is experimental and unstable. | ||
""" | ||
|
||
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]], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
from datetime import datetime | ||
from enum import IntEnum | ||
from itertools import zip_longest | ||
from logging import getLogger | ||
from typing import ( | ||
Any, | ||
Awaitable, | ||
|
@@ -40,6 +41,7 @@ | |
import google.protobuf.json_format | ||
import google.protobuf.message | ||
import google.protobuf.symbol_database | ||
import nexusrpc | ||
import typing_extensions | ||
|
||
import temporalio.api.common.v1 | ||
|
@@ -60,6 +62,8 @@ | |
if sys.version_info >= (3, 10): | ||
from types import UnionType | ||
|
||
logger = getLogger(__name__) | ||
|
||
|
||
class PayloadConverter(ABC): | ||
"""Base payload converter to/from multiple payloads/values.""" | ||
|
@@ -911,6 +915,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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For symmetry reasons, I suspect we also need to convert There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
@@ -1006,6 +1016,33 @@ 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 | ||
try: | ||
_type = nexusrpc.HandlerErrorType[nexus_handler_failure_info.type] | ||
except KeyError: | ||
logger.warning( | ||
f"Unknown Nexus HandlerErrorType: {nexus_handler_failure_info.type}" | ||
) | ||
_type = nexusrpc.HandlerErrorType.INTERNAL | ||
return nexusrpc.HandlerError( | ||
failure.message or "Nexus handler error", | ||
type=_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 | ||
|
There was a problem hiding this comment.
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