Triggering fragment rerun from full scope rerun

Hello,

Here is my use case :
In my app I want the user to be able to track the status of a thing. To do this I have a fragment that makes an api call and displays the result on screen, at first I want that fragment to refresh quickly, let’s say once a second, but then I want to strech the refresh time so I don’t get ten thousand API calls because someone left their page open.

At first I tried to make this happen with st.fragment(run_every=something) The issue here is that I am unable to change the “something” from within the fragment so I am locked to a single refersh rate.

My second idea was to do something like this :

import streamlit as st
import time

def main():
    # Reset refresh rate
    st.session_state.refresh_rate = 1
    refreshing_subpage()

@st.fragment
def refreshing_subpage():
    data = "some_api_call"
    st.write(data)

    time.sleep(st.session_state.refresh_rate)
    st.session_state.refresh_rate += 1
    st.rerun(scope="fragment")

if __name__ == "__main__":
    main()

But with this I get :

streamlit.errors.StreamlitAPIException: scope="fragment" can only be specified from `@st.fragment`-decorated functions during fragment reruns.

My understanding is that this issue arises because when my fragment first runs it does so from a full page rerun, not a fragment rerun. This means I need to be in a fragment rerun to trigger a fragment rerun which kinda creates a chicken and egg problem.

The only other ways I know to enter a fragment rerun are the run_every parameter talked about earlier and user interaction. Since I don’t really want to have a big red button inside the fragment that says “CLICK ME PLEASE MY CODE WON’T WORK WHITOUT YOUR HELP” I tried other solutions.

I briefly considered having an invisible button that would get triggered by javascript to trigger the fragment rerun but that’s a bit too janky even for me. Ultimately what I ended up with is a having two different version of my display function, one decorated with a run_every and one whithout. The decorated version keeps track of the number of second passed and when it goes above x it trigger a full rerun that brings the user to the non-decorated non-refreshing version.

This is an imperfect and pretty janky solution.
I would love to know if there is a better way and if this is how rerun(scope=“fragment”) is intended to work.

Thank you in advance for your help.

Welcome to the community and thanks for the detailed question! :balloon: You’re right: the run_every parameter of @st.fragment is set at function definition time and can’t be dynamically changed from within the fragment itself. Also, as you’ve discovered, st.rerun(scope="fragment") can only be called during a fragment rerun, not from a full-script rerun, which makes adaptive refresh rates tricky to implement in a single fragment function.

The recommended approach is to control the refresh interval outside the fragment and pass it as the run_every parameter when defining the fragment. To dynamically adjust the refresh rate, you need to re-define the fragment with the new interval on each full-script rerun. This is shown in the official tutorial and discussed in the fragment docs. There is currently no built-in way to change run_every from inside the fragment or during a fragment rerun—your workaround (switching between two versions of the function) is a common pattern, though not ideal.

If you’d like a step-by-step example or want to discuss alternative patterns (like using a callback or session state to trigger a full rerun and redefine the fragment), let me know! And if anyone in the community has a clever hack, please jump in and share your insights. :blush:

Sources:

I think the simpler solution would be for rerun(scope=”fragment”) run from a full-script rerun to simply initiate a fragment rerun the same way it would if a user had cliked an empty button inside the fragment.
Intuitively it seems like how you would expect it to work and it would leave the user free to control the refresh rate manualy from within the fragment.
Maybe there is technical limitation or reason to want to throw an error in this case that I am not seeing ?