python-statemachine 3.1.2__py3-none-any.whl → 3.2.0__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.
- {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/METADATA +16 -3
- python_statemachine-3.2.0.dist-info/RECORD +72 -0
- {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/WHEEL +1 -1
- statemachine/__init__.py +1 -1
- statemachine/callbacks.py +5 -11
- statemachine/configuration.py +5 -6
- statemachine/contrib/diagram/extract.py +23 -24
- statemachine/contrib/diagram/formatter.py +5 -7
- statemachine/contrib/diagram/model.py +9 -11
- statemachine/contrib/diagram/renderers/dot.py +20 -26
- statemachine/contrib/diagram/renderers/mermaid.py +36 -40
- statemachine/contrib/diagram/renderers/table.py +7 -9
- statemachine/contrib/weighted.py +7 -11
- statemachine/dispatcher.py +13 -12
- statemachine/engines/async_.py +5 -6
- statemachine/engines/base.py +12 -14
- statemachine/event.py +1 -2
- statemachine/exceptions.py +1 -1
- statemachine/factory.py +11 -15
- statemachine/graph.py +2 -2
- statemachine/invoke.py +12 -11
- statemachine/io/__init__.py +45 -225
- statemachine/io/{scxml/actions.py → actions.py} +158 -288
- statemachine/io/builder.py +195 -0
- statemachine/io/class_factory.py +236 -0
- statemachine/io/evaluators.py +275 -0
- statemachine/io/interpreter.py +128 -0
- statemachine/io/{scxml/invoke.py → invoke.py} +77 -49
- statemachine/io/json/__init__.py +1 -0
- statemachine/io/json/reader.py +27 -0
- statemachine/io/loader.py +161 -0
- statemachine/io/model.py +268 -0
- statemachine/io/native.py +402 -0
- statemachine/io/ports.py +83 -0
- statemachine/io/schemas/statechart.schema.json +258 -0
- statemachine/io/scxml/__init__.py +12 -0
- statemachine/io/scxml/processor.py +23 -253
- statemachine/io/scxml/{parser.py → reader.py} +64 -47
- statemachine/io/system_variables.py +184 -0
- statemachine/io/validation.py +44 -0
- statemachine/io/yaml/__init__.py +1 -0
- statemachine/io/yaml/reader.py +65 -0
- statemachine/locale/en/LC_MESSAGES/statemachine.po +19 -19
- statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +19 -19
- statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +19 -19
- statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +19 -19
- statemachine/orderedset.py +3 -3
- statemachine/registry.py +1 -4
- statemachine/signature.py +2 -5
- statemachine/spec_parser.py +171 -42
- statemachine/state.py +5 -6
- statemachine/statemachine.py +18 -20
- statemachine/states.py +3 -5
- statemachine/transition.py +3 -4
- statemachine/transition_list.py +4 -5
- statemachine/transition_mixin.py +1 -1
- python_statemachine-3.1.2.dist-info/RECORD +0 -58
- statemachine/io/scxml/schema.py +0 -175
- {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://fgmacedo.github.io/python-statemachine/schemas/statechart/v1.json",
|
|
4
|
+
"title": "python-statemachine statechart",
|
|
5
|
+
"description": "Declarative statechart definition for python-statemachine (JSON/YAML native format).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["states"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": { "type": "string" },
|
|
10
|
+
"description": { "type": "string" },
|
|
11
|
+
"datamodel": {
|
|
12
|
+
"oneOf": [
|
|
13
|
+
{ "type": "object", "additionalProperties": true },
|
|
14
|
+
{ "type": "array", "items": { "$ref": "#/$defs/dataItem" } }
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"states": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"minProperties": 1,
|
|
20
|
+
"additionalProperties": { "$ref": "#/$defs/state" }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"$defs": {
|
|
25
|
+
"flag": {
|
|
26
|
+
"oneOf": [
|
|
27
|
+
{ "type": "boolean" },
|
|
28
|
+
{ "type": "string", "enum": ["true", "false", "yes", "no", "on", "off"] }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"guard": {
|
|
32
|
+
"oneOf": [
|
|
33
|
+
{ "type": "string" },
|
|
34
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"dataItem": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["id"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"id": { "type": "string" },
|
|
42
|
+
"expr": { "type": "string" },
|
|
43
|
+
"content": {},
|
|
44
|
+
"src": { "type": "string" }
|
|
45
|
+
},
|
|
46
|
+
"additionalProperties": false
|
|
47
|
+
},
|
|
48
|
+
"param": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"required": ["name"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"name": { "type": "string" },
|
|
53
|
+
"expr": { "type": "string" },
|
|
54
|
+
"location": { "type": "string" }
|
|
55
|
+
},
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
},
|
|
58
|
+
"structuredAction": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"minProperties": 1,
|
|
61
|
+
"maxProperties": 1,
|
|
62
|
+
"properties": {
|
|
63
|
+
"assign": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"required": ["location"],
|
|
66
|
+
"properties": {
|
|
67
|
+
"location": { "type": "string" },
|
|
68
|
+
"expr": { "type": "string" }
|
|
69
|
+
},
|
|
70
|
+
"additionalProperties": false
|
|
71
|
+
},
|
|
72
|
+
"raise": {
|
|
73
|
+
"oneOf": [
|
|
74
|
+
{ "type": "string" },
|
|
75
|
+
{
|
|
76
|
+
"type": "object",
|
|
77
|
+
"required": ["event"],
|
|
78
|
+
"properties": { "event": { "type": "string" } },
|
|
79
|
+
"additionalProperties": false
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"log": {
|
|
84
|
+
"oneOf": [
|
|
85
|
+
{ "type": "string" },
|
|
86
|
+
{
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"label": { "type": "string" },
|
|
90
|
+
"expr": { "type": "string" }
|
|
91
|
+
},
|
|
92
|
+
"additionalProperties": false
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"script": { "type": "string" },
|
|
97
|
+
"cancel": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"properties": {
|
|
100
|
+
"sendid": { "type": "string" },
|
|
101
|
+
"sendidexpr": { "type": "string" }
|
|
102
|
+
},
|
|
103
|
+
"additionalProperties": false
|
|
104
|
+
},
|
|
105
|
+
"send": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"properties": {
|
|
108
|
+
"event": { "type": "string" },
|
|
109
|
+
"eventexpr": { "type": "string" },
|
|
110
|
+
"target": { "type": "string" },
|
|
111
|
+
"type": { "type": "string" },
|
|
112
|
+
"id": { "type": "string" },
|
|
113
|
+
"idlocation": { "type": "string" },
|
|
114
|
+
"delay": { "type": "string" },
|
|
115
|
+
"delayexpr": { "type": "string" },
|
|
116
|
+
"namelist": { "type": "string" },
|
|
117
|
+
"params": { "type": "array", "items": { "$ref": "#/$defs/param" } },
|
|
118
|
+
"content": { "type": "string" }
|
|
119
|
+
},
|
|
120
|
+
"additionalProperties": false
|
|
121
|
+
},
|
|
122
|
+
"foreach": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"required": ["array", "item"],
|
|
125
|
+
"properties": {
|
|
126
|
+
"array": { "type": "string" },
|
|
127
|
+
"item": { "type": "string" },
|
|
128
|
+
"index": { "type": "string" },
|
|
129
|
+
"do": { "$ref": "#/$defs/structuredActions" }
|
|
130
|
+
},
|
|
131
|
+
"additionalProperties": false
|
|
132
|
+
},
|
|
133
|
+
"if": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"required": ["cond"],
|
|
136
|
+
"properties": {
|
|
137
|
+
"cond": { "type": "string" },
|
|
138
|
+
"then": { "$ref": "#/$defs/structuredActions" },
|
|
139
|
+
"elif": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"required": ["cond"],
|
|
144
|
+
"properties": {
|
|
145
|
+
"cond": { "type": "string" },
|
|
146
|
+
"then": { "$ref": "#/$defs/structuredActions" }
|
|
147
|
+
},
|
|
148
|
+
"additionalProperties": false
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"else": { "$ref": "#/$defs/structuredActions" }
|
|
152
|
+
},
|
|
153
|
+
"additionalProperties": false
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"additionalProperties": false
|
|
157
|
+
},
|
|
158
|
+
"action": {
|
|
159
|
+
"oneOf": [
|
|
160
|
+
{ "type": "string" },
|
|
161
|
+
{ "$ref": "#/$defs/structuredAction" }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"actions": {
|
|
165
|
+
"oneOf": [
|
|
166
|
+
{ "$ref": "#/$defs/action" },
|
|
167
|
+
{ "type": "array", "items": { "$ref": "#/$defs/action" } }
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
"structuredActions": {
|
|
171
|
+
"oneOf": [
|
|
172
|
+
{ "$ref": "#/$defs/structuredAction" },
|
|
173
|
+
{ "type": "array", "items": { "$ref": "#/$defs/structuredAction" } }
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
"invoke": {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"properties": {
|
|
179
|
+
"type": { "type": "string" },
|
|
180
|
+
"typeexpr": { "type": "string" },
|
|
181
|
+
"src": { "type": "string" },
|
|
182
|
+
"srcexpr": { "type": "string" },
|
|
183
|
+
"id": { "type": "string" },
|
|
184
|
+
"idlocation": { "type": "string" },
|
|
185
|
+
"autoforward": { "$ref": "#/$defs/flag" },
|
|
186
|
+
"namelist": { "type": "string" },
|
|
187
|
+
"params": { "type": "array", "items": { "$ref": "#/$defs/param" } },
|
|
188
|
+
"content": {
|
|
189
|
+
"oneOf": [
|
|
190
|
+
{ "type": "string" },
|
|
191
|
+
{ "$ref": "#" }
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
"finalize": { "$ref": "#/$defs/structuredActions" }
|
|
195
|
+
},
|
|
196
|
+
"additionalProperties": false
|
|
197
|
+
},
|
|
198
|
+
"invokeList": {
|
|
199
|
+
"oneOf": [
|
|
200
|
+
{ "$ref": "#/$defs/invoke" },
|
|
201
|
+
{ "type": "array", "items": { "$ref": "#/$defs/invoke" } }
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
"transition": {
|
|
205
|
+
"type": "object",
|
|
206
|
+
"properties": {
|
|
207
|
+
"event": { "type": "string" },
|
|
208
|
+
"target": { "type": "string" },
|
|
209
|
+
"cond": { "$ref": "#/$defs/guard" },
|
|
210
|
+
"unless": { "$ref": "#/$defs/guard" },
|
|
211
|
+
"internal": { "$ref": "#/$defs/flag" },
|
|
212
|
+
"initial": { "$ref": "#/$defs/flag" },
|
|
213
|
+
"on": { "$ref": "#/$defs/actions" },
|
|
214
|
+
"before": { "$ref": "#/$defs/actions" },
|
|
215
|
+
"after": { "$ref": "#/$defs/actions" }
|
|
216
|
+
},
|
|
217
|
+
"additionalProperties": false
|
|
218
|
+
},
|
|
219
|
+
"history": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"properties": {
|
|
222
|
+
"type": { "type": "string", "enum": ["shallow", "deep"] },
|
|
223
|
+
"transitions": { "type": "array", "items": { "$ref": "#/$defs/transition" } }
|
|
224
|
+
},
|
|
225
|
+
"additionalProperties": false
|
|
226
|
+
},
|
|
227
|
+
"donedata": {
|
|
228
|
+
"type": "object",
|
|
229
|
+
"properties": {
|
|
230
|
+
"params": { "type": "array", "items": { "$ref": "#/$defs/param" } },
|
|
231
|
+
"content": { "type": "string" }
|
|
232
|
+
},
|
|
233
|
+
"additionalProperties": false
|
|
234
|
+
},
|
|
235
|
+
"state": {
|
|
236
|
+
"type": "object",
|
|
237
|
+
"properties": {
|
|
238
|
+
"initial": { "$ref": "#/$defs/flag" },
|
|
239
|
+
"final": { "$ref": "#/$defs/flag" },
|
|
240
|
+
"parallel": { "$ref": "#/$defs/flag" },
|
|
241
|
+
"enter": { "$ref": "#/$defs/actions" },
|
|
242
|
+
"exit": { "$ref": "#/$defs/actions" },
|
|
243
|
+
"transitions": { "type": "array", "items": { "$ref": "#/$defs/transition" } },
|
|
244
|
+
"invoke": { "$ref": "#/$defs/invokeList" },
|
|
245
|
+
"states": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"additionalProperties": { "$ref": "#/$defs/state" }
|
|
248
|
+
},
|
|
249
|
+
"history": {
|
|
250
|
+
"type": "object",
|
|
251
|
+
"additionalProperties": { "$ref": "#/$defs/history" }
|
|
252
|
+
},
|
|
253
|
+
"donedata": { "$ref": "#/$defs/donedata" }
|
|
254
|
+
},
|
|
255
|
+
"additionalProperties": false
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""SCXML ingestion support.
|
|
2
|
+
|
|
3
|
+
SCXML is, per the W3C specification, *executable content*: ``cond``/``expr`` attributes and
|
|
4
|
+
``<script>`` elements are evaluated in the document's datamodel language. This implementation
|
|
5
|
+
provides a Python datamodel and loads SCXML through the same
|
|
6
|
+
:func:`~statemachine.io.load` facade as the native JSON/YAML formats.
|
|
7
|
+
|
|
8
|
+
Like every format, SCXML is loaded **secure by default** (``trusted=False`` rejects
|
|
9
|
+
``<script>`` and evaluates expressions with the restricted AST-whitelist evaluator); pass
|
|
10
|
+
``trusted=True`` only for documents you control. See the security note in
|
|
11
|
+
:mod:`statemachine.io` and the GHSA-v4jc-pm6r-3vj8 advisory.
|
|
12
|
+
"""
|
|
@@ -1,263 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
from contextlib import contextmanager
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
6
|
-
from typing import Dict
|
|
7
|
-
from typing import List
|
|
1
|
+
"""Back-compatible SCXML entry point: a preconfigured neutral Interpreter.
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
from .. import TransitionsList
|
|
16
|
-
from .. import create_machine_class_from_definition
|
|
17
|
-
from .actions import Cond
|
|
18
|
-
from .actions import DoneDataCallable
|
|
19
|
-
from .actions import EventDataWrapper
|
|
20
|
-
from .actions import ExecuteBlock
|
|
21
|
-
from .actions import create_datamodel_action_callable
|
|
22
|
-
from .actions import create_invoke_init_callable
|
|
23
|
-
from .invoke import SCXMLInvoker
|
|
24
|
-
from .parser import parse_scxml
|
|
25
|
-
from .schema import HistoryState
|
|
26
|
-
from .schema import InvokeDefinition
|
|
27
|
-
from .schema import State
|
|
28
|
-
from .schema import Transition
|
|
3
|
+
The runtime is the format-neutral :class:`~statemachine.io.interpreter.Interpreter`.
|
|
4
|
+
``SCXMLProcessor`` is simply that interpreter wired with the SCXML reader and the
|
|
5
|
+
trusted/restricted evaluator, plus the ``parse_scxml`` convenience for parsing a document
|
|
6
|
+
from a string. New code should prefer :func:`statemachine.io.load` (which also reads files
|
|
7
|
+
and detects the format from the extension).
|
|
8
|
+
"""
|
|
29
9
|
|
|
10
|
+
from ..evaluators import evaluator_for
|
|
11
|
+
from ..interpreter import Interpreter
|
|
12
|
+
from .reader import SCXMLReader
|
|
13
|
+
from .reader import parse_scxml
|
|
30
14
|
|
|
31
|
-
@contextmanager
|
|
32
|
-
def temporary_directory(new_current_dir):
|
|
33
|
-
original_dir = os.getcwd()
|
|
34
|
-
try:
|
|
35
|
-
os.chdir(new_current_dir)
|
|
36
|
-
yield
|
|
37
|
-
finally:
|
|
38
|
-
os.chdir(original_dir)
|
|
39
15
|
|
|
16
|
+
class SCXMLProcessor(Interpreter):
|
|
17
|
+
"""Parses SCXML documents into :class:`~statemachine.statemachine.StateChart` classes.
|
|
40
18
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
19
|
+
Args:
|
|
20
|
+
trusted: when ``False`` (default), datamodel expressions are evaluated by
|
|
21
|
+
a restricted AST-whitelist evaluator and ``<script>`` is rejected, so
|
|
22
|
+
loading a document cannot execute arbitrary code. When ``True``,
|
|
23
|
+
expressions and ``<script>`` are evaluated as arbitrary Python via
|
|
24
|
+
``eval``/``exec`` — only use it for SCXML you trust (see the package
|
|
25
|
+
docstring and GHSA-v4jc-pm6r-3vj8).
|
|
26
|
+
"""
|
|
45
27
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def location(self):
|
|
51
|
-
return self.machine.name
|
|
52
|
-
|
|
53
|
-
def get(self, name: str):
|
|
54
|
-
return getattr(self, name)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@dataclass
|
|
58
|
-
class SessionData:
|
|
59
|
-
machine: StateChart
|
|
60
|
-
processor: IOProcessor
|
|
61
|
-
first_event_raised: bool = False
|
|
62
|
-
|
|
63
|
-
def __post_init__(self):
|
|
64
|
-
self.session_id = f"{self.machine.name}:{id(self.machine)}"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class SCXMLProcessor:
|
|
68
|
-
def __init__(self):
|
|
69
|
-
self.scs: "Dict[str, type[StateChart]]" = {}
|
|
70
|
-
self.sessions: Dict[str, SessionData] = {}
|
|
71
|
-
self._ioprocessors = {
|
|
72
|
-
"http://www.w3.org/TR/scxml/#SCXMLEventProcessor": self,
|
|
73
|
-
"scxml": self,
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
def parse_scxml_file(self, path: Path):
|
|
77
|
-
scxml_content = path.read_text()
|
|
78
|
-
with temporary_directory(path.parent):
|
|
79
|
-
return self.parse_scxml(path.stem, scxml_content)
|
|
28
|
+
def __init__(self, trusted: bool = False):
|
|
29
|
+
super().__init__(reader=SCXMLReader(), evaluator=evaluator_for(trusted))
|
|
80
30
|
|
|
81
31
|
def parse_scxml(self, sm_name: str, scxml_content: str):
|
|
82
32
|
definition = parse_scxml(scxml_content)
|
|
83
33
|
self.process_definition(definition, location=definition.name or sm_name)
|
|
84
|
-
|
|
85
|
-
def process_definition(self, definition, location: str, is_invoked: bool = False):
|
|
86
|
-
states_dict = self._process_states(definition.states)
|
|
87
|
-
|
|
88
|
-
# Find the initial state for inserting init callbacks
|
|
89
|
-
try:
|
|
90
|
-
initial_state = next(s for s in iter(states_dict.values()) if s.get("initial"))
|
|
91
|
-
except StopIteration:
|
|
92
|
-
initial_state = next(iter(states_dict.values()))
|
|
93
|
-
|
|
94
|
-
if "enter" not in initial_state:
|
|
95
|
-
initial_state["enter"] = []
|
|
96
|
-
|
|
97
|
-
insert_pos = 0
|
|
98
|
-
|
|
99
|
-
# For invoked children, insert invoke_init to pop _invoke_session/_invoke_params
|
|
100
|
-
# from kwargs and store them on the machine instance before any other callbacks.
|
|
101
|
-
if is_invoked:
|
|
102
|
-
initial_state["enter"].insert(0, create_invoke_init_callable()) # type: ignore[union-attr]
|
|
103
|
-
insert_pos = 1
|
|
104
|
-
|
|
105
|
-
# Process datamodel (initial variables)
|
|
106
|
-
if definition.datamodel:
|
|
107
|
-
datamodel = create_datamodel_action_callable(definition.datamodel)
|
|
108
|
-
if datamodel: # pragma: no branch – parse_datamodel guarantees non-empty
|
|
109
|
-
if isinstance( # pragma: no branch – always a list from lines above
|
|
110
|
-
initial_state["enter"], list
|
|
111
|
-
):
|
|
112
|
-
initial_state["enter"].insert(insert_pos, datamodel) # type: ignore[arg-type]
|
|
113
|
-
|
|
114
|
-
self._add(
|
|
115
|
-
location,
|
|
116
|
-
{
|
|
117
|
-
"states": states_dict,
|
|
118
|
-
"prepare_event": self._prepare_event,
|
|
119
|
-
"validate_disconnected_states": False,
|
|
120
|
-
"validate_trap_states": False,
|
|
121
|
-
"validate_final_reachability": False,
|
|
122
|
-
"start_configuration_values": list(definition.initial_states),
|
|
123
|
-
},
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def _prepare_event(self, *args, event: Event, **kwargs):
|
|
127
|
-
machine = kwargs["machine"]
|
|
128
|
-
session_data = self._get_session(machine)
|
|
129
|
-
|
|
130
|
-
if not session_data.first_event_raised and event and event != "__initial__":
|
|
131
|
-
session_data.first_event_raised = True
|
|
132
|
-
|
|
133
|
-
_event: "EventDataWrapper | None" = None
|
|
134
|
-
if session_data.first_event_raised:
|
|
135
|
-
_event = EventDataWrapper(kwargs["event_data"])
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
"_name": machine.name,
|
|
139
|
-
"_sessionid": session_data.session_id,
|
|
140
|
-
"_ioprocessors": session_data.processor,
|
|
141
|
-
"_event": _event,
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
def _get_session(self, machine: StateChart):
|
|
145
|
-
if machine.name not in self.sessions:
|
|
146
|
-
self.sessions[machine.name] = SessionData(
|
|
147
|
-
processor=IOProcessor(self, machine=machine), machine=machine
|
|
148
|
-
)
|
|
149
|
-
return self.sessions[machine.name]
|
|
150
|
-
|
|
151
|
-
def _process_history(self, history: Dict[str, HistoryState]) -> Dict[str, HistoryDefinition]:
|
|
152
|
-
states_dict: Dict[str, HistoryDefinition] = {}
|
|
153
|
-
for state_id, state in history.items():
|
|
154
|
-
state_dict = HistoryDefinition()
|
|
155
|
-
|
|
156
|
-
state_dict["type"] = state.type
|
|
157
|
-
|
|
158
|
-
# Process transitions
|
|
159
|
-
if state.transitions:
|
|
160
|
-
state_dict["transitions"] = self._process_transitions(state.transitions)
|
|
161
|
-
|
|
162
|
-
states_dict[state_id] = state_dict
|
|
163
|
-
|
|
164
|
-
return states_dict
|
|
165
|
-
|
|
166
|
-
def _process_states(self, states: Dict[str, State]) -> Dict[str, StateDefinition]:
|
|
167
|
-
states_dict: Dict[str, StateDefinition] = {}
|
|
168
|
-
for state_id, state in states.items():
|
|
169
|
-
states_dict[state_id] = self._process_state(state)
|
|
170
|
-
return states_dict
|
|
171
|
-
|
|
172
|
-
def _process_state(self, state: State) -> StateDefinition: # noqa: C901
|
|
173
|
-
state_dict = StateDefinition()
|
|
174
|
-
if state.initial:
|
|
175
|
-
state_dict["initial"] = True
|
|
176
|
-
if state.final:
|
|
177
|
-
state_dict["final"] = True
|
|
178
|
-
if state.parallel:
|
|
179
|
-
state_dict["parallel"] = True
|
|
180
|
-
|
|
181
|
-
# Process enter actions
|
|
182
|
-
enter_callables: list = [
|
|
183
|
-
ExecuteBlock(content) for content in state.onentry if not content.is_empty
|
|
184
|
-
]
|
|
185
|
-
if enter_callables:
|
|
186
|
-
state_dict["enter"] = enter_callables
|
|
187
|
-
if state.final and state.donedata:
|
|
188
|
-
state_dict["donedata"] = DoneDataCallable(state.donedata)
|
|
189
|
-
|
|
190
|
-
# Process exit actions
|
|
191
|
-
if state.onexit:
|
|
192
|
-
callables = [ExecuteBlock(content) for content in state.onexit if not content.is_empty]
|
|
193
|
-
state_dict["exit"] = callables
|
|
194
|
-
|
|
195
|
-
# Process transitions
|
|
196
|
-
if state.transitions:
|
|
197
|
-
state_dict["transitions"] = self._process_transitions(state.transitions)
|
|
198
|
-
|
|
199
|
-
# Process invoke elements
|
|
200
|
-
if state.invocations:
|
|
201
|
-
invokers = [self._process_invocation(inv) for inv in state.invocations]
|
|
202
|
-
state_dict["invoke"] = invokers # type: ignore[typeddict-unknown-key]
|
|
203
|
-
|
|
204
|
-
if state.states:
|
|
205
|
-
state_dict["states"] = self._process_states(state.states)
|
|
206
|
-
|
|
207
|
-
if state.history:
|
|
208
|
-
state_dict["history"] = self._process_history(state.history)
|
|
209
|
-
|
|
210
|
-
return state_dict
|
|
211
|
-
|
|
212
|
-
def _process_invocation(self, invoke_def: InvokeDefinition) -> SCXMLInvoker:
|
|
213
|
-
"""Convert an InvokeDefinition into an SCXMLInvoker."""
|
|
214
|
-
return SCXMLInvoker(
|
|
215
|
-
definition=invoke_def,
|
|
216
|
-
base_dir=os.getcwd(),
|
|
217
|
-
register_child=self._register_child,
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
def _register_child(self, scxml_content: str, child_name: str) -> type:
|
|
221
|
-
"""Parse SCXML content, register it as a child machine, and return its class."""
|
|
222
|
-
definition = parse_scxml(scxml_content)
|
|
223
|
-
self.process_definition(definition, location=child_name, is_invoked=True)
|
|
224
|
-
return self.scs[child_name]
|
|
225
|
-
|
|
226
|
-
def _process_transitions(self, transitions: List[Transition]):
|
|
227
|
-
result: TransitionsList = []
|
|
228
|
-
for transition in transitions:
|
|
229
|
-
event = transition.event or None
|
|
230
|
-
transition_dict: TransitionDict = {
|
|
231
|
-
"event": event,
|
|
232
|
-
"target": transition.target,
|
|
233
|
-
"internal": transition.internal,
|
|
234
|
-
"initial": transition.initial,
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
# Process cond
|
|
238
|
-
if transition.cond:
|
|
239
|
-
cond_callable = Cond.create(transition.cond, processor=self)
|
|
240
|
-
if cond_callable is not None: # pragma: no branch – cond already truthy
|
|
241
|
-
transition_dict["cond"] = cond_callable
|
|
242
|
-
|
|
243
|
-
# Process actions
|
|
244
|
-
if transition.on and not transition.on.is_empty:
|
|
245
|
-
transition_dict["on"] = ExecuteBlock(transition.on)
|
|
246
|
-
|
|
247
|
-
result.append(transition_dict)
|
|
248
|
-
return result
|
|
249
|
-
|
|
250
|
-
def _add(self, location: str, definition: Dict[str, Any]):
|
|
251
|
-
try:
|
|
252
|
-
sc_class = create_machine_class_from_definition(location, **definition)
|
|
253
|
-
self.scs[location] = sc_class
|
|
254
|
-
return sc_class
|
|
255
|
-
except Exception as e: # pragma: no cover
|
|
256
|
-
raise InvalidDefinition(
|
|
257
|
-
f"Failed to create state machine class: {e} from definition: {definition}"
|
|
258
|
-
) from e
|
|
259
|
-
|
|
260
|
-
def start(self, **kwargs):
|
|
261
|
-
self.root_cls = next(iter(self.scs.values()))
|
|
262
|
-
self.root = self.root_cls(**kwargs)
|
|
263
|
-
return self.root
|