I was trying to get something with a stream working in streamlit. ( no pun intended )
I tried a couple of approaches with threading and finished my search on asyncio.run with a small coroutine and for the most part it works really well. No need for any fancy code just a asyncio.run at the end. But there’s a small problem, I am seeing None printed at every run of loop.
Any idea what might be causing this ?
This is my script I am using to show a simple clock
import asyncio
import streamlit as st
from datetime import datetime
st.set_page_config(layout="wide")
st.markdown(
"""
<style>
.time {
font-size: 130px !important;
font-weight: 700 !important;
color: #ec5953 !important;
}
</style>
""",
unsafe_allow_html=True
)
async def watch(test):
while True:
test.markdown(
f"""
<p class="time">
{str(datetime.now())}
</p>
""", unsafe_allow_html=True)
await asyncio.sleep(1)
test = st.empty()
if st.button("Click me."):
st.image("https://cdn11.bigcommerce.com/s-7va6f0fjxr/images/stencil/1280x1280/products/40655/56894/Jdm-Decals-Like-A-Boss-Meme-Jdm-Decal-Sticker-Vinyl-Decal-Sticker__31547.1506197439.jpg?c=2", width=200)
asyncio.run(watch(test))
I’m pretty sure we got this problem before when building a realtime monitoring solution, but I don’t clearly remember I’d have to go back into old code …
I recall we added a rerun somewhere and maybe an empty, like:
import asyncio
import streamlit as st
from datetime import datetime
st.set_page_config(layout="wide")
st.markdown(
"""
<style>
.time {
font-size: 130px !important;
font-weight: 700 !important;
color: #ec5953 !important;
}
</style>
""",
unsafe_allow_html=True
)
async def watch(t: st._DeltaGenerator):
while True:
t.markdown(
f"""
<p class="time">
{str(datetime.now())}
</p>
""", unsafe_allow_html=True)
await asyncio.sleep(1)
st.experimental_rerun() # <-- here
test = st.empty()
asyncio.run(watch(test))
Yep I thought of something similar but the issue is if you see my script I have some widgets after the empty and if I add a re-run they will keep refreshing as well
Aww yeah in my case it was the very last widget I remember
Unfortunately the code is buried into company internal code. I’ll have to dig into it…
While I will continue following your debugging for an asyncio solution (too much FastAPI recently, so getting to learn asyncio too ), I think this has a solution using Threading and injecting the ReportContext info in the new thread here and here. Maybe we have a kinda similar issue with asyncio, where t has 2 references, one in the Streamlit thread and one in the asyncio thread, and one of them is lost and returns None… or the t.markdown() reruns the asyncio.run and this breaks things ? something like that… ?
sorry this is all brainstorming without testing and I’m writing a HDP tutorial at the same time
@tim@thiago maybe you have some tips on this, how ReportContext, threads, the Tornado event loop and an external asyncio loop would all run together when t.markdown is called ?
@ash2shukla this is even better, well time to dig into what running a co-routines really means and if it tries to build a Streamlit container in the wrong place
import asyncio
import streamlit as st
async def periodic():
while True:
st.write("Hello world")
await asyncio.sleep(1)
await asyncio.sleep(1)
await asyncio.sleep(1)
asyncio.run(periodic())
…so I guess the None is actually the result of await asyncio.sleep(0) which Streamlit naturally writes in Streamlit like when you just write "Hello World" in a Python script.
import asyncio
import streamlit as st
async def periodic():
while True:
st.write("Hello world")
r = await asyncio.sleep(1)
st.write(f"asyncio sleep ? {r}")
asyncio.run(periodic())
So back to your code
import asyncio
import streamlit as st
from datetime import datetime
st.set_page_config(layout="wide")
st.markdown(
"""
<style>
.time {
font-size: 130px !important;
font-weight: 700 !important;
color: #ec5953 !important;
}
</style>
""",
unsafe_allow_html=True
)
async def watch(test):
while True:
test.markdown(
f"""
<p class="time">
{str(datetime.now())}
</p>
""", unsafe_allow_html=True)
r = await asyncio.sleep(1)
test = st.empty()
if st.button("Click me."):
st.image("https://cdn11.bigcommerce.com/s-7va6f0fjxr/images/stencil/1280x1280/products/40655/56894/Jdm-Decals-Like-A-Boss-Meme-Jdm-Decal-Sticker-Vinyl-Decal-Sticker__31547.1506197439.jpg?c=2", width=200)
asyncio.run(watch(test))
Dang now I badly want to try this for realtime monitoring! But I really need to finish this Hortonworks university tutorial, so I expect a full demo and a blogpost from you this weekend
Hi, I’m wondering if you could assist with implementing your code for a timedelta countdown. The countdown works but reruns with every widget interaction. Thanks!
H = 11
M = 32
S = 32
async def watch(test):
while True:
t2 = timedelta(hours=H, minutes=M, seconds=S)
while t2 > timedelta(hours=0, minutes=0, seconds=0):
t2 -= timedelta(seconds=1)
test.markdown(
f"""
<p class="time">
{t2}
</p>
""", unsafe_allow_html=True)
r = await asyncio.sleep(1)
test = st.empty()
number_input = st.number_input('Test', key='Test')
asyncio.run(watch(test))
One way to do this is to protect your initialization with st.session_state. Something like…
if 'timer' not in st.session_state:
st.session_state.timer = (11, 32, 32)
async def watch(test):
while True:
H, M, S = st.session_state.timer
t2 = timedelta(hours=H, minutes=M, seconds=S)
...