transitions-reactflow 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.
- transitions_reactflow-0.1.0/LICENSE +21 -0
- transitions_reactflow-0.1.0/MANIFEST.in +4 -0
- transitions_reactflow-0.1.0/PKG-INFO +222 -0
- transitions_reactflow-0.1.0/README.md +166 -0
- transitions_reactflow-0.1.0/pyproject.toml +63 -0
- transitions_reactflow-0.1.0/setup.cfg +4 -0
- transitions_reactflow-0.1.0/tests/test_graph.py +165 -0
- transitions_reactflow-0.1.0/tests/test_machine.py +216 -0
- transitions_reactflow-0.1.0/transitions_reactflow/__init__.py +14 -0
- transitions_reactflow-0.1.0/transitions_reactflow/__init__.pyi +50 -0
- transitions_reactflow-0.1.0/transitions_reactflow/diagrams_reactflow.py +157 -0
- transitions_reactflow-0.1.0/transitions_reactflow/diagrams_reactflow.pyi +8 -0
- transitions_reactflow-0.1.0/transitions_reactflow/machine.py +131 -0
- transitions_reactflow-0.1.0/transitions_reactflow/machine.pyi +21 -0
- transitions_reactflow-0.1.0/transitions_reactflow/py.typed +0 -0
- transitions_reactflow-0.1.0/transitions_reactflow.egg-info/PKG-INFO +222 -0
- transitions_reactflow-0.1.0/transitions_reactflow.egg-info/SOURCES.txt +18 -0
- transitions_reactflow-0.1.0/transitions_reactflow.egg-info/dependency_links.txt +1 -0
- transitions_reactflow-0.1.0/transitions_reactflow.egg-info/requires.txt +10 -0
- transitions_reactflow-0.1.0/transitions_reactflow.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 transitions-reactflow contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: transitions-reactflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: React Flow graph engine for pytransitions state machines
|
|
5
|
+
Author: transitions_reactflow contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 transitions-reactflow contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/johnsonlm/transitions-reactflow
|
|
29
|
+
Project-URL: Bug Reports, https://github.com/johnsonlm/transitions-reactflow/issues
|
|
30
|
+
Project-URL: Source, https://github.com/johnsonlm/transitions-reactflow
|
|
31
|
+
Keywords: state-machine,transitions,visualization,react-flow
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
43
|
+
Requires-Python: >=3.8
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Requires-Dist: transitions>=0.8.0
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
50
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
51
|
+
Requires-Dist: mypy>=0.950; extra == "dev"
|
|
52
|
+
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
|
53
|
+
Requires-Dist: build>=0.10.0; extra == "dev"
|
|
54
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# transitions_reactflow
|
|
58
|
+
|
|
59
|
+
A React Flow graph engine extension for the [pytransitions](https://github.com/pytransitions/transitions) state machine library.
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- ✅ React Flow compatible graph data
|
|
64
|
+
- ✅ Hierarchical states with `children` parameter
|
|
65
|
+
- ✅ Smart filtering of unused states
|
|
66
|
+
- ✅ Unique edge IDs for duplicate transitions
|
|
67
|
+
- ✅ Full type hints with .pyi stub files
|
|
68
|
+
- ✅ Comprehensive test suite
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install transitions-reactflow
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or install from source:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/johnsonlm/transitions-reactflow
|
|
80
|
+
cd transitions-reactflow
|
|
81
|
+
pip install -e .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from transitions_reactflow import ReactFlowMachine
|
|
88
|
+
|
|
89
|
+
# Define states with hierarchy
|
|
90
|
+
states = [
|
|
91
|
+
'idle',
|
|
92
|
+
{'name': 'processing', 'children': ['validating', 'payment']},
|
|
93
|
+
'completed'
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
# Define transitions
|
|
97
|
+
transitions = [
|
|
98
|
+
{'trigger': 'start', 'source': 'idle', 'dest': 'processing_validating'},
|
|
99
|
+
{'trigger': 'validate', 'source': 'processing_validating', 'dest': 'processing_payment'},
|
|
100
|
+
{'trigger': 'finish', 'source': 'processing_payment', 'dest': 'completed'}
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
# Create machine and get graph data
|
|
104
|
+
machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
|
|
105
|
+
graph_data = machine.get_graph()
|
|
106
|
+
|
|
107
|
+
# Returns: {'nodes': [...], 'edges': [...]}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Usage
|
|
111
|
+
|
|
112
|
+
### With Flask Backend
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from flask import Flask, jsonify
|
|
116
|
+
from flask_cors import CORS
|
|
117
|
+
from transitions_reactflow import ReactFlowMachine
|
|
118
|
+
|
|
119
|
+
app = Flask(__name__)
|
|
120
|
+
CORS(app) # Enable CORS for frontend requests
|
|
121
|
+
|
|
122
|
+
machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
|
|
123
|
+
|
|
124
|
+
@app.route('/graph-data')
|
|
125
|
+
def get_graph_data():
|
|
126
|
+
return jsonify(machine.get_graph())
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### With React Frontend
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
import ReactFlow from 'reactflow';
|
|
133
|
+
|
|
134
|
+
function StateMachineFlow() {
|
|
135
|
+
const [data, setData] = useState(null);
|
|
136
|
+
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
fetch('/graph-data').then(res => res.json()).then(setData);
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
return data && <ReactFlow nodes={data.nodes} edges={data.edges} fitView />;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Hierarchical States
|
|
146
|
+
|
|
147
|
+
Use `children` for nested states:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
states = [
|
|
151
|
+
'idle',
|
|
152
|
+
{'name': 'error', 'children': ['validation', 'payment', 'network']}
|
|
153
|
+
]
|
|
154
|
+
# Creates: error, error_validation, error_payment, error_network
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Note**: Unused parent states are automatically filtered from the graph.
|
|
158
|
+
|
|
159
|
+
## API
|
|
160
|
+
|
|
161
|
+
### ReactFlowMachine
|
|
162
|
+
|
|
163
|
+
Extends `transitions.extensions.GraphMachine`.
|
|
164
|
+
|
|
165
|
+
- `get_graph(title=None, roi_state=None)` → `{'nodes': [...], 'edges': [...]}`
|
|
166
|
+
- `add_states(states)` - supports hierarchical definitions
|
|
167
|
+
- All standard pytransitions methods
|
|
168
|
+
|
|
169
|
+
### ReactFlowGraph
|
|
170
|
+
|
|
171
|
+
Extends `transitions.extensions.diagrams_base.BaseGraph`.
|
|
172
|
+
|
|
173
|
+
- `get_graph(title=None, roi_state=None)` → React Flow data
|
|
174
|
+
- Standard BaseGraph methods
|
|
175
|
+
|
|
176
|
+
## Graph Data Format
|
|
177
|
+
|
|
178
|
+
The `get_graph()` method returns React Flow compatible data:
|
|
179
|
+
|
|
180
|
+
**Nodes:**
|
|
181
|
+
```python
|
|
182
|
+
{
|
|
183
|
+
"id": "state_name",
|
|
184
|
+
"data": {"label": "state_name"},
|
|
185
|
+
"position": {"x": 0, "y": 0}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Edges:**
|
|
190
|
+
```python
|
|
191
|
+
{
|
|
192
|
+
"id": "e-source-target",
|
|
193
|
+
"source": "source_state",
|
|
194
|
+
"target": "target_state",
|
|
195
|
+
"label": "trigger_name"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Tests
|
|
203
|
+
pytest tests/
|
|
204
|
+
|
|
205
|
+
# Coverage
|
|
206
|
+
pytest --cov=transitions_reactflow tests/
|
|
207
|
+
|
|
208
|
+
# Type checking
|
|
209
|
+
mypy transitions_reactflow/
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT - see LICENSE file
|
|
215
|
+
|
|
216
|
+
## Contributing
|
|
217
|
+
|
|
218
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
219
|
+
|
|
220
|
+
## Credits
|
|
221
|
+
|
|
222
|
+
Built on top of the [pytransitions](https://github.com/pytransitions/transitions) library.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# transitions_reactflow
|
|
2
|
+
|
|
3
|
+
A React Flow graph engine extension for the [pytransitions](https://github.com/pytransitions/transitions) state machine library.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ React Flow compatible graph data
|
|
8
|
+
- ✅ Hierarchical states with `children` parameter
|
|
9
|
+
- ✅ Smart filtering of unused states
|
|
10
|
+
- ✅ Unique edge IDs for duplicate transitions
|
|
11
|
+
- ✅ Full type hints with .pyi stub files
|
|
12
|
+
- ✅ Comprehensive test suite
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install transitions-reactflow
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or install from source:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/johnsonlm/transitions-reactflow
|
|
24
|
+
cd transitions-reactflow
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from transitions_reactflow import ReactFlowMachine
|
|
32
|
+
|
|
33
|
+
# Define states with hierarchy
|
|
34
|
+
states = [
|
|
35
|
+
'idle',
|
|
36
|
+
{'name': 'processing', 'children': ['validating', 'payment']},
|
|
37
|
+
'completed'
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Define transitions
|
|
41
|
+
transitions = [
|
|
42
|
+
{'trigger': 'start', 'source': 'idle', 'dest': 'processing_validating'},
|
|
43
|
+
{'trigger': 'validate', 'source': 'processing_validating', 'dest': 'processing_payment'},
|
|
44
|
+
{'trigger': 'finish', 'source': 'processing_payment', 'dest': 'completed'}
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Create machine and get graph data
|
|
48
|
+
machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
|
|
49
|
+
graph_data = machine.get_graph()
|
|
50
|
+
|
|
51
|
+
# Returns: {'nodes': [...], 'edges': [...]}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### With Flask Backend
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from flask import Flask, jsonify
|
|
60
|
+
from flask_cors import CORS
|
|
61
|
+
from transitions_reactflow import ReactFlowMachine
|
|
62
|
+
|
|
63
|
+
app = Flask(__name__)
|
|
64
|
+
CORS(app) # Enable CORS for frontend requests
|
|
65
|
+
|
|
66
|
+
machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
|
|
67
|
+
|
|
68
|
+
@app.route('/graph-data')
|
|
69
|
+
def get_graph_data():
|
|
70
|
+
return jsonify(machine.get_graph())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### With React Frontend
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import ReactFlow from 'reactflow';
|
|
77
|
+
|
|
78
|
+
function StateMachineFlow() {
|
|
79
|
+
const [data, setData] = useState(null);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
fetch('/graph-data').then(res => res.json()).then(setData);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
return data && <ReactFlow nodes={data.nodes} edges={data.edges} fitView />;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Hierarchical States
|
|
90
|
+
|
|
91
|
+
Use `children` for nested states:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
states = [
|
|
95
|
+
'idle',
|
|
96
|
+
{'name': 'error', 'children': ['validation', 'payment', 'network']}
|
|
97
|
+
]
|
|
98
|
+
# Creates: error, error_validation, error_payment, error_network
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Note**: Unused parent states are automatically filtered from the graph.
|
|
102
|
+
|
|
103
|
+
## API
|
|
104
|
+
|
|
105
|
+
### ReactFlowMachine
|
|
106
|
+
|
|
107
|
+
Extends `transitions.extensions.GraphMachine`.
|
|
108
|
+
|
|
109
|
+
- `get_graph(title=None, roi_state=None)` → `{'nodes': [...], 'edges': [...]}`
|
|
110
|
+
- `add_states(states)` - supports hierarchical definitions
|
|
111
|
+
- All standard pytransitions methods
|
|
112
|
+
|
|
113
|
+
### ReactFlowGraph
|
|
114
|
+
|
|
115
|
+
Extends `transitions.extensions.diagrams_base.BaseGraph`.
|
|
116
|
+
|
|
117
|
+
- `get_graph(title=None, roi_state=None)` → React Flow data
|
|
118
|
+
- Standard BaseGraph methods
|
|
119
|
+
|
|
120
|
+
## Graph Data Format
|
|
121
|
+
|
|
122
|
+
The `get_graph()` method returns React Flow compatible data:
|
|
123
|
+
|
|
124
|
+
**Nodes:**
|
|
125
|
+
```python
|
|
126
|
+
{
|
|
127
|
+
"id": "state_name",
|
|
128
|
+
"data": {"label": "state_name"},
|
|
129
|
+
"position": {"x": 0, "y": 0}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Edges:**
|
|
134
|
+
```python
|
|
135
|
+
{
|
|
136
|
+
"id": "e-source-target",
|
|
137
|
+
"source": "source_state",
|
|
138
|
+
"target": "target_state",
|
|
139
|
+
"label": "trigger_name"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Tests
|
|
147
|
+
pytest tests/
|
|
148
|
+
|
|
149
|
+
# Coverage
|
|
150
|
+
pytest --cov=transitions_reactflow tests/
|
|
151
|
+
|
|
152
|
+
# Type checking
|
|
153
|
+
mypy transitions_reactflow/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT - see LICENSE file
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
163
|
+
|
|
164
|
+
## Credits
|
|
165
|
+
|
|
166
|
+
Built on top of the [pytransitions](https://github.com/pytransitions/transitions) library.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "transitions-reactflow"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "React Flow graph engine for pytransitions state machines"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {file = "LICENSE"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "transitions_reactflow contributors"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["state-machine", "transitions", "visualization", "react-flow"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"transitions>=0.8.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=7.0.0",
|
|
36
|
+
"pytest-cov>=3.0.0",
|
|
37
|
+
"black>=22.0.0",
|
|
38
|
+
"mypy>=0.950",
|
|
39
|
+
"flake8>=4.0.0",
|
|
40
|
+
"build>=0.10.0",
|
|
41
|
+
"twine>=4.0.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/johnsonlm/transitions-reactflow"
|
|
46
|
+
"Bug Reports" = "https://github.com/johnsonlm/transitions-reactflow/issues"
|
|
47
|
+
"Source" = "https://github.com/johnsonlm/transitions-reactflow"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools]
|
|
50
|
+
packages = ["transitions_reactflow"]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.package-data]
|
|
53
|
+
transitions_reactflow = ["*.pyi", "py.typed"]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
testpaths = ["tests"]
|
|
57
|
+
python_files = ["test_*.py"]
|
|
58
|
+
|
|
59
|
+
[tool.mypy]
|
|
60
|
+
python_version = "3.8"
|
|
61
|
+
warn_return_any = true
|
|
62
|
+
warn_unused_configs = true
|
|
63
|
+
disallow_untyped_defs = false
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Tests for ReactFlowGraph class."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from transitions_reactflow import ReactFlowMachine
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestReactFlowGraph:
|
|
8
|
+
"""Test cases for ReactFlowGraph."""
|
|
9
|
+
|
|
10
|
+
def test_node_structure(self):
|
|
11
|
+
"""Test that nodes have the correct structure."""
|
|
12
|
+
states = ['idle', 'running']
|
|
13
|
+
transitions = [{'trigger': 'start',
|
|
14
|
+
'source': 'idle', 'dest': 'running'}]
|
|
15
|
+
|
|
16
|
+
machine = ReactFlowMachine(
|
|
17
|
+
states=states, transitions=transitions, initial='idle')
|
|
18
|
+
graph = machine.get_graph()
|
|
19
|
+
|
|
20
|
+
# Check node structure
|
|
21
|
+
for node in graph['nodes']:
|
|
22
|
+
assert 'id' in node
|
|
23
|
+
assert 'data' in node
|
|
24
|
+
assert 'position' in node
|
|
25
|
+
assert 'label' in node['data']
|
|
26
|
+
assert 'x' in node['position']
|
|
27
|
+
assert 'y' in node['position']
|
|
28
|
+
|
|
29
|
+
def test_edge_structure(self):
|
|
30
|
+
"""Test that edges have the correct structure."""
|
|
31
|
+
states = ['idle', 'running']
|
|
32
|
+
transitions = [{'trigger': 'start',
|
|
33
|
+
'source': 'idle', 'dest': 'running'}]
|
|
34
|
+
|
|
35
|
+
machine = ReactFlowMachine(
|
|
36
|
+
states=states, transitions=transitions, initial='idle')
|
|
37
|
+
graph = machine.get_graph()
|
|
38
|
+
|
|
39
|
+
# Check edge structure
|
|
40
|
+
for edge in graph['edges']:
|
|
41
|
+
assert 'id' in edge
|
|
42
|
+
assert 'source' in edge
|
|
43
|
+
assert 'target' in edge
|
|
44
|
+
assert 'label' in edge
|
|
45
|
+
|
|
46
|
+
def test_multiple_transitions(self):
|
|
47
|
+
"""Test graph with multiple transitions."""
|
|
48
|
+
states = ['idle', 'validating', 'payment', 'completed']
|
|
49
|
+
transitions = [
|
|
50
|
+
{'trigger': 'start', 'source': 'idle', 'dest': 'validating'},
|
|
51
|
+
{'trigger': 'validate', 'source': 'validating', 'dest': 'payment'},
|
|
52
|
+
{'trigger': 'pay', 'source': 'payment', 'dest': 'completed'},
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
machine = ReactFlowMachine(
|
|
56
|
+
states=states, transitions=transitions, initial='idle')
|
|
57
|
+
graph = machine.get_graph()
|
|
58
|
+
|
|
59
|
+
assert len(graph['nodes']) == 4
|
|
60
|
+
assert len(graph['edges']) == 3
|
|
61
|
+
|
|
62
|
+
def test_self_transition(self):
|
|
63
|
+
"""Test graph with self-transition."""
|
|
64
|
+
states = ['idle', 'running']
|
|
65
|
+
transitions = [
|
|
66
|
+
{'trigger': 'start', 'source': 'idle', 'dest': 'running'},
|
|
67
|
+
{'trigger': 'retry', 'source': 'running',
|
|
68
|
+
'dest': 'running'}, # Self-loop
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
machine = ReactFlowMachine(
|
|
72
|
+
states=states, transitions=transitions, initial='idle')
|
|
73
|
+
graph = machine.get_graph()
|
|
74
|
+
|
|
75
|
+
# Find the self-loop edge
|
|
76
|
+
self_loop = [e for e in graph['edges'] if e['source'] == e['target']]
|
|
77
|
+
assert len(self_loop) == 1
|
|
78
|
+
assert self_loop[0]['source'] == 'running'
|
|
79
|
+
assert self_loop[0]['target'] == 'running'
|
|
80
|
+
|
|
81
|
+
def test_multiple_source_states(self):
|
|
82
|
+
"""Test transition from multiple source states."""
|
|
83
|
+
states = ['idle', 'running', 'paused', 'stopped']
|
|
84
|
+
transitions = [
|
|
85
|
+
{'trigger': 'stop', 'source': [
|
|
86
|
+
'running', 'paused'], 'dest': 'stopped'},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
machine = ReactFlowMachine(
|
|
90
|
+
states=states, transitions=transitions, initial='idle')
|
|
91
|
+
graph = machine.get_graph()
|
|
92
|
+
|
|
93
|
+
# Should create separate edges for each source
|
|
94
|
+
stop_edges = [e for e in graph['edges'] if e['label'] == 'stop']
|
|
95
|
+
assert len(stop_edges) == 2
|
|
96
|
+
|
|
97
|
+
sources = {e['source'] for e in stop_edges}
|
|
98
|
+
assert 'running' in sources
|
|
99
|
+
assert 'paused' in sources
|
|
100
|
+
|
|
101
|
+
def test_empty_graph(self):
|
|
102
|
+
"""Test graph with no transitions."""
|
|
103
|
+
states = ['idle']
|
|
104
|
+
transitions = []
|
|
105
|
+
|
|
106
|
+
machine = ReactFlowMachine(
|
|
107
|
+
states=states, transitions=transitions, initial='idle')
|
|
108
|
+
graph = machine.get_graph()
|
|
109
|
+
|
|
110
|
+
# No transitions means no nodes (since unused states are filtered)
|
|
111
|
+
assert len(graph['nodes']) == 0
|
|
112
|
+
assert len(graph['edges']) == 0
|
|
113
|
+
|
|
114
|
+
def test_node_labels(self):
|
|
115
|
+
"""Test that node labels default to state ID."""
|
|
116
|
+
states = ['idle', 'running']
|
|
117
|
+
transitions = [
|
|
118
|
+
{'trigger': 'start', 'source': 'idle', 'dest': 'running'}
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
machine = ReactFlowMachine(
|
|
122
|
+
states=states, transitions=transitions, initial='idle')
|
|
123
|
+
graph = machine.get_graph()
|
|
124
|
+
|
|
125
|
+
# Check default labels (same as ID)
|
|
126
|
+
idle_node = [n for n in graph['nodes'] if n['id'] == 'idle'][0]
|
|
127
|
+
assert idle_node['data']['label'] == 'idle'
|
|
128
|
+
|
|
129
|
+
running_node = [n for n in graph['nodes'] if n['id'] == 'running'][0]
|
|
130
|
+
assert running_node['data']['label'] == 'running'
|
|
131
|
+
|
|
132
|
+
def test_roi_state_parameter(self):
|
|
133
|
+
"""Test that roi_state parameter is accepted (though not implemented)."""
|
|
134
|
+
states = ['idle', 'running']
|
|
135
|
+
transitions = [{'trigger': 'start',
|
|
136
|
+
'source': 'idle', 'dest': 'running'}]
|
|
137
|
+
|
|
138
|
+
machine = ReactFlowMachine(
|
|
139
|
+
states=states, transitions=transitions, initial='idle')
|
|
140
|
+
# The roi_state parameter is in the method signature but not used
|
|
141
|
+
# This test just ensures the method can be called
|
|
142
|
+
graph = machine.get_graph()
|
|
143
|
+
assert 'nodes' in graph
|
|
144
|
+
assert 'edges' in graph
|
|
145
|
+
|
|
146
|
+
def test_set_previous_transition(self):
|
|
147
|
+
"""Test set_previous_transition method (no-op for React Flow)."""
|
|
148
|
+
from transitions_reactflow.diagrams_reactflow import ReactFlowGraph
|
|
149
|
+
|
|
150
|
+
graph = ReactFlowGraph(None)
|
|
151
|
+
# Should not raise an error
|
|
152
|
+
graph.set_previous_transition('idle', 'running')
|
|
153
|
+
# Method is no-op, so no assertions needed
|
|
154
|
+
|
|
155
|
+
def test_get_graph_exception_handling(self):
|
|
156
|
+
"""Test exception handling in get_graph method."""
|
|
157
|
+
from transitions_reactflow.diagrams_reactflow import ReactFlowGraph
|
|
158
|
+
from unittest.mock import patch
|
|
159
|
+
|
|
160
|
+
graph = ReactFlowGraph(None)
|
|
161
|
+
|
|
162
|
+
# Mock _get_elements to return invalid data that causes an exception
|
|
163
|
+
with patch.object(graph, '_get_elements', return_value=(None, [])):
|
|
164
|
+
with pytest.raises(ValueError, match="Failed to generate React Flow graph"):
|
|
165
|
+
graph.get_graph()
|