Issue with switching to pages upon creation

I’m creating a multipage app where pages are dynamically created then navigated to with page_switch but Streamlit refuses to navigate to the page, saying it does not yet exist despite Python signaling the page exists and its content has finished being written. There seems to be a buffer between when a page is ready to be read by Python versus displayed with Streamlit. Is there a way to signal when Streamlit is ready to display the page to avoid errors?

Here’s the error Streamlit displays when creating a page called Star Wars:

FileNotFoundError: [Errno 2] No such file or directory: 'pages/StarWars.py'
Traceback:
File "/usr/local/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 88, in exec_func_with_error_handling
    result = func()
             ^^^^^^
File "/usr/local/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 579, in code_to_exec
    exec(code, module.__dict__)
File "/app/app.py", line 133, in <module>
    main()
File "/app/app.py", line 130, in main
    create_new_page(query)
File "/app/app.py", line 92, in create_new_page
    Path(file_path).touch()
File "/usr/local/lib/python3.12/pathlib.py", line 1303, in touch
    fd = os.open(self, flags, mode)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^

Here’s my current approach to the problem –focusing on file size stabilization– which doesn’t work. The page_template function with its arguments returns Python code for a Streamlit page:

# Create new file & write the page content to the file
    Path(file_path).touch()
    with open(file_path, "w") as file:
        page_content = get_template(title=page_title, search_query=search_query)
        file.write(page_content)
    
    # Wait until the file is populated
    start_time = time.time()
    last_size = -1

    while True:
        # Check if the file exists
        if os.path.exists(file_path):
            # Get the current file size
            current_size = os.path.getsize(file_path)

            # If size hasn't changed for two consecutive checks, assume it's ready
            if current_size == last_size:
                st.switch_page(file_path)
            last_size = current_size
        else:
            # File doesn't exist yet, continue waiting
            pass

        # Timeout handling
        if time.time() - start_time > 30:
            raise TimeoutError(f"Request Timeout: File {file_path} did not stabilize within 30 seconds. Try again.")

        time.sleep(0.1)

Locally, I’ve gotten around this by adding a time.sleep() for 3 seconds after the file size has stabilized (not ideal and very scrappy) but in production this fails and I get the original error. Anything less than a 3 second wait is spotty at best and fails most of the time. This error affects all newly created pages.

Has anyone encountered similar race conditions when creating and navigating to dynamically generated pages? Any suggestions would be super helpful. Happy to share more of my code too. Thanks everyone

Streamlit uses threading to handle sessions. There’s a main thread running the server, which includes the file watcher. Each rerun of each session runs on a separate script thread. The st.switch_page function matches the referenced page to registered pages of the app and doesn’t do a fresh lookup of files in the directory. It’s the main thread that watches and registers the new file as a page. You’ll need to wait for that to happen before you attempt to switch to it.

Warning:

When you dynamically add a page to the pages directory, it will show up for all users. So this kind of page handling won’t be restricted to the user’s session. You might want to consider using st.navigation. You’d need to rerun the app if st.navigation appears before the page creation, but at least you’d have a specific command executed in the session to register the page so you aren’t stuck waiting for the file watcher to respond. This would also allow pages registered only to one session, if that’s relevant.

If I pull out some of the code from st.switch_page to check if the page is registered before switching (and retrying if it’s not), I can see the app make it through a few thousand loops before the main thread completes the registration. (I’d recommend using a time.sleep but you might be able to trim it down if you build in retries with something like this, or with try-blocks.)

import streamlit as st
from streamlit.runtime.scriptrunner import get_script_run_ctx
from streamlit.file_util import get_main_script_directory, normalize_path_join
import os
import time

def page_list():
    ctx = get_script_run_ctx()
    all_app_pages = ctx.pages_manager.get_pages().values()
    return [p["script_path"] for p in all_app_pages]

def page_path(page):
    ctx = get_script_run_ctx()
    main_script_directory = get_main_script_directory(ctx.main_script_path)
    return os.path.realpath(
        normalize_path_join(main_script_directory, page)
    )

def create_page(code):
    with open("pages/page.py", "w") as f:
        f.write(code)

def del_page():
    page = "pages/page.py"
    if os.path.exists(page):
        os.remove(page)

CODE = """
import streamlit as st

st.title("Page")
st.session_state.count
"""

if st.button("Create page"):
    create_page(CODE)
    page = "pages/page.py"
    st.session_state.count = 0
    while True:
        requested_page = page_path(page)
        all_page_names = page_list()
        if requested_page in all_page_names:
            st.switch_page(requested_page)
        st.session_state.count += 1
        # time.sleep(.05)

if st.button("Del page"):
    del_page()

Just implemented these changes and this works, thanks!

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