Search code examples
pytransitions

GraphMachine doesn't generate graph for NarcolepticSuperhero quickstart example


I tried creating a Diagram using the NarcolepticSuperhero machine defined in the GitHub documentation but it only outputs this:

enter image description here

Steps to recreate:

  1. Create a file named test.py, with this content:
    from transitions import Machine
    from transitions.extensions import GraphMachine
    import random
    
    class NarcolepticSuperhero(object):
    
        # Define some states. Most of the time, narcoleptic superheroes are just like
        # everyone else. Except for...
        states = ['asleep', 'hanging out', 'hungry', 'sweaty', 'saving the world']
    
        def __init__(self, name):
    
            # No anonymous superheroes on my watch! Every narcoleptic superhero gets
            # a name. Any name at all. SleepyMan. SlumberGirl. You get the idea.
            self.name = name
    
            # What have we accomplished today?
            self.kittens_rescued = 0
    
            # Initialize the state machine
            self.machine = Machine(model=self, states=NarcolepticSuperhero.states, initial='asleep')
    
            # Add some transitions. We could also define these using a static list of
            # dictionaries, as we did with states above, and then pass the list to
            # the Machine initializer as the transitions= argument.
    
            # At some point, every superhero must rise and shine.
            self.machine.add_transition(trigger='wake_up', source='asleep', dest='hanging out')
    
            # Superheroes need to keep in shape.
            self.machine.add_transition('work_out', 'hanging out', 'hungry')
    
            # Those calories won't replenish themselves!
            self.machine.add_transition('eat', 'hungry', 'hanging out')
    
            # Superheroes are always on call. ALWAYS. But they're not always
            # dressed in work-appropriate clothing.
            self.machine.add_transition('distress_call', '*', 'saving the world',
                                before='change_into_super_secret_costume')
    
            # When they get off work, they're all sweaty and disgusting. But before
            # they do anything else, they have to meticulously log their latest
            # escapades. Because the legal department says so.
            self.machine.add_transition('complete_mission', 'saving the world', 'sweaty',
                                after='update_journal')
    
            # Sweat is a disorder that can be remedied with water.
            # Unless you've had a particularly long day, in which case... bed time!
            self.machine.add_transition('clean_up', 'sweaty', 'asleep', conditions=['is_exhausted'])
            self.machine.add_transition('clean_up', 'sweaty', 'hanging out')
    
            # Our NarcolepticSuperhero can fall asleep at pretty much any time.
            self.machine.add_transition('nap', '*', 'asleep')
    
        def update_journal(self):
            """ Dear Diary, today I saved Mr. Whiskers. Again. """
            self.kittens_rescued += 1
    
        @property
        def is_exhausted(self):
            """ Basically a coin toss. """
            return random.random() < 0.5
    
        def change_into_super_secret_costume(self):
            print("Beauty, eh?")
            
    batman = NarcolepticSuperhero("Batman")
    batman.wake_up()
    batman.state
    
    machine = GraphMachine(model=batman)
    batman.get_graph().draw("test.png", prog='dot')
  1. Install the requirements (Ubuntu 20.10, I tested on it's docker image) and run the script:
$ apt install graphviz graphviz-dev
$ pip3 install transitions graphviz pygraphviz
$ python3 test.py
  1. Check the generated image

Solution

  • You are instantiating a GraphMachine without any states and transitions here:

    machine = GraphMachine(model=batman)
    

    You basically reuse NarcolepticSuperhero as a model for that new machine but what you should do instead is changing the NarcolepticSuperhero's machine into a GraphMachine:

                # Initialize the state machine
                self.machine = GraphMachine(model=self, states=NarcolepticSuperhero.states, initial='asleep')
    
    # [...]
        batman.state
        
        # this is not required anymore
        # machine = GraphMachine(model=batman)
        batman.get_graph().draw("test.png", prog='dot')