1- import warnings
21from typing import Any
32from typing import Dict
43from typing import List
@@ -28,12 +27,18 @@ class StateMachineMetaclass(type):
2827 validate_disconnected_states : bool = True
2928 """If `True`, the state machine will validate that there are no unreachable states."""
3029
30+ validate_trap_states : bool = True
31+ """If ``True``, non-final states without outgoing transitions raise ``InvalidDefinition``."""
32+
33+ validate_final_reachability : bool = True
34+ """If ``True`` and final states exist, non-final states without a path to any final
35+ state raise ``InvalidDefinition``."""
36+
3137 def __init__ (
3238 cls ,
3339 name : str ,
3440 bases : Tuple [type ],
3541 attrs : Dict [str , Any ],
36- strict_states : bool = False ,
3742 ) -> None :
3843 super ().__init__ (name , bases , attrs )
3944 registry .register (cls )
@@ -46,7 +51,6 @@ def __init__(
4651 """Map of ``state.value`` to the corresponding :ref:`state`."""
4752
4853 cls ._abstract = True
49- cls ._strict_states = strict_states
5054 cls ._events : Dict [Event , None ] = {} # used Dict to preserve order and avoid duplicates
5155 cls ._protected_attrs : set = set ()
5256 cls ._events_to_update : Dict [Event , Optional [Event ]] = {}
@@ -173,30 +177,30 @@ def _check_final_states(cls):
173177 )
174178
175179 def _check_trap_states (cls ):
180+ if not cls .validate_trap_states :
181+ return
176182 trap_states = [s for s in cls .states if not s .final and not s .transitions ]
177183 if trap_states :
178- message = _ (
179- "All non-final states should have at least one outgoing transition. "
180- "These states have no outgoing transition: {!r}"
181- ).format ([s .id for s in trap_states ])
182- if cls ._strict_states :
183- raise InvalidDefinition (message )
184- else :
185- warnings .warn (message , UserWarning , stacklevel = 4 )
184+ raise InvalidDefinition (
185+ _ (
186+ "All non-final states should have at least one outgoing transition. "
187+ "These states have no outgoing transition: {!r}"
188+ ).format ([s .id for s in trap_states ])
189+ )
186190
187191 def _check_reachable_final_states (cls ):
192+ if not cls .validate_final_reachability :
193+ return
188194 if not any (s .final for s in cls .states ):
189195 return # No need to check final reachability
190196 disconnected_states = list (states_without_path_to_final_states (cls .states ))
191197 if disconnected_states :
192- message = _ (
193- "All non-final states should have at least one path to a final state. "
194- "These states have no path to a final state: {!r}"
195- ).format ([s .id for s in disconnected_states ])
196- if cls ._strict_states :
197- raise InvalidDefinition (message )
198- else :
199- warnings .warn (message , UserWarning , stacklevel = 1 )
198+ raise InvalidDefinition (
199+ _ (
200+ "All non-final states should have at least one path to a final state. "
201+ "These states have no path to a final state: {!r}"
202+ ).format ([s .id for s in disconnected_states ])
203+ )
200204
201205 def _check_disconnected_state (cls ):
202206 if not cls .validate_disconnected_states :
0 commit comments