pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.52k stars 525 forks source link

AsyncGraphMachine cannot be initialized with Enums as states in 0.8.0 #400

Closed kbinpgh closed 4 years ago

kbinpgh commented 4 years ago

The following snippet of code throws an error when trying to initialize an AsyncGraphMachine

from transitions.extensions import MachineFactory
from enum import Enum, auto
import time

class State(Enum):
    START = auto(),
    ONE = auto(),
    TWO = auto(),
    THREE = auto(),

def before(which=None, future=None):
    global start_time
    print(f"Before transition{which}")
    if which == 'one':
        start_time = time.time()

def after(which=None):
    global start_time
    print(f"After transition{which}")
    if which == 'reset':
        end_time = time.time()
        print(f"Done at {end_time} in {end_time-start_time}s")    

transition_1 = dict(
    trigger='one',
    source=State.START,
    dest=State.ONE,
    before=before,
    after=after,
)

transition_2 = dict(
    trigger='two',
    source=State.ONE,
    dest=State.TWO,
    before=before,
    after=after,
)

transition_3 = dict(
    trigger='three',
    source=State.TWO,
    dest=State.THREE,
    before=before,
    after=after,
)

transition_0 = dict(
    trigger='reset',
    source=State.THREE,
    dest=State.START,
    before=before,
    after=after,
)

class Model(object):
    pass

global start_time
start_time = None
model = Model()

machine = MachineFactory.get_predefined(graph=True, asyncio=True)(
    model=model,
    states = [v for v in State],
    transitions = [transition_0, transition_1, transition_2, transition_3],
    queued = True,
    initial = State.START,
)

If the line: initial = State.START, is commented out, there is no error thrown, but a default, unconnected inital state is created in the machine. Transitions seem to work normally with this workaround.

Error details:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/.local/lib/python3.7/site-packages/pygraphviz/agraph.py in __new__(self, graph, name, nh)
   1614             try:
-> 1615                 nh = gv.agnode(graph.handle, n.encode(graph.encoding), _Action.find)
   1616             except KeyError:

KeyError: 'agnode: no key'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-58-83ccb3c51131> in <module>
     66     transitions = [transition_0, transition_1, transition_2, transition_3],
     67     queued = True,
---> 68     initial = State.START
     69 )

/usr/local/lib/python3.7/site-packages/transitions/extensions/diagrams.py in __init__(self, *args, **kwargs)
    139 
    140         _LOGGER.debug("Using graph engine %s", self.graph_cls)
--> 141         _super(GraphMachine, self).__init__(*args, **kwargs)
    142 
    143         # for backwards compatibility assign get_combined_graph to get_graph

/usr/local/lib/python3.7/site-packages/transitions/extensions/markup.py in __init__(self, *args, **kwargs)
     26                 self._add_markup_model(m)
     27         else:
---> 28             super(MarkupMachine, self).__init__(*args, **kwargs)
     29             self._markup['before_state_change'] = [x for x in (rep(f) for f in self.before_state_change) if x]
     30             self._markup['after_state_change'] = [x for x in (rep(f) for f in self.before_state_change) if x]

/usr/local/lib/python3.7/site-packages/transitions/core.py in __init__(self, model, states, initial, transitions, send_event, auto_transitions, ordered_transitions, ignore_invalid_triggers, before_state_change, after_state_change, name, queued, prepare_event, finalize_event, model_attribute, **kwargs)
    572 
    573         if model:
--> 574             self.add_model(model)
    575 
    576     def add_model(self, model, initial=None):

/usr/local/lib/python3.7/site-packages/transitions/extensions/diagrams.py in add_model(self, model, initial)
    203                 raise AttributeError('Model already has a get_graph attribute. Graph retrieval cannot be bound.')
    204             setattr(mod, 'get_graph', partial(self._get_graph, mod))
--> 205             _ = mod.get_graph(title=self.title, force_new=True)  # initialises graph
    206 
    207     def add_states(self, states, on_enter=None, on_exit=None,

/usr/local/lib/python3.7/site-packages/transitions/extensions/diagrams.py in _get_graph(self, model, title, force_new, show_roi)
    169             self.model_graphs[model] = grph
    170             try:
--> 171                 self.model_graphs[model].set_node_style(getattr(model, self.model_attribute), 'active')
    172             except AttributeError:
    173                 _LOGGER.info("Could not set active state of diagram")

/usr/local/lib/python3.7/site-packages/transitions/extensions/diagrams_pygraphviz.py in set_node_style(self, state, style)
    129 
    130     def set_node_style(self, state, style):
--> 131         node = self.fsm_graph.get_node(state)
    132         style_attr = self.fsm_graph.style_attributes.get('node', {}).get(style)
    133         node.attr.update(style_attr)

~/.local/lib/python3.7/site-packages/pygraphviz/agraph.py in get_node(self, n)
    420         a
    421         """
--> 422         return Node(self, n)
    423 
    424     def add_edge(self, u, v=None, key=None, **attr):

~/.local/lib/python3.7/site-packages/pygraphviz/agraph.py in __new__(self, graph, name, nh)
   1615                 nh = gv.agnode(graph.handle, n.encode(graph.encoding), _Action.find)
   1616             except KeyError:
-> 1617                 raise KeyError("Node %s not in graph." % n)
   1618 
   1619         n.ghandle = graph.handle

KeyError: 'Node State.START not in graph.'
kbinpgh commented 4 years ago

Possibly a dup of 391

kbinpgh commented 4 years ago

setting use_pygraphviz=False when initializing the machine works

aleneum commented 4 years ago

I can confirm multiple issues when using Enum and GraphMachine and will try to fix them today.