Exemplo n.º 1
0
def import_from_dict(data: dict) -> Statechart:
    data = data['statechart']

    statechart = Statechart(name=data['name'],
                            description=data.get('description', None),
                            preamble=data.get('preamble', None))

    states = []  # (StateMixin instance, parent name)
    transitions = []  # Transition instances
    # (State dict, parent name)
    data_to_consider = [(data['root state'], None)]  # type: List[Tuple[dict, Optional[str]]]

    while data_to_consider:
        state_data, parent_name = data_to_consider.pop()

        # Get state
        try:
            state = _import_state_from_dict(state_data)
        except StatechartError:
            raise
        except Exception as e:
            raise StatechartError('Unable to load given YAML') from e
        states.append((state, parent_name))

        # Get substates
        if isinstance(state, CompoundState):
            for substate_data in state_data['states']:
                data_to_consider.append((substate_data, state.name))
        elif isinstance(state, OrthogonalState):
            for substate_data in state_data['parallel states']:
                data_to_consider.append((substate_data, state.name))

        # Get transition(s)
        for transition_data in state_data.get('transitions', []):
            try:
                transition = _import_transition_from_dict(state.name, transition_data)
            except StatechartError:
                raise
            except Exception as e:
                raise StatechartError('Unable to load given YAML') from e
            transitions.append(transition)

    # Register on statechart
    for state, parent in states:
        statechart.add_state(state, parent)
    for transition in transitions:
        statechart.add_transition(transition)

    return statechart
Exemplo n.º 2
0
    def generic_test(self, condition: Condition, success_expected: bool, failure_expected: bool, delay: int = 0):
        statechart = Statechart('test')
        parallel_state = OrthogonalState('parallel_state')
        statechart.add_state(parallel_state, parent=None)

        initial_state = CompoundState('initial_state', initial='Cond')
        statechart.add_state(initial_state, "parallel_state")

        statechart.add_state(BasicState('success'), 'initial_state')
        statechart.add_state(BasicState('failure'), 'initial_state')

        condition.add_to(statechart=statechart,
                         id='Cond',
                         parent_id='initial_state',
                         status_id=parallel_state,
                         success_id='success',
                         failure_id='failure')

        interpreter = Interpreter(statechart)

        self.assertFalse('success' in interpreter.configuration)
        self.assertFalse('failure' in interpreter.configuration)

        interpreter.execute()

        interpreter.time += delay
        interpreter.queue(Event(Condition.STEP_ENDED_EVENT))
        interpreter.queue(Event(Condition.STEP_ENDED_EVENT))
        interpreter.execute()

        self.assertEqual(success_expected, 'success' in interpreter.configuration)
        self.assertEqual(failure_expected, 'failure' in interpreter.configuration)
Exemplo n.º 3
0
 def setUp(self):
     self.sequential_statechart = Statechart('tested_statechart')
     initial_state = CompoundState('initial_state', initial='a_state')
     a_state = BasicState('a_state')
     b_state = BasicState('b_state')
     self.sequential_statechart.add_state(initial_state, None)
     self.sequential_statechart.add_state(a_state, 'initial_state')
     self.sequential_statechart.add_state(b_state, 'initial_state')
     self.sequential_statechart.add_transition(Transition(source='a_state', target='b_state', event='event'))
Exemplo n.º 4
0
    def test_name_collision(self):
        root = CompoundState('root', 'a')
        sc = Statechart('test')
        sc.add_state(root, None)
        s1 = BasicState('a')
        s2 = BasicState('a')
        sc.add_state(s1, parent='root')

        with pytest.raises(StatechartError) as e:
            sc.add_state(s2, parent='root')
        assert 'already exists!' in str(e.value)
Exemplo n.º 5
0
    def test_root_already_defined(self):
        root = CompoundState('root', 'a')
        sc = Statechart('test')
        sc.add_state(root, None)

        with pytest.raises(StatechartError) as e:
            sc.add_state(root, None)
        assert 'already exists!' in str(e.value)
