|
23 | 23 | overload, |
24 | 24 | ) |
25 | 25 |
|
| 26 | +import nexusrpc |
26 | 27 | from nexusrpc.handler import ( |
27 | 28 | CancelOperationContext, |
28 | 29 | OperationContext, |
|
44 | 45 |
|
45 | 46 | from ._link_conversion import ( |
46 | 47 | nexus_link_to_temporal_link, |
| 48 | + temporal_link_to_nexus_link, |
47 | 49 | workflow_event_to_nexus_link, |
48 | 50 | workflow_execution_started_event_link_from_workflow_handle, |
49 | 51 | ) |
@@ -167,6 +169,11 @@ def _try_temporal_context() -> ( |
167 | 169 | return start_ctx or cancel_ctx |
168 | 170 |
|
169 | 171 |
|
| 172 | +def _try_start_operation_context() -> _TemporalStartOperationContext | None: # pyright: ignore[reportUnusedFunction] |
| 173 | + """The Nexus start-operation context if a handler is currently running, else None.""" |
| 174 | + return _temporal_start_operation_context.get(None) |
| 175 | + |
| 176 | + |
170 | 177 | @contextmanager |
171 | 178 | def _nexus_backing_workflow_start_context() -> Generator[None]: |
172 | 179 | token = _temporal_nexus_backing_workflow_start_context.set(True) |
@@ -239,44 +246,73 @@ def _get_callbacks(self, token: str) -> list[temporalio.client.Callback]: |
239 | 246 | else [] |
240 | 247 | ) |
241 | 248 |
|
242 | | - def _get_links( |
243 | | - self, |
244 | | - ) -> list[temporalio.api.common.v1.Link]: |
| 249 | + def _get_request_links(self) -> list[temporalio.api.common.v1.Link]: |
| 250 | + """Request links to attach to RPCs the operation handler issues. |
| 251 | +
|
| 252 | + These are the inbound Nexus task links. When the operation handler signals, |
| 253 | + signal-with-starts, or starts a workflow, these links are added to the request's |
| 254 | + ``links`` field so the callee's history event links back to whatever scheduled this |
| 255 | + Nexus operation. |
| 256 | + """ |
245 | 257 | event_links: list[temporalio.api.common.v1.Link] = [] |
246 | 258 | for inbound_link in self.nexus_context.inbound_links: |
247 | 259 | if link := nexus_link_to_temporal_link(inbound_link): |
248 | 260 | event_links.append(link) |
249 | 261 | return event_links |
250 | 262 |
|
251 | | - def _add_outbound_links( |
| 263 | + def _add_start_workflow_response_link( |
252 | 264 | self, workflow_handle: temporalio.client.WorkflowHandle[Any, Any] |
253 | 265 | ): |
254 | | - # If links were not sent in StartWorkflowExecutionResponse then construct them. |
255 | | - wf_event_links: list[temporalio.api.common.v1.Link.WorkflowEvent] = [] |
256 | | - try: |
257 | | - if isinstance( |
258 | | - workflow_handle._start_workflow_response, |
259 | | - temporalio.api.workflowservice.v1.StartWorkflowExecutionResponse, |
260 | | - ): |
261 | | - if workflow_handle._start_workflow_response.HasField("link"): |
262 | | - if link := workflow_handle._start_workflow_response.link: |
263 | | - if link.HasField("workflow_event"): |
264 | | - wf_event_links.append(link.workflow_event) |
265 | | - if not wf_event_links: |
266 | | - wf_event_links = [ |
267 | | - workflow_execution_started_event_link_from_workflow_handle( |
| 266 | + response = workflow_handle._start_workflow_response |
| 267 | + |
| 268 | + nexus_link: nexusrpc.Link | None = None |
| 269 | + if isinstance( |
| 270 | + response, temporalio.api.workflowservice.v1.StartWorkflowExecutionResponse |
| 271 | + ): |
| 272 | + if response.HasField("link"): |
| 273 | + nexus_link = temporal_link_to_nexus_link(response.link) |
| 274 | + else: |
| 275 | + # If a link was not sent in response then construct it. |
| 276 | + link = temporalio.api.common.v1.Link( |
| 277 | + workflow_event=workflow_execution_started_event_link_from_workflow_handle( |
268 | 278 | workflow_handle, |
269 | 279 | self.nexus_context.request_id, |
270 | 280 | ) |
271 | | - ] |
272 | | - self.nexus_context.outbound_links.extend( |
273 | | - workflow_event_to_nexus_link(link) for link in wf_event_links |
274 | | - ) |
| 281 | + ) |
| 282 | + nexus_link = temporal_link_to_nexus_link(link) |
| 283 | + |
| 284 | + elif isinstance( |
| 285 | + response, |
| 286 | + temporalio.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse, |
| 287 | + ): |
| 288 | + # Server >= 1.31 with EnableCHASMSignalBacklinks returns signal_link pointing at |
| 289 | + # the WorkflowExecutionSignaled event; older servers leave it unset. |
| 290 | + if response.HasField("signal_link"): |
| 291 | + nexus_link = temporal_link_to_nexus_link(response.signal_link) |
| 292 | + |
| 293 | + try: |
| 294 | + if nexus_link is not None: |
| 295 | + self.nexus_context.outbound_links.append(nexus_link) |
275 | 296 | except Exception as e: |
276 | 297 | logger.warning( |
277 | | - f"Failed to create WorkflowExecutionStarted event links for workflow {workflow_handle}: {e}" |
| 298 | + f"Failed to create event links for workflow {workflow_handle}: {e}" |
278 | 299 | ) |
279 | | - return workflow_handle |
| 300 | + |
| 301 | + def _add_response_link(self, link: temporalio.api.common.v1.Link | None) -> None: |
| 302 | + """Append a response link returned by an RPC the operation handler issued. |
| 303 | +
|
| 304 | + ``link`` is the ``common.v1.Link`` returned on a signal, signal-with-start, or start |
| 305 | + response (or ``None`` against a server that did not return one). When present and of the |
| 306 | + ``workflow_event`` variant, it is converted to a Nexus link and added to the operation's |
| 307 | + outbound links so the caller workflow's Nexus history event links to the callee event. |
| 308 | +
|
| 309 | + This is only safe to call from the single thread/task that runs the operation handler. |
| 310 | + """ |
| 311 | + if link is None or not link.HasField("workflow_event"): |
| 312 | + return |
| 313 | + self.nexus_context.outbound_links.append( |
| 314 | + workflow_event_to_nexus_link(link.workflow_event) |
| 315 | + ) |
280 | 316 |
|
281 | 317 |
|
282 | 318 | class WorkflowRunOperationContext(StartOperationContext): |
@@ -674,10 +710,8 @@ async def _start_nexus_backing_workflow( |
674 | 710 | priority=priority, |
675 | 711 | versioning_override=versioning_override, |
676 | 712 | callbacks=temporal_context._get_callbacks(token), |
677 | | - links=temporal_context._get_links(), |
| 713 | + links=temporal_context._get_request_links(), |
678 | 714 | request_id=temporal_context.nexus_context.request_id, |
679 | 715 | ) |
680 | 716 |
|
681 | | - temporal_context._add_outbound_links(wf_handle) |
682 | | - |
683 | 717 | return WorkflowHandle[ReturnType]._unsafe_from_client_workflow_handle(wf_handle) |
0 commit comments