tinyfsm 0.0.2__tar.gz → 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyfsm
3
- Version: 0.0.2
3
+ Version: 0.1.0
4
4
  Summary: A tiny and minimal finite state machine for Python.
5
5
  License: MIT
6
6
  License-File: LICENSE.txt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tinyfsm"
3
- version = "0.0.2"
3
+ version = "0.1.0"
4
4
  description = "A tiny and minimal finite state machine for Python."
5
5
  authors = [
6
6
  {name = "Maciej Wiatrzyk",email = "maciej.wiatrzyk@gmail.com"}
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,107 @@
1
+ from typing import Any, Sequence
2
+
3
+ from . import _export_list
4
+
5
+ __all__ = export = _export_list.ExportList() # type: ignore
6
+
7
+
8
+ @export
9
+ class TinyFSMError(Exception):
10
+ """Common base class for all exception this library may raise."""
11
+
12
+
13
+ @export
14
+ class InconsistentDefinitionError(TinyFSMError):
15
+ """Raised when state machine definition is found to be inconsistent.
16
+
17
+ This means that no traversal was defined for the current state, i.e. there
18
+ is no next state to move to, no matter what the input will be.
19
+ """
20
+
21
+ #: The rejected input object.
22
+ input: Any
23
+
24
+ #: The current state name.
25
+ current_state: str
26
+
27
+ def __init__(self, input: Any, current_state: str):
28
+ super().__init__()
29
+ self.input = input
30
+ self.current_state = current_state
31
+
32
+ def __str__(self) -> str:
33
+ return f"input {self.input!r} was rejected; no next state defined for the current state {self.current_state!r}"
34
+
35
+
36
+ @export
37
+ class InputRejectedError(TinyFSMError):
38
+ """Raised when input was rejected by the state machine.
39
+
40
+ Input is rejected when it is not possible to deterministically choose the
41
+ next state for the current input.
42
+ """
43
+
44
+ #: The rejected input object.
45
+ input: Any
46
+
47
+ #: The current state name.
48
+ current_state: str
49
+
50
+ #: The candidate state names.
51
+ #:
52
+ #: These are names of the states that are accessible from the current state
53
+ #: given via :attr:`current_state`.
54
+ next_state_candidates: Sequence[str]
55
+
56
+ def __init__(self, input: Any, current_state: str, next_state_candidates: Sequence[str]):
57
+ super().__init__()
58
+ self.input = input
59
+ self.current_state = current_state
60
+ self.next_state_candidates = next_state_candidates
61
+
62
+ def __str__(self) -> str:
63
+ return (
64
+ f"input {self.input!r} was rejected; "
65
+ f"cannot traverse from {self.current_state!r} to any of: "
66
+ f"{', '.join(repr(x) for x in self.next_state_candidates)}"
67
+ )
68
+
69
+
70
+ @export
71
+ class FinalStateNotReached(TinyFSMError):
72
+ """Raised when closing state machine if final state was not reached.
73
+
74
+ This usually means that either the input sequence is incomplete and more
75
+ data is required to reach the final state. For example, a final state
76
+ requires text to end with a period, but a period is missing and no more
77
+ characters are fed into the state machine.
78
+ """
79
+
80
+ #: The last dispatched input.
81
+ last_input: Any
82
+
83
+ #: The name of the final state.
84
+ final_state: str
85
+
86
+ #: The name of the current state.
87
+ current_state: str
88
+
89
+ #: The candidate state names.
90
+ #:
91
+ #: These are names of the states that are accessible from the current state
92
+ #: given via :attr:`current_state`.
93
+ next_state_candidates: Sequence[str]
94
+
95
+ def __init__(self, last_input: Any, final_state: str, current_state: str, next_state_candidates: Sequence[str]):
96
+ super().__init__()
97
+ self.last_input = last_input
98
+ self.final_state = final_state
99
+ self.current_state = current_state
100
+ self.next_state_candidates = next_state_candidates
101
+
102
+ def __str__(self) -> str:
103
+ return (
104
+ f"final state {self.final_state!r} was not reached for last input {self.last_input!r}; "
105
+ f"more input data is expected to traverse from {self.current_state!r} "
106
+ f"to any of: {', '.join(repr(x) for x in self.next_state_candidates)}"
107
+ )
@@ -1,7 +1,7 @@
1
1
  from typing import Generic, Optional, Sequence, TypeVar
2
2
 
3
3
  from . import _export_list
4
- from .exc import InputRejectedError, FinalStateNotReached
4
+ from .exc import InconsistentDefinitionError, InputRejectedError, FinalStateNotReached
5
5
  from .interface import StateMachineListener, Traversal
6
6
 
7
7
  __all__ = export = _export_list.ExportList() # type: ignore
@@ -102,7 +102,7 @@ class StateMachineRunner(Generic[T]):
102
102
  self.__last_input = input
103
103
  current_state_traversals = self.__traversal_map.get(self.__current_state)
104
104
  if current_state_traversals is None:
105
- raise InputRejectedError(input, self.__current_state)
105
+ raise InconsistentDefinitionError(input, self.__current_state)
106
106
  for traversal in current_state_traversals:
107
107
  if traversal.traverse_func(input):
108
108
  next_state = traversal.target_state
@@ -110,7 +110,8 @@ class StateMachineRunner(Generic[T]):
110
110
  self.__current_state = next_state
111
111
  break
112
112
  else:
113
- raise InputRejectedError(input, self.__current_state)
113
+ next_state_candidates = tuple(x.target_state for x in current_state_traversals)
114
+ raise InputRejectedError(input, self.__current_state, next_state_candidates)
114
115
  self.__listener.on_dispatch_done(input, self.__current_state)
115
116
 
116
117
  def close(self):
@@ -122,7 +123,11 @@ class StateMachineRunner(Generic[T]):
122
123
  :exc:`tinyfsm.exc.FinalStateNotReached` error.
123
124
  """
124
125
  if not self.is_final():
125
- raise FinalStateNotReached(self.__last_input, self.__final_state, self.__current_state)
126
+ current_state_traversals = self.__traversal_map.get(self.__current_state)
127
+ next_state_candidates = tuple(x.target_state for x in (current_state_traversals or []))
128
+ raise FinalStateNotReached(
129
+ self.__last_input, self.__final_state, self.__current_state, next_state_candidates
130
+ )
126
131
 
127
132
  def is_final(self) -> bool:
128
133
  """Check if the final state is reached."""
@@ -1 +0,0 @@
1
- __version__ = "0.0.2"
@@ -1,60 +0,0 @@
1
- from typing import Any
2
-
3
- from . import _export_list
4
-
5
- __all__ = export = _export_list.ExportList() # type: ignore
6
-
7
-
8
- @export
9
- class TinyFSMError(Exception):
10
- """Common base class for all exception this library may raise."""
11
-
12
-
13
- @export
14
- class InputRejectedError(TinyFSMError):
15
- """Raised when input was rejected by the state machine.
16
-
17
- Input is rejected only if there was no matching traversal found or if all
18
- traversal functions returned ``False`` for that input.
19
- """
20
-
21
- #: The rejected input object.
22
- input: Any
23
-
24
- #: The current state name.
25
- current_state: str
26
-
27
- def __init__(self, input: Any, current_state: str):
28
- super().__init__()
29
- self.input = input
30
- self.current_state = current_state
31
-
32
- def __str__(self) -> str:
33
- return f"input {self.input!r} was rejected; no traversal found for current state {self.current_state!r}"
34
-
35
-
36
- @export
37
- class FinalStateNotReached(TinyFSMError):
38
- """Raised when closing state machine if final state was not reached.
39
-
40
- This means that either the input sequence is incomplete, or that the state
41
- machine definition is malformed and final state is unreachable.
42
- """
43
-
44
- #: The last dispatched input.
45
- last_input: Any
46
-
47
- #: The name of the final state.
48
- final_state: str
49
-
50
- #: The name of the current state.
51
- current_state: str
52
-
53
- def __init__(self, last_input: Any, final_state: str, current_state: str):
54
- super().__init__()
55
- self.last_input = last_input
56
- self.final_state = final_state
57
- self.current_state = current_state
58
-
59
- def __str__(self) -> str:
60
- return f"final state {self.final_state!r} was not reached; current state is {self.current_state!r}, last input was {self.last_input!r}"
File without changes
File without changes
File without changes
File without changes
File without changes