2
2
from contextlib import asynccontextmanager
3
3
from dataclasses import dataclass
4
4
from datetime import datetime , timedelta
5
- import inspect
6
5
import logging
7
6
from typing import Awaitable , Callable , Optional , Type
8
7
9
8
from temporalio import common , workflow , activity
10
9
from temporalio .client import Client , WorkflowHandle
11
10
from temporalio .worker import Worker
11
+ from temporalio .workflow import UpdateMethodMultiParam
12
12
13
13
14
14
JobID = str
@@ -42,6 +42,13 @@ class JobOutput:
42
42
UpdateID = str
43
43
Workflow = Type
44
44
45
+
46
+ @dataclass
47
+ class Update :
48
+ id : UpdateID
49
+ arg : I
50
+
51
+
45
52
_sdk_internals_pending_tasks_count = 0
46
53
_sdk_internals_handler_mutex = asyncio .Lock ()
47
54
@@ -67,41 +74,44 @@ async def _sdk_internals__track_pending__wait_until_ready__serialize_execution(
67
74
_sdk_internals_pending_tasks_count -= 1
68
75
69
76
70
- class SDKInternals :
71
- # Here, the SDK is wrapping the user's update handlers with the required wait-until-ready,
72
- # pending tasks tracking, and synchronization functionality. This is a fake implementation: the
73
- # real implementation will automatically inspect and wrap the user's declared update handlers.
77
+ _original_workflow_update_decorator = workflow .update
74
78
75
- def ready_to_execute (self , arg : I ) -> bool :
76
- # Implemented by user
77
- return True
78
79
79
- @workflow .update
80
- async def run_shell_script_job (self , arg : I ) -> O :
81
- handler = getattr (self , "_" + inspect .currentframe ().f_code .co_name )
82
- async with _sdk_internals__track_pending__wait_until_ready__serialize_execution (
83
- lambda : self .ready_to_execute (arg )
84
- ):
85
- return await handler (arg )
80
+ def _new_workflow_update_decorator (
81
+ execute_condition : Callable [[Workflow , Update ], bool ], ** kwargs
82
+ ) -> Callable [
83
+ [Callable [[Workflow , I ], Awaitable [O ]]],
84
+ UpdateMethodMultiParam [[Workflow , I ], O ],
85
+ ]:
86
+ def decorator (
87
+ handler : Callable [[Workflow , I ], Awaitable [O ]]
88
+ ) -> UpdateMethodMultiParam :
89
+ async def wrapped_handler (self : Workflow , arg : I ):
90
+ async with _sdk_internals__track_pending__wait_until_ready__serialize_execution (
91
+ lambda : execute_condition (self , Update (arg .id , arg ))
92
+ ):
93
+ return await handler (self , arg )
94
+
95
+ dec = (
96
+ _original_workflow_update_decorator (** kwargs )
97
+ if kwargs
98
+ else _original_workflow_update_decorator
99
+ )
100
+ return dec (wrapped_handler )
86
101
87
- @workflow .update
88
- async def run_python_job (self , arg : I ) -> O :
89
- handler = getattr (self , "_" + inspect .currentframe ().f_code .co_name )
90
- async with _sdk_internals__track_pending__wait_until_ready__serialize_execution (
91
- lambda : self .ready_to_execute (arg )
92
- ):
93
- return await handler (arg )
102
+ return decorator
94
103
95
104
96
105
# Monkey-patch proposed new public API
97
106
setattr (workflow , "all_handlers_completed" , _sdk_internals_all_handlers_completed )
107
+ setattr (workflow , "update" , _new_workflow_update_decorator )
98
108
##
99
109
## END SDK internals prototype
100
110
##
101
111
102
112
103
113
@workflow .defn
104
- class JobRunner ( SDKInternals ) :
114
+ class JobRunner :
105
115
"""
106
116
Jobs must be executed in order dictated by job dependency graph (see `job.depends_on`) and
107
117
not before `job.after_time`.
@@ -115,12 +125,13 @@ async def run(self):
115
125
await workflow .wait_condition (
116
126
lambda : (
117
127
workflow .info ().is_continue_as_new_suggested ()
118
- and self .all_handlers_completed ()
128
+ and workflow .all_handlers_completed ()
119
129
)
120
130
)
121
131
workflow .continue_as_new ()
122
132
123
- def ready_to_execute (self , job : Job ) -> bool :
133
+ def ready_to_execute (self , update : Update ) -> bool :
134
+ job = update .arg
124
135
if not set (job .depends_on ) <= self .completed_tasks :
125
136
return False
126
137
if after_time := job .after_time :
@@ -131,8 +142,11 @@ def ready_to_execute(self, job: Job) -> bool:
131
142
# These are the real handler functions. When we implement SDK support, these will use the
132
143
# @workflow.update decorator and will not use an underscore prefix.
133
144
134
- # @workflow.update
135
- async def _run_shell_script_job (self , job : Job ) -> JobOutput :
145
+ # @workflow.update(
146
+ # execute_condition=lambda self, update: self.ready_to_execute(update)
147
+ # )
148
+ @_new_workflow_update_decorator (execute_condition = ready_to_execute )
149
+ async def run_shell_script_job (self , job : Job ) -> JobOutput :
136
150
try :
137
151
if security_errors := await workflow .execute_activity (
138
152
run_shell_script_security_linter ,
@@ -148,8 +162,8 @@ async def _run_shell_script_job(self, job: Job) -> JobOutput:
148
162
# FIXME: unbounded memory usage
149
163
self .completed_tasks .add (job .id )
150
164
151
- # @workflow.update
152
- async def _run_python_job (self , job : Job ) -> JobOutput :
165
+ @ _new_workflow_update_decorator ( execute_condition = ready_to_execute )
166
+ async def run_python_job (self , job : Job ) -> JobOutput :
153
167
try :
154
168
if not await workflow .execute_activity (
155
169
check_python_interpreter_version ,
0 commit comments