def _process_request_queue(self): """Process the ScriptRequestQueue and then exits. This is run in a separate thread. """ LOGGER.debug("Beginning script thread") while not self._shutdown_requested and self._request_queue.has_request: request, data = self._request_queue.dequeue() if request == ScriptRequest.STOP: LOGGER.debug("Ignoring STOP request while not running") elif request == ScriptRequest.SHUTDOWN: LOGGER.debug("Shutting down") self._shutdown_requested = True elif request == ScriptRequest.RERUN: self._run_script(data) else: raise RuntimeError("Unrecognized ScriptRequest: %s" % request) # Send a SHUTDOWN event before exiting. This includes the widget values # as they existed after our last successful script run, which the # ReportSession will pass on to the next ScriptRunner that gets # created. client_state = ClientState() client_state.query_string = self._client_state.query_string self._widgets.marshall(client_state) self.on_event.send(ScriptRunnerEvent.SHUTDOWN, client_state=client_state)
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)
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 _run_script_thread(self) -> None: """The entry point for the script thread. Processes the ScriptRequestQueue, which will at least contain the RERUN request that will trigger the first script-run. When the ScriptRequestQueue is empty, or when a SHUTDOWN request is dequeued, this function will exit and its thread will terminate. """ assert self._is_in_script_thread() LOGGER.debug("Beginning script thread") # Create and attach the thread's ScriptRunContext ctx = ScriptRunContext( session_id=self._session_id, enqueue=self._enqueue, query_string=self._client_state.query_string, session_state=self._session_state, uploaded_file_mgr=self._uploaded_file_mgr, ) add_script_run_ctx(threading.current_thread(), ctx) while not self._shutdown_requested and self._request_queue.has_request: request, data = self._request_queue.dequeue() if request == ScriptRequest.STOP: LOGGER.debug("Ignoring STOP request while not running") elif request == ScriptRequest.SHUTDOWN: LOGGER.debug("Shutting down") self._shutdown_requested = True elif request == ScriptRequest.RERUN: self._run_script(data) else: raise RuntimeError("Unrecognized ScriptRequest: %s" % request) # Send a SHUTDOWN event before exiting. This includes the widget values # as they existed after our last successful script run, which the # AppSession will pass on to the next ScriptRunner that gets # created. client_state = ClientState() client_state.query_string = ctx.query_string widget_states = self._session_state.as_widget_states() client_state.widget_states.widgets.extend(widget_states) self.on_event.send(self, event=ScriptRunnerEvent.SHUTDOWN, client_state=client_state)
def _run_script_thread(self) -> None: """The entry point for the script thread. Processes the ScriptRequestQueue, which will at least contain the RERUN request that will trigger the first script-run. When the ScriptRequestQueue is empty, or when a SHUTDOWN request is dequeued, this function will exit and its thread will terminate. """ assert self._is_in_script_thread() LOGGER.debug("Beginning script thread") # Create and attach the thread's ScriptRunContext ctx = ScriptRunContext( session_id=self._session_id, enqueue=self._enqueue_forward_msg, query_string=self._client_state.query_string, session_state=self._session_state, uploaded_file_mgr=self._uploaded_file_mgr, ) add_script_run_ctx(threading.current_thread(), ctx) request = self._requests.on_scriptrunner_ready() while request.type == ScriptRequestType.RERUN: # When the script thread starts, we'll have a pending rerun # request that we'll handle immediately. When the script finishes, # it's possible that another request has come in that we need to # handle, which is why we call _run_script in a loop. self._run_script(request.rerun_data) request = self._requests.on_scriptrunner_ready() assert request.type == ScriptRequestType.STOP # Send a SHUTDOWN event before exiting. This includes the widget values # as they existed after our last successful script run, which the # AppSession will pass on to the next ScriptRunner that gets # created. client_state = ClientState() client_state.query_string = ctx.query_string widget_states = self._session_state.get_widget_states() client_state.widget_states.widgets.extend(widget_states) self.on_event.send(self, event=ScriptRunnerEvent.SHUTDOWN, client_state=client_state)
def __init__(self, script_name: str): """Initializes the ScriptRunner for the given script_name""" # DeltaGenerator deltas will be enqueued into self.forward_msg_queue. self.forward_msg_queue = ForwardMsgQueue() main_script_path = os.path.join(os.path.dirname(__file__), "test_data", script_name) super().__init__( session_id="test session id", session_data=SessionData(main_script_path, "test command line"), client_state=ClientState(), session_state=SessionState(), uploaded_file_mgr=UploadedFileManager(), initial_rerun_data=RerunData(), ) # Accumulates uncaught exceptions thrown by our run thread. self.script_thread_exceptions: List[BaseException] = [] # Accumulates all ScriptRunnerEvents emitted by us. self.events: List[ScriptRunnerEvent] = [] self.event_data: List[Any] = [] def record_event(sender: Optional[ScriptRunner], event: ScriptRunnerEvent, **kwargs) -> None: # 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) # Send ENQUEUE_FORWARD_MSGs to our queue if event == ScriptRunnerEvent.ENQUEUE_FORWARD_MSG: forward_msg = kwargs["forward_msg"] self.forward_msg_queue.enqueue(forward_msg) self.on_event.connect(record_event, weak=False)
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)