예제 #1
0
 def __init__(self, redis_client: StrictRedis, query_id: str):
     self.redis_client = redis_client
     self.query_id = query_id
     self.state_machine = Finist(redis_client, f"{query_id}-state",
                                 QueryState.KNOWN)
     self.state_machine.on(QueryEvent.QUEUE, QueryState.KNOWN,
                           QueryState.QUEUED)
     self.state_machine.on(QueryEvent.EXECUTE, QueryState.QUEUED,
                           QueryState.EXECUTING)
     self.state_machine.on(QueryEvent.ERROR, QueryState.EXECUTING,
                           QueryState.ERRORED)
     self.state_machine.on(QueryEvent.FINISH, QueryState.EXECUTING,
                           QueryState.COMPLETED)
     self.state_machine.on(QueryEvent.CANCEL, QueryState.QUEUED,
                           QueryState.CANCELLED)
     self.state_machine.on(QueryEvent.CANCEL, QueryState.EXECUTING,
                           QueryState.CANCELLED)
     self.state_machine.on(QueryEvent.RESET, QueryState.CANCELLED,
                           QueryState.RESETTING)
     self.state_machine.on(QueryEvent.RESET, QueryState.ERRORED,
                           QueryState.RESETTING)
     self.state_machine.on(QueryEvent.RESET, QueryState.COMPLETED,
                           QueryState.RESETTING)
     self.state_machine.on(QueryEvent.RESET, QueryState.COMPLETED,
                           QueryState.RESETTING)
     self.state_machine.on(QueryEvent.FINISH_RESET, QueryState.RESETTING,
                           QueryState.KNOWN)
예제 #2
0
def get_fsm(chat_id):
    fsm = Finist(redis_conn, chat_id, "pending")
    if fsm.state() == "pending":
        # init transitions 
        fsm.on("approve", "pending", "approved")
        fsm.on("auction", "approved", "auction_in")
        fsm.on("reset", "approved", "pending")
        fsm.on("reset", "pending", "pending")
        fsm.on("reset", "auction", "pending")
    return fsm
예제 #3
0
파일: test.py 프로젝트: kmatt/finist
 def setup_finist(self):
     redis_conn.flushdb()
     self.fsm = Finist(redis_conn, "myfsm", "pending")
     self.fsm.on("approve", "pending", "approved")
     self.fsm.on("cancel", "pending", "cancelled")
     self.fsm.on("cancel", "approved", "cancelled")
     self.fsm.on("reset", "cancelled", "pending")
예제 #4
0
 def __init__(self, redis_client: StrictRedis, query_id: str):
     self.redis_client = redis_client
     self.query_id = query_id
     must_populate = redis_client.get(f"finist:{query_id}-state") is None
     self.state_machine = Finist(redis_client, f"{query_id}-state", QueryState.KNOWN)
     if must_populate:  # Need to create the state machine for this query
         self.state_machine.on(QueryEvent.QUEUE, QueryState.KNOWN, QueryState.QUEUED)
         self.state_machine.on(
             QueryEvent.EXECUTE, QueryState.QUEUED, QueryState.EXECUTING
         )
         self.state_machine.on(
             QueryEvent.ERROR, QueryState.EXECUTING, QueryState.ERRORED
         )
         self.state_machine.on(
             QueryEvent.FINISH, QueryState.EXECUTING, QueryState.COMPLETED
         )
         self.state_machine.on(
             QueryEvent.CANCEL, QueryState.QUEUED, QueryState.CANCELLED
         )
         self.state_machine.on(
             QueryEvent.CANCEL, QueryState.EXECUTING, QueryState.CANCELLED
         )
         self.state_machine.on(
             QueryEvent.RESET, QueryState.CANCELLED, QueryState.RESETTING
         )
         self.state_machine.on(
             QueryEvent.RESET, QueryState.ERRORED, QueryState.RESETTING
         )
         self.state_machine.on(
             QueryEvent.RESET, QueryState.COMPLETED, QueryState.RESETTING
         )
         self.state_machine.on(
             QueryEvent.RESET, QueryState.COMPLETED, QueryState.RESETTING
         )
         self.state_machine.on(
             QueryEvent.FINISH_RESET, QueryState.RESETTING, QueryState.KNOWN
         )
