Skip to content

Commit 03fb1ba

Browse files
authored
feat: State.name is now optional (#354)
1 parent a74defb commit 03fb1ba

26 files changed

Lines changed: 212 additions & 147 deletions

README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ Define your state machine:
5555

5656
>>> class TrafficLightMachine(StateMachine):
5757
... "A traffic light machine"
58-
... green = State("Green", initial=True)
59-
... yellow = State("Yellow")
60-
... red = State("Red")
58+
... green = State(initial=True)
59+
... yellow = State()
60+
... red = State()
6161
...
6262
... cycle = green.to(yellow) | yellow.to(red) | red.to(green)
6363
...
@@ -100,6 +100,14 @@ You can inspect the current state:
100100

101101
```
102102

103+
A `State` human-readable name is automatically derived from the `State.id`:
104+
105+
```py
106+
>>> traffic_light.current_state.name
107+
'Yellow'
108+
109+
```
110+
103111
Or get a complete state representation for debugging purposes:
104112

105113
```py
@@ -199,10 +207,10 @@ A simple didactic state machine for controlling an `Order`:
199207

200208
```py
201209
>>> class OrderControl(StateMachine):
202-
... waiting_for_payment = State("Waiting for payment", initial=True)
203-
... processing = State("Processing")
204-
... shipping = State("Shipping")
205-
... completed = State("Completed", final=True)
210+
... waiting_for_payment = State(initial=True)
211+
... processing = State()
212+
... shipping = State()
213+
... completed = State(final=True)
206214
...
207215
... add_to_order = waiting_for_payment.to(waiting_for_payment)
208216
... receive_payment = (
@@ -254,6 +262,9 @@ You can use this machine as follows.
254262
>>> control.current_state.id
255263
'waiting_for_payment'
256264

265+
>>> control.current_state.name
266+
'Waiting for payment'
267+
257268
>>> control.process_order()
258269
Traceback (most recent call last):
259270
...

docs/actions.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ The follow example can get you an overview of the "generic" callbacks available:
3232
>>> from statemachine import StateMachine, State
3333

3434
>>> class ExampleStateMachine(StateMachine):
35-
... initial = State("Initial", initial=True)
36-
... final = State("Final", final=True)
35+
... initial = State(initial=True)
36+
... final = State(final=True)
3737
...
3838
... loop = initial.to.itself()
3939
... go = initial.to(final)
@@ -101,7 +101,7 @@ model, using the patterns:
101101
>>> from statemachine import StateMachine, State
102102

103103
>>> class ExampleStateMachine(StateMachine):
104-
... initial = State("Initial", initial=True)
104+
... initial = State(initial=True)
105105
...
106106
... loop = initial.to.itself()
107107
...
@@ -121,7 +121,7 @@ Use the `enter` or `exit` params available on the `State` constructor.
121121
>>> from statemachine import StateMachine, State
122122

123123
>>> class ExampleStateMachine(StateMachine):
124-
... initial = State("Initial", initial=True, enter="entering_initial", exit="leaving_initial")
124+
... initial = State(initial=True, enter="entering_initial", exit="leaving_initial")
125125
...
126126
... loop = initial.to.itself()
127127
...
@@ -140,7 +140,7 @@ Use the `enter` or `exit` params available on the `State` constructor.
140140
>>> from statemachine import StateMachine, State
141141

142142
>>> class ExampleStateMachine(StateMachine):
143-
... initial = State("Initial", initial=True)
143+
... initial = State(initial=True)
144144
...
145145
... loop = initial.to.itself()
146146
...
@@ -176,7 +176,7 @@ model, using the patterns:
176176
>>> from statemachine import StateMachine, State
177177

178178
>>> class ExampleStateMachine(StateMachine):
179-
... initial = State("Initial", initial=True)
179+
... initial = State(initial=True)
180180
...
181181
... loop = initial.to.itself()
182182
...
@@ -198,7 +198,7 @@ model, using the patterns:
198198
>>> from statemachine import StateMachine, State
199199

200200
>>> class ExampleStateMachine(StateMachine):
201-
... initial = State("Initial", initial=True)
201+
... initial = State(initial=True)
202202
...
203203
... loop = initial.to.itself(before="just_before", on="its_happening", after="loop_completed")
204204
...
@@ -222,7 +222,7 @@ The action will be registered for every {ref}`transition` associated with the ev
222222
>>> from statemachine import StateMachine, State
223223

224224
>>> class ExampleStateMachine(StateMachine):
225-
... initial = State("Initial", initial=True)
225+
... initial = State(initial=True)
226226
...
227227
... loop = initial.to.itself()
228228
...
@@ -256,7 +256,7 @@ You can also declare an event while also adding a callback:
256256
>>> from statemachine import StateMachine, State
257257

258258
>>> class ExampleStateMachine(StateMachine):
259-
... initial = State("Initial", initial=True)
259+
... initial = State(initial=True)
260260
...
261261
... @initial.to.itself()
262262
... def loop(self):

docs/guards.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ Consider this example:
8181
```py
8282

8383
class InvoiceStateMachine(StateMachine):
84-
unpaid = State("unpaid", initial=True)
85-
paid = State("paid")
86-
failed = State("failed")
84+
unpaid = State(initial=True)
85+
paid = State()
86+
failed = State()
8787

8888
paused = False
8989
offer_valid = True
14 Bytes
Loading

docs/releases/2.0.0.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,33 @@ StateMachine 2.0 supports Python 3.7, 3.8, 3.9, 3.10, and 3.11.
1818

1919
## What's new in 2.0
2020

21+
### State names are now optional
22+
23+
{ref}`State` names are now by default derived from the class variable that they are assigned to. You can keep declaring explicit names, but we encourage you to only assign a name
24+
when it is different than the one derived from its id.
25+
26+
```py
27+
>>> from statemachine import StateMachine, State
28+
29+
>>> class SM(StateMachine):
30+
... pending = State(initial=True)
31+
... waiting_approval = State()
32+
... approved = State(final=True)
33+
...
34+
... start = pending.to(waiting_approval)
35+
... approve = waiting_approval.to(approved)
36+
...
37+
38+
>>> SM.pending.name
39+
'Pending'
40+
41+
>>> SM.waiting_approval.name
42+
'Waiting approval'
43+
44+
>>> SM.approved.name
45+
'Approved'
46+
47+
```
2148

2249
### Added support for internal transitions
2350

@@ -28,7 +55,7 @@ are ever executed as a result of an internal transition.
2855
>>> from statemachine import StateMachine, State
2956

3057
>>> class TestStateMachine(StateMachine):
31-
... initial = State("initial", initial=True)
58+
... initial = State(initial=True)
3259
...
3360
... loop = initial.to.itself(internal=True)
3461

docs/releases/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ with advance notice in the **Deprecations** section of releases.
1010

1111
Below are release notes through StateMachine and its patch releases.
1212

13-
### 2.0 release
13+
### 2.0 releases
1414

1515
```{toctree}
1616
:maxdepth: 1
@@ -20,7 +20,7 @@ Below are release notes through StateMachine and its patch releases.
2020
```
2121

2222

23-
### 1.0 release
23+
### 1.0 releases
2424

2525
This is the last release series to support Python 2.X series.
2626

@@ -34,7 +34,7 @@ This is the last release series to support Python 2.X series.
3434
3535
```
3636

37-
### 0.* release
37+
### 0.* releases
3838

3939
```{toctree}
4040
:maxdepth: 1

docs/transitions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Example:
107107

108108
```py
109109
>>> class TestStateMachine(StateMachine):
110-
... initial = State("initial", initial=True)
110+
... initial = State(initial=True)
111111
...
112112
... external_loop = initial.to.itself(on="do_something")
113113
... internal_loop = initial.to.itself(internal=True, on="do_something")

statemachine/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def _add_unbounded_callback(cls, attr_name, func):
114114
for ref in func._callbacks_to_update:
115115
ref(attr_name)
116116

117-
def add_state(cls, id, state):
117+
def add_state(cls, id, state: State):
118118
state._set_id(id)
119119
cls.states.append(state)
120120
cls.states_map[state.value] = state

statemachine/state.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@ class State:
1515
in the way that state describes.
1616
1717
Args:
18-
name: An human readable representation of the state.
18+
name: A human-readable representation of the state. Default is derived
19+
from the name of the variable assigned to the state machine class.
20+
The name is derived from the id using this logic::
21+
22+
name = id.replace("_", " ").capitalize()
23+
1924
value: A specific value to the storage and retrieval of states.
2025
If specified, you can use It to map a more friendly representation to a low-level
2126
value.
22-
initial: Set `' True`` if the ``State`` is the initial one. There must be one and only
27+
initial: Set ``True`` if the ``State`` is the initial one. There must be one and only
2328
one initial state in a statemachine. Defaults to ``False``.
2429
final: Set ``True`` if represents a final state. A machine can have
2530
optionally many final states. Final states have no :ref:`transition` starting from It.
@@ -83,16 +88,21 @@ class State:
8388
"""
8489

8590
def __init__(
86-
self, name, value=None, initial=False, final=False, enter=None, exit=None
91+
self,
92+
name: str = "",
93+
value: Any = None,
94+
initial: bool = False,
95+
final: bool = False,
96+
enter: Any = None,
97+
exit: Any = None,
8798
):
88-
# type: (str, Optional[Any], bool, bool, Optional[Any], Optional[Any]) -> None
8999
self.name = name
90100
self.value = value
91-
self._id = None # type: Optional[str]
92-
self._storage = ""
93101
self._initial = initial
94-
self.transitions = TransitionList()
95102
self._final = final
103+
self._id: str = ""
104+
self._storage: str = ""
105+
self.transitions = TransitionList()
96106
self.enter = Callbacks().add(enter)
97107
self.exit = Callbacks().add(exit)
98108

@@ -144,23 +154,25 @@ def clone(self):
144154
return deepcopy(self)
145155

146156
@property
147-
def id(self):
157+
def id(self) -> str:
148158
return self._id
149159

150-
def _set_id(self, id):
160+
def _set_id(self, id: str):
151161
self._id = id
152162
self._storage = f"_{id}"
153163
if self.value is None:
154164
self.value = id
165+
if not self.name:
166+
self.name = self._id.replace("_", " ").capitalize()
155167

156-
def _to_(self, *states, **kwargs):
168+
def _to_(self, *states: "State", **kwargs):
157169
transitions = TransitionList(
158170
Transition(self, state, **kwargs) for state in states
159171
)
160172
self.transitions.add_transitions(transitions)
161173
return transitions
162174

163-
def _from_(self, *states, **kwargs):
175+
def _from_(self, *states: "State", **kwargs):
164176
transitions = TransitionList()
165177
for origin in states:
166178
transition = Transition(origin, self, **kwargs)
@@ -169,7 +181,7 @@ def _from_(self, *states, **kwargs):
169181
return transitions
170182

171183
def _get_proxy_method_to_itself(self, method):
172-
def proxy(*states, **kwargs):
184+
def proxy(*states: "State", **kwargs):
173185
return method(*states, **kwargs)
174186

175187
def proxy_to_itself(**kwargs):

0 commit comments

Comments
 (0)