How to have global variable outside of session state?

I am using Python 3.11 with Streamlit 1.28.0.

Coming from st.experimental_rerun() raises exception if run in thread 路 Issue #5588 路 streamlit/streamlit 路 GitHub (reposted in Changing session state not reflecting in active python thread - #2 by Caroline), the conclusion is to have a global shared queue that is processed by Streamlit in a main thread.

I am trying to figure out how to have a global variable that isn鈥檛 overwritten by Streamlit on page refresh. From How to use global variable in streamlib? - #2 by ferdy, it seems session_state is the only way to accomplish this.

So my question is, how can one have a global variable in a Python script, outside of session_state, that isn鈥檛 overwritten upon refreshing Streamlit?


I guess one solution is using something entirely outside of a Python process, like the OS filesystem, reading/writing from a file. Basically, Socket.IO events write to the file, Streamlit reads from the file.

Anything in a module other than your main script won鈥檛 be overwritten by reruns or refreshes. So just put the queue in a separate module

Here is an example monkey-patching streamlit (not that I recommend it, but it makes it easier to demonstrate). The queue will will be shared across sessions and it will maintain its state through reruns and refreshes. The usual caveats about sharing mutable global state in multi-threading code apply. Appending to and popping from a deque are safe operations according to the docs.

import streamlit as st
from collections import deque 

if not hasattr(st, "app_queue"):
    st.app_queue = deque()

element = st.text_input("Value")

if st.button("Add to app queue"):
    st.app_queue.append(element)
    st.info(f"\"{element}\" added to the queue.")

if st.button("Pop from app queue"):
    try:
        element = st.app_queue.popleft()
    except IndexError:
        st.error("Queue is empty.")
    else:
        st.info(f"\"{element}\" popped from the queue.")
1 Like

I just realized that the idiomatic way of having app-level mutable state in streamlit apps is using st.cache_resource.

import streamlit as st
from collections import deque 


@st.cache_resource
def get_app_queue():
    return deque()


app_queue = get_app_queue()

element = st.text_input("Value")

if st.button("Add to app queue"):
    app_queue.append(element)
    st.info(f"\"{element}\" added to the queue.")

if st.button("Pop from app queue"):
    try:
        element = app_queue.popleft()
    except IndexError:
        st.error("Queue is empty.")
    else:
        st.info(f"\"{element}\" popped from the queue.")
1 Like

Firstly, thank you for replying! This really unblocks me, I was frustrated at using the filesystem as a way of sharing state.

To follow up on your second post, using st.cache_resource doesn鈥檛 seem to support multithreading via python-socketio:

import socketio
import streamlit as st

sio_client = socketio.Client()

@st.cache_resource
def get_app_queue() -> list[str]:
    return []

@sio_client.event
def log(msg: str) -> None:
    get_app_queue().append(msg)

sio_client.connect(API_URL, socketio_path="/ws/socket.io")

Running this throws a bunch of errors:

2023-11-07 11:09:50.896 Thread 'Thread-6 (_handle_eio_message)': missing ScriptRunContext
2023-11-07 11:09:50.896 Thread 'Thread-6 (_handle_eio_message)': missing ScriptRunContext
2023-11-07 11:09:50.897 Thread 'Thread-6 (_handle_eio_message)': missing ScriptRunContext
2023-11-07 11:09:50.899 Thread 'Thread-6 (_handle_eio_message)': missing ScriptRunContext

So whereas st.cache_resource may be the idiomatic way, it doesn鈥檛 work for multithreading (as of Streamlit 1.28.1).

Anything in a module other than your main script won鈥檛 be overwritten by reruns or refreshes. So just put the queue in a separate module

Do you know where this is documented? Any chance you can link me to the docs in https://docs.streamlit.io/ for this?

I learned (not from the docs but from a post in the forum that I cannot find right now) that each instance of a streamlit app runs in a single process. Each session runs in a separate thread of the same process.

So reruns and refreshes just kill and create threads, but module-level variables are not affected by it.

1 Like

I have seen messages like these reported before, but I have little experience with multithreadng, specially in streamlit apps. There is this open issue that might help:

I guess calling a cached function from another thread is an instance of 鈥渋ssuing Streamlit commands from other threads鈥 and you should be using a global variable instead.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.