예제 #5
0
class QueryStateMachine:
    """
    Implements a state machine for a query's lifecycle, backed by redis.

    Each query, once instantiated, is in one of a number of possible states.
    - known, indicating that the query has been created, but not yet run, or queued for storage.
    - queued, which indicates that a query is going to be executed in future (i.e. `store` has been called on it)
    - executing, for queries which are currently running in FlowDB
    - completed, indicating that the query has finished running successfully
    - errored, when a query has been run but failed to succeed
    - cancelled, when execution was terminated by the user
    - resetting, when a previously run query is being purged from cache

    When the query is in a queued, executing, or resetting state, methods which need
    to use the results of the query should wait. The `wait_until_complete` method
    will block while the query is in any of these states.

    The initial state for the query is 'known'.

    Parameters
    ----------
    redis_client : StrictRedis
        Client for redis
    query_id : str
        md5 query identifier

    Notes
    -----
    Creating a new instance of a state machine for a query will not alter the state, as
    the state is persisted in redis.

    """
    def __init__(self, redis_client: StrictRedis, query_id: str):
        self.redis_client = redis_client
        self.query_id = query_id
        self.state_machine = Finist(redis_client, f"{query_id}-state",
                                    QueryState.KNOWN)
        self.state_machine.on(QueryEvent.QUEUE, QueryState.KNOWN,
                              QueryState.QUEUED)
        self.state_machine.on(QueryEvent.EXECUTE, QueryState.QUEUED,
                              QueryState.EXECUTING)
        self.state_machine.on(QueryEvent.ERROR, QueryState.EXECUTING,
                              QueryState.ERRORED)
        self.state_machine.on(QueryEvent.FINISH, QueryState.EXECUTING,
                              QueryState.COMPLETED)
        self.state_machine.on(QueryEvent.CANCEL, QueryState.QUEUED,
                              QueryState.CANCELLED)
        self.state_machine.on(QueryEvent.CANCEL, QueryState.EXECUTING,
                              QueryState.CANCELLED)
        self.state_machine.on(QueryEvent.RESET, QueryState.CANCELLED,
                              QueryState.RESETTING)
        self.state_machine.on(QueryEvent.RESET, QueryState.ERRORED,
                              QueryState.RESETTING)
        self.state_machine.on(QueryEvent.RESET, QueryState.COMPLETED,
                              QueryState.RESETTING)
        self.state_machine.on(QueryEvent.RESET, QueryState.COMPLETED,
                              QueryState.RESETTING)
        self.state_machine.on(QueryEvent.FINISH_RESET, QueryState.RESETTING,
                              QueryState.KNOWN)

    @property
    def current_query_state(self) -> QueryState:
        """

        Returns
        -------
        QueryState
            Current state of the query this state machine refers to
        """
        return QueryState(self.state_machine.state().decode())

    @property
    def is_executing(self) -> bool:
        """
        Returns
        -------
        bool
            True if the query is currently running

        """
        return self.current_query_state == QueryState.EXECUTING

    @property
    def is_queued(self) -> bool:
        """
        Returns
        -------
        bool
            True if the the query's store method has been called and it has not begin running

        """
        return self.current_query_state == QueryState.QUEUED

    @property
    def is_completed(self) -> bool:
        """
        Returns
        -------
        bool
            True if the query ran successfully and is in cache

        """
        return self.current_query_state == QueryState.COMPLETED

    @property
    def is_finished_executing(self) -> bool:
        """
        True if the query state is `completed` or `errored`. I.e., it was previously
        executing and is now finished either successfully or with a failure (but it
        wasn't cancelled manually).
        """
        return (self.current_query_state
                == QueryState.COMPLETED) or (self.current_query_state
                                             == QueryState.ERRORED)

    @property
    def is_errored(self) -> bool:
        """
        Returns
        -------
        bool
            True if the query failed to run with an error

        """
        return self.current_query_state == QueryState.ERRORED

    @property
    def is_resetting(self) -> bool:
        """
        Returns
        -------
        bool
            True if the query is currently being removed from cache, or recovered from cancellation or error

        """
        return self.current_query_state == QueryState.RESETTING

    @property
    def is_known(self) -> bool:
        """
        Returns
        -------
        bool
            True if the query has not been set running and may be queued to do so

        """
        return self.current_query_state == QueryState.KNOWN

    @property
    def is_cancelled(self) -> bool:
        """
        Returns
        -------
        bool
            True if the query was previously queued or running but was cancelled

        """
        return self.current_query_state == QueryState.CANCELLED

    def trigger_event(self, event: QueryEvent) -> Tuple[QueryState, bool]:
        """
        Attempts to trigger a state transition - will only transition if a
        transition is possible given the current state of the query.

        Parameters
        ----------
        event: QueryEvent
            Event to trigger

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether this method
            call caused a transition to that state

        """
        state, trigger_success = self.state_machine.trigger(event)
        return QueryState(state.decode()), trigger_success

    def cancel(self):
        """
        Attempt to mark the query as cancelled.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
            triggered the query to be cancelled with this call

        """
        return self.trigger_event(QueryEvent.CANCEL)

    def enqueue(self):
        """
        Attempt to mark the query as queued.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
            triggered the query to be queued with this call

        """
        return self.trigger_event(QueryEvent.QUEUE)

    def raise_error(self):
        """
        Attempt to mark the query as having errored while running.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
            marked the query as erroring with this call

        """
        return self.trigger_event(QueryEvent.ERROR)

    def execute(self):
        """
        Attempt to mark the query as in the process of executing.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
            marked the query as executing with this call

        """
        return self.trigger_event(QueryEvent.EXECUTE)

    def finish(self):
        """
        Attempt to mark the query as completed.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
            marked the query as finished with this call

        """
        return self.trigger_event(QueryEvent.FINISH)

    def reset(self):
        """
        Attempt to mark the query as in the process of resetting.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
            triggered the query to be reset with this call

        """
        return self.trigger_event(QueryEvent.RESET)

    def finish_resetting(self):
        """
        Attempt to mark the query as having finished resetting.

        Returns
        -------
        tuple of QueryState, bool
            Returns a tuple of the new query state, and a bool indicating whether the caller
           marked the reset as complete with this call.

        """
        return self.trigger_event(QueryEvent.FINISH_RESET)

    def wait_until_complete(self, sleep_duration=1):
        """
        Blocks until the query is in a state where its result is determinate
        (i.e., one of "know", "errored", "completed", "cancelled").

        """
        if self.is_executing or self.is_queued or self.is_resetting:
            while not (self.is_finished_executing or self.is_cancelled
                       or self.is_known):
                _sleep(sleep_duration)
