In [1]:
import os, sys, inspect, io

cmd_folder = os.path.realpath(
    os.path.dirname(
        os.path.abspath(os.path.split(inspect.getfile( inspect.currentframe() ))[0])))

if cmd_folder not in sys.path:
    sys.path.insert(0, cmd_folder)
    
from transitions import *
from transitions.extensions import GraphMachine
from IPython.display import Image, display, display_png

class Matter(object):
    def alert(self):
        pass
    
    def resume(self):
        pass
    
    def notify(self):
        pass
    
    def is_valid(self):
        return True
    
    def is_not_valid(self):
        return False
    
    def is_also_valid(self):
        return True
    
    # graph object is created by the machine
    def show_graph(self, **kwargs):
        stream = io.BytesIO()
        self.get_graph(**kwargs).draw(stream, prog='dot', format='png')
        display(Image(stream.getvalue()))
        
extra_args = dict(initial='solid', title='Matter is Fun!',
                  show_conditions=True, show_state_attributes=True)

The Matter graph


In [2]:
transitions = [
    { 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
    { 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas', 'conditions':'is_valid' },
    { 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas', 'unless':'is_not_valid' },
    { 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma', 
      'conditions':['is_valid','is_also_valid'] }
]
states=['solid', 'liquid', {'name': 'gas', 'on_exit': ['resume', 'notify']},
        {'name': 'plasma', 'on_enter': 'alert', 'on_exit': 'resume'}]

model = Matter()
machine = GraphMachine(model=model, states=states, transitions=transitions, 
                       show_auto_transitions=True, **extra_args)
model.show_graph()


Hide auto transitions


In [3]:
machine.auto_transitions_markup = False # hide auto transitions
model.show_graph(force_new=True) # rerender graph


Previous state and transition notation


In [4]:
model.melt()
model.show_graph()



In [5]:
model.evaporate()
model.show_graph()



In [6]:
model.ionize()
model.show_graph()


One Machine and multiple models


In [7]:
# multimodel test
model1 = Matter()
model2 = Matter()
machine = GraphMachine(model=[model1, model2], states=states, transitions=transitions, **extra_args)
model1.melt()
model1.show_graph()



In [8]:
model2.sublimate()
model2.show_graph()


Show only the current region of interest


In [9]:
# show only region of interest which is previous state, active state and all reachable states
model2.show_graph(show_roi=True)


Example graph from Readme.md


In [10]:
from transitions.extensions.states import Timeout, Tags, add_state_features

@add_state_features(Timeout, Tags)
class CustomMachine(GraphMachine):
    pass


states = ['new', 'approved', 'ready', 'finished', 'provisioned',
          {'name': 'failed', 'on_enter': 'notify', 'on_exit': 'reset',
           'tags': ['error', 'urgent'], 'timeout': 10, 'on_timeout': 'shutdown'},
          'in_iv', 'initializing', 'booting', 'os_ready', {'name': 'testing', 'on_exit': 'create_report'},
          'provisioning']

transitions = [{'trigger': 'approve', 'source': ['new', 'testing'], 'dest':'approved',
                'conditions': 'is_valid', 'unless': 'abort_triggered'},
               ['fail', '*', 'failed'],
               ['add_to_iv', ['approved', 'failed'], 'in_iv'],
               ['create', ['failed','in_iv'], 'initializing'],
               ['init', 'in_iv', 'initializing'],
               ['finish', 'approved', 'finished'],
               ['boot', ['booting', 'initializing'], 'booting'],
               ['ready', ['booting', 'initializing'], 'os_ready'],
               ['run_checks', ['failed', 'os_ready'], 'testing'],
               ['provision', ['os_ready', 'failed'], 'provisioning'],
               ['provisioning_done', 'provisioning', 'os_ready']]

class Model():
    def is_valid(self):
        return True
    
    def abort_triggered(self):
        return False
    
    # graph object is created by the machine
    def show_graph(self, **kwargs):
        stream = io.BytesIO()
        self.get_graph(**kwargs).draw(stream, prog='dot', format='png')
        display(Image(stream.getvalue()))

extra_args['title'] = "System State"
extra_args['initial'] = "new"
model = Model()
machine = CustomMachine(model=model, states=states, transitions=transitions, **extra_args)
model.approve()
model.show_graph()


Custom styling


In [15]:
# thanks to @dan-bar-dov (https://github.com/pytransitions/transitions/issues/367)
model = Model()

transient_states = ['T1', 'T2', 'T3']
target_states = ['G1', 'G2']
fail_states = ['F1', 'F2']
transitions = [['eventA', 'INITIAL', 'T1'], ['eventB', 'INITIAL', 'T2'], ['eventC', 'INITIAL', 'T3'],
               ['success', ['T1', 'T2'], 'G1'], ['defered', 'T3', 'G2'], ['fallback', ['T1', 'T2'], 'T3'],
               ['error', ['T1', 'T2'], 'F1'], ['error', 'T3', 'F2']]

machine = GraphMachine(model, states=transient_states + target_states + fail_states,
                       transitions=transitions, initial='INITIAL', show_conditions=True,
                       show_state_attributes=True)

machine.machine_attributes['ratio'] = '0.471'
machine.style_attributes['node']['fail'] = {'fillcolor': 'brown1'}
machine.style_attributes['node']['transient'] = {'fillcolor': 'gold'}
machine.style_attributes['node']['target'] = {'fillcolor': 'chartreuse'}
model.eventC()

# customize node styling
for s in transient_states:
    machine.model_graphs[model].set_node_style(s, 'transient')
for s in target_states:
    machine.model_graphs[model].set_node_style(s, 'target')
for s in fail_states:
    machine.model_graphs[model].set_node_style(s, 'fail')

# draw the whole graph ...
model.show_graph()



In [ ]: