|
43 | 43 | ) |
44 | 44 |
|
45 | 45 | from ._link_conversion import ( |
| 46 | + activity_link_to_nexus_link, |
46 | 47 | nexus_link_to_temporal_link, |
47 | 48 | workflow_event_to_nexus_link, |
48 | 49 | workflow_execution_started_event_link_from_workflow_handle, |
|
64 | 65 | ContextVar("temporal-cancel-operation-context") |
65 | 66 | ) |
66 | 67 |
|
67 | | -# A Nexus start handler might start zero or more workflows as usual using a Temporal client. In |
68 | | -# addition, it may start one "nexus-backing" workflow, using |
69 | | -# WorkflowRunOperationContext.start_workflow. This context is active while the latter is being done. |
| 68 | +# A Nexus start handler might start zero or async Temporal actions as usual using a Temporal client. In |
| 69 | +# addition, it may start one "nexus-backing" async Temporal action, using |
| 70 | +# WorkflowRunOperationContext.start_workflow or methods from TemporalNexusClient. This context is active while the latter is being done. |
70 | 71 | # It is thus a narrower context than _temporal_start_operation_context. |
71 | | -_temporal_nexus_backing_workflow_start_context: ContextVar[bool] = ContextVar( |
72 | | - "temporal-nexus-backing-workflow-start-context" |
| 72 | +_temporal_nexus_backing_start_context: ContextVar[bool] = ContextVar( |
| 73 | + "temporal-nexus-backing-start-context" |
73 | 74 | ) |
74 | 75 |
|
75 | 76 |
|
@@ -168,16 +169,16 @@ def _try_temporal_context() -> ( |
168 | 169 |
|
169 | 170 |
|
170 | 171 | @contextmanager |
171 | | -def _nexus_backing_workflow_start_context() -> Generator[None]: |
172 | | - token = _temporal_nexus_backing_workflow_start_context.set(True) |
| 172 | +def _nexus_backing_start_context() -> Generator[None]: |
| 173 | + token = _temporal_nexus_backing_start_context.set(True) |
173 | 174 | try: |
174 | 175 | yield |
175 | 176 | finally: |
176 | | - _temporal_nexus_backing_workflow_start_context.reset(token) |
| 177 | + _temporal_nexus_backing_start_context.reset(token) |
177 | 178 |
|
178 | 179 |
|
179 | | -def _in_nexus_backing_workflow_start_context() -> bool: # type:ignore[reportUnusedClass] |
180 | | - return _temporal_nexus_backing_workflow_start_context.get(False) |
| 180 | +def _in_nexus_backing_start_context() -> bool: # type:ignore[reportUnusedClass] |
| 181 | + return _temporal_nexus_backing_start_context.get(False) |
181 | 182 |
|
182 | 183 |
|
183 | 184 | _OperationCtxT = TypeVar("_OperationCtxT", bound=OperationContext) |
@@ -243,13 +244,13 @@ def _get_callbacks( |
243 | 244 | def _get_links( |
244 | 245 | self, |
245 | 246 | ) -> list[temporalio.api.common.v1.Link]: |
246 | | - event_links: list[temporalio.api.common.v1.Link] = [] |
| 247 | + links: list[temporalio.api.common.v1.Link] = [] |
247 | 248 | for inbound_link in self.nexus_context.inbound_links: |
248 | 249 | if link := nexus_link_to_temporal_link(inbound_link): |
249 | | - event_links.append(link) |
250 | | - return event_links |
| 250 | + links.append(link) |
| 251 | + return links |
251 | 252 |
|
252 | | - def _add_outbound_links( |
| 253 | + def _add_outbound_workflow_links( |
253 | 254 | self, workflow_handle: temporalio.client.WorkflowHandle[Any, Any] |
254 | 255 | ): |
255 | 256 | # If links were not sent in StartWorkflowExecutionResponse then construct them. |
@@ -279,6 +280,45 @@ def _add_outbound_links( |
279 | 280 | ) |
280 | 281 | return workflow_handle |
281 | 282 |
|
| 283 | + def _add_outbound_activity_links( |
| 284 | + self, activity_handle: temporalio.client.ActivityHandle[Any] |
| 285 | + ): |
| 286 | + activity_links: list[temporalio.api.common.v1.Link.Activity] = [] |
| 287 | + try: |
| 288 | + if isinstance( |
| 289 | + activity_handle._start_activity_response, |
| 290 | + temporalio.api.workflowservice.v1.StartActivityExecutionResponse, |
| 291 | + ): |
| 292 | + if activity_handle._start_activity_response.HasField("link"): |
| 293 | + if activity_handle._start_activity_response.link.HasField( |
| 294 | + "activity" |
| 295 | + ): |
| 296 | + activity_links.append( |
| 297 | + activity_handle._start_activity_response.link.activity |
| 298 | + ) |
| 299 | + if not activity_links: |
| 300 | + activity_run_id = activity_handle.run_id |
| 301 | + if activity_run_id is None: |
| 302 | + raise ValueError( |
| 303 | + f"Activity handle {activity_handle} has no run ID. " |
| 304 | + "Cannot create Activity link." |
| 305 | + ) |
| 306 | + activity_links.append( |
| 307 | + temporalio.api.common.v1.Link.Activity( |
| 308 | + namespace=activity_handle._client.namespace, |
| 309 | + activity_id=activity_handle.id, |
| 310 | + run_id=activity_run_id, |
| 311 | + ) |
| 312 | + ) |
| 313 | + self.nexus_context.outbound_links.extend( |
| 314 | + activity_link_to_nexus_link(link) for link in activity_links |
| 315 | + ) |
| 316 | + except Exception as e: |
| 317 | + logger.warning( |
| 318 | + f"Failed to create Activity link for activity {activity_handle}: {e}" |
| 319 | + ) |
| 320 | + return activity_handle |
| 321 | + |
282 | 322 |
|
283 | 323 | class WorkflowRunOperationContext(StartOperationContext): |
284 | 324 | """Context received by a workflow run operation.""" |
@@ -642,7 +682,7 @@ async def _start_nexus_backing_workflow( |
642 | 682 | # namespace to deliver the result to the caller namespace when the workflow reaches a |
643 | 683 | # terminal state) and inbound links to the caller workflow (attached to history events of |
644 | 684 | # the workflow started in the handler namespace, and displayed in the UI). |
645 | | - with _nexus_backing_workflow_start_context(): |
| 685 | + with _nexus_backing_start_context(): |
646 | 686 | wf_handle = await temporal_context.client.start_workflow( # type: ignore |
647 | 687 | workflow=workflow, |
648 | 688 | arg=arg, |
@@ -674,6 +714,6 @@ async def _start_nexus_backing_workflow( |
674 | 714 | request_id=temporal_context.nexus_context.request_id, |
675 | 715 | ) |
676 | 716 |
|
677 | | - temporal_context._add_outbound_links(wf_handle) |
| 717 | + temporal_context._add_outbound_workflow_links(wf_handle) |
678 | 718 |
|
679 | 719 | return WorkflowHandle[ReturnType]._unsafe_from_client_workflow_handle(wf_handle) |
0 commit comments