-
Notifications
You must be signed in to change notification settings - Fork 37
fix(workflow): stop losing widget input on Streamlit < 1.50 #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -559,7 +559,9 @@ def _select_input_file_impl(self, key, name, multiple, display_file_path, reacti | |
| if not path.exists(): | ||
| st.warning(f"No **{name}** files!") | ||
| return | ||
| options = [str(f) for f in path.iterdir() if "external_files.txt" not in str(f)] | ||
| options = sorted( | ||
| str(f) for f in path.iterdir() if "external_files.txt" not in str(f) | ||
| ) | ||
|
|
||
| # Check if local files are available | ||
| external_files = Path( | ||
|
|
@@ -675,11 +677,21 @@ def format_files(input: Any) -> List[str]: | |
|
|
||
| key = f"{self.parameter_manager.param_prefix}{key}" | ||
|
|
||
| # Streamlit ignores a widget's initial-value argument (value=/default=/index=) | ||
| # once that key already exists in session state -- but on Streamlit < 1.50 the | ||
| # argument is still hashed into the widget's element id. Since this method feeds | ||
| # the persisted parameter straight back in as that argument, the id changed on | ||
| # every interaction, the following interaction arrived under the now-stale id and | ||
| # was silently dropped: selecting six mzML files kept only three. Seed the widget | ||
| # on first render only; from then on session state owns the value. | ||
| def seed(**kwargs: Any) -> dict: | ||
| return {} if key in st.session_state else kwargs | ||
|
Comment on lines
+687
to
+688
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: # Inspect the repository rule scope and the changed helper with its direct widget call sites.
printf '%s\n' '--- repository conventions ---'
head -5 /tmp/coderabbit-repo-knowledge/openms-streamlit-template-6b41a083/*/*.md 2>/dev/null || true
printf '%s\n' '--- StreamlitUI.py relevant ranges ---'
sed -n '620,840p' src/workflow/StreamlitUI.pyRepository: OpenMS/streamlit-template Length of output: 12137 🌐 Web query:
💡 Result: In Streamlit 1.49.1, the Citations:
🏁 Script executed: python3 - <<'PY'
from urllib.request import urlopen
urls = {
"selectbox": "https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/widgets/selectbox.py",
"multiselect": "https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/widgets/multiselect.py",
"widget": "https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/runtime/state/widgets.py",
}
for name, url in urls.items():
print(f"--- {name}: {url} ---")
try:
text = urlopen(url, timeout=15).read().decode()
except Exception as exc:
print(f"FETCH_ERROR: {exc}")
continue
lines = text.splitlines()
needles = ("compute_and_register_element_id", "index=", "default=", "options=",
"register_widget", "WidgetMetadata")
for i, line in enumerate(lines, 1):
if any(n in line for n in needles):
lo, hi = max(1, i-3), min(len(lines), i+5)
print(f"[{lo}-{hi}]")
for j in range(lo, hi+1):
print(f"{j}: {lines[j-1]}")
PYRepository: OpenMS/streamlit-template Length of output: 975 🏁 Script executed: set -o pipefail
printf '%s\n' '--- declared Streamlit dependency ---'
rg -n -i 'streamlit' requirements.txt pyproject.toml setup.cfg setup.py 2>/dev/null || true
for spec in \
'selectbox|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/widgets/selectbox.py' \
'multiselect|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/widgets/multiselect.py' \
'widgets|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/runtime/state/widgets.py'
do
name=${spec%%|*}
url=${spec#*|}
printf '%s\n' "--- $name ---"
curl -k -L --fail --silent "$url" |
nl -ba |
grep -E -C 5 'compute_and_register_element_id|register_widget|WidgetMetadata|index=|default=' |
head -180
doneRepository: OpenMS/streamlit-template Length of output: 1211 🏁 Script executed: for spec in \
'selectbox|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/widgets/selectbox.py' \
'multiselect|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/widgets/multiselect.py' \
'widgets|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/runtime/state/widgets.py'
do
name=${spec%%|*}
url=${spec#*|}
printf '%s\n' "--- $name ---"
curl -k -L --fail --silent "$url" |
grep -E -C 8 'compute_and_register_element_id|register_widget|WidgetMetadata|index=|default=' |
head -240
doneRepository: OpenMS/streamlit-template Length of output: 10485 🏁 Script executed: for spec in \
'utils|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/elements/lib/utils.py' \
'session_state|https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/runtime/state/session_state.py'
do
name=${spec%%|*}
url=${spec#*|}
printf '%s\n' "--- $name ---"
curl -k -L --fail --silent "$url" |
grep -E -C 12 'def compute_and_register_element_id|def compute_element_id|def register_widget|user_key|initial_value|deserial' |
head -360
doneRepository: OpenMS/streamlit-template Length of output: 19297 🏁 Script executed: curl -k -L --fail --silent \
https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/runtime/state/common.py |
grep -E -C 20 'class KeyIdMapper|def get_id_from_key|id_key_mapping|key_id_mapping'Repository: OpenMS/streamlit-template Length of output: 163 🏁 Script executed: curl -k -L --fail --silent \
https://raw.githubusercontent.com/streamlit/streamlit/1.49.1/lib/streamlit/runtime/state/session_state.py |
grep -E -C 25 'KeyIdMapper|id_key_mapping|get_id_from_key|key_id_mapping'Repository: OpenMS/streamlit-template Length of output: 12159 Preserve state when widget identity changes. In Streamlit 1.49.1, 🤖 Prompt for AI AgentsSource: MCP tools |
||
|
|
||
| if widget_type == "text": | ||
| st.text_input(name, value=value, key=key, help=help, on_change=on_change) | ||
| st.text_input(name, key=key, help=help, on_change=on_change, **seed(value=value)) | ||
|
|
||
| elif widget_type == "textarea": | ||
| st.text_area(name, value=value, key=key, help=help, on_change=on_change) | ||
| st.text_area(name, key=key, help=help, on_change=on_change, **seed(value=value)) | ||
|
|
||
| elif widget_type == "number": | ||
| number_type = float if isinstance(value, float) else int | ||
|
|
@@ -693,27 +705,27 @@ def format_files(input: Any) -> List[str]: | |
| name, | ||
| min_value=min_value, | ||
| max_value=max_value, | ||
| value=value, | ||
| step=step_size, | ||
| format=None, | ||
| key=key, | ||
| help=help, | ||
| on_change=on_change, | ||
| **seed(value=value), | ||
| ) | ||
|
|
||
| elif widget_type == "checkbox": | ||
| st.checkbox(name, value=value, key=key, help=help, on_change=on_change) | ||
| st.checkbox(name, key=key, help=help, on_change=on_change, **seed(value=value)) | ||
|
|
||
| elif widget_type == "selectbox": | ||
| if options is not None: | ||
| st.selectbox( | ||
| name, | ||
| options=options, | ||
| index=options.index(value) if value in options else 0, | ||
| key=key, | ||
| format_func=format_files, | ||
| help=help, | ||
| on_change=on_change, | ||
| **seed(index=options.index(value) if value in options else 0), | ||
| ) | ||
| else: | ||
| st.warning(f"Select widget '{name}' requires options parameter") | ||
|
|
@@ -723,11 +735,11 @@ def format_files(input: Any) -> List[str]: | |
| st.multiselect( | ||
| name, | ||
| options=options, | ||
| default=value, | ||
| key=key, | ||
| format_func=format_files, | ||
| help=help, | ||
| on_change=on_change, | ||
| **seed(default=value), | ||
| ) | ||
| else: | ||
| st.warning(f"Select widget '{name}' requires options parameter") | ||
|
|
@@ -744,25 +756,25 @@ def format_files(input: Any) -> List[str]: | |
| name, | ||
| min_value=min_value, | ||
| max_value=max_value, | ||
| value=value, | ||
| step=step_size, | ||
| key=key, | ||
| format=None, | ||
| help=help, | ||
| on_change=on_change, | ||
| **seed(value=value), | ||
| ) | ||
| else: | ||
| st.warning( | ||
| f"Slider widget '{name}' requires min_value and max_value parameters" | ||
| ) | ||
|
|
||
| elif widget_type == "password": | ||
| st.text_input(name, value=value, type="password", key=key, help=help, on_change=on_change) | ||
| st.text_input(name, type="password", key=key, help=help, on_change=on_change, **seed(value=value)) | ||
|
|
||
| elif widget_type == "auto": | ||
| # Auto-determine widget type based on value | ||
| if isinstance(value, bool): | ||
| st.checkbox(name, value=value, key=key, help=help, on_change=on_change) | ||
| st.checkbox(name, key=key, help=help, on_change=on_change, **seed(value=value)) | ||
| elif isinstance(value, (int, float)): | ||
| self._input_widget_impl( | ||
| key, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Sort the complete option list.
The current
sorted(...)call covers only files underpath. External paths are appended later without sorting. Mixed options can therefore depend on the order inexternal_files.txt, so the promised alphabetical display order is not guaranteed.Sort
optionsagain after adding external paths.Suggested fix
if external_files.exists(): with open(external_files, "r") as f: external_files_list = f.read().splitlines() options += [f for f in external_files_list if os.path.exists(f)] + options.sort()🤖 Prompt for AI Agents