I am trying to figure out how to have a global variable that isn’t 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’t 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’t 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.")
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.")
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.
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: