0
# State and Transition Components
1
2
Individual components for states, transitions, events, and conditions that can be used to build custom state machine configurations. These components provide fine-grained control over state machine behavior and enable advanced customization.
3
4
## Capabilities
5
6
### State Class
7
8
Represents a persistent state managed by a Machine with entry/exit callbacks and trigger handling.
9
10
```python { .api }
11
class State:
12
def __init__(
13
self,
14
name,
15
on_enter=None,
16
on_exit=None,
17
ignore_invalid_triggers=None,
18
final=False
19
):
20
"""
21
Initialize a state.
22
23
Parameters:
24
- name: The name of the state (str or Enum)
25
- on_enter: Callable(s) triggered when state is entered
26
- on_exit: Callable(s) triggered when state is exited
27
- ignore_invalid_triggers: Override machine's ignore_invalid_triggers setting
28
- final: Whether this is a final state
29
"""
30
31
def enter(self, event_data):
32
"""
33
Triggered when a state is entered.
34
35
Parameters:
36
- event_data: EventData object containing transition context
37
"""
38
39
def exit(self, event_data):
40
"""
41
Triggered when a state is exited.
42
43
Parameters:
44
- event_data: EventData object containing transition context
45
"""
46
47
def add_callback(self, trigger, func):
48
"""
49
Add a new enter or exit callback.
50
51
Parameters:
52
- trigger: 'enter' or 'exit'
53
- func: Callback function to add
54
"""
55
56
@property
57
def name(self):
58
"""The name of the state."""
59
60
@property
61
def value(self):
62
"""The state's value (equivalent to name for string states)."""
63
```
64
65
### Transition Class
66
67
Represents a transition between states with conditions, callbacks, and execution logic.
68
69
```python { .api }
70
class Transition:
71
def __init__(
72
self,
73
source,
74
dest,
75
conditions=None,
76
unless=None,
77
before=None,
78
after=None,
79
prepare=None
80
):
81
"""
82
Initialize a transition.
83
84
Parameters:
85
- source: Source state name
86
- dest: Destination state name
87
- conditions: Callbacks that must return True for transition to execute
88
- unless: Callbacks that must return False for transition to execute
89
- before: Callbacks executed before the transition
90
- after: Callbacks executed after the transition
91
- prepare: Callbacks executed before condition checks
92
"""
93
94
def execute(self, event_data):
95
"""
96
Execute the transition if conditions are met.
97
98
Parameters:
99
- event_data: EventData object containing transition context
100
101
Returns:
102
bool: True if transition was executed, False otherwise
103
"""
104
105
def add_callback(self, trigger, func):
106
"""
107
Add a new before, after, or prepare callback.
108
109
Parameters:
110
- trigger: 'before', 'after', or 'prepare'
111
- func: Callback function to add
112
"""
113
```
114
115
### Event Class
116
117
Manages a collection of transitions assigned to the same trigger and handles event execution.
118
119
```python { .api }
120
class Event:
121
def __init__(self, name, machine):
122
"""
123
Initialize an event.
124
125
Parameters:
126
- name: The name of the event/trigger
127
- machine: The Machine instance this event belongs to
128
"""
129
130
def add_transition(self, transition):
131
"""
132
Add a transition to the list of potential transitions.
133
134
Parameters:
135
- transition: Transition object to add
136
"""
137
138
def trigger(self, model, *args, **kwargs):
139
"""
140
Execute all transitions that match the current state.
141
142
Parameters:
143
- model: The model object to trigger on
144
- args: Positional arguments passed to callbacks
145
- kwargs: Keyword arguments passed to callbacks
146
147
Returns:
148
bool: True if at least one transition was successful
149
"""
150
151
def add_callback(self, trigger, func):
152
"""
153
Add a new before or after callback to all available transitions.
154
155
Parameters:
156
- trigger: 'before' or 'after'
157
- func: Callback function to add
158
"""
159
```
160
161
### EventData Class
162
163
Container for relevant data related to an ongoing transition attempt, passed to all callbacks.
164
165
```python { .api }
166
class EventData:
167
def __init__(self, state, event, machine, model, args, kwargs):
168
"""
169
Initialize event data.
170
171
Parameters:
172
- state: The State from which the Event was triggered
173
- event: The triggering Event
174
- machine: The current Machine instance
175
- model: The model/object the machine is bound to
176
- args: Optional positional arguments from trigger method
177
- kwargs: Optional keyword arguments from trigger method
178
"""
179
180
def update(self, state):
181
"""
182
Update the EventData object with a new state.
183
184
Parameters:
185
- state: New State object
186
"""
187
188
# Attributes available on EventData instances:
189
state: State # The State from which the Event was triggered
190
event: Event # The triggering Event
191
machine: Machine # The current Machine instance
192
model: object # The model/object the machine is bound to
193
args: list # Optional positional arguments from trigger method
194
kwargs: dict # Optional keyword arguments from trigger method
195
transition: Transition # Currently active transition
196
error: Exception # In case a triggered event causes an Error
197
result: bool # True if transition successful, False otherwise
198
```
199
200
### Condition Class
201
202
Helper class for condition checks with target validation support.
203
204
```python { .api }
205
class Condition:
206
def __init__(self, func, target=True):
207
"""
208
Initialize a condition.
209
210
Parameters:
211
- func: The function to call for the condition check (str or callable)
212
- target: Indicates the target state for condition validation
213
"""
214
215
def check(self, event_data):
216
"""
217
Check whether the condition passes.
218
219
Parameters:
220
- event_data: EventData object containing transition context
221
222
Returns:
223
bool: True if condition passes, False otherwise
224
"""
225
```
226
227
## Usage Examples
228
229
### Creating Custom States
230
231
```python
232
from transitions import State, Machine
233
234
# Create states with callbacks
235
def on_enter_working():
236
print("Started working!")
237
238
def on_exit_working():
239
print("Stopped working!")
240
241
working_state = State(
242
name='working',
243
on_enter=on_enter_working,
244
on_exit=on_exit_working
245
)
246
247
# Use in machine
248
states = ['idle', working_state, 'done']
249
machine = Machine(model=MyModel(), states=states, initial='idle')
250
```
251
252
### Creating Custom Transitions with Conditions
253
254
```python
255
from transitions import Machine, Transition
256
257
class Robot:
258
def __init__(self):
259
self.battery = 100
260
self.task_queue = []
261
262
def has_battery(self):
263
return self.battery > 20
264
265
def has_tasks(self):
266
return len(self.task_queue) > 0
267
268
def start_task(self):
269
if self.task_queue:
270
task = self.task_queue.pop(0)
271
print(f"Starting task: {task}")
272
self.battery -= 10
273
274
# Create custom transitions with multiple conditions
275
states = ['idle', 'working', 'charging']
276
277
transitions = [
278
{
279
'trigger': 'start_work',
280
'source': 'idle',
281
'dest': 'working',
282
'conditions': ['has_battery', 'has_tasks'],
283
'before': 'start_task'
284
},
285
{
286
'trigger': 'finish_work',
287
'source': 'working',
288
'dest': 'idle'
289
},
290
{
291
'trigger': 'charge',
292
'source': ['idle', 'working'],
293
'dest': 'charging',
294
'unless': lambda: self.battery > 90 # Don't charge if battery is high
295
}
296
]
297
298
robot = Robot()
299
robot.task_queue = ['task1', 'task2', 'task3']
300
301
machine = Machine(model=robot, states=states, transitions=transitions, initial='idle')
302
303
# Test the conditions
304
robot.start_work() # Will work if battery > 20 and tasks available
305
```
306
307
### Working with EventData
308
309
```python
310
from transitions import Machine
311
312
class LoggingRobot:
313
def __init__(self):
314
self.activity_log = []
315
316
def log_transition(self, event_data):
317
"""Log all transition details"""
318
log_entry = {
319
'from_state': event_data.state.name,
320
'to_state': event_data.transition.dest,
321
'trigger': event_data.event.name,
322
'timestamp': time.time(),
323
'args': event_data.args,
324
'kwargs': event_data.kwargs
325
}
326
self.activity_log.append(log_entry)
327
print(f"Transition: {log_entry['from_state']} -> {log_entry['to_state']} via {log_entry['trigger']}")
328
329
states = ['idle', 'working', 'done']
330
transitions = [
331
{
332
'trigger': 'start',
333
'source': 'idle',
334
'dest': 'working',
335
'after': 'log_transition'
336
},
337
{
338
'trigger': 'finish',
339
'source': 'working',
340
'dest': 'done',
341
'after': 'log_transition'
342
}
343
]
344
345
robot = LoggingRobot()
346
machine = Machine(
347
model=robot,
348
states=states,
349
transitions=transitions,
350
initial='idle',
351
send_event=True # This ensures EventData is passed to callbacks
352
)
353
354
robot.start(task_id=123, priority='high') # Arguments will be in event_data
355
robot.finish(result='success')
356
```
357
358
### Custom Event Handling
359
360
```python
361
from transitions import Machine, Event
362
363
class CustomMachine(Machine):
364
def __init__(self, *args, **kwargs):
365
super().__init__(*args, **kwargs)
366
# Override event creation to add custom behavior
367
368
def _create_event(self, trigger, machine):
369
"""Create custom event with additional logging"""
370
event = Event(trigger, machine)
371
372
# Add custom behavior to all events
373
original_trigger = event.trigger
374
375
def logged_trigger(model, *args, **kwargs):
376
print(f"Event '{trigger}' triggered on {model}")
377
result = original_trigger(model, *args, **kwargs)
378
print(f"Event result: {result}")
379
return result
380
381
event.trigger = logged_trigger
382
return event
383
384
# This machine will log all event triggers
385
machine = CustomMachine(model=MyModel(), states=['A', 'B'], transitions=[
386
{'trigger': 'go', 'source': 'A', 'dest': 'B'}
387
], initial='A')
388
```
389
390
### Conditional Transitions with Complex Logic
391
392
```python
393
from transitions import Machine, Condition
394
395
class SmartThermostat:
396
def __init__(self):
397
self.temperature = 20
398
self.target_temp = 22
399
self.hvac_enabled = True
400
401
def too_cold(self):
402
return self.temperature < self.target_temp - 1
403
404
def too_hot(self):
405
return self.temperature > self.target_temp + 1
406
407
def hvac_available(self):
408
return self.hvac_enabled
409
410
def start_heating(self):
411
print(f"Starting heating. Current: {self.temperature}°C, Target: {self.target_temp}°C")
412
413
def start_cooling(self):
414
print(f"Starting cooling. Current: {self.temperature}°C, Target: {self.target_temp}°C")
415
416
states = ['idle', 'heating', 'cooling', 'error']
417
418
transitions = [
419
{
420
'trigger': 'check_temperature',
421
'source': 'idle',
422
'dest': 'heating',
423
'conditions': ['too_cold', 'hvac_available'],
424
'before': 'start_heating'
425
},
426
{
427
'trigger': 'check_temperature',
428
'source': 'idle',
429
'dest': 'cooling',
430
'conditions': ['too_hot', 'hvac_available'],
431
'before': 'start_cooling'
432
},
433
{
434
'trigger': 'check_temperature',
435
'source': ['heating', 'cooling'],
436
'dest': 'idle',
437
'unless': ['too_cold', 'too_hot'] # Return to idle if temperature is right
438
},
439
{
440
'trigger': 'hvac_failure',
441
'source': ['heating', 'cooling'],
442
'dest': 'error'
443
}
444
]
445
446
thermostat = SmartThermostat()
447
machine = Machine(model=thermostat, states=states, transitions=transitions, initial='idle')
448
449
# Simulate temperature changes
450
thermostat.temperature = 18 # Too cold
451
thermostat.check_temperature() # Should start heating
452
453
thermostat.temperature = 22 # Right temperature
454
thermostat.check_temperature() # Should return to idle
455
```