@@ -120,29 +120,45 @@ def cancel_event(self, send_id: str):
120120 """Cancel the event with the given send_id."""
121121 self .external_queue .remove (send_id )
122122
123- @property
124- def _on_error_execution (self ):
125- """Return an error handler for per-block error isolation, or None.
123+ def _on_error_handler (self , trigger_data : TriggerData ) -> "Callable[[Exception], None] | None" :
124+ """Return a per-block error handler bound to *trigger_data*, or ``None``.
126125
127- When ``error_on_execution`` is enabled, returns a handler that queues
126+ When ``error_on_execution`` is enabled, returns a callable that queues
128127 ``error.execution`` on the internal queue. Otherwise returns ``None``
129128 so that exceptions propagate normally.
130129 """
131- if self .sm .error_on_execution :
130+ if not self .sm .error_on_execution :
131+ return None
132132
133- def _handler (e : Exception ):
134- if isinstance (e , InvalidDefinition ):
135- raise
136- self .sm .send ("error.execution" , error = e , internal = True )
133+ def handler (error : Exception ) -> None :
134+ if isinstance (error , InvalidDefinition ):
135+ raise error
136+ # Per-block errors always queue error.execution — even when the current
137+ # event is itself error.execution. The SCXML spec mandates that the
138+ # new error.execution is a separate event that may trigger a different
139+ # transition (see W3C test 152). The infinite-loop guard lives at the
140+ # *microstep* level (in ``_send_error_execution``), not here.
141+ self .sm .send ("error.execution" , error = error , internal = True )
137142
138- return _handler
139- return None
143+ return handler
140144
141- def _send_error_execution (self , trigger_data : TriggerData , error : Exception ):
145+ def _handle_error (self , error : Exception , trigger_data : TriggerData ):
146+ """Handle an execution error: send ``error.execution`` or re-raise.
147+
148+ Centralises the ``if error_on_execution`` check so callers don't need
149+ to know about the variation.
150+ """
151+ if self .sm .error_on_execution :
152+ self ._send_error_execution (error , trigger_data )
153+ else :
154+ raise error
155+
156+ def _send_error_execution (self , error : Exception , trigger_data : TriggerData ):
142157 """Send error.execution to internal queue (SCXML spec).
143158
144159 If already processing an error.execution event, ignore to avoid infinite loops.
145160 """
161+ logger .debug ("Error %s captured while executing event=%s" , error , trigger_data .event )
146162 if trigger_data .event and str (trigger_data .event ) == "error.execution" :
147163 logger .warning ("Error while processing error.execution, ignoring: %s" , error )
148164 return
@@ -353,10 +369,8 @@ def microstep(self, transitions: List[Transition], trigger_data: TriggerData):
353369 raise
354370 except Exception as e :
355371 self .sm .configuration = previous_configuration
356- if self .sm .error_on_execution :
357- self ._send_error_execution (trigger_data , e )
358- return None
359- raise
372+ self ._handle_error (e , trigger_data )
373+ return None
360374
361375 try :
362376 self ._execute_transition_content (
@@ -368,10 +382,7 @@ def microstep(self, transitions: List[Transition], trigger_data: TriggerData):
368382 except InvalidDefinition :
369383 raise
370384 except Exception as e :
371- if self .sm .error_on_execution :
372- self ._send_error_execution (trigger_data , e )
373- else :
374- raise
385+ self ._handle_error (e , trigger_data )
375386
376387 if len (result ) == 0 :
377388 result = None
@@ -407,13 +418,10 @@ def _get_args_kwargs(
407418
408419 def _conditions_match (self , transition : Transition , trigger_data : TriggerData ):
409420 args , kwargs = self ._get_args_kwargs (transition , trigger_data )
421+ on_error = self ._on_error_handler (trigger_data )
410422
411- self .sm ._callbacks .call (
412- transition .validators .key , * args , on_error = self ._on_error_execution , ** kwargs
413- )
414- return self .sm ._callbacks .all (
415- transition .cond .key , * args , on_error = self ._on_error_execution , ** kwargs
416- )
423+ self .sm ._callbacks .call (transition .validators .key , * args , on_error = on_error , ** kwargs )
424+ return self .sm ._callbacks .all (transition .cond .key , * args , on_error = on_error , ** kwargs )
417425
418426 def _prepare_exit_states (
419427 self ,
@@ -457,15 +465,14 @@ def _exit_states(
457465 ) -> OrderedSet [State ]:
458466 """Compute and process the states to exit for the given transitions."""
459467 ordered_states , result = self ._prepare_exit_states (enabled_transitions )
468+ on_error = self ._on_error_handler (trigger_data )
460469
461470 for info in ordered_states :
462471 args , kwargs = self ._get_args_kwargs (info .transition , trigger_data )
463472
464473 # Execute `onexit` handlers — same per-block error isolation as onentry.
465474 if info .state is not None : # TODO: and not info.transition.internal:
466- self .sm ._callbacks .call (
467- info .state .exit .key , * args , on_error = self ._on_error_execution , ** kwargs
468- )
475+ self .sm ._callbacks .call (info .state .exit .key , * args , on_error = on_error , ** kwargs )
469476
470477 self ._remove_state_from_configuration (info .state )
471478
@@ -568,6 +575,7 @@ def _enter_states( # noqa: C901
568575 previous_configuration : OrderedSet [State ],
569576 ):
570577 """Enter the states as determined by the given transitions."""
578+ on_error = self ._on_error_handler (trigger_data )
571579 ordered_states , states_for_default_entry , default_history_content , new_configuration = (
572580 self ._prepare_entry_states (enabled_transitions , states_to_exit , previous_configuration )
573581 )
@@ -598,7 +606,7 @@ def _enter_states( # noqa: C901
598606 # Execute `onentry` handlers — each handler is a separate block per
599607 # SCXML spec: errors in one block MUST NOT affect other blocks.
600608 on_entry_result = self .sm ._callbacks .call (
601- target .enter .key , * args , on_error = self . _on_error_execution , ** kwargs
609+ target .enter .key , * args , on_error = on_error , ** kwargs
602610 )
603611
604612 # Handle default initial states
0 commit comments