/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
e2e_playwright/st_multiselect.py
251 строка
7 KB
Lukas Masuch
Fix selectbox and multiselect clearing selections for custom objects (#13648)
22 янв 2026, 21:32
Не верифицирован
22 янв 2026, 21:32
8150720
Код
Авторство
О чём код?
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations from typing import Any import streamlit as st from streamlit import runtime def set_multiselect_9_to_have_bad_state(): if "multiselect 9" in st.session_state: st.session_state["multiselect 9"] = ["male", "female"] options = ("male", "female") i1 = st.multiselect( "multiselect 1", options, placeholder="Please select", help="Help text" ) st.text(f"value 1: {i1}") i2 = st.multiselect("multiselect 2", options, format_func=lambda x: x.capitalize()) st.text(f"value 2: {i2}") i3: list[Any] = st.multiselect("multiselect 3", []) st.text(f"value 3: {i3}") i4 = st.multiselect("multiselect 4", ["coffee", "tea", "water"], ["tea", "water"]) st.text(f"value 4: {i4}") i5 = st.multiselect( "multiselect 5", [ f"{x} I am a ridiculously long string to have in a multiselect, " "so perhaps I should just not wrap and go to the next line." for x in range(5) ], ) st.text(f"value 5: {i5}") i6 = st.multiselect("multiselect 6", options, disabled=True) st.text(f"value 6: {i6}") i7 = st.multiselect("Hidden label", options, label_visibility="hidden") st.text(f"value 7: {i7}") i8 = st.multiselect("Collapsed label", options, label_visibility="collapsed") st.text(f"value 8: {i8}") set_multiselect_9 = st.checkbox( "set_multiselect_9", on_change=set_multiselect_9_to_have_bad_state ) i9 = st.multiselect("multiselect 9", options, max_selections=1, key="multiselect 9") st.text(f"value 9: {i9}") with st.form("my_max_selections_ms_in_form"): i10 = st.multiselect( "multiselect 10", options, max_selections=1, key="multiselect 10" ) st.text(f"value 10: {i10}") submitted = st.form_submit_button("Submit") if runtime.exists(): def on_change(): st.session_state.multiselect_changed = True st.multiselect("multiselect 11", options, key="multiselect11", on_change=on_change) st.text(f"value 11: {st.session_state.multiselect11}") st.text(f"multiselect changed: {'multiselect_changed' in st.session_state}") multiple_cols = st.columns(5) i12 = multiple_cols[0].multiselect( "multiselect 12", ["A long option"], default="A long option" ) st.text(f"value 12: {i12}") st.multiselect( "multiselect 13 -> :material/check: :rainbow[Fancy] _**markdown** `label` _support_", options=options, ) # Add new multiselect with accept_new_options=True and verify that the format_func # is applied to the original options but not to the new options i14 = st.multiselect( "multiselect 14 - accept new options", options=["apple", "banana", "orange", "kiwi"], accept_new_options=True, max_selections=3, format_func=lambda x: x.upper(), ) st.text(f"value 14: {i14}") # Add a multiselect with session_state pre-set value if "multiselect15" not in st.session_state: st.session_state.multiselect15 = ["apple", "orange"] i15 = st.multiselect( "multiselect 15 - session_state values", options=["apple", "banana", "orange", "kiwi"], key="multiselect15", ) st.text(f"value 15: {i15}") # Add a multiselect with empty options but accept_new_options=True i16 = st.multiselect( "multiselect 16 - empty options with accept_new_options", options=[], accept_new_options=True, ) st.text(f"value 16: {i16}") many_options = ( "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty", "twenty-one", "twenty-two", "twenty-three", "twenty-four", "twenty-five", "twenty-six", "twenty-seven", "twenty-eight", "twenty-nine", "thirty", ) st.multiselect( "multiselect 17 - show maxHeight", options=many_options, default=many_options[0:28], ) st.multiselect( "multiselect 18 (width=300px)", many_options, default=many_options[0:28], width=300 ) st.multiselect( "multiselect 19 (width='stretch')", many_options, default=many_options[0:28], width="stretch", ) if st.toggle("Update multiselect props"): ms_value = st.multiselect( "Updated dynamic multiselect", default=[], width=300, help="updated help", key="dynamic_multiselect_with_key", on_change=lambda a, param: print( f"Updated multiselect - callback triggered: {a} {param}" ), args=("Updated ms arg",), kwargs={"param": "updated kwarg param"}, placeholder="updated placeholder", options=["mango", "papaya", "grape", "apple"], # Whitelisted kwargs (keep stable): max_selections=3, accept_new_options=False, # format_func is not whitelisted. Changing format_func is allowed, # but selected options will be unselected if their formatted label # no longer matches (e.g., "Apple" vs "APPLE"). This is something # we might be able to support with some additional refactorings. format_func=lambda x: x.capitalize(), ) st.write("Updated multiselect value:", str(ms_value)) else: sms_value = st.multiselect( "Initial dynamic multiselect", default=["apple"], width="stretch", help="initial help", key="dynamic_multiselect_with_key", on_change=lambda a, param: print( f"Initial multiselect - callback triggered: {a} {param}" ), args=("Initial ms arg",), kwargs={"param": "initial kwarg param"}, placeholder="initial placeholder", options=["apple", "banana", "mango", "orange"], # Whitelisted kwargs (keep stable): max_selections=3, accept_new_options=False, format_func=lambda x: x.capitalize(), ) st.write("Initial multiselect value:", str(sms_value)) # Test for issue #13646: Custom class objects without __eq__ should work with format_func # This tests that selections are preserved for custom class objects after script reruns # when the widget uses a format_func to display the options. class CustomOption: # noqa: B903 """Custom class without __eq__ implementation. This simulates the common pattern where users have custom objects with a format_func that extracts a display string, but the class itself doesn't implement __eq__ for value comparison. """ def __init__(self, value: str, label: str): self.value = value self.label = label # Create new options on each script run (simulating the behavior that triggers the bug) custom_options_20 = [ CustomOption("opt_a", "Option A"), CustomOption("opt_b", "Option B"), CustomOption("opt_c", "Option C"), ] i20 = st.multiselect( "multiselect 20 - custom objects", options=custom_options_20, format_func=lambda x: x.label, key="multiselect_custom_objects", ) st.text(f"value 20: {[opt.value for opt in i20]}")