Programatically setting widget values and bookmarking app state

Hi all,

I ran into some trouble with getting and setting query parameters yesterday, but found a solution which I would like to share. It relies on a few of the well-known hacks, and allows the definition of predefined states accessible by the push of a button. In addition, it showcases how the experimental get and set query parameters can be used, inspired by this comment. Hope the snippet is useful for some people :slight_smile:

import streamlit as st
import session_state
from streamlit.script_runner import RerunException
from streamlit.script_request_queue import RerunData

query_params = st.experimental_get_query_params()

state = session_state.get(
    session_id=0, first_query_params=st.experimental_get_query_params()
)
first_query_params = state.first_query_params

default_values = {
    "my_checkbox": int(state.first_query_params.get("my_checkbox", [0])[0]),
    "my_radio_button": int(state.first_query_params.get("my_radio_button", [2])[0]),
    "my_slider": int(state.first_query_params.get("my_slider", [1])[0]),
    "my_slider_with_two_values": (
        int(state.first_query_params.get("my_slider_with_two_values_min", [5])[0]),
        int(state.first_query_params.get("my_slider_with_two_values_max", [100])[0]),
    ),
    "my_multiselect": state.first_query_params.get("my_multiselect", ["Option 1"]),
}

my_checkbox = st.checkbox(
    "Tick me!", key=state.session_id, value=default_values["my_checkbox"]
)
query_params["my_checkbox"] = int(my_checkbox)

my_radio_options = ["Eat", "Sleep", "Both"]

my_radio_button = st.radio(
    "Options",
    my_radio_options,
    key=state.session_id,
    index=default_values["my_radio_button"],
)
query_params["my_radio_button"] = my_radio_options.index(my_radio_button)

my_slider = st.slider(
    "Slider",
    min_value=0,
    max_value=200,
    key=state.session_id,
    value=default_values["my_slider"],
)
query_params["my_slider"] = my_slider

my_slider_with_two_values = st.slider(
    "Slider with two values",
    min_value=0,
    max_value=100,
    key=state.session_id,
    value=default_values["my_slider_with_two_values"],
)
query_params["my_slider_with_two_values_min"] = my_slider_with_two_values[0]
query_params["my_slider_with_two_values_max"] = my_slider_with_two_values[1]

my_multiselect = st.multiselect(
    "Select options!",
    ["Option 1", "Option 2", "Option 3"],
    key=state.session_id,
    default=default_values["my_multiselect"],
)
query_params["my_multiselect"] = my_multiselect

if st.sidebar.button("Set predefined filtering"):
    state.first_query_params = dict(
        my_checkbox=[1],
        my_radio_button=[0],
        my_slider_with_two_values_min=[15],
        my_multiselect=["Option 2"],
    )
    state.session_id += 1
    raise RerunException(RerunData())

st.experimental_set_query_params(**query_params)
7 Likes

Hey can you please explain what RerunException(RerunData()) is using and when I do just st.experimental_rerun() , for some reason the queryparam dictionary is becoming {} , can you please help me

My requirement is when the user click some button , I should update the query parameters and rerun the script from start using the updated query param list

Hi Meesa, welcome to the community!

Could you share some sample code, perhaps that makes it easier to help? :slight_smile: I think the code above should be able to meet your requirements, but perhaps it needs some small adjustments.

I do not think there is a difference between RerunException(RerunData()) and st.experimental_rerun(), you can see in the streamlit source code that experimental rerun is just a prettier wrapper of the same code.

Best,
Peter

Hi Peter,

I figured out the solution with the help of your code, I have a doubt regarding scalability of solution like.,as we are keeping key= session_id and session_id being incremented when user clicks some button , so now number of keys just keep on growing and will we be running into some performance issues?

Hi Meesa,

Good question! Iā€™m not sure how Streamlit handles it internally when new keys are generated and the old widgets are no longer used. Iā€™ve not experienced problems thus far, but it may be a good idea to do some kind of testing on thisā€¦ Sorry to not be of more help.

Best,
Peter

Yeah Peter when new widgets are been used streamlit is removing old ones , so instead of incrementing session id I am making it 0,1 alternatively and itā€™s working fine.

thanks,
Shivaram

Hi Peter ,

this state variable is getting shared across two different application instances , I tried running my app in two different tabs of my machine and the state variable is behaving as a common variable to both the tabs like itā€™s getting overwritten by other tabs .

Let say I incremented state.id in 1 tab , the change is getting reflected in other tab , Can you suggest me a way to keep this state variable as local to particular user .

My use case is I want my app to be used by multiple users at same time.

thanks,
Shivaram

Hi Shivaram,

I think the state will not be shared if you e.g. use different browsers or different machines. So it should be possible for several users to use the app at a time as long as theyā€™re not all using the same machine :slight_smile:

Hey Peter ,

So I found a solution for this

import streamlit.report_thread as ReportThread
from streamlit.server.server import Server


class SessionState(object):
    def __init__(self, **kwargs):
        for key, val in kwargs.items():
            setattr(self, key, val)


def get(**kwargs):

    ctx = ReportThread.get_report_ctx()
    current_server = Server.get_current()

    if not hasattr(current_server, 'my_state'):
        current_server.my_state = {}

    if str(ctx.session_id) not in current_server.my_state.keys():
        current_server.my_state[str(ctx.session_id)] = SessionState(**kwargs)

    return current_server.my_state[str(ctx.session_id)]

So when I am creating from different browsers I am getting different ctx.session_id , and as current_server is fixed for this Server , so I just added a new attribute my state which is a dictionary with keys as ctx.session_id and values are the state objects , so by this code I can keep different states for different browsers , itā€™s working though , is this correct way of doing things or will I get issues in future?

Hi Shivaram,

If it seems to be working for you, I guess it is ok. To be honest, I donā€™t think Iā€™m the right person to answer thisā€¦
What I can say though, is that Streamlit are quite soon releasing integrated session_state so we do not have to rely on this ā€˜hackā€™ with the session_state.py file anymore and I think it will be beneficial to switch to this official implementation once it is released. With this, there are also possibilities to provide widget callbacks which I think will be very handy for managing query parameters. Iā€™m not employed by Streamlit, so I cannot give a timeline on when its out, but looking at the heavy activity in the PRs I assume it will be released within a month or two.

Is there any hack to synchronize multiple people writing into same file in streamlit , I want to do some thing like lock() unlock() before everythread accessing the shared resource so that one thread waits for the other.

Iā€™m not sure - it sounds like it will be a bit difficult, but I can see you made another thread, lets hope someone can help you there. My intuition would say that you probably need to write to a database instead, but I do not know the usecase.

Hey can u see my solution on other thread? I feel itā€™s correct only!!

@PeterT @Meesa_Shivaram_Prasa I made a library of url-aware widgets that gives you the ā€œbookmarking app stateā€ functionality for free: streamlit-permalink