Simultaneous multipages (new API), widget persistence, duplicate widgets, multiprocessing, robust data editors

This is an update to my previous post in case people would like similar functionality, summarized in the title of this post and detailed in the comments of the full code example below:

# app.py

import streamlit as st
import one
import two
import three

def main():

    # Use the new st.naviation()/st.Page() API to create a multi-page app
    pg = st.navigation({
        'first section':
            [st.Page(one.main, title="Home", url_path='home'),
             st.Page(two.main, title="Second page", url_path='two')],
        'second section':
            [st.Page(three.main, title="Third page", url_path='three')]
        })

    # For widget persistence, we need always copy the session state to itself, being careful with widgets that cannot be persisted, like st.data_editor() (where we use the "__do_not_persist" suffix to avoid persisting it)
    for key in st.session_state.keys():
        if not key.endswith('__do_not_persist'):
            st.session_state[key] = st.session_state[key]

    # This is needed for the st.dataframe_editor() class (https://github.com/andrew-weisman/streamlit-dataframe-editor) but is useful for seeing where we are and where we've been
    st.session_state['url_path'] = pg.url_path if pg.url_path != '' else 'Home'
    if 'url_path_prev' not in st.session_state:
        st.session_state['url_path_prev'] = st.session_state['url_path']

    # On every page, display its title
    st.title(pg.title)

    # Output where we are and where we just were
    st.write(f'Your page location: {st.session_state["url_path"]}')
    st.write(f'Previous page location: {st.session_state["url_path_prev"]}')

    # Render the select page
    pg.run()

    # Update the previous page location
    st.session_state['url_path_prev'] = st.session_state['url_path']

# Needed for rendering pages which use multiprocessing (https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods)
if __name__ == '__main__':
    main()
# one.py

import streamlit as st

def main():

    # Slider widget
    if 'slider' not in st.session_state:
        st.session_state['slider'] = 40
    st.slider("Slider", 0, 100, key="slider")

    # Display dataframe_editor2's contents (from three.py)
    if 'dataframe_editor2' in st.session_state:
        st.write(st.session_state['dataframe_editor2'].reconstruct_edited_dataframe())
    else:
        st.write('No dataframe editors have been initialized; do so on the third page')
# two.py

import streamlit as st
import multiprocessing as mp

def f(x):
    return x*x

def main():

    # Simulate cleanly returning from a page
    if st.button('Return'):
        st.warning('Returning')
        return
    
    # Toggle widget
    if 'toggle' not in st.session_state:
        st.session_state['toggle'] = False
    st.toggle('Toggle me', key='toggle')

    # Run a parallel process
    if st.button('Run parallel process'):
        with mp.get_context('forkserver').Pool(4) as p:
            st.write(p.map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
# three.py

import streamlit as st
import multiprocessing as mp
import streamlit_dataframe_editor as sde
import pandas as pd

def f(x):
    return x**3

def main():

    # Selectbox widget
    if 'selectbox' not in st.session_state:
        st.session_state['selectbox'] = 'one'
    st.selectbox('Selectbox', ['one', 'two', 'three'], key='selectbox')

    # Run a parallel process
    if st.button('Run parallel process'):
        with mp.get_context('forkserver').Pool(4) as p:
            st.write(p.map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))

    # Create a default dataframe
    df_default = pd.DataFrame({'a': [1, 2, 3], 'b': [False, False, True], 'c': [4, 5, 6]})

    # Dataframe editor widget
    if 'dataframe_editor' not in st.session_state:
        st.session_state['dataframe_editor'] = sde.DataframeEditor(df_name='my_dataframe', default_df_contents=df_default)
    st.session_state['dataframe_editor'].dataframe_editor(current_page_key='url_path', previous_page_key='url_path_prev')

    # Dataframe editor widget with identical contents (will be displayed on one.py)
    if 'dataframe_editor2' not in st.session_state:
        st.session_state['dataframe_editor2'] = sde.DataframeEditor(df_name='my_dataframe2', default_df_contents=df_default)
    st.session_state['dataframe_editor2'].dataframe_editor(current_page_key='url_path', previous_page_key='url_path_prev')
1 Like