Exemplo n.º 6
0
def import_from_dict(data: Mapping[str, Any]) -> Statechart:
    data = data['statechart']

    statechart = Statechart(name=data['name'],
                            description=data.get('description', None),
                            preamble=data.get('preamble', None))

    states = []  # (StateMixin instance, parent name)
    transitions = []  # Transition instances
    # (State dict, parent name)
    data_to_consider = [
        (data['root state'], None)
    ]  # type: List[Tuple[Mapping[str, Any], Optional[str]]]

    while data_to_consider:
        state_data, parent_name = data_to_consider.pop()

        # Get state
        try:
            state = _import_state_from_dict(state_data)
        except StatechartError:
            raise
        except Exception as e:
            raise StatechartError('Unable to load given YAML') from e
        states.append((state, parent_name))

        # Get substates
        if isinstance(state, CompoundState):
            for substate_data in state_data['states']:
                data_to_consider.append((substate_data, state.name))
        elif isinstance(state, OrthogonalState):
            for substate_data in state_data['parallel states']:
                data_to_consider.append((substate_data, state.name))

        # Get transition(s)
        for transition_data in state_data.get('transitions', []):
            try:
                transition = _import_transition_from_dict(
                    state.name, transition_data)
            except StatechartError:
                raise
            except Exception as e:
                raise StatechartError('Unable to load given YAML') from e
            transitions.append(transition)

    # Register on statechart
    for state, parent in states:
        statechart.add_state(state, parent)
    for transition in transitions:
        statechart.add_transition(transition)

    return statechart
Exemplo n.º 7
0
    def generic_test(self,
                     tested_statechart: Statechart,
                     events: list,
                     property: Condition,
                     expected_success: bool,
                     expected_failure: bool):
        from sismic.interpreter import log_trace
        tester_statechart = Statechart('tester_statechart')

        tester_statechart.add_state(OrthogonalState('parallel_state'), None)
        tester_statechart.add_state(CompoundState('testing_area', initial='property'), parent='parallel_state')
        tester_statechart.add_state(BasicState('success_state'), parent='testing_area')
        tester_statechart.add_state(BasicState('failure_state'), parent='testing_area')

        property.add_to(tester_statechart,
                        id='property',
                        parent_id='testing_area',
                        status_id='parallel_state',
                        success_id='success_state',
                        failure_id='failure_state')

        tester_interpreter = Interpreter(tester_statechart)

        self.assertFalse('success_state' in tester_interpreter.configuration)
        self.assertFalse('failure_state' in tester_interpreter.configuration)

        tested_interpreter = Interpreter(tested_statechart)
        trace = log_trace(tested_interpreter)

        for event in events:
            tested_interpreter.queue(event)

        tested_interpreter.execute()

        story = teststory_from_trace(trace)
        story.tell(tester_interpreter)

        self.assertEqual(expected_success, 'success_state' in tester_interpreter.configuration)
        self.assertEqual(expected_failure, 'failure_state' in tester_interpreter.configuration)
Exemplo n.º 8
0
    def test_check_guard_failure(self):
        statechart = Statechart('statechart')
        statechart.add_state(OrthogonalState('parallel_state'), parent=None)

        initial_state = CompoundState('initial_state', initial='condition')
        statechart.add_state(initial_state, parent='parallel_state')
        statechart.add_state(BasicState('success'), parent='initial_state')
        statechart.add_state(BasicState('failure'), parent='initial_state')

        CheckGuard('x == 1').add_to(statechart=statechart,
                                    id='condition',
                                    parent_id='initial_state',
                                    status_id='parallel_state',
                                    success_id='success',
                                    failure_id='failure')

        interpreter = Interpreter(statechart)
        interpreter.context['x'] = 42

        interpreter.execute()
        self.assertFalse('success' in interpreter.configuration)
        self.assertTrue('failure' in interpreter.configuration)
