Repository: streamlit/streamlit

Issue:
Multiple sliders with the same key but different labels do not cause error
### Summary

Making multiple sliders with the same key, but different labels, results in only one slider value in `session_state` (and does not cause an error). Making multiple sliders with the same key and same label causes an error (this is expected behavior).

### Steps to reproduce

Code snippet:

```
import streamlit as st

for k in range(5):
    st.slider(
        f"Slider {k}",
        min_value=0,
        max_value=100,
        value=50,
        step=1,
        key="slider",
    )

st.write(st.session_state)
```

**Expected behavior:**

An error should occur.

**Actual behavior:**

No error occurs.  All five sliders appear, but there is only one slider value in `st.session_state`

### Is this a regression?

Unknown.

### Debug info

- Streamlit version: 1.7.0
- Python version: 3.9.9
- Using Conda? PipEnv? PyEnv? Pex?  No.
- OS version: Mac OS X 12.3
- Browser version: Version 15.4 (17613.1.17.1.6)

### Additional information

None.

Multiple sliders with the same key but different labels do not cause error
### Summary

Making multiple sliders with the same key, but different labels, results in only one slider value in `session_state` (and does not cause an error). Making multiple sliders with the same key and same label causes an error (this is expected behavior).

### Steps to reproduce

Code snippet:

```
import streamlit as st

for k in range(5):
    st.slider(
        f"Slider {k}",
        min_value=0,
        max_value=100,
        value=50,
        step=1,
        key="slider",
    )

st.write(st.session_state)
```

**Expected behavior:**

An error should occur.

**Actual behavior:**

No error occurs.  All five sliders appear, but there is only one slider value in `st.session_state`

### Is this a regression?

Unknown.

### Debug info

- Streamlit version: 1.7.0
- Python version: 3.9.9
- Using Conda? PipEnv? PyEnv? Pex?  No.
- OS version: Mac OS X 12.3
- Browser version: Version 15.4 (17613.1.17.1.6)

### Additional information

None.



Discussion:
Hi @rcutler-ursa! 😃 
Thank you again for your contributions, I was able to confirm the bug and would agree that enforcing unique keys would be the expected widget behavior. We're going to get this in front of our product team to see how we can best integrate this with our future plans.
Hi @rcutler-ursa! 😃 
Thank you again for your contributions, I was able to confirm the bug and would agree that enforcing unique keys would be the expected widget behavior. We're going to get this in front of our product team to see how we can best integrate this with our future plans.

Resolving patch:
diff --git a/lib/streamlit/scriptrunner/script_run_context.py b/lib/streamlit/scriptrunner/script_run_context.py
--- a/lib/streamlit/scriptrunner/script_run_context.py
+++ b/lib/streamlit/scriptrunner/script_run_context.py
@@ -53,6 +53,7 @@ class ScriptRunContext:
     _set_page_config_allowed: bool = True
     _has_script_started: bool = False
     widget_ids_this_run: Set[str] = field(default_factory=set)
