Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ repos:
types: [python]
language: system
pass_filenames: false
- id: generate-images
name: Generate README images
entry: >-
uv run python -m statemachine.contrib.diagram
tests.examples.traffic_light_machine.TrafficLightMachine
docs/images/readme_trafficlightmachine.png
language: system
pass_filenames: false
files: >-
(statemachine/contrib/diagram/
|tests/examples/traffic_light_machine\.py)
- id: pytest
name: Pytest
entry: uv run pytest -n auto --cov-fail-under=100
Expand Down
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,8 @@ True

Generate a diagram:

```py
>>> # This example will only run on automated tests if dot is present
>>> getfixture("requires_dot_installed")
>>> img_path = "docs/images/readme_trafficlightmachine.png"
>>> sm._graph().write_png(img_path)

```python
sm._graph().write_png("traffic_light.png")
```

![](https://raw.githubusercontent.com/fgmacedo/python-statemachine/develop/docs/images/readme_trafficlightmachine.png)
Expand Down
97 changes: 38 additions & 59 deletions docs/diagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,62 +33,52 @@ For other systems, see the [Graphviz downloads page](https://graphviz.org/downlo
Every state machine instance exposes a `_graph()` method that returns a
[pydot.Dot](https://github.com/pydot/pydot) graph object:

```py
>>> from tests.examples.order_control_machine import OrderControl

>>> sm = OrderControl()

>>> sm._graph() # doctest: +ELLIPSIS
<pydot.core.Dot ...
```python
from tests.examples.order_control_machine import OrderControl

sm = OrderControl()
graph = sm._graph() # returns a pydot.Dot object
```

### Highlighting the current state

The diagram automatically highlights the current state of the instance.
Send events to advance the machine and see the active state change:

``` py
>>> # This example will only run on automated tests if dot is present
>>> getfixture("requires_dot_installed")

>>> from tests.examples.order_control_machine import OrderControl

>>> sm = OrderControl()

>>> sm.receive_payment(10)
[10]

>>> sm._graph().write_png("docs/images/order_control_machine_processing.png")
```python
from tests.examples.traffic_light_machine import TrafficLightMachine

sm = TrafficLightMachine()
sm.send("cycle")
sm._graph().write_png("traffic_light_yellow.png")
```

![OrderControl after receiving payment](images/order_control_machine_processing.png)
```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine
:events: cycle
:caption: TrafficLightMachine after one cycle
```


### Exporting to a file

The `pydot.Dot` object supports writing to many formats — use
`write_png()`, `write_svg()`, `write_pdf()`, etc.:

```py
>>> from tests.examples.order_control_machine import OrderControl

>>> sm = OrderControl()

>>> sm._graph().write_png("docs/images/order_control_machine_initial.png")

```python
sm = OrderControl()
sm._graph().write_png("order_control.png")
```

![OrderControl](images/order_control_machine_initial.png)
```{statemachine-diagram} tests.examples.order_control_machine.OrderControl
:caption: OrderControl
```

For higher resolution PNGs, set the DPI before exporting:

```py
>>> sm._graph().set_dpi(300)

>>> sm._graph().write_png("docs/images/order_control_machine_initial_300dpi.png")

```python
graph = sm._graph()
graph.set_dpi(300)
graph.write_png("order_control_300dpi.png")
```

```{note}
Expand Down Expand Up @@ -258,11 +248,9 @@ The `DotGraphMachine` class gives you control over the diagram's visual
properties. Subclass it and override the class attributes to customize
fonts, colors, and layout:

```py
>>> from statemachine.contrib.diagram import DotGraphMachine

>>> from tests.examples.order_control_machine import OrderControl

```python
from statemachine.contrib.diagram import DotGraphMachine
from tests.examples.order_control_machine import OrderControl
```

Available attributes:
Expand All @@ -279,34 +267,25 @@ Available attributes:
For example, to generate a top-to-bottom diagram with a custom active
state color:

```py
>>> class CustomDiagram(DotGraphMachine):
... graph_rankdir = "TB"
... state_active_fillcolor = "lightyellow"

>>> sm = OrderControl()

>>> sm.receive_payment(10)
[10]

>>> graph = CustomDiagram(sm)

>>> dot = graph()
```python
class CustomDiagram(DotGraphMachine):
graph_rankdir = "TB"
state_active_fillcolor = "lightyellow"

>>> dot.to_string() # doctest: +ELLIPSIS
'digraph OrderControl {...
sm = OrderControl()
sm.receive_payment(10)

graph = CustomDiagram(sm)
dot = graph()
dot.write_svg("order_control_custom.svg")
```

`DotGraphMachine` also works with **classes** (not just instances) to
generate diagrams without an active state:

```py
>>> dot = DotGraphMachine(OrderControl)()

>>> dot.to_string() # doctest: +ELLIPSIS
'digraph OrderControl {...

```python
dot = DotGraphMachine(OrderControl)()
dot.write_png("order_control_class.png")
```


Expand Down
Binary file removed docs/images/order_control_machine_initial.png
Binary file not shown.
Binary file removed docs/images/order_control_machine_initial_300dpi.png
Binary file not shown.
Binary file removed docs/images/order_control_machine_processing.png
Binary file not shown.
Binary file removed docs/images/readme_orderworkflow.png
Binary file not shown.
Binary file modified docs/images/readme_trafficlightmachine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion docs/releases/1.0.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ You can generate diagrams from your state machine.

Example:

![OrderControl](../images/order_control_machine_initial.png)
```{statemachine-diagram} tests.examples.order_control_machine.OrderControl
:caption: OrderControl
```


```{seealso}
Expand Down
22 changes: 22 additions & 0 deletions tests/test_contrib_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -1091,3 +1091,25 @@ def test_render_without_caption_uses_div(self, tmp_path):
html = result[0].astext()
assert "<figure" not in html
assert "<div" in html


class TestGraphMethod:
"""Test the ``_graph()`` convenience method on state machine instances."""

def test_graph_returns_pydot_dot(self):
import pydot

from tests.examples.traffic_light_machine import TrafficLightMachine

sm = TrafficLightMachine()
graph = sm._graph()
assert isinstance(graph, pydot.Dot)

def test_graph_reflects_active_state(self):
from tests.examples.traffic_light_machine import TrafficLightMachine

sm = TrafficLightMachine()
sm.send("cycle")
svg_root = _parse_svg(sm._graph())
yellow_node = _find_state_node(svg_root, "yellow")
assert yellow_node is not None
Loading