Exemplo n.º 9
0
def import_from_amola(xmi_path: str, *, ignore_validation: bool=False) -> Statechart:
    """
    Experiment support to import a statechart from AMOLA.

    :param xmi_path: path to an xmi file
    :param ignore_validation: Set to True to bypass statechart validation.
    :return: a statechart instance
    """
    _, rset = load_metamodel()

    resource = rset.get_resource(URI(xmi_path))
    model = resource.contents[0]

    # Create statechart
    if model.metadata:
        try:
            statechart = Statechart(**json.loads(model.metadata))
        except json.JSONDecodeError as e:
            warnings.warn('Invalid model metadata. Expected a JSON field, got "{}".'.format(model.metadata))
    else:
        statechart = Statechart(os.path.split(xmi_path)[-1])

    # Create states
    state_klasses = {
        'BASIC': BasicState,
        'OR': CompoundState,
        'AND': OrthogonalState,
        'HISTORY': DeepHistoryState,
        'SHALLOW_HISTORY': ShallowHistoryState,
        'END': FinalState,
    }

    # State name
    def name(node):
        return node.name if node.name else 's'+str(id(node))

    nodes = list(model.nodes)
    parents = {}
    while len(nodes) > 0:
        node = nodes.pop()

        state_klass = state_klasses.get(node.type, None)

        if state_klass:
            state = state_klass(name(node))

            try:
                metadata = json.loads(node.metadata) if node.metadata else {}
            except json.JSONDecodeError:
                warnings.warn('Invalid metadata for node {}. Expected a JSON field, got "{}".'.format(node.name, node.metadata))
                metadata = {}

            state.preconditions = metadata.get('preconditions', [])
            state.postconditions = metadata.get('postconditions', [])
            state.invariants = metadata.get('invariants', [])

            parents[name(node)] = name(node.Father) if node.Father else None
            statechart.add_state(state, parents.get(name(node), None))
            nodes.extend(list(node.Children))

            if node.actions:
                for action in node.actions.split('\n'):
                    if action.startswith('entry /'):
                        state.on_entry = action[7:].strip()
                    elif action.startswith('exit /'):
                        state.on_exit = action[6:].strip()
                    elif len(action.strip()) > 0:
                        # Static reactions should be encoded as internal transitions
                        event, guard, action = import_TE(action)
                        if event or guard or action:
                            tr = Transition(
                                source=name(node),
                                target=None,
                                event=event,
                                guard=guard,
                                action=action
                            )
                            statechart.add_transition(tr)

    # Create transitions
    for transition in model.transitions:
        try:
            metadata = json.loads(transition.metadata) if transition.metadata else {}
        except json.JSONDecodeError:
            warnings.warn('Invalid metadata for transition {} -> {}. Expected a JSON field, got "{}".'.format(
                transition.source.name, transition.target.name, transition.metadata))
            metadata = {}

        # Convert START state transition to state.initial
        if transition.source.type == 'START':
            statechart.state_for(name(transition.source.Father)).initial = name(transition.target)
        # Convert HISTORY/SHALLOW_HISTORY state transition to state.memory
        elif transition.source.type in ['HISTORY', 'SHALLOW_HISTORY']:
            statechart.state_for(name(transition.source)).memory = name(transition.target)
        else:
            # Parse Transition Expression
            event, guard, action = import_TE(transition.TE)

            tr = Transition(
                source=name(transition.source),
                target=None if metadata.get('internal', False) else name(transition.target),
                event=event,
                guard=guard,
                action=action,
            )
            tr.preconditions = metadata.get('preconditions', [])
            tr.postconditions = metadata.get('postconditions', [])
            tr.invariants = metadata.get('invariants', [])

            statechart.add_transition(tr)

    # Validate, if required
    if not ignore_validation:
        statechart.validate()

    return statechart
