@@ -122,11 +122,54 @@ def __init__(self, schedule_id: str) -> None:
122122class QueryFailed (DurableWorkflowError ):
123123 """A workflow query was rejected or the workflow raised while handling it."""
124124
125- def __init__ (self , message : str , * , reason : str | None = None , body : object | None = None ) -> None :
125+ def __init__ (
126+ self ,
127+ message : str ,
128+ * ,
129+ reason : str | None = None ,
130+ status : int | None = None ,
131+ body : object | None = None ,
132+ ) -> None :
133+ super ().__init__ (message )
134+ self .reason = reason
135+ self .status = status
136+ self .body = body
137+
138+ @property
139+ def validation_errors (self ) -> dict [str , Any ] | None :
140+ """Return structured query argument validation errors, if the server provided them."""
141+ if isinstance (self .body , dict ):
142+ errors = self .body .get ("validation_errors" ) or self .body .get ("errors" )
143+ if isinstance (errors , dict ):
144+ return errors
145+ return None
146+
147+
148+ class SignalFailed (DurableWorkflowError ):
149+ """A workflow signal was rejected before it could be delivered."""
150+
151+ def __init__ (
152+ self ,
153+ message : str ,
154+ * ,
155+ reason : str | None = None ,
156+ status : int | None = None ,
157+ body : object | None = None ,
158+ ) -> None :
126159 super ().__init__ (message )
127160 self .reason = reason
161+ self .status = status
128162 self .body = body
129163
164+ @property
165+ def validation_errors (self ) -> dict [str , Any ] | None :
166+ """Return structured signal argument validation errors, if the server provided them."""
167+ if isinstance (self .body , dict ):
168+ errors = self .body .get ("validation_errors" ) or self .body .get ("errors" )
169+ if isinstance (errors , dict ):
170+ return errors
171+ return None
172+
130173
131174class WorkflowPayloadDecodeError (DurableWorkflowError ):
132175 """A committed workflow history payload could not be decoded during replay."""
@@ -282,14 +325,30 @@ def _raise_for_status(status: int, body: object, *, context: str = "") -> None:
282325
283326 reason = body .get ("reason" ) if isinstance (body , dict ) else None
284327 message = body .get ("message" , "" ) if isinstance (body , dict ) else str (body )
328+ operation = _control_plane_operation (body )
285329
286330 if status == 401 :
287331 raise Unauthorized (message or "unauthorized" )
288332
289333 def query_failed (default : str ) -> QueryFailed :
290- return QueryFailed (message or default , reason = reason if isinstance (reason , str ) else None , body = body )
334+ return QueryFailed (
335+ message or default ,
336+ reason = reason if isinstance (reason , str ) else None ,
337+ status = status ,
338+ body = body ,
339+ )
340+
341+ def signal_failed (default : str ) -> SignalFailed :
342+ return SignalFailed (
343+ message or default ,
344+ reason = reason if isinstance (reason , str ) else None ,
345+ status = status ,
346+ body = body ,
347+ )
291348
292349 if status == 404 :
350+ if reason == "unknown_signal" :
351+ raise signal_failed ("signal not found" )
293352 if reason in ("query_not_found" , "rejected_unknown_query" ):
294353 raise query_failed ("query not found" )
295354 if reason == "schedule_not_found" :
@@ -305,6 +364,11 @@ def query_failed(default: str) -> QueryFailed:
305364 raise ScheduleAlreadyExists (context )
306365 if reason == "duplicate_not_allowed" :
307366 raise WorkflowAlreadyStarted (context )
367+ if reason == "run_not_active" :
368+ if operation == "signal" :
369+ raise signal_failed ("signal rejected" )
370+ if operation == "query" :
371+ raise query_failed ("query rejected" )
308372 if reason in (
309373 "query_rejected" ,
310374 "query_worker_unavailable" ,
@@ -326,9 +390,30 @@ def query_failed(default: str) -> QueryFailed:
326390 raise ServerError (status , body )
327391
328392 if status == 422 :
393+ if reason == "invalid_signal_arguments" :
394+ raise signal_failed ("signal argument validation failed" )
395+ if reason == "invalid_query_arguments" :
396+ raise query_failed ("query argument validation failed" )
329397 errors = None
330398 if isinstance (body , dict ):
331399 errors = body .get ("errors" ) or body .get ("validation_errors" )
332400 raise InvalidArgument (message , errors )
333401
334402 raise ServerError (status , body )
403+
404+
405+ def _control_plane_operation (body : object ) -> str | None :
406+ if not isinstance (body , dict ):
407+ return None
408+
409+ control_plane = body .get ("control_plane" )
410+ if isinstance (control_plane , dict ):
411+ operation = control_plane .get ("operation" )
412+ if isinstance (operation , str ) and operation :
413+ return operation
414+
415+ operation = body .get ("control_plane_operation" )
416+ if isinstance (operation , str ) and operation :
417+ return operation
418+
419+ return None
0 commit comments