def main() -> None: """Code here shows how everything is wired together. The following lines are the expected output: state 'state-a' got event 'None' state 'state-b' got event 'Event(name="to-state-b", data={})' """ # Create the state machine -- the root context of every workflow. state_machine = Statechart("state-machine") # Create the workflow metadata with all the data accessible to all states and workflows. metadata = WorkflowMetadata(a=10, b="foo", c="bar") # Create the workflow factory backed by the workflow metadata. factory = WorkflowFactory(metadata) # Create the main workflow using the workflow factory. main_workflow = factory.create_workflow("main-workflow", state_machine, MainWorkflow) # Set up the main transitions. init = InitialState(state_machine) final = FinalState(state_machine) Transition(init, main_workflow) Transition(main_workflow, final, event=Event("final")) # Run the state machine. state_machine.start() state_machine.dispatch(Event("to-state-b"))
def test_default_transition_from_finished_composite_state(self): statechart = Statechart(name='statechart') statechart_init = InitialState(statechart) composite_state = CompositeState(name='composite', context=statechart) comp_init = InitialState(composite_state) a = State(name='a', context=composite_state) comp_final = FinalState(composite_state) Transition(start=statechart_init, end=composite_state) Transition(start=comp_init, end=a) Transition(start=a, end=comp_final, event=Event('e')) b = State(name='b', context=statechart) c = State(name='c', context=statechart) d = State(name='d', context=statechart) Transition(start=composite_state, end=c, event=Event('f')) Transition(start=composite_state, end=b, event=Event('e')) Transition(start=composite_state, end=d) statechart.start() assert statechart.is_active('a') statechart.dispatch(Event('e')) assert statechart.is_active('d')
def init(self, factory: WorkflowFactory) -> None: state_a = factory.create_state("state-a", self, StateA) state_b = factory.create_state("state-b", self, StateB) init = InitialState(self) final = FinalState(self) Transition(init, state_a) Transition(state_a, state_b, event=Event("to-state-b")) Transition(state_b, final, event=Event("final"))
def test_transition_hierarchy(self, empty_statechart): sc = empty_statechart init = InitialState(sc) top = CompositeState(name='top', context=sc) top_init = InitialState(top) middle_a = CompositeState(name='middle_a', context=top) middle_b = CompositeState(name='middle_b', context=top) middle_a_init = InitialState(middle_a) bottom_a1 = State(name='bottom_a1', context=middle_a) bottom_a2 = State(name='bottom_a2', context=middle_a) middle_b_init = InitialState(middle_b) bottom_b1 = State(name='bottom_b1', context=middle_b) # Setup default transitions Transition(start=init, end=top) Transition(start=top_init, end=middle_a) Transition(start=middle_a_init, end=bottom_a1) Transition(start=middle_b_init, end=bottom_b1) # Setup event triggers across = Event('across') up = Event('up') # Setup external transitions Transition(start=bottom_a1, end=bottom_a2, event=across) Transition(start=bottom_a2, end=middle_b, event=up) Transition(start=middle_b, end=middle_a, event=across) sc.start() assert sc.is_active('top') assert sc.is_active('middle_a') assert sc.is_active('bottom_a1') sc.dispatch(across) assert sc.is_active('top') assert sc.is_active('middle_a') assert sc.is_active('bottom_a2') sc.dispatch(up) assert sc.is_active('top') assert sc.is_active('middle_b') assert sc.is_active('bottom_b1') sc.dispatch(across) assert sc.is_active('top') assert sc.is_active('middle_a') assert sc.is_active('bottom_a1')
def test_event_data(self): name = 'ev' data = {'a': 1} ev = Event(name, data) assert ev.name == name assert ev.data == data
def test_transition_from_initial_state_with_event_trigger(self): startchart = Statechart(name='statechart') initial_state = InitialState(startchart) default_state = State(name='default', context=startchart) with pytest.raises(RuntimeError): Transition(start=initial_state, end=default_state, event=Event('event'))
def test_submachines(self): statechart = Statechart(name='statechart') init = InitialState(statechart) top_a = self.Submachine('top a', statechart) top_b = self.Submachine('top b', statechart) top_a_to_b = Event('top ab') Transition(start=init, end=top_a) Transition(start=top_a, end=top_b, event=top_a_to_b) statechart.start() assert statechart.is_active('top a') assert statechart.is_active('sub state a') statechart.dispatch(top_a.sub_a_to_b) assert statechart.is_active('top a') assert statechart.is_active('sub state b') statechart.dispatch(top_a_to_b) assert statechart.is_active('top b') assert statechart.is_active('sub state a') statechart.dispatch(top_a.sub_a_to_b) assert statechart.is_active('top b') assert statechart.is_active('sub state b')
def concurrent_statechart(): def composite(name, context): composite = CompositeState(name=name, context=context) composite.init = InitialState(composite) composite.state = State(name='state', context=composite) composite.final = FinalState(composite) Transition(start=composite.init, end=composite.state) Transition(start=composite.state, end=composite.final, event=Event('finish')) return composite sc = Statechart('simple') sc.init = InitialState(sc) sc.concurrent = ConcurrentState(name='compound', context=sc) sc.concurrent.composite_a = composite(name='a', context=sc.concurrent) sc.concurrent.composite_b = composite(name='b', context=sc.concurrent) sc.concurrent.composite_c = composite(name='c', context=sc.concurrent) sc.final = FinalState(sc) Transition(start=sc.init, end=sc.concurrent) Transition(start=sc.concurrent, end=sc.final, event=Event('finish')) return sc
def __init__(self, start, end, event=None, guard=None, action=None): self._logger = logging.getLogger(self.__class__.__name__) self.start = start self.end = end self.event = event self.guard = guard self.action = action if isinstance(event, str): self.event = Event(event) if guard is not None and not callable(guard): raise ValueError('Guard must be callable') if action is not None and not callable(action): raise ValueError('Action must be callable') """ Used to store the states that will get activated """ self.activate = list() """ Used to store the states that will get de-activated """ self.deactivate = list() self._calculate_state_set(start=start, end=end) start.add_transition(self)
def test_execute(self, empty_statechart): initial_state = InitialState(empty_statechart) default_state = self.StateSpy(name='next', context=empty_statechart) Transition(start=initial_state, end=default_state) internal_event = Event(name='internal-event') internal_action = ActionSpy() InternalTransition(state=default_state, event=internal_event, action=internal_action) empty_statechart.start() assert empty_statechart.is_active('next') assert default_state.entry_executed is True assert default_state.do_executed is True assert default_state.exit_executed is False # Ensure we don't leave and re-enter the default state after triggering # the internal transition. default_state.entry_executed = False default_state.do_executed = False empty_statechart.dispatch(internal_event) assert default_state.entry_executed is False assert default_state.do_executed is False assert default_state.exit_executed is False assert internal_action.executed is True
def test_transition_event_consumed(self, empty_statechart): sc = empty_statechart init = InitialState(sc) # a = State(name='a', context=sc) b = State(name='b', context=sc) cs = CompositeState(name='cs', context=sc) cs_init = InitialState(cs) cs_a = State(name='cs a', context=cs) cs_b = State(name='cs b', context=cs) Transition(start=init, end=cs) Transition(start=cs, end=cs_init) Transition(start=cs_init, end=cs_a) Transition(start=cs_a, end=cs_b, event='home') Transition(start=cs, end=b, event='home') sc.start() assert sc.is_active('cs a') sc.dispatch(Event('home')) assert sc.is_active('cs b')
def test_composite_statechart_finished(self): statechart = Statechart(name='statechart') init = InitialState(statechart) final = FinalState(statechart) composite = CompositeState(name='composite', context=statechart) composite_init = InitialState(composite) composite_default = State(name='composite_default', context=composite) composite_final = FinalState(composite) finish = Event('finish') Transition(start=init, end=composite) Transition(start=composite_init, end=composite_default) Transition(start=composite_default, end=composite_final, event=finish) Transition(start=composite, end=final) statechart.start() assert statechart.is_active('composite') assert statechart.is_active('composite_default') assert not statechart.is_finished() statechart.dispatch(finish) assert statechart.is_finished()
def test_light_switch(self): """ --Flick--> init ---> Off On <--Flick-- Entry: Light = ON Exit: Light = OFF Internal: Flick: Count++ """ class On(State): def __init__(self, name, context, data): State.__init__(self, name=name, context=context) self.data = data def entry(self, event): self.data['light'] = 'on' def exit(self, event): self.data['light'] = 'off' def handle_internal(self, event): if event.name == 'flick': self.data['on_count'] += 1 sm = Statechart(name='sm') data = dict(light='off', on_count=0) sm.initial_state = InitialState(context=sm) off = State(name='off', context=sm) on = On(name='on', context=sm, data=data) Transition(start=sm.initial_state, end=off) Transition(start=off, end=on, event=Event('flick')) Transition(start=on, end=off, event=Event('flick')) sm.start() assert data['light'] == 'off' sm.dispatch(Event('flick')) assert data['light'] == 'on' assert data['on_count'] == 0 sm.dispatch(Event('flick')) assert data['light'] == 'off' assert data['on_count'] == 1
def simple_statechart(): sc = Statechart('simple') sc.init = InitialState(sc) sc.state = State(name='state', context=sc) sc.final = FinalState(sc) Transition(start=sc.init, end=sc.state) Transition(start=sc.state, end=sc.final, event=Event('finish')) return sc
def __init__(self, name, context): CompositeState.__init__(self, name=name, context=context) init = InitialState(self) self.state_a = State(name='sub state a', context=self) self.state_b = State(name='sub state b', context=self) self.sub_a_to_b = Event('sub_ab') Transition(start=init, end=self.state_a) Transition(start=self.state_a, end=self.state_b, event=self.sub_a_to_b)
def composite(name, context): composite = CompositeState(name=name, context=context) composite.init = InitialState(composite) composite.state = State(name='state', context=composite) composite.final = FinalState(composite) Transition(start=composite.init, end=composite.state) Transition(start=composite.state, end=composite.final, event=Event('finish')) return composite
def test_local_transition(self, empty_statechart): init = InitialState(empty_statechart) state_spy = self.StateSpy(name='spy', context=empty_statechart) Transition(start=init, end=state_spy) Transition(start=state_spy, end=state_spy.local, event=Event('local')) empty_statechart.start() assert empty_statechart.is_active('spy') assert empty_statechart.is_active('default') assert state_spy.entries is 1 assert state_spy.exits is 0 empty_statechart.dispatch(Event('local')) assert empty_statechart.is_active('spy') assert not empty_statechart.is_active('default') assert empty_statechart.is_active('local') assert state_spy.entries is 1 assert state_spy.exits is 0
def __init__(self, start, end, event=None, guard=None, action=None): self._logger = logging.getLogger(self.__class__.__name__) self.start = start self.end = end self.event = event self.guard = guard self.action = action if isinstance(event, str): self.event = Event(event) """ Used to store the states that will get activated """ self.activate = list() """ Used to store the states that will get de-activated """ self.deactivate = list() self._calculate_state_set(start=start, end=end) start.add_transition(self)
def test_transition_action_function_with_event(self, empty_statechart): self.state = False def set_state(event): self.state = event.data['state'] sc = empty_statechart initial = InitialState(sc) default = State(name='default', context=sc) next = State(name='next', context=sc) Transition(start=initial, end=default) Transition(start=default, end=next, event='next', action=set_state) sc.start() sc.dispatch(Event(name='next', data={'state': True})) assert self.state
def test_simple_statechart_finished(self): statechart = Statechart(name='statechart') init = InitialState(statechart) default = State(name='default', context=statechart) final = FinalState(statechart) finish = Event('finish') Transition(start=init, end=default) Transition(start=default, end=final, event=finish) statechart.start() assert statechart.is_active('default') assert not statechart.is_finished() statechart.dispatch(finish) assert statechart.is_finished()
def test_external_transition(self, empty_statechart): init = InitialState(empty_statechart) state_spy = self.StateSpy(name='spy', context=empty_statechart) Transition(start=init, end=state_spy) Transition(start=state_spy, end=state_spy, event='extern') empty_statechart.start() assert empty_statechart.is_active('spy') assert state_spy.entries is 1 assert state_spy.exits is 0 empty_statechart.dispatch(Event('extern')) # After dispatching the external event from the state spy, the # state should be deactivated and activated again. assert empty_statechart.is_active('spy') assert state_spy.entries is 2 assert state_spy.exits is 1
def test_transition_action_function_with_metadata(self, empty_statechart): sc = empty_statechart sc.metadata.state = True self.state = False def set_state(event): self.state = sc.metadata.state initial = InitialState(sc) default = State(name='default', context=sc) next = State(name='next', context=sc) Transition(start=initial, end=default) Transition(start=default, end=next, event='next', action=set_state) sc.start() sc.dispatch(Event('next')) assert self.state
def test_transition_action_function(self, empty_statechart): self.state = False def set_state(state): self.state = bool(state) set_true = partial(set_state, True) sc = empty_statechart initial = InitialState(sc) default = State(name='default', context=sc) next = State(name='next', context=sc) Transition(start=initial, end=default) Transition(start=default, end=next, event='next', action=set_true) sc.start() sc.dispatch(Event('next')) assert self.state
def test_top_level_internal_transition(self, empty_statechart): sc = empty_statechart sc_init = InitialState(sc) cs = CompositeState(name='cs', context=sc) cs_init = InitialState(cs) cs_default = State(name='cs_default', context=cs) Transition(start=sc_init, end=cs) Transition(start=cs_init, end=cs_default) test_event = Event('internal-event-trigger') InternalTransition(state=cs, event=test_event) sc.start() assert sc.is_active('cs_default') sc.dispatch(test_event) assert sc.is_active('cs_default')
def test_default_transition_isnt_executed_from_unfinished_composite_state( self): statechart = Statechart(name='statechart') statechart_init = InitialState(statechart) composite_state = CompositeState(name='composite', context=statechart) comp_init = InitialState(composite_state) a = State(name='a', context=composite_state) Transition(start=statechart_init, end=composite_state) Transition(start=comp_init, end=a) b = State(name='b', context=statechart) Transition(start=composite_state, end=b) statechart.start() assert statechart.is_active('a') statechart.dispatch(Event('e')) assert statechart.is_active('a')
def test_diff_data(self, event): diff_event = Event(name='diff_event', data={'a': 2}) assert event != diff_event
def test_diff_names(self, event): diff_event = Event(name='diff_event') assert diff_event is not event
def test_create_event(self): Event(name='event')
def event(): return Event(name='event', data={'a': 1})
def test_activate_shallow_history_state(self): """ statechart: statechart_init | *** csa ********************** *** csb ************* * * * * * csa_init-csa_hist * * csb_init * * | * --J--> * | * * A --I--> B * <--K-- * C --L--> D * * * * * ****************************** ********************* """ # Top level states statechart = Statechart(name='statechart') csa = CompositeState(name='csa', context=statechart) csb = CompositeState(name='csb', context=statechart) # Child states # statechart statechart_init = InitialState(statechart) # csa csa_init = InitialState(csa) csa_hist = ShallowHistoryState(context=csa) A = State(name='A', context=csa) B = State(name='B', context=csa) # csb csb_init = InitialState(csb) C = State(name='C', context=csb) D = State(name='D', context=csb) # Events I = Event(name='I') J = Event(name='J') K = Event(name='K') L = Event(name='L') # Transitions between states & event triggers Transition(start=statechart_init, end=csa) Transition(start=csa_init, end=csa_hist) Transition(start=csa_hist, end=A) Transition(start=A, end=B, event=I) Transition(start=csa, end=csb, event=J) Transition(start=csb, end=csa, event=K) Transition(start=csb_init, end=C) Transition(start=C, end=D, event=L) # Execute statechart statechart.start() statechart.dispatch(I) # Assert we have reached state B, history should restore this state assert statechart.is_active('B') statechart.dispatch(J) # Assert we have reached state C assert statechart.is_active('C') statechart.dispatch(K) # Assert the history state has restored state B assert statechart.is_active('B')