Exemplo n.º 10
0
def export_to_amola(statechart: Statechart, xmi_path: str=None):
    """
    Experimental support to export a statechart to AMOLA using pyecore.

    :param statechart: statechart to export
    :param xmi_path: if provided, will save the XMI
    :return: an instance of the metamodel
    """
    metamodel, _ = load_metamodel()

    # Create statechart
    e_statechart = metamodel.Model()
    e_statechart.metadata = convert_to_json({
        'name': statechart.name,
        'description': statechart.description,
        'preamble': statechart.preamble,
    })

    # Create nodes/states
    e_states = {}
    for name in statechart.states:
        state = statechart.state_for(name)

        # Create node and set metadata
        e_state = metamodel.Node(name=name)
        e_state.metadata = convert_to_json({
            'preconditions': getattr(state, 'preconditions', None),
            'postconditions': getattr(state, 'postconditions', None),
            'invariants': getattr(state, 'invariants', None),
        })

        # Actions
        actions = []
        if getattr(state, 'on_entry', None):
            actions.append('entry / {}'.format(state.on_entry.replace('\n', '\\n')))
        if getattr(state, 'on_exit', None):
            actions.append('exit / {}'.format(state.on_exit.replace('\n', '\\n')))

        # Internal transitions are actions in AMOLA
        for transition in statechart.transitions_from(state.name):
            if transition.internal:
                actions.append(export_TE(transition))

        e_state.actions = '\n'.join(actions)

        # Define its type
        if isinstance(state, CompoundState):
            e_state.type = 'OR'
        elif isinstance(state, OrthogonalState):
            e_state.type = 'AND'
        elif isinstance(state, FinalState):
            e_state.type = 'END'
        elif isinstance(state, ShallowHistoryState):
            e_state.type = 'SHALLOW_HISTORY'
        elif isinstance(state, DeepHistoryState):
            e_state.type = 'HISTORY'
        else:
            e_state.type = 'BASIC'

        e_states[name] = e_state

    # Define children now that all states are created
    for name in statechart.states:
        e_states[name].Children.extend(
            [e_states[child] for child in statechart.children_for(name)]
        )

    # Create transitions
    e_transitions = []
    for transition in statechart.transitions:
        # Internal transitions should be considered as actions (and are defined elsewhere)
        if transition.internal:
            continue

        e_transition = metamodel.Transition(
            source=e_states[transition.source],
            target=e_states[transition.target],
        )

        e_transition.metadata = convert_to_json({
            'preconditions': getattr(transition, 'preconditions', []),
            'postconditions': getattr(transition, 'postconditions', []),
            'invariants': getattr(transition, 'invariants', []),
        })
        e_transition.TE = export_TE(transition)

        e_transitions.append(e_transition)

    # Create initial states for compound states
    # Create transition for history state memory
    for name in statechart.states:
        state = statechart.state_for(name)

        if isinstance(state, CompoundState) and state.initial:
            e_state = metamodel.Node(type='START')
            e_states[state.name].Children.add(e_state)
            e_transition = metamodel.Transition(
                source=e_state,
                target=e_states[state.initial],
            )
            e_states[len(e_states)] = e_state  # len(e_states) ensures a new key
            e_transitions.append(e_transition)
        elif isinstance(state, HistoryStateMixin) and state.memory:
            e_transition = metamodel.Transition(
                source=e_states[state.name],
                target=e_states[state.memory],
            )
            e_transitions.append(e_transition)

    e_statechart.nodes.add(e_states[statechart.root])
    e_statechart.transitions.extend(e_transitions)

    if xmi_path:
        resource = XMIResource(URI(xmi_path))
        resource.append(e_statechart)
        resource.save()

    return e_statechart
