Multi-page app with session state

Hi @synode
Thanks for your kind. Now its done its working and maintaining the state too.

1 Like

Hi @synode
After updating to the latest Streamlit version, I’ve came across an error
ModuleNotFoundError: No module named 'streamlit.ReportThread'

Have you implemented anything new to overcome this? Thanks.

1 Like

Hello @vnguyendc,

I didn’t had time to check all the recent Streamlit changes, but with release 0.65 there was some code refactoring that broke session state implementations. I’ll work on it asap!

1 Like

Alright, I’ve made a quick (untested :scream:) fix. I’ve walked through the new code, and there was no major change in the parts I use. Some python files were renamed, and updating the imports should be enough.

Thanks. Yes it works on v0.65.


Off-topic:
Is there a way to persist the session state across refreshes?
Maybe something like getting the device fingerprint should do right, instead of session id?
Is there a way for that? Or can it be added?

If memory serves me correctly, there’s another implementation of state with a GlobalState that is maintained between refreshes.

1 Like

Great, thanks! That worked.
But it seems to be global for anyone accessing the page from anywhere.

This would be a good solution for that right?

It’s an interesting proposition…could you take this in another post on the forum so we don’t hijack this one too much :wink: ?

1 Like

I still get this error 'ModuleNotFoundError: No module named ‘streamlit.ReportThread’ after installing 0.65.0. Do i need to explicitly change anything in my code?

Hi @synode
Thanks for your sharing.
Can you share how can set up Session State for multi users working. As I use Session State of @thiago but session merge btw each users frequently

In my app, That’s embed in our ERP system, where one user click to Streamlit App, will have one auth_token in url para it’s user_id. But when other users come to same time, it’s will go to earler user session.

Do you have some advise for me?

I have been getting it aswell. Not sure how onerous it would be for your code, but you could always write the data using pickle and read it again when you need it at a later stage. Its how I over came this issue…not the most elegant solution but it worked…

Hmm, this is weird. Merging is not supposed to happen and may be related to another problem. Could you share some code reproducing the issue you’re encountering?

@fdubinski, @AteBitHavoc,

Are you still encountering this error after the fix I’ve made recently? Could you post your traceback error to check where the exception is raised?


Hi! Thanks for your precious work. I have this problem: inside this for loop it seems that the session state is not working because it cannot save the values if i turn back on this page after i move to another page.
Do you know why?

Hello @davins90,

Does it raise any exception, or it just fails to save your values silently?

Hey @synode,

thanks for the reply. It just fails to save the results, unfortunately. It works on a single “number_input/selectboc/ecc…” but in this for cycle, it doesn’t. If i move forward on the page of the apps i see the values insert, but if i turning back to the page in which I’ve inserted the values, they disappear. I hope to have explained in a good way my situation.
Thanks

Thanks @synode for the nice demo!

I am running into issues when having different state variables depend on each other. Maybe it’s just a logic error. What I am trying to do is to not have state.input depend on state.selectbox if text is written into the text_input. If a different choice is selected, then state.input should depend on it again. Additionally, a function call should update state.input every time it is called.

My example is the same as your gist, but I changed the main() and page_settings() functions. Here is my code:

import streamlit as st
from streamlit.hashing import _CodeHasher

try:
    # Before Streamlit 0.65
    from streamlit.ReportThread import get_report_ctx
    from streamlit.server.Server import Server
except ModuleNotFoundError:
    # After Streamlit 0.65
    from streamlit.report_thread import get_report_ctx
    from streamlit.server.server import Server


def main():
    state = _get_state()
    pages = {
        "Dashboard": page_dashboard,
        "Settings": page_settings,
    }

    st.sidebar.title(":floppy_disk: Page states")
    page = st.sidebar.radio("Select your page", tuple(pages.keys()))

    # Display the selected page with the session state
    pages[page](state)

    # Mandatory to avoid rollbacks with widgets, must be called at the end of your app
    state.sync()


def page_dashboard(state):
    st.title(":chart_with_upwards_trend: Dashboard page")
    display_state_values(state)


def page_settings(state):
    st.title(":wrench: Settings")
    display_state_values(state)

    st.write("---")
    options = ["", "Hello", "World", "Goodbye"]
    state.selectbox = st.selectbox("Select value.", options, options.index(state.selectbox) if state.selectbox else 0)
    state.input = st.text_input("Set input value.", state.input or "")
    state.input = state.selectbox

    state.function = st.button("Function")
    if state.function:
        state.input = state.input + " function"
        print("Press function")


def display_state_values(state):
    st.write("Selectbox state:", state.selectbox)
    st.write("Input state:", state.input)

    if st.button("Clear state"):
        state.clear()


