State Machines in Python
Python is the most popular programming language (TIOBE #1, 2026), yet
most Python state machine libraries offer only flat FSMs without
hierarchical states, parallel regions, or standard compliance.
SCE brings the full power of W3C SCXML 1.0 to Python
by wrapping the battle-tested C++ interpreter via pybind11.
Existing approaches compared
| Approach |
Pros |
Cons |
| transitions |
Pure Python, simple API |
Flat states only, no parallel, no standard |
| python-statemachine |
Declarative, Django integration |
No hierarchy, no history, no W3C compliance |
| sismic |
YAML statecharts, hierarchical |
Pure Python (slow), no W3C SCXML support |
| SCE |
W3C standard, C++ speed, full hierarchy |
Requires C++ build step for bindings |
Quick Start
Step 1: Build the Python bindings
# Clone and build
git clone --recursive https://github.com/newmassrael/scxml-core-engine.git
cd scxml-core-engine
mkdir build_python && cd build_python
cmake .. -DBUILD_PYTHON_BINDINGS=ON -DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF
cmake --build . --target _sce
Step 2: Use with context manager
import sce
# Context manager automatically starts/stops the engine
with sce.Engine.from_file("traffic_light.scxml") as engine:
print(engine.current_state) # 'red'
engine.send_event("timer")
print(engine.current_state) # 'green'
engine.send_event("timer")
print(engine.current_state) # 'yellow'
Step 3: Or use explicit lifecycle
import sce
engine = sce.Engine.from_file("traffic_light.scxml")
engine.start()
engine.send_event("timer")
print(engine.current_state) # 'green'
print(engine.active_states) # ['green']
print(engine.running) # True
engine.stop()
API Reference
Factory Methods
| Method |
Description |
Engine.from_file(path) |
Create engine from an SCXML file. Raises ValueError on failure. |
Engine.from_string(content) |
Create engine from an SCXML string. Raises ValueError on failure. |
Core Operations
| Method |
Description |
start() |
Start the state machine. Returns True if started successfully. |
stop() |
Stop the state machine. |
send_event(name, data="") |
Send an event (internal queue). Returns True on success. |
send_external_event(name, data="") |
Send to external queue (W3C SCXML 5.10). For HTTP/BasicHTTPEventProcessor. |
is_in_state(state_id) |
Check if a specific state is active. |
set_variable(name, value) |
Set datamodel variable (bool, int, float, or str). |
get_variable(name) |
Get datamodel variable as string. |
Properties
| Property |
Type |
Description |
current_state |
str |
Current active state ID |
active_states |
list[str] |
All currently active state IDs (for parallel states) |
running |
bool |
Whether the engine is currently running |
last_error |
str |
Last error message |
statistics |
Statistics |
Execution statistics (events, transitions) |
W3C SCXML Features
SCE's Python bindings pass all 202 mandatory W3C SCXML
conformance tests, including 13 HTTP event tests:
| Feature |
W3C Section |
Status |
| Compound (hierarchical) states |
3.3 |
Supported |
| Parallel states |
3.4 |
Supported |
| History states (shallow + deep) |
3.10 |
Supported |
| ECMAScript datamodel |
B.2 |
Supported |
| Invoke (parent-child) |
6.4 |
Supported |
| Delayed send |
6.2 |
Supported |
| BasicHTTPEventProcessor |
C.2 |
Supported |
Architecture
The Python bindings are a thin wrapper over the C++ ReadySCXMLEngine
interpreter. No Python reimplementation -- the same C++ code that passes
all W3C tests runs under the hood.
Python (sce package)
|
v
pybind11 (PyEngine wrapper, GIL management)
|
v
C++ ReadySCXMLEngine (Interpreter + EventScheduler + EventDispatcher)
|
v
Lua 5.4 / QuickJS (ECMAScript evaluation)
GIL management: All C++ calls release the Python GIL
(py::gil_scoped_release) to prevent deadlocks with C++ worker
threads (EventRaiser, EventScheduler). This enables safe multi-threaded
operation with invoke and delayed send features.
Module structure
| Path |
Description |
sce-python/src/bindings.cpp |
pybind11 wrapper (PyEngine, PyStatistics) |
sce-python/python/sce/__init__.py |
Python package (exports Engine, Statistics) |
sce-python/tests/test_w3c.py |
W3C conformance test runner (202 tests) |
sce-python/pyproject.toml |
scikit-build-core wheel configuration |
sce-python/CMakeLists.txt |
pybind11 v2.13.6 (FetchContent) |
Running W3C Tests
# Build Python bindings first (see Quick Start above)
# Run all 202 W3C tests (including HTTP)
SPDLOG_LEVEL=off PYTHONPATH=build_python/sce-python:sce-python/python \
python3 sce-python/tests/test_w3c.py
# Skip HTTP tests for faster iteration
SPDLOG_LEVEL=off PYTHONPATH=build_python/sce-python:sce-python/python \
python3 sce-python/tests/test_w3c.py --skip-http
# Run specific test
SPDLOG_LEVEL=off PYTHONPATH=build_python/sce-python:sce-python/python \
python3 sce-python/tests/test_w3c.py 144