Skip to content

Commit bc40199

Browse files
committed
fix: render human-readable Event name in diagrams (#600)
Event.name is now auto-generated as a humanized form of the id (split by _ and . separators, first word capitalized) instead of echoing the raw identifier. Explicit name= passed by the user is preserved. - Add humanize_id() in utils.py (compiled regex, shared by Event and State) - Remove redundant name= from Event call sites in factory, events, and SCXML actions so auto-generation kicks in - Use event.name in diagram labels (_format_event_names) - Update docs and tests to reflect humanized names Closes #600
1 parent 9a3bfef commit bc40199

20 files changed

Lines changed: 127 additions & 80 deletions

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,10 @@ Generate a diagram or get a text representation with f-strings:
8383
>>> print(f"{sm:md}")
8484
| State | Event | Guard | Target |
8585
| ------ | ----- | ----- | ------ |
86-
| Green | cycle | | Yellow |
87-
| Yellow | cycle | | Red |
88-
| Red | cycle | | Green |
86+
| Green | Cycle | | Yellow |
87+
| Yellow | Cycle | | Red |
88+
| Red | Cycle | | Green |
89+
<BLANKLINE>
8990

9091
```
9192

docs/diagram.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ stateDiagram-v2
126126
state "Yellow" as yellow
127127
state "Red" as red
128128
[*] --> green
129-
green --> yellow : cycle
130-
yellow --> red : cycle
131-
red --> green : cycle
129+
green --> yellow : Cycle
130+
yellow --> red : Cycle
131+
red --> green : Cycle
132132
<BLANKLINE>
133133
classDef active fill:#40E0D0,stroke:#333
134134
green:::active
@@ -137,9 +137,9 @@ stateDiagram-v2
137137
>>> print(f"{sm:md}")
138138
| State | Event | Guard | Target |
139139
| ------ | ----- | ----- | ------ |
140-
| Green | cycle | | Yellow |
141-
| Yellow | cycle | | Red |
142-
| Red | cycle | | Green |
140+
| Green | Cycle | | Yellow |
141+
| Yellow | Cycle | | Red |
142+
| Red | Cycle | | Green |
143143
<BLANKLINE>
144144

145145
```
@@ -154,9 +154,9 @@ stateDiagram-v2
154154
state "Yellow" as yellow
155155
state "Red" as red
156156
[*] --> green
157-
green --> yellow : cycle
158-
yellow --> red : cycle
159-
red --> green : cycle
157+
green --> yellow : Cycle
158+
yellow --> red : Cycle
159+
red --> green : Cycle
160160
<BLANKLINE>
161161

162162
```
@@ -191,9 +191,9 @@ stateDiagram-v2
191191
state "Yellow" as yellow
192192
state "Red" as red
193193
[*] --> green
194-
green --> yellow : cycle
195-
yellow --> red : cycle
196-
red --> green : cycle
194+
green --> yellow : Cycle
195+
yellow --> red : Cycle
196+
red --> green : Cycle
197197
<BLANKLINE>
198198

199199
>>> formatter.supported_formats()
@@ -294,9 +294,9 @@ A traffic light.
294294
<BLANKLINE>
295295
| State | Event | Guard | Target |
296296
| ------ | ----- | ----- | ------ |
297-
| Green | cycle | | Yellow |
298-
| Yellow | cycle | | Red |
299-
| Red | cycle | | Green |
297+
| Green | Cycle | | Yellow |
298+
| Yellow | Cycle | | Red |
299+
| Red | Cycle | | Green |
300300
<BLANKLINE>
301301
<BLANKLINE>
302302

docs/events.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,31 @@ Every event has two string properties:
8080

8181
- **`id`** — the programmatic identifier, derived from the class attribute name.
8282
Use this in `send()`, guards, and comparisons.
83-
- **`name`** — a human-readable label for display purposes. Defaults to the `id`
84-
when not explicitly set.
83+
- **`name`** — a human-readable label for display purposes. Auto-generated from
84+
the `id` by replacing `_` and `.` with spaces and capitalizing the first word.
85+
You can override the automatic name by passing `name=` explicitly when
86+
declaring the event:
8587

8688
```py
8789
>>> TrafficLight.cycle.id
8890
'cycle'
8991

9092
>>> TrafficLight.cycle.name
91-
'cycle'
93+
'Cycle'
94+
95+
>>> class Example(StateChart):
96+
... on = State(initial=True)
97+
... off = State(final=True)
98+
... shut_down = Event(on.to(off), name="Shut the system down")
99+
100+
>>> Example.shut_down.name
101+
'Shut the system down'
92102

93103
```
94104

95105
```{tip}
96106
Always use `event.id` for programmatic checks. The `name` property is intended
97-
for UI display and may change format in future versions.
107+
for UI display and may differ from the `id`.
98108
```
99109

100110

609 Bytes
Loading

docs/tutorial.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,9 @@ You can also get text representations of any state machine using Python's built-
375375
>>> print(f"{CoffeeOrder:md}")
376376
| State | Event | Guard | Target |
377377
| --------- | ------- | ----- | --------- |
378-
| Pending | start | | Preparing |
379-
| Preparing | finish | | Ready |
380-
| Ready | pick_up | | Picked up |
378+
| Pending | Start | | Preparing |
379+
| Preparing | Finish | | Ready |
380+
| Ready | Pick up | | Picked up |
381381

382382
```
383383

@@ -394,9 +394,9 @@ stateDiagram-v2
394394
state "Picked up" as picked_up
395395
[*] --> pending
396396
picked_up --> [*]
397-
pending --> preparing : start
398-
preparing --> ready : finish
399-
ready --> picked_up : pick_up
397+
pending --> preparing : Start
398+
preparing --> ready : Finish
399+
ready --> picked_up : Pick up
400400
<BLANKLINE>
401401

402402
```

statemachine/contrib/diagram/extract.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,17 @@ def _format_event_names(transition: "Transition") -> str:
116116

117117
all_ids = {str(e) for e in events}
118118

119+
seen_ids: Set[str] = set()
119120
display: List[str] = []
120121
for event in events:
121122
eid = str(event)
122123
# Skip dot-form aliases (e.g. "done.invoke.X") when the underscore
123124
# form ("done_invoke_X") is also registered on this transition.
124125
if "." in eid and eid.replace(".", "_") in all_ids:
125126
continue
126-
if eid not in display: # pragma: no branch
127-
display.append(eid)
127+
if eid not in seen_ids: # pragma: no branch
128+
seen_ids.add(eid)
129+
display.append(event.name if event.name else eid)
128130

129131
return " ".join(display)
130132

statemachine/event.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .exceptions import InvalidDefinition
1010
from .i18n import _
1111
from .transition_mixin import AddCallbacksMixin
12+
from .utils import humanize_id
1213

1314
if TYPE_CHECKING:
1415
from .statemachine import StateChart
@@ -107,7 +108,7 @@ def __new__(
107108
if name:
108109
instance.name = name
109110
elif _has_real_id:
110-
instance.name = str(id).replace("_", " ").capitalize()
111+
instance.name = humanize_id(id)
111112
else:
112113
instance.name = ""
113114
if transitions:

statemachine/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def add(self, events):
3030
if isinstance(event, Event):
3131
self._items.append(event)
3232
else:
33-
self._items.append(Event(id=event, name=event))
33+
self._items.append(Event(id=event))
3434

3535
return self
3636

statemachine/factory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def add_from_attributes(cls, attrs): # noqa: C901
310310
cls.add_state(key, value)
311311
elif isinstance(value, (Transition, TransitionList)):
312312
event_id = _expand_event_id(key)
313-
cls.add_event(event=Event(transitions=value, id=event_id, name=key))
313+
cls.add_event(event=Event(transitions=value, id=event_id))
314314
elif isinstance(value, (Event,)):
315315
if value._has_real_id:
316316
event_id = value.id
@@ -338,7 +338,7 @@ def _add_unbounded_callback(cls, attr_name, func):
338338
# machinery that is stored at ``func.attr_name``
339339
setattr(cls, func.attr_name, func)
340340
if func.is_event:
341-
cls.add_event(event=Event(func._transitions, id=attr_name, name=attr_name))
341+
cls.add_event(event=Event(func._transitions, id=attr_name))
342342

343343
def add_state(cls, id, state: State):
344344
state._set_id(id)

statemachine/io/scxml/actions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def create_raise_action_callable(action: RaiseAction) -> Callable:
382382
def raise_action(*args, **kwargs):
383383
machine: StateChart = kwargs["machine"]
384384

385-
Event(id=action.event, name=action.event, internal=True, _sm=machine).put()
385+
Event(id=action.event, internal=True, _sm=machine).put()
386386

387387
raise_action.action = action # type: ignore[attr-defined]
388388
return raise_action
@@ -492,7 +492,7 @@ def send_action(*args, **kwargs): # noqa: C901
492492
continue
493493
params_values[param.name] = _eval(param.expr, **kwargs)
494494

495-
Event(id=event, name=event, delay=delay, internal=internal, _sm=machine).put(
495+
Event(id=event, delay=delay, internal=internal, _sm=machine).put(
496496
*content,
497497
send_id=send_id,
498498
**params_values,

0 commit comments

Comments
 (0)