icon: LiNotebook
Title: Streamlit - A Deep Dive
Now that you have experience with the basics of Streamlit, we will look into some important concepts of Streamlit that have caught some developers off guard and led to unexpected behavior in their applications.
Understanding these concepts is crucial for designing and developing efficient and effective Streamlit apps. In this deep dive, we will explore session state, button behavior, and dynamic widget management, providing you with the knowledge to avoid common pitfalls and enhance your app's functionality.
By having a solid understanding of these elements, you will be better equipped to create user-friendly applications and make the most of Streamlit's features in your projects.
This note provides a concise introduction to key "advanced" concepts in Streamlit, aiming to familiarize you with the framework's mechanics and empower you to begin building apps quickly. That's the best way to understand the concepts is to open up your VS Code and try to experiment with the code.
However, it's essential to understand that this is not an exhaustive resource. The official Streamlit documentation remains the definitive guide, offering comprehensive explanations, advanced techniques, and best practices.
To ensure a thorough understanding of Streamlit, we strongly recommend reading the official documentation. We will be including the links to the corresponding section in the official documentation in the different parts of the note below.
We recommend that you first try working with the content in this note. This should be sufficient to get you started. Remember to revisit this note for the links to the official documentation after you have gained some hands-on experience or when you encounter challenges implementing certain things. This approach is more effective.
Streamlit defines access to a Streamlit app in a browser tab as a session.
A session is a single instance of viewing an app. If you view an app from two different tabs in your browser, each tab will have its own session. So each viewer of an app will have a Session State tied to their specific view. Streamlit maintains this session as the user interacts with the app. If the user refreshes their browser page or reloads the URL to the app, their Session State resets and they begin again with a new session.
Session State
is a way to share variables between reruns, for each user session. In addition to the ability to store and persist state, Streamlit also exposes the ability to manipulate state using Callbacks. Session state also persists across apps inside a multipage app.
Check out this video by Streamlit Developer Advocate Dr. Marisa Smith to on two very essential concepts for Streamlit app development:
session state
callback
Session State provides a dictionary-like interface where you can save information that is preserved between script reruns. Use st.session_state
with key or attribute notation to store and recall values. For example, st.session_state["my_key"]
or st.session_state.my_key
. Remember that widgets handle their statefulness all by themselves, so you won't always need to use Session State!
There are a few common scenarios where Session State is helpful. As demonstrated in the video above, Session State is used when you have a progressive process that you want to build upon from one rerun to the next. Session State can also be used to prevent recalculation, similar to caching. However, the differences are important:
st.button
st.button
do not retain state. They return True
on the script rerun resulting from their click and immediately return to False
on the next script rerun.
if st.button('Click me'):
False
.This note explains the use of buttons and explain common misconceptions.
if st.button()
When code is conditioned on a button's value, it will execute once in response to the button being clicked and not again (until the button is clicked again).
✅ Good to nest inside buttons:
❌ Bad to nest inside buttons:
If you want to give the user a quick button to check if an entry is valid, but not keep that check displayed as the user continues.
animal
string is in the animal_shelter
list. st.text_input
, the script reruns and the message disappears until they click "Check availability" again.import streamlit as st
animal_shelter = ['cat', 'dog', 'rabbit', 'bird']
animal = st.text_input('Type an animal')
if st.button('Check availability'):
have_it = animal.lower() in animal_shelter
'We have that animal!' if have_it else 'We don\'t have that animal.'
If you want a clicked button to continue to be True
, create a value in st.session_state
and use the button to set that value to True
in a callback.
import streamlit as st
if 'clicked' not in st.session_state:
st.session_state.clicked = False
def click_button():
st.session_state.clicked = True
st.button('Click me', on_click=click_button)
if st.session_state.clicked:
# The message and nested widget will remain on the page
st.write('Button clicked!')
st.slider('Select a value')
st.session_state
If you modify st.session_state
inside of a button, you must consider where that button is within the script.
In this example, we access st.session_state.name
both before and after the buttons which modify it. When a button ("Jane" or "John") is clicked, the script reruns. The info displayed before the buttons lags behind the info written after the button. The data in st.session_state
before the button is not updated. When the script executes the button function, that is when the conditional code to update st.session_state
creates the change. Thus, this change is reflected after the button.
import streamlit as st
import pandas as pd
if 'name' not in st.session_state:
st.session_state['name'] = 'John Doe'
st.header(st.session_state['name'])
if st.button('Jane'):
st.session_state['name'] = 'Jane Doe'
if st.button('John'):
st.session_state['name'] = 'John Doe'
st.header(st.session_state['name'])
Callbacks are a clean way to modify st.session_state
. Callbacks are executed as a prefix to the script rerunning, so the position of the button relative to accessing data is not important.
import streamlit as st
import pandas as pd
if 'name' not in st.session_state:
st.session_state['name'] = 'John Doe'
def change_name(name):
st.session_state['name'] = name
st.header(st.session_state['name'])
st.button('Jane', on_click=change_name, args=['Jane Doe'])
st.button('John', on_click=change_name, args=['John Doe'])
st.header(st.session_state['name'])
When a button is used to modify or reset another widget, it is the same as the above examples to modify st.session_state
. However, an extra consideration exists: you cannot modify a key-value pair in st.session_state
if the widget with that key has already been rendered on the page for the current script run.
import streamlit as st
st.text_input('Name', key='name')
# These buttons will error because their nested code changes
# a widget's state after that widget within the script.
if st.button('Clear name'):
st.session_state.name = ''
if st.button('Streamlit!'):
st.session_state.name = ('Streamlit')
If you assign a key to a button, you can condition code on a button's state by using its value in st.session_state
. This means that logic depending on your button can be in your script before that button.
In the following example, we use the .get()
method on st.session_state
because the keys for the buttons will not exist when the script runs for the first time. The .get()
method will return False
if it can't find the key. Otherwise, it will return the value of the key.
import streamlit as st
# Use the get method since the keys won't be in session_state
# on the first script run
if st.session_state.get('clear'):
st.session_state['name'] = ''
if st.session_state.get('streamlit'):
st.session_state['name'] = 'Streamlit'
st.text_input('Name', key='name')
st.button('Clear name', key='clear')
st.button('Streamlit!', key='streamlit')
import streamlit as st
st.text_input('Name', key='name')
def set_name(name):
st.session_state.name = name
st.button('Clear name', on_click=set_name, args=[''])
st.button('Streamlit!', on_click=set_name, args=['Streamlit'])`
By using st.container
you can have widgets appear in different orders in your script and frontend view (webpage).
import streamlit as st
begin = st.container()
if st.button('Clear name'):
st.session_state.name = ''
if st.button('Streamlit!'):
st.session_state.name = ('Streamlit')
# The widget is second in logic, but first in display
begin.text_input('Name', key='name')
Storing unencrypted secrets in a git repository is a bad practice. For applications that require access to sensitive credentials, the recommended solution is to store those credentials outside the repository - such as using a credentials file not committed to the repository or passing them as environment variables
.
Streamlit provides native file-based secrets management to easily store and securely access your secrets in your Streamlit app.
Create a file secrets.toml
in the $CWD/.streamlit
folder where $CWD
is the folder you're running Streamlit from. For example, $CWD/.streamlit/secrets.toml
.
# Everything in this section will be available as an environment variable
OPENAPI_API_KEY = "sk-1241infjsenfuhnh81n8cdn"
# You can also add other sections if you like.
# The contents of sections as shown below will not become environment variables, # but they'll be easily accessible from within Streamlit anyway as we show
# later.
[my_other_secrets]
things_i_like = ["Streamlit", "Python"]
.gitignore
so you don't upload your secrets to online repositoryAccess your secrets by querying the st.secrets
dict, or as environment variables. For example, if you enter the secrets from the section above, the code below shows you how to access them within your Streamlit app
import streamlit as st
# Everything is accessible via the st.secrets dict:
st.write("OPENAPI_API_KEY:", st.secrets["OPENAPI_API_KEY"])
# Store the value in "Environment Variable"
# This is required by some packages like LangChain
os.environ['OPENAPI_API_KEY'] = st.secrets['OPENAPI_API_KEY']