예제 #6
0
파일: test.py 프로젝트: kmatt/finist
class FinistTest(unittest.TestCase):
    """docstring for FinistTest"""
    def __init__(self, *args, **kwargs):
        super(FinistTest, self).__init__(*args, **kwargs)
        self.setup_finist()

    def setup_finist(self):
        redis_conn.flushdb()
        self.fsm = Finist(redis_conn, "myfsm", "pending")
        self.fsm.on("approve", "pending", "approved")
        self.fsm.on("cancel", "pending", "cancelled")
        self.fsm.on("cancel", "approved", "cancelled")
        self.fsm.on("reset", "cancelled", "pending")

    def send_event(self, event):
        # Change event for the FSM
        self.fsm.trigger(event)

    def setup_finist_with_client(self):
        self.fsm2 = Finist(redis_conn, "myfsm", "pending")

    def test_run(self):
        # Verify initial state
        self.assertEqual("pending", self.fsm.state())
        # Send an event
        self.send_event("approve")
        # Verify transition to "approved"
        self.assertEqual("approved", self.fsm.state())
        # Send an event
        self.send_event("cancel")
        # Verify transition to "cancelled"
        self.assertEqual("cancelled", self.fsm.state())
        # Send an event
        self.send_event("approve")
        # Verify state remains as "cancelled"
        self.assertEqual("cancelled", self.fsm.state())
        # Create a different fsm with client
        self.setup_finist_with_client()
        # Verify state remains as "cancelled"
        self.assertEqual("cancelled", self.fsm2.state())
        state, changed = self.fsm.trigger("reset")
        # A successful event returns true
        self.assertEqual(True, changed)
        self.assertEqual("pending", state)
        state, changed = self.fsm.trigger("reset")
        # An unsuccessful event returns false
        self.assertEqual(False, changed)
        self.assertEqual("pending", state)
        # Delete an event
        self.fsm.rm("approve")
        state, changed = self.fsm.trigger("approve")
        # Non existent events return false
        self.assertEqual(False, changed)
        self.assertEqual("pending", state)
예제 #7
0
파일: test.py 프로젝트: kmatt/finist
 def setup_finist_with_client(self):
     self.fsm2 = Finist(redis_conn, "myfsm", "pending")