[Good Practices] Streamlit Code


Hi, I am creating this topic to Learn/Teach good practices of Streamlit code.

First things first:

  1. I Love Streamlit (Team and Community)
  2. Being a beginner at coding, I accomplished more than I ever thought possible because of the Front-end and “tangibility” to the problem-solving that Streamlit provides.

Please feel free to add whatever you may see fit.

Good channels to check:

Misra Turp for Pandas / Streamlit

  • Her Session State videos saved me a lot of time.

Avra for Streamlit/AI

  • Has many videos building real Streamlit apps, which are very, very helpful!

Fanilo for Streamlit

  • His overview of Streamlit Components is awesome!

My 2 cents on: Session State;

(For Noobs like me :wink: )

Session State is used when you want your app to “Remember” things.

Every time you click a Streamlit widget, your app runs linearly from top to bottom, and it forgets things that you did, variables, user inputs, etc.

To make your app “remember” what you did, we create a session state variable, which is “memorized” by the app and can be re-used.

STEP 1: Think about the problem; What information do you need the app to remember?

In my case I have a dataframe, and I want to allow the user to create as many filters as he/she would like; So I need two buttons “Add Filter” and “Remove Filter”;

When the button is clicked, a st.Selectbox will appear, and the user will select the filter option;

So this is the problem:
Since my app reruns, when I click button ‘Add Filter’ it will add the filter1; but it won’t remember how many filters I currently have.

So I need the app to remember the number of filters that I want to display, and button will increase the number of filters by 1;

STEP 2: Initialize Session State variable;

Import streamlit as st

if "number_of_filters" not in st.session_state:
     st.session_state.number_of_filters = 0

This is stored as a dictionary;

st.session_state['name of what you want to store'] = 'value of what you want to store'
In my case: st.session_state['number_of_filters'] = 0

Tip: do you want to know what your app remembers? I always display this at the beginning, so I can see what it remembers at first, and I write it at the end as well, to see what has changed;

st.write(st.session_state) at the beginning

st.write(st.session_state) at the end of the app

STEP 3: Create the buttons; it will increase the value of session state when clicked;

if st.button("Add",key='add_filter_btn',use_container_width=True):
        st.session_state.number_of_filters += 1

if st.button("Remove",key='remove_filter_btn',use_container_width=False) and st.session_state.number_of_filters > 0:
                        st.session_state.number_of_filters -= 1

STEP 4: Create a number of filters based on the value of st.session_state['number_of_filters'];

Problem: each widget has a name, and the name needs to be unique. The “name” of the widget is the key, because it is also stored in st.session_state, in my case.

In my case I want the user to select a column name from the list of available columns, and then filter the dataframe on possible column values for the selected column.

First I create a copy of the df I want to use, so I can apply each filter on the “smaller dataframe”

filtered_df = df.copy()

Now I use the “number_of_filters” in session_state to create n filters.

for number_filter in range(st.session_state.number_of_filters):

         # this will give me available columns on my filtered dataframe
         option = filtered_df.columns.tolist()

         #Remember we need the unique_key for the widget
          unique_key_selectbox = f"{number_filter}_selectbox"       #this is my unique_key needed on the selectbox

          selectbox_result = st.selectbox(options ,key= unique_key_selectbox)
          # now based on the selected column, I want to allow the user to filter by using a multiselect, so first I create a list of options on the multiselect;

          multiselect_options = filtered_df[selectbox_result].unique().tolist()
          unique_key_multiselect =  f"{number_filter}_multiselect"
          multiselect_box = st.multiselect(multiselect_options, key = unique_key_multiselect)

          # Finally I filter the dataframe on the selected value, which will be used on the next filter on the "for" loop
         filtered_df = filtered_df.loc[df[selet_box].isin(multiselect_box)]

Now my question:

  1. Can you share good practices on how you organize your code of a complex streamlit app?
  2. Do you have a utils folder with your functions?
  3. Do you store css separately?
  4. Can you please give-me examples?

Hi @pmshadow33 ! Cool initiative!

As for 3 (css) I am used to store css separately and start my app with page config and local_css function:

# -------------- app config ---------------

st.set_page_config(page_title="StreamlitLand Adventure RPG", page_icon="🐲")

# define external css
def local_css(file_name):
    with open(file_name) as f:
        st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
1 Like

I created a modules folder to store modules that supports the main page and the modules in the pages folder.

So for every page in the pages folder there is an equivalent module in the modules folder. In the module you can add functions, classes, etc.

See an example in github.

That example is certainly not a complex one. The idea is as you add pages to your app, you have to add support modules in the modules folder. It is easier to track the source of the problem if certain pages are not working as expected.

Put up some efforts to teach the streamlit main concepts especially on the Data flow. From experience, both here and in stackoverflow, some have not understood this well or perhaps just ignore it. I plan to create an article on this specific area. It is better if we have more info on this topic.

1 Like