def __init__(self, script_name):
        """Initializes the ScriptRunner for the given script_name"""
        # DeltaGenerator deltas will be enqueued into self.report_queue.
        self.report_queue = ReportQueue()

        def enqueue_fn(msg):
            self.report_queue.enqueue(msg)
            self.maybe_handle_execution_control_request()

        self.script_request_queue = ScriptRequestQueue()
        script_path = os.path.join(os.path.dirname(__file__), "test_data",
                                   script_name)

        super(TestScriptRunner, self).__init__(
            session_id="test session id",
            report=Report(script_path, "test command line"),
            enqueue_forward_msg=enqueue_fn,
            client_state=ClientState(),
            request_queue=self.script_request_queue,
        )

        # Accumulates uncaught exceptions thrown by our run thread.
        self.script_thread_exceptions = []

        # Accumulates all ScriptRunnerEvents emitted by us.
        self.events = []

        def record_event(event, **kwargs):
            self.events.append(event)

        self.on_event.connect(record_event, weak=False)
Exemple #2
0
    def __init__(self, ioloop, script_path, command_line,
                 uploaded_file_manager):
        """Initialize the ReportSession.

        Parameters
        ----------
        ioloop : tornado.ioloop.IOLoop
            The Tornado IOLoop that we're running within.

        script_path : str
            Path of the Python file from which this report is generated.

        command_line : str
            Command line as input by the user.

        uploaded_file_manager : UploadedFileManager
            The server's UploadedFileManager.

        """
        # Each ReportSession has a unique string ID.
        self.id = str(uuid.uuid4())

        self._ioloop = ioloop
        self._report = Report(script_path, command_line)
        self._uploaded_file_mgr = uploaded_file_manager

        self._state = ReportSessionState.REPORT_NOT_RUNNING

        # Need to remember the client state here because when a script reruns
        # due to the source code changing we need to pass in the previous client state.
        self._client_state = ClientState()

        self._local_sources_watcher = LocalSourcesWatcher(
            self._report, self._on_source_file_changed)
        self._stop_config_listener = config.on_config_parsed(
            self._on_source_file_changed, force_connect=True)
        self._storage = None
        self._maybe_reuse_previous_run = False
        self._run_on_save = config.get_option("server.runOnSave")

        # The ScriptRequestQueue is the means by which we communicate
        # with the active ScriptRunner.
        self._script_request_queue = ScriptRequestQueue()

        self._scriptrunner = None

        # This needs to be lazily imported to avoid a dependency cycle.
        from streamlit.state.session_state import SessionState

        self._session_state = SessionState()

        LOGGER.debug("ReportSession initialized (id=%s)", self.id)
    def test_dequeue(self):
        """Test that we can enqueue and dequeue on different threads"""

        queue = ScriptRequestQueue()

        # This should return immediately
        self.assertEqual((None, None), queue.dequeue())

        lock = Lock()
        dequeued_evt = [None]

        def get_event():
            with lock:
                return dequeued_evt[0]

        def set_event(value):
            with lock:
                dequeued_evt[0] = value

        def do_dequeue():
            event = None
            while event is None:
                event, _ = queue.dequeue()
            set_event(event)

        thread = Thread(target=do_dequeue, name="test_dequeue")
        thread.start()

        self.assertIsNone(get_event())

        queue.enqueue(ScriptRequest.STOP)
        time.sleep(0.1)

        self.assertEqual(ScriptRequest.STOP, get_event())

        thread.join(timeout=0.25)
        self.assertFalse(thread.is_alive())
