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,27 @@
|
|
|
1
|
+
"""JSON format adapter: parse a JSON statechart document into the neutral IR."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from ..model import StateMachineDefinition
|
|
6
|
+
from ..native import native_dict_to_definition
|
|
7
|
+
from ..ports import FormatSpec
|
|
8
|
+
from ..ports import register_format
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class JSONReader:
|
|
12
|
+
"""Format adapter that parses JSON documents (stdlib :mod:`json`) into the IR."""
|
|
13
|
+
|
|
14
|
+
def parse_document(self, text: str) -> dict:
|
|
15
|
+
return json.loads(text) # type: ignore[no-any-return]
|
|
16
|
+
|
|
17
|
+
def read(self, text: str, *, source_name: "str | None" = None) -> StateMachineDefinition:
|
|
18
|
+
return native_dict_to_definition(self.parse_document(text), source_name=source_name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
register_format(
|
|
22
|
+
FormatSpec(
|
|
23
|
+
name="json",
|
|
24
|
+
extensions=(".json",),
|
|
25
|
+
reader_factory=JSONReader,
|
|
26
|
+
)
|
|
27
|
+
)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""High-level, format-neutral facade for loading statecharts.
|
|
2
|
+
|
|
3
|
+
:func:`load` is the simple entry point: give it a file path (format detected by
|
|
4
|
+
extension) or inline content (with an explicit ``format=``) and it returns the
|
|
5
|
+
ready-to-instantiate :class:`~statemachine.statemachine.StateChart` class. It is
|
|
6
|
+
**secure by default** — expressions are evaluated by a restricted AST-whitelist
|
|
7
|
+
evaluator and ``<script>`` / arbitrary Python is rejected unless ``trusted=True``.
|
|
8
|
+
|
|
9
|
+
For advanced scenarios (a document declaring several statecharts, or SCXML files
|
|
10
|
+
that import/invoke others), use :func:`build_processor` to get the underlying
|
|
11
|
+
processor and reach ``.scs`` / ``.start()``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import cast
|
|
18
|
+
|
|
19
|
+
from ..statemachine import StateChart
|
|
20
|
+
from .evaluators import evaluator_for
|
|
21
|
+
from .interpreter import Interpreter
|
|
22
|
+
from .json import reader as _json_reader # noqa: F401
|
|
23
|
+
from .ports import detect_format
|
|
24
|
+
from .ports import get_format
|
|
25
|
+
|
|
26
|
+
# Importing the reader modules registers their formats (extensions). None of these
|
|
27
|
+
# imports pulls in PyYAML or jsonschema at module load time.
|
|
28
|
+
from .scxml import reader as _scxml_reader # noqa: F401
|
|
29
|
+
from .yaml import reader as _yaml_reader # noqa: F401
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextmanager
|
|
33
|
+
def _chdir(new_dir: Path):
|
|
34
|
+
original = os.getcwd()
|
|
35
|
+
try:
|
|
36
|
+
os.chdir(new_dir)
|
|
37
|
+
yield
|
|
38
|
+
finally:
|
|
39
|
+
os.chdir(original)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_source(source: "str | Path", format: "str | None"):
|
|
43
|
+
"""Return ``(text, format_name, location_hint, base_dir)`` for a source.
|
|
44
|
+
|
|
45
|
+
A :class:`~pathlib.Path`, or a single-line string naming an existing file, is
|
|
46
|
+
read from disk (format detected by extension unless overridden). Any other
|
|
47
|
+
string is treated as inline content and requires an explicit ``format``.
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(source, Path):
|
|
50
|
+
path: "Path | None" = source
|
|
51
|
+
elif "\n" not in source and Path(source).is_file():
|
|
52
|
+
path = Path(source)
|
|
53
|
+
else:
|
|
54
|
+
path = None
|
|
55
|
+
|
|
56
|
+
if path is not None:
|
|
57
|
+
text = path.read_text()
|
|
58
|
+
fmt = detect_format(path, format)
|
|
59
|
+
return text, fmt, path.stem, path.parent
|
|
60
|
+
|
|
61
|
+
fmt = detect_format(Path("<inline>"), format)
|
|
62
|
+
return source, fmt, None, None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _build(source, *, format, trusted, validate, name):
|
|
66
|
+
"""Shared pipeline: read source -> IR -> interpreter (returns ``(interpreter, location)``)."""
|
|
67
|
+
text, fmt, location_hint, base_dir = _resolve_source(source, format)
|
|
68
|
+
spec = get_format(fmt)
|
|
69
|
+
reader = spec.reader_factory()
|
|
70
|
+
|
|
71
|
+
parse_document = getattr(reader, "parse_document", None)
|
|
72
|
+
if validate and parse_document is None:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"validate=True is not supported for the {fmt!r} format; "
|
|
75
|
+
"validation applies to the native JSON/YAML schema."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def _read_and_build():
|
|
79
|
+
# Parsing and compilation both run here, inside the file's directory when loading
|
|
80
|
+
# from a file, so a reader that resolves external references (e.g. SCXML
|
|
81
|
+
# ``<data src="...">``) and invoke ``src`` resolve relative to the document.
|
|
82
|
+
if parse_document is not None:
|
|
83
|
+
doc = parse_document(text)
|
|
84
|
+
if validate:
|
|
85
|
+
from .validation import validate_document
|
|
86
|
+
|
|
87
|
+
validate_document(doc)
|
|
88
|
+
from .native import native_dict_to_definition
|
|
89
|
+
|
|
90
|
+
definition = native_dict_to_definition(doc, source_name=name or location_hint)
|
|
91
|
+
else:
|
|
92
|
+
definition = reader.read(text, source_name=name or location_hint)
|
|
93
|
+
|
|
94
|
+
location = name or definition.name or location_hint or "statechart"
|
|
95
|
+
# The runtime is the format-neutral Interpreter, wired with this format's reader
|
|
96
|
+
# (so invoked children compile in the same format) and the chosen evaluator.
|
|
97
|
+
interpreter = Interpreter(reader=reader, evaluator=evaluator_for(trusted))
|
|
98
|
+
interpreter.process_definition(definition, location=location)
|
|
99
|
+
return interpreter, location
|
|
100
|
+
|
|
101
|
+
if base_dir is not None:
|
|
102
|
+
with _chdir(base_dir):
|
|
103
|
+
return _read_and_build()
|
|
104
|
+
return _read_and_build()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_processor(
|
|
108
|
+
source: "str | Path",
|
|
109
|
+
*,
|
|
110
|
+
format: "str | None" = None,
|
|
111
|
+
trusted: bool = False,
|
|
112
|
+
validate: bool = False,
|
|
113
|
+
name: "str | None" = None,
|
|
114
|
+
):
|
|
115
|
+
"""Load a statechart and return the underlying interpreter (low-level API).
|
|
116
|
+
|
|
117
|
+
Returns the :class:`~statemachine.io.interpreter.Interpreter`. Use it when you need
|
|
118
|
+
access to ``interpreter.scs`` (all compiled classes) or ``interpreter.start(...)`` —
|
|
119
|
+
e.g. documents that invoke/import children. See :func:`load` for the argument semantics.
|
|
120
|
+
"""
|
|
121
|
+
interpreter, _location = _build(
|
|
122
|
+
source, format=format, trusted=trusted, validate=validate, name=name
|
|
123
|
+
)
|
|
124
|
+
return interpreter
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def load(
|
|
128
|
+
source: "str | Path",
|
|
129
|
+
*,
|
|
130
|
+
format: "str | None" = None,
|
|
131
|
+
trusted: bool = False,
|
|
132
|
+
validate: bool = False,
|
|
133
|
+
name: "str | None" = None,
|
|
134
|
+
) -> "type[StateChart]":
|
|
135
|
+
"""Load a statechart from a file or inline content and return its class.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
source: a file path (``str``/:class:`~pathlib.Path`; format detected from
|
|
139
|
+
the extension) or inline document content (requires ``format``).
|
|
140
|
+
format: explicit format name (``"scxml"``, ``"json"``, ``"yaml"``),
|
|
141
|
+
overriding extension detection and required for inline content.
|
|
142
|
+
trusted: when ``False`` (default), expressions are evaluated by a restricted
|
|
143
|
+
AST-whitelist evaluator and ``<script>`` / arbitrary Python is rejected.
|
|
144
|
+
When ``True``, expressions and scripts are evaluated as arbitrary Python.
|
|
145
|
+
validate: when ``True`` (native JSON/YAML only), validate the document against
|
|
146
|
+
the published JSON Schema before building (requires the ``[validation]``
|
|
147
|
+
extra).
|
|
148
|
+
name: explicit name for the generated class (defaults to the document name
|
|
149
|
+
or the file stem).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The :class:`~statemachine.statemachine.StateChart` subclass. Instantiate it
|
|
153
|
+
to run the machine (``load("m.yaml")()``).
|
|
154
|
+
"""
|
|
155
|
+
interpreter, location = _build(
|
|
156
|
+
source, format=format, trusted=trusted, validate=validate, name=name
|
|
157
|
+
)
|
|
158
|
+
cls = interpreter.scs[location]
|
|
159
|
+
# Keep the interpreter reachable (and alive) from the returned class.
|
|
160
|
+
cls._io_processor = interpreter # pyright: ignore[reportAttributeAccessIssue]
|
|
161
|
+
return cast("type[StateChart]", cls)
|
statemachine/io/model.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Format-neutral intermediate representation (IR) of a statechart.
|
|
2
|
+
|
|
3
|
+
These dataclasses are the common target every format reader (SCXML, JSON, YAML)
|
|
4
|
+
produces, and the single input the :class:`~statemachine.io.processor.GenericProcessor`
|
|
5
|
+
consumes to build a :class:`~statemachine.statemachine.StateChart` class. They carry
|
|
6
|
+
the *structure* of a statechart (states, transitions, executable content, datamodel)
|
|
7
|
+
as plain data, with expressions kept as un-evaluated strings; turning those strings
|
|
8
|
+
into callables is the evaluator's job, not the IR's.
|
|
9
|
+
|
|
10
|
+
The vocabulary mirrors the SCXML semantic model (the formalism this library
|
|
11
|
+
standardizes on) plus a few optional callback-reference fields used by the native
|
|
12
|
+
JSON/YAML format to integrate with methods on a bound model.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from dataclasses import field
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Action:
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return f"{self.__class__.__name__}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ExecutableContent:
|
|
28
|
+
"""An ordered block of actions (the body of an ``onentry``/``onexit``/transition).
|
|
29
|
+
|
|
30
|
+
A state may carry several blocks (SCXML allows multiple ``<onentry>`` elements),
|
|
31
|
+
which is why ``State.onentry``/``onexit`` are *lists* of ``ExecutableContent``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
actions: list[Action] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return ", ".join(str(action) for action in self.actions)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def is_empty(self):
|
|
41
|
+
return not self.actions
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class RaiseAction(Action):
|
|
46
|
+
event: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class AssignAction(Action):
|
|
51
|
+
"""Assign a value to a datamodel location (``location = expr``).
|
|
52
|
+
|
|
53
|
+
``location`` is a dotted attribute path on the model (e.g. ``user.name``).
|
|
54
|
+
``expr`` is the value expression; when it is ``None``, ``child_xml`` carries the
|
|
55
|
+
literal inline XML/text assigned instead (SCXML ``<assign>`` with element body).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
location: str
|
|
59
|
+
expr: "str | None" = None
|
|
60
|
+
child_xml: "str | None" = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class LogAction(Action):
|
|
65
|
+
label: "str | None"
|
|
66
|
+
expr: "str | None"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class IfBranch(Action):
|
|
71
|
+
"""One branch of an :class:`IfAction`.
|
|
72
|
+
|
|
73
|
+
``cond`` is the guard expression for this branch. A ``cond`` of ``None`` marks the
|
|
74
|
+
final ``else`` branch, which always matches.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
cond: "str | None"
|
|
78
|
+
actions: list[Action] = field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
def __str__(self):
|
|
81
|
+
return self.cond or "<empty cond>"
|
|
82
|
+
|
|
83
|
+
def append(self, action: Action):
|
|
84
|
+
self.actions.append(action)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class IfAction(Action):
|
|
89
|
+
"""An ``if``/``elif``/``else`` chain.
|
|
90
|
+
|
|
91
|
+
``branches`` is ordered and evaluated top to bottom; the first branch whose ``cond``
|
|
92
|
+
is truthy runs its actions and the rest are skipped. By convention the first branch
|
|
93
|
+
is the ``if``, intermediate branches with a ``cond`` are ``elif``, and a trailing
|
|
94
|
+
branch with ``cond=None`` is the ``else`` (see :class:`IfBranch`).
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
branches: list[IfBranch] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class ForeachAction(Action):
|
|
102
|
+
"""Iterate ``item`` (and optional ``index``) over the iterable ``array`` evaluates to,
|
|
103
|
+
running ``content`` once per element."""
|
|
104
|
+
|
|
105
|
+
array: str
|
|
106
|
+
item: str
|
|
107
|
+
index: "str | None"
|
|
108
|
+
content: ExecutableContent
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class Param:
|
|
113
|
+
name: str
|
|
114
|
+
expr: "str | None"
|
|
115
|
+
location: "str | None" = None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class SendAction(Action):
|
|
120
|
+
event: "str | None" = None
|
|
121
|
+
eventexpr: "str | None" = None
|
|
122
|
+
target: "str | None" = None
|
|
123
|
+
type: "str | None" = None
|
|
124
|
+
id: "str | None" = None
|
|
125
|
+
idlocation: "str | None" = None
|
|
126
|
+
delay: "str | None" = None
|
|
127
|
+
delayexpr: "str | None" = None
|
|
128
|
+
namelist: "str | None" = None
|
|
129
|
+
params: list[Param] = field(default_factory=list)
|
|
130
|
+
content: "str | None" = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class CancelAction(Action):
|
|
135
|
+
sendid: "str | None" = None
|
|
136
|
+
sendidexpr: "str | None" = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class ScriptAction(Action):
|
|
141
|
+
content: str
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class Transition:
|
|
146
|
+
"""A transition out of a state.
|
|
147
|
+
|
|
148
|
+
Two attributes carry non-obvious conventions:
|
|
149
|
+
|
|
150
|
+
- ``event=None`` makes the transition **eventless**: it fires automatically
|
|
151
|
+
whenever ``cond`` holds (the SCXML NULL transition), instead of on a named event.
|
|
152
|
+
- ``target=None`` makes it **targetless**: taking it runs ``on`` but does not change
|
|
153
|
+
the active configuration (a self-action with no state change).
|
|
154
|
+
|
|
155
|
+
``cond``/``unless`` are guard expressions and may be a single string or a list (all
|
|
156
|
+
must hold). ``on`` is the executable content run when the transition is taken.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
target: "str | None" = None
|
|
160
|
+
internal: bool = False
|
|
161
|
+
initial: bool = False
|
|
162
|
+
event: "str | None" = None
|
|
163
|
+
cond: "str | None | list" = None
|
|
164
|
+
on: "ExecutableContent | None" = None
|
|
165
|
+
unless: "str | None | list" = None
|
|
166
|
+
"""Negated guard expression (or list); allowed only if falsy. Native format only."""
|
|
167
|
+
on_refs: list = field(default_factory=list)
|
|
168
|
+
"""Extra ``on`` callbacks referenced by name. Native JSON/YAML format only (the SCXML
|
|
169
|
+
reader leaves it empty)."""
|
|
170
|
+
before: "ExecutableContent | None" = None
|
|
171
|
+
"""``before`` executable content (the library lifecycle slot that runs once the guards
|
|
172
|
+
pass, before the state change). Native format only; SCXML has no equivalent slot."""
|
|
173
|
+
before_refs: list = field(default_factory=list)
|
|
174
|
+
"""Extra ``before`` callbacks referenced by name. Native format only."""
|
|
175
|
+
after: "ExecutableContent | None" = None
|
|
176
|
+
"""``after`` executable content (runs after the configuration has settled). Native
|
|
177
|
+
format only; SCXML has no equivalent slot."""
|
|
178
|
+
after_refs: list = field(default_factory=list)
|
|
179
|
+
"""Extra ``after`` callbacks referenced by name. Native format only."""
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass
|
|
183
|
+
class DoneData:
|
|
184
|
+
params: list[Param] = field(default_factory=list)
|
|
185
|
+
content_expr: "str | None" = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class InvokeDefinition:
|
|
190
|
+
type: "str | None" = None
|
|
191
|
+
typeexpr: "str | None" = None
|
|
192
|
+
src: "str | None" = None
|
|
193
|
+
srcexpr: "str | None" = None
|
|
194
|
+
id: "str | None" = None
|
|
195
|
+
idlocation: "str | None" = None
|
|
196
|
+
autoforward: bool = False
|
|
197
|
+
namelist: "str | None" = None
|
|
198
|
+
params: list[Param] = field(default_factory=list)
|
|
199
|
+
content: "str | StateMachineDefinition | None" = None
|
|
200
|
+
"""Inline child content. A string (inline SCXML, or an expression to evaluate) or, for
|
|
201
|
+
the native format, an already-parsed :class:`StateMachineDefinition`."""
|
|
202
|
+
finalize: "ExecutableContent | None" = None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class State:
|
|
207
|
+
"""A state node.
|
|
208
|
+
|
|
209
|
+
``parallel`` marks an orthogonal region (all child states are active at once);
|
|
210
|
+
``final`` marks an accepting state (which may carry ``donedata``). ``states`` holds
|
|
211
|
+
nested children (compound state), keyed by id.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
id: str
|
|
215
|
+
initial: bool = False
|
|
216
|
+
final: bool = False
|
|
217
|
+
parallel: bool = False
|
|
218
|
+
transitions: list[Transition] = field(default_factory=list)
|
|
219
|
+
onentry: list[ExecutableContent] = field(default_factory=list)
|
|
220
|
+
onexit: list[ExecutableContent] = field(default_factory=list)
|
|
221
|
+
states: dict[str, "State"] = field(default_factory=dict)
|
|
222
|
+
history: dict[str, "HistoryState"] = field(default_factory=dict)
|
|
223
|
+
donedata: "DoneData | None" = None
|
|
224
|
+
invocations: list[InvokeDefinition] = field(default_factory=list)
|
|
225
|
+
enter_refs: list = field(default_factory=list)
|
|
226
|
+
"""``enter`` callbacks referenced by name, appended after the ``onentry`` content.
|
|
227
|
+
Native JSON/YAML format only (the SCXML reader leaves it empty)."""
|
|
228
|
+
exit_refs: list = field(default_factory=list)
|
|
229
|
+
"""``exit`` callbacks referenced by name, appended after the ``onexit`` content.
|
|
230
|
+
Native format only."""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class HistoryState:
|
|
235
|
+
id: str
|
|
236
|
+
type: "Literal['shallow', 'deep']" = "shallow"
|
|
237
|
+
transitions: list[Transition] = field(default_factory=list)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass
|
|
241
|
+
class DataItem:
|
|
242
|
+
"""A datamodel variable initializer (``id``), from ``expr``, inline ``content`` or
|
|
243
|
+
an external ``src`` location (kept as a raw string; URL parsing happens in the reader)."""
|
|
244
|
+
|
|
245
|
+
id: str
|
|
246
|
+
src: "str | None"
|
|
247
|
+
expr: "str | None"
|
|
248
|
+
content: "str | None"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass
|
|
252
|
+
class DataModel:
|
|
253
|
+
data: list[DataItem] = field(default_factory=list)
|
|
254
|
+
scripts: list[ScriptAction] = field(default_factory=list)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass
|
|
258
|
+
class StateMachineDefinition:
|
|
259
|
+
"""Top-level, format-neutral statechart definition produced by every reader.
|
|
260
|
+
|
|
261
|
+
``initial_states`` is a list because the initial configuration may name several
|
|
262
|
+
states at once (one per parallel region).
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
name: "str | None" = None
|
|
266
|
+
states: dict[str, State] = field(default_factory=dict)
|
|
267
|
+
initial_states: list[str] = field(default_factory=list)
|
|
268
|
+
datamodel: "DataModel | None" = None
|