Repository: arnaudmiribel/streamlit-extras

Issue:
No-default selectbox doesn't work if you pass a Series

Discussion:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cddc01a..8de794d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 repos:
-- repo: https://gitlab.com/pycqa/flake8
+- repo: https://github.com/pycqa/flake8
   rev: "4.0.1"
   hooks:
   - id: flake8
diff --git a/src/streamlit_extras/no_default_selectbox/__init__.py b/src/streamlit_extras/no_default_selectbox/__init__.py
index 972809a..0e8c85c 100644
--- a/src/streamlit_extras/no_default_selectbox/__init__.py
+++ b/src/streamlit_extras/no_default_selectbox/__init__.py
@@ -1,8 +1,39 @@
+from __future__ import annotations
+
+from typing import Any, Iterable
+
+import pandas as pd
 import streamlit as st
 
 from streamlit_extras import extra
 
 
+def _transform_arguments(*args, **kwargs) -> tuple[str, Iterable[Any], dict[str, Any]]:
+    no_selection_label = kwargs.pop("no_selection_label", "---")
+
+    _args = list(args)
+
+    # Get the options from either the args or kwargs
+    try:
+        options = _args.pop(1)
+    except IndexError:
+        options = kwargs["options"]
+
+    # Prepend the no-selection option to the list of options
+    if no_selection_label not in options:
+        if isinstance(options, pd.Series):
+            options = list(pd.concat([pd.Series([no_selection_label]), options]))
+        elif isinstance(options, pd.DataFrame):
+            # If the options are a DataFrame, the options are just the first column
+            options = options.iloc[:, 0]
+            options = list(pd.concat([pd.Series([no_selection_label]), options]))
+        else:
+            options = [no_selection_label] + list(options)
+        kwargs["options"] = options
+
+    return no_selection_label, _args, kwargs
+
+
 @extra
 def selectbox(*args, **kwargs):
     """A selectbox that returns None unless the user has explicitly selected one of the
@@ -16,22 +47,9 @@ def selectbox(*args, **kwargs):
     no_selection_label : str
         The label to use for the no-selection option. Defaults to "---".
     """
-    no_selection_label = kwargs.pop("no_selection_label", "---")
-
-    args = list(args)
+    no_selection_label, _args, _kwargs = _transform_arguments(*args, **kwargs)
 
-    # Get the options from either the args or kwargs
-    try:
-        options = args.pop(1)
-    except IndexError:
-        options = kwargs["options"]
-
-    # Prepend the no-selection option to the list of options
-    if no_selection_label not in options:
-        options = [no_selection_label] + options
-        kwargs["options"] = options
-
-    result = st.selectbox(*args, **kwargs)
+    result = st.selectbox(*_args, **_kwargs)
     if result == no_selection_label:
         return None
     return result
@@ -42,10 +60,7 @@ def example():
         """
         This is an example of a selectbox that returns None unless the user has
         explicitly selected one of the options.
-        """
-    )
-    st.write(
-        """
+
         The selectbox below has no default value, so it will return None until the
         user selects an option.
         """
@@ -53,6 +68,13 @@ def example():
     result = selectbox("Select an option", ["A", "B", "C"])
     st.write("Result:", result)
 
+    result = selectbox(
+        "Select an option with different label",
+        ["A", "B", "C"],
+        no_selection_label="<None>",
+    )
+    st.write("Result:", result)
+
 
 __title__ = "No-Default Selectbox"
 __desc__ = """


Test patch:
diff --git a/tests/test_no_default_selectbox.py b/tests/test_no_default_selectbox.py
new file mode 100644
index 0000000..be0a341
--- /dev/null
+++ b/tests/test_no_default_selectbox.py
@@ -0,0 +1,73 @@
+import pandas as pd
+
+
+def test_args():
+    from streamlit_extras.no_default_selectbox import _transform_arguments
+
+    no_selection_label, args, kwargs = _transform_arguments(
+        "Select a value", ["a", "b", "c"], no_selection_label="None"
+    )
+
+    assert no_selection_label == "None"
+    assert args == ["Select a value"]
+    assert kwargs == {"options": ["None", "a", "b", "c"]}
+
+
+def test_kwargs():
+    from streamlit_extras.no_default_selectbox import _transform_arguments
+
+    no_selection_label, args, kwargs = _transform_arguments(
+        label="Select a value", options=["a", "b", "c"]
+    )
+
+    assert no_selection_label == "---"
+    assert args == []
+    assert kwargs == {"label": "Select a value", "options": ["---", "a", "b", "c"]}
+
+
+def test_args_with_kwargs():
+    from streamlit_extras.no_default_selectbox import _transform_arguments
+
+    no_selection_label, args, kwargs = _transform_arguments(
+        "Select a value", ["a", "b", "c"], default="b", no_selection_label="None"
+    )
+
+    assert no_selection_label == "None"
+    assert args == ["Select a value"]
+    assert kwargs == {"options": ["None", "a", "b", "c"], "default": "b"}
+
+
+def test_args_with_tuple():
+    from streamlit_extras.no_default_selectbox import _transform_arguments
+
+    no_selection_label, args, kwargs = _transform_arguments(
+        "Select a value", ("a", "b", "c")
+    )
+
+    assert no_selection_label == "---"
+    assert args == ["Select a value"]
+    assert kwargs == {"options": ["---", "a", "b", "c"]}
+
+
+def test_args_with_series():
+    from streamlit_extras.no_default_selectbox import _transform_arguments
+
+    no_selection_label, args, kwargs = _transform_arguments(
+        "Select a value", pd.Series(["a", "b", "c"])
+    )
+
+    assert no_selection_label == "---"
+    assert args == ["Select a value"]
+    assert kwargs == {"options": ["---", "a", "b", "c"]}
+
+
+def test_args_with_dataframe():
+    from streamlit_extras.no_default_selectbox import _transform_arguments
+
+    no_selection_label, args, kwargs = _transform_arguments(
+        "Select a value", pd.DataFrame(["a", "b", "c"])
+    )
+
+    assert no_selection_label == "---"
+    assert args == ["Select a value"]
+    assert kwargs == {"options": ["---", "a", "b", "c"]}