Repository: streamlit/streamlit

Issue:
st.query_params type attributes incorrectly prohibit `Iterable[str]` values in `from_dict` and `update`
### Checklist

- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.
- [X] I added a very descriptive title to this issue.
- [X] I have provided sufficient information below to help reproduce this issue.

### Summary

Related to fixes for #8470 and #8347, we didn't quite get the type attributes right.

### Reproducible Code Example

_No response_

### Steps To Reproduce

_No response_

### Expected Behavior

_No response_

### Current Behavior

_No response_

### Is this a regression?

- [ ] Yes, this used to work in a previous version.

### Debug info

- Streamlit version: 1.34.0
- Python version: 3.12
- Operating System: Linux
- Browser: Opera


### Additional Information

_No response_


Discussion:
**If this issue affects you, please react with a 👍 (thumbs up emoji) to the initial post.**

Your feedback helps us prioritize which bugs to investigate and address first.

![Visits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fstreamlit%2Fstreamlit%2Fissues%2F8613&title=visits&edge_flat=false)


Resolving patch:
diff --git a/lib/streamlit/runtime/state/query_params.py b/lib/streamlit/runtime/state/query_params.py
--- a/lib/streamlit/runtime/state/query_params.py
+++ b/lib/streamlit/runtime/state/query_params.py
@@ -98,7 +98,8 @@ def __delitem__(self, key: str) -> None:
 
     def update(
         self,
-        other: Iterable[tuple[str, str]] | SupportsKeysAndGetItem[str, str] = (),
+        other: Iterable[tuple[str, str | Iterable[str]]]
+        | SupportsKeysAndGetItem[str, str | Iterable[str]] = (),
         /,
         **kwds: str,
     ):
@@ -164,7 +165,8 @@ def to_dict(self) -> dict[str, str]:
 
     def from_dict(
         self,
-        _dict: Iterable[tuple[str, str]] | SupportsKeysAndGetItem[str, str],
+        _dict: Iterable[tuple[str, str | Iterable[str]]]
+        | SupportsKeysAndGetItem[str, str | Iterable[str]],
     ):
         self._ensure_single_query_api_used()
         old_value = self._query_params.copy()
diff --git a/lib/streamlit/runtime/state/query_params_proxy.py b/lib/streamlit/runtime/state/query_params_proxy.py
--- a/lib/streamlit/runtime/state/query_params_proxy.py
+++ b/lib/streamlit/runtime/state/query_params_proxy.py
@@ -72,17 +72,19 @@ def __delattr__(self, key: str) -> None:
                 raise AttributeError(missing_key_error_message(key))
 
     @overload
-    def update(self, mapping: SupportsKeysAndGetItem[str, str], /, **kwds: str) -> None:
+    def update(
+        self, mapping: SupportsKeysAndGetItem[str, str | Iterable[str]], /, **kwds: str
+    ) -> None:
         ...
 
     @overload
     def update(
-        self, keys_and_values: Iterable[tuple[str, str]], /, **kwds: str
+        self, keys_and_values: Iterable[tuple[str, str | Iterable[str]]], /, **kwds: str
     ) -> None:
         ...
 
     @overload
-    def update(self, **kwds: str) -> None:
+    def update(self, **kwds: str | Iterable[str]) -> None:
         ...
 
     def update(self, other=(), /, **kwds):
@@ -163,11 +165,15 @@ def to_dict(self) -> dict[str, str]:
             return qp.to_dict()
 
     @overload
-    def from_dict(self, keys_and_values: Iterable[tuple[str, str]]) -> None:
+    def from_dict(
+        self, keys_and_values: Iterable[tuple[str, str | Iterable[str]]]
+    ) -> None:
         ...
 
     @overload
-    def from_dict(self, mapping: SupportsKeysAndGetItem[str, str]) -> None:
+    def from_dict(
+        self, mapping: SupportsKeysAndGetItem[str, str | Iterable[str]]
+    ) -> None:
         ...
 
     @gather_metrics("query_params.from_dict")


Test patch:
diff --git a/lib/tests/streamlit/runtime/state/query_params_test.py b/lib/tests/streamlit/runtime/state/query_params_test.py
--- a/lib/tests/streamlit/runtime/state/query_params_test.py
+++ b/lib/tests/streamlit/runtime/state/query_params_test.py
@@ -130,6 +130,28 @@ def test_update_adds_values(self):
         message = self.get_message_from_queue(0)
         assert "foo=bar" in message.page_info_changed.query_string
 
+    def test_update_adds_list_values(self):
+        self.query_params.update({"foo": ["bar", "baz"]})
+        assert self.query_params.get_all("foo") == ["bar", "baz"]
+        message = self.get_message_from_queue(0)
+        assert "foo=bar&foo=baz" in message.page_info_changed.query_string
+
+    def test_update_with_iterable(self):
+        self.query_params.update([("foo", "bar"), ("stream", ["lit", "rocks"])])
+        assert self.query_params.get("foo") == "bar"
+        assert self.query_params.get("stream") == "rocks"
+        message = self.get_message_from_queue(0)
+        assert "foo=bar" in message.page_info_changed.query_string
+        assert "stream=lit&stream=rocks" in message.page_info_changed.query_string
+
+    def test_update_with_keywords(self):
+        self.query_params.update(foo="bar", stream=["lit", "rocks"])
+        assert self.query_params.get("foo") == "bar"
+        assert self.query_params.get("stream") == "rocks"
+        message = self.get_message_from_queue(0)
+        assert "foo=bar" in message.page_info_changed.query_string
+        assert "stream=lit&stream=rocks" in message.page_info_changed.query_string
+
     def test_update_raises_error_with_embed_key(self):
         with pytest.raises(StreamlitAPIException):
             self.query_params.update({"foo": "bar", "embed": "true"})