|
3 | 3 | import dataclasses |
4 | 4 | import logging |
5 | 5 | from collections.abc import Awaitable, Mapping, MutableMapping, Sequence |
| 6 | +from contextlib import contextmanager |
6 | 7 | from contextvars import ContextVar |
7 | 8 | from dataclasses import dataclass |
8 | 9 | from datetime import timedelta |
9 | 10 | from typing import ( |
10 | 11 | TYPE_CHECKING, |
11 | 12 | Any, |
12 | 13 | Callable, |
| 14 | + Generator, |
13 | 15 | Optional, |
14 | 16 | Union, |
15 | 17 | overload, |
|
47 | 49 | ContextVar("temporal-cancel-operation-context") |
48 | 50 | ) |
49 | 51 |
|
| 52 | +# A Nexus start handler might start zero or more workflows as usual using a Temporal client. In |
| 53 | +# addition, it may start one "nexus-backing" workflow, using |
| 54 | +# WorkflowRunOperationContext.start_workflow. This context is active while the latter is being done. |
| 55 | +# It is thus a narrower context than _temporal_start_operation_context. |
| 56 | +_temporal_nexus_backing_workflow_start_context: ContextVar[bool] = ContextVar( |
| 57 | + "temporal-nexus-backing-workflow-start-context" |
| 58 | +) |
| 59 | + |
50 | 60 |
|
51 | 61 | @dataclass(frozen=True) |
52 | 62 | class Info: |
@@ -96,6 +106,19 @@ def _try_temporal_context() -> ( |
96 | 106 | return start_ctx or cancel_ctx |
97 | 107 |
|
98 | 108 |
|
| 109 | +@contextmanager |
| 110 | +def _nexus_backing_workflow_start_context() -> Generator[None, None, None]: |
| 111 | + token = _temporal_nexus_backing_workflow_start_context.set(True) |
| 112 | + try: |
| 113 | + yield |
| 114 | + finally: |
| 115 | + _temporal_nexus_backing_workflow_start_context.reset(token) |
| 116 | + |
| 117 | + |
| 118 | +def _in_nexus_backing_workflow_start_context() -> bool: |
| 119 | + return _temporal_nexus_backing_workflow_start_context.get(False) |
| 120 | + |
| 121 | + |
99 | 122 | @dataclass |
100 | 123 | class _TemporalStartOperationContext: |
101 | 124 | """Context for a Nexus start operation being handled by a Temporal Nexus Worker.""" |
@@ -396,56 +419,46 @@ async def start_workflow( |
396 | 419 | Nexus caller is itself a workflow, this means that the workflow in the caller |
397 | 420 | namespace web UI will contain links to the started workflow, and vice versa. |
398 | 421 | """ |
399 | | - # TODO(nexus-preview): When sdk-python supports on_conflict_options, Typescript does this: |
400 | | - # if (workflowOptions.workflowIdConflictPolicy === 'USE_EXISTING') { |
401 | | - # internalOptions.onConflictOptions = { |
402 | | - # attachLinks: true, |
403 | | - # attachCompletionCallbacks: true, |
404 | | - # attachRequestId: true, |
405 | | - # }; |
406 | | - # } |
407 | | - if ( |
408 | | - id_conflict_policy |
409 | | - == temporalio.common.WorkflowIDConflictPolicy.USE_EXISTING |
410 | | - ): |
411 | | - raise RuntimeError( |
412 | | - "WorkflowIDConflictPolicy.USE_EXISTING is not yet supported when starting a workflow " |
413 | | - "that backs a Nexus operation (Python SDK Nexus support is at Pre-release stage)." |
414 | | - ) |
415 | | - |
416 | 422 | # We must pass nexus_completion_callbacks, workflow_event_links, and request_id, |
417 | 423 | # but these are deliberately not exposed in overloads, hence the type-check |
418 | 424 | # violation. |
419 | | - wf_handle = await self._temporal_context.client.start_workflow( # type: ignore |
420 | | - workflow=workflow, |
421 | | - arg=arg, |
422 | | - args=args, |
423 | | - id=id, |
424 | | - task_queue=task_queue or self._temporal_context.info().task_queue, |
425 | | - result_type=result_type, |
426 | | - execution_timeout=execution_timeout, |
427 | | - run_timeout=run_timeout, |
428 | | - task_timeout=task_timeout, |
429 | | - id_reuse_policy=id_reuse_policy, |
430 | | - id_conflict_policy=id_conflict_policy, |
431 | | - retry_policy=retry_policy, |
432 | | - cron_schedule=cron_schedule, |
433 | | - memo=memo, |
434 | | - search_attributes=search_attributes, |
435 | | - static_summary=static_summary, |
436 | | - static_details=static_details, |
437 | | - start_delay=start_delay, |
438 | | - start_signal=start_signal, |
439 | | - start_signal_args=start_signal_args, |
440 | | - rpc_metadata=rpc_metadata, |
441 | | - rpc_timeout=rpc_timeout, |
442 | | - request_eager_start=request_eager_start, |
443 | | - priority=priority, |
444 | | - versioning_override=versioning_override, |
445 | | - callbacks=self._temporal_context._get_callbacks(), |
446 | | - workflow_event_links=self._temporal_context._get_workflow_event_links(), |
447 | | - request_id=self._temporal_context.nexus_context.request_id, |
448 | | - ) |
| 425 | + |
| 426 | + # Here we are starting a "nexus-backing" workflow. That means that the StartWorkflow request |
| 427 | + # contains nexus-specific data such as a completion callback (used by the handler server |
| 428 | + # namespace to deliver the result to the caller namespace when the workflow reaches a |
| 429 | + # terminal state) and inbound links to the caller workflow (attached to history events of |
| 430 | + # the workflow started in the handler namespace, and displayed in the UI). |
| 431 | + with _nexus_backing_workflow_start_context(): |
| 432 | + wf_handle = await self._temporal_context.client.start_workflow( # type: ignore |
| 433 | + workflow=workflow, |
| 434 | + arg=arg, |
| 435 | + args=args, |
| 436 | + id=id, |
| 437 | + task_queue=task_queue or self._temporal_context.info().task_queue, |
| 438 | + result_type=result_type, |
| 439 | + execution_timeout=execution_timeout, |
| 440 | + run_timeout=run_timeout, |
| 441 | + task_timeout=task_timeout, |
| 442 | + id_reuse_policy=id_reuse_policy, |
| 443 | + id_conflict_policy=id_conflict_policy, |
| 444 | + retry_policy=retry_policy, |
| 445 | + cron_schedule=cron_schedule, |
| 446 | + memo=memo, |
| 447 | + search_attributes=search_attributes, |
| 448 | + static_summary=static_summary, |
| 449 | + static_details=static_details, |
| 450 | + start_delay=start_delay, |
| 451 | + start_signal=start_signal, |
| 452 | + start_signal_args=start_signal_args, |
| 453 | + rpc_metadata=rpc_metadata, |
| 454 | + rpc_timeout=rpc_timeout, |
| 455 | + request_eager_start=request_eager_start, |
| 456 | + priority=priority, |
| 457 | + versioning_override=versioning_override, |
| 458 | + callbacks=self._temporal_context._get_callbacks(), |
| 459 | + workflow_event_links=self._temporal_context._get_workflow_event_links(), |
| 460 | + request_id=self._temporal_context.nexus_context.request_id, |
| 461 | + ) |
449 | 462 |
|
450 | 463 | self._temporal_context._add_outbound_links(wf_handle) |
451 | 464 |
|
|
0 commit comments