|
| 1 | +# StateChart 3.2.2 |
| 2 | + |
| 3 | +*Not released yet* |
| 4 | + |
| 5 | +## Bug fixes in 3.2.2 |
| 6 | + |
| 7 | +### Event declarations inside `State.Compound` |
| 8 | + |
| 9 | +A `State.Compound` (or `State.Parallel`) class body only understood the assignment form of an |
| 10 | +event declaration (`visit_pub = bag_end.to(green_dragon)`). The two other documented forms were |
| 11 | +silently dropped: the name was bound to a detached object and the transition it wrapped stayed |
| 12 | +{ref}`eventless <eventless>`, firing as soon as its source state became active. |
| 13 | + |
| 14 | +The `Event` class now declares an event inside a nested state body: |
| 15 | + |
| 16 | +```py |
| 17 | +>>> from statemachine import Event, State, StateChart |
| 18 | + |
| 19 | +>>> class Journey(StateChart): |
| 20 | +... class shire(State.Compound): |
| 21 | +... bag_end = State(initial=True) |
| 22 | +... green_dragon = State() |
| 23 | +... |
| 24 | +... visit_pub = Event(bag_end.to(green_dragon)) |
| 25 | +... |
| 26 | +... road = State(final=True) |
| 27 | +... depart = Event(shire.to(road)) |
| 28 | + |
| 29 | +>>> sm = Journey() |
| 30 | +>>> set(sm.configuration_values) == {"shire", "bag_end"} |
| 31 | +True |
| 32 | + |
| 33 | +>>> sm.send("visit_pub") |
| 34 | +>>> set(sm.configuration_values) == {"shire", "green_dragon"} |
| 35 | +True |
| 36 | + |
| 37 | +``` |
| 38 | + |
| 39 | +Before this fix, `Journey` started already in `green_dragon` and `visit_pub` was not among its |
| 40 | +events. The attribute name now becomes the event `id`, an explicit `id` takes precedence, and |
| 41 | +the `error_` / `done_state_` / `done_invoke_` prefixes expand to their dotted form. |
| 42 | + |
| 43 | +The same applies to the `@<source>.to(<target>)` decorator, which declares an event and its |
| 44 | +inline action at once. Inside a compound body it registered no event and never ran its body: |
| 45 | + |
| 46 | +```py |
| 47 | +>>> class Gate(StateChart): |
| 48 | +... class gate(State.Compound): |
| 49 | +... locked = State(initial=True) |
| 50 | +... unlocked = State() |
| 51 | +... |
| 52 | +... push = unlocked.to(locked) |
| 53 | +... |
| 54 | +... @locked.to(unlocked) |
| 55 | +... def coin(self): |
| 56 | +... return "accepted" |
| 57 | +... |
| 58 | +... broken = State(final=True) |
| 59 | +... smash = gate.to(broken) |
| 60 | + |
| 61 | +>>> sm = Gate() |
| 62 | +>>> sm.send("coin") |
| 63 | +'accepted' |
| 64 | + |
| 65 | +>>> set(sm.configuration_values) == {"gate", "unlocked"} |
| 66 | +True |
| 67 | + |
| 68 | +``` |
| 69 | + |
| 70 | +Two differences from the top-level form remain, both because a nested body is evaluated before |
| 71 | +the owning class exists: an explicit `id` that differs from the attribute name does not also |
| 72 | +bind the attribute name, and an `Event` with no transitions is dropped instead of becoming a |
| 73 | +class attribute. |
| 74 | + |
| 75 | +Reported by [@Dolecor](https://github.com/Dolecor). |
| 76 | + |
| 77 | +[#643](https://github.com/fgmacedo/python-statemachine/issues/643). |
| 78 | + |
| 79 | +### `delay` and `internal` dropped from an explicit `Event` |
| 80 | + |
| 81 | +`Event(dark.to(lit), delay=50)` rebuilt the event without its `delay`, so |
| 82 | +`BeaconsOfGondor.light.delay` was `0` and the event fired immediately instead of being queued. |
| 83 | +The same happened to `internal`. Both are now preserved, although `internal` still has no |
| 84 | +effect at trigger time: |
| 85 | + |
| 86 | +```py |
| 87 | +>>> class BeaconsOfGondor(StateChart): |
| 88 | +... dark = State(initial=True) |
| 89 | +... lit = State(final=True) |
| 90 | +... |
| 91 | +... light = Event(dark.to(lit), delay=50) |
| 92 | + |
| 93 | +>>> BeaconsOfGondor.light.delay |
| 94 | +50 |
| 95 | + |
| 96 | +``` |
0 commit comments