Skip to content

Commit 0e6c359

Browse files
committed
docs: switch order of HSM and diagram section; fix indent
mermaid diagram illustrates a nested graph; better introduce this in advance
1 parent 65f9944 commit 0e6c359

File tree

1 file changed

+179
-179
lines changed

1 file changed

+179
-179
lines changed

README.md

+179-179
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ A lightweight, object-oriented state machine implementation in Python with many
5151
- [(Re-)Storing machine instances](#restoring)
5252
- [Typing support](#typing-support)
5353
- [Extensions](#extensions)
54-
- [Diagrams](#diagrams)
5554
- [Hierarchical State Machine](#hsm)
55+
- [Diagrams](#diagrams)
5656
- [Threading](#threading)
5757
- [Async](#async)
5858
- [State features](#state-features)
@@ -1357,8 +1357,8 @@ assert model.state == State.A
13571357

13581358
Even though the core of transitions is kept lightweight, there are a variety of MixIns to extend its functionality. Currently supported are:
13591359

1360-
- **Diagrams** to visualize the current state of a machine
13611360
- **Hierarchical State Machines** for nesting and reuse
1361+
- **Diagrams** to visualize the current state of a machine
13621362
- **Threadsafe Locks** for parallel execution
13631363
- **Async callbacks** for asynchronous execution
13641364
- **Custom States** for extended state-related behaviour
@@ -1406,182 +1406,7 @@ from transitions.extensions import LockedHierarchicalGraphMachine as LHGMachine
14061406
machine = LHGMachine(model, states, transitions)
14071407
```
14081408

1409-
#### <a name="diagrams"></a> Diagrams
1410-
1411-
Additional Keywords:
1412-
1413-
- `title` (optional): Sets the title of the generated image.
1414-
- `show_conditions` (default False): Shows conditions at transition edges
1415-
- `show_auto_transitions` (default False): Shows auto transitions in graph
1416-
- `show_state_attributes` (default False): Show callbacks (enter, exit), tags and timeouts in graph
1417-
1418-
Transitions can generate basic state diagrams displaying all valid transitions between states.
1419-
The basic diagram support generates a [mermaid](https://mermaid.js.org) state machine definition which can be used with mermaid's [live editor](https://mermaid.live), in markdown files in GitLab or GitHub and other web services.
1420-
For instance, this code:
1421-
```python
1422-
from transitions.extensions.diagrams import HierarchicalGraphMachine
1423-
import pyperclip
1424-
1425-
states = ['A', 'B', {'name': 'C',
1426-
'final': True,
1427-
'parallel': [{'name': '1', 'children': ['a', {"name": "b", "final": True}],
1428-
'initial': 'a',
1429-
'transitions': [['go', 'a', 'b']]},
1430-
{'name': '2', 'children': ['a', {"name": "b", "final": True}],
1431-
'initial': 'a',
1432-
'transitions': [['go', 'a', 'b']]}]}]
1433-
transitions = [['reset', 'C', 'A'], ["init", "A", "B"], ["do", "B", "C"]]
1434-
1435-
1436-
m = HierarchicalGraphMachine(states=states, transitions=transitions, initial="A", show_conditions=True,
1437-
title="Mermaid", graph_engine="mermaid", auto_transitions=False)
1438-
m.init()
1439-
1440-
pyperclip.copy(m.get_graph().draw(None)) # using pyperclip for convenience
1441-
print("Graph copied to clipboard!")
1442-
```
1443-
1444-
Produces this diagram (check the document source to see the markdown notation):
1445-
1446-
```mermaid
1447-
---
1448-
Mermaid Graph
1449-
---
1450-
stateDiagram-v2
1451-
direction LR
1452-
classDef s_default fill:white,color:black
1453-
classDef s_inactive fill:white,color:black
1454-
classDef s_parallel color:black,fill:white
1455-
classDef s_active color:red,fill:darksalmon
1456-
classDef s_previous color:blue,fill:azure
1457-
1458-
state "A" as A
1459-
Class A s_previous
1460-
state "B" as B
1461-
Class B s_active
1462-
state "C" as C
1463-
C --> [*]
1464-
Class C s_default
1465-
state C {
1466-
state "1" as C_1
1467-
state C_1 {
1468-
[*] --> C_1_a
1469-
state "a" as C_1_a
1470-
state "b" as C_1_b
1471-
C_1_b --> [*]
1472-
}
1473-
--
1474-
state "2" as C_2
1475-
state C_2 {
1476-
[*] --> C_2_a
1477-
state "a" as C_2_a
1478-
state "b" as C_2_b
1479-
C_2_b --> [*]
1480-
}
1481-
}
1482-
1483-
C --> A: reset
1484-
A --> B: init
1485-
B --> C: do
1486-
C_1_a --> C_1_b: go
1487-
C_2_a --> C_2_b: go
1488-
[*] --> A
1489-
```
1490-
1491-
To use more sophisticated graphing functionality, you'll need to have `graphviz` and/or `pygraphviz` installed.
1492-
To generate graphs with the package `graphviz`, you need to install [Graphviz](https://graphviz.org/) manually or via a package manager.
1493-
1494-
sudo apt-get install graphviz graphviz-dev # Ubuntu and Debian
1495-
brew install graphviz # MacOS
1496-
conda install graphviz python-graphviz # (Ana)conda
1497-
1498-
Now you can install the actual Python packages
1499-
1500-
pip install graphviz pygraphviz # install graphviz and/or pygraphviz manually...
1501-
pip install transitions[diagrams] # ... or install transitions with 'diagrams' extras which currently depends on pygraphviz
1502-
1503-
Currently, `GraphMachine` will use `pygraphviz` when available and fall back to `graphviz` when `pygraphviz` cannot be
1504-
found.
1505-
If `graphviz` is not available either, `mermaid` will be used.
1506-
This can be overridden by passing `graph_engine="graphviz"` (or `"mermaid"`) to the constructor.
1507-
Note that this default might change in the future and `pygraphviz` support may be dropped.
1508-
With `Model.get_graph()` you can get the current graph or the region of interest (roi) and draw it like this:
1509-
1510-
```python
1511-
# import transitions
1512-
1513-
from transitions.extensions import GraphMachine
1514-
m = Model()
1515-
# without further arguments pygraphviz will be used
1516-
machine = GraphMachine(model=m, ...)
1517-
# when you want to use graphviz explicitly
1518-
machine = GraphMachine(model=m, graph_engine="graphviz", ...)
1519-
# in cases where auto transitions should be visible
1520-
machine = GraphMachine(model=m, show_auto_transitions=True, ...)
1521-
1522-
# draw the whole graph ...
1523-
m.get_graph().draw('my_state_diagram.png', prog='dot')
1524-
# ... or just the region of interest
1525-
# (previous state, active state and all reachable states)
1526-
roi = m.get_graph(show_roi=True).draw('my_state_diagram.png', prog='dot')
1527-
```
1528-
1529-
This produces something like this:
1530-
1531-
![state diagram example](https://user-images.githubusercontent.com/205986/47524268-725c1280-d89a-11e8-812b-1d3b6e667b91.png)
1532-
1533-
Independent of the backend you use, the draw function also accepts a file descriptor or a binary stream as the first argument. If you set this parameter to `None`, the byte stream will be returned:
1534-
1535-
```python
1536-
import io
1537-
1538-
with open('a_graph.png', 'bw') as f:
1539-
# you need to pass the format when you pass objects instead of filenames.
1540-
m.get_graph().draw(f, format="png", prog='dot')
1541-
1542-
# you can pass a (binary) stream too
1543-
b = io.BytesIO()
1544-
m.get_graph().draw(b, format="png", prog='dot')
1545-
1546-
# or just handle the binary string yourself
1547-
result = m.get_graph().draw(None, format="png", prog='dot')
1548-
assert result == b.getvalue()
1549-
```
1550-
1551-
References and partials passed as callbacks will be resolved as good as possible:
1552-
1553-
```python
1554-
from transitions.extensions import GraphMachine
1555-
from functools import partial
1556-
1557-
1558-
class Model:
1559-
1560-
def clear_state(self, deep=False, force=False):
1561-
print("Clearing state ...")
1562-
return True
1563-
1564-
1565-
model = Model()
1566-
machine = GraphMachine(model=model, states=['A', 'B', 'C'],
1567-
transitions=[
1568-
{'trigger': 'clear', 'source': 'B', 'dest': 'A', 'conditions': model.clear_state},
1569-
{'trigger': 'clear', 'source': 'C', 'dest': 'A',
1570-
'conditions': partial(model.clear_state, False, force=True)},
1571-
],
1572-
initial='A', show_conditions=True)
1573-
1574-
model.get_graph().draw('my_state_diagram.png', prog='dot')
1575-
```
1576-
1577-
This should produce something similar to this:
1578-
1579-
![state diagram references_example](https://user-images.githubusercontent.com/205986/110783076-39087f80-8268-11eb-8fa1-fc7bac97f4cf.png)
1580-
1581-
If the format of references does not suit your needs, you can override the static method `GraphMachine.format_references`. If you want to skip reference entirely, just let `GraphMachine.format_references` return `None`.
1582-
Also, have a look at our [example](./examples) IPython/Jupyter notebooks for a more detailed example about how to use and edit graphs.
1583-
1584-
### <a name="hsm"></a>Hierarchical State Machine (HSM)
1409+
#### <a name="hsm"></a>Hierarchical State Machine (HSM)
15851410

15861411
Transitions includes an extension module which allows nesting states.
15871412
This allows us to create contexts and to model cases where states are related to certain subtasks in the state machine.
@@ -1832,7 +1657,7 @@ machine.final_Z()
18321657
# >>> Machine is final!
18331658
```
18341659

1835-
#### Reuse of previously created HSMs
1660+
##### Reuse of previously created HSMs
18361661

18371662
Besides semantic order, nested states are very handy if you want to specify state machines for specific tasks and plan to reuse them.
18381663
Before _0.8.0_, a `HierarchicalMachine` would not integrate the machine instance itself but the states and transitions by creating copies of them.
@@ -1949,6 +1774,181 @@ collector.increase()
19491774
assert collector.is_counting_2()
19501775
```
19511776

1777+
#### <a name="diagrams"></a> Diagrams
1778+
1779+
Additional Keywords:
1780+
1781+
- `title` (optional): Sets the title of the generated image.
1782+
- `show_conditions` (default False): Shows conditions at transition edges
1783+
- `show_auto_transitions` (default False): Shows auto transitions in graph
1784+
- `show_state_attributes` (default False): Show callbacks (enter, exit), tags and timeouts in graph
1785+
1786+
Transitions can generate basic state diagrams displaying all valid transitions between states.
1787+
The basic diagram support generates a [mermaid](https://mermaid.js.org) state machine definition which can be used with mermaid's [live editor](https://mermaid.live), in markdown files in GitLab or GitHub and other web services.
1788+
For instance, this code:
1789+
```python
1790+
from transitions.extensions.diagrams import HierarchicalGraphMachine
1791+
import pyperclip
1792+
1793+
states = ['A', 'B', {'name': 'C',
1794+
'final': True,
1795+
'parallel': [{'name': '1', 'children': ['a', {"name": "b", "final": True}],
1796+
'initial': 'a',
1797+
'transitions': [['go', 'a', 'b']]},
1798+
{'name': '2', 'children': ['a', {"name": "b", "final": True}],
1799+
'initial': 'a',
1800+
'transitions': [['go', 'a', 'b']]}]}]
1801+
transitions = [['reset', 'C', 'A'], ["init", "A", "B"], ["do", "B", "C"]]
1802+
1803+
1804+
m = HierarchicalGraphMachine(states=states, transitions=transitions, initial="A", show_conditions=True,
1805+
title="Mermaid", graph_engine="mermaid", auto_transitions=False)
1806+
m.init()
1807+
1808+
pyperclip.copy(m.get_graph().draw(None)) # using pyperclip for convenience
1809+
print("Graph copied to clipboard!")
1810+
```
1811+
1812+
Produces this diagram (check the document source to see the markdown notation):
1813+
1814+
```mermaid
1815+
---
1816+
Mermaid Graph
1817+
---
1818+
stateDiagram-v2
1819+
direction LR
1820+
classDef s_default fill:white,color:black
1821+
classDef s_inactive fill:white,color:black
1822+
classDef s_parallel color:black,fill:white
1823+
classDef s_active color:red,fill:darksalmon
1824+
classDef s_previous color:blue,fill:azure
1825+
1826+
state "A" as A
1827+
Class A s_previous
1828+
state "B" as B
1829+
Class B s_active
1830+
state "C" as C
1831+
C --> [*]
1832+
Class C s_default
1833+
state C {
1834+
state "1" as C_1
1835+
state C_1 {
1836+
[*] --> C_1_a
1837+
state "a" as C_1_a
1838+
state "b" as C_1_b
1839+
C_1_b --> [*]
1840+
}
1841+
--
1842+
state "2" as C_2
1843+
state C_2 {
1844+
[*] --> C_2_a
1845+
state "a" as C_2_a
1846+
state "b" as C_2_b
1847+
C_2_b --> [*]
1848+
}
1849+
}
1850+
1851+
C --> A: reset
1852+
A --> B: init
1853+
B --> C: do
1854+
C_1_a --> C_1_b: go
1855+
C_2_a --> C_2_b: go
1856+
[*] --> A
1857+
```
1858+
1859+
To use more sophisticated graphing functionality, you'll need to have `graphviz` and/or `pygraphviz` installed.
1860+
To generate graphs with the package `graphviz`, you need to install [Graphviz](https://graphviz.org/) manually or via a package manager.
1861+
1862+
sudo apt-get install graphviz graphviz-dev # Ubuntu and Debian
1863+
brew install graphviz # MacOS
1864+
conda install graphviz python-graphviz # (Ana)conda
1865+
1866+
Now you can install the actual Python packages
1867+
1868+
pip install graphviz pygraphviz # install graphviz and/or pygraphviz manually...
1869+
pip install transitions[diagrams] # ... or install transitions with 'diagrams' extras which currently depends on pygraphviz
1870+
1871+
Currently, `GraphMachine` will use `pygraphviz` when available and fall back to `graphviz` when `pygraphviz` cannot be
1872+
found.
1873+
If `graphviz` is not available either, `mermaid` will be used.
1874+
This can be overridden by passing `graph_engine="graphviz"` (or `"mermaid"`) to the constructor.
1875+
Note that this default might change in the future and `pygraphviz` support may be dropped.
1876+
With `Model.get_graph()` you can get the current graph or the region of interest (roi) and draw it like this:
1877+
1878+
```python
1879+
# import transitions
1880+
1881+
from transitions.extensions import GraphMachine
1882+
m = Model()
1883+
# without further arguments pygraphviz will be used
1884+
machine = GraphMachine(model=m, ...)
1885+
# when you want to use graphviz explicitly
1886+
machine = GraphMachine(model=m, graph_engine="graphviz", ...)
1887+
# in cases where auto transitions should be visible
1888+
machine = GraphMachine(model=m, show_auto_transitions=True, ...)
1889+
1890+
# draw the whole graph ...
1891+
m.get_graph().draw('my_state_diagram.png', prog='dot')
1892+
# ... or just the region of interest
1893+
# (previous state, active state and all reachable states)
1894+
roi = m.get_graph(show_roi=True).draw('my_state_diagram.png', prog='dot')
1895+
```
1896+
1897+
This produces something like this:
1898+
1899+
![state diagram example](https://user-images.githubusercontent.com/205986/47524268-725c1280-d89a-11e8-812b-1d3b6e667b91.png)
1900+
1901+
Independent of the backend you use, the draw function also accepts a file descriptor or a binary stream as the first argument. If you set this parameter to `None`, the byte stream will be returned:
1902+
1903+
```python
1904+
import io
1905+
1906+
with open('a_graph.png', 'bw') as f:
1907+
# you need to pass the format when you pass objects instead of filenames.
1908+
m.get_graph().draw(f, format="png", prog='dot')
1909+
1910+
# you can pass a (binary) stream too
1911+
b = io.BytesIO()
1912+
m.get_graph().draw(b, format="png", prog='dot')
1913+
1914+
# or just handle the binary string yourself
1915+
result = m.get_graph().draw(None, format="png", prog='dot')
1916+
assert result == b.getvalue()
1917+
```
1918+
1919+
References and partials passed as callbacks will be resolved as good as possible:
1920+
1921+
```python
1922+
from transitions.extensions import GraphMachine
1923+
from functools import partial
1924+
1925+
1926+
class Model:
1927+
1928+
def clear_state(self, deep=False, force=False):
1929+
print("Clearing state ...")
1930+
return True
1931+
1932+
1933+
model = Model()
1934+
machine = GraphMachine(model=model, states=['A', 'B', 'C'],
1935+
transitions=[
1936+
{'trigger': 'clear', 'source': 'B', 'dest': 'A', 'conditions': model.clear_state},
1937+
{'trigger': 'clear', 'source': 'C', 'dest': 'A',
1938+
'conditions': partial(model.clear_state, False, force=True)},
1939+
],
1940+
initial='A', show_conditions=True)
1941+
1942+
model.get_graph().draw('my_state_diagram.png', prog='dot')
1943+
```
1944+
1945+
This should produce something similar to this:
1946+
1947+
![state diagram references_example](https://user-images.githubusercontent.com/205986/110783076-39087f80-8268-11eb-8fa1-fc7bac97f4cf.png)
1948+
1949+
If the format of references does not suit your needs, you can override the static method `GraphMachine.format_references`. If you want to skip reference entirely, just let `GraphMachine.format_references` return `None`.
1950+
Also, have a look at our [example](./examples) IPython/Jupyter notebooks for a more detailed example about how to use and edit graphs.
1951+
19521952
#### <a name="threading"></a> Threadsafe(-ish) State Machine
19531953

19541954
In cases where event dispatching is done in threads, one can use either `LockedMachine` or `LockedHierarchicalMachine` where **function access** (!sic) is secured with reentrant locks.

0 commit comments

Comments
 (0)