Exemple #4
0
    def __init__(self, script_name):
        """Initializes the ScriptRunner for the given script_name"""
        # DeltaGenerator deltas will be enqueued into self.forward_msg_queue.
        self.forward_msg_queue = ForwardMsgQueue()

        self.script_request_queue = ScriptRequestQueue()
        main_script_path = os.path.join(os.path.dirname(__file__), "test_data",
                                        script_name)

        super(TestScriptRunner, self).__init__(
            session_id="test session id",
            session_data=SessionData(main_script_path, "test command line"),
            enqueue_forward_msg=self.forward_msg_queue.enqueue,
            client_state=ClientState(),
            session_state=SessionState(),
            request_queue=self.script_request_queue,
            uploaded_file_mgr=UploadedFileManager(),
        )

        # Accumulates uncaught exceptions thrown by our run thread.
        self.script_thread_exceptions = []

        # Accumulates all ScriptRunnerEvents emitted by us.
        self.events: List[ScriptRunnerEvent] = []
        self.event_data: List[Any] = []

        def record_event(sender: Optional[ScriptRunner],
                         event: ScriptRunnerEvent, **kwargs):
            # Assert that we're not getting unexpected `sender` params
            # from ScriptRunner.on_event
            assert (sender is None
                    or sender == self), "Unexpected ScriptRunnerEvent sender!"
            self.events.append(event)
            self.event_data.append(kwargs)

        self.on_event.connect(record_event, weak=False)
    def test_rerun_data_coalescing(self):
        """Test that multiple RERUN requests get coalesced with
        expected values.

        (This is similar to widgets_test.test_coalesce_widget_states -
        it's testing the same thing, but through the ScriptEventQueue
        interface.)
        """
        queue = ScriptRequestQueue()
        session_state = SessionState()

        states = WidgetStates()
        _create_widget("trigger", states).trigger_value = True
        _create_widget("int", states).int_value = 123

        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=states))

        states = WidgetStates()
        _create_widget("trigger", states).trigger_value = False
        _create_widget("int", states).int_value = 456

        session_state.set_metadata(
            WidgetMetadata("trigger", lambda x, s: x, None, "trigger_value"))
        session_state.set_metadata(
            WidgetMetadata("int", lambda x, s: x, lambda x: x, "int_value"))

        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=states))

        event, data = queue.dequeue()
        self.assertEqual(event, ScriptRequest.RERUN)

        session_state.set_widgets_from_proto(data.widget_states)

        # Coalesced triggers should be True if either the old or
        # new value was True
        self.assertEqual(True, session_state.get("trigger"))

        # Other widgets should have their newest value
        self.assertEqual(456, session_state.get("int"))

        # We should have no more events
        self.assertEqual((None, None), queue.dequeue(),
                         "Expected empty event queue")

        # Test that we can coalesce if previous widget state is None
        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=None))
        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=None))

        states = WidgetStates()
        _create_widget("int", states).int_value = 789

        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=states))

        event, data = queue.dequeue()
        session_state.set_widgets_from_proto(data.widget_states)

        self.assertEqual(event, ScriptRequest.RERUN)
        self.assertEqual(789, session_state.get("int"))

        # We should have no more events
        self.assertEqual((None, None), queue.dequeue(),
                         "Expected empty event queue")

        # Test that we can coalesce if our *new* widget state is None
        states = WidgetStates()
        _create_widget("int", states).int_value = 101112

        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=states))

        queue.enqueue(ScriptRequest.RERUN, RerunData(widget_states=None))

        event, data = queue.dequeue()
        session_state.set_widgets_from_proto(data.widget_states)

        self.assertEqual(event, ScriptRequest.RERUN)
        self.assertEqual(101112, session_state.get("int"))

        # We should have no more events
        self.assertEqual((None, None), queue.dequeue(),
                         "Expected empty event queue")
Exemple #6
0
    def __init__(
        self,
        ioloop: tornado.ioloop.IOLoop,
        session_data: SessionData,
        uploaded_file_manager: UploadedFileManager,
        message_enqueued_callback: Optional[Callable[[], None]],
        local_sources_watcher: LocalSourcesWatcher,
    ):
        """Initialize the AppSession.

        Parameters
        ----------
        ioloop : tornado.ioloop.IOLoop
            The Tornado IOLoop that we're running within.

        session_data : SessionData
            Object storing parameters related to running a script

        uploaded_file_manager : UploadedFileManager
            The server's UploadedFileManager.

        message_enqueued_callback : Callable[[], None]
            After enqueuing a message, this callable notification will be invoked.

        local_sources_watcher: LocalSourcesWatcher
            The file watcher that lets the session know local files have changed.

        """
        # Each AppSession has a unique string ID.
        self.id = str(uuid.uuid4())

        self._ioloop = ioloop
        self._session_data = session_data
        self._uploaded_file_mgr = uploaded_file_manager
        self._message_enqueued_callback = message_enqueued_callback

        self._state = AppSessionState.APP_NOT_RUNNING

        # Need to remember the client state here because when a script reruns
        # due to the source code changing we need to pass in the previous client state.
        self._client_state = ClientState()

        self._local_sources_watcher = local_sources_watcher
        self._local_sources_watcher.register_file_change_callback(
            self._on_source_file_changed)
        self._stop_config_listener = config.on_config_parsed(
            self._on_source_file_changed, force_connect=True)

        # The script should rerun when the `secrets.toml` file has been changed.
        secrets._file_change_listener.connect(self._on_secrets_file_changed)

        self._run_on_save = config.get_option("server.runOnSave")

        # The ScriptRequestQueue is the means by which we communicate
        # with the active ScriptRunner.
        self._script_request_queue = ScriptRequestQueue()

        self._scriptrunner: Optional[ScriptRunner] = None

        # This needs to be lazily imported to avoid a dependency cycle.
        from streamlit.state.session_state import SessionState

        self._session_state = SessionState()

        LOGGER.debug("AppSession initialized (id=%s)", self.id)