What is Streamlit?

Working with tons of data can easily get overwhelming. Have you ever wanted to explore and visualize it quickly, without all the frontend setup? Streamlit has you covered.

Streamlit is an open-source framework that enables engineers to create dynamic data web applications using Python without any frontend setup. There is no need to use HTML, CSS, JavaScript, or any complex web framework. Python knowledge is everything you need.

To get started with Streamlit, simply follow the official installation guide.

The Rerun Model – How Streamlit Actually Executes

Coding using Streamlit is easy once you understand how it actually works. Streamlit applications are Python scripts that run from top to bottom and have a specific flow and architecture. Like any Python script, Streamlit apps execute statements sequentially from top to bottom. Every user interaction with the component triggers the entire script to be rerun. The need for this stems from Streamlit’s simplicity: write apps the same way plain Python scripts are written. When coding with Streamlit, you don’t need to think in terms of callbacks and events, because each user interaction triggers a full rerun of the script to update the UI.

Sessions – Preserving State Across Reruns

A session is a single instance of the state of a running Streamlit application. Application state consists of the state of every UI component and is stored in-memory on the server as st.session_state.

Every user session has its own st.session_state. For example, if we open our application in two tabs, each tab will have its own session. Since the script is rerun on each interaction, it is necessary to save the components’ previous state to render them accurately on the next run.


Streamlit components have an optional parameter key that, when set, points to the session_state key that holds the component’s state, as shown in the example below.

import streamlit as st

st.radio(

    label="Choose a movie genre",

    options=[

        "Comedy",

        "Drama",

        "Romance",

    ],

   key="radio_movie_genre"

)

if st.session_state.radio_movie_genre == "Comedy":

st.text("You have chosen Comedy")

elif st.session_state.radio_movie_genre == "Drama":

  st.text("You have chosen Drama")

elif st.session_state.radio_movie_genre == "Romance":

st.text("You have chosen Romance")

The important thing to remember is that values in session state are only available in the current active session. 

Caching – Avoiding Redundant Work

The fact that the app is rerun frequently can cause significant time and memory overhead when loading data from the database or fetching it via an API call. On every rerun, the data would be fetched again. To prevent this, Streamlit provides a built-in caching mechanism. Cached values can be accessed by all users/tabs of the app, unlike a session, which can be accessed only by a single user/tab. 

@st.cache_data

Since the cached values are used by multiple sessions, modifying the data can cause a race condition. To ensure the original data state remains unchanged, Streamlit returns a copy of the cached data instead of the original.

To cache the return value of the function that fetches the data, append the decorator @st.cache_data above its declaration.

@st.cache_data
def fetch_the_data(param1:str, param2:str) -> List:
# API call or DB access
return data

To determine whether to call the function or read the cached value, Streamlit checks the two conditions: 

  1. If the number, variable types, and values of the input parameters are the same 
  2. If the code in the function is the same

But how does Streamlit actually check whether the input parameter values are the same? This is where hashing comes into play. The input values are hashed and stored so that, on subsequent calls, they can be compared with new values.

On the first run, the function is called; on subsequent runs, if the conditions are met, the returned value is read from the cache, and a copy is created.

By setting the ttl (time-to-live) of the cache decorator, the time during which the cache exists is determined. After that time, the cached value is no longer available, and the function will be executed again on a call.

#The cached data will be stored for 3600s ( 1 hour )
@st.cache_data(ttl=3600)
def fetch_the_data(param1:str, param2:str) -> List:
# API call or DB access
return data

@st.cache_resource

For caching global resources such as ML models or database connections, which are typically expensive to create, Streamlit uses the @st.cache_resource decorator. Unlike @st.cache_data, the @st.cache_resource decorator does not create and return a copy of the cached value; instead, it returns the same shared instance (singleton) of the resource every time.

That makes it a perfect fit for caching resources that are not meant to be duplicated. This helps avoid unnecessary memory usage, especially for large objects like ML models or database connections.

However, because a cached resource is shared across all users, sessions, and reruns, it raises thread-safety concerns. This means that multiple users or threads can access the resource simultaneously and read or write to it concurrently.

Because of this, it is important to ensure that cached resources are thread-safe. A thread-safe resource behaves correctly when multiple threads access and use it simultaneously, without producing inconsistent results. 

In general, cached resources should be treated as read-only or designed to handle concurrent access.

The example of usage is shown below:

import streamlit as st
import psycopg2
import pandas as pd

@st.cache_resource
def get_db_connection():
conn = psycopg2.connect(
host="host",
database="database",
user="user",
password="password"
)
return conn

# Function that fetches the data from db
def fetch_data(query):
conn = get_db_connection() # uses cached connection
df = pd.read_sql_query(query, conn)
return df

The illustration below, from the official Streamlit documentation, represents, in a simple way, what can be cached using these two decorators from Streamlit.

cache data and resources explained : everything you can and can't store in a database

Summary / Key Takeaways

Creating a modern, interactive UI to visualize and manage large datasets is both fun and easy with Streamlit. The diagram below shows how Streamlit works under the hood. From user interaction that triggers the script rerun, through session state restoration, to the execution of the script, with caching as a secret weapon for improving performance and reducing memory costs.

cache schema explained

Now that you understand how Streamlit handles reruns, state, and caching, you are all set to build more powerful Streamlit apps. These concepts are what make Streamlit seem simple, yet powerful and efficient in the background.

References:

Streamlit documentation.

Leave a comment

Your email address will not be published. Required fields are marked *