From d31790ff7e8a2c05934b99a42575e99d113d1d77 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 9 Dec 2024 14:44:22 -0800 Subject: [PATCH 01/13] Client workflow starting updates --- .gitignore | 1 + temporalio/client.py | 102 ++++++++++++++++++++++++++++++++-------- temporalio/converter.py | 26 ++++++++++ 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 021ac7338..f94200d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ temporalio/bridge/temporal_sdk_bridge* /tests/helpers/golangworker/golangworker /.idea /sdk-python.iml +/.zed diff --git a/temporalio/client.py b/temporalio/client.py index e9ecefe62..8974c7c6f 100644 --- a/temporalio/client.py +++ b/temporalio/client.py @@ -308,6 +308,8 @@ async def start_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -339,6 +341,8 @@ async def start_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -372,6 +376,8 @@ async def start_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -405,6 +411,8 @@ async def start_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -436,6 +444,8 @@ async def start_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -470,6 +480,12 @@ async def start_workflow( search_attributes: Search attributes for the workflow. The dictionary form of this is deprecated, use :py:class:`temporalio.common.TypedSearchAttributes`. + static_summary: A single-line fixed summary for this workflow execution that may appear + in the UI/CLI. This can be in single-line Temporal markdown format. + static_details: General fixed details for this workflow execution that may appear in + UI/CLI. This can be in Temporal markdown format and can span multiple lines. This is + a fixed value on the workflow that cannot be updated. For details that can be + updated, use `Workflow.CurrentDetails` within the workflow. start_delay: Amount of time to wait before starting the workflow. This does not work with ``cron_schedule``. start_signal: If present, this signal is sent as signal-with-start @@ -526,6 +542,8 @@ async def start_workflow( search_attributes=search_attributes, start_delay=start_delay, headers={}, + static_summary=static_summary, + static_details=static_details, start_signal=start_signal, start_signal_args=start_signal_args, ret_type=result_type, @@ -557,6 +575,8 @@ async def execute_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -588,6 +608,8 @@ async def execute_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -621,6 +643,8 @@ async def execute_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -654,6 +678,8 @@ async def execute_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -685,6 +711,8 @@ async def execute_workflow( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, start_delay: Optional[timedelta] = None, start_signal: Optional[str] = None, start_signal_args: Sequence[Any] = [], @@ -716,6 +744,8 @@ async def execute_workflow( cron_schedule=cron_schedule, memo=memo, search_attributes=search_attributes, + static_summary=static_summary, + static_details=static_details, start_delay=start_delay, start_signal=start_signal, start_signal_args=start_signal_args, @@ -923,6 +953,8 @@ async def create_schedule( temporalio.common.SearchAttributes, ] ] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, rpc_metadata: Mapping[str, str] = {}, rpc_timeout: Optional[timedelta] = None, ) -> ScheduleHandle: @@ -941,6 +973,12 @@ async def create_schedule( attributes for a scheduled workflow are part of the scheduled action. The dictionary form of this is DEPRECATED, use :py:class:`temporalio.common.TypedSearchAttributes`. + static_summary: A single-line fixed summary for this workflow execution that may appear + in the UI/CLI. This can be in single-line Temporal markdown format. + static_details: General fixed details for this workflow execution that may appear in + UI/CLI. This can be in Temporal markdown format and can span multiple lines. This is + a fixed value on the workflow that cannot be updated. For details that can be + updated, use `Workflow.CurrentDetails` within the workflow. rpc_metadata: Headers used on the RPC call. Keys here override client-level RPC metadata keys. rpc_timeout: Optional RPC deadline to set for the RPC call. @@ -961,6 +999,8 @@ async def create_schedule( backfill=backfill, memo=memo, search_attributes=search_attributes, + static_summary=static_summary, + static_details=static_details, rpc_metadata=rpc_metadata, rpc_timeout=rpc_timeout, ) @@ -2421,7 +2461,7 @@ class WorkflowExecutionCount: count: int """Approximate number of workflows matching the original query. - + If the query had a group-by clause, this is simply the sum of all the counts in py:attr:`groups`. """ @@ -2985,7 +3025,7 @@ class ScheduleSpec: cron_expressions: Sequence[str] = dataclasses.field(default_factory=list) """Cron-based specification of times. - + This is provided for easy migration from legacy string-based cron scheduling. New uses should use :py:attr:`calendars` instead. These expressions will be translated to calendar-based specifications on the @@ -3067,7 +3107,7 @@ class ScheduleRange: end: int = 0 """Inclusive end of the range. - + If unset or less than start, defaults to start. """ @@ -3244,6 +3284,8 @@ class ScheduleActionStartWorkflow(ScheduleAction): """This is deprecated and is only present in case existing untyped attributes already exist for update. This should never be used when creating.""" + static_summary: Optional[str] + static_details: Optional[str] headers: Optional[Mapping[str, temporalio.api.common.v1.Payload]] @@ -3267,6 +3309,8 @@ def __init__( retry_policy: Optional[temporalio.common.RetryPolicy] = None, memo: Optional[Mapping[str, Any]] = None, typed_search_attributes: temporalio.common.TypedSearchAttributes = temporalio.common.TypedSearchAttributes.empty, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> None: ... # Overload for single-param workflow @@ -3284,6 +3328,8 @@ def __init__( retry_policy: Optional[temporalio.common.RetryPolicy] = None, memo: Optional[Mapping[str, Any]] = None, typed_search_attributes: temporalio.common.TypedSearchAttributes = temporalio.common.TypedSearchAttributes.empty, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> None: ... # Overload for multi-param workflow @@ -3303,6 +3349,8 @@ def __init__( retry_policy: Optional[temporalio.common.RetryPolicy] = None, memo: Optional[Mapping[str, Any]] = None, typed_search_attributes: temporalio.common.TypedSearchAttributes = temporalio.common.TypedSearchAttributes.empty, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> None: ... # Overload for string-name workflow @@ -3321,6 +3369,8 @@ def __init__( retry_policy: Optional[temporalio.common.RetryPolicy] = None, memo: Optional[Mapping[str, Any]] = None, typed_search_attributes: temporalio.common.TypedSearchAttributes = temporalio.common.TypedSearchAttributes.empty, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> None: ... # Overload for raw info @@ -3347,6 +3397,8 @@ def __init__( memo: Optional[Mapping[str, Any]] = None, typed_search_attributes: temporalio.common.TypedSearchAttributes = temporalio.common.TypedSearchAttributes.empty, untyped_search_attributes: temporalio.common.SearchAttributes = {}, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, headers: Optional[Mapping[str, temporalio.api.common.v1.Payload]] = None, raw_info: Optional[temporalio.api.workflow.v1.NewWorkflowExecutionInfo] = None, ) -> None: @@ -3425,6 +3477,8 @@ def __init__( self.typed_search_attributes = typed_search_attributes self.untyped_search_attributes = untyped_search_attributes self.headers = headers + self.static_summary = static_summary + self.static_details = static_details async def _to_proto( self, client: Client @@ -3474,6 +3528,9 @@ async def _to_proto( for k, v in self.memo.items() }, ), + user_metadata=await client.data_converter._encode_user_metadata( + self.static_summary, self.static_details + ), ), ) # Add any untyped attributes that are not also in the typed set @@ -3506,7 +3563,7 @@ class ScheduleOverlapPolicy(IntEnum): temporalio.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_SKIP ) """Don't start anything. - + When the workflow completes, the next scheduled event after that time will be considered. """ @@ -3516,7 +3573,7 @@ class ScheduleOverlapPolicy(IntEnum): ) """Start the workflow again soon as the current one completes, but only buffer one start in this way. - + If another start is supposed to happen when the workflow is running, and one is already buffered, then only the first one will be started after the running workflow finishes. @@ -3544,7 +3601,7 @@ class ScheduleOverlapPolicy(IntEnum): temporalio.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL ) """Start any number of concurrent workflows. - + Note that with this policy, last completion result and last failure will not be available since workflows are not sequential.""" @@ -3557,7 +3614,7 @@ class ScheduleBackfill: start_at: datetime """Start of the range to evaluate the schedule in. - + This is exclusive """ end_at: datetime @@ -3596,7 +3653,7 @@ class SchedulePolicy: pause_on_failure: bool = False """Whether to pause the schedule if an action fails or times out. - + Note: For workflows, this only applies after all retries have been exhausted. """ @@ -3627,7 +3684,7 @@ class ScheduleState: note: Optional[str] = None """Human readable message for the schedule. - + The system may overwrite this value on certain conditions like pause-on-failure. """ @@ -3646,7 +3703,7 @@ class ScheduleState: remaining_actions: int = 0 """Actions remaining on this schedule. - + Once this number hits 0, no further actions are scheduled automatically. """ @@ -3722,7 +3779,7 @@ class ScheduleDescription: search_attributes: temporalio.common.SearchAttributes """Search attributes on the schedule. - + .. deprecated:: Use :py:attr:`typed_search_attributes` instead. """ @@ -3948,14 +4005,14 @@ class ScheduleListDescription: schedule: Optional[ScheduleListSchedule] """Schedule details that can be mutated. - + This may not be present in older Temporal servers without advanced visibility. """ info: Optional[ScheduleListInfo] """Information about the schedule. - + This may not be present in older Temporal servers without advanced visibility. """ @@ -3965,7 +4022,7 @@ class ScheduleListDescription: search_attributes: temporalio.common.SearchAttributes """Search attributes on the schedule. - + .. deprecated:: Use :py:attr:`typed_search_attributes` instead. """ @@ -4111,14 +4168,14 @@ class ScheduleListInfo: recent_actions: Sequence[ScheduleActionResult] """Most recent actions, oldest first. - + This may be a smaller amount than present on :py:attr:`ScheduleDescription.info`. """ next_action_times: Sequence[datetime] """Next scheduled action times. - + This may be a smaller amount than present on :py:attr:`ScheduleDescription.info`. """ @@ -4144,7 +4201,7 @@ class ScheduleListState: note: Optional[str] """Human readable message for the schedule. - + The system may overwrite this value on certain conditions like pause-on-failure. """ @@ -4536,6 +4593,8 @@ class StartWorkflowInput: headers: Mapping[str, temporalio.api.common.v1.Payload] start_signal: Optional[str] start_signal_args: Sequence[Any] + static_summary: Optional[str] + static_details: Optional[str] # Type may be absent ret_type: Optional[Type] rpc_metadata: Mapping[str, str] @@ -5063,6 +5122,9 @@ async def start_workflow( temporalio.converter.encode_search_attributes( input.search_attributes, req.search_attributes ) + req.user_metadata = self._client.data_converter._encode_user_metadata( + input.static_summary, input.static_details + ) if input.start_delay is not None: req.workflow_start_delay.FromTimedelta(input.start_delay) if input.headers is not None: @@ -5955,7 +6017,7 @@ class BuildIdOpAddNewCompatible(BuildIdOp): """The Build Id to add to the compatible set.""" existing_compatible_build_id: str - """A Build Id which must already be defined on the task queue, and is used to find the + """A Build Id which must already be defined on the task queue, and is used to find the compatible set to add the new id to. """ @@ -5982,7 +6044,7 @@ class BuildIdOpPromoteSetByBuildId(BuildIdOp): """ build_id: str - """A Build Id which must already be defined on the task queue, and is used to find the + """A Build Id which must already be defined on the task queue, and is used to find the compatible set to promote.""" def _as_partial_proto( @@ -6077,7 +6139,7 @@ class BuildIdReachability: """ unretrieved_task_queues: FrozenSet[str] - """If any Task Queues could not be retrieved because the server limits the number that can be + """If any Task Queues could not be retrieved because the server limits the number that can be queried at once, they will be listed here. """ diff --git a/temporalio/converter.py b/temporalio/converter.py index 24f9b6b47..9fb7886d4 100644 --- a/temporalio/converter.py +++ b/temporalio/converter.py @@ -43,6 +43,7 @@ import temporalio.api.common.v1 import temporalio.api.enums.v1 import temporalio.api.failure.v1 +import temporalio.api.sdk.v1 import temporalio.common import temporalio.exceptions import temporalio.types @@ -1084,6 +1085,16 @@ async def encode_wrapper( """ return temporalio.api.common.v1.Payloads(payloads=(await self.encode(values))) + async def _encode_user_metadata( + self, summary: Optional[str], details: Optional[str] + ) -> Optional[temporalio.api.sdk.v1.UserMetadata]: + if summary is None and details is None: + return None + return temporalio.api.sdk.v1.UserMetadata( + summary=None if summary is None else (await self.encode([summary]))[0], + details=None if details is None else (await self.encode([details]))[0], + ) + async def decode_wrapper( self, payloads: Optional[temporalio.api.common.v1.Payloads], @@ -1112,6 +1123,21 @@ async def decode_failure( await self.payload_codec.decode_failure(failure) return self.failure_converter.from_failure(failure, self.payload_converter) + async def _decode_user_metadata( + self, metadata: Optional[temporalio.api.sdk.v1.UserMetadata] + ) -> Tuple[Optional[str], Optional[str]]: + """Returns (summary, details)""" + if metadata is None: + return None, None + return ( + None + if metadata.summary is None + else (await self.decode([metadata.summary]))[0], + None + if metadata.details is None + else (await self.decode([metadata.details]))[0], + ) + DefaultPayloadConverter.default_encoding_payload_converters = ( BinaryNullPayloadConverter(), From 6f71c3a4ed73136be847b451c8a7b8617e3255f7 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Tue, 10 Dec 2024 09:40:34 -0800 Subject: [PATCH 02/13] Add to child workflow / activity --- temporalio/worker/_interceptor.py | 1 + temporalio/worker/_workflow_instance.py | 4 ++++ temporalio/workflow.py | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/temporalio/worker/_interceptor.py b/temporalio/worker/_interceptor.py index ef0970de2..bccff0109 100644 --- a/temporalio/worker/_interceptor.py +++ b/temporalio/worker/_interceptor.py @@ -246,6 +246,7 @@ class StartActivityInput: headers: Mapping[str, temporalio.api.common.v1.Payload] disable_eager_execution: bool versioning_intent: Optional[VersioningIntent] + summary: Optional[str] # The types may be absent arg_types: Optional[List[Type]] ret_type: Optional[Type] diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index c1748848c..9100df525 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -1159,6 +1159,7 @@ def workflow_start_activity( cancellation_type: temporalio.workflow.ActivityCancellationType, activity_id: Optional[str], versioning_intent: Optional[temporalio.workflow.VersioningIntent], + summary: Optional[str] = None, ) -> temporalio.workflow.ActivityHandle[Any]: self._assert_not_read_only("start activity") # Get activity definition if it's callable @@ -1194,6 +1195,7 @@ def workflow_start_activity( arg_types=arg_types, ret_type=ret_type, versioning_intent=versioning_intent, + summary=summary, ) ) @@ -2369,6 +2371,8 @@ def _apply_schedule_command( command.schedule_activity.versioning_intent = ( self._input.versioning_intent._to_proto() ) + # if self._input.summary: + # command. if isinstance(self._input, StartLocalActivityInput): if self._input.local_retry_threshold: command.schedule_local_activity.local_retry_threshold.FromTimedelta( diff --git a/temporalio/workflow.py b/temporalio/workflow.py index 521d3c10e..78248a316 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -659,6 +659,7 @@ def workflow_start_activity( cancellation_type: ActivityCancellationType, activity_id: Optional[str], versioning_intent: Optional[VersioningIntent], + summary: Optional[str] = None, ) -> ActivityHandle[Any]: ... @abstractmethod @@ -1869,6 +1870,7 @@ def start_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[Any]: """Start an activity and return its handle. @@ -1902,6 +1904,8 @@ def start_activity( need to. Contact Temporal before setting this value. versioning_intent: When using the Worker Versioning feature, specifies whether this Activity should run on a worker with a compatible Build Id or not. + summary: Gets or sets a single-line fixed summary for this activity that may appear in UI/CLI. + This can be in single-line Temporal markdown format. Returns: An activity handle to the activity which is an async task. @@ -1919,6 +1923,7 @@ def start_activity( cancellation_type=cancellation_type, activity_id=activity_id, versioning_intent=versioning_intent, + summary=summary, ) From c7e1a83915f776c4c2ae3c27cb77120123a42a8c Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Tue, 10 Dec 2024 09:42:22 -0800 Subject: [PATCH 03/13] Update core --- temporalio/bridge/Cargo.lock | 124 ++++++++-------- .../workflow_activation_pb2.py | 6 +- .../workflow_activation_pb2.pyi | 8 + .../workflow_commands_pb2.py | 137 +++++++++--------- .../workflow_commands_pb2.pyi | 45 +++--- temporalio/bridge/sdk-core | 2 +- temporalio/client.py | 6 +- temporalio/worker/_workflow_instance.py | 3 +- 8 files changed, 168 insertions(+), 163 deletions(-) diff --git a/temporalio/bridge/Cargo.lock b/temporalio/bridge/Cargo.lock index ba644121d..b4dcb97e2 100644 --- a/temporalio/bridge/Cargo.lock +++ b/temporalio/bridge/Cargo.lock @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", @@ -267,9 +267,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "num-traits", "serde", @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" @@ -1223,9 +1223,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -1239,9 +1239,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libredox" @@ -1464,9 +1464,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c365a63eec4f55b7efeceb724f1336f26a9cf3427b70e59e2cd2a5b947fba96" +checksum = "570074cc999d1a58184080966e5bd3bf3a9a4af650c3b05047c2621e7405cd17" dependencies = [ "futures-core", "futures-sink", @@ -1478,9 +1478,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.17.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b925a602ffb916fb7421276b86756027b37ee708f9dce2dbdcc51739f07e727" +checksum = "29e1f9c8b032d4f635c730c0efcf731d5e2530ea13fa8bef7939ddc8420696bd" dependencies = [ "async-trait", "futures-core", @@ -1497,8 +1497,7 @@ dependencies = [ [[package]] name = "opentelemetry-prometheus" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4191ce34aa274621861a7a9d68dbcf618d5b6c66b10081631b61fd81fbc015" +source = "git+https://github.com/open-telemetry/opentelemetry-rust.git?rev=e911383#e91138351a689cd21923c15eb48f5fbc95ded807" dependencies = [ "once_cell", "opentelemetry", @@ -1509,9 +1508,9 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.7.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee9f20bff9c984511a02f082dc8ede839e4a9bf15cc2487c8d6fea5ad850d9" +checksum = "c9d3968ce3aefdcca5c27e3c4ea4391b37547726a70893aab52d3de95d5f8b34" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -1521,9 +1520,9 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" -version = "0.24.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692eac490ec80f24a17828d49b40b60f5aeaccdfe6a503f939713afd22bc28df" +checksum = "d2c627d9f4c9cdc1f21a29ee4bfbd6028fcb8bcf2a857b43f3abdf72c9c862f3" dependencies = [ "async-trait", "futures-channel", @@ -1725,9 +1724,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -1735,11 +1734,10 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" +checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "bytes", "heck 0.5.0", "itertools", "log", @@ -1756,9 +1754,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", "itertools", @@ -1769,9 +1767,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" dependencies = [ "prost", ] @@ -1943,7 +1941,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -1962,7 +1960,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.4", + "thiserror 2.0.6", "tinyvec", "tracing", "web-time", @@ -1970,9 +1968,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" dependencies = [ "cfg_aliases", "libc", @@ -2189,15 +2187,15 @@ version = "0.1.0" [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2565,7 +2563,7 @@ dependencies = [ "slotmap", "temporal-sdk-core-api", "temporal-sdk-core-protos", - "thiserror 1.0.69", + "thiserror 2.0.6", "tokio", "tonic", "tower 0.5.1", @@ -2644,7 +2642,7 @@ dependencies = [ "temporal-client", "temporal-sdk-core-api", "temporal-sdk-core-protos", - "thiserror 1.0.69", + "thiserror 2.0.6", "tokio", "tokio-stream", "tokio-util", @@ -2669,7 +2667,7 @@ dependencies = [ "prost-types", "serde_json", "temporal-sdk-core-protos", - "thiserror 1.0.69", + "thiserror 2.0.6", "tonic", "tracing-core", "url", @@ -2684,13 +2682,14 @@ dependencies = [ "derive_more", "prost", "prost-build", + "prost-types", "prost-wkt", "prost-wkt-build", "prost-wkt-types", "rand", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.6", "tonic", "tonic-build", "uuid", @@ -2713,11 +2712,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.6", ] [[package]] @@ -2733,9 +2732,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -2837,9 +2836,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -3141,9 +3140,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -3152,13 +3151,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -3167,9 +3165,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -3180,9 +3178,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3190,9 +3188,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -3203,9 +3201,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-streams" @@ -3222,9 +3220,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -3580,7 +3578,7 @@ dependencies = [ "pbkdf2", "rand", "sha1", - "thiserror 2.0.4", + "thiserror 2.0.6", "time", "zeroize", "zopfli", diff --git a/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.py b/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.py index 03a6d8698..0ac544a2f 100644 --- a/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.py +++ b/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.py @@ -40,7 +40,7 @@ ) DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n?temporal/sdk/core/workflow_activation/workflow_activation.proto\x12\x1b\x63oresdk.workflow_activation\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a%temporal/api/failure/v1/message.proto\x1a$temporal/api/update/v1/message.proto\x1a$temporal/api/common/v1/message.proto\x1a$temporal/api/enums/v1/workflow.proto\x1a\x37temporal/sdk/core/activity_result/activity_result.proto\x1a\x35temporal/sdk/core/child_workflow/child_workflow.proto\x1a%temporal/sdk/core/common/common.proto"\xc7\x02\n\x12WorkflowActivation\x12\x0e\n\x06run_id\x18\x01 \x01(\t\x12-\n\ttimestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0cis_replaying\x18\x03 \x01(\x08\x12\x16\n\x0ehistory_length\x18\x04 \x01(\r\x12@\n\x04jobs\x18\x05 \x03(\x0b\x32\x32.coresdk.workflow_activation.WorkflowActivationJob\x12 \n\x18\x61vailable_internal_flags\x18\x06 \x03(\r\x12\x1a\n\x12history_size_bytes\x18\x07 \x01(\x04\x12!\n\x19\x63ontinue_as_new_suggested\x18\x08 \x01(\x08\x12!\n\x19\x62uild_id_for_current_task\x18\t \x01(\t"\xa7\t\n\x15WorkflowActivationJob\x12N\n\x13initialize_workflow\x18\x01 \x01(\x0b\x32/.coresdk.workflow_activation.InitializeWorkflowH\x00\x12<\n\nfire_timer\x18\x02 \x01(\x0b\x32&.coresdk.workflow_activation.FireTimerH\x00\x12K\n\x12update_random_seed\x18\x04 \x01(\x0b\x32-.coresdk.workflow_activation.UpdateRandomSeedH\x00\x12\x44\n\x0equery_workflow\x18\x05 \x01(\x0b\x32*.coresdk.workflow_activation.QueryWorkflowH\x00\x12\x46\n\x0f\x63\x61ncel_workflow\x18\x06 \x01(\x0b\x32+.coresdk.workflow_activation.CancelWorkflowH\x00\x12\x46\n\x0fsignal_workflow\x18\x07 \x01(\x0b\x32+.coresdk.workflow_activation.SignalWorkflowH\x00\x12H\n\x10resolve_activity\x18\x08 \x01(\x0b\x32,.coresdk.workflow_activation.ResolveActivityH\x00\x12G\n\x10notify_has_patch\x18\t \x01(\x0b\x32+.coresdk.workflow_activation.NotifyHasPatchH\x00\x12q\n&resolve_child_workflow_execution_start\x18\n \x01(\x0b\x32?.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartH\x00\x12\x66\n resolve_child_workflow_execution\x18\x0b \x01(\x0b\x32:.coresdk.workflow_activation.ResolveChildWorkflowExecutionH\x00\x12\x66\n resolve_signal_external_workflow\x18\x0c \x01(\x0b\x32:.coresdk.workflow_activation.ResolveSignalExternalWorkflowH\x00\x12u\n(resolve_request_cancel_external_workflow\x18\r \x01(\x0b\x32\x41.coresdk.workflow_activation.ResolveRequestCancelExternalWorkflowH\x00\x12:\n\tdo_update\x18\x0e \x01(\x0b\x32%.coresdk.workflow_activation.DoUpdateH\x00\x12I\n\x11remove_from_cache\x18\x32 \x01(\x0b\x32,.coresdk.workflow_activation.RemoveFromCacheH\x00\x42\t\n\x07variant"\xe3\t\n\x12InitializeWorkflow\x12\x15\n\rworkflow_type\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\x12\x32\n\targuments\x18\x03 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x17\n\x0frandomness_seed\x18\x04 \x01(\x04\x12M\n\x07headers\x18\x05 \x03(\x0b\x32<.coresdk.workflow_activation.InitializeWorkflow.HeadersEntry\x12\x10\n\x08identity\x18\x06 \x01(\t\x12I\n\x14parent_workflow_info\x18\x07 \x01(\x0b\x32+.coresdk.common.NamespacedWorkflowExecution\x12=\n\x1aworkflow_execution_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x37\n\x14workflow_run_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x38\n\x15workflow_task_timeout\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x12\'\n\x1f\x63ontinued_from_execution_run_id\x18\x0b \x01(\t\x12J\n\x13\x63ontinued_initiator\x18\x0c \x01(\x0e\x32-.temporal.api.enums.v1.ContinueAsNewInitiator\x12;\n\x11\x63ontinued_failure\x18\r \x01(\x0b\x32 .temporal.api.failure.v1.Failure\x12@\n\x16last_completion_result\x18\x0e \x01(\x0b\x32 .temporal.api.common.v1.Payloads\x12\x1e\n\x16\x66irst_execution_run_id\x18\x0f \x01(\t\x12\x39\n\x0cretry_policy\x18\x10 \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x0f\n\x07\x61ttempt\x18\x11 \x01(\x05\x12\x15\n\rcron_schedule\x18\x12 \x01(\t\x12\x46\n"workflow_execution_expiration_time\x18\x13 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x45\n"cron_schedule_to_schedule_interval\x18\x14 \x01(\x0b\x32\x19.google.protobuf.Duration\x12*\n\x04memo\x18\x15 \x01(\x0b\x32\x1c.temporal.api.common.v1.Memo\x12\x43\n\x11search_attributes\x18\x16 \x01(\x0b\x32(.temporal.api.common.v1.SearchAttributes\x12.\n\nstart_time\x18\x17 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\x18\n\tFireTimer\x12\x0b\n\x03seq\x18\x01 \x01(\r"m\n\x0fResolveActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12;\n\x06result\x18\x02 \x01(\x0b\x32+.coresdk.activity_result.ActivityResolution\x12\x10\n\x08is_local\x18\x03 \x01(\x08"\xd1\x02\n"ResolveChildWorkflowExecutionStart\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12[\n\tsucceeded\x18\x02 \x01(\x0b\x32\x46.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartSuccessH\x00\x12X\n\x06\x66\x61iled\x18\x03 \x01(\x0b\x32\x46.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartFailureH\x00\x12]\n\tcancelled\x18\x04 \x01(\x0b\x32H.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartCancelledH\x00\x42\x08\n\x06status";\n)ResolveChildWorkflowExecutionStartSuccess\x12\x0e\n\x06run_id\x18\x01 \x01(\t"\xa6\x01\n)ResolveChildWorkflowExecutionStartFailure\x12\x13\n\x0bworkflow_id\x18\x01 \x01(\t\x12\x15\n\rworkflow_type\x18\x02 \x01(\t\x12M\n\x05\x63\x61use\x18\x03 \x01(\x0e\x32>.coresdk.child_workflow.StartChildWorkflowExecutionFailedCause"`\n+ResolveChildWorkflowExecutionStartCancelled\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"i\n\x1dResolveChildWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12;\n\x06result\x18\x02 \x01(\x0b\x32+.coresdk.child_workflow.ChildWorkflowResult"+\n\x10UpdateRandomSeed\x12\x17\n\x0frandomness_seed\x18\x01 \x01(\x04"\x84\x02\n\rQueryWorkflow\x12\x10\n\x08query_id\x18\x01 \x01(\t\x12\x12\n\nquery_type\x18\x02 \x01(\t\x12\x32\n\targuments\x18\x03 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12H\n\x07headers\x18\x05 \x03(\x0b\x32\x37.coresdk.workflow_activation.QueryWorkflow.HeadersEntry\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"B\n\x0e\x43\x61ncelWorkflow\x12\x30\n\x07\x64\x65tails\x18\x01 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload"\x83\x02\n\x0eSignalWorkflow\x12\x13\n\x0bsignal_name\x18\x01 \x01(\t\x12.\n\x05input\x18\x02 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x10\n\x08identity\x18\x03 \x01(\t\x12I\n\x07headers\x18\x05 \x03(\x0b\x32\x38.coresdk.workflow_activation.SignalWorkflow.HeadersEntry\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01""\n\x0eNotifyHasPatch\x12\x10\n\x08patch_id\x18\x01 \x01(\t"_\n\x1dResolveSignalExternalWorkflow\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x31\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"f\n$ResolveRequestCancelExternalWorkflow\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x31\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"\xcb\x02\n\x08\x44oUpdate\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1c\n\x14protocol_instance_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12.\n\x05input\x18\x04 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x43\n\x07headers\x18\x05 \x03(\x0b\x32\x32.coresdk.workflow_activation.DoUpdate.HeadersEntry\x12*\n\x04meta\x18\x06 \x01(\x0b\x32\x1c.temporal.api.update.v1.Meta\x12\x15\n\rrun_validator\x18\x07 \x01(\x08\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\xc1\x02\n\x0fRemoveFromCache\x12\x0f\n\x07message\x18\x01 \x01(\t\x12K\n\x06reason\x18\x02 \x01(\x0e\x32;.coresdk.workflow_activation.RemoveFromCache.EvictionReason"\xcf\x01\n\x0e\x45victionReason\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0e\n\nCACHE_FULL\x10\x01\x12\x0e\n\nCACHE_MISS\x10\x02\x12\x12\n\x0eNONDETERMINISM\x10\x03\x12\r\n\tLANG_FAIL\x10\x04\x12\x12\n\x0eLANG_REQUESTED\x10\x05\x12\x12\n\x0eTASK_NOT_FOUND\x10\x06\x12\x15\n\x11UNHANDLED_COMMAND\x10\x07\x12\t\n\x05\x46\x41TAL\x10\x08\x12\x1f\n\x1bPAGINATION_OR_HISTORY_FETCH\x10\tB8\xea\x02\x35Temporalio::Internal::Bridge::Api::WorkflowActivationb\x06proto3' + b'\n?temporal/sdk/core/workflow_activation/workflow_activation.proto\x12\x1b\x63oresdk.workflow_activation\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a%temporal/api/failure/v1/message.proto\x1a$temporal/api/update/v1/message.proto\x1a$temporal/api/common/v1/message.proto\x1a$temporal/api/enums/v1/workflow.proto\x1a\x37temporal/sdk/core/activity_result/activity_result.proto\x1a\x35temporal/sdk/core/child_workflow/child_workflow.proto\x1a%temporal/sdk/core/common/common.proto"\xc7\x02\n\x12WorkflowActivation\x12\x0e\n\x06run_id\x18\x01 \x01(\t\x12-\n\ttimestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x14\n\x0cis_replaying\x18\x03 \x01(\x08\x12\x16\n\x0ehistory_length\x18\x04 \x01(\r\x12@\n\x04jobs\x18\x05 \x03(\x0b\x32\x32.coresdk.workflow_activation.WorkflowActivationJob\x12 \n\x18\x61vailable_internal_flags\x18\x06 \x03(\r\x12\x1a\n\x12history_size_bytes\x18\x07 \x01(\x04\x12!\n\x19\x63ontinue_as_new_suggested\x18\x08 \x01(\x08\x12!\n\x19\x62uild_id_for_current_task\x18\t \x01(\t"\xa7\t\n\x15WorkflowActivationJob\x12N\n\x13initialize_workflow\x18\x01 \x01(\x0b\x32/.coresdk.workflow_activation.InitializeWorkflowH\x00\x12<\n\nfire_timer\x18\x02 \x01(\x0b\x32&.coresdk.workflow_activation.FireTimerH\x00\x12K\n\x12update_random_seed\x18\x04 \x01(\x0b\x32-.coresdk.workflow_activation.UpdateRandomSeedH\x00\x12\x44\n\x0equery_workflow\x18\x05 \x01(\x0b\x32*.coresdk.workflow_activation.QueryWorkflowH\x00\x12\x46\n\x0f\x63\x61ncel_workflow\x18\x06 \x01(\x0b\x32+.coresdk.workflow_activation.CancelWorkflowH\x00\x12\x46\n\x0fsignal_workflow\x18\x07 \x01(\x0b\x32+.coresdk.workflow_activation.SignalWorkflowH\x00\x12H\n\x10resolve_activity\x18\x08 \x01(\x0b\x32,.coresdk.workflow_activation.ResolveActivityH\x00\x12G\n\x10notify_has_patch\x18\t \x01(\x0b\x32+.coresdk.workflow_activation.NotifyHasPatchH\x00\x12q\n&resolve_child_workflow_execution_start\x18\n \x01(\x0b\x32?.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartH\x00\x12\x66\n resolve_child_workflow_execution\x18\x0b \x01(\x0b\x32:.coresdk.workflow_activation.ResolveChildWorkflowExecutionH\x00\x12\x66\n resolve_signal_external_workflow\x18\x0c \x01(\x0b\x32:.coresdk.workflow_activation.ResolveSignalExternalWorkflowH\x00\x12u\n(resolve_request_cancel_external_workflow\x18\r \x01(\x0b\x32\x41.coresdk.workflow_activation.ResolveRequestCancelExternalWorkflowH\x00\x12:\n\tdo_update\x18\x0e \x01(\x0b\x32%.coresdk.workflow_activation.DoUpdateH\x00\x12I\n\x11remove_from_cache\x18\x32 \x01(\x0b\x32,.coresdk.workflow_activation.RemoveFromCacheH\x00\x42\t\n\x07variant"\xe3\t\n\x12InitializeWorkflow\x12\x15\n\rworkflow_type\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\x12\x32\n\targuments\x18\x03 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x17\n\x0frandomness_seed\x18\x04 \x01(\x04\x12M\n\x07headers\x18\x05 \x03(\x0b\x32<.coresdk.workflow_activation.InitializeWorkflow.HeadersEntry\x12\x10\n\x08identity\x18\x06 \x01(\t\x12I\n\x14parent_workflow_info\x18\x07 \x01(\x0b\x32+.coresdk.common.NamespacedWorkflowExecution\x12=\n\x1aworkflow_execution_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x37\n\x14workflow_run_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x38\n\x15workflow_task_timeout\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x12\'\n\x1f\x63ontinued_from_execution_run_id\x18\x0b \x01(\t\x12J\n\x13\x63ontinued_initiator\x18\x0c \x01(\x0e\x32-.temporal.api.enums.v1.ContinueAsNewInitiator\x12;\n\x11\x63ontinued_failure\x18\r \x01(\x0b\x32 .temporal.api.failure.v1.Failure\x12@\n\x16last_completion_result\x18\x0e \x01(\x0b\x32 .temporal.api.common.v1.Payloads\x12\x1e\n\x16\x66irst_execution_run_id\x18\x0f \x01(\t\x12\x39\n\x0cretry_policy\x18\x10 \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x0f\n\x07\x61ttempt\x18\x11 \x01(\x05\x12\x15\n\rcron_schedule\x18\x12 \x01(\t\x12\x46\n"workflow_execution_expiration_time\x18\x13 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x45\n"cron_schedule_to_schedule_interval\x18\x14 \x01(\x0b\x32\x19.google.protobuf.Duration\x12*\n\x04memo\x18\x15 \x01(\x0b\x32\x1c.temporal.api.common.v1.Memo\x12\x43\n\x11search_attributes\x18\x16 \x01(\x0b\x32(.temporal.api.common.v1.SearchAttributes\x12.\n\nstart_time\x18\x17 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\x18\n\tFireTimer\x12\x0b\n\x03seq\x18\x01 \x01(\r"m\n\x0fResolveActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12;\n\x06result\x18\x02 \x01(\x0b\x32+.coresdk.activity_result.ActivityResolution\x12\x10\n\x08is_local\x18\x03 \x01(\x08"\xd1\x02\n"ResolveChildWorkflowExecutionStart\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12[\n\tsucceeded\x18\x02 \x01(\x0b\x32\x46.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartSuccessH\x00\x12X\n\x06\x66\x61iled\x18\x03 \x01(\x0b\x32\x46.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartFailureH\x00\x12]\n\tcancelled\x18\x04 \x01(\x0b\x32H.coresdk.workflow_activation.ResolveChildWorkflowExecutionStartCancelledH\x00\x42\x08\n\x06status";\n)ResolveChildWorkflowExecutionStartSuccess\x12\x0e\n\x06run_id\x18\x01 \x01(\t"\xa6\x01\n)ResolveChildWorkflowExecutionStartFailure\x12\x13\n\x0bworkflow_id\x18\x01 \x01(\t\x12\x15\n\rworkflow_type\x18\x02 \x01(\t\x12M\n\x05\x63\x61use\x18\x03 \x01(\x0e\x32>.coresdk.child_workflow.StartChildWorkflowExecutionFailedCause"`\n+ResolveChildWorkflowExecutionStartCancelled\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"i\n\x1dResolveChildWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12;\n\x06result\x18\x02 \x01(\x0b\x32+.coresdk.child_workflow.ChildWorkflowResult"+\n\x10UpdateRandomSeed\x12\x17\n\x0frandomness_seed\x18\x01 \x01(\x04"\x84\x02\n\rQueryWorkflow\x12\x10\n\x08query_id\x18\x01 \x01(\t\x12\x12\n\nquery_type\x18\x02 \x01(\t\x12\x32\n\targuments\x18\x03 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12H\n\x07headers\x18\x05 \x03(\x0b\x32\x37.coresdk.workflow_activation.QueryWorkflow.HeadersEntry\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"B\n\x0e\x43\x61ncelWorkflow\x12\x30\n\x07\x64\x65tails\x18\x01 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload"\x83\x02\n\x0eSignalWorkflow\x12\x13\n\x0bsignal_name\x18\x01 \x01(\t\x12.\n\x05input\x18\x02 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x10\n\x08identity\x18\x03 \x01(\t\x12I\n\x07headers\x18\x05 \x03(\x0b\x32\x38.coresdk.workflow_activation.SignalWorkflow.HeadersEntry\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01""\n\x0eNotifyHasPatch\x12\x10\n\x08patch_id\x18\x01 \x01(\t"_\n\x1dResolveSignalExternalWorkflow\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x31\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"f\n$ResolveRequestCancelExternalWorkflow\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x31\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"\xcb\x02\n\x08\x44oUpdate\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1c\n\x14protocol_instance_id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12.\n\x05input\x18\x04 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x43\n\x07headers\x18\x05 \x03(\x0b\x32\x32.coresdk.workflow_activation.DoUpdate.HeadersEntry\x12*\n\x04meta\x18\x06 \x01(\x0b\x32\x1c.temporal.api.update.v1.Meta\x12\x15\n\rrun_validator\x18\x07 \x01(\x08\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\xe0\x02\n\x0fRemoveFromCache\x12\x0f\n\x07message\x18\x01 \x01(\t\x12K\n\x06reason\x18\x02 \x01(\x0e\x32;.coresdk.workflow_activation.RemoveFromCache.EvictionReason"\xee\x01\n\x0e\x45victionReason\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0e\n\nCACHE_FULL\x10\x01\x12\x0e\n\nCACHE_MISS\x10\x02\x12\x12\n\x0eNONDETERMINISM\x10\x03\x12\r\n\tLANG_FAIL\x10\x04\x12\x12\n\x0eLANG_REQUESTED\x10\x05\x12\x12\n\x0eTASK_NOT_FOUND\x10\x06\x12\x15\n\x11UNHANDLED_COMMAND\x10\x07\x12\t\n\x05\x46\x41TAL\x10\x08\x12\x1f\n\x1bPAGINATION_OR_HISTORY_FETCH\x10\t\x12\x1d\n\x19WORKFLOW_EXECUTION_ENDING\x10\nB8\xea\x02\x35Temporalio::Internal::Bridge::Api::WorkflowActivationb\x06proto3' ) @@ -391,7 +391,7 @@ _DOUPDATE_HEADERSENTRY._serialized_start = 3162 _DOUPDATE_HEADERSENTRY._serialized_end = 3241 _REMOVEFROMCACHE._serialized_start = 5365 - _REMOVEFROMCACHE._serialized_end = 5686 + _REMOVEFROMCACHE._serialized_end = 5717 _REMOVEFROMCACHE_EVICTIONREASON._serialized_start = 5479 - _REMOVEFROMCACHE_EVICTIONREASON._serialized_end = 5686 + _REMOVEFROMCACHE_EVICTIONREASON._serialized_end = 5717 # @@protoc_insertion_point(module_scope) diff --git a/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.pyi b/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.pyi index a543959d4..db52a5024 100644 --- a/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.pyi +++ b/temporalio/bridge/proto/workflow_activation/workflow_activation_pb2.pyi @@ -1281,6 +1281,10 @@ class RemoveFromCache(google.protobuf.message.Message): """ PAGINATION_OR_HISTORY_FETCH: RemoveFromCache._EvictionReason.ValueType # 9 """Something went wrong attempting to fetch more history events.""" + WORKFLOW_EXECUTION_ENDING: RemoveFromCache._EvictionReason.ValueType # 10 + """The workflow is being completed with a terminal command and we sent the WFT completion + to server successfully. + """ class EvictionReason(_EvictionReason, metaclass=_EvictionReasonEnumTypeWrapper): ... UNSPECIFIED: RemoveFromCache.EvictionReason.ValueType # 0 @@ -1310,6 +1314,10 @@ class RemoveFromCache(google.protobuf.message.Message): """ PAGINATION_OR_HISTORY_FETCH: RemoveFromCache.EvictionReason.ValueType # 9 """Something went wrong attempting to fetch more history events.""" + WORKFLOW_EXECUTION_ENDING: RemoveFromCache.EvictionReason.ValueType # 10 + """The workflow is being completed with a terminal command and we sent the WFT completion + to server successfully. + """ MESSAGE_FIELD_NUMBER: builtins.int REASON_FIELD_NUMBER: builtins.int diff --git a/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.py b/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.py index 078dfa8a7..5c2cf25b8 100644 --- a/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.py +++ b/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.py @@ -28,6 +28,9 @@ from temporalio.api.failure.v1 import ( message_pb2 as temporal_dot_api_dot_failure_dot_v1_dot_message__pb2, ) +from temporalio.api.sdk.v1 import ( + user_metadata_pb2 as temporal_dot_api_dot_sdk_dot_v1_dot_user__metadata__pb2, +) from temporalio.bridge.proto.child_workflow import ( child_workflow_pb2 as temporal_dot_sdk_dot_core_dot_child__workflow_dot_child__workflow__pb2, ) @@ -36,7 +39,7 @@ ) DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n;temporal/sdk/core/workflow_commands/workflow_commands.proto\x12\x19\x63oresdk.workflow_commands\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a$temporal/api/common/v1/message.proto\x1a$temporal/api/enums/v1/workflow.proto\x1a%temporal/api/failure/v1/message.proto\x1a\x35temporal/sdk/core/child_workflow/child_workflow.proto\x1a%temporal/sdk/core/common/common.proto"\xf2\r\n\x0fWorkflowCommand\x12<\n\x0bstart_timer\x18\x01 \x01(\x0b\x32%.coresdk.workflow_commands.StartTimerH\x00\x12H\n\x11schedule_activity\x18\x02 \x01(\x0b\x32+.coresdk.workflow_commands.ScheduleActivityH\x00\x12\x42\n\x10respond_to_query\x18\x03 \x01(\x0b\x32&.coresdk.workflow_commands.QueryResultH\x00\x12S\n\x17request_cancel_activity\x18\x04 \x01(\x0b\x32\x30.coresdk.workflow_commands.RequestCancelActivityH\x00\x12>\n\x0c\x63\x61ncel_timer\x18\x05 \x01(\x0b\x32&.coresdk.workflow_commands.CancelTimerH\x00\x12[\n\x1b\x63omplete_workflow_execution\x18\x06 \x01(\x0b\x32\x34.coresdk.workflow_commands.CompleteWorkflowExecutionH\x00\x12S\n\x17\x66\x61il_workflow_execution\x18\x07 \x01(\x0b\x32\x30.coresdk.workflow_commands.FailWorkflowExecutionH\x00\x12g\n"continue_as_new_workflow_execution\x18\x08 \x01(\x0b\x32\x39.coresdk.workflow_commands.ContinueAsNewWorkflowExecutionH\x00\x12W\n\x19\x63\x61ncel_workflow_execution\x18\t \x01(\x0b\x32\x32.coresdk.workflow_commands.CancelWorkflowExecutionH\x00\x12\x45\n\x10set_patch_marker\x18\n \x01(\x0b\x32).coresdk.workflow_commands.SetPatchMarkerH\x00\x12`\n\x1estart_child_workflow_execution\x18\x0b \x01(\x0b\x32\x36.coresdk.workflow_commands.StartChildWorkflowExecutionH\x00\x12\x62\n\x1f\x63\x61ncel_child_workflow_execution\x18\x0c \x01(\x0b\x32\x37.coresdk.workflow_commands.CancelChildWorkflowExecutionH\x00\x12w\n*request_cancel_external_workflow_execution\x18\r \x01(\x0b\x32\x41.coresdk.workflow_commands.RequestCancelExternalWorkflowExecutionH\x00\x12h\n"signal_external_workflow_execution\x18\x0e \x01(\x0b\x32:.coresdk.workflow_commands.SignalExternalWorkflowExecutionH\x00\x12Q\n\x16\x63\x61ncel_signal_workflow\x18\x0f \x01(\x0b\x32/.coresdk.workflow_commands.CancelSignalWorkflowH\x00\x12S\n\x17schedule_local_activity\x18\x10 \x01(\x0b\x32\x30.coresdk.workflow_commands.ScheduleLocalActivityH\x00\x12^\n\x1drequest_cancel_local_activity\x18\x11 \x01(\x0b\x32\x35.coresdk.workflow_commands.RequestCancelLocalActivityH\x00\x12\x66\n!upsert_workflow_search_attributes\x18\x12 \x01(\x0b\x32\x39.coresdk.workflow_commands.UpsertWorkflowSearchAttributesH\x00\x12Y\n\x1amodify_workflow_properties\x18\x13 \x01(\x0b\x32\x33.coresdk.workflow_commands.ModifyWorkflowPropertiesH\x00\x12\x44\n\x0fupdate_response\x18\x14 \x01(\x0b\x32).coresdk.workflow_commands.UpdateResponseH\x00\x42\t\n\x07variant"S\n\nStartTimer\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x38\n\x15start_to_fire_timeout\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration"\x1a\n\x0b\x43\x61ncelTimer\x12\x0b\n\x03seq\x18\x01 \x01(\r"\x84\x06\n\x10ScheduleActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x13\n\x0b\x61\x63tivity_id\x18\x02 \x01(\t\x12\x15\n\ractivity_type\x18\x03 \x01(\t\x12\x12\n\ntask_queue\x18\x05 \x01(\t\x12I\n\x07headers\x18\x06 \x03(\x0b\x32\x38.coresdk.workflow_commands.ScheduleActivity.HeadersEntry\x12\x32\n\targuments\x18\x07 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12<\n\x19schedule_to_close_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12<\n\x19schedule_to_start_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x16start_to_close_timeout\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x34\n\x11heartbeat_timeout\x18\x0b \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x0cretry_policy\x18\x0c \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12N\n\x11\x63\x61ncellation_type\x18\r \x01(\x0e\x32\x33.coresdk.workflow_commands.ActivityCancellationType\x12\x1e\n\x16\x64o_not_eagerly_execute\x18\x0e \x01(\x08\x12;\n\x11versioning_intent\x18\x0f \x01(\x0e\x32 .coresdk.common.VersioningIntent\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\xee\x05\n\x15ScheduleLocalActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x13\n\x0b\x61\x63tivity_id\x18\x02 \x01(\t\x12\x15\n\ractivity_type\x18\x03 \x01(\t\x12\x0f\n\x07\x61ttempt\x18\x04 \x01(\r\x12:\n\x16original_schedule_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12N\n\x07headers\x18\x06 \x03(\x0b\x32=.coresdk.workflow_commands.ScheduleLocalActivity.HeadersEntry\x12\x32\n\targuments\x18\x07 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12<\n\x19schedule_to_close_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12<\n\x19schedule_to_start_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x16start_to_close_timeout\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x0cretry_policy\x18\x0b \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x38\n\x15local_retry_threshold\x18\x0c \x01(\x0b\x32\x19.google.protobuf.Duration\x12N\n\x11\x63\x61ncellation_type\x18\r \x01(\x0e\x32\x33.coresdk.workflow_commands.ActivityCancellationType\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"$\n\x15RequestCancelActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r")\n\x1aRequestCancelLocalActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r"\x9c\x01\n\x0bQueryResult\x12\x10\n\x08query_id\x18\x01 \x01(\t\x12<\n\tsucceeded\x18\x02 \x01(\x0b\x32\'.coresdk.workflow_commands.QuerySuccessH\x00\x12\x32\n\x06\x66\x61iled\x18\x03 \x01(\x0b\x32 .temporal.api.failure.v1.FailureH\x00\x42\t\n\x07variant"A\n\x0cQuerySuccess\x12\x31\n\x08response\x18\x01 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload"L\n\x19\x43ompleteWorkflowExecution\x12/\n\x06result\x18\x01 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload"J\n\x15\x46\x61ilWorkflowExecution\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"\xfb\x06\n\x1e\x43ontinueAsNewWorkflowExecution\x12\x15\n\rworkflow_type\x18\x01 \x01(\t\x12\x12\n\ntask_queue\x18\x02 \x01(\t\x12\x32\n\targuments\x18\x03 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x37\n\x14workflow_run_timeout\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x38\n\x15workflow_task_timeout\x18\x05 \x01(\x0b\x32\x19.google.protobuf.Duration\x12Q\n\x04memo\x18\x06 \x03(\x0b\x32\x43.coresdk.workflow_commands.ContinueAsNewWorkflowExecution.MemoEntry\x12W\n\x07headers\x18\x07 \x03(\x0b\x32\x46.coresdk.workflow_commands.ContinueAsNewWorkflowExecution.HeadersEntry\x12j\n\x11search_attributes\x18\x08 \x03(\x0b\x32O.coresdk.workflow_commands.ContinueAsNewWorkflowExecution.SearchAttributesEntry\x12\x39\n\x0cretry_policy\x18\t \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12;\n\x11versioning_intent\x18\n \x01(\x0e\x32 .coresdk.common.VersioningIntent\x1aL\n\tMemoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aX\n\x15SearchAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\x19\n\x17\x43\x61ncelWorkflowExecution"6\n\x0eSetPatchMarker\x12\x10\n\x08patch_id\x18\x01 \x01(\t\x12\x12\n\ndeprecated\x18\x02 \x01(\x08"\xe0\t\n\x1bStartChildWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x03 \x01(\t\x12\x15\n\rworkflow_type\x18\x04 \x01(\t\x12\x12\n\ntask_queue\x18\x05 \x01(\t\x12.\n\x05input\x18\x06 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12=\n\x1aworkflow_execution_timeout\x18\x07 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x37\n\x14workflow_run_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x38\n\x15workflow_task_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x46\n\x13parent_close_policy\x18\n \x01(\x0e\x32).coresdk.child_workflow.ParentClosePolicy\x12N\n\x18workflow_id_reuse_policy\x18\x0c \x01(\x0e\x32,.temporal.api.enums.v1.WorkflowIdReusePolicy\x12\x39\n\x0cretry_policy\x18\r \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x15\n\rcron_schedule\x18\x0e \x01(\t\x12T\n\x07headers\x18\x0f \x03(\x0b\x32\x43.coresdk.workflow_commands.StartChildWorkflowExecution.HeadersEntry\x12N\n\x04memo\x18\x10 \x03(\x0b\x32@.coresdk.workflow_commands.StartChildWorkflowExecution.MemoEntry\x12g\n\x11search_attributes\x18\x11 \x03(\x0b\x32L.coresdk.workflow_commands.StartChildWorkflowExecution.SearchAttributesEntry\x12P\n\x11\x63\x61ncellation_type\x18\x12 \x01(\x0e\x32\x35.coresdk.child_workflow.ChildWorkflowCancellationType\x12;\n\x11versioning_intent\x18\x13 \x01(\x0e\x32 .coresdk.common.VersioningIntent\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aL\n\tMemoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aX\n\x15SearchAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01":\n\x1c\x43\x61ncelChildWorkflowExecution\x12\x1a\n\x12\x63hild_workflow_seq\x18\x01 \x01(\r"\xa7\x01\n&RequestCancelExternalWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12I\n\x12workflow_execution\x18\x02 \x01(\x0b\x32+.coresdk.common.NamespacedWorkflowExecutionH\x00\x12\x1b\n\x11\x63hild_workflow_id\x18\x03 \x01(\tH\x00\x42\x08\n\x06target"\x8f\x03\n\x1fSignalExternalWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12I\n\x12workflow_execution\x18\x02 \x01(\x0b\x32+.coresdk.common.NamespacedWorkflowExecutionH\x00\x12\x1b\n\x11\x63hild_workflow_id\x18\x03 \x01(\tH\x00\x12\x13\n\x0bsignal_name\x18\x04 \x01(\t\x12-\n\x04\x61rgs\x18\x05 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12X\n\x07headers\x18\x06 \x03(\x0b\x32G.coresdk.workflow_commands.SignalExternalWorkflowExecution.HeadersEntry\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x42\x08\n\x06target"#\n\x14\x43\x61ncelSignalWorkflow\x12\x0b\n\x03seq\x18\x01 \x01(\r"\xe6\x01\n\x1eUpsertWorkflowSearchAttributes\x12j\n\x11search_attributes\x18\x01 \x03(\x0b\x32O.coresdk.workflow_commands.UpsertWorkflowSearchAttributes.SearchAttributesEntry\x1aX\n\x15SearchAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"O\n\x18ModifyWorkflowProperties\x12\x33\n\rupserted_memo\x18\x01 \x01(\x0b\x32\x1c.temporal.api.common.v1.Memo"\xd2\x01\n\x0eUpdateResponse\x12\x1c\n\x14protocol_instance_id\x18\x01 \x01(\t\x12*\n\x08\x61\x63\x63\x65pted\x18\x02 \x01(\x0b\x32\x16.google.protobuf.EmptyH\x00\x12\x34\n\x08rejected\x18\x03 \x01(\x0b\x32 .temporal.api.failure.v1.FailureH\x00\x12\x34\n\tcompleted\x18\x04 \x01(\x0b\x32\x1f.temporal.api.common.v1.PayloadH\x00\x42\n\n\x08response*X\n\x18\x41\x63tivityCancellationType\x12\x0e\n\nTRY_CANCEL\x10\x00\x12\x1f\n\x1bWAIT_CANCELLATION_COMPLETED\x10\x01\x12\x0b\n\x07\x41\x42\x41NDON\x10\x02\x42\x36\xea\x02\x33Temporalio::Internal::Bridge::Api::WorkflowCommandsb\x06proto3' + b'\n;temporal/sdk/core/workflow_commands/workflow_commands.proto\x12\x19\x63oresdk.workflow_commands\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a$temporal/api/common/v1/message.proto\x1a$temporal/api/enums/v1/workflow.proto\x1a%temporal/api/failure/v1/message.proto\x1a\'temporal/api/sdk/v1/user_metadata.proto\x1a\x35temporal/sdk/core/child_workflow/child_workflow.proto\x1a%temporal/sdk/core/common/common.proto"\xac\x0e\n\x0fWorkflowCommand\x12\x38\n\ruser_metadata\x18\x64 \x01(\x0b\x32!.temporal.api.sdk.v1.UserMetadata\x12<\n\x0bstart_timer\x18\x01 \x01(\x0b\x32%.coresdk.workflow_commands.StartTimerH\x00\x12H\n\x11schedule_activity\x18\x02 \x01(\x0b\x32+.coresdk.workflow_commands.ScheduleActivityH\x00\x12\x42\n\x10respond_to_query\x18\x03 \x01(\x0b\x32&.coresdk.workflow_commands.QueryResultH\x00\x12S\n\x17request_cancel_activity\x18\x04 \x01(\x0b\x32\x30.coresdk.workflow_commands.RequestCancelActivityH\x00\x12>\n\x0c\x63\x61ncel_timer\x18\x05 \x01(\x0b\x32&.coresdk.workflow_commands.CancelTimerH\x00\x12[\n\x1b\x63omplete_workflow_execution\x18\x06 \x01(\x0b\x32\x34.coresdk.workflow_commands.CompleteWorkflowExecutionH\x00\x12S\n\x17\x66\x61il_workflow_execution\x18\x07 \x01(\x0b\x32\x30.coresdk.workflow_commands.FailWorkflowExecutionH\x00\x12g\n"continue_as_new_workflow_execution\x18\x08 \x01(\x0b\x32\x39.coresdk.workflow_commands.ContinueAsNewWorkflowExecutionH\x00\x12W\n\x19\x63\x61ncel_workflow_execution\x18\t \x01(\x0b\x32\x32.coresdk.workflow_commands.CancelWorkflowExecutionH\x00\x12\x45\n\x10set_patch_marker\x18\n \x01(\x0b\x32).coresdk.workflow_commands.SetPatchMarkerH\x00\x12`\n\x1estart_child_workflow_execution\x18\x0b \x01(\x0b\x32\x36.coresdk.workflow_commands.StartChildWorkflowExecutionH\x00\x12\x62\n\x1f\x63\x61ncel_child_workflow_execution\x18\x0c \x01(\x0b\x32\x37.coresdk.workflow_commands.CancelChildWorkflowExecutionH\x00\x12w\n*request_cancel_external_workflow_execution\x18\r \x01(\x0b\x32\x41.coresdk.workflow_commands.RequestCancelExternalWorkflowExecutionH\x00\x12h\n"signal_external_workflow_execution\x18\x0e \x01(\x0b\x32:.coresdk.workflow_commands.SignalExternalWorkflowExecutionH\x00\x12Q\n\x16\x63\x61ncel_signal_workflow\x18\x0f \x01(\x0b\x32/.coresdk.workflow_commands.CancelSignalWorkflowH\x00\x12S\n\x17schedule_local_activity\x18\x10 \x01(\x0b\x32\x30.coresdk.workflow_commands.ScheduleLocalActivityH\x00\x12^\n\x1drequest_cancel_local_activity\x18\x11 \x01(\x0b\x32\x35.coresdk.workflow_commands.RequestCancelLocalActivityH\x00\x12\x66\n!upsert_workflow_search_attributes\x18\x12 \x01(\x0b\x32\x39.coresdk.workflow_commands.UpsertWorkflowSearchAttributesH\x00\x12Y\n\x1amodify_workflow_properties\x18\x13 \x01(\x0b\x32\x33.coresdk.workflow_commands.ModifyWorkflowPropertiesH\x00\x12\x44\n\x0fupdate_response\x18\x14 \x01(\x0b\x32).coresdk.workflow_commands.UpdateResponseH\x00\x42\t\n\x07variant"S\n\nStartTimer\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x38\n\x15start_to_fire_timeout\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration"\x1a\n\x0b\x43\x61ncelTimer\x12\x0b\n\x03seq\x18\x01 \x01(\r"\x84\x06\n\x10ScheduleActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x13\n\x0b\x61\x63tivity_id\x18\x02 \x01(\t\x12\x15\n\ractivity_type\x18\x03 \x01(\t\x12\x12\n\ntask_queue\x18\x05 \x01(\t\x12I\n\x07headers\x18\x06 \x03(\x0b\x32\x38.coresdk.workflow_commands.ScheduleActivity.HeadersEntry\x12\x32\n\targuments\x18\x07 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12<\n\x19schedule_to_close_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12<\n\x19schedule_to_start_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x16start_to_close_timeout\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x34\n\x11heartbeat_timeout\x18\x0b \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x0cretry_policy\x18\x0c \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12N\n\x11\x63\x61ncellation_type\x18\r \x01(\x0e\x32\x33.coresdk.workflow_commands.ActivityCancellationType\x12\x1e\n\x16\x64o_not_eagerly_execute\x18\x0e \x01(\x08\x12;\n\x11versioning_intent\x18\x0f \x01(\x0e\x32 .coresdk.common.VersioningIntent\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\xee\x05\n\x15ScheduleLocalActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x13\n\x0b\x61\x63tivity_id\x18\x02 \x01(\t\x12\x15\n\ractivity_type\x18\x03 \x01(\t\x12\x0f\n\x07\x61ttempt\x18\x04 \x01(\r\x12:\n\x16original_schedule_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12N\n\x07headers\x18\x06 \x03(\x0b\x32=.coresdk.workflow_commands.ScheduleLocalActivity.HeadersEntry\x12\x32\n\targuments\x18\x07 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12<\n\x19schedule_to_close_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12<\n\x19schedule_to_start_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x16start_to_close_timeout\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x39\n\x0cretry_policy\x18\x0b \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x38\n\x15local_retry_threshold\x18\x0c \x01(\x0b\x32\x19.google.protobuf.Duration\x12N\n\x11\x63\x61ncellation_type\x18\r \x01(\x0e\x32\x33.coresdk.workflow_commands.ActivityCancellationType\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"$\n\x15RequestCancelActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r")\n\x1aRequestCancelLocalActivity\x12\x0b\n\x03seq\x18\x01 \x01(\r"\x9c\x01\n\x0bQueryResult\x12\x10\n\x08query_id\x18\x01 \x01(\t\x12<\n\tsucceeded\x18\x02 \x01(\x0b\x32\'.coresdk.workflow_commands.QuerySuccessH\x00\x12\x32\n\x06\x66\x61iled\x18\x03 \x01(\x0b\x32 .temporal.api.failure.v1.FailureH\x00\x42\t\n\x07variant"A\n\x0cQuerySuccess\x12\x31\n\x08response\x18\x01 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload"L\n\x19\x43ompleteWorkflowExecution\x12/\n\x06result\x18\x01 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload"J\n\x15\x46\x61ilWorkflowExecution\x12\x31\n\x07\x66\x61ilure\x18\x01 \x01(\x0b\x32 .temporal.api.failure.v1.Failure"\xfb\x06\n\x1e\x43ontinueAsNewWorkflowExecution\x12\x15\n\rworkflow_type\x18\x01 \x01(\t\x12\x12\n\ntask_queue\x18\x02 \x01(\t\x12\x32\n\targuments\x18\x03 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12\x37\n\x14workflow_run_timeout\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x38\n\x15workflow_task_timeout\x18\x05 \x01(\x0b\x32\x19.google.protobuf.Duration\x12Q\n\x04memo\x18\x06 \x03(\x0b\x32\x43.coresdk.workflow_commands.ContinueAsNewWorkflowExecution.MemoEntry\x12W\n\x07headers\x18\x07 \x03(\x0b\x32\x46.coresdk.workflow_commands.ContinueAsNewWorkflowExecution.HeadersEntry\x12j\n\x11search_attributes\x18\x08 \x03(\x0b\x32O.coresdk.workflow_commands.ContinueAsNewWorkflowExecution.SearchAttributesEntry\x12\x39\n\x0cretry_policy\x18\t \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12;\n\x11versioning_intent\x18\n \x01(\x0e\x32 .coresdk.common.VersioningIntent\x1aL\n\tMemoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aX\n\x15SearchAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"\x19\n\x17\x43\x61ncelWorkflowExecution"6\n\x0eSetPatchMarker\x12\x10\n\x08patch_id\x18\x01 \x01(\t\x12\x12\n\ndeprecated\x18\x02 \x01(\x08"\xe0\t\n\x1bStartChildWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x03 \x01(\t\x12\x15\n\rworkflow_type\x18\x04 \x01(\t\x12\x12\n\ntask_queue\x18\x05 \x01(\t\x12.\n\x05input\x18\x06 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12=\n\x1aworkflow_execution_timeout\x18\x07 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x37\n\x14workflow_run_timeout\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x38\n\x15workflow_task_timeout\x18\t \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x46\n\x13parent_close_policy\x18\n \x01(\x0e\x32).coresdk.child_workflow.ParentClosePolicy\x12N\n\x18workflow_id_reuse_policy\x18\x0c \x01(\x0e\x32,.temporal.api.enums.v1.WorkflowIdReusePolicy\x12\x39\n\x0cretry_policy\x18\r \x01(\x0b\x32#.temporal.api.common.v1.RetryPolicy\x12\x15\n\rcron_schedule\x18\x0e \x01(\t\x12T\n\x07headers\x18\x0f \x03(\x0b\x32\x43.coresdk.workflow_commands.StartChildWorkflowExecution.HeadersEntry\x12N\n\x04memo\x18\x10 \x03(\x0b\x32@.coresdk.workflow_commands.StartChildWorkflowExecution.MemoEntry\x12g\n\x11search_attributes\x18\x11 \x03(\x0b\x32L.coresdk.workflow_commands.StartChildWorkflowExecution.SearchAttributesEntry\x12P\n\x11\x63\x61ncellation_type\x18\x12 \x01(\x0e\x32\x35.coresdk.child_workflow.ChildWorkflowCancellationType\x12;\n\x11versioning_intent\x18\x13 \x01(\x0e\x32 .coresdk.common.VersioningIntent\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aL\n\tMemoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x1aX\n\x15SearchAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01":\n\x1c\x43\x61ncelChildWorkflowExecution\x12\x1a\n\x12\x63hild_workflow_seq\x18\x01 \x01(\r"~\n&RequestCancelExternalWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12G\n\x12workflow_execution\x18\x02 \x01(\x0b\x32+.coresdk.common.NamespacedWorkflowExecution"\x8f\x03\n\x1fSignalExternalWorkflowExecution\x12\x0b\n\x03seq\x18\x01 \x01(\r\x12I\n\x12workflow_execution\x18\x02 \x01(\x0b\x32+.coresdk.common.NamespacedWorkflowExecutionH\x00\x12\x1b\n\x11\x63hild_workflow_id\x18\x03 \x01(\tH\x00\x12\x13\n\x0bsignal_name\x18\x04 \x01(\t\x12-\n\x04\x61rgs\x18\x05 \x03(\x0b\x32\x1f.temporal.api.common.v1.Payload\x12X\n\x07headers\x18\x06 \x03(\x0b\x32G.coresdk.workflow_commands.SignalExternalWorkflowExecution.HeadersEntry\x1aO\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01\x42\x08\n\x06target"#\n\x14\x43\x61ncelSignalWorkflow\x12\x0b\n\x03seq\x18\x01 \x01(\r"\xe6\x01\n\x1eUpsertWorkflowSearchAttributes\x12j\n\x11search_attributes\x18\x01 \x03(\x0b\x32O.coresdk.workflow_commands.UpsertWorkflowSearchAttributes.SearchAttributesEntry\x1aX\n\x15SearchAttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.temporal.api.common.v1.Payload:\x02\x38\x01"O\n\x18ModifyWorkflowProperties\x12\x33\n\rupserted_memo\x18\x01 \x01(\x0b\x32\x1c.temporal.api.common.v1.Memo"\xd2\x01\n\x0eUpdateResponse\x12\x1c\n\x14protocol_instance_id\x18\x01 \x01(\t\x12*\n\x08\x61\x63\x63\x65pted\x18\x02 \x01(\x0b\x32\x16.google.protobuf.EmptyH\x00\x12\x34\n\x08rejected\x18\x03 \x01(\x0b\x32 .temporal.api.failure.v1.FailureH\x00\x12\x34\n\tcompleted\x18\x04 \x01(\x0b\x32\x1f.temporal.api.common.v1.PayloadH\x00\x42\n\n\x08response*X\n\x18\x41\x63tivityCancellationType\x12\x0e\n\nTRY_CANCEL\x10\x00\x12\x1f\n\x1bWAIT_CANCELLATION_COMPLETED\x10\x01\x12\x0b\n\x07\x41\x42\x41NDON\x10\x02\x42\x36\xea\x02\x33Temporalio::Internal::Bridge::Api::WorkflowCommandsb\x06proto3' ) _ACTIVITYCANCELLATIONTYPE = DESCRIPTOR.enum_types_by_name["ActivityCancellationType"] @@ -479,70 +482,70 @@ _SIGNALEXTERNALWORKFLOWEXECUTION_HEADERSENTRY._serialized_options = b"8\001" _UPSERTWORKFLOWSEARCHATTRIBUTES_SEARCHATTRIBUTESENTRY._options = None _UPSERTWORKFLOWSEARCHATTRIBUTES_SEARCHATTRIBUTESENTRY._serialized_options = b"8\001" - _ACTIVITYCANCELLATIONTYPE._serialized_start = 7700 - _ACTIVITYCANCELLATIONTYPE._serialized_end = 7788 - _WORKFLOWCOMMAND._serialized_start = 394 - _WORKFLOWCOMMAND._serialized_end = 2172 - _STARTTIMER._serialized_start = 2174 - _STARTTIMER._serialized_end = 2257 - _CANCELTIMER._serialized_start = 2259 - _CANCELTIMER._serialized_end = 2285 - _SCHEDULEACTIVITY._serialized_start = 2288 - _SCHEDULEACTIVITY._serialized_end = 3060 - _SCHEDULEACTIVITY_HEADERSENTRY._serialized_start = 2981 - _SCHEDULEACTIVITY_HEADERSENTRY._serialized_end = 3060 - _SCHEDULELOCALACTIVITY._serialized_start = 3063 - _SCHEDULELOCALACTIVITY._serialized_end = 3813 - _SCHEDULELOCALACTIVITY_HEADERSENTRY._serialized_start = 2981 - _SCHEDULELOCALACTIVITY_HEADERSENTRY._serialized_end = 3060 - _REQUESTCANCELACTIVITY._serialized_start = 3815 - _REQUESTCANCELACTIVITY._serialized_end = 3851 - _REQUESTCANCELLOCALACTIVITY._serialized_start = 3853 - _REQUESTCANCELLOCALACTIVITY._serialized_end = 3894 - _QUERYRESULT._serialized_start = 3897 - _QUERYRESULT._serialized_end = 4053 - _QUERYSUCCESS._serialized_start = 4055 - _QUERYSUCCESS._serialized_end = 4120 - _COMPLETEWORKFLOWEXECUTION._serialized_start = 4122 - _COMPLETEWORKFLOWEXECUTION._serialized_end = 4198 - _FAILWORKFLOWEXECUTION._serialized_start = 4200 - _FAILWORKFLOWEXECUTION._serialized_end = 4274 - _CONTINUEASNEWWORKFLOWEXECUTION._serialized_start = 4277 - _CONTINUEASNEWWORKFLOWEXECUTION._serialized_end = 5168 - _CONTINUEASNEWWORKFLOWEXECUTION_MEMOENTRY._serialized_start = 4921 - _CONTINUEASNEWWORKFLOWEXECUTION_MEMOENTRY._serialized_end = 4997 - _CONTINUEASNEWWORKFLOWEXECUTION_HEADERSENTRY._serialized_start = 2981 - _CONTINUEASNEWWORKFLOWEXECUTION_HEADERSENTRY._serialized_end = 3060 - _CONTINUEASNEWWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_start = 5080 - _CONTINUEASNEWWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_end = 5168 - _CANCELWORKFLOWEXECUTION._serialized_start = 5170 - _CANCELWORKFLOWEXECUTION._serialized_end = 5195 - _SETPATCHMARKER._serialized_start = 5197 - _SETPATCHMARKER._serialized_end = 5251 - _STARTCHILDWORKFLOWEXECUTION._serialized_start = 5254 - _STARTCHILDWORKFLOWEXECUTION._serialized_end = 6502 - _STARTCHILDWORKFLOWEXECUTION_HEADERSENTRY._serialized_start = 2981 - _STARTCHILDWORKFLOWEXECUTION_HEADERSENTRY._serialized_end = 3060 - _STARTCHILDWORKFLOWEXECUTION_MEMOENTRY._serialized_start = 4921 - _STARTCHILDWORKFLOWEXECUTION_MEMOENTRY._serialized_end = 4997 - _STARTCHILDWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_start = 5080 - _STARTCHILDWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_end = 5168 - _CANCELCHILDWORKFLOWEXECUTION._serialized_start = 6504 - _CANCELCHILDWORKFLOWEXECUTION._serialized_end = 6562 - _REQUESTCANCELEXTERNALWORKFLOWEXECUTION._serialized_start = 6565 - _REQUESTCANCELEXTERNALWORKFLOWEXECUTION._serialized_end = 6732 - _SIGNALEXTERNALWORKFLOWEXECUTION._serialized_start = 6735 - _SIGNALEXTERNALWORKFLOWEXECUTION._serialized_end = 7134 - _SIGNALEXTERNALWORKFLOWEXECUTION_HEADERSENTRY._serialized_start = 2981 - _SIGNALEXTERNALWORKFLOWEXECUTION_HEADERSENTRY._serialized_end = 3060 - _CANCELSIGNALWORKFLOW._serialized_start = 7136 - _CANCELSIGNALWORKFLOW._serialized_end = 7171 - _UPSERTWORKFLOWSEARCHATTRIBUTES._serialized_start = 7174 - _UPSERTWORKFLOWSEARCHATTRIBUTES._serialized_end = 7404 - _UPSERTWORKFLOWSEARCHATTRIBUTES_SEARCHATTRIBUTESENTRY._serialized_start = 5080 - _UPSERTWORKFLOWSEARCHATTRIBUTES_SEARCHATTRIBUTESENTRY._serialized_end = 5168 - _MODIFYWORKFLOWPROPERTIES._serialized_start = 7406 - _MODIFYWORKFLOWPROPERTIES._serialized_end = 7485 - _UPDATERESPONSE._serialized_start = 7488 - _UPDATERESPONSE._serialized_end = 7698 + _ACTIVITYCANCELLATIONTYPE._serialized_start = 7757 + _ACTIVITYCANCELLATIONTYPE._serialized_end = 7845 + _WORKFLOWCOMMAND._serialized_start = 435 + _WORKFLOWCOMMAND._serialized_end = 2271 + _STARTTIMER._serialized_start = 2273 + _STARTTIMER._serialized_end = 2356 + _CANCELTIMER._serialized_start = 2358 + _CANCELTIMER._serialized_end = 2384 + _SCHEDULEACTIVITY._serialized_start = 2387 + _SCHEDULEACTIVITY._serialized_end = 3159 + _SCHEDULEACTIVITY_HEADERSENTRY._serialized_start = 3080 + _SCHEDULEACTIVITY_HEADERSENTRY._serialized_end = 3159 + _SCHEDULELOCALACTIVITY._serialized_start = 3162 + _SCHEDULELOCALACTIVITY._serialized_end = 3912 + _SCHEDULELOCALACTIVITY_HEADERSENTRY._serialized_start = 3080 + _SCHEDULELOCALACTIVITY_HEADERSENTRY._serialized_end = 3159 + _REQUESTCANCELACTIVITY._serialized_start = 3914 + _REQUESTCANCELACTIVITY._serialized_end = 3950 + _REQUESTCANCELLOCALACTIVITY._serialized_start = 3952 + _REQUESTCANCELLOCALACTIVITY._serialized_end = 3993 + _QUERYRESULT._serialized_start = 3996 + _QUERYRESULT._serialized_end = 4152 + _QUERYSUCCESS._serialized_start = 4154 + _QUERYSUCCESS._serialized_end = 4219 + _COMPLETEWORKFLOWEXECUTION._serialized_start = 4221 + _COMPLETEWORKFLOWEXECUTION._serialized_end = 4297 + _FAILWORKFLOWEXECUTION._serialized_start = 4299 + _FAILWORKFLOWEXECUTION._serialized_end = 4373 + _CONTINUEASNEWWORKFLOWEXECUTION._serialized_start = 4376 + _CONTINUEASNEWWORKFLOWEXECUTION._serialized_end = 5267 + _CONTINUEASNEWWORKFLOWEXECUTION_MEMOENTRY._serialized_start = 5020 + _CONTINUEASNEWWORKFLOWEXECUTION_MEMOENTRY._serialized_end = 5096 + _CONTINUEASNEWWORKFLOWEXECUTION_HEADERSENTRY._serialized_start = 3080 + _CONTINUEASNEWWORKFLOWEXECUTION_HEADERSENTRY._serialized_end = 3159 + _CONTINUEASNEWWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_start = 5179 + _CONTINUEASNEWWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_end = 5267 + _CANCELWORKFLOWEXECUTION._serialized_start = 5269 + _CANCELWORKFLOWEXECUTION._serialized_end = 5294 + _SETPATCHMARKER._serialized_start = 5296 + _SETPATCHMARKER._serialized_end = 5350 + _STARTCHILDWORKFLOWEXECUTION._serialized_start = 5353 + _STARTCHILDWORKFLOWEXECUTION._serialized_end = 6601 + _STARTCHILDWORKFLOWEXECUTION_HEADERSENTRY._serialized_start = 3080 + _STARTCHILDWORKFLOWEXECUTION_HEADERSENTRY._serialized_end = 3159 + _STARTCHILDWORKFLOWEXECUTION_MEMOENTRY._serialized_start = 5020 + _STARTCHILDWORKFLOWEXECUTION_MEMOENTRY._serialized_end = 5096 + _STARTCHILDWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_start = 5179 + _STARTCHILDWORKFLOWEXECUTION_SEARCHATTRIBUTESENTRY._serialized_end = 5267 + _CANCELCHILDWORKFLOWEXECUTION._serialized_start = 6603 + _CANCELCHILDWORKFLOWEXECUTION._serialized_end = 6661 + _REQUESTCANCELEXTERNALWORKFLOWEXECUTION._serialized_start = 6663 + _REQUESTCANCELEXTERNALWORKFLOWEXECUTION._serialized_end = 6789 + _SIGNALEXTERNALWORKFLOWEXECUTION._serialized_start = 6792 + _SIGNALEXTERNALWORKFLOWEXECUTION._serialized_end = 7191 + _SIGNALEXTERNALWORKFLOWEXECUTION_HEADERSENTRY._serialized_start = 3080 + _SIGNALEXTERNALWORKFLOWEXECUTION_HEADERSENTRY._serialized_end = 3159 + _CANCELSIGNALWORKFLOW._serialized_start = 7193 + _CANCELSIGNALWORKFLOW._serialized_end = 7228 + _UPSERTWORKFLOWSEARCHATTRIBUTES._serialized_start = 7231 + _UPSERTWORKFLOWSEARCHATTRIBUTES._serialized_end = 7461 + _UPSERTWORKFLOWSEARCHATTRIBUTES_SEARCHATTRIBUTESENTRY._serialized_start = 5179 + _UPSERTWORKFLOWSEARCHATTRIBUTES_SEARCHATTRIBUTESENTRY._serialized_end = 5267 + _MODIFYWORKFLOWPROPERTIES._serialized_start = 7463 + _MODIFYWORKFLOWPROPERTIES._serialized_end = 7542 + _UPDATERESPONSE._serialized_start = 7545 + _UPDATERESPONSE._serialized_end = 7755 # @@protoc_insertion_point(module_scope) diff --git a/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.pyi b/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.pyi index 53e8a8305..ca2959159 100644 --- a/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.pyi +++ b/temporalio/bridge/proto/workflow_commands/workflow_commands_pb2.pyi @@ -23,6 +23,7 @@ import google.protobuf.timestamp_pb2 import temporalio.api.common.v1.message_pb2 import temporalio.api.enums.v1.workflow_pb2 import temporalio.api.failure.v1.message_pb2 +import temporalio.api.sdk.v1.user_metadata_pb2 import temporalio.bridge.proto.child_workflow.child_workflow_pb2 import temporalio.bridge.proto.common.common_pb2 @@ -76,6 +77,7 @@ global___ActivityCancellationType = ActivityCancellationType class WorkflowCommand(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor + USER_METADATA_FIELD_NUMBER: builtins.int START_TIMER_FIELD_NUMBER: builtins.int SCHEDULE_ACTIVITY_FIELD_NUMBER: builtins.int RESPOND_TO_QUERY_FIELD_NUMBER: builtins.int @@ -97,6 +99,12 @@ class WorkflowCommand(google.protobuf.message.Message): MODIFY_WORKFLOW_PROPERTIES_FIELD_NUMBER: builtins.int UPDATE_RESPONSE_FIELD_NUMBER: builtins.int @property + def user_metadata(self) -> temporalio.api.sdk.v1.user_metadata_pb2.UserMetadata: + """User metadata that may or may not be persisted into history depending on the command type. + Lang layers are expected to expose the setting of the internals of this metadata on a + per-command basis where applicable. + """ + @property def start_timer(self) -> global___StartTimer: ... @property def schedule_activity(self) -> global___ScheduleActivity: ... @@ -151,6 +159,8 @@ class WorkflowCommand(google.protobuf.message.Message): def __init__( self, *, + user_metadata: temporalio.api.sdk.v1.user_metadata_pb2.UserMetadata + | None = ..., start_timer: global___StartTimer | None = ..., schedule_activity: global___ScheduleActivity | None = ..., respond_to_query: global___QueryResult | None = ..., @@ -221,6 +231,8 @@ class WorkflowCommand(google.protobuf.message.Message): b"update_response", "upsert_workflow_search_attributes", b"upsert_workflow_search_attributes", + "user_metadata", + b"user_metadata", "variant", b"variant", ], @@ -268,6 +280,8 @@ class WorkflowCommand(google.protobuf.message.Message): b"update_response", "upsert_workflow_search_attributes", b"upsert_workflow_search_attributes", + "user_metadata", + b"user_metadata", "variant", b"variant", ], @@ -1340,59 +1354,40 @@ class CancelChildWorkflowExecution(google.protobuf.message.Message): global___CancelChildWorkflowExecution = CancelChildWorkflowExecution class RequestCancelExternalWorkflowExecution(google.protobuf.message.Message): - """Request cancellation of an external workflow execution (which may be a started child)""" + """Request cancellation of an external workflow execution. For cancellation of a child workflow, + prefer `CancelChildWorkflowExecution` instead, as it guards against cancel-before-start issues. + """ DESCRIPTOR: google.protobuf.descriptor.Descriptor SEQ_FIELD_NUMBER: builtins.int WORKFLOW_EXECUTION_FIELD_NUMBER: builtins.int - CHILD_WORKFLOW_ID_FIELD_NUMBER: builtins.int seq: builtins.int """Lang's incremental sequence number, used as the operation identifier""" @property def workflow_execution( self, ) -> temporalio.bridge.proto.common.common_pb2.NamespacedWorkflowExecution: - """A specific workflow instance""" - child_workflow_id: builtins.str - """The desired target must be a child of the issuing workflow, and this is its workflow id""" + """The workflow instance being targeted""" def __init__( self, *, seq: builtins.int = ..., workflow_execution: temporalio.bridge.proto.common.common_pb2.NamespacedWorkflowExecution | None = ..., - child_workflow_id: builtins.str = ..., ) -> None: ... def HasField( self, field_name: typing_extensions.Literal[ - "child_workflow_id", - b"child_workflow_id", - "target", - b"target", - "workflow_execution", - b"workflow_execution", + "workflow_execution", b"workflow_execution" ], ) -> builtins.bool: ... def ClearField( self, field_name: typing_extensions.Literal[ - "child_workflow_id", - b"child_workflow_id", - "seq", - b"seq", - "target", - b"target", - "workflow_execution", - b"workflow_execution", + "seq", b"seq", "workflow_execution", b"workflow_execution" ], ) -> None: ... - def WhichOneof( - self, oneof_group: typing_extensions.Literal["target", b"target"] - ) -> ( - typing_extensions.Literal["workflow_execution", "child_workflow_id"] | None - ): ... global___RequestCancelExternalWorkflowExecution = RequestCancelExternalWorkflowExecution diff --git a/temporalio/bridge/sdk-core b/temporalio/bridge/sdk-core index 9879b552e..75800f1c3 160000 --- a/temporalio/bridge/sdk-core +++ b/temporalio/bridge/sdk-core @@ -1 +1 @@ -Subproject commit 9879b552e4acda73236a3593d91b7ccc052c5c36 +Subproject commit 75800f1c399b1d2b1e86b4cfd6fa81f913a085b6 diff --git a/temporalio/client.py b/temporalio/client.py index 8974c7c6f..4ce9e28dc 100644 --- a/temporalio/client.py +++ b/temporalio/client.py @@ -999,8 +999,6 @@ async def create_schedule( backfill=backfill, memo=memo, search_attributes=search_attributes, - static_summary=static_summary, - static_details=static_details, rpc_metadata=rpc_metadata, rpc_timeout=rpc_timeout, ) @@ -5122,9 +5120,11 @@ async def start_workflow( temporalio.converter.encode_search_attributes( input.search_attributes, req.search_attributes ) - req.user_metadata = self._client.data_converter._encode_user_metadata( + metadata = await self._client.data_converter._encode_user_metadata( input.static_summary, input.static_details ) + if metadata is not None: + req.user_metadata.CopyFrom(metadata) if input.start_delay is not None: req.workflow_start_delay.FromTimedelta(input.start_delay) if input.headers is not None: diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index 9100df525..179f16611 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -2371,8 +2371,9 @@ def _apply_schedule_command( command.schedule_activity.versioning_intent = ( self._input.versioning_intent._to_proto() ) + # TODO: Needs async conversion to happen somewhere # if self._input.summary: - # command. + # command.user_metadata = self._instance._payload_converter if isinstance(self._input, StartLocalActivityInput): if self._input.local_retry_threshold: command.schedule_local_activity.local_retry_threshold.FromTimedelta( From 52881bb7499a7b6fd2d31fdd7799ff48384495d3 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Tue, 10 Dec 2024 15:23:17 -0800 Subject: [PATCH 04/13] Implement user metadata in workflow commands --- temporalio/client.py | 6 +- temporalio/converter.py | 23 +++++-- temporalio/worker/_interceptor.py | 2 + temporalio/worker/_workflow_instance.py | 89 ++++++++++++++++++++----- temporalio/workflow.py | 82 ++++++++++++++++++++++- tests/test_client.py | 2 + tests/worker/test_workflow.py | 81 +++++++++++++++++++++- 7 files changed, 258 insertions(+), 27 deletions(-) diff --git a/temporalio/client.py b/temporalio/client.py index 4ce9e28dc..698f12a67 100644 --- a/temporalio/client.py +++ b/temporalio/client.py @@ -3282,8 +3282,8 @@ class ScheduleActionStartWorkflow(ScheduleAction): """This is deprecated and is only present in case existing untyped attributes already exist for update. This should never be used when creating.""" - static_summary: Optional[str] - static_details: Optional[str] + static_summary: Optional[Union[str, temporalio.api.common.v1.Payload]] + static_details: Optional[Union[str, temporalio.api.common.v1.Payload]] headers: Optional[Mapping[str, temporalio.api.common.v1.Payload]] @@ -3450,6 +3450,8 @@ def __init__( if pair.key.name in self.untyped_search_attributes: # We know this is mutable here del self.untyped_search_attributes[pair.key.name] # type: ignore + self.static_summary = raw_info.user_metadata.summary + self.static_details = raw_info.user_metadata.details else: if not id: raise ValueError("ID required") diff --git a/temporalio/converter.py b/temporalio/converter.py index 9fb7886d4..b850a8923 100644 --- a/temporalio/converter.py +++ b/temporalio/converter.py @@ -1086,13 +1086,26 @@ async def encode_wrapper( return temporalio.api.common.v1.Payloads(payloads=(await self.encode(values))) async def _encode_user_metadata( - self, summary: Optional[str], details: Optional[str] + self, + summary: Optional[Union[str, temporalio.api.common.v1.Payload]], + details: Optional[Union[str, temporalio.api.common.v1.Payload]], ) -> Optional[temporalio.api.sdk.v1.UserMetadata]: if summary is None and details is None: return None + enc_summary = None + enc_details = None + if summary is not None: + if isinstance(summary, str): + enc_summary = (await self.encode([summary]))[0] + else: + enc_summary = summary + if details is not None: + if isinstance(details, str): + enc_details = (await self.encode([details]))[0] + else: + enc_details = details return temporalio.api.sdk.v1.UserMetadata( - summary=None if summary is None else (await self.encode([summary]))[0], - details=None if details is None else (await self.encode([details]))[0], + summary=enc_summary, details=enc_details ) async def decode_wrapper( @@ -1218,7 +1231,7 @@ def encode_typed_search_attribute_value( if isinstance(value, Sequence): for v in value: if not isinstance(v, str): - raise TypeError(f"All values of a keyword list must be strings") + raise TypeError("All values of a keyword list must be strings") # Convert value payload = default().payload_converter.to_payload(value) # Set metadata type @@ -1256,7 +1269,7 @@ def encode_search_attribute_values( ) elif val_type and type(v) is not val_type: raise TypeError( - f"Search attribute values must have the same type for the same key" + "Search attribute values must have the same type for the same key" ) elif not val_type: val_type = type(v) diff --git a/temporalio/worker/_interceptor.py b/temporalio/worker/_interceptor.py index bccff0109..6606e5ec2 100644 --- a/temporalio/worker/_interceptor.py +++ b/temporalio/worker/_interceptor.py @@ -276,6 +276,8 @@ class StartChildWorkflowInput: ] headers: Mapping[str, temporalio.api.common.v1.Payload] versioning_intent: Optional[VersioningIntent] + static_summary: Optional[str] + static_details: Optional[str] # The types may be absent arg_types: Optional[List[Type]] ret_type: Optional[Type] diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index 179f16611..5386ddb5b 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -315,6 +315,10 @@ def __init__(self, det: WorkflowInstanceDetails) -> None: # For tracking the thread this workflow is running on (primarily for deadlock situations) self._current_thread_id: Optional[int] = None + # Since timer creation often happens indirectly through asyncio, we need some place to + # temporarily store options for timers created by, ex `wait_condition`. + self._next_timer_options: Optional[_TimerOptions] = None + def get_thread_id(self) -> Optional[int]: return self._current_thread_id @@ -1222,6 +1226,8 @@ async def workflow_start_child_workflow( ] ], versioning_intent: Optional[temporalio.workflow.VersioningIntent], + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> temporalio.workflow.ChildWorkflowHandle[Any, Any]: # Use definition if callable name: str @@ -1259,6 +1265,8 @@ async def workflow_start_child_workflow( arg_types=arg_types, ret_type=ret_type, versioning_intent=versioning_intent, + static_summary=static_summary, + static_details=static_details, ) ) @@ -1418,11 +1426,23 @@ def workflow_upsert_search_attributes( ) async def workflow_wait_condition( - self, fn: Callable[[], bool], *, timeout: Optional[float] = None + self, + fn: Callable[[], bool], + *, + timeout: Optional[float] = None, + timeout_summary: Optional[str] = None, ) -> None: self._assert_not_read_only("wait condition") fut = self.create_future() self._conditions.append((fn, fut)) + user_metadata = ( + temporalio.api.sdk.v1.UserMetadata( + summary=self._payload_converter.to_payload(timeout_summary) + ) + if timeout_summary + else None + ) + self._next_timer_options = _TimerOptions(user_metadata=user_metadata) await asyncio.wait_for(fut, timeout) #### Calls from outbound impl #### @@ -1956,10 +1976,39 @@ def _enhanced_stack_trace(self) -> temporalio.api.sdk.v1.EnhancedStackTrace: ) return est + def _timer_impl( + self, + delay: float, + callback: Callable[..., Any], + *args: Any, + context: Optional[contextvars.Context] = None, + options: Optional[_TimerOptions] = None, + ): + self._assert_not_read_only("schedule timer") + # Delay must be positive + if delay < 0: + raise RuntimeError("Attempting to schedule timer with negative delay") + + # Create, schedule, and return + seq = self._next_seq("timer") + # If options aren't explicitly passed, attempt to fetch them from the class field, + # erasing them afterward. Support callers who cannot call this directly because they + # rely on asyncio functions. + if options is None: + options = self._next_timer_options + self._next_timer_options = None + handle = _TimerHandle( + seq, self.time() + delay, options, callback, args, self, context + ) + handle._apply_start_command(self._add_command(), delay) + self._pending_timers[seq] = handle + return handle + #### asyncio.AbstractEventLoop function impls #### # These are in the order defined in CPython's impl of the base class. Many # functions are intentionally not implemented/supported. + # TODO/Review: This doesn't appear to implement any base class fn and isn't called anywhere? def _timer_handle_cancelled(self, handle: asyncio.TimerHandle) -> None: if not isinstance(handle, _TimerHandle): raise TypeError("Expected Temporal timer handle") @@ -1987,17 +2036,7 @@ def call_later( *args: Any, context: Optional[contextvars.Context] = None, ) -> asyncio.TimerHandle: - self._assert_not_read_only("schedule timer") - # Delay must be positive - if delay < 0: - raise RuntimeError("Attempting to schedule timer with negative delay") - - # Create, schedule, and return - seq = self._next_seq("timer") - handle = _TimerHandle(seq, self.time() + delay, callback, args, self, context) - handle._apply_start_command(self._add_command(), delay) - self._pending_timers[seq] = handle - return handle + return self._timer_impl(delay, callback, *args, context=context) def call_at( self, @@ -2219,11 +2258,17 @@ def start_local_activity( return self._instance._outbound_schedule_activity(input) +@dataclass(frozen=True) +class _TimerOptions: + user_metadata: Optional[temporalio.api.sdk.v1.UserMetadata] = None + + class _TimerHandle(asyncio.TimerHandle): def __init__( self, seq: int, when: float, + options: Optional[_TimerOptions], callback: Callable[..., Any], args: Sequence[Any], loop: asyncio.AbstractEventLoop, @@ -2231,6 +2276,7 @@ def __init__( ) -> None: super().__init__(when, callback, args, loop, context) self._seq = seq + self._options = options def _apply_start_command( self, @@ -2238,6 +2284,8 @@ def _apply_start_command( delay: float, ) -> None: command.start_timer.seq = self._seq + if self._options and self._options.user_metadata: + command.user_metadata.CopyFrom(self._options.user_metadata) command.start_timer.start_to_fire_timeout.FromNanoseconds(int(delay * 1e9)) def _apply_cancel_command( @@ -2371,9 +2419,10 @@ def _apply_schedule_command( command.schedule_activity.versioning_intent = ( self._input.versioning_intent._to_proto() ) - # TODO: Needs async conversion to happen somewhere - # if self._input.summary: - # command.user_metadata = self._instance._payload_converter + if self._input.summary: + command.user_metadata.summary.CopyFrom( + self._instance._payload_converter.to_payload(self._input.summary) + ) if isinstance(self._input, StartLocalActivityInput): if self._input.local_retry_threshold: command.schedule_local_activity.local_retry_threshold.FromTimedelta( @@ -2384,8 +2433,6 @@ def _apply_schedule_command( command.schedule_local_activity.original_schedule_time.CopyFrom( local_backoff.original_schedule_time ) - # TODO(cretz): Remove when https://github.com/temporalio/sdk-core/issues/316 fixed - command.schedule_local_activity.retry_policy.SetInParent() def _apply_cancel_command( self, @@ -2511,6 +2558,14 @@ def _apply_start_command(self) -> None: ) if self._input.versioning_intent: v.versioning_intent = self._input.versioning_intent._to_proto() + if self._input.static_summary: + command.user_metadata.summary.CopyFrom( + self._instance._payload_converter.to_payload(self._input.static_summary) + ) + if self._input.static_details: + command.user_metadata.details.CopyFrom( + self._instance._payload_converter.to_payload(self._input.static_details) + ) # If request cancel external, result does _not_ have seq def _apply_cancel_command( diff --git a/temporalio/workflow.py b/temporalio/workflow.py index 78248a316..c30c59b6d 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -686,6 +686,8 @@ async def workflow_start_child_workflow( ] ], versioning_intent: Optional[VersioningIntent], + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> ChildWorkflowHandle[Any, Any]: ... @abstractmethod @@ -717,7 +719,11 @@ def workflow_upsert_search_attributes( @abstractmethod async def workflow_wait_condition( - self, fn: Callable[[], bool], *, timeout: Optional[float] = None + self, + fn: Callable[[], bool], + *, + timeout: Optional[float] = None, + timeout_summary: Optional[str] = None, ) -> None: ... @@ -1093,7 +1099,10 @@ def uuid4() -> uuid.UUID: async def wait_condition( - fn: Callable[[], bool], *, timeout: Optional[Union[timedelta, float]] = None + fn: Callable[[], bool], + *, + timeout: Optional[Union[timedelta, float]] = None, + timeout_summary: Optional[str] = None, ) -> None: """Wait on a callback to become true. @@ -1104,10 +1113,14 @@ async def wait_condition( fn: Non-async callback that accepts no parameters and returns a boolean. timeout: Optional number of seconds to wait until throwing :py:class:`asyncio.TimeoutError`. + timeout_summary: Optional simple string identifying the timer (created if `timeout` is + present) that may be visible in UI/CLI. While it can be normal text, it is best to treat + as a timer ID. """ await _Runtime.current().workflow_wait_condition( fn, timeout=timeout.total_seconds() if isinstance(timeout, timedelta) else timeout, + timeout_summary=timeout_summary, ) @@ -1941,6 +1954,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -1958,6 +1972,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -1976,6 +1991,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -1994,6 +2010,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2012,6 +2029,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2030,6 +2048,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2050,6 +2069,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> Any: ... @@ -2068,6 +2088,7 @@ async def execute_activity( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> Any: """Start an activity and wait for completion. @@ -2088,6 +2109,7 @@ async def execute_activity( cancellation_type=cancellation_type, activity_id=activity_id, versioning_intent=versioning_intent, + summary=summary, ) @@ -2105,6 +2127,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2122,6 +2145,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2140,6 +2164,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2158,6 +2183,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2176,6 +2202,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2194,6 +2221,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2211,6 +2239,7 @@ def start_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[Any]: """Start an activity from a callable class. @@ -2229,6 +2258,7 @@ def start_activity_class( cancellation_type=cancellation_type, activity_id=activity_id, versioning_intent=versioning_intent, + summary=summary, ) @@ -2246,6 +2276,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2263,6 +2294,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2281,6 +2313,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2299,6 +2332,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2317,6 +2351,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2335,6 +2370,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2352,6 +2388,7 @@ async def execute_activity_class( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> Any: """Start an activity from a callable class and wait for completion. @@ -2370,6 +2407,7 @@ async def execute_activity_class( cancellation_type=cancellation_type, activity_id=activity_id, versioning_intent=versioning_intent, + summary=summary, ) @@ -2387,6 +2425,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2404,6 +2443,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2422,6 +2462,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2440,6 +2481,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2458,6 +2500,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2476,6 +2519,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[ReturnType]: ... @@ -2493,6 +2537,7 @@ def start_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ActivityHandle[Any]: """Start an activity from a method. @@ -2511,6 +2556,7 @@ def start_activity_method( cancellation_type=cancellation_type, activity_id=activity_id, versioning_intent=versioning_intent, + summary=summary, ) @@ -2528,6 +2574,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2545,6 +2592,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2563,6 +2611,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2581,6 +2630,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2599,6 +2649,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2617,6 +2668,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -2634,6 +2686,7 @@ async def execute_activity_method( cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, activity_id: Optional[str] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> Any: """Start an activity from a method and wait for completion. @@ -2654,6 +2707,7 @@ async def execute_activity_method( cancellation_type=cancellation_type, activity_id=activity_id, versioning_intent=versioning_intent, + summary=summary, ) @@ -3656,6 +3710,8 @@ async def start_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> ChildWorkflowHandle[SelfType, ReturnType]: ... @@ -3682,6 +3738,8 @@ async def start_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> ChildWorkflowHandle[SelfType, ReturnType]: ... @@ -3708,6 +3766,8 @@ async def start_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> ChildWorkflowHandle[SelfType, ReturnType]: ... @@ -3736,6 +3796,8 @@ async def start_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> ChildWorkflowHandle[Any, Any]: ... @@ -3762,6 +3824,8 @@ async def start_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + static_summary: Optional[str] = None, + static_details: Optional[str] = None, ) -> ChildWorkflowHandle[Any, Any]: """Start a child workflow and return its handle. @@ -3791,6 +3855,12 @@ async def start_child_workflow( form of this is DEPRECATED. versioning_intent: When using the Worker Versioning feature, specifies whether this Child Workflow should run on a worker with a compatible Build Id or not. + static_summary: A single-line fixed summary for this child workflow execution that may appear + in the UI/CLI. This can be in single-line Temporal markdown format. + static_details: General fixed details for this child workflow execution that may appear in + UI/CLI. This can be in Temporal markdown format and can span multiple lines. This is + a fixed value on the workflow that cannot be updated. For details that can be + updated, use `Workflow.CurrentDetails` within the workflow. Returns: A workflow handle to the started/existing workflow. @@ -3813,6 +3883,8 @@ async def start_child_workflow( memo=memo, search_attributes=search_attributes, versioning_intent=versioning_intent, + static_summary=static_summary, + static_details=static_details, ) @@ -3838,6 +3910,7 @@ async def execute_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -3864,6 +3937,7 @@ async def execute_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -3890,6 +3964,7 @@ async def execute_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> ReturnType: ... @@ -3918,6 +3993,7 @@ async def execute_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> Any: ... @@ -3944,6 +4020,7 @@ async def execute_child_workflow( ] ] = None, versioning_intent: Optional[VersioningIntent] = None, + summary: Optional[str] = None, ) -> Any: """Start a child workflow and wait for completion. @@ -3969,6 +4046,7 @@ async def execute_child_workflow( memo=memo, search_attributes=search_attributes, versioning_intent=versioning_intent, + static_summary=summary, ) return await handle diff --git a/tests/test_client.py b/tests/test_client.py index e1bc2448a..dcdc51055 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -780,6 +780,8 @@ async def test_schedule_basics( pytest.skip("Older proto library cannot compare repeated fields") await assert_no_schedules(client) + # TODO: Metadata + # Create a schedule with a lot of stuff schedule = Schedule( action=ScheduleActionStartWorkflow( diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 91eb8c49e..0e96edc22 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -3018,7 +3018,9 @@ def __init__(self) -> None: async def run(self) -> None: # Force timeout, ignore, wait again try: - await workflow.wait_condition(lambda: self._done, timeout=0.01) + await workflow.wait_condition( + lambda: self._done, timeout=0.01, timeout_summary="hi!" + ) raise RuntimeError("Expected timeout") except asyncio.TimeoutError: pass @@ -6169,3 +6171,80 @@ async def test_workflow_run_sees_workflow_init(client: Client): task_queue=worker.task_queue, ) assert workflow_result == "hello, world" + + +@workflow.defn +class UserMetadataWorkflow: + def __init__(self) -> None: + self._done = False + self._waiting = False + + @workflow.run + async def run(self) -> None: + await workflow.execute_activity( + say_hello, + "Enchi", + start_to_close_timeout=timedelta(seconds=5), + summary="meow", + ) + # Force timeout, ignore, wait again + try: + await workflow.wait_condition( + lambda: self._done, timeout=0.01, timeout_summary="hi!" + ) + raise RuntimeError("Expected timeout") + except asyncio.TimeoutError: + pass + self._waiting = True + # workflow.upd + await workflow.wait_condition(lambda: self._done) + + @workflow.signal + def done(self) -> None: + self._done = True + + @workflow.query + def waiting(self) -> bool: + return self._waiting + + +async def test_user_metadata_is_set(client: Client): + async with new_worker(client, UserMetadataWorkflow, activities=[say_hello]) as worker: + handle = await client.start_workflow( + UserMetadataWorkflow.run, + id=f"workflow-{uuid.uuid4()}", + task_queue=worker.task_queue, + static_summary="cool workflow bro", + static_details="xtremely detailed", + ) + + # Wait until it's waiting, then send the signal + async def waiting() -> bool: + return await handle.query(UserMetadataWorkflow.waiting) + + await assert_eq_eventually(True, waiting) + await handle.signal(UserMetadataWorkflow.done) + + # Ensure metadatas are present in history + resp = await client.workflow_service.get_workflow_execution_history( + GetWorkflowExecutionHistoryRequest( + namespace=client.namespace, + execution=WorkflowExecution(workflow_id=handle.id), + ) + ) + for event in resp.history.events: + if event.event_type == EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: + assert "cool workflow bro" in PayloadConverter.default.from_payload( + event.user_metadata.summary + ) + assert "xtremely detailed" in PayloadConverter.default.from_payload( + event.user_metadata.details + ) + elif event.event_type == EventType.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: + assert "meow" in PayloadConverter.default.from_payload( + event.user_metadata.summary + ) + elif event.event_type == EventType.EVENT_TYPE_TIMER_STARTED: + assert "hi!" in PayloadConverter.default.from_payload( + event.user_metadata.summary + ) From d5cc5d31b283e0fbfad68c312b1ffbd15d7b1142 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 11 Dec 2024 14:27:54 -0800 Subject: [PATCH 05/13] Add metadata query stuff --- temporalio/worker/_workflow_instance.py | 57 ++++++++++++++++ temporalio/workflow.py | 91 ++++++++++++++++++++++--- tests/test_workflow.py | 33 +++++---- tests/worker/test_workflow.py | 39 +++++++++-- 4 files changed, 196 insertions(+), 24 deletions(-) diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index 5386ddb5b..0d09e53b1 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -282,6 +282,16 @@ def __init__(self, det: WorkflowInstanceDetails) -> None: ret_type=temporalio.api.sdk.v1.EnhancedStackTrace, ) + self._queries["__temporal_workflow_metadata"] = ( + temporalio.workflow._QueryDefinition( + name="__temporal_workflow_metadata", + fn=self._temporal_workflow_metadata, + is_method=False, + arg_types=[], + ret_type=temporalio.api.sdk.v1.WorkflowMetadata, + ) + ) + # Maintain buffered signals for later-added dynamic handlers self._buffered_signals: Dict[ str, List[temporalio.bridge.proto.workflow_activation.SignalWorkflow] @@ -319,6 +329,10 @@ def __init__(self, det: WorkflowInstanceDetails) -> None: # temporarily store options for timers created by, ex `wait_condition`. self._next_timer_options: Optional[_TimerOptions] = None + # The current details (as opposed to static details on workflow start), returned in the + # metadata query + self._current_details: str = "" + def get_thread_id(self) -> Optional[int]: return self._current_thread_id @@ -1445,6 +1459,13 @@ async def workflow_wait_condition( self._next_timer_options = _TimerOptions(user_metadata=user_metadata) await asyncio.wait_for(fut, timeout) + def workflow_get_current_details(self) -> str: + return self._current_details + + def workflow_set_current_details(self, description): + self._assert_not_read_only("set current details") + self._current_details = description + #### Calls from outbound impl #### # These are in alphabetical order and all start with "_outbound_". @@ -1976,6 +1997,42 @@ def _enhanced_stack_trace(self) -> temporalio.api.sdk.v1.EnhancedStackTrace: ) return est + def _temporal_workflow_metadata(self) -> temporalio.api.sdk.v1.WorkflowMetadata: + query_definitions = [ + temporalio.api.sdk.v1.WorkflowInteractionDefinition( + name=qd.name if qd.name is not None else "", + description=qd.description if qd.description is not None else "", + ) + for qd in self._queries.values() + ] + query_definitions.sort(key=lambda qd: qd.name) + signal_definitions = [ + temporalio.api.sdk.v1.WorkflowInteractionDefinition( + name=sd.name if sd.name is not None else "", + description=sd.description if sd.description is not None else "", + ) + for sd in self._signals.values() + ] + signal_definitions.sort(key=lambda sd: sd.name) + update_definitions = [ + temporalio.api.sdk.v1.WorkflowInteractionDefinition( + name=ud.name if ud.name is not None else "", + description=ud.description if ud.description is not None else "", + ) + for ud in self._updates.values() + ] + update_definitions.sort(key=lambda ud: ud.name) + wf_def = temporalio.api.sdk.v1.WorkflowDefinition( + type=self._info.workflow_type, + query_definitions=query_definitions, + signal_definitions=signal_definitions, + update_definitions=update_definitions, + ) + cur_details = self.workflow_get_current_details() + return temporalio.api.sdk.v1.WorkflowMetadata( + definition=wf_def, current_details=cur_details + ) + def _timer_impl( self, delay: float, diff --git a/temporalio/workflow.py b/temporalio/workflow.py index c30c59b6d..85fb7b5f4 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -234,6 +234,7 @@ def signal( *, name: str, unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, ) -> Callable[ [CallableSyncOrAsyncReturnNoneType], CallableSyncOrAsyncReturnNoneType ]: ... @@ -244,6 +245,17 @@ def signal( *, dynamic: Literal[True], unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, +) -> Callable[ + [CallableSyncOrAsyncReturnNoneType], CallableSyncOrAsyncReturnNoneType +]: ... + + +@overload +def signal( + *, + description: str, + unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, ) -> Callable[ [CallableSyncOrAsyncReturnNoneType], CallableSyncOrAsyncReturnNoneType ]: ... @@ -255,6 +267,7 @@ def signal( name: Optional[str] = None, dynamic: Optional[bool] = False, unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, ): """Decorator for a workflow signal method. @@ -277,6 +290,7 @@ def signal( present. unfinished_policy: Actions taken if a workflow terminates with a running instance of this handler. + description: A short description of the signal that may appear in the UI/CLI. """ def decorator( @@ -291,6 +305,7 @@ def decorator( fn=fn, is_method=True, unfinished_policy=unfinished_policy, + description=description, ) setattr(fn, "__temporal_signal_definition", defn) if defn.dynamic_vararg: @@ -314,11 +329,19 @@ def query(fn: CallableType) -> CallableType: ... @overload -def query(*, name: str) -> Callable[[CallableType], CallableType]: ... +def query( + *, name: str, description: Optional[str] = None +) -> Callable[[CallableType], CallableType]: ... @overload -def query(*, dynamic: Literal[True]) -> Callable[[CallableType], CallableType]: ... +def query( + *, dynamic: Literal[True], description: Optional[str] = None +) -> Callable[[CallableType], CallableType]: ... + + +@overload +def query(*, description: str) -> Callable[[CallableType], CallableType]: ... def query( @@ -326,6 +349,7 @@ def query( *, name: Optional[str] = None, dynamic: Optional[bool] = False, + description: Optional[str] = None, ): """Decorator for a workflow query method. @@ -346,18 +370,27 @@ def query( ``Sequence[RawValue]``. An older form of this accepted vararg parameters which will now warn. Cannot be present when ``name`` is present. + description: A short description of the query that may appear in the UI/CLI. """ - def with_name( - name: Optional[str], fn: CallableType, *, bypass_async_check: bool = False + def decorator( + name: Optional[str], + description: Optional[str], + fn: CallableType, + *, + bypass_async_check: bool = False, ) -> CallableType: + if not name and not dynamic: + name = fn.__name__ if not bypass_async_check and inspect.iscoroutinefunction(fn): warnings.warn( "Queries as async def functions are deprecated", DeprecationWarning, stacklevel=2, ) - defn = _QueryDefinition(name=name, fn=fn, is_method=True) + defn = _QueryDefinition( + name=name, fn=fn, is_method=True, description=description + ) setattr(fn, "__temporal_query_definition", defn) if defn.dynamic_vararg: warnings.warn( @@ -367,10 +400,10 @@ def with_name( ) return fn - if name is not None or dynamic: + if name is not None or dynamic or description: if name is not None and dynamic: raise RuntimeError("Cannot provide name and dynamic boolean") - return partial(with_name, name) + return partial(decorator, name, description) if fn is None: raise RuntimeError("Cannot create query without function or name or dynamic") if inspect.iscoroutinefunction(fn): @@ -379,7 +412,7 @@ def with_name( DeprecationWarning, stacklevel=2, ) - return with_name(fn.__name__, fn, bypass_async_check=True) + return decorator(fn.__name__, description, fn, bypass_async_check=True) @dataclass(frozen=True) @@ -726,6 +759,12 @@ async def workflow_wait_condition( timeout_summary: Optional[str] = None, ) -> None: ... + @abstractmethod + def workflow_get_current_details(self) -> str: ... + + @abstractmethod + def workflow_set_current_details(self, description): ... + _current_update_info: contextvars.ContextVar[UpdateInfo] = contextvars.ContextVar( "__temporal_current_update_info" @@ -837,6 +876,24 @@ def memo_value( return _Runtime.current().workflow_memo_value(key, default, type_hint=type_hint) +def get_current_details() -> str: + """Get the current details of the workflow which may appear in the UI/CLI. + Unlike static details set at start, this value can be updated throughout + the life of the workflow and is independent of the static details. + This can be in Temporal markdown format and can span multiple lines. + """ + return _Runtime.current().workflow_get_current_details() + + +def set_current_details(description: str) -> None: + """Set the current details of the workflow which may appear in the UI/CLI. + Unlike static details set at start, this value can be updated throughout + the life of the workflow and is independent of the static details. + This can be in Temporal markdown format and can span multiple lines. + """ + _Runtime.current().workflow_set_current_details(description) + + def metric_meter() -> temporalio.common.MetricMeter: """Get the metric meter for the current workflow. @@ -983,6 +1040,7 @@ def update( def update( *, unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, ) -> Callable[ [Callable[MultiParamSpec, ReturnType]], UpdateMethodMultiParam[MultiParamSpec, ReturnType], @@ -994,6 +1052,7 @@ def update( *, name: str, unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, ) -> Callable[ [Callable[MultiParamSpec, ReturnType]], UpdateMethodMultiParam[MultiParamSpec, ReturnType], @@ -1005,6 +1064,16 @@ def update( *, dynamic: Literal[True], unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, +) -> Callable[ + [Callable[MultiParamSpec, ReturnType]], + UpdateMethodMultiParam[MultiParamSpec, ReturnType], +]: ... + + +@overload +def update( + *, description: str ) -> Callable[ [Callable[MultiParamSpec, ReturnType]], UpdateMethodMultiParam[MultiParamSpec, ReturnType], @@ -1017,6 +1086,7 @@ def update( name: Optional[str] = None, dynamic: Optional[bool] = False, unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, ): """Decorator for a workflow update handler method. @@ -1046,6 +1116,7 @@ def update( present. unfinished_policy: Actions taken if a workflow terminates with a running instance of this handler. + description: A short description of the update that may appear in the UI/CLI. """ def decorator( @@ -1060,6 +1131,7 @@ def decorator( fn=fn, is_method=True, unfinished_policy=unfinished_policy, + description=description, ) if defn.dynamic_vararg: raise RuntimeError( @@ -1573,6 +1645,7 @@ class _SignalDefinition: unfinished_policy: HandlerUnfinishedPolicy = ( HandlerUnfinishedPolicy.WARN_AND_ABANDON ) + description: Optional[str] = None # Types loaded on post init if None arg_types: Optional[List[Type]] = None dynamic_vararg: bool = False @@ -1620,6 +1693,7 @@ class _QueryDefinition: name: Optional[str] fn: Callable[..., Any] is_method: bool + description: Optional[str] = None # Types loaded on post init if both are None arg_types: Optional[List[Type]] = None ret_type: Optional[Type] = None @@ -1657,6 +1731,7 @@ class _UpdateDefinition: unfinished_policy: HandlerUnfinishedPolicy = ( HandlerUnfinishedPolicy.WARN_AND_ABANDON ) + description: Optional[str] = None # Types loaded on post init if None arg_types: Optional[List[Type]] = None ret_type: Optional[Type] = None diff --git a/tests/test_workflow.py b/tests/test_workflow.py index d4a5b45e1..1c3478a91 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -36,11 +36,11 @@ async def run(self, name: str) -> str: def signal1(self): pass - @workflow.signal(name="signal-custom") + @workflow.signal(name="signal-custom", description="fun") def signal2(self): pass - @workflow.signal(dynamic=True) + @workflow.signal(dynamic=True, description="boo") def signal3(self, name: str, args: Sequence[RawValue]): pass @@ -48,11 +48,11 @@ def signal3(self, name: str, args: Sequence[RawValue]): def query1(self): pass - @workflow.query(name="query-custom") + @workflow.query(name="query-custom", description="qd") def query2(self): pass - @workflow.query(dynamic=True) + @workflow.query(dynamic=True, description="dqd") def query3(self, name: str, args: Sequence[RawValue]): pass @@ -60,11 +60,11 @@ def query3(self, name: str, args: Sequence[RawValue]): def update1(self): pass - @workflow.update(name="update-custom") + @workflow.update(name="update-custom", description="ud") def update2(self): pass - @workflow.update(dynamic=True) + @workflow.update(dynamic=True, description="dud") def update3(self, name: str, args: Sequence[RawValue]): pass @@ -82,10 +82,13 @@ def test_workflow_defn_good(): name="signal1", fn=GoodDefn.signal1, is_method=True ), "signal-custom": workflow._SignalDefinition( - name="signal-custom", fn=GoodDefn.signal2, is_method=True + name="signal-custom", + fn=GoodDefn.signal2, + is_method=True, + description="fun", ), None: workflow._SignalDefinition( - name=None, fn=GoodDefn.signal3, is_method=True + name=None, fn=GoodDefn.signal3, is_method=True, description="boo" ), "base_signal": workflow._SignalDefinition( name="base_signal", fn=GoodDefnBase.base_signal, is_method=True @@ -96,10 +99,13 @@ def test_workflow_defn_good(): name="query1", fn=GoodDefn.query1, is_method=True ), "query-custom": workflow._QueryDefinition( - name="query-custom", fn=GoodDefn.query2, is_method=True + name="query-custom", + fn=GoodDefn.query2, + is_method=True, + description="qd", ), None: workflow._QueryDefinition( - name=None, fn=GoodDefn.query3, is_method=True + name=None, fn=GoodDefn.query3, is_method=True, description="dqd" ), "base_query": workflow._QueryDefinition( name="base_query", fn=GoodDefnBase.base_query, is_method=True @@ -110,10 +116,13 @@ def test_workflow_defn_good(): name="update1", fn=GoodDefn.update1, is_method=True ), "update-custom": workflow._UpdateDefinition( - name="update-custom", fn=GoodDefn.update2, is_method=True + name="update-custom", + fn=GoodDefn.update2, + is_method=True, + description="ud", ), None: workflow._UpdateDefinition( - name=None, fn=GoodDefn.update3, is_method=True + name=None, fn=GoodDefn.update3, is_method=True, description="dud" ), "base_update": workflow._UpdateDefinition( name="base_update", fn=GoodDefnBase.base_update, is_method=True diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 0e96edc22..1fdbb8cc3 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -6196,20 +6196,26 @@ async def run(self) -> None: except asyncio.TimeoutError: pass self._waiting = True - # workflow.upd + workflow.set_current_details("such detail") await workflow.wait_condition(lambda: self._done) - @workflow.signal + @workflow.signal(description="sdesc") def done(self) -> None: self._done = True - @workflow.query + @workflow.query(description="qdesc") def waiting(self) -> bool: return self._waiting + @workflow.update(description="udesc") + def some_update(self): + pass + async def test_user_metadata_is_set(client: Client): - async with new_worker(client, UserMetadataWorkflow, activities=[say_hello]) as worker: + async with new_worker( + client, UserMetadataWorkflow, activities=[say_hello] + ) as worker: handle = await client.start_workflow( UserMetadataWorkflow.run, id=f"workflow-{uuid.uuid4()}", @@ -6223,6 +6229,31 @@ async def waiting() -> bool: return await handle.query(UserMetadataWorkflow.waiting) await assert_eq_eventually(True, waiting) + + md_query: temporalio.api.sdk.v1.WorkflowMetadata = await handle.query( + "__temporal_workflow_metadata", + result_type=temporalio.api.sdk.v1.WorkflowMetadata, + ) + matched_q = [ + q for q in md_query.definition.query_definitions if q.name == "waiting" + ] + assert len(matched_q) == 1 + assert matched_q[0].description == "qdesc" + + matched_u = [ + u for u in md_query.definition.update_definitions if u.name == "some_update" + ] + assert len(matched_u) == 1 + assert matched_u[0].description == "udesc" + + matched_s = [ + s for s in md_query.definition.signal_definitions if s.name == "done" + ] + assert len(matched_s) == 1 + assert matched_s[0].description == "sdesc" + + assert md_query.current_details == "such detail" + await handle.signal(UserMetadataWorkflow.done) # Ensure metadatas are present in history From d842dadfa216a44387a40bf766b544af074b9f8c Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 11 Dec 2024 16:29:43 -0800 Subject: [PATCH 06/13] Add top level sleep fn --- temporalio/worker/_workflow_instance.py | 14 ++++++++++ temporalio/workflow.py | 34 +++++++++++++++++-------- tests/worker/test_workflow.py | 7 +++-- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index 0d09e53b1..25945b469 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -1439,6 +1439,20 @@ def workflow_upsert_search_attributes( else [update.value] ) + async def workflow_sleep( + self, duration: float, *, summary: Optional[str] = None + ) -> None: + user_metadata = ( + temporalio.api.sdk.v1.UserMetadata( + summary=self._payload_converter.to_payload(summary) + ) + if summary + else None + ) + self._timer_impl( + duration, lambda _: None, options=_TimerOptions(user_metadata=user_metadata) + ) + async def workflow_wait_condition( self, fn: Callable[[], bool], diff --git a/temporalio/workflow.py b/temporalio/workflow.py index 85fb7b5f4..b38ff788f 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -750,6 +750,11 @@ def workflow_upsert_search_attributes( ], ) -> None: ... + @abstractmethod + async def workflow_sleep( + self, duration: float, *, summary: Optional[str] = None + ) -> None: ... + @abstractmethod async def workflow_wait_condition( self, @@ -1071,15 +1076,6 @@ def update( ]: ... -@overload -def update( - *, description: str -) -> Callable[ - [Callable[MultiParamSpec, ReturnType]], - UpdateMethodMultiParam[MultiParamSpec, ReturnType], -]: ... - - def update( fn: Optional[CallableSyncOrAsyncType] = None, *, @@ -1170,6 +1166,24 @@ def uuid4() -> uuid.UUID: return uuid.UUID(bytes=random().getrandbits(16 * 8).to_bytes(16, "big"), version=4) +async def sleep( + duration: Union[float, timedelta], *, summary: Optional[str] = None +) -> None: + """Sleep for the given duration. + + Args: + duration: Duration to sleep in seconds or as a timedelta. + summary: A single-line fixed summary for this timer that may appear in UI/CLI. + This can be in single-line Temporal markdown format. + """ + await _Runtime.current().workflow_sleep( + duration=duration.total_seconds() + if isinstance(duration, timedelta) + else duration, + summary=summary, + ) + + async def wait_condition( fn: Callable[[], bool], *, @@ -1992,7 +2006,7 @@ def start_activity( need to. Contact Temporal before setting this value. versioning_intent: When using the Worker Versioning feature, specifies whether this Activity should run on a worker with a compatible Build Id or not. - summary: Gets or sets a single-line fixed summary for this activity that may appear in UI/CLI. + summary: A single-line fixed summary for this activity that may appear in UI/CLI. This can be in single-line Temporal markdown format. Returns: diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 1fdbb8cc3..b93af9f4c 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -6195,6 +6195,7 @@ async def run(self) -> None: raise RuntimeError("Expected timeout") except asyncio.TimeoutError: pass + await workflow.sleep(0.01, summary="timer2") self._waiting = True workflow.set_current_details("such detail") await workflow.wait_condition(lambda: self._done) @@ -6263,6 +6264,7 @@ async def waiting() -> bool: execution=WorkflowExecution(workflow_id=handle.id), ) ) + timer_summs = set() for event in resp.history.events: if event.event_type == EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: assert "cool workflow bro" in PayloadConverter.default.from_payload( @@ -6276,6 +6278,7 @@ async def waiting() -> bool: event.user_metadata.summary ) elif event.event_type == EventType.EVENT_TYPE_TIMER_STARTED: - assert "hi!" in PayloadConverter.default.from_payload( - event.user_metadata.summary + timer_summs.add( + PayloadConverter.default.from_payload(event.user_metadata.summary) ) + assert timer_summs == {"hi!", "timer2"} From 544b9823496dc4bdbca1dcbdffde074d6c6841f5 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 11 Dec 2024 16:46:27 -0800 Subject: [PATCH 07/13] Add to workflow describe --- temporalio/client.py | 15 +++++++++++++-- tests/worker/test_workflow.py | 5 +++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/temporalio/client.py b/temporalio/client.py index 698f12a67..dbc2779c6 100644 --- a/temporalio/client.py +++ b/temporalio/client.py @@ -2411,16 +2411,27 @@ class WorkflowExecutionDescription(WorkflowExecution): raw_description: temporalio.api.workflowservice.v1.DescribeWorkflowExecutionResponse """Underlying protobuf description.""" + static_summary: Optional[str] + """Gets the single-line fixed summary for this workflow execution that may appear in + UI/CLI. This can be in single-line Temporal markdown format.""" + static_details: Optional[str] + """Gets the general fixed details for this workflow execution that may appear in UI/CLI. + This can be in Temporal markdown format and can span multiple lines.""" @staticmethod - def _from_raw_description( + async def _from_raw_description( description: temporalio.api.workflowservice.v1.DescribeWorkflowExecutionResponse, converter: temporalio.converter.DataConverter, ) -> WorkflowExecutionDescription: + (summ, deets) = await converter._decode_user_metadata( + description.execution_config.user_metadata + ) return WorkflowExecutionDescription._from_raw_info( # type: ignore description.workflow_execution_info, converter, raw_description=description, + static_summary=summ, + static_details=deets, ) @@ -5199,7 +5210,7 @@ async def cancel_workflow(self, input: CancelWorkflowInput) -> None: async def describe_workflow( self, input: DescribeWorkflowInput ) -> WorkflowExecutionDescription: - return WorkflowExecutionDescription._from_raw_description( + return await WorkflowExecutionDescription._from_raw_description( await self._client.workflow_service.describe_workflow_execution( temporalio.api.workflowservice.v1.DescribeWorkflowExecutionRequest( namespace=self._client.namespace, diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index b93af9f4c..1ce3c1c17 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -46,6 +46,7 @@ from temporalio.api.workflowservice.v1 import ( GetWorkflowExecutionHistoryRequest, ResetStickyTaskQueueRequest, + DescribeWorkflowExecutionRequest ) from temporalio.bridge.proto.workflow_activation import WorkflowActivation from temporalio.bridge.proto.workflow_completion import WorkflowActivationCompletion @@ -6282,3 +6283,7 @@ async def waiting() -> bool: PayloadConverter.default.from_payload(event.user_metadata.summary) ) assert timer_summs == {"hi!", "timer2"} + + describe_r = await handle.describe() + assert describe_r.static_summary == "cool workflow bro" + assert describe_r.static_details == "xtremely detailed" From de38b9d64b26b65699244a9ab29914b7203347e7 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Wed, 11 Dec 2024 17:17:12 -0800 Subject: [PATCH 08/13] Fix tests --- temporalio/client.py | 12 ++++++++++-- temporalio/converter.py | 4 ++-- tests/test_client.py | 13 +++++++++++-- tests/worker/test_workflow.py | 8 ++++---- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/temporalio/client.py b/temporalio/client.py index dbc2779c6..a0d802f2a 100644 --- a/temporalio/client.py +++ b/temporalio/client.py @@ -3461,8 +3461,16 @@ def __init__( if pair.key.name in self.untyped_search_attributes: # We know this is mutable here del self.untyped_search_attributes[pair.key.name] # type: ignore - self.static_summary = raw_info.user_metadata.summary - self.static_details = raw_info.user_metadata.details + self.static_summary = ( + raw_info.user_metadata.summary + if raw_info.HasField("user_metadata") and raw_info.user_metadata.summary + else None + ) + self.static_details = ( + raw_info.user_metadata.details + if raw_info.HasField("user_metadata") and raw_info.user_metadata.details + else None + ) else: if not id: raise ValueError("ID required") diff --git a/temporalio/converter.py b/temporalio/converter.py index b850a8923..7d9eba4be 100644 --- a/temporalio/converter.py +++ b/temporalio/converter.py @@ -1144,10 +1144,10 @@ async def _decode_user_metadata( return None, None return ( None - if metadata.summary is None + if not metadata.HasField("summary") else (await self.decode([metadata.summary]))[0], None - if metadata.details is None + if not metadata.HasField("details") else (await self.decode([metadata.details]))[0], ) diff --git a/tests/test_client.py b/tests/test_client.py index dcdc51055..dc0128f38 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -780,8 +780,6 @@ async def test_schedule_basics( pytest.skip("Older proto library cannot compare repeated fields") await assert_no_schedules(client) - # TODO: Metadata - # Create a schedule with a lot of stuff schedule = Schedule( action=ScheduleActionStartWorkflow( @@ -794,6 +792,8 @@ async def test_schedule_basics( task_timeout=timedelta(hours=3), retry_policy=RetryPolicy(maximum_attempts=20), memo={"memokey1": "memoval1"}, + static_summary="summary", + static_details="details", ), spec=ScheduleSpec( calendars=[ @@ -864,6 +864,15 @@ async def test_schedule_basics( day_of_week=(ScheduleRange(1),), ) ) + # Summary & description are encoded + assert schedule.action.static_summary + assert schedule.action.static_details + schedule.action.static_summary = ( + await DataConverter.default.encode([schedule.action.static_summary]) + )[0] + schedule.action.static_details = ( + await DataConverter.default.encode([schedule.action.static_details]) + )[0] # Describe it and confirm desc = await handle.describe() diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 1ce3c1c17..9a1d1cca3 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -44,9 +44,9 @@ from temporalio.api.failure.v1 import Failure from temporalio.api.sdk.v1 import EnhancedStackTrace from temporalio.api.workflowservice.v1 import ( + DescribeWorkflowExecutionRequest, GetWorkflowExecutionHistoryRequest, ResetStickyTaskQueueRequest, - DescribeWorkflowExecutionRequest ) from temporalio.bridge.proto.workflow_activation import WorkflowActivation from temporalio.bridge.proto.workflow_completion import WorkflowActivationCompletion @@ -505,7 +505,7 @@ async def test_workflow_signal_and_query_errors(client: Client): await handle.query("non-existent query") assert str(rpc_err.value) == ( "Query handler for 'non-existent query' expected but not found," - " known queries: [__enhanced_stack_trace __stack_trace bad_query other_query]" + " known queries: [__enhanced_stack_trace __stack_trace __temporal_workflow_metadata bad_query other_query]" ) @@ -1553,12 +1553,12 @@ async def test_workflow_with_custom_runner(client: Client): task_queue=worker.task_queue, ) assert result == "Hello, Temporal!" - # Confirm first activation and last completion + # Confirm first activation and last non-eviction-reply completion assert ( runner._pairs[0][0].jobs[0].initialize_workflow.workflow_type == "HelloWorkflow" ) assert ( - runner._pairs[-1][-1] + runner._pairs[-2][-1] .successful.commands[0] .complete_workflow_execution.result.data == b'"Hello, Temporal!"' From 3673a44457bdc08538ad0eb4cbc2f64eb3e3baac Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 12 Dec 2024 10:45:43 -0800 Subject: [PATCH 09/13] Review fixes --- README.md | 2 +- temporalio/client.py | 53 ++++++++++++++++++++++--- temporalio/converter.py | 38 ------------------ temporalio/worker/_workflow_instance.py | 40 ++++++++++--------- temporalio/workflow.py | 13 +----- tests/worker/test_workflow.py | 18 +++++++++ 6 files changed, 89 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 2206e108a..533bae74f 100644 --- a/README.md +++ b/README.md @@ -680,7 +680,7 @@ Some things to note about the above code: #### Timers -* A timer is represented by normal `asyncio.sleep()` +* A timer is represented by normal `asyncio.sleep()` or a `workflow.sleep()` call * Timers are also implicitly started on any `asyncio` calls with timeouts (e.g. `asyncio.wait_for`) * Timers are Temporal server timers, not local ones, so sub-second resolution rarely has value * Calls that use a specific point in time, e.g. `call_at` or `timeout_at`, should be based on the current loop time diff --git a/temporalio/client.py b/temporalio/client.py index a0d802f2a..765c00714 100644 --- a/temporalio/client.py +++ b/temporalio/client.py @@ -26,6 +26,7 @@ Mapping, Optional, Sequence, + Tuple, Type, Union, cast, @@ -43,6 +44,7 @@ import temporalio.api.failure.v1 import temporalio.api.history.v1 import temporalio.api.schedule.v1 +import temporalio.api.sdk.v1 import temporalio.api.taskqueue.v1 import temporalio.api.update.v1 import temporalio.api.workflow.v1 @@ -2423,8 +2425,8 @@ async def _from_raw_description( description: temporalio.api.workflowservice.v1.DescribeWorkflowExecutionResponse, converter: temporalio.converter.DataConverter, ) -> WorkflowExecutionDescription: - (summ, deets) = await converter._decode_user_metadata( - description.execution_config.user_metadata + (summ, deets) = await _decode_user_metadata( + converter, description.execution_config.user_metadata ) return WorkflowExecutionDescription._from_raw_info( # type: ignore description.workflow_execution_info, @@ -3547,8 +3549,8 @@ async def _to_proto( for k, v in self.memo.items() }, ), - user_metadata=await client.data_converter._encode_user_metadata( - self.static_summary, self.static_details + user_metadata=await _encode_user_metadata( + client.data_converter, self.static_summary, self.static_details ), ), ) @@ -5141,8 +5143,8 @@ async def start_workflow( temporalio.converter.encode_search_attributes( input.search_attributes, req.search_attributes ) - metadata = await self._client.data_converter._encode_user_metadata( - input.static_summary, input.static_details + metadata = await _encode_user_metadata( + self._client.data_converter, input.static_summary, input.static_details ) if metadata is not None: req.user_metadata.CopyFrom(metadata) @@ -6373,3 +6375,42 @@ def api_key(self, value: Optional[str]) -> None: # Update config and perform update self.service_client.config.api_key = value self.service_client.update_api_key(value) + + +async def _encode_user_metadata( + converter: temporalio.converter.DataConverter, + summary: Optional[Union[str, temporalio.api.common.v1.Payload]], + details: Optional[Union[str, temporalio.api.common.v1.Payload]], +) -> Optional[temporalio.api.sdk.v1.UserMetadata]: + if summary is None and details is None: + return None + enc_summary = None + enc_details = None + if summary is not None: + if isinstance(summary, str): + enc_summary = (await converter.encode([summary]))[0] + else: + enc_summary = summary + if details is not None: + if isinstance(details, str): + enc_details = (await converter.encode([details]))[0] + else: + enc_details = details + return temporalio.api.sdk.v1.UserMetadata(summary=enc_summary, details=enc_details) + + +async def _decode_user_metadata( + converter: temporalio.converter.DataConverter, + metadata: Optional[temporalio.api.sdk.v1.UserMetadata], +) -> Tuple[Optional[str], Optional[str]]: + """Returns (summary, details)""" + if metadata is None: + return None, None + return ( + None + if not metadata.HasField("summary") + else (await converter.decode([metadata.summary]))[0], + None + if not metadata.HasField("details") + else (await converter.decode([metadata.details]))[0], + ) diff --git a/temporalio/converter.py b/temporalio/converter.py index 7d9eba4be..61037dc3b 100644 --- a/temporalio/converter.py +++ b/temporalio/converter.py @@ -1085,29 +1085,6 @@ async def encode_wrapper( """ return temporalio.api.common.v1.Payloads(payloads=(await self.encode(values))) - async def _encode_user_metadata( - self, - summary: Optional[Union[str, temporalio.api.common.v1.Payload]], - details: Optional[Union[str, temporalio.api.common.v1.Payload]], - ) -> Optional[temporalio.api.sdk.v1.UserMetadata]: - if summary is None and details is None: - return None - enc_summary = None - enc_details = None - if summary is not None: - if isinstance(summary, str): - enc_summary = (await self.encode([summary]))[0] - else: - enc_summary = summary - if details is not None: - if isinstance(details, str): - enc_details = (await self.encode([details]))[0] - else: - enc_details = details - return temporalio.api.sdk.v1.UserMetadata( - summary=enc_summary, details=enc_details - ) - async def decode_wrapper( self, payloads: Optional[temporalio.api.common.v1.Payloads], @@ -1136,21 +1113,6 @@ async def decode_failure( await self.payload_codec.decode_failure(failure) return self.failure_converter.from_failure(failure, self.payload_converter) - async def _decode_user_metadata( - self, metadata: Optional[temporalio.api.sdk.v1.UserMetadata] - ) -> Tuple[Optional[str], Optional[str]]: - """Returns (summary, details)""" - if metadata is None: - return None, None - return ( - None - if not metadata.HasField("summary") - else (await self.decode([metadata.summary]))[0], - None - if not metadata.HasField("details") - else (await self.decode([metadata.details]))[0], - ) - DefaultPayloadConverter.default_encoding_payload_converters = ( BinaryNullPayloadConverter(), diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index 25945b469..213438981 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -331,7 +331,7 @@ def __init__(self, det: WorkflowInstanceDetails) -> None: # The current details (as opposed to static details on workflow start), returned in the # metadata query - self._current_details: str = "" + self._current_details = "" def get_thread_id(self) -> Optional[int]: return self._current_thread_id @@ -1449,9 +1449,13 @@ async def workflow_sleep( if summary else None ) + fut = self.create_future() self._timer_impl( - duration, lambda _: None, options=_TimerOptions(user_metadata=user_metadata) + duration, + _TimerOptions(user_metadata=user_metadata), + lambda: fut.set_result(None), ) + await fut async def workflow_wait_condition( self, @@ -1476,9 +1480,9 @@ async def workflow_wait_condition( def workflow_get_current_details(self) -> str: return self._current_details - def workflow_set_current_details(self, description): + def workflow_set_current_details(self, details: str): self._assert_not_read_only("set current details") - self._current_details = description + self._current_details = details #### Calls from outbound impl #### # These are in alphabetical order and all start with "_outbound_". @@ -2014,24 +2018,24 @@ def _enhanced_stack_trace(self) -> temporalio.api.sdk.v1.EnhancedStackTrace: def _temporal_workflow_metadata(self) -> temporalio.api.sdk.v1.WorkflowMetadata: query_definitions = [ temporalio.api.sdk.v1.WorkflowInteractionDefinition( - name=qd.name if qd.name is not None else "", - description=qd.description if qd.description is not None else "", + name=qd.name or "", + description=qd.description or "", ) for qd in self._queries.values() ] query_definitions.sort(key=lambda qd: qd.name) signal_definitions = [ temporalio.api.sdk.v1.WorkflowInteractionDefinition( - name=sd.name if sd.name is not None else "", - description=sd.description if sd.description is not None else "", + name=sd.name or "", + description=sd.description or "", ) for sd in self._signals.values() ] signal_definitions.sort(key=lambda sd: sd.name) update_definitions = [ temporalio.api.sdk.v1.WorkflowInteractionDefinition( - name=ud.name if ud.name is not None else "", - description=ud.description if ud.description is not None else "", + name=ud.name or "", + description=ud.description or "", ) for ud in self._updates.values() ] @@ -2050,10 +2054,10 @@ def _temporal_workflow_metadata(self) -> temporalio.api.sdk.v1.WorkflowMetadata: def _timer_impl( self, delay: float, + options: _TimerOptions, callback: Callable[..., Any], *args: Any, context: Optional[contextvars.Context] = None, - options: Optional[_TimerOptions] = None, ): self._assert_not_read_only("schedule timer") # Delay must be positive @@ -2062,12 +2066,6 @@ def _timer_impl( # Create, schedule, and return seq = self._next_seq("timer") - # If options aren't explicitly passed, attempt to fetch them from the class field, - # erasing them afterward. Support callers who cannot call this directly because they - # rely on asyncio functions. - if options is None: - options = self._next_timer_options - self._next_timer_options = None handle = _TimerHandle( seq, self.time() + delay, options, callback, args, self, context ) @@ -2079,7 +2077,6 @@ def _timer_impl( # These are in the order defined in CPython's impl of the base class. Many # functions are intentionally not implemented/supported. - # TODO/Review: This doesn't appear to implement any base class fn and isn't called anywhere? def _timer_handle_cancelled(self, handle: asyncio.TimerHandle) -> None: if not isinstance(handle, _TimerHandle): raise TypeError("Expected Temporal timer handle") @@ -2107,7 +2104,12 @@ def call_later( *args: Any, context: Optional[contextvars.Context] = None, ) -> asyncio.TimerHandle: - return self._timer_impl(delay, callback, *args, context=context) + # Fetch options from the class field, erasing them afterward. + options = ( + self._next_timer_options if self._next_timer_options else _TimerOptions() + ) + self._next_timer_options = None + return self._timer_impl(delay, options, callback, *args, context=context) def call_at( self, diff --git a/temporalio/workflow.py b/temporalio/workflow.py index b38ff788f..dd52d49f9 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -224,6 +224,7 @@ def signal( def signal( *, unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, + description: Optional[str] = None, ) -> Callable[ [CallableSyncOrAsyncReturnNoneType], CallableSyncOrAsyncReturnNoneType ]: ... @@ -251,16 +252,6 @@ def signal( ]: ... -@overload -def signal( - *, - description: str, - unfinished_policy: HandlerUnfinishedPolicy = HandlerUnfinishedPolicy.WARN_AND_ABANDON, -) -> Callable[ - [CallableSyncOrAsyncReturnNoneType], CallableSyncOrAsyncReturnNoneType -]: ... - - def signal( fn: Optional[CallableSyncOrAsyncReturnNoneType] = None, *, @@ -768,7 +759,7 @@ async def workflow_wait_condition( def workflow_get_current_details(self) -> str: ... @abstractmethod - def workflow_set_current_details(self, description): ... + def workflow_set_current_details(self, details: str): ... _current_update_info: contextvars.ContextVar[UpdateInfo] = contextvars.ContextVar( diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 9a1d1cca3..8ee84b1b4 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -6287,3 +6287,21 @@ async def waiting() -> bool: describe_r = await handle.describe() assert describe_r.static_summary == "cool workflow bro" assert describe_r.static_details == "xtremely detailed" + + +@workflow.defn +class WorkflowSleepWorkflow: + @workflow.run + async def run(self) -> None: + await workflow.sleep(1) + + +async def test_workflow_sleep(client: Client): + async with new_worker(client, WorkflowSleepWorkflow) as worker: + start_time = datetime.now() + await client.execute_workflow( + WorkflowSleepWorkflow.run, + id=f"workflow-{uuid.uuid4()}", + task_queue=worker.task_queue, + ) + assert (datetime.now() - start_time) >= timedelta(seconds=1) From 594a5afd328d9f9525e7607bc309a357f141aefc Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 12 Dec 2024 11:38:37 -0800 Subject: [PATCH 10/13] Fix init paradox due to evict-on-complete --- temporalio/worker/_workflow_instance.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index 213438981..ba2422733 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -1834,8 +1834,9 @@ def _run_once(self, *, check_conditions: bool) -> None: asyncio._set_running_loop(self) # We instantiate the workflow class _inside_ here because __init__ - # needs to run with this event loop set - if not self._object: + # needs to run with this event loop set. If we're deleting and + # we've never initialized, we don't need to bother. + if not self._object and not self._deleting: self._object = self._instantiate_workflow_object() # Run while there is anything ready From 19c48f9d91a3e5726d8a4ec3aefaea38467a19d0 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 12 Dec 2024 13:47:58 -0800 Subject: [PATCH 11/13] Test race --- tests/worker/test_workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 8ee84b1b4..44df77b4f 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -6257,6 +6257,7 @@ async def waiting() -> bool: assert md_query.current_details == "such detail" await handle.signal(UserMetadataWorkflow.done) + await handle.result() # Ensure metadatas are present in history resp = await client.workflow_service.get_workflow_execution_history( From 9fc0aa70f71c35f42d2d1d0076d9c4231db134de Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 12 Dec 2024 14:16:46 -0800 Subject: [PATCH 12/13] Use context & add test for concurrent timer creation w/ summaries --- temporalio/worker/_workflow_instance.py | 24 +++++---- tests/worker/test_workflow.py | 72 ++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index ba2422733..8c59992bd 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -325,10 +325,6 @@ def __init__(self, det: WorkflowInstanceDetails) -> None: # For tracking the thread this workflow is running on (primarily for deadlock situations) self._current_thread_id: Optional[int] = None - # Since timer creation often happens indirectly through asyncio, we need some place to - # temporarily store options for timers created by, ex `wait_condition`. - self._next_timer_options: Optional[_TimerOptions] = None - # The current details (as opposed to static details on workflow start), returned in the # metadata query self._current_details = "" @@ -1474,8 +1470,13 @@ async def workflow_wait_condition( if timeout_summary else None ) - self._next_timer_options = _TimerOptions(user_metadata=user_metadata) - await asyncio.wait_for(fut, timeout) + ctxvars = contextvars.copy_context() + + async def in_context(): + _TimerOptionsCtxVar.set(_TimerOptions(user_metadata=user_metadata)) + await asyncio.wait_for(fut, timeout) + + await ctxvars.run(in_context) def workflow_get_current_details(self) -> str: return self._current_details @@ -2105,11 +2106,7 @@ def call_later( *args: Any, context: Optional[contextvars.Context] = None, ) -> asyncio.TimerHandle: - # Fetch options from the class field, erasing them afterward. - options = ( - self._next_timer_options if self._next_timer_options else _TimerOptions() - ) - self._next_timer_options = None + options = _TimerOptionsCtxVar.get() return self._timer_impl(delay, options, callback, *args, context=context) def call_at( @@ -2337,6 +2334,11 @@ class _TimerOptions: user_metadata: Optional[temporalio.api.sdk.v1.UserMetadata] = None +_TimerOptionsCtxVar: contextvars.ContextVar[_TimerOptions] = contextvars.ContextVar( + "__temporal_timer_options", default=_TimerOptions() +) + + class _TimerHandle(asyncio.TimerHandle): def __init__( self, diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 44df77b4f..3c5fed2d1 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -44,7 +44,6 @@ from temporalio.api.failure.v1 import Failure from temporalio.api.sdk.v1 import EnhancedStackTrace from temporalio.api.workflowservice.v1 import ( - DescribeWorkflowExecutionRequest, GetWorkflowExecutionHistoryRequest, ResetStickyTaskQueueRequest, ) @@ -6306,3 +6305,74 @@ async def test_workflow_sleep(client: Client): task_queue=worker.task_queue, ) assert (datetime.now() - start_time) >= timedelta(seconds=1) + + +@workflow.defn +class ConcurrentSleepsWorkflow: + @workflow.run + async def run(self) -> None: + sleeps_a = [workflow.sleep(0.1, summary=f"t{i}") for i in range(5)] + zero_a = workflow.sleep(0, summary="zero_timer") + wait_some = workflow.wait_condition( + lambda: False, timeout=0.1, timeout_summary="wait_some" + ) + zero_b = workflow.wait_condition( + lambda: False, timeout=0, timeout_summary="zero_wait" + ) + no_summ = workflow.sleep(0.1) + sleeps_b = [workflow.sleep(0.1, summary=f"t{i}") for i in range(5, 10)] + try: + await asyncio.gather( + *sleeps_a, + zero_a, + wait_some, + zero_b, + no_summ, + *sleeps_b, + return_exceptions=True, + ) + except asyncio.TimeoutError: + pass + + task_1 = asyncio.create_task(self.make_timers(100, 105)) + task_2 = asyncio.create_task(self.make_timers(105, 110)) + await asyncio.gather(task_1, task_2) + + async def make_timers(self, start: int, end: int): + await asyncio.gather( + *[workflow.sleep(0.1, summary=f"m_t{i}") for i in range(start, end)] + ) + + +async def test_concurrent_sleeps_use_proper_options(client: Client): + async with new_worker(client, ConcurrentSleepsWorkflow) as worker: + handle = await client.start_workflow( + ConcurrentSleepsWorkflow.run, + id=f"workflow-{uuid.uuid4()}", + task_queue=worker.task_queue, + ) + await handle.result() + resp = await client.workflow_service.get_workflow_execution_history( + GetWorkflowExecutionHistoryRequest( + namespace=client.namespace, + execution=WorkflowExecution(workflow_id=handle.id), + ) + ) + timer_summaries = [ + PayloadConverter.default.from_payload(e.user_metadata.summary) + if e.user_metadata.HasField("summary") + else "" + for e in resp.history.events + if e.event_type == EventType.EVENT_TYPE_TIMER_STARTED + ] + assert timer_summaries == [ + *[f"t{i}" for i in range(5)], + "zero_timer", + "wait_some", + "", + *[f"t{i}" for i in range(5, 10)], + *[f"m_t{i}" for i in range(100, 110)], + ] + + # Force replay with a query to ensure determinism + await handle.query("__temporal_workflow_metadata") From 8747bda24ce6893e25b6f584de063565112536c4 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Thu, 12 Dec 2024 15:31:07 -0800 Subject: [PATCH 13/13] Skip metadata tests on java server --- tests/worker/test_workflow.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/worker/test_workflow.py b/tests/worker/test_workflow.py index 3c5fed2d1..2611ecd54 100644 --- a/tests/worker/test_workflow.py +++ b/tests/worker/test_workflow.py @@ -6213,7 +6213,11 @@ def some_update(self): pass -async def test_user_metadata_is_set(client: Client): +async def test_user_metadata_is_set(client: Client, env: WorkflowEnvironment): + if env.supports_time_skipping: + pytest.skip( + "Java test server: https://github.com/temporalio/sdk-java/issues/2219" + ) async with new_worker( client, UserMetadataWorkflow, activities=[say_hello] ) as worker: @@ -6344,7 +6348,13 @@ async def make_timers(self, start: int, end: int): ) -async def test_concurrent_sleeps_use_proper_options(client: Client): +async def test_concurrent_sleeps_use_proper_options( + client: Client, env: WorkflowEnvironment +): + if env.supports_time_skipping: + pytest.skip( + "Java test server: https://github.com/temporalio/sdk-java/issues/2219" + ) async with new_worker(client, ConcurrentSleepsWorkflow) as worker: handle = await client.start_workflow( ConcurrentSleepsWorkflow.run,