@@ -59,10 +59,16 @@ class WorkflowDefinition:
5959 Provides type safety and metadata for workflow classes.
6060 """
6161
62- def __init__ (self , cls : Type , name : str , run_method_name : str ):
62+ def __init__ (self , cls : Type , name : str , run_method_name : str , signals : dict [ str , Callable [..., Any ]] ):
6363 self ._cls = cls
6464 self ._name = name
6565 self ._run_method_name = run_method_name
66+ self ._signals = signals
67+
68+ @property
69+ def signals (self ) -> dict [str , Callable [..., Any ]]:
70+ """Get the signals."""
71+ return self ._signals
6672
6773 @property
6874 def name (self ) -> str :
@@ -98,6 +104,9 @@ def wrap(cls: Type, opts: WorkflowDefinitionOptions) -> "WorkflowDefinition":
98104 name = opts ["name" ]
99105
100106 # Validate that the class has exactly one run method and find it
107+ # Also validate that class does not have multiple signal methods with the same name
108+ signals : dict [str , Callable [..., Any ]] = {}
109+ signal_names : dict [str , str ] = {} # Map signal name to method name for duplicate detection
101110 run_method_name = None
102111 for attr_name in dir (cls ):
103112 if attr_name .startswith ("_" ):
@@ -114,11 +123,22 @@ def wrap(cls: Type, opts: WorkflowDefinitionOptions) -> "WorkflowDefinition":
114123 f"Multiple @workflow.run methods found in class { cls .__name__ } "
115124 )
116125 run_method_name = attr_name
126+
127+ if hasattr (attr , "_workflow_signal" ):
128+ signal_name = getattr (attr , "_workflow_signal" )
129+ if signal_name in signal_names :
130+ raise ValueError (
131+ f"Multiple @workflow.signal methods found in class { cls .__name__ } "
132+ f"with signal name '{ signal_name } ': '{ attr_name } ' and '{ signal_names [signal_name ]} '"
133+ )
134+ signals [attr_name ] = attr
135+ signal_names [signal_name ] = attr_name
117136
118137 if run_method_name is None :
119138 raise ValueError (f"No @workflow.run method found in class { cls .__name__ } " )
120139
121- return WorkflowDefinition (cls , name , run_method_name )
140+ return WorkflowDefinition (cls , name , run_method_name , signals )
141+
122142
123143
124144def run (func : Optional [T ] = None ) -> Union [T , Callable [[T ], T ]]:
@@ -161,6 +181,33 @@ def decorator(f: T) -> T:
161181 # Called without parentheses: @workflow.run
162182 return decorator (func )
163183
184+ def signal (name : str | None = None ) -> Callable [[T ], T ]:
185+ """
186+ Decorator to mark a method as a workflow signal handler.
187+
188+ Example:
189+ @workflow.signal(name="approval_channel")
190+ async def approve(self, approved: bool):
191+ self.approved = approved
192+
193+ Args:
194+ name: The name of the signal
195+
196+ Returns:
197+ The decorated method with workflow signal metadata
198+
199+ Raises:
200+ ValueError: If name is not provided
201+
202+ """
203+ if name is None :
204+ raise ValueError ("name is required" )
205+
206+ def decorator (f : T ) -> T :
207+ f ._workflow_signal = name # type: ignore
208+ return f
209+ # Only allow @workflow.signal(name), require name to be explicitly provided
210+ return decorator
164211
165212@dataclass
166213class WorkflowInfo :
0 commit comments