We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[v1.21.0] "TypeError: Unicode-objects must be encoded before hashing" in `util.calc_md5` due to inadequate type check
### 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

After upgrading to version 1.21.0, [`util.calc_md5`](https://github.com/streamlit/streamlit/blob/316cef3426c4f3c81e99414446b0e9a131dbdb57/lib/streamlit/util.py#L180) throws an error when loading a page: `TypeError: Unicode-objects must be encoded before hashing`.

This is due to the check `if type(s) is str` in `b = cast(bytes, s.encode("utf-8") if type(s) is str else s)` not being broad enough to catch all `str`-like objects.

### Reproducible Code Example

```Python
from streamlit import util

class MyString(str):
    pass

s = MyString("foobar")

util.calc_md5(s)
```


### Steps To Reproduce

Run the provided code example.

### Expected Behavior

No error.

### Current Behavior

TypeError: Unicode-objects must be encoded before hashing

### Is this a regression?

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

### Debug info

- Streamlit version: 1.21.0
- Python version: 3.8.14
- Operating System: Ubuntu 18.04.6
- Browser: Chrome Version 107.0.5304.87 (Official Build) (64-bit)
- Virtual environment:


### Additional Information

In my case, the [`main_script_path_str` passed to `get_pages` here](https://github.com/streamlit/streamlit/blob/316cef3426c4f3c81e99414446b0e9a131dbdb57/lib/streamlit/source_util.py#L106) ends up being a subclass of `str`.

A good enough fix for my specific case is using `isinstance` instead of `type`:

```
b = cast(bytes, s.encode("utf-8") if isinstance(s, str) else s)
```

Or something like this could maybe catch more potential issues:

```
b = cast(bytes, str(s).encode("utf-8") if type(s) is not bytes else s)
```

### Are you willing to submit a PR?

- [X] Yes, I am willing to submit a PR!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/streamlit/util.py`
Content:
```
1 # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """A bunch of useful utilities."""
16 
17 from __future__ import annotations
18 
19 import dataclasses
20 import functools
21 import hashlib
22 import os
23 import subprocess
24 from typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union, cast
25 
26 from typing_extensions import Final
27 
28 from streamlit import env_util
29 
30 # URL of Streamlit's help page.
31 HELP_DOC: Final = "https://docs.streamlit.io/"
32 FLOAT_EQUALITY_EPSILON: Final[float] = 0.000000000005
33 
34 
35 def memoize(func):
36     """Decorator to memoize the result of a no-args func."""
37     result: List[Any] = []
38 
39     @functools.wraps(func)
40     def wrapped_func():
41         if not result:
42             result.append(func())
43         return result[0]
44 
45     return wrapped_func
46 
47 
48 def open_browser(url):
49     """Open a web browser pointing to a given URL.
50 
51     We use this function instead of Python's `webbrowser` module because this
52     way we can capture stdout/stderr to avoid polluting the terminal with the
53     browser's messages. For example, Chrome always prints things like "Created
54     new window in existing browser session", and those get on the user's way.
55 
56     url : str
57         The URL. Must include the protocol.
58 
59     """
60     # Treat Windows separately because:
61     # 1. /dev/null doesn't exist.
62     # 2. subprocess.Popen(['start', url]) doesn't actually pop up the
63     #    browser even though 'start url' works from the command prompt.
64     # Fun!
65     # Also, use webbrowser if we are on Linux and xdg-open is not installed.
66     #
67     # We don't use the webbrowser module on Linux and Mac because some browsers
68     # (ahem... Chrome) always print "Opening in existing browser session" to
69     # the terminal, which is spammy and annoying. So instead we start the
70     # browser ourselves and send all its output to /dev/null.
71 
72     if env_util.IS_WINDOWS:
73         _open_browser_with_webbrowser(url)
74         return
75     if env_util.IS_LINUX_OR_BSD:
76         if env_util.is_executable_in_path("xdg-open"):
77             _open_browser_with_command("xdg-open", url)
78             return
79         _open_browser_with_webbrowser(url)
80         return
81     if env_util.IS_DARWIN:
82         _open_browser_with_command("open", url)
83         return
84 
85     import platform
86 
87     raise Error('Cannot open browser in platform "%s"' % platform.system())
88 
89 
90 def _open_browser_with_webbrowser(url):
91     import webbrowser
92 
93     webbrowser.open(url)
94 
95 
96 def _open_browser_with_command(command, url):
97     cmd_line = [command, url]
98     with open(os.devnull, "w") as devnull:
99         subprocess.Popen(cmd_line, stdout=devnull, stderr=subprocess.STDOUT)
100 
101 
102 def _maybe_tuple_to_list(item: Any) -> Any:
103     """Convert a tuple to a list. Leave as is if it's not a tuple."""
104     if isinstance(item, tuple):
105         return list(item)
106     return item
107 
108 
109 def repr_(self: Any) -> str:
110     """A clean repr for a class, excluding both values that are likely defaults,
111     and those explicitly default for dataclasses.
112     """
113     classname = self.__class__.__name__
114     # Most of the falsey value, but excluding 0 and 0.0, since those often have
115     # semantic meaning within streamlit.
116     defaults: list[Any] = [None, "", False, [], set(), dict()]
117     if dataclasses.is_dataclass(self):
118         fields_vals = (
119             (f.name, getattr(self, f.name))
120             for f in dataclasses.fields(self)
121             if f.repr
122             and getattr(self, f.name) != f.default
123             and getattr(self, f.name) not in defaults
124         )
125     else:
126         fields_vals = ((f, v) for (f, v) in self.__dict__.items() if v not in defaults)
127 
128     field_reprs = ", ".join(f"{field}={value!r}" for field, value in fields_vals)
129     return f"{classname}({field_reprs})"
130 
131 
132 _Value = TypeVar("_Value")
133 
134 
135 def index_(iterable: Iterable[_Value], x: _Value) -> int:
136     """Return zero-based index of the first item whose value is equal to x.
137     Raises a ValueError if there is no such item.
138 
139     We need a custom implementation instead of the built-in list .index() to
140     be compatible with NumPy array and Pandas Series.
141 
142     Parameters
143     ----------
144     iterable : list, tuple, numpy.ndarray, pandas.Series
145     x : Any
146 
147     Returns
148     -------
149     int
150     """
151     for i, value in enumerate(iterable):
152         if x == value:
153             return i
154         elif isinstance(value, float) and isinstance(x, float):
155             if abs(x - value) < FLOAT_EQUALITY_EPSILON:
156                 return i
157     raise ValueError("{} is not in iterable".format(str(x)))
158 
159 
160 _Key = TypeVar("_Key", bound=str)
161 
162 
163 def lower_clean_dict_keys(dict: Mapping[_Key, _Value]) -> Dict[str, _Value]:
164     return {k.lower().strip(): v for k, v in dict.items()}
165 
166 
167 # TODO: Move this into errors.py? Replace with StreamlitAPIException?
168 class Error(Exception):
169     pass
170 
171 
172 def calc_md5(s: Union[bytes, str]) -> str:
173     """Return the md5 hash of the given string."""
174     h = hashlib.new("md5")
175 
176     # mypy seems to have trouble inferring that the type of the if/else expression is
177     # always bytes.
178     b = cast(bytes, s.encode("utf-8") if type(s) is str else s)
179 
180     h.update(b)
181     return h.hexdigest()
182 
183 
184 def exclude_key_query_params(
185     query_params: Dict[str, List[str]], keys_to_exclude: List[str]
186 ) -> Dict[str, List[str]]:
187     """Returns new object query_params : Dict[str, List[str]], but without keys defined with keys_to_drop : List[str]."""
188     return {
189         key: value
190         for key, value in query_params.items()
191         if key.lower() not in keys_to_exclude
192     }
193 
194 
195 def extract_key_query_params(
196     query_params: Dict[str, List[str]], param_key: str
197 ) -> Set[str]:
198     """Extracts key (case-insensitive) query params from Dict, and returns them as Set of str."""
199     return set(
200         [
201             item.lower()
202             for sublist in [
203                 [value.lower() for value in query_params[key]]
204                 for key in query_params.keys()
205                 if key.lower() == param_key and query_params.get(key)
206             ]
207             for item in sublist
208         ]
209     )
210
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/streamlit/util.py b/lib/streamlit/util.py
--- a/lib/streamlit/util.py
+++ b/lib/streamlit/util.py
@@ -21,7 +21,7 @@
 import hashlib
 import os
 import subprocess
-from typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union, cast
+from typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union
 
 from typing_extensions import Final
 
@@ -173,9 +173,7 @@
     """Return the md5 hash of the given string."""
     h = hashlib.new("md5")
 
-    # mypy seems to have trouble inferring that the type of the if/else expression is
-    # always bytes.
-    b = cast(bytes, s.encode("utf-8") if type(s) is str else s)
+    b = s.encode("utf-8") if isinstance(s, str) else s
 
     h.update(b)
     return h.hexdigest()

{"golden_diff": "diff --git a/lib/streamlit/util.py b/lib/streamlit/util.py\n--- a/lib/streamlit/util.py\n+++ b/lib/streamlit/util.py\n@@ -21,7 +21,7 @@\n import hashlib\n import os\n import subprocess\n-from typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union, cast\n+from typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union\n \n from typing_extensions import Final\n \n@@ -173,9 +173,7 @@\n     \"\"\"Return the md5 hash of the given string.\"\"\"\n     h = hashlib.new(\"md5\")\n \n-    # mypy seems to have trouble inferring that the type of the if/else expression is\n-    # always bytes.\n-    b = cast(bytes, s.encode(\"utf-8\") if type(s) is str else s)\n+    b = s.encode(\"utf-8\") if isinstance(s, str) else s\n \n     h.update(b)\n     return h.hexdigest()\n", "issue": "[v1.21.0] \"TypeError: Unicode-objects must be encoded before hashing\" in `util.calc_md5` due to inadequate type check\n### Checklist\r\n\r\n- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.\r\n- [X] I added a very descriptive title to this issue.\r\n- [X] I have provided sufficient information below to help reproduce this issue.\r\n\r\n### Summary\r\n\r\nAfter upgrading to version 1.21.0, [`util.calc_md5`](https://github.com/streamlit/streamlit/blob/316cef3426c4f3c81e99414446b0e9a131dbdb57/lib/streamlit/util.py#L180) throws an error when loading a page: `TypeError: Unicode-objects must be encoded before hashing`.\r\n\r\nThis is due to the check `if type(s) is str` in `b = cast(bytes, s.encode(\"utf-8\") if type(s) is str else s)` not being broad enough to catch all `str`-like objects.\r\n\r\n### Reproducible Code Example\r\n\r\n```Python\r\nfrom streamlit import util\r\n\r\nclass MyString(str):\r\n    pass\r\n\r\ns = MyString(\"foobar\")\r\n\r\nutil.calc_md5(s)\r\n```\r\n\r\n\r\n### Steps To Reproduce\r\n\r\nRun the provided code example.\r\n\r\n### Expected Behavior\r\n\r\nNo error.\r\n\r\n### Current Behavior\r\n\r\nTypeError: Unicode-objects must be encoded before hashing\r\n\r\n### Is this a regression?\r\n\r\n- [X] Yes, this used to work in a previous version.\r\n\r\n### Debug info\r\n\r\n- Streamlit version: 1.21.0\r\n- Python version: 3.8.14\r\n- Operating System: Ubuntu 18.04.6\r\n- Browser: Chrome Version 107.0.5304.87 (Official Build) (64-bit)\r\n- Virtual environment:\r\n\r\n\r\n### Additional Information\r\n\r\nIn my case, the [`main_script_path_str` passed to `get_pages` here](https://github.com/streamlit/streamlit/blob/316cef3426c4f3c81e99414446b0e9a131dbdb57/lib/streamlit/source_util.py#L106) ends up being a subclass of `str`.\r\n\r\nA good enough fix for my specific case is using `isinstance` instead of `type`:\r\n\r\n```\r\nb = cast(bytes, s.encode(\"utf-8\") if isinstance(s, str) else s)\r\n```\r\n\r\nOr something like this could maybe catch more potential issues:\r\n\r\n```\r\nb = cast(bytes, str(s).encode(\"utf-8\") if type(s) is not bytes else s)\r\n```\r\n\r\n### Are you willing to submit a PR?\r\n\r\n- [X] Yes, I am willing to submit a PR!\n", "before_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A bunch of useful utilities.\"\"\"\n\nfrom __future__ import annotations\n\nimport dataclasses\nimport functools\nimport hashlib\nimport os\nimport subprocess\nfrom typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union, cast\n\nfrom typing_extensions import Final\n\nfrom streamlit import env_util\n\n# URL of Streamlit's help page.\nHELP_DOC: Final = \"https://docs.streamlit.io/\"\nFLOAT_EQUALITY_EPSILON: Final[float] = 0.000000000005\n\n\ndef memoize(func):\n    \"\"\"Decorator to memoize the result of a no-args func.\"\"\"\n    result: List[Any] = []\n\n    @functools.wraps(func)\n    def wrapped_func():\n        if not result:\n            result.append(func())\n        return result[0]\n\n    return wrapped_func\n\n\ndef open_browser(url):\n    \"\"\"Open a web browser pointing to a given URL.\n\n    We use this function instead of Python's `webbrowser` module because this\n    way we can capture stdout/stderr to avoid polluting the terminal with the\n    browser's messages. For example, Chrome always prints things like \"Created\n    new window in existing browser session\", and those get on the user's way.\n\n    url : str\n        The URL. Must include the protocol.\n\n    \"\"\"\n    # Treat Windows separately because:\n    # 1. /dev/null doesn't exist.\n    # 2. subprocess.Popen(['start', url]) doesn't actually pop up the\n    #    browser even though 'start url' works from the command prompt.\n    # Fun!\n    # Also, use webbrowser if we are on Linux and xdg-open is not installed.\n    #\n    # We don't use the webbrowser module on Linux and Mac because some browsers\n    # (ahem... Chrome) always print \"Opening in existing browser session\" to\n    # the terminal, which is spammy and annoying. So instead we start the\n    # browser ourselves and send all its output to /dev/null.\n\n    if env_util.IS_WINDOWS:\n        _open_browser_with_webbrowser(url)\n        return\n    if env_util.IS_LINUX_OR_BSD:\n        if env_util.is_executable_in_path(\"xdg-open\"):\n            _open_browser_with_command(\"xdg-open\", url)\n            return\n        _open_browser_with_webbrowser(url)\n        return\n    if env_util.IS_DARWIN:\n        _open_browser_with_command(\"open\", url)\n        return\n\n    import platform\n\n    raise Error('Cannot open browser in platform \"%s\"' % platform.system())\n\n\ndef _open_browser_with_webbrowser(url):\n    import webbrowser\n\n    webbrowser.open(url)\n\n\ndef _open_browser_with_command(command, url):\n    cmd_line = [command, url]\n    with open(os.devnull, \"w\") as devnull:\n        subprocess.Popen(cmd_line, stdout=devnull, stderr=subprocess.STDOUT)\n\n\ndef _maybe_tuple_to_list(item: Any) -> Any:\n    \"\"\"Convert a tuple to a list. Leave as is if it's not a tuple.\"\"\"\n    if isinstance(item, tuple):\n        return list(item)\n    return item\n\n\ndef repr_(self: Any) -> str:\n    \"\"\"A clean repr for a class, excluding both values that are likely defaults,\n    and those explicitly default for dataclasses.\n    \"\"\"\n    classname = self.__class__.__name__\n    # Most of the falsey value, but excluding 0 and 0.0, since those often have\n    # semantic meaning within streamlit.\n    defaults: list[Any] = [None, \"\", False, [], set(), dict()]\n    if dataclasses.is_dataclass(self):\n        fields_vals = (\n            (f.name, getattr(self, f.name))\n            for f in dataclasses.fields(self)\n            if f.repr\n            and getattr(self, f.name) != f.default\n            and getattr(self, f.name) not in defaults\n        )\n    else:\n        fields_vals = ((f, v) for (f, v) in self.__dict__.items() if v not in defaults)\n\n    field_reprs = \", \".join(f\"{field}={value!r}\" for field, value in fields_vals)\n    return f\"{classname}({field_reprs})\"\n\n\n_Value = TypeVar(\"_Value\")\n\n\ndef index_(iterable: Iterable[_Value], x: _Value) -> int:\n    \"\"\"Return zero-based index of the first item whose value is equal to x.\n    Raises a ValueError if there is no such item.\n\n    We need a custom implementation instead of the built-in list .index() to\n    be compatible with NumPy array and Pandas Series.\n\n    Parameters\n    ----------\n    iterable : list, tuple, numpy.ndarray, pandas.Series\n    x : Any\n\n    Returns\n    -------\n    int\n    \"\"\"\n    for i, value in enumerate(iterable):\n        if x == value:\n            return i\n        elif isinstance(value, float) and isinstance(x, float):\n            if abs(x - value) < FLOAT_EQUALITY_EPSILON:\n                return i\n    raise ValueError(\"{} is not in iterable\".format(str(x)))\n\n\n_Key = TypeVar(\"_Key\", bound=str)\n\n\ndef lower_clean_dict_keys(dict: Mapping[_Key, _Value]) -> Dict[str, _Value]:\n    return {k.lower().strip(): v for k, v in dict.items()}\n\n\n# TODO: Move this into errors.py? Replace with StreamlitAPIException?\nclass Error(Exception):\n    pass\n\n\ndef calc_md5(s: Union[bytes, str]) -> str:\n    \"\"\"Return the md5 hash of the given string.\"\"\"\n    h = hashlib.new(\"md5\")\n\n    # mypy seems to have trouble inferring that the type of the if/else expression is\n    # always bytes.\n    b = cast(bytes, s.encode(\"utf-8\") if type(s) is str else s)\n\n    h.update(b)\n    return h.hexdigest()\n\n\ndef exclude_key_query_params(\n    query_params: Dict[str, List[str]], keys_to_exclude: List[str]\n) -> Dict[str, List[str]]:\n    \"\"\"Returns new object query_params : Dict[str, List[str]], but without keys defined with keys_to_drop : List[str].\"\"\"\n    return {\n        key: value\n        for key, value in query_params.items()\n        if key.lower() not in keys_to_exclude\n    }\n\n\ndef extract_key_query_params(\n    query_params: Dict[str, List[str]], param_key: str\n) -> Set[str]:\n    \"\"\"Extracts key (case-insensitive) query params from Dict, and returns them as Set of str.\"\"\"\n    return set(\n        [\n            item.lower()\n            for sublist in [\n                [value.lower() for value in query_params[key]]\n                for key in query_params.keys()\n                if key.lower() == param_key and query_params.get(key)\n            ]\n            for item in sublist\n        ]\n    )\n", "path": "lib/streamlit/util.py"}], "after_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"A bunch of useful utilities.\"\"\"\n\nfrom __future__ import annotations\n\nimport dataclasses\nimport functools\nimport hashlib\nimport os\nimport subprocess\nfrom typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar, Union\n\nfrom typing_extensions import Final\n\nfrom streamlit import env_util\n\n# URL of Streamlit's help page.\nHELP_DOC: Final = \"https://docs.streamlit.io/\"\nFLOAT_EQUALITY_EPSILON: Final[float] = 0.000000000005\n\n\ndef memoize(func):\n    \"\"\"Decorator to memoize the result of a no-args func.\"\"\"\n    result: List[Any] = []\n\n    @functools.wraps(func)\n    def wrapped_func():\n        if not result:\n            result.append(func())\n        return result[0]\n\n    return wrapped_func\n\n\ndef open_browser(url):\n    \"\"\"Open a web browser pointing to a given URL.\n\n    We use this function instead of Python's `webbrowser` module because this\n    way we can capture stdout/stderr to avoid polluting the terminal with the\n    browser's messages. For example, Chrome always prints things like \"Created\n    new window in existing browser session\", and those get on the user's way.\n\n    url : str\n        The URL. Must include the protocol.\n\n    \"\"\"\n    # Treat Windows separately because:\n    # 1. /dev/null doesn't exist.\n    # 2. subprocess.Popen(['start', url]) doesn't actually pop up the\n    #    browser even though 'start url' works from the command prompt.\n    # Fun!\n    # Also, use webbrowser if we are on Linux and xdg-open is not installed.\n    #\n    # We don't use the webbrowser module on Linux and Mac because some browsers\n    # (ahem... Chrome) always print \"Opening in existing browser session\" to\n    # the terminal, which is spammy and annoying. So instead we start the\n    # browser ourselves and send all its output to /dev/null.\n\n    if env_util.IS_WINDOWS:\n        _open_browser_with_webbrowser(url)\n        return\n    if env_util.IS_LINUX_OR_BSD:\n        if env_util.is_executable_in_path(\"xdg-open\"):\n            _open_browser_with_command(\"xdg-open\", url)\n            return\n        _open_browser_with_webbrowser(url)\n        return\n    if env_util.IS_DARWIN:\n        _open_browser_with_command(\"open\", url)\n        return\n\n    import platform\n\n    raise Error('Cannot open browser in platform \"%s\"' % platform.system())\n\n\ndef _open_browser_with_webbrowser(url):\n    import webbrowser\n\n    webbrowser.open(url)\n\n\ndef _open_browser_with_command(command, url):\n    cmd_line = [command, url]\n    with open(os.devnull, \"w\") as devnull:\n        subprocess.Popen(cmd_line, stdout=devnull, stderr=subprocess.STDOUT)\n\n\ndef _maybe_tuple_to_list(item: Any) -> Any:\n    \"\"\"Convert a tuple to a list. Leave as is if it's not a tuple.\"\"\"\n    if isinstance(item, tuple):\n        return list(item)\n    return item\n\n\ndef repr_(self: Any) -> str:\n    \"\"\"A clean repr for a class, excluding both values that are likely defaults,\n    and those explicitly default for dataclasses.\n    \"\"\"\n    classname = self.__class__.__name__\n    # Most of the falsey value, but excluding 0 and 0.0, since those often have\n    # semantic meaning within streamlit.\n    defaults: list[Any] = [None, \"\", False, [], set(), dict()]\n    if dataclasses.is_dataclass(self):\n        fields_vals = (\n            (f.name, getattr(self, f.name))\n            for f in dataclasses.fields(self)\n            if f.repr\n            and getattr(self, f.name) != f.default\n            and getattr(self, f.name) not in defaults\n        )\n    else:\n        fields_vals = ((f, v) for (f, v) in self.__dict__.items() if v not in defaults)\n\n    field_reprs = \", \".join(f\"{field}={value!r}\" for field, value in fields_vals)\n    return f\"{classname}({field_reprs})\"\n\n\n_Value = TypeVar(\"_Value\")\n\n\ndef index_(iterable: Iterable[_Value], x: _Value) -> int:\n    \"\"\"Return zero-based index of the first item whose value is equal to x.\n    Raises a ValueError if there is no such item.\n\n    We need a custom implementation instead of the built-in list .index() to\n    be compatible with NumPy array and Pandas Series.\n\n    Parameters\n    ----------\n    iterable : list, tuple, numpy.ndarray, pandas.Series\n    x : Any\n\n    Returns\n    -------\n    int\n    \"\"\"\n    for i, value in enumerate(iterable):\n        if x == value:\n            return i\n        elif isinstance(value, float) and isinstance(x, float):\n            if abs(x - value) < FLOAT_EQUALITY_EPSILON:\n                return i\n    raise ValueError(\"{} is not in iterable\".format(str(x)))\n\n\n_Key = TypeVar(\"_Key\", bound=str)\n\n\ndef lower_clean_dict_keys(dict: Mapping[_Key, _Value]) -> Dict[str, _Value]:\n    return {k.lower().strip(): v for k, v in dict.items()}\n\n\n# TODO: Move this into errors.py? Replace with StreamlitAPIException?\nclass Error(Exception):\n    pass\n\n\ndef calc_md5(s: Union[bytes, str]) -> str:\n    \"\"\"Return the md5 hash of the given string.\"\"\"\n    h = hashlib.new(\"md5\")\n\n    b = s.encode(\"utf-8\") if isinstance(s, str) else s\n\n    h.update(b)\n    return h.hexdigest()\n\n\ndef exclude_key_query_params(\n    query_params: Dict[str, List[str]], keys_to_exclude: List[str]\n) -> Dict[str, List[str]]:\n    \"\"\"Returns new object query_params : Dict[str, List[str]], but without keys defined with keys_to_drop : List[str].\"\"\"\n    return {\n        key: value\n        for key, value in query_params.items()\n        if key.lower() not in keys_to_exclude\n    }\n\n\ndef extract_key_query_params(\n    query_params: Dict[str, List[str]], param_key: str\n) -> Set[str]:\n    \"\"\"Extracts key (case-insensitive) query params from Dict, and returns them as Set of str.\"\"\"\n    return set(\n        [\n            item.lower()\n            for sublist in [\n                [value.lower() for value in query_params[key]]\n                for key in query_params.keys()\n                if key.lower() == param_key and query_params.get(key)\n            ]\n            for item in sublist\n        ]\n    )\n", "path": "lib/streamlit/util.py"}]}