class SessionStateMethodTests(unittest.TestCase): def setUp(self): old_state = {"foo": "bar", "baz": "qux", "corge": "grault"} new_session_state = {"foo": "bar2"} new_widget_state = WStates( { "baz": Value("qux2"), f"{GENERATED_WIDGET_KEY_PREFIX}-foo-None": Value("bar"), }, ) self.session_state = SessionState(old_state, new_session_state, new_widget_state) def test_compact(self): self.session_state.compact_state() assert self.session_state._old_state == { "foo": "bar2", "baz": "qux2", "corge": "grault", f"{GENERATED_WIDGET_KEY_PREFIX}-foo-None": "bar", } assert self.session_state._new_session_state == {} assert self.session_state._new_widget_state == WStates() def test_clear_state(self): self.session_state.clear_state() assert self.session_state._merged_state == {} def test_safe_widget_state(self): new_session_state = MagicMock() wstate = {"foo": "bar"} new_session_state.__getitem__.side_effect = wstate.__getitem__ new_session_state.keys = lambda: {"foo", "baz"} self.session_state = SessionState({}, {}, new_session_state) assert self.session_state._safe_widget_state() == wstate def test_merged_state(self): assert self.session_state._merged_state == { "foo": "bar2", "baz": "qux2", "corge": "grault", f"{GENERATED_WIDGET_KEY_PREFIX}-foo-None": "bar", } def test_filtered_state(self): assert self.session_state.filtered_state == { "foo": "bar2", "baz": "qux2", "corge": "grault", } def test_filtered_state_resilient_to_missing_metadata(self): old_state = {"foo": "bar", "corge": "grault"} new_session_state = {} new_widget_state = WStates( {f"{GENERATED_WIDGET_KEY_PREFIX}-baz": Serialized(None)}, ) self.session_state = SessionState(old_state, new_session_state, new_widget_state) assert self.session_state.filtered_state == { "foo": "bar", "corge": "grault", } def is_new_state_value(self): assert self.session_state.is_new_state_value("foo") assert not self.session_state.is_new_state_value("corge") def test_getitem(self): assert self.session_state["foo"] == "bar2" def test_getitem_error(self): with pytest.raises(KeyError): self.session_state["nonexistent"] def test_setitem(self): assert not self.session_state.is_new_state_value("corge") self.session_state["corge"] = "grault2" assert self.session_state["corge"] == "grault2" assert self.session_state.is_new_state_value("corge") def test_setitem_disallows_setting_created_widget(self): mock_ctx = MagicMock() mock_ctx.widget_ids_this_run = _StringSet() mock_ctx.widget_ids_this_run.add("widget_id") with patch("streamlit.report_thread.get_report_ctx", return_value=mock_ctx): with pytest.raises(StreamlitAPIException) as e: self.session_state._key_id_mapping = {"widget_id": "widget_id"} self.session_state["widget_id"] = "blah" assert "`st.session_state.widget_id` cannot be modified" in str( e.value) def test_setitem_disallows_setting_created_form(self): mock_ctx = MagicMock() mock_ctx.form_ids_this_run = _StringSet() mock_ctx.form_ids_this_run.add("form_id") with patch("streamlit.report_thread.get_report_ctx", return_value=mock_ctx): with pytest.raises(StreamlitAPIException) as e: self.session_state["form_id"] = "blah" assert "`st.session_state.form_id` cannot be modified" in str( e.value) def test_delitem(self): del self.session_state["foo"] assert "foo" not in self.session_state def test_delitem_errors(self): for key in ["_new_session_state", "_new_widget_state", "_old_state"]: with pytest.raises(KeyError): del self.session_state[key] with pytest.raises(KeyError): del self.session_state["nonexistent"] def test_widget_changed(self): assert self.session_state._widget_changed("foo") self.session_state._new_widget_state.set_from_value("foo", "bar") assert not self.session_state._widget_changed("foo") def test_cull_nonexistent(self): generated_widget_key = f"{GENERATED_WIDGET_KEY_PREFIX}-removed_widget" self.session_state._old_state = { "existing_widget": True, generated_widget_key: True, "val_set_via_state": 5, } wstates = WStates() self.session_state._new_widget_state = wstates self.session_state.cull_nonexistent({"existing_widget"}) assert self.session_state["existing_widget"] == True assert generated_widget_key not in self.session_state assert self.session_state["val_set_via_state"] == 5 def test_should_set_frontend_state_value_new_widget(self): # The widget is being registered for the first time, so there's no need # to have the frontend update with a new value. wstates = WStates() self.session_state._new_widget_state = wstates metadata = WidgetMetadata( id=f"{GENERATED_WIDGET_KEY_PREFIX}-0-widget_id_1", deserializer=lambda _, __: 0, serializer=identity, value_type="int_value", ) self.session_state.set_keyed_widget( metadata, f"{GENERATED_WIDGET_KEY_PREFIX}-0-widget_id_1", "widget_id_1") assert (self.session_state.should_set_frontend_state_value( f"{GENERATED_WIDGET_KEY_PREFIX}-0-widget_id_1", "widget_id_1", ) == False) assert self.session_state["widget_id_1"] == 0
class SessionStateMethodTests(unittest.TestCase): def setUp(self): old_state = {"foo": "bar", "baz": "qux", "corge": "grault"} new_session_state = {"foo": "bar2"} new_widget_state = WStates( { "baz": Value("qux2"), f"{GENERATED_WIDGET_KEY_PREFIX}-foo-None": Value("bar"), }, ) self.session_state = SessionState(old_state, new_session_state, new_widget_state) def test_compact(self): self.session_state._compact_state() assert self.session_state._old_state == { "foo": "bar2", "baz": "qux2", "corge": "grault", f"{GENERATED_WIDGET_KEY_PREFIX}-foo-None": "bar", } assert self.session_state._new_session_state == {} assert self.session_state._new_widget_state == WStates() def test_clear_state(self): # Sanity test keys = { "foo", "baz", "corge", f"{GENERATED_WIDGET_KEY_PREFIX}-foo-None" } self.assertEqual(keys, self.session_state._keys()) # Clear state self.session_state.clear() # Keys should be empty self.assertEqual(set(), self.session_state._keys()) def test_filtered_state(self): assert self.session_state.filtered_state == { "foo": "bar2", "baz": "qux2", "corge": "grault", } def test_filtered_state_resilient_to_missing_metadata(self): old_state = {"foo": "bar", "corge": "grault"} new_session_state = {} new_widget_state = WStates( { f"{GENERATED_WIDGET_KEY_PREFIX}-baz": Serialized(WidgetStateProto()) }, ) self.session_state = SessionState(old_state, new_session_state, new_widget_state) assert self.session_state.filtered_state == { "foo": "bar", "corge": "grault", } def is_new_state_value(self): assert self.session_state.is_new_state_value("foo") assert not self.session_state.is_new_state_value("corge") def test_getitem(self): assert self.session_state["foo"] == "bar2" def test_getitem_error(self): with pytest.raises(KeyError): self.session_state["nonexistent"] def test_setitem(self): assert not self.session_state.is_new_state_value("corge") self.session_state["corge"] = "grault2" assert self.session_state["corge"] == "grault2" assert self.session_state.is_new_state_value("corge") def test_setitem_disallows_setting_created_widget(self): mock_ctx = MagicMock() mock_ctx.widget_ids_this_run = {"widget_id"} with patch("streamlit.scriptrunner.get_script_run_ctx", return_value=mock_ctx): with pytest.raises(StreamlitAPIException) as e: self.session_state._key_id_mapping = {"widget_id": "widget_id"} self.session_state["widget_id"] = "blah" assert "`st.session_state.widget_id` cannot be modified" in str( e.value) def test_setitem_disallows_setting_created_form(self): mock_ctx = MagicMock() mock_ctx.form_ids_this_run = {"form_id"} with patch("streamlit.scriptrunner.get_script_run_ctx", return_value=mock_ctx): with pytest.raises(StreamlitAPIException) as e: self.session_state["form_id"] = "blah" assert "`st.session_state.form_id` cannot be modified" in str( e.value) def test_delitem(self): del self.session_state["foo"] assert "foo" not in self.session_state def test_delitem_errors(self): for key in ["_new_session_state", "_new_widget_state", "_old_state"]: with pytest.raises(KeyError): del self.session_state[key] with pytest.raises(KeyError): del self.session_state["nonexistent"] def test_widget_changed(self): assert self.session_state._widget_changed("foo") self.session_state._new_widget_state.set_from_value("foo", "bar") assert not self.session_state._widget_changed("foo") def test_cull_nonexistent(self): generated_widget_key = f"{GENERATED_WIDGET_KEY_PREFIX}-removed_widget" self.session_state._old_state = { "existing_widget": True, generated_widget_key: True, "val_set_via_state": 5, } wstates = WStates() self.session_state._new_widget_state = wstates self.session_state._cull_nonexistent({"existing_widget"}) assert self.session_state["existing_widget"] == True assert generated_widget_key not in self.session_state assert self.session_state["val_set_via_state"] == 5 def test_should_set_frontend_state_value_new_widget(self): # The widget is being registered for the first time, so there's no need # to have the frontend update with a new value. wstates = WStates() self.session_state._new_widget_state = wstates WIDGET_VALUE = 123 metadata = WidgetMetadata( id=f"{GENERATED_WIDGET_KEY_PREFIX}-0-widget_id_1", deserializer=lambda _, __: WIDGET_VALUE, serializer=identity, value_type="int_value", ) _, widget_value_changed = self.session_state.register_widget( metadata=metadata, user_key="widget_id_1", ) assert not widget_value_changed assert self.session_state["widget_id_1"] == WIDGET_VALUE