def test_coalesce_widget_states(self): old_states = WidgetStates() _create_widget("old_set_trigger", old_states).trigger_value = True _create_widget("old_unset_trigger", old_states).trigger_value = False _create_widget("missing_in_new", old_states).int_value = 123 _create_widget("shape_changing_trigger", old_states).trigger_value = True new_states = WidgetStates() _create_widget("old_set_trigger", new_states).trigger_value = False _create_widget("new_set_trigger", new_states).trigger_value = True _create_widget("added_in_new", new_states).int_value = 456 _create_widget("shape_changing_trigger", new_states).int_value = 3 widgets = Widgets() widgets.set_state(coalesce_widget_states(old_states, new_states)) self.assertIsNone(widgets.get_widget_value("old_unset_trigger")) self.assertIsNone(widgets.get_widget_value("missing_in_new")) self.assertEqual(True, widgets.get_widget_value("old_set_trigger")) self.assertEqual(True, widgets.get_widget_value("new_set_trigger")) self.assertEqual(456, widgets.get_widget_value("added_in_new")) # Widgets that were triggers before, but no longer are, will *not* # be coalesced self.assertEqual(3, widgets.get_widget_value("shape_changing_trigger"))
def setUp(self, override_root=True): self.report_queue = ReportQueue() if override_root: main_dg = self.new_delta_generator() sidebar_dg = self.new_delta_generator() setattr(threading.current_thread(), REPORT_CONTEXT_ATTR_NAME, ReportContext(main_dg=main_dg, sidebar_dg=sidebar_dg, widgets=Widgets()))
def test_set_page_config_immutable(self): """st.set_page_config must be called at most once""" fake_enqueue = lambda msg: None ctx = ReportContext("TestSessionID", fake_enqueue, "", Widgets(), UploadedFileManager()) msg = ForwardMsg() msg.page_config_changed.title = "foo" ctx.enqueue(msg) with self.assertRaises(StreamlitAPIException): ctx.enqueue(msg)
def __init__(self, report): """Initialize. Parameters ---------- report : Report The report with the script to run. """ self._report = report self._event_queue = ScriptEventQueue() self._state = ScriptState.STOPPED self._last_run_data = RerunData(argv=report.argv, widget_state=WidgetStates()) self._widgets = Widgets() self.run_on_save = config.get_option('server.runOnSave') self.on_state_changed = Signal( doc="""Emitted when the script's execution state state changes. Parameters ---------- state : ScriptState """) self.on_file_change_not_handled = Signal( doc="Emitted when the file is modified and we haven't handled it.") self.on_script_compile_error = Signal( doc="""Emitted if our script fails to compile. (*Not* emitted for normal exceptions thrown while a script is running.) Parameters ---------- exception : Exception The exception that was thrown """) self._local_sources_watcher = LocalSourcesWatcher( self._report, self._on_source_file_changed) # Will be set to true when we process a SHUTDOWN event self._shutdown_requested = False self._script_thread = None self._ctx = None # Set to true while we're executing. Used by # maybe_handle_execution_control_request. self._execing = False
def test_reset_triggers(self): states = WidgetStates() widgets = Widgets() _create_widget("trigger", states).trigger_value = True _create_widget("int", states).int_value = 123 widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value("trigger")) self.assertEqual(123, widgets.get_widget_value("int")) widgets.reset_triggers() self.assertEqual(None, widgets.get_widget_value("trigger")) self.assertEqual(123, widgets.get_widget_value("int"))
def test_reset_triggers(self): states = WidgetStates() widgets = Widgets() _create_widget('trigger', states).trigger_value = True _create_widget('int', states).int_value = 123 widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value('trigger')) self.assertEqual(123, widgets.get_widget_value('int')) widgets.set_state(reset_widget_triggers(states)) self.assertEqual(False, widgets.get_widget_value('trigger')) self.assertEqual(123, widgets.get_widget_value('int'))
def test_set_page_config_reset(self): """st.set_page_config should be allowed after a rerun""" fake_enqueue = lambda msg: None ctx = ReportContext("TestSessionID", fake_enqueue, "", Widgets(), UploadedFileManager()) msg = ForwardMsg() msg.page_config_changed.title = "foo" ctx.enqueue(msg) ctx.reset() try: ctx.enqueue(msg) except StreamlitAPIException: self.fail("set_page_config should have succeeded after reset!")
def test_set_page_config_first(self): """st.set_page_config must be called before other st commands""" fake_enqueue = lambda msg: None ctx = ReportContext("TestSessionID", fake_enqueue, "", Widgets(), UploadedFileManager()) markdown_msg = ForwardMsg() markdown_msg.delta.new_element.markdown.body = "foo" msg = ForwardMsg() msg.page_config_changed.title = "foo" ctx.enqueue(markdown_msg) with self.assertRaises(StreamlitAPIException): ctx.enqueue(msg)
def setUp(self, override_root=True): self.report_queue = ReportQueue() self.override_root = override_root self.orig_report_ctx = None if self.override_root: self.orig_report_ctx = get_report_ctx() add_report_ctx( threading.current_thread(), ReportContext( enqueue=self.report_queue.enqueue, widgets=Widgets(), widget_ids_this_run=_WidgetIDSet(), uploaded_file_mgr=UploadedFileManager(), ), )
def setUp(self, override_root=True): self.report_queue = ReportQueue() if override_root: main_dg = self.new_delta_generator() sidebar_dg = self.new_delta_generator(container=BlockPath.SIDEBAR) setattr( threading.current_thread(), REPORT_CONTEXT_ATTR_NAME, ReportContext( main_dg=main_dg, sidebar_dg=sidebar_dg, widgets=Widgets(), widget_ids_this_run=_WidgetIDSet(), ), )
def test_values(self): states = WidgetStates() _create_widget('trigger', states).trigger_value = True _create_widget('bool', states).bool_value = True _create_widget('float', states).float_value = 0.5 _create_widget('int', states).int_value = 123 _create_widget('string', states).string_value = 'howdy!' widgets = Widgets() widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value('trigger')) self.assertEqual(True, widgets.get_widget_value('bool')) self.assertAlmostEqual(0.5, widgets.get_widget_value('float')) self.assertEqual(123, widgets.get_widget_value('int')) self.assertEqual('howdy!', widgets.get_widget_value('string'))
def setUp(self, override_root=True): self.report_queue = ReportQueue() self.override_root = override_root self.orig_report_ctx = None if self.override_root: self.orig_report_ctx = get_report_ctx() add_report_ctx( threading.current_thread(), ReportContext( session_id="test session id", enqueue=self.report_queue.enqueue, query_string="", widgets=Widgets(), uploaded_file_mgr=UploadedFileManager(), ), )
def test_values(self): states = WidgetStates() _create_widget("trigger", states).trigger_value = True _create_widget("bool", states).bool_value = True _create_widget("float", states).double_value = 0.5 _create_widget("int", states).int_value = 123 _create_widget("string", states).string_value = "howdy!" widgets = Widgets() widgets.set_state(states) self.assertEqual(True, widgets.get_widget_value("trigger")) self.assertEqual(True, widgets.get_widget_value("bool")) self.assertAlmostEqual(0.5, widgets.get_widget_value("float")) self.assertEqual(123, widgets.get_widget_value("int")) self.assertEqual("howdy!", widgets.get_widget_value("string"))
def test_handle_save_request(self, _1): """Test that handle_save_request serializes files correctly.""" # Create a ReportSession with some mocked bits rs = ReportSession(self.io_loop, "mock_report.py", "", UploadedFileManager()) rs._report.report_id = "TestReportID" orig_ctx = get_report_ctx() ctx = ReportContext("TestSessionID", rs._report.enqueue, "", Widgets(), UploadedFileManager()) add_report_ctx(ctx=ctx) rs._scriptrunner = MagicMock() storage = MockStorage() rs._storage = storage # Send two deltas: empty and markdown st.empty() st.markdown("Text!") yield rs.handle_save_request(_create_mock_websocket()) # Check the order of the received files. Manifest should be last. self.assertEqual(3, len(storage.files)) self.assertEqual("reports/TestReportID/0.pb", storage.get_filename(0)) self.assertEqual("reports/TestReportID/1.pb", storage.get_filename(1)) self.assertEqual("reports/TestReportID/manifest.pb", storage.get_filename(2)) # Check the manifest manifest = storage.get_message(2, StaticManifest) self.assertEqual("mock_report", manifest.name) self.assertEqual(2, manifest.num_messages) self.assertEqual(StaticManifest.DONE, manifest.server_status) # Check that the deltas we sent match messages in storage sent_messages = rs._report._master_queue._queue received_messages = [ storage.get_message(0, ForwardMsg), storage.get_message(1, ForwardMsg), ] self.assertEqual(sent_messages, received_messages) add_report_ctx(ctx=orig_ctx)
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() states = WidgetStates() _create_widget('trigger', states).trigger_value = True _create_widget('int', states).int_value = 123 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) states = WidgetStates() _create_widget('trigger', states).trigger_value = False _create_widget('int', states).int_value = 456 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) event, data = queue.dequeue() self.assertEqual(event, ScriptRequest.RERUN) widgets = Widgets() widgets.set_state(data.widget_state) # Coalesced triggers should be True if either the old or # new value was True self.assertEqual(True, widgets.get_widget_value('trigger')) # Other widgets should have their newest value self.assertEqual(456, widgets.get_widget_value('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(argv=None, widget_state=None)) queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=None)) states = WidgetStates() _create_widget('int', states).int_value = 789 queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=states)) event, data = queue.dequeue() widgets = Widgets() widgets.set_state(data.widget_state) self.assertEqual(event, ScriptRequest.RERUN) self.assertEqual(789, widgets.get_widget_value('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(argv=None, widget_state=states)) queue.enqueue(ScriptRequest.RERUN, RerunData(argv=None, widget_state=None)) event, data = queue.dequeue() widgets = Widgets() widgets.set_state(data.widget_state) self.assertEqual(event, ScriptRequest.RERUN) self.assertEqual(101112, widgets.get_widget_value('int')) # We should have no more events self.assertEqual((None, None), queue.dequeue(), 'Expected empty event queue')
def __init__( self, session_id, report, enqueue_forward_msg, client_state, request_queue, uploaded_file_mgr=None, ): """Initialize the ScriptRunner. (The ScriptRunner won't start executing until start() is called.) Parameters ---------- session_id : str The ReportSession's id. report : Report The ReportSession's report. client_state : streamlit.proto.ClientState_pb2.ClientState The current state from the client (widgets and query params). request_queue : ScriptRequestQueue The queue that the ReportSession is publishing ScriptRequests to. ScriptRunner will continue running until the queue is empty, and then shut down. uploaded_file_mgr : UploadedFileManager The File manager to store the data uploaded by the file_uploader widget. """ self._session_id = session_id self._report = report self._enqueue_forward_msg = enqueue_forward_msg self._request_queue = request_queue self._uploaded_file_mgr = uploaded_file_mgr self._client_state = client_state self._widgets = Widgets() self._widgets.set_state(client_state.widget_states) self.on_event = Signal(doc="""Emitted when a ScriptRunnerEvent occurs. This signal is *not* emitted on the same thread that the ScriptRunner was created on. Parameters ---------- event : ScriptRunnerEvent exception : BaseException | None Our compile error. Set only for the SCRIPT_STOPPED_WITH_COMPILE_ERROR event. widget_states : streamlit.proto.WidgetStates_pb2.WidgetStates | None The ScriptRunner's final WidgetStates. Set only for the SHUTDOWN event. """) # Set to true when we process a SHUTDOWN request self._shutdown_requested = False # Set to true while we're executing. Used by # maybe_handle_execution_control_request. self._execing = False # This is initialized in start() self._script_thread = None
def __init__(self, report, main_dg, sidebar_dg, widget_states, request_queue): """Initialize the ScriptRunner. (The ScriptRunner won't start executing until start() is called.) Parameters ---------- report : Report The ReportSession's report. main_dg : DeltaGenerator The ReportSession's main DeltaGenerator. sidebar_dg : DeltaGenerator The ReportSession's sidebar DeltaGenerator. widget_states : streamlit.proto.Widget_pb2.WidgetStates The ReportSession's current widget states request_queue : ScriptRequestQueue The queue that the ReportSession is publishing ScriptRequests to. ScriptRunner will continue running until the queue is empty, and then shut down. """ self._report = report self._main_dg = main_dg self._sidebar_dg = sidebar_dg self._request_queue = request_queue self._widgets = Widgets() self._widgets.set_state(widget_states) self.on_event = Signal(doc="""Emitted when a ScriptRunnerEvent occurs. This signal is *not* emitted on the same thread that the ScriptRunner was created on. Parameters ---------- event : ScriptRunnerEvent exception : BaseException | None Our compile error. Set only for the SCRIPT_STOPPED_WITH_COMPILE_ERROR event. widget_states : streamlit.proto.Widget_pb2.WidgetStates | None The ScriptRunner's final WidgetStates. Set only for the SHUTDOWN event. """) # Set to true when we process a SHUTDOWN request self._shutdown_requested = False # Set to true while we're executing. Used by # maybe_handle_execution_control_request. self._execing = False # This is initialized in start() self._script_thread = None
def __init__(self, delta_generator): self.root_dg = delta_generator self.widgets = Widgets()