Exemplo n.º 11
0
def _export_state_to_dict(statechart: Statechart,
                          state_name: str,
                          ordered=True) -> Mapping[str, Any]:
    data = OrderedDict() if ordered else {}

    state = statechart.state_for(state_name)

    data['name'] = state.name
    if isinstance(state, ShallowHistoryState):
        data['type'] = 'shallow history'
        if state.memory:
            data['memory'] = state.memory
    elif isinstance(state, DeepHistoryState):
        data['type'] = 'deep history'
        if state.memory:
            data['memory'] = state.memory
    elif isinstance(state, FinalState):
        data['type'] = 'final'

    if isinstance(state, ActionStateMixin):
        if state.on_entry:
            data['on entry'] = state.on_entry
        if state.on_exit:
            data['on exit'] = state.on_exit

    if isinstance(state, CompoundState):
        if state.initial:
            data['initial'] = state.initial

    preconditions = getattr(state, 'preconditions', [])
    postconditions = getattr(state, 'postconditions', [])
    invariants = getattr(state, 'invariants', [])
    if preconditions or postconditions or invariants:
        conditions = []
        for condition in preconditions:
            conditions.append({'before': condition})
        for condition in postconditions:
            conditions.append({'after': condition})
        for condition in invariants:
            conditions.append({'always': condition})
        data['contract'] = conditions

    if isinstance(state, TransitionStateMixin):
        # event, guard, target, action
        transitions = statechart.transitions_from(cast(StateMixin, state).name)
        if len(transitions) > 0:
            data['transitions'] = []

            for transition in transitions:
                transition_data = OrderedDict() if ordered else {}
                if transition.event:
                    transition_data['event'] = transition.event
                if transition.guard:
                    transition_data['guard'] = transition.guard
                if transition.target:
                    transition_data['target'] = transition.target
                if transition.action:
                    transition_data['action'] = transition.action
                data['transitions'].append(transition_data)

    if isinstance(state, CompositeStateMixin):
        children = statechart.children_for(cast(StateMixin, state).name)
        children_data = [
            _export_state_to_dict(statechart, child, ordered)
            for child in children
        ]

        if isinstance(state, CompoundState):
            data['states'] = children_data
        elif isinstance(state, OrthogonalState):
            data['parallel states'] = children_data

    return data
Exemplo n.º 12
0
class PropertiesTests(unittest.TestCase):
    def setUp(self):
        self.sequential_statechart = Statechart('tested_statechart')
        initial_state = CompoundState('initial_state', initial='a_state')
        a_state = BasicState('a_state')
        b_state = BasicState('b_state')
        self.sequential_statechart.add_state(initial_state, None)
        self.sequential_statechart.add_state(a_state, 'initial_state')
        self.sequential_statechart.add_state(b_state, 'initial_state')
        self.sequential_statechart.add_transition(Transition(source='a_state', target='b_state', event='event'))

    def generic_test(self,
                     tested_statechart: Statechart,
                     events: list,
                     property: Condition,
                     expected_success: bool,
                     expected_failure: bool):
        from sismic.interpreter import log_trace
        tester_statechart = Statechart('tester_statechart')

        tester_statechart.add_state(OrthogonalState('parallel_state'), None)
        tester_statechart.add_state(CompoundState('testing_area', initial='property'), parent='parallel_state')
        tester_statechart.add_state(BasicState('success_state'), parent='testing_area')
        tester_statechart.add_state(BasicState('failure_state'), parent='testing_area')

        property.add_to(tester_statechart,
                        id='property',
                        parent_id='testing_area',
                        status_id='parallel_state',
                        success_id='success_state',
                        failure_id='failure_state')

        tester_interpreter = Interpreter(tester_statechart)

        self.assertFalse('success_state' in tester_interpreter.configuration)
        self.assertFalse('failure_state' in tester_interpreter.configuration)

        tested_interpreter = Interpreter(tested_statechart)
        trace = log_trace(tested_interpreter)

        for event in events:
            tested_interpreter.queue(event)

        tested_interpreter.execute()

        story = teststory_from_trace(trace)
        story.tell(tester_interpreter)

        self.assertEqual(expected_success, 'success_state' in tester_interpreter.configuration)
        self.assertEqual(expected_failure, 'failure_state' in tester_interpreter.configuration)

    def test_condition_success(self):
        for (event, condition) in [
            ('event', EnterState('b_state')),
            ('event', EnterState('foo', 'b_state', 'bar')),
            ('event', EnterAnyState()),
            ('event', ExitState('a_state')),
            ('event', ExitState('foo', 'a_state', 'bar')),
            ('event', ExitAnyState()),
            ('foo', ConsumeEvent('foo')),
            ('foo', ConsumeEvent('foo', 'bar')),
            ('foo', ConsumeAnyEvent()),
            ('foo', ConsumeAnyEventBut('bar')),
            ('foo', ConsumeAnyEventBut('bar', 'baz')),
            ('foo', ExecutionStart()),
            ('foo', ExecutionStop()),
            ('foo', StartStep()),
            ('foo', ExecutionStop()),
            ('event', TransitionProcess()),
            ('event', TransitionProcess(event='event')),
            ('event', TransitionProcess(source='a_state')),
            ('event', TransitionProcess(target='b_state')),
            ('event', Before(EnterState('a_state'), EnterState('b_state'))),
            ('event', Before(EnterState('a_state'), FalseCondition())),
            ('event', Before(EnterState('a_state'), UndeterminedCondition())),
            ('event', Before(EnterState('b_state'), FalseCondition())),
            ('event', Before(EnterState('b_state'), UndeterminedCondition())),

        ]:
            with self.subTest(condition=condition):
                self.generic_test(self.sequential_statechart, [Event(event)], condition, True, False)

    def test_condition_faillure(self):
        for (event, condition) in [
            ('event', Before(EnterState('b_state'), EnterState('a_state'))),
        ]:
            with self.subTest(condition=condition):
                self.generic_test(self.sequential_statechart, [Event(event)], condition, False, True)

    def test_condition_undetermined(self):
        for (event, condition) in [
            ('event', EnterState('foo')),
            ('event', ExitState('b_state')),
            ('foo', ExitAnyState()),
            ('bar', ConsumeEvent('foo')),
            ('foo', TransitionProcess()),
            ('event', TransitionProcess(event='foo')),
            ('event', TransitionProcess(source='foo')),
            ('event', TransitionProcess(target='foo')),
            ('event', TransitionProcess(event='')),

        ]:
            with self.subTest(condition=condition):
                self.generic_test(self.sequential_statechart, [Event(event)], condition, False, False)

    def test_check_guard_success(self):
        statechart = Statechart('statechart')
        statechart.add_state(OrthogonalState('parallel_state'), parent=None)

        initial_state = CompoundState('initial_state', initial='condition')
        statechart.add_state(initial_state, parent='parallel_state')

        statechart.add_state(BasicState('success'), parent='initial_state')
        statechart.add_state(BasicState('failure'), parent='initial_state')

        CheckGuard('x == 1').add_to(statechart=statechart,
                                    id='condition',
                                    parent_id='initial_state',
                                    status_id='parallel_state',
                                    success_id='success',
                                    failure_id='failure')

        interpreter = Interpreter(statechart)
        interpreter.context['x'] = 1

        interpreter.execute()
        self.assertTrue('success' in interpreter.configuration)
        self.assertFalse('failure' in interpreter.configuration)

    def test_check_guard_failure(self):
        statechart = Statechart('statechart')
        statechart.add_state(OrthogonalState('parallel_state'), parent=None)

        initial_state = CompoundState('initial_state', initial='condition')
        statechart.add_state(initial_state, parent='parallel_state')
        statechart.add_state(BasicState('success'), parent='initial_state')
        statechart.add_state(BasicState('failure'), parent='initial_state')

        CheckGuard('x == 1').add_to(statechart=statechart,
                                    id='condition',
                                    parent_id='initial_state',
                                    status_id='parallel_state',
                                    success_id='success',
                                    failure_id='failure')

        interpreter = Interpreter(statechart)
        interpreter.context['x'] = 42

        interpreter.execute()
        self.assertFalse('success' in interpreter.configuration)
        self.assertTrue('failure' in interpreter.configuration)



    def test_consume_any_event_but_wrong(self):
        self.generic_test(self.sequential_statechart, [], ConsumeAnyEventBut('foo'), False, False)

    def test_transition_process_right_eventless(self):
        self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event'))
        self.sequential_statechart.add_transition(Transition(source='a_state', target='b_state'))

        self.generic_test(self.sequential_statechart,
                          [Event('event')],
                          TransitionProcess(event=''),
                          True,
                          False)

    def test_active_state_right(self):
        self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event'))

        self.generic_test(self.sequential_statechart,
                          [Event('event')],
                          Then(ConsumeEvent('event'), ActiveState(state='a_state')),
                          True,
                          False)

    def test_active_state_wrong(self):
        self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event'))

        self.generic_test(self.sequential_statechart,
                          [Event('event')],
                          Then(ConsumeEvent('event'), ActiveState(state='b_state')),
                          False,
                          True)

    def test_inactive_state_right(self):
        self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event'))

        self.generic_test(self.sequential_statechart,
                          [Event('event')],
                          Then(ConsumeEvent('event'), InactiveState(state='b_state')),
                          True,
                          False)

    def test_inactive_state_wrong(self):
        self.sequential_statechart.remove_transition(Transition(source='a_state', target='b_state', event='event'))

        self.generic_test(self.sequential_statechart,
                          [Event('event')],
                          Then(ConsumeEvent('event'), InactiveState(state='a_state')),
                          False,
                          True)
Exemplo n.º 13
0
 def test_name_is_none(self):
     sc = Statechart('test')
     state = BasicState(name=None)
     with pytest.raises(StatechartError) as e:
         sc.add_state(state, None)
     assert 'must have a name' in str(e.value)
Exemplo n.º 14
0
def _export_state_to_dict(statechart: Statechart, state_name: str, ordered=True) -> dict:
    data = OrderedDict() if ordered else {}

    state = statechart.state_for(state_name)

    data['name'] = state.name
    if isinstance(state, ShallowHistoryState):
        data['type'] = 'shallow history'
        if state.memory:
            data['memory'] = state.memory
    elif isinstance(state, DeepHistoryState):
        data['type'] = 'deep history'
        if state.memory:
            data['memory'] = state.memory
    elif isinstance(state, FinalState):
        data['type'] = 'final'

    if isinstance(state, ActionStateMixin):
        if state.on_entry:
            data['on entry'] = state.on_entry
        if state.on_exit:
            data['on exit'] = state.on_exit

    if isinstance(state, CompoundState):
        if state.initial:
            data['initial'] = state.initial

    preconditions = getattr(state, 'preconditions', [])
    postconditions = getattr(state, 'postconditions', [])
    invariants = getattr(state, 'invariants', [])
    if preconditions or postconditions or invariants:
        conditions = []
        for condition in preconditions:
            conditions.append({'before': condition})
        for condition in postconditions:
            conditions.append({'after': condition})
        for condition in invariants:
            conditions.append({'always': condition})
        data['contract'] = conditions

    if isinstance(state, TransitionStateMixin):
        # event, guard, target, action
        transitions = statechart.transitions_from(state.name)
        if len(transitions) > 0:
            data['transitions'] = []

            for transition in transitions:
                transition_data = OrderedDict() if ordered else {}
                if transition.event:
                    transition_data['event'] = transition.event
                if transition.guard:
                    transition_data['guard'] = transition.guard
                if transition.target:
                    transition_data['target'] = transition.target
                if transition.action:
                    transition_data['action'] = transition.action
                data['transitions'].append(transition_data)

    if isinstance(state, CompositeStateMixin):
        children = statechart.children_for(state.name)
        children_data = [_export_state_to_dict(statechart, child, ordered) for child in children]

        if isinstance(state, CompoundState):
            data['states'] = children_data
        elif isinstance(state, OrthogonalState):
            data['parallel states'] = children_data

    return data