+    widget_user_keys_this_run: Set[str] = field(default_factory=set)
     form_ids_this_run: Set[str] = field(default_factory=set)
     cursors: Dict[int, "streamlit.cursor.RunningCursor"] = field(default_factory=dict)
     dg_stack: List["streamlit.delta_generator.DeltaGenerator"] = field(
@@ -62,6 +63,7 @@ class ScriptRunContext:
     def reset(self, query_string: str = "", page_script_hash: str = "") -> None:
         self.cursors = {}
         self.widget_ids_this_run = set()
+        self.widget_user_keys_this_run = set()
         self.form_ids_this_run = set()
         self.query_string = query_string
         self.page_script_hash = page_script_hash
diff --git a/lib/streamlit/state/widgets.py b/lib/streamlit/state/widgets.py
--- a/lib/streamlit/state/widgets.py
+++ b/lib/streamlit/state/widgets.py
@@ -191,6 +191,18 @@ def register_widget(
         # we're running as a "bare" Python script, and not via `streamlit run`).
         return RegisterWidgetResult.failure(deserializer=deserializer)
 
+    # Ensure another widget with the same user key hasn't already been registered.
+    if user_key is not None:
+        if user_key not in ctx.widget_user_keys_this_run:
+            ctx.widget_user_keys_this_run.add(user_key)
+        else:
+            raise DuplicateWidgetID(
+                _build_duplicate_widget_message(
+                    widget_func_name if widget_func_name is not None else element_type,
+                    user_key,
+                )
+            )
+
     # Ensure another widget with the same id hasn't already been registered.
     new_widget = widget_id not in ctx.widget_ids_this_run
     if new_widget:


Test patch:
diff --git a/lib/tests/streamlit/caching/common_cache_test.py b/lib/tests/streamlit/caching/common_cache_test.py
--- a/lib/tests/streamlit/caching/common_cache_test.py
+++ b/lib/tests/streamlit/caching/common_cache_test.py
@@ -68,6 +68,8 @@ def tearDown(self):
         ctx = script_run_context.get_script_run_ctx()
         if ctx is not None:
             ctx.widget_ids_this_run.clear()
+            ctx.widget_user_keys_this_run.clear()
+
         super().tearDown()
 
     def get_text_delta_contents(self) -> List[str]:
diff --git a/lib/tests/streamlit/delta_generator_test.py b/lib/tests/streamlit/delta_generator_test.py
--- a/lib/tests/streamlit/delta_generator_test.py
+++ b/lib/tests/streamlit/delta_generator_test.py
@@ -148,7 +148,7 @@ def set_widget_requires_args(self):
         self.assertEqual(c.type, "TypeError")
 
     def test_duplicate_widget_id_error(self):
-        """Multiple widgets with the same key should report an error."""
+        """Multiple widgets with the same generated key should report an error."""
         widgets = {
             "button": lambda key=None: st.button("", key=key),
             "checkbox": lambda key=None: st.checkbox("", key=key),
@@ -190,6 +190,55 @@ def test_duplicate_widget_id_error(self):
                 str(ctx.exception),
             )
 
+    def test_duplicate_widget_id_error_when_user_key_specified(self):
+        """Multiple widgets with the different generated key, but same user specified
+        key should report an error.
+        """
+
+        widgets = {
+            "button": lambda key=None, label="": st.button(label=label, key=key),
+            "checkbox": lambda key=None, label="": st.checkbox(label=label, key=key),
+            "multiselect": lambda key=None, label="": st.multiselect(
+                label=label, options=[1, 2], key=key
+            ),
+            "radio": lambda key=None, label="": st.radio(
+                label=label, options=[1, 2], key=key
+            ),
+            "selectbox": lambda key=None, label="": st.selectbox(
+                label=label, options=[1, 2], key=key
+            ),
+            "slider": lambda key=None, label="": st.slider(label=label, key=key),
+            "text_area": lambda key=None, label="": st.text_area(label=label, key=key),
+            "text_input": lambda key=None, label="": st.text_input(
+                label=label, key=key
+            ),
+            "time_input": lambda key=None, label="": st.time_input(
+                label=label, key=key
+            ),
+            "date_input": lambda key=None, label="": st.date_input(
+                label=label, key=key
+            ),
+            "number_input": lambda key=None, label="": st.number_input(
+                label=label, key=key
+            ),
+        }
+
+        for widget_type, create_widget in widgets.items():
+            user_key = widget_type
+            create_widget(label="LABEL_A", key=user_key)
+            with self.assertRaises(DuplicateWidgetID) as ctx:
+                # We specify different labels for widgets, so auto-generated keys
+                # (widget_ids) will be different.
+                # Test creating a widget with a different auto-generated key but same
+                # user specified key raises an exception.
+                create_widget(label="LABEL_B", key=user_key)
+            self.assertEqual(
+                _build_duplicate_widget_message(
+                    widget_func_name=widget_type, user_key=user_key
+                ),
+                str(ctx.exception),
+            )
+
 
 class DeltaGeneratorClassTest(testutil.DeltaGeneratorTestCase):
     """Test DeltaGenerator Class."""