python-statemachine 3.2.0__py3-none-any.whl → 3.2.2__py3-none-any.whl

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.
Files changed (40) hide show
  1. {python_statemachine-3.2.0.dist-info → python_statemachine-3.2.2.dist-info}/METADATA +7 -6
  2. python_statemachine-3.2.2.dist-info/RECORD +73 -0
  3. {python_statemachine-3.2.0.dist-info → python_statemachine-3.2.2.dist-info}/WHEEL +1 -1
  4. statemachine/__init__.py +1 -1
  5. statemachine/callbacks.py +11 -0
  6. statemachine/contrib/diagram/__init__.py +15 -6
  7. statemachine/contrib/diagram/extract.py +3 -3
  8. statemachine/contrib/diagram/formatter.py +4 -2
  9. statemachine/contrib/diagram/graphviz.py +66 -0
  10. statemachine/contrib/diagram/sphinx_ext.py +28 -7
  11. statemachine/dispatcher.py +8 -3
  12. statemachine/engines/async_.py +224 -180
  13. statemachine/engines/base.py +257 -245
  14. statemachine/engines/sync.py +224 -108
  15. statemachine/event.py +29 -10
  16. statemachine/event_data.py +19 -3
  17. statemachine/events.py +5 -2
  18. statemachine/exceptions.py +1 -1
  19. statemachine/factory.py +126 -29
  20. statemachine/io/__init__.py +18 -11
  21. statemachine/io/actions.py +124 -15
  22. statemachine/io/evaluators.py +37 -9
  23. statemachine/io/invoke.py +12 -0
  24. statemachine/io/loader.py +17 -3
  25. statemachine/io/schemas/statechart.schema.json +8 -1
  26. statemachine/io/scxml/__init__.py +6 -3
  27. statemachine/io/scxml/processor.py +1 -1
  28. statemachine/io/scxml/reader.py +24 -9
  29. statemachine/io/system_variables.py +34 -19
  30. statemachine/locale/en/LC_MESSAGES/statemachine.po +58 -17
  31. statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +66 -17
  32. statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +66 -17
  33. statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +60 -17
  34. statemachine/orderedset.py +12 -3
  35. statemachine/registry.py +6 -1
  36. statemachine/spec_parser.py +151 -21
  37. statemachine/state.py +47 -8
  38. statemachine/statemachine.py +42 -19
  39. python_statemachine-3.2.0.dist-info/RECORD +0 -72
  40. {python_statemachine-3.2.0.dist-info → python_statemachine-3.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ msgid ""
5
5
  msgstr ""
6
6
  "Project-Id-Version: 3.1.1\n"
7
7
  "Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
8
- "POT-Creation-Date: 2026-06-16 23:00-0300\n"
8
+ "POT-Creation-Date: 2026-10-13 21:26-0300\n"
9
9
  "PO-Revision-Date: 2024-06-07 17:41-0300\n"
10
10
  "Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
11
11
  "Language: pt_BR\n"
@@ -16,7 +16,7 @@ msgstr ""
16
16
  "Content-Transfer-Encoding: 8bit\n"
17
17
  "Generated-By: Babel 2.18.0\n"
18
18
 
19
- #: statemachine/callbacks.py:398 statemachine/callbacks.py:403
19
+ #: statemachine/callbacks.py:409 statemachine/callbacks.py:414
20
20
  #, python-brace-format
21
21
  msgid "Did not found name '{}' from model or statemachine"
22
22
  msgstr "Nome '{}' não encontrado no modelo ou na máquina de estados"
@@ -29,12 +29,24 @@ msgstr ""
29
29
  "Nenhum estado atual definido. Em código assíncrono, você ativou o estado "
30
30
  "inicial? (ex.: `await sm.activate_initial_state()`)"
31
31
 
32
- #: statemachine/dispatcher.py:123
32
+ #: statemachine/dispatcher.py:127
33
33
  #, python-brace-format
34
34
  msgid "Failed to parse boolean expression '{}'"
35
35
  msgstr "Falha ao interpretar a expressão booleana '{}'"
36
36
 
37
- #: statemachine/event.py:94
37
+ #: statemachine/event.py:56
38
+ #, python-brace-format
39
+ msgid ""
40
+ "Event {id!r} cannot be both delayed and internal: the internal queue runs"
41
+ " within the current macrostep, which takes no time. Drop 'internal' to "
42
+ "deliver it later on the external queue, or drop 'delay' to run it now."
43
+ msgstr ""
44
+ "O evento {id!r} não pode ser atrasado e interno ao mesmo tempo: a fila "
45
+ "interna roda dentro do macrostep atual, que não leva tempo. Remova "
46
+ "'internal' para entregá-lo depois pela fila externa, ou remova 'delay' "
47
+ "para executá-lo agora."
48
+
49
+ #: statemachine/event.py:108
38
50
  #, python-brace-format
39
51
  msgid ""
40
52
  "Event() received a non-string 'id' ({cls_name}). To combine multiple "
@@ -43,12 +55,12 @@ msgstr ""
43
55
  "Event() recebeu um 'id' que não é uma string ({cls_name}). Para combinar "
44
56
  "múltiplas transições em um único evento, use o operador |: t1 | t2."
45
57
 
46
- #: statemachine/event.py:130
58
+ #: statemachine/event.py:147
47
59
  #, python-brace-format
48
60
  msgid "Cannot add callback '{}' to an event with no transitions."
49
61
  msgstr "Não é possível adicionar callback '{}' a um evento sem transições."
50
62
 
51
- #: statemachine/event.py:163
63
+ #: statemachine/event.py:182
52
64
  #, python-brace-format
53
65
  msgid "Event {} cannot be called without a SM instance"
54
66
  msgstr "O evento {} não pode ser chamado sem uma instância de SM"
@@ -63,7 +75,7 @@ msgstr "{!r} não é um valor de estado válido."
63
75
  msgid "Can't {} when in {}."
64
76
  msgstr "Não é possível {} quando em {}."
65
77
 
66
- #: statemachine/factory.py:77
78
+ #: statemachine/factory.py:91
67
79
  #, python-brace-format
68
80
  msgid ""
69
81
  "There should be one and only one initial state. Your currently have "
@@ -72,7 +84,36 @@ msgstr ""
72
84
  "Deve haver um e apenas um estado inicial. Atualmente, você possui estes: "
73
85
  "{0}"
74
86
 
75
- #: statemachine/factory.py:194
87
+ #: statemachine/factory.py:249
88
+ #, python-brace-format
89
+ msgid "is declared in both the '{}' and the '{}' state bodies"
90
+ msgstr "está declarado nos corpos dos estados '{}' e '{}'"
91
+
92
+ #: statemachine/factory.py:253
93
+ #, python-brace-format
94
+ msgid "in the '{}' state body clashes with the event '{}'"
95
+ msgstr "no corpo do estado '{}' conflita com o evento '{}'"
96
+
97
+ #: statemachine/factory.py:257
98
+ #, python-brace-format
99
+ msgid "is declared in both the '{}' state body and the '{}' class body"
100
+ msgstr "está declarado no corpo do estado '{}' e no corpo da classe '{}'"
101
+
102
+ #: statemachine/factory.py:261
103
+ #, python-brace-format
104
+ msgid "in the '{}' state body clashes with the '{}' attribute of '{}'"
105
+ msgstr "no corpo do estado '{}' conflita com o atributo '{}' de '{}'"
106
+
107
+ #: statemachine/factory.py:267
108
+ #, python-brace-format
109
+ msgid ""
110
+ "Callback '{}' {}. Nested state bodies share the class namespace. Rename "
111
+ "one of them."
112
+ msgstr ""
113
+ "Callback '{}' {}. Corpos de estados aninhados compartilham o namespace da"
114
+ " classe. Renomeie um deles."
115
+
116
+ #: statemachine/factory.py:291
76
117
  #, python-brace-format
77
118
  msgid ""
78
119
  "There should be one and only one initial state. You currently have these:"
@@ -81,14 +122,14 @@ msgstr ""
81
122
  "Deve haver um e apenas um estado inicial. Atualmente, você possui estes: "
82
123
  "{!r}"
83
124
 
84
- #: statemachine/factory.py:209
125
+ #: statemachine/factory.py:306
85
126
  #, python-brace-format
86
127
  msgid "Cannot declare transitions from final state. Invalid state(s): {}"
87
128
  msgstr ""
88
129
  "Não é possível declarar transições a partir de um estado final. Estado(s)"
89
130
  " inválido(s): {}"
90
131
 
91
- #: statemachine/factory.py:221
132
+ #: statemachine/factory.py:318
92
133
  #, python-brace-format
93
134
  msgid ""
94
135
  "All non-final states should have at least one outgoing transition. These "
@@ -97,7 +138,7 @@ msgstr ""
97
138
  "Todos os estados não finais devem ter pelo menos uma transição de saída. "
98
139
  "Estes estados não possuem transição de saída: {!r}"
99
140
 
100
- #: statemachine/factory.py:235
141
+ #: statemachine/factory.py:332
101
142
  #, python-brace-format
102
143
  msgid ""
103
144
  "All non-final states should have at least one path to a final state. "
@@ -107,7 +148,7 @@ msgstr ""
107
148
  "estado final. Estes estados não possuem caminho para um estado final: "
108
149
  "{!r}"
109
150
 
110
- #: statemachine/factory.py:248
151
+ #: statemachine/factory.py:345
111
152
  #, python-brace-format
112
153
  msgid ""
113
154
  "There are unreachable states. The statemachine graph should have a single"
@@ -116,7 +157,7 @@ msgstr ""
116
157
  "Há estados inacessíveis. O grafo da máquina de estados deve ter um único "
117
158
  "componente. Estados desconectados: {}"
118
159
 
119
- #: statemachine/factory.py:285
160
+ #: statemachine/factory.py:382
120
161
  #, python-brace-format
121
162
  msgid ""
122
163
  "Invalid entry in 'listeners': {!r}. Expected a class, callable, or "
@@ -125,7 +166,7 @@ msgstr ""
125
166
  "Entrada inválida em 'listeners': {!r}. Esperado uma classe, callable ou "
126
167
  "instância de listener."
127
168
 
128
- #: statemachine/factory.py:384
169
+ #: statemachine/factory.py:481
129
170
  #, python-brace-format
130
171
  msgid "An event in the '{}' has no id."
131
172
  msgstr "Um evento em '{}' não possui id."
@@ -135,15 +176,15 @@ msgstr "Um evento em '{}' não possui id."
135
176
  msgid "{!r} is not a valid state machine name."
136
177
  msgstr "{!r} não é um nome de máquina de estados válido."
137
178
 
138
- #: statemachine/state.py:249
179
+ #: statemachine/state.py:273
139
180
  msgid "'donedata' can only be specified on final states."
140
181
  msgstr "'donedata' só pode ser especificado em estados finais."
141
182
 
142
- #: statemachine/statemachine.py:167
183
+ #: statemachine/statemachine.py:168
143
184
  msgid "There are no states or transitions."
144
185
  msgstr "Não há estados ou transições."
145
186
 
146
- #: statemachine/statemachine.py:237
187
+ #: statemachine/statemachine.py:242
147
188
  #, python-brace-format
148
189
  msgid "State overriding is not allowed. Trying to add '{}' to {}"
149
190
  msgstr "Sobrescrever estados não é permitido. Tentando adicionar '{}' a {}"
@@ -162,6 +203,14 @@ msgstr ""
162
203
  msgid "{} only supports the decorator syntax to register callbacks."
163
204
  msgstr "{} suporta apenas a sintaxe de decorator para registrar callbacks."
164
205
 
206
+ #: statemachine/engines/async_.py:50
207
+ msgid ""
208
+ "The task running the processing loop was cancelled while it processed "
209
+ "this event."
210
+ msgstr ""
211
+ "A tarefa que executava o loop de processamento foi cancelada enquanto "
212
+ "processava este evento."
213
+
165
214
  #~ msgid "There are no states."
166
215
  #~ msgstr "Não há estados."
167
216
 
@@ -5,7 +5,7 @@ msgid ""
5
5
  msgstr ""
6
6
  "Project-Id-Version: 3.1.1\n"
7
7
  "Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
8
- "POT-Creation-Date: 2026-06-16 23:00-0300\n"
8
+ "POT-Creation-Date: 2026-10-13 21:26-0300\n"
9
9
  "PO-Revision-Date: 2024-06-07 17:41-0300\n"
10
10
  "Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
11
11
  "Language: zh_CN\n"
@@ -16,7 +16,7 @@ msgstr ""
16
16
  "Content-Transfer-Encoding: 8bit\n"
17
17
  "Generated-By: Babel 2.18.0\n"
18
18
 
19
- #: statemachine/callbacks.py:398 statemachine/callbacks.py:403
19
+ #: statemachine/callbacks.py:409 statemachine/callbacks.py:414
20
20
  #, python-brace-format
21
21
  msgid "Did not found name '{}' from model or statemachine"
22
22
  msgstr "在模型或状态机中未找到名称 '{}'"
@@ -27,24 +27,34 @@ msgid ""
27
27
  " state? (e.g., `await sm.activate_initial_state()`)"
28
28
  msgstr "没有设置当前状态。在异步代码中,您是否激活了初始状态?(例如,`await sm.activate_initial_state()`)"
29
29
 
30
- #: statemachine/dispatcher.py:123
30
+ #: statemachine/dispatcher.py:127
31
31
  #, python-brace-format
32
32
  msgid "Failed to parse boolean expression '{}'"
33
33
  msgstr "无法解析布尔表达式 '{}'"
34
34
 
35
- #: statemachine/event.py:94
35
+ #: statemachine/event.py:56
36
+ #, python-brace-format
37
+ msgid ""
38
+ "Event {id!r} cannot be both delayed and internal: the internal queue runs"
39
+ " within the current macrostep, which takes no time. Drop 'internal' to "
40
+ "deliver it later on the external queue, or drop 'delay' to run it now."
41
+ msgstr ""
42
+ "事件 {id!r} 不能同时是延迟的和内部的:内部队列在当前宏步骤内运行,不占用时间。去掉 'internal' 以便稍后通过外部队列投递,或去掉"
43
+ " 'delay' 以立即执行。"
44
+
45
+ #: statemachine/event.py:108
36
46
  #, python-brace-format
37
47
  msgid ""
38
48
  "Event() received a non-string 'id' ({cls_name}). To combine multiple "
39
49
  "transitions under one event, use the | operator: t1 | t2."
40
50
  msgstr "Event() 接收到的 'id' 不是字符串 ({cls_name})。要将多个转换组合在同一个事件下,请使用 | 运算符:t1 | t2。"
41
51
 
42
- #: statemachine/event.py:130
52
+ #: statemachine/event.py:147
43
53
  #, python-brace-format
44
54
  msgid "Cannot add callback '{}' to an event with no transitions."
45
55
  msgstr "无法将回调 '{}' 添加到没有转换的事件。"
46
56
 
47
- #: statemachine/event.py:163
57
+ #: statemachine/event.py:182
48
58
  #, python-brace-format
49
59
  msgid "Event {} cannot be called without a SM instance"
50
60
  msgstr "事件 {} 不能在没有 SM 实例的情况下调用"
@@ -59,54 +69,81 @@ msgstr "{!r} 不是有效的状态值。"
59
69
  msgid "Can't {} when in {}."
60
70
  msgstr "在 {} 时无法 {}。"
61
71
 
62
- #: statemachine/factory.py:77
72
+ #: statemachine/factory.py:91
63
73
  #, python-brace-format
64
74
  msgid ""
65
75
  "There should be one and only one initial state. Your currently have "
66
76
  "these: {0}"
67
77
  msgstr "应有且仅有一个初始状态。当前您有这些:{0}"
68
78
 
69
- #: statemachine/factory.py:194
79
+ #: statemachine/factory.py:249
80
+ #, python-brace-format
81
+ msgid "is declared in both the '{}' and the '{}' state bodies"
82
+ msgstr "同时声明在 '{}' 和 '{}' 状态体中"
83
+
84
+ #: statemachine/factory.py:253
85
+ #, python-brace-format
86
+ msgid "in the '{}' state body clashes with the event '{}'"
87
+ msgstr "在 '{}' 状态体中与事件 '{}' 冲突"
88
+
89
+ #: statemachine/factory.py:257
90
+ #, python-brace-format
91
+ msgid "is declared in both the '{}' state body and the '{}' class body"
92
+ msgstr "同时声明在 '{}' 状态体和 '{}' 类体中"
93
+
94
+ #: statemachine/factory.py:261
95
+ #, python-brace-format
96
+ msgid "in the '{}' state body clashes with the '{}' attribute of '{}'"
97
+ msgstr "在 '{0}' 状态体中与 '{2}' 的属性 '{1}' 冲突"
98
+
99
+ #: statemachine/factory.py:267
100
+ #, python-brace-format
101
+ msgid ""
102
+ "Callback '{}' {}. Nested state bodies share the class namespace. Rename "
103
+ "one of them."
104
+ msgstr "回调 '{}' {}。嵌套状态体共享类的命名空间。请重命名其中一个。"
105
+
106
+ #: statemachine/factory.py:291
70
107
  #, python-brace-format
71
108
  msgid ""
72
109
  "There should be one and only one initial state. You currently have these:"
73
110
  " {!r}"
74
111
  msgstr "应有且仅有一个初始状态。当前您有这些:{!r}"
75
112
 
76
- #: statemachine/factory.py:209
113
+ #: statemachine/factory.py:306
77
114
  #, python-brace-format
78
115
  msgid "Cannot declare transitions from final state. Invalid state(s): {}"
79
116
  msgstr "无法从终止状态声明转换。无效状态:{}"
80
117
 
81
- #: statemachine/factory.py:221
118
+ #: statemachine/factory.py:318
82
119
  #, python-brace-format
83
120
  msgid ""
84
121
  "All non-final states should have at least one outgoing transition. These "
85
122
  "states have no outgoing transition: {!r}"
86
123
  msgstr "所有非终止状态都应至少有一个外部转换。这些状态没有外部转换:{!r}"
87
124
 
88
- #: statemachine/factory.py:235
125
+ #: statemachine/factory.py:332
89
126
  #, python-brace-format
90
127
  msgid ""
91
128
  "All non-final states should have at least one path to a final state. "
92
129
  "These states have no path to a final state: {!r}"
93
130
  msgstr "所有非终止状态应至少有一个到终止状态的路径。这些状态没有到终止状态的路径:{!r}"
94
131
 
95
- #: statemachine/factory.py:248
132
+ #: statemachine/factory.py:345
96
133
  #, python-brace-format
97
134
  msgid ""
98
135
  "There are unreachable states. The statemachine graph should have a single"
99
136
  " component. Disconnected states: {}"
100
137
  msgstr "存在不可到达的状态。状态机图应具有单个组件。断开的状态:{}"
101
138
 
102
- #: statemachine/factory.py:285
139
+ #: statemachine/factory.py:382
103
140
  #, python-brace-format
104
141
  msgid ""
105
142
  "Invalid entry in 'listeners': {!r}. Expected a class, callable, or "
106
143
  "listener instance."
107
144
  msgstr "'listeners' 中的条目无效:{!r}。期望为类、可调用对象或监听器实例。"
108
145
 
109
- #: statemachine/factory.py:384
146
+ #: statemachine/factory.py:481
110
147
  #, python-brace-format
111
148
  msgid "An event in the '{}' has no id."
112
149
  msgstr "'{}' 中的事件没有 ID。"
@@ -116,15 +153,15 @@ msgstr "'{}' 中的事件没有 ID。"
116
153
  msgid "{!r} is not a valid state machine name."
117
154
  msgstr "{!r} 不是有效的状态机名称。"
118
155
 
119
- #: statemachine/state.py:249
156
+ #: statemachine/state.py:273
120
157
  msgid "'donedata' can only be specified on final states."
121
158
  msgstr "'donedata' 只能在终止状态上指定。"
122
159
 
123
- #: statemachine/statemachine.py:167
160
+ #: statemachine/statemachine.py:168
124
161
  msgid "There are no states or transitions."
125
162
  msgstr "没有状态或转换。"
126
163
 
127
- #: statemachine/statemachine.py:237
164
+ #: statemachine/statemachine.py:242
128
165
  #, python-brace-format
129
166
  msgid "State overriding is not allowed. Trying to add '{}' to {}"
130
167
  msgstr "不允许覆盖状态。尝试将 '{}' 添加到 {}"
@@ -141,6 +178,12 @@ msgstr "从源 {source!r} 的内部转换无效,目标 {target!r} 应为自身
141
178
  msgid "{} only supports the decorator syntax to register callbacks."
142
179
  msgstr "{} 仅支持使用装饰器语法注册回调。"
143
180
 
181
+ #: statemachine/engines/async_.py:50
182
+ msgid ""
183
+ "The task running the processing loop was cancelled while it processed "
184
+ "this event."
185
+ msgstr "运行处理循环的任务在处理此事件时被取消。"
186
+
144
187
  #~ msgid "There are no states."
145
188
  #~ msgstr "没有状态。"
146
189
 
@@ -62,10 +62,14 @@ class OrderedSet(MutableSet[T]):
62
62
  >>> s[2]
63
63
  3
64
64
 
65
- >>> eval(repr(OrderedSet(['a', 'b', 'c'])))
66
- OrderedSet(['a', 'b', 'c'])
65
+ >>> s[-1]
66
+ 3
67
67
 
68
+ >>> s[-3]
69
+ 1
68
70
 
71
+ >>> eval(repr(OrderedSet(['a', 'b', 'c'])))
72
+ OrderedSet(['a', 'b', 'c'])
69
73
 
70
74
  """
71
75
 
@@ -84,10 +88,15 @@ class OrderedSet(MutableSet[T]):
84
88
  self._d.pop(x, None)
85
89
 
86
90
  def __getitem__(self, index) -> T:
91
+ original_index = index
92
+ if index < 0:
93
+ index += len(self)
94
+ if index < 0:
95
+ raise IndexError(f"index {original_index} out of range")
87
96
  try:
88
97
  return next(itertools.islice(self._d, index, index + 1))
89
98
  except StopIteration as e:
90
- raise IndexError(f"index {index} out of range") from e
99
+ raise IndexError(f"index {original_index} out of range") from e
91
100
 
92
101
  def __contains__(self, x: object) -> bool:
93
102
  return self._d.__contains__(x)
statemachine/registry.py CHANGED
@@ -1,5 +1,10 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from .utils import qualname
2
4
 
5
+ if TYPE_CHECKING:
6
+ from .statemachine import StateMachine
7
+
3
8
  try:
4
9
  from django.utils.module_loading import autodiscover_modules
5
10
  except ImportError: # pragma: no cover
@@ -8,7 +13,7 @@ except ImportError: # pragma: no cover
8
13
  pass
9
14
 
10
15
 
11
- _REGISTRY = {}
16
+ _REGISTRY: "dict[str, type[StateMachine]]" = {}
12
17
  _initialized = False
13
18
 
14
19