class _SessionState:

    def __init__(self, session, hash_funcs):
        """Initialize SessionState instance."""
        self.__dict__["_state"] = {
            "data": {},
            "hash": None,
            "hasher": _CodeHasher(hash_funcs),
            "is_rerun": False,
            "session": session,
        }

    def __call__(self, **kwargs):
        """Initialize state data once."""
        for item, value in kwargs.items():
            if item not in self._state["data"]:
                self._state["data"][item] = value

    def __getitem__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)
        
    def __getattr__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)

    def __setitem__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value

    def __setattr__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value
    
    def clear(self):
        """Clear session state and request a rerun."""
        self._state["data"].clear()
        self._state["session"].request_rerun()
    
    def sync(self):
        """Rerun the app with all state values up to date from the beginning to fix rollbacks."""

        # Ensure to rerun only once to avoid infinite loops
        # caused by a constantly changing state value at each run.
        #
        # Example: state.value += 1
        if self._state["is_rerun"]:
            self._state["is_rerun"] = False
        
        elif self._state["hash"] is not None:
            if self._state["hash"] != self._state["hasher"].to_bytes(self._state["data"], None):
                self._state["is_rerun"] = True
                self._state["session"].request_rerun()

        self._state["hash"] = self._state["hasher"].to_bytes(self._state["data"], None)


def _get_session():
    session_id = get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError("Couldn't get your Streamlit Session object.")
    
    return session_info.session


def _get_state(hash_funcs=None):
    session = _get_session()

    if not hasattr(session, "_custom_session_state"):
        session._custom_session_state = _SessionState(session, hash_funcs)

    return session._custom_session_state


if __name__ == "__main__":
    main()

Is it possible to implement session state with st.button? It seems to be the only widget that is not supported with this framework at the moment.

Hello @e-tony, welcome to the forum!

Indeed, this is just a matter of logic. Are you expecting something like this? (I’ve removed pages)

from streamlit.hashing import _CodeHasher
from streamlit.report_thread import get_report_ctx
from streamlit.server.server import Server
from random import choices
from string import ascii_letters
import streamlit as st


DATES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]


def main():
    state = _get_state()

    st.write("**Current state:**", state.input)

    predef = st.selectbox("Predefined input", options=DATES)
    custom = st.text_input("Custom input")
    random = "".join(choices(ascii_letters, k=10))

    # This can be a function which changes state.input
    if st.button("Random input"):
        state.input = random

    # Here we check if text input is empty or not
    elif custom:
        state.input = custom

    # If we didn't call our function, and text input is empty, we use predefined choices
    else:
        state.input = predef

    # Mandatory to avoid rollbacks with widgets, must be called at the end of your app
    state.sync()


class _SessionState:

    def __init__(self, session, hash_funcs):
        """Initialize SessionState instance."""
        self.__dict__["_state"] = {
            "data": {},
            "hash": None,
            "hasher": _CodeHasher(hash_funcs),
            "is_rerun": False,
            "session": session,
        }

    def __call__(self, **kwargs):
        """Initialize state data once."""
        for item, value in kwargs.items():
            if item not in self._state["data"]:
                self._state["data"][item] = value

    def __getitem__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)
        
    def __getattr__(self, item):
        """Return a saved state value, None if item is undefined."""
        return self._state["data"].get(item, None)

    def __setitem__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value

    def __setattr__(self, item, value):
        """Set state value."""
        self._state["data"][item] = value
    
    def clear(self):
        """Clear session state and request a rerun."""
        self._state["data"].clear()
        self._state["session"].request_rerun()
    
    def sync(self):
        """Rerun the app with all state values up to date from the beginning to fix rollbacks."""

        # Ensure to rerun only once to avoid infinite loops
        # caused by a constantly changing state value at each run.
        #
        # Example: state.value += 1
        if self._state["is_rerun"]:
            self._state["is_rerun"] = False
        
        elif self._state["hash"] is not None:
            if self._state["hash"] != self._state["hasher"].to_bytes(self._state["data"], None):
                self._state["is_rerun"] = True
                self._state["session"].request_rerun()

        self._state["hash"] = self._state["hasher"].to_bytes(self._state["data"], None)


def _get_session():
    session_id = get_report_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)

    if session_info is None:
        raise RuntimeError("Couldn't get your Streamlit Session object.")
    
    return session_info.session


def _get_state(hash_funcs=None):
    session = _get_session()

    if not hasattr(session, "_custom_session_state"):
        session._custom_session_state = _SessionState(session, hash_funcs)

    return session._custom_session_state


if __name__ == "__main__":
    main()

Hello @mkhorasani,

The persisting equivalent to buttons are checkboxes IMO. And buttons doesn’t take a value parameter.
How do you imagine (or plan to use) session state with buttons?