<commit_before>import os
from contextlib import suppress

from transitions.extensions.states import Tags as MachineState
from transitions import Machine
from panoptes.utils import error
from panoptes.utils.utils import listify
from panoptes.utils.library import load_module
from panoptes.utils.serializers import from_yaml


class PanStateMachine(Machine):
    """ A finite state machine for PANOPTES.

    The state machine guides the overall action of the unit.
    """

    def __init__(self, state_machine_table, **kwargs):

        if isinstance(state_machine_table, str):
            self.logger.info(f"Loading state table: {state_machine_table}")
            state_machine_table = PanStateMachine.load_state_table(
                state_table_name=state_machine_table)

        assert 'states' in state_machine_table, self.logger.warning('states keyword required.')
        assert 'transitions' in state_machine_table, self.logger.warning(
            'transitions keyword required.')

        self._state_table_name = state_machine_table.get('name', 'default')
        self._states_location = state_machine_table.get('location', 'panoptes.pocs.state.states')

        # Setup Transitions.
        _transitions = [self._load_transition(transition)
                        for transition in state_machine_table['transitions']]

        # States can require the horizon to be at a certain level.
        self._horizon_lookup = dict()

        # Setup States.
        states = [
            self._load_state(state, state_info=state_info)
            for state, state_info
            in state_machine_table.get('states', dict()).items()
        ]

        self.logger.debug(f'Horizon limits: {self._horizon_lookup!r}')

        # Create state machine.
        super(PanStateMachine, self).__init__(
            states=states,
            transitions=_transitions,
            initial=state_machine_table.get('initial'),
            send_event=True,
            before_state_change='before_state',
            after_state_change='after_state',
            auto_transitions=False,
            name="POCS State Machine",
            **kwargs
        )

        self._state_machine_table = state_machine_table
        self.next_state = None

        self.logger.debug("State machine created")

    ################################################################################################
    # Properties
    ################################################################################################

    @property
    def next_state(self):
        return self._next_state

    @next_state.setter
    def next_state(self, value):
        """ Set the tracking rate """
        self._next_state = value

    ################################################################################################
    # Methods
    ################################################################################################

    def run(self, exit_when_done=False, run_once=False, initial_next_state='ready'):
        """Runs the state machine loop.

        This runs the state machine in a loop. Setting the machine property
        `is_running` to False will stop the loop.

        Args:
            exit_when_done (bool, optional): If True, the loop will exit when `do_states`
                has become False, otherwise will wait (default)
            run_once (bool, optional): If the machine loop should only run one time, defaults
                to False to loop continuously.
            initial_next_state (str, optional): The first state the machine should move to from
                the `sleeping` state, default `ready`.
        """
        if not self.is_initialized:
            self.logger.warning("POCS not initialized")
            return False

        run_once = run_once or self.run_once

        self.next_state = initial_next_state

        _transition_iteration = 0
        max_transition_attempts = self.get_config('max_transition_attempts', default=5)
        check_delay = self.get_config('wait_delay', default=120)

        self.logger.debug(f'Starting run loop')
        while self.keep_running:

            # BEFORE TRANSITION TO STATE
            self.logger.info(f'Run loop: {self.state!r} -> {self.next_state!r}')

            # Before moving to next state, wait for required horizon if necessary.
            while True:
                # If not safe, go to park
                is_safe = self.is_safe(park_if_not_safe=True, ignore=['is_dark'])

                # The state may have changed since the start of the while loop
                # e.g. if self.park is called from self.is_safe
                # So we need to check if the new state is always safe
                if self.get_state(self.next_state).is_always_safe:
                    break

                # Check the horizon here because next state may have changed in loop.
                required_horizon = self._horizon_lookup.get(self.next_state, 'observe')
                if self.is_dark(horizon=required_horizon):
                    break

                # Sleep before checking again.
                self.logger.info(f"Waiting for {required_horizon=!r} for {self.next_state=!r}")
                self.wait(delay=check_delay)

            # TRANSITION TO STATE
            self.logger.info(f'Going to {self.next_state!r}')
            try:
                # The state's `on_enter` logic will be performed here.
                state_changed = self.goto_next_state()
            except Exception as e:
                self.logger.critical(f"Problem going from {self.state!r} to {self.next_state!r}, "
                                     f"exiting loop [{e!r}]")
                # TODO should we automatically park here?<commit_msg>Check for darkness when checking safety in the morning.<commit_after>import os
from contextlib import suppress

from transitions.extensions.states import Tags as MachineState
from transitions import Machine
from panoptes.utils import error
from panoptes.utils.utils import listify
from panoptes.utils.library import load_module
from panoptes.utils.serializers import from_yaml


