Repository: streamlit/streamlit

Issue:
Allow initialization of range slider with session state
### Summary

I want to be able to create a slider and a checkbox where if the checkbox is unclicked, the slider functions as normal with some default value `val1`, but if the checkbox is clicked, the slider is locked in to a default value `val2` and resets to this value with every rerun, independent of user action. 

This functionality is possible with a standard slider using session state and key to initialize the slider value depending on the value of the checkbox, however, streamlit throws an error if you attempt to use session state to initialize a tuple of initial values for the slider (i.e. to create a range_slider). 

### Steps to reproduce

Code snippet:
Functionality for typical single-value slider (this works as expected):
```
import streamlit as st

val1 = 30
val2 = 70
low = 0
high = 100

use_val2 = st.checkbox('Val2?')

if use_val2:
    st.session_state.my_slider = val2
elif 'my_slider' not in st.session_state:
    st.session_state.my_slider = val1

slider = st.slider('My slider', min_value=low, max_value=high, key='my_slider')

```

This raises the error:
```
import streamlit as st

val1_0, val1_f = 30, 50
val2_0, val2_f = 70, 90
low = 0
high = 100

use_val2 = st.checkbox('Val2?')

if use_val2:
    st.session_state.my_slider = (val2_0, val2_f)
elif 'my_slider' not in st.session_state:
    st.session_state.my_slider = (val1_0, val1_f)

slider = st.slider('My range slider', min_value=low,
                   max_value=high, key='my_slider')

```

**Expected behavior:**

I would like the default slider range to be 30-50 and adjustable if `use_val2` is checked, and 70-90 and fixed if `use_val2` is not checked.

**Actual behavior:**
Raises  `TypeError: (30, 50) has type tuple, but expected one of: int, long, float`

### Is this a regression?

No

### Debug info

- Streamlit version: 0.84.0 
- Python version: 3.8.11
- Using: pip
- OS version: Windows 10
- Browser version: Chrome 


Discussion:
I found a workaround for now by initializing values in the call to `slider` and manually setting them when `use_val2` is checked.
```
import streamlit as st

val1_0, val1_f = 30, 50
val2_0, val2_f = 70, 90
low = 0
high = 100

use_val2 = st.checkbox('Val2?')
if use_val2:
    st.session_state.my_slider = (val2_0, val2_f)
slider = st.slider('My range slider', min_value=low,
                   max_value=high, value=(val1_0, val1_f), key='my_slider')
```