class PanStateMachine(Machine):
    """ A finite state machine for PANOPTES.

    The state machine guides the overall action of the unit.
    """

    def __init__(self, state_machine_table, **kwargs):

        if isinstance(state_machine_table, str):
            self.logger.info(f"Loading state table: {state_machine_table}")
            state_machine_table = PanStateMachine.load_state_table(
                state_table_name=state_machine_table)

        assert 'states' in state_machine_table, self.logger.warning('states keyword required.')
        assert 'transitions' in state_machine_table, self.logger.warning(
            'transitions keyword required.')

        self._state_table_name = state_machine_table.get('name', 'default')
        self._states_location = state_machine_table.get('location', 'panoptes.pocs.state.states')

        # Setup Transitions.
        _transitions = [self._load_transition(transition)
                        for transition in state_machine_table['transitions']]

        # States can require the horizon to be at a certain level.
        self._horizon_lookup = dict()

        # Setup States.
        states = [
            self._load_state(state, state_info=state_info)
            for state, state_info
            in state_machine_table.get('states', dict()).items()
        ]

        self.logger.debug(f'Horizon limits: {self._horizon_lookup!r}')

        # Create state machine.
        super(PanStateMachine, self).__init__(
            states=states,
            transitions=_transitions,
            initial=state_machine_table.get('initial'),
            send_event=True,
            before_state_change='before_state',
            after_state_change='after_state',
            auto_transitions=False,
            name="POCS State Machine",
            **kwargs
        )

        self._state_machine_table = state_machine_table
        self.next_state = None

        self.logger.debug("State machine created")

    ################################################################################################
    # Properties
    ################################################################################################

    @property
    def next_state(self):
        return self._next_state

    @next_state.setter
    def next_state(self, value):
        """ Set the tracking rate """
        self._next_state = value

    ################################################################################################
    # Methods
    ################################################################################################

    def run(self, exit_when_done=False, run_once=False, initial_next_state='ready'):
        """Runs the state machine loop.

        This runs the state machine in a loop. Setting the machine property
        `is_running` to False will stop the loop.

        Args:
            exit_when_done (bool, optional): If True, the loop will exit when `do_states`
                has become False, otherwise will wait (default)
            run_once (bool, optional): If the machine loop should only run one time, defaults
                to False to loop continuously.
            initial_next_state (str, optional): The first state the machine should move to from
                the `sleeping` state, default `ready`.
        """
        if not self.is_initialized:
            self.logger.warning("POCS not initialized")
            return False

        run_once = run_once or self.run_once

        self.next_state = initial_next_state

        _transition_iteration = 0
        max_transition_attempts = self.get_config('max_transition_attempts', default=5)
        check_delay = self.get_config('wait_delay', default=120)

        self.logger.debug(f'Starting run loop')
        while self.keep_running:

            # BEFORE TRANSITION TO STATE
            self.logger.info(f'Run loop: {self.state!r} -> {self.next_state!r}')

            # Before moving to next state, wait for required horizon if necessary.
            while True:
                # If not safe, go to park
                ignore_safety_items = []
                # We ignore the dark safety reading if mount has not yet moved.
                if self.state not in ['observing', 'analyzing']:
                    ignore_safety_items.append('is_dark')
                is_safe = self.is_safe(park_if_not_safe=True, ignore=ignore_safety_items)

                # The state may have changed since the start of the while loop
                # e.g. if self.park is called from self.is_safe
                # So we need to check if the new state is always safe
                if self.get_state(self.next_state).is_always_safe:
                    break

                # Check the horizon here because next state may have changed in loop.
                required_horizon = self._horizon_lookup.get(self.next_state, 'observe')
                if self.is_dark(horizon=required_horizon):
                    break

                # Sleep before checking again.
                self.logger.info(f"Waiting for {required_horizon=!r} for {self.next_state=!r}")
                self.wait(delay=check_delay)

            # TRANSITION TO STATE
            self.logger.info(f'Going to {self.next_state!r}')
            try:
                # The state's `on_enter` logic will be performed here.
                state_changed = self.goto_next_state()
            except Exception as e:
                self.logger.critical(f"Problem going from {self.state!r} to {self.next_state!r}, "
                                     f"exiting loop [{e!r}]")
                # TODO should we automatically park here?
{'fraction_non_alphanumeric': 0.13883709103237837, 'fraction_numerical': 0.0008476012883539584, 'mean_word_length': 2.4259581881533103, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2472569', 'n_tokens_mistral': 3074, 'n_tokens_neox': 2890, 'n_words': 823}