This throws a warning described at the bottom of [this page](https://docs.streamlit.io/en/stable/session_state_api.html)...is there an easy way to disable this warning?
Thanks for reporting this @scott-trinkle! It seems like `st.slider`'s type validation is a bit too restrictive now that it's possible to set a widget's value via `st.session_state`. I'll try to get a fix out for this if I have some time to do so ​in the next few weeks.

> This throws a warning described at the bottom of this page...is there an easy way to disable this warning?

There's unfortunately no easy way to disable the warning you're getting at the moment, but I agree that it's something we may want to add a config option for. Thankfully, I think the warning should only appear a single time until streamlit is completely restarted, so hopefully the warning will only be mildly annoying until then. 

Resolving patch:
diff --git a/lib/streamlit/elements/select_slider.py b/lib/streamlit/elements/select_slider.py
--- a/lib/streamlit/elements/select_slider.py
+++ b/lib/streamlit/elements/select_slider.py
@@ -117,23 +117,27 @@ def select_slider(
             raise StreamlitAPIException("The `options` argument needs to be non-empty")
 
         is_range_value = isinstance(value, (list, tuple))
-        slider_value = value
+
+        def as_index_list(v):
+            is_range_value = isinstance(v, (list, tuple))
+            if is_range_value:
+                slider_value = [index_(opt, val) for val in v]
+                start, end = slider_value
+                if start > end:
+                    slider_value = [end, start]
+                return slider_value
+            else:
+                # Simplify future logic by always making value a list
+                try:
+                    return [index_(opt, v)]
+                except ValueError:
+                    if value is not None:
+                        raise
+
+                    return [0]
 
         # Convert element to index of the elements
-        if is_range_value:
-            slider_value = list(map(lambda v: index_(opt, v), value))
-            start, end = slider_value
-            if start > end:
-                slider_value = [end, start]
-        else:
-            # Simplify future logic by always making value a list
-            try:
-                slider_value = [index_(opt, value)]
-            except ValueError:
-                if value is not None:
-                    raise
-
-                slider_value = [0]
+        slider_value = as_index_list(value)
 
         slider_proto = SliderProto()
         slider_proto.label = label
@@ -160,8 +164,7 @@ def deserialize_select_slider(ui_value, widget_id=""):
             return tuple(return_value) if is_range_value else return_value[0]
 
         def serialize_select_slider(v):
-            to_serialize = v if is_range_value else [v]
-            return [index_(opt, u) for u in to_serialize]
+            return as_index_list(v)
 
         current_value, set_frontend_value = register_widget(
             "slider",
diff --git a/lib/streamlit/elements/slider.py b/lib/streamlit/elements/slider.py
--- a/lib/streamlit/elements/slider.py
+++ b/lib/streamlit/elements/slider.py
@@ -423,7 +423,8 @@ def deserialize_slider(ui_value: Optional[List[float]], widget_id=""):
             return val[0] if single_value else tuple(val)
 
         def serialize_slider(v: Any) -> List[Any]:
-            value = [v] if single_value else list(v)
+            range_value = isinstance(v, (list, tuple))
+            value = list(v) if range_value else [v]
             if data_type == SliderProto.DATE:
                 value = [_datetime_to_micros(_date_to_datetime(v)) for v in value]
             if data_type == SliderProto.TIME:


Test patch:
diff --git a/lib/tests/streamlit/select_slider_test.py b/lib/tests/streamlit/select_slider_test.py
--- a/lib/tests/streamlit/select_slider_test.py
+++ b/lib/tests/streamlit/select_slider_test.py
@@ -103,6 +103,19 @@ def test_range_out_of_order(self):
         c = self.get_delta_from_queue().new_element.slider
         self.assertEqual(c.default, [0, 2])
 
+    def test_range_session_state(self):
+        """Test a range set by session state."""
+        state = st.session_state
+        state["colors"] = ("red", "orange")
+
+        colors = st.select_slider(
+            "select colors",
+            options=["red", "orange", "yellow"],
+            key="colors",
+        )
+
+        assert colors == ("red", "orange")
+
     def test_format_func(self):
         """Test that format_func sends down correct strings of the options."""
         DAYS_OF_WEEK = [
diff --git a/lib/tests/streamlit/slider_test.py b/lib/tests/streamlit/slider_test.py
--- a/lib/tests/streamlit/slider_test.py
+++ b/lib/tests/streamlit/slider_test.py
@@ -122,6 +122,20 @@ def test_naive_timelikes(self, value, return_value):
         self.assertEqual(ret, return_value)
         self.assertEqual(c.label, "the label")
 
+    def test_range_session_state(self):
+        """Test a range set by session state."""
+        state = st.session_state
+        state["slider"] = [10, 20]
+
+        slider = st.slider(
+            "select a range",
+            min_value=0,
+            max_value=100,
+            key="slider",
+        )
+
+        assert slider == [10, 20]
+
     def test_value_greater_than_min(self):
         ret = st.slider("Slider label", 10, 100, 0)
         c = self.get_delta_from_queue().new_element.slider
diff --git a/lib/tests/streamlit/state/session_state_test.py b/lib/tests/streamlit/state/session_state_test.py
--- a/lib/tests/streamlit/state/session_state_test.py
+++ b/lib/tests/streamlit/state/session_state_test.py
@@ -351,6 +351,14 @@ def test_select_slider_serde(self):
         )
         check_roundtrip("select_slider", select_slider)
 
+        select_slider_range = st.select_slider(
+            "select_slider_range",
+            options=["a", "b", "c"],
+            value=["a", "b"],
+            key="select_slider_range",
+        )
+        check_roundtrip("select_slider_range", select_slider_range)
+
     def test_slider_serde(self):
         slider = st.slider("slider", key="slider")
         check_roundtrip("slider", slider)