From 4e75052601353208314c4393e524a59b108527bb Mon Sep 17 00:00:00 2001 From: Thiago Teixeira Date: Fri, 15 Aug 2025 15:08:03 -0300 Subject: [PATCH 1/6] Make llms-full autogen on deploy --- netlify.toml | 18 +- public/llms-full.txt | 25007 +---------------------------------------- 2 files changed, 12 insertions(+), 25013 deletions(-) diff --git a/netlify.toml b/netlify.toml index 287030946..497b9f91f 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,14 +1,18 @@ +[build] +command = "npm export" +publish = "out" + [[plugins]] - # Setting the plugin manually, so we can pick the version (see package.json). - package = "@netlify/plugin-nextjs" +# Setting the plugin manually, so we can pick the version (see package.json). +package = "@netlify/plugin-nextjs" # IMPORTANT: Keep this in sync with next.config.js [[headers]] - for = "/*" - [headers.values] - Strict-Transport-Security = "max-age=31536000; includeSubDomains" - X-Content-Type-Options = "nosniff" - Content-Security-Policy = """ +for = "/*" +[headers.values] +Strict-Transport-Security = "max-age=31536000; includeSubDomains" +X-Content-Type-Options = "nosniff" +Content-Security-Policy = """ upgrade-insecure-requests; \ frame-ancestors 'self' ; \ frame-src https: ; \ diff --git a/public/llms-full.txt b/public/llms-full.txt index cfa8d0030..128908e7f 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -1,25007 +1,2 @@ -# Get started with Streamlit - -Source: https://docs.streamlit.io/get-started - - -This Get Started guide explains how Streamlit works, how to install Streamlit on your preferred -operating system, and how to create your first Streamlit app! - - -helps you set up your development environment. Walk through installing Streamlit on Windows, macOS, or Linux. Alternatively, code right in your browser with GitHub Codespaces or Streamlit in Snowflake. -introduces you to Streamlit's data model and development flow. You'll learn what makes Streamlit the most powerful way to build data apps, including the ability to display and style data, draw charts and maps, add interactive widgets, customize app layouts, cache computation, and define themes. -walks you through creating apps using core features to fetch and cache data, draw charts, plot information on a map, and use interactive widgets to filter results. -if you want to skip past local installation and code right in your browser. This guide uses Streamlit Community Cloud to help you automatically configure a codespace. - -{{ - text: "Start the challenge", - link: "https://30days.streamlit.app/", - target: "_blank", - }} -image="/30days.png" -/> - ---- - -# Install Streamlit - -Source: https://docs.streamlit.io/get-started/installation - - -There are multiple ways to set up your development environment and install Streamlit. Read below to -understand these options. Developing locally with Python installed on your own computer is the most -common scenario. - -## Summary for experts - -1. Set up your Python development environment. -2. Run: - ```bash - pip install streamlit - ``` -3. Validate the installation by running our Hello app: - ```bash - streamlit hello - ``` -4. Jump to our [Basic concepts](/get-started/fundamentals/main-concepts). - -## Installation steps for the rest of us - - - -
Option 1: I'm comfortable with the command line
- -Install Streamlit on your own machine using tools like `venv` and `pip`. - -
- -
Option 2: I prefer a graphical interface
- -Install Streamlit using the Anaconda Distribution graphical user interface. This is also the best -approach if you're on Windows or don't have Python set up. - -
- -
Option 3: I'd rather use a cloud-based environment
- -Use Streamlit Community Cloud with GitHub Codespaces so you don't have to go through the trouble -of installing Python and setting up an environment. - -
- -
Option 4: I need something secure, controlled, and in the cloud
- -Use Streamlit in Snowflake to code your apps in the cloud, right alongside your -data with role-based access controls. - -
-
- ---- - -# Install Streamlit using command line - -Source: https://docs.streamlit.io/get-started/installation/command-line - - -This page will walk you through creating an environment with `venv` and installing Streamlit with `pip`. These are our recommended tools, but if you are familiar with others you can use your favorite ones too. At the end, you'll build a simple "Hello world" app and run it. If you prefer to have a graphical interface to manage your Python environments, check out how to [Install Streamlit using Anaconda Distribution](/get-started/installation/anaconda-distribution). - -## Prerequisites - -As with any programming tool, in order to install Streamlit you first need to make sure your -computer is properly set up. More specifically, youโ€™ll need: - -1. **Python** - - We support [version 3.9 to 3.13](https://www.python.org/downloads/). - -1. **A Python environment manager** (recommended) - - Environment managers create virtual environments to isolate Python package installations between - projects. - - We recommend using virtual environments because installing or upgrading a Python package may - cause unintentional effects on another package. For a detailed introduction to Python - environments, check out - [Python Virtual Environments: A Primer](https://realpython.com/python-virtual-environments-a-primer/). - - For this guide, we'll be using `venv`, which comes with Python. - -1. **A Python package manager** - - Package managers handle installing each of your Python packages, including Streamlit. - - For this guide, we'll be using `pip`, which comes with Python. - -1. **Only on MacOS: Xcode command line tools** - - Download Xcode command line tools using [these instructions](https://mac.install.guide/commandlinetools/4.html) - in order to let the package manager install some of Streamlit's dependencies. - -1. **A code editor** - - Our favorite editor is [VS Code](https://code.visualstudio.com/download), which is also what we use in - all our tutorials. - -## Create an environment using `venv` - -1. Open a terminal and navigate to your project folder. - - ```bash - cd myproject - ``` - -2. In your terminal, type: - - ```bash - python -m venv .venv - ``` - -3. A folder named ".venv" will appear in your project. This directory is where your virtual environment and its dependencies are installed. - -## Activate your environment - -4. In your terminal, activate your environment with one of the following commands, depending on your operating system. - - ```bash - # Windows command prompt - .venv\Scripts\activate.bat - - # Windows PowerShell - .venv\Scripts\Activate.ps1 - - # macOS and Linux - source .venv/bin/activate - ``` - -5. Once activated, you will see your environment name in parentheses before your prompt. "(.venv)" - -## Install Streamlit in your environment - -6. In the terminal with your environment activated, type: - - ```bash - pip install streamlit - ``` - -7. Test that the installation worked by launching the Streamlit Hello example app: - - ```bash - streamlit hello - ``` - - If this doesn't work, use the long-form command: - - ```bash - python -m streamlit hello - ``` - -8. Streamlit's Hello app should appear in a new tab in your web browser! - -9. Close your terminal when you are done. - -## Create a "Hello World" app and run it - -10. Create a file named `app.py` in your project folder. - -```python -import streamlit as st - -st.write("Hello world") -``` - -11. Any time you want to use your new environment, you first need to go to your project folder (where the `.venv` directory lives) and run the command to activate it: - -```bash -# Windows command prompt -.venv\Scripts\activate.bat - -# Windows PowerShell -.venv\Scripts\Activate.ps1 - -# macOS and Linux -source .venv/bin/activate -``` - -12. Once activated, you will see your environment's name in parentheses at the beginning of your terminal prompt. "(.venv)" - -13. Run your Streamlit app. - -```bash -streamlit run app.py -``` - -If this doesn't work, use the long-form command: - -```bash -python -m streamlit run app.py -``` - -14. To stop the Streamlit server, press `Ctrl+C` in the terminal. - -15. When you're done using this environment, return to your normal shell by typing: - -```bash -deactivate -``` - -## What's next? - -Read about our [Basic concepts](/get-started/fundamentals/main-concepts) to understand Streamlit's dataflow model. - ---- - -# Install Streamlit using Anaconda Distribution - -Source: https://docs.streamlit.io/get-started/installation/anaconda-distribution - - -This page walks you through installing Streamlit locally using Anaconda Distribution. At the end, you'll build a simple "Hello world" app and run it. You can read more about [Getting started with Anaconda Distribution](https://docs.anaconda.com/free/anaconda/getting-started/) in Anaconda's docs. If you prefer to manage your Python environments via command line, check out how to [Install Streamlit using command line](/get-started/installation/command-line). - -## Prerequisites - -1. **A code editor** - - Anaconda Distribution includes Python and basically everything you need to get started. - The only thing left for you to choose is a code editor. - - Our favorite editor is [VS Code](https://code.visualstudio.com/download), which is also what we - use in all our tutorials. - -1. **Knowledge about environment managers** - - Environment managers create virtual environments to isolate Python package installations between - projects. For a detailed introduction to Python environments, check out - [Python Virtual Environments: A Primer](https://realpython.com/python-virtual-environments-a-primer/). - - But don't worry! In this guide we'll teach you how to install and use an environment manager - (Anaconda). - -## Install Anaconda Distribution - -1. Go to [anaconda.com/download](https://www.anaconda.com/download). - -2. Install Anaconda Distribution for your OS. - -## Create an environment using Anaconda Navigator - -3. Open Anaconda Navigator (the graphical interface included with Anaconda Distribution). - -4. You can decline signing in to Anaconda if prompted. - -5. In the left menu, click "**Environments**." - ![Open your environments list in Anaconda Navigator](/images/get-started/Anaconda-Navigator-environment-1.png) - -6. At the bottom of your environments list, click "**Create**." - ![Click "Create" to open the Create new environment dialog](/images/get-started/Anaconda-Navigator-environment-2-create.png) - -7. Enter "streamlitenv" for the name of your environment. - -8. Click "**Create**." -
{{ maxWidth: '50%', margin: 'auto' }}> - Finalize your new conda environment -
- ---- - -# Use Community Cloud to develop with GitHub Codespaces - -Source: https://docs.streamlit.io/get-started/installation/community-cloud - - -To use GitHub Codespaces for Streamlit development, you need a properly configured `devcontainer.json` file to set up the environment. Fortunately, Streamlit Community Cloud is here to help! Although Community Cloud is primarily used to deploy and share apps with the rest of the world, we've built in some handy features to make it easy to use GitHub Codespaces. This guide explains how to create a Community Cloud account and use an automated workflow to get you into a GitHub codespace and live-editing a Streamlit app. All this happens right in your browser, no installation required. - -If you already created a Community Cloud account and connected GitHub, jump ahead to [Create a new app from a template](/get-started/installation/community-cloud#create-a-new-app-from-a-template). - -## Prerequisites - -- You must have a GitHub account. - -## Sign up for Streamlit Community Cloud - -1. Go to share.streamlit.io. -1. Click "**Continue to sign-in**." -1. Click "**Continue with GitHub**." -1. Enter your GitHub credentials and follow GitHub's authentication prompts. -1. Fill in your account information, and click "**I accept**" at the bottom. - -## Add access to your public repositories - -1. In the upper-left corner, click on "**Workspaces {{ verticalAlign: "-.25em", color: "#ff8700" }} className={{ class: "material-icons-sharp" }}>warning - ---- - -# Use Streamlit in Snowflake to code in a secure environment - -Source: https://docs.streamlit.io/get-started/installation/streamlit-in-snowflake - - -Snowflake is a single, global platform that powers the Data Cloud. If you want to use a secure platform with role-based access control, this is the option for you! This page walks you through creating a trial Snowflake account and building a "Hello world" app. Your trial account comes with an account credit so you can try out the service without entering any payment information. - - - -For more information, see [Limitations and unsupported features](https://docs.snowflake.com/en/developer-guide/streamlit/limitations) in the Snowflake documentation. - - - -## Prerequisites - -All you need is an email address! Everything else happens in your 30-day trial account. - -## Create an account - -1. Go to . (This link will open in a new tab.) - -1. Fill in your information, and click "**CONTINUE**." - -1. Select "**Standard**" for your Snowflake edition and "**Amazon Web Services**" for your cloud provider. - -1. Choose the region nearest you, accept the terms, and click "**GET STARTED**." - -
{{ maxWidth: '50%', margin: 'auto' }}> - Choose your Snowflake edition, provider, and region -
- ---- - -# Fundamental concepts - -Source: https://docs.streamlit.io/get-started/fundamentals - - -Are you new to Streamlit and want the grand tour? If so, you're in the right place! - - -Learn the fundamental concepts of Streamlit. How is a Streamlit app structured? How does it run? How does it magically get your data on a webpage? -After you understand the rerun logic of Streamlit, learn how to make efficient and dynamic apps with caching and Session State. Get introduced to handling database connections. -Learn about Streamlit's additional features. You don't need to know these concepts for your first app, but check it out to know what's available. - - ---- - -# Basic concepts of Streamlit - -Source: https://docs.streamlit.io/get-started/fundamentals/main-concepts - - -Working with Streamlit is simple. First you sprinkle a few Streamlit commands -into a normal Python script, then you run it with `streamlit run`: - -```bash -streamlit run your_script.py [-- script args] -``` - -As soon as you run the script as shown above, a local Streamlit server will -spin up and your app will open in a new tab in your default web browser. The app -is your canvas, where you'll draw charts, text, widgets, tables, and more. - -What gets drawn in the app is up to you. For example -[`st.text`](/develop/api-reference/text/st.text) writes raw text to your app, and -[`st.line_chart`](/develop/api-reference/charts/st.line_chart) draws โ€” you guessed it โ€” a -line chart. Refer to our [API documentation](/develop/api-reference) to see all commands that -are available to you. - - - -When passing your script some custom arguments, they must be passed after two dashes. Otherwise the -arguments get interpreted as arguments to Streamlit itself. - - - -Another way of running Streamlit is to run it as a Python module. This can be -useful when configuring an IDE like PyCharm to work with Streamlit: - -```bash -# Running -python -m streamlit run your_script.py - -# is equivalent to: -streamlit run your_script.py -``` - - - -You can also pass a URL to `streamlit run`! This is great when combined with -GitHub Gists. For example: - -```bash -streamlit run https://raw.githubusercontent.com/streamlit/demo-uber-nyc-pickups/master/streamlit_app.py -``` - - - -## Development flow - -Every time you want to update your app, save the source file. When you do -that, Streamlit detects if there is a change and asks you whether you want to -rerun your app. Choose "Always rerun" at the top-right of your screen to -automatically update your app every time you change its source code. - -This allows you to work in a fast interactive loop: you type some code, save -it, try it out live, then type some more code, save it, try it out, and so on -until you're happy with the results. This tight loop between coding and viewing -results live is one of the ways Streamlit makes your life easier. - - - -While developing a Streamlit app, it's recommended to lay out your editor and -browser windows side by side, so the code and the app can be seen at the same -time. Give it a try! - - - -As of Streamlit version 1.10.0 and higher, Streamlit apps cannot be run from the root directory of Linux distributions. If you try to run a Streamlit app from the root directory, Streamlit will throw a `FileNotFoundError: [Errno 2] No such file or directory` error. For more information, see GitHub issue [#5239](https://github.com/streamlit/streamlit/issues/5239). - -If you are using Streamlit version 1.10.0 or higher, your main script should live in a directory other than the root directory. When using Docker, you can use the `WORKDIR` command to specify the directory where your main script lives. For an example of how to do this, read [Create a Dockerfile](/deploy/tutorials/docker#create-a-dockerfile). - -## Data flow - -Streamlit's architecture allows you to write apps the same way you write plain -Python scripts. To unlock this, Streamlit apps have a unique data flow: any -time something must be updated on the screen, Streamlit reruns your entire -Python script from top to bottom. - -This can happen in two situations: - -- Whenever you modify your app's source code. - -- Whenever a user interacts with widgets in the app. For example, when dragging - a slider, entering text in an input box, or clicking a button. - -Whenever a callback is passed to a widget via the `on_change` (or `on_click`) parameter, the callback will always run before the rest of your script. For details on the Callbacks API, please refer to our [Session State API Reference Guide](/develop/api-reference/caching-and-state/st.session_state#use-callbacks-to-update-session-state). - -And to make all of this fast and seamless, Streamlit does some heavy lifting -for you behind the scenes. A big player in this story is the -[`@st.cache_data`](#caching) decorator, which allows developers to skip certain -costly computations when their apps rerun. We'll cover caching later in this -page. - -## Display and style data - -There are a few ways to display data (tables, arrays, data frames) in Streamlit -apps. [Below](#use-magic), you will be introduced to _magic_ -and [`st.write()`](/develop/api-reference/write-magic/st.write), which can be used to write -anything from text to tables. After that, let's take a look at methods designed -specifically for visualizing data. - -### Use magic - -You can also write to your app without calling any Streamlit methods. -Streamlit supports "[magic commands](/develop/api-reference/write-magic/magic)," which means you don't have to use -[`st.write()`](/develop/api-reference/write-magic/st.write) at all! To see this in action try this snippet: - -```python -""" -# My first app -Here's our first attempt at using data to create a table: -""" - -import streamlit as st -import pandas as pd -df = pd.DataFrame({ - 'first column': [1, 2, 3, 4], - 'second column': [10, 20, 30, 40] -}) - -df -``` - -Any time that Streamlit sees a variable or a literal -value on its own line, it automatically writes that to your app using -[`st.write()`](/develop/api-reference/write-magic/st.write). For more information, refer to the -documentation on [magic commands](/develop/api-reference/write-magic/magic). - -### Write a data frame - -Along with [magic commands](/develop/api-reference/write-magic/magic), -[`st.write()`](/develop/api-reference/write-magic/st.write) is Streamlit's "Swiss Army knife". You -can pass almost anything to [`st.write()`](/develop/api-reference/write-magic/st.write): -text, data, Matplotlib figures, Altair charts, and more. Don't worry, Streamlit -will figure it out and render things the right way. - -```python -import streamlit as st -import pandas as pd - -st.write("Here's our first attempt at using data to create a table:") -st.write(pd.DataFrame({ - 'first column': [1, 2, 3, 4], - 'second column': [10, 20, 30, 40] -})) -``` - -There are other data specific functions like -[`st.dataframe()`](/develop/api-reference/data/st.dataframe) and -[`st.table()`](/develop/api-reference/data/st.table) that you can also use for displaying -data. Let's understand when to use these features and how to add colors and styling to your data frames. - -You might be asking yourself, "why wouldn't I always use `st.write()`?" There are -a few reasons: - -1. _Magic_ and [`st.write()`](/develop/api-reference/write-magic/st.write) inspect the type of - data that you've passed in, and then decide how to best render it in the - app. Sometimes you want to draw it another way. For example, instead of - drawing a dataframe as an interactive table, you may want to draw it as a - static table by using `st.table(df)`. -2. The second reason is that other methods return an object that can be used - and modified, either by adding data to it or replacing it. -3. Finally, if you use a more specific Streamlit method you can pass additional - arguments to customize its behavior. - -For example, let's create a data frame and change its formatting with a Pandas -`Styler` object. In this example, you'll use Numpy to generate a random sample, -and the [`st.dataframe()`](/develop/api-reference/data/st.dataframe) method to draw an -interactive table. - - - -This example uses Numpy to generate a random sample, but you can use Pandas -DataFrames, Numpy arrays, or plain Python arrays. - - - -```python -import streamlit as st -import numpy as np - -dataframe = np.random.randn(10, 20) -st.dataframe(dataframe) -``` - -Let's expand on the first example using the Pandas `Styler` object to highlight -some elements in the interactive table. - -```python -import streamlit as st -import numpy as np -import pandas as pd - -dataframe = pd.DataFrame( - np.random.randn(10, 20), - columns=('col %d' % i for i in range(20))) - -st.dataframe(dataframe.style.highlight_max(axis=0)) -``` - -Streamlit also has a method for static table generation: -[`st.table()`](/develop/api-reference/data/st.table). - -```python -import streamlit as st -import numpy as np -import pandas as pd - -dataframe = pd.DataFrame( - np.random.randn(10, 20), - columns=('col %d' % i for i in range(20))) -st.table(dataframe) -``` - -### Draw charts and maps - -Streamlit supports several popular data charting libraries like [Matplotlib, -Altair, deck.gl, and more](/develop/api-reference#chart-elements). In this section, you'll -add a bar chart, line chart, and a map to your app. - -### Draw a line chart - -You can easily add a line chart to your app with -[`st.line_chart()`](/develop/api-reference/charts/st.line_chart). We'll generate a random -sample using Numpy and then chart it. - -```python -import streamlit as st -import numpy as np -import pandas as pd - -chart_data = pd.DataFrame( - np.random.randn(20, 3), - columns=['a', 'b', 'c']) - -st.line_chart(chart_data) -``` - -### Plot a map - -With [`st.map()`](/develop/api-reference/charts/st.map) you can display data points on a map. -Let's use Numpy to generate some sample data and plot it on a map of -San Francisco. - -```python -import streamlit as st -import numpy as np -import pandas as pd - -map_data = pd.DataFrame( - np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4], - columns=['lat', 'lon']) - -st.map(map_data) -``` - -## Widgets - -When you've got the data or model into the state that you want to explore, you -can add in widgets like [`st.slider()`](/develop/api-reference/widgets/st.slider), -[`st.button()`](/develop/api-reference/widgets/st.button) or -[`st.selectbox()`](/develop/api-reference/widgets/st.selectbox). It's really straightforward -โ€” treat widgets as variables: - -```python -import streamlit as st -x = st.slider('x') # ๐Ÿ‘ˆ this is a widget -st.write(x, 'squared is', x * x) -``` - -On first run, the app above should output the text "0 squared is 0". Then -every time a user interacts with a widget, Streamlit simply reruns your script -from top to bottom, assigning the current state of the widget to your variable -in the process. - -For example, if the user moves the slider to position `10`, Streamlit will -rerun the code above and set `x` to `10` accordingly. So now you should see the -text "10 squared is 100". - -Widgets can also be accessed by key, if you choose to specify a string to use as the unique key for the widget: - -```python -import streamlit as st -st.text_input("Your name", key="name") - -# You can access the value at any point with: -st.session_state.name -``` - -Every widget with a key is automatically added to Session State. For more information about Session State, its association with widget state, and its limitations, see [Session State API Reference Guide](/develop/api-reference/caching-and-state/st.session_state). - -### Use checkboxes to show/hide data - -One use case for checkboxes is to hide or show a specific chart or section in -an app. [`st.checkbox()`](/develop/api-reference/widgets/st.checkbox) takes a single argument, -which is the widget label. In this sample, the checkbox is used to toggle a -conditional statement. - -```python -import streamlit as st -import numpy as np -import pandas as pd - -if st.checkbox('Show dataframe'): - chart_data = pd.DataFrame( - np.random.randn(20, 3), - columns=['a', 'b', 'c']) - - chart_data -``` - -### Use a selectbox for options - -Use [`st.selectbox`](/develop/api-reference/widgets/st.selectbox) to choose from a series. You -can write in the options you want, or pass through an array or data frame -column. - -Let's use the `df` data frame we created earlier. - -```python -import streamlit as st -import pandas as pd - -df = pd.DataFrame({ - 'first column': [1, 2, 3, 4], - 'second column': [10, 20, 30, 40] - }) - -option = st.selectbox( - 'Which number do you like best?', - df['first column']) - -'You selected: ', option -``` - -## Layout - -Streamlit makes it easy to organize your widgets in a left panel sidebar with -[`st.sidebar`](/develop/api-reference/layout/st.sidebar). Each element that's passed to -[`st.sidebar`](/develop/api-reference/layout/st.sidebar) is pinned to the left, allowing -users to focus on the content in your app while still having access to UI -controls. - -For example, if you want to add a selectbox and a slider to a sidebar, -use `st.sidebar.slider` and `st.sidebar.selectbox` instead of `st.slider` and -`st.selectbox`: - -```python -import streamlit as st - -# Add a selectbox to the sidebar: -add_selectbox = st.sidebar.selectbox( - 'How would you like to be contacted?', - ('Email', 'Home phone', 'Mobile phone') -) - -# Add a slider to the sidebar: -add_slider = st.sidebar.slider( - 'Select a range of values', - 0.0, 100.0, (25.0, 75.0) -) -``` - -Beyond the sidebar, Streamlit offers several other ways to control the layout -of your app. [`st.columns`](/develop/api-reference/layout/st.columns) lets you place widgets side-by-side, and -[`st.expander`](/develop/api-reference/layout/st.expander) lets you conserve space by hiding away large content. - -```python -import streamlit as st - -left_column, right_column = st.columns(2) -# You can use a column just like st.sidebar: -left_column.button('Press me!') - -# Or even better, call Streamlit functions inside a "with" block: -with right_column: - chosen = st.radio( - 'Sorting hat', - ("Gryffindor", "Ravenclaw", "Hufflepuff", "Slytherin")) - st.write(f"You are in {chosen} house!") -``` - - - -`st.echo` and `st.spinner` are not currently supported inside the sidebar -or layout options. Rest assured, though, we're currently working on adding support for those too! - - - -### Show progress - -When adding long running computations to an app, you can use -[`st.progress()`](/develop/api-reference/status/st.progress) to display status in real time. - -First, let's import time. We're going to use the `time.sleep()` method to -simulate a long running computation: - -```python -import time -``` - -Now, let's create a progress bar: - -```python -import streamlit as st -import time - -'Starting a long computation...' - -# Add a placeholder -latest_iteration = st.empty() -bar = st.progress(0) - -for i in range(100): - # Update the progress bar with each iteration. - latest_iteration.text(f'Iteration {i+1}') - bar.progress(i + 1) - time.sleep(0.1) - -'...and now we\'re done!' -``` - ---- - -# Advanced concepts of Streamlit - -Source: https://docs.streamlit.io/get-started/fundamentals/advanced-concepts - - -Now that you know how a Streamlit app runs and handles data, let's talk about being efficient. Caching allows you to save the output of a function so you can skip over it on rerun. Session State lets you save information for each user that is preserved between reruns. This not only allows you to avoid unecessary recalculation, but also allows you to create dynamic pages and handle progressive processes. - -## Caching - -Caching allows your app to stay performant even when loading data from the web, manipulating large datasets, or performing expensive computations. - -The basic idea behind caching is to store the results of expensive function calls and return the cached result when the same inputs occur again. This avoids repeated execution of a function with the same input values. - -To cache a function in Streamlit, you need to apply a caching decorator to it. You have two choices: - -- `st.cache_data` is the recommended way to cache computations that return data. Use `st.cache_data` when you use a function that returns a serializable data object (e.g. str, int, float, DataFrame, dict, list). **It creates a new copy of the data at each function call**, making it safe against [mutations and race conditions](/develop/concepts/architecture/caching#mutation-and-concurrency-issues). The behavior of `st.cache_data` is what you want in most cases โ€“ so if you're unsure, start withย `st.cache_data`ย and see if it works! -- `st.cache_resource` is the recommended way to cache global resources like ML models or database connections. Use `st.cache_resource` when your function returns unserializable objects that you donโ€™t want to load multiple times. **It returns the cached object itself**, which is shared across all reruns and sessions without copying or duplication. If you mutate an object that is cached using `st.cache_resource`, that mutation will exist across all reruns and sessions. - -Example: - -```python -@st.cache_data -def long_running_function(param1, param2): - return โ€ฆ -``` - -In the above example, `long_running_function` is decorated with `@st.cache_data`. As a result, Streamlit notes the following: - -- The name of the function (`"long_running_function"`). -- The value of the inputs (`param1`, `param2`). -- The code within the function. - -Before running the code within `long_running_function`, Streamlit checks its cache for a previously saved result. If it finds a cached result for the given function and input values, it will return that cached result and not rerun function's code. Otherwise, Streamlit executes the function, saves the result in its cache, and proceeds with the script run. During development, the cache updates automatically as the function code changes, ensuring that the latest changes are reflected in the cache. - -Streamlit's two caching decorators and their use cases. Use st.cache_data for anything you'd store in a database. Use st.cache_resource for anything you can't store in a database, like a connection to a database or a machine learning model. - -For more information about the Streamlit caching decorators, their configuration parameters, and their limitations, see [Caching](/develop/concepts/architecture/caching). - -## Session State - -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! - -### What is 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. - -### Examples of using Session State - -Here's a simple app that counts the number of times the page has been run. Every time you click the button, the script will rerun. - -```python -import streamlit as st - -if "counter" not in st.session_state: - st.session_state.counter = 0 - -st.session_state.counter += 1 - -st.header(f"This page has run {st.session_state.counter} times.") -st.button("Run it again") -``` - -- **First run:** The first time the app runs for each user, Session State is empty. Therefore, a key-value pair is created (`"counter":0`). As the script continues, the counter is immediately incremented (`"counter":1`) and the result is displayed: "This page has run 1 times." When the page has fully rendered, the script has finished and the Streamlit server waits for the user to do something. When that user clicks the button, a rerun begins. - -- **Second run:** Since "counter" is already a key in Session State, it is not reinitialized. As the script continues, the counter is incremented (`"counter":2`) and the result is displayed: "This page has run 2 times." - -There are a few common scenarios where Session State is helpful. As demonstrated 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: - -- Caching associates stored values to specific functions and inputs. Cached values are accessible to all users across all sessions. -- Session State associates stored values to keys (strings). Values in session state are only available in the single session where it was saved. - -If you have random number generation in your app, you'd likely use Session State. Here's an example where data is generated randomly at the beginning of each session. By saving this random information in Session State, each user gets different random data when they open the app but it won't keep changing on them as they interact with it. If you select different colors with the picker you'll see that the data does not get re-randomized with each rerun. (If you open the app in a new tab to start a new session, you'll see different data!) - -```python -import streamlit as st -import pandas as pd -import numpy as np - -if "df" not in st.session_state: - st.session_state.df = pd.DataFrame(np.random.randn(20, 2), columns=["x", "y"]) - -st.header("Choose a datapoint color") -color = st.color_picker("Color", "#FF0000") -st.divider() -st.scatter_chart(st.session_state.df, x="x", y="y", color=color) -``` - -If you are pulling the same data for all users, you'd likely cache a function that retrieves that data. On the other hand, if you pull data specific to a user, such as querying their personal information, you may want to save that in Session State. That way, the queried data is only available in that one session. - -As mentioned in [Basic concepts](/get-started/fundamentals/main-concepts#widgets), Session State is also related to widgets. Widgets are magical and handle statefulness quietly on their own. As an advanced feature however, you can manipulate the value of widgets within your code by assigning keys to them. Any key assigned to a widget becomes a key in Session State tied to the value of the widget. This can be used to manipulate the widget. After you finish understanding the basics of Streamlit, check out our guide on [Widget behavior](/develop/concepts/architecture/widget-behavior) to dig in the details if you're interested. - -## Connections - -As hinted above, you can use `@st.cache_resource` to cache connections. This is the most general solution which allows you to use almost any connection from any Python library. However, Streamlit also offers a convenient way to handle some of the most popular connections, like SQL! `st.connection` takes care of the caching for you so you can enjoy fewer lines of code. Getting data from your database can be as easy as: - -```python -import streamlit as st - -conn = st.connection("my_database") -df = conn.query("select * from my_table") -st.dataframe(df) -``` - -Of course, you may be wondering where your username and password go. Streamlit has a convenient mechanism for [Secrets management](/develop/concepts/connections/secrets-management). For now, let's just see how `st.connection` works very nicely with secrets. In your local project directory, you can save a `.streamlit/secrets.toml` file. You save your secrets in the toml file and `st.connection` just uses them! For example, if you have an app file `streamlit_app.py` your project directory may look like this: - -```bash -your-LOCAL-repository/ -โ”œโ”€โ”€ .streamlit/ -โ”‚ โ””โ”€โ”€ secrets.toml # Make sure to gitignore this! -โ””โ”€โ”€ streamlit_app.py -``` - -For the above SQL example, your `secrets.toml` file might look like the following: - -```toml -[connections.my_database] - type="sql" - dialect="mysql" - username="xxx" - password="xxx" - host="example.com" # IP or URL - port=3306 # Port number - database="mydb" # Database name -``` - -Since you don't want to commit your `secrets.toml` file to your repository, you'll need to learn how your host handles secrets when you're ready to publish your app. Each host platform may have a different way for you to pass your secrets. If you use Streamlit Community Cloud for example, each deployed app has a settings menu where you can load your secrets. After you've written an app and are ready to deploy, you can read all about how to [Deploy your app](/deploy/streamlit-community-cloud/deploy-your-app) on Community Cloud. - ---- - -# Additional Streamlit features - -Source: https://docs.streamlit.io/get-started/fundamentals/additional-features - - -So you've read all about Streamlit's [Basic concepts](/get-started/fundamentals/main-concepts) and gotten a taste of caching and Session State in [Advanced concepts](/get-started/fundamentals/advanced-concepts). But what about the bells and whistles? Here's a quick look at some extra features to take your app to the next level. - -## Theming - -Streamlit supports Light and Dark themes out of the box. Streamlit will first -check if the user viewing an app has a Light or Dark mode preference set by -their operating system and browser. If so, then that preference will be used. -Otherwise, the Light theme is applied by default. - -You can also change the active theme from "โ‹ฎ" โ†’ "Settings". - -![Changing Themes](/images/change_theme.gif) - -Want to add your own theme to an app? The "Settings" menu has a theme editor -accessible by clicking on "Edit active theme". You can use this editor to try -out different colors and see your app update live. - -![Editing Themes](/images/edit_theme.gif) - -When you're happy with your work, themes can be saved by -[setting config options](/develop/concepts/configuration) -in the `[theme]` config section. After you've defined a theme for your app, it -will appear as "Custom Theme" in the theme selector and will be applied by -default instead of the included Light and Dark themes. - -More information about the options available when defining a theme can be found -in the [theme option documentation](/develop/concepts/configuration/theming). - - - -The theme editor menu is available only in local development. If you've deployed your app using -Streamlit Community Cloud, the "Edit active theme" button will no longer be displayed in the "Settings" -menu. - - - - -Another way to experiment with different theme colors is to turn on the "Run on save" option, edit -your config.toml file, and watch as your app reruns with the new theme colors applied. - - - -## Pages - -As apps grow large, it becomes useful to organize them into multiple pages. This makes the app easier to manage as a developer and easier to navigate as a user. Streamlit provides a powerful way to create multipage apps using [`st.Page`](/develop/api-reference/navigation/st.page) and [`st.navigation`](/develop/api-reference/navigation/st.navigation). Just create your pages and connect them with navigation as follows: - -1. Create an entry point script that defines and connects your pages -2. Create separate Python files for each page's content -3. Use [`st.Page`](/develop/api-reference/navigation/st.page) to define your pages and [`st.navigation`](/develop/api-reference/navigation/st.navigation) to connect them - -Here's an example of a three-page app: - -
-streamlit_app.py - -```python -import streamlit as st - -# Define the pages -main_page = st.Page("main_page.py", title="Main Page", icon="๐ŸŽˆ") -page_2 = st.Page("page_2.py", title="Page 2", icon="โ„๏ธ") -page_3 = st.Page("page_3.py", title="Page 3", icon="๐ŸŽ‰") - -# Set up navigation -pg = st.navigation([main_page, page_2, page_3]) - -# Run the selected page -pg.run() -``` - -
-
-main_page.py - -```python -import streamlit as st - -# Main page content -st.markdown("# Main page ๐ŸŽˆ") -st.sidebar.markdown("# Main page ๐ŸŽˆ") -``` - -
-
-page_2.py - -```python -import streamlit as st - -st.markdown("# Page 2 โ„๏ธ") -st.sidebar.markdown("# Page 2 โ„๏ธ") -``` - -
-
-page_3.py - -```python -import streamlit as st - -st.markdown("# Page 3 ๐ŸŽ‰") -st.sidebar.markdown("# Page 3 ๐ŸŽ‰") -``` - -
-
- -Now run `streamlit run streamlit_app.py` and view your shiny new multipage app! The navigation menu will automatically appear, allowing users to switch between pages. - - - -Our documentation on [Multipage apps](/develop/concepts/multipage-apps) teaches you how to add pages to your app, including how to define pages, structure and run multipage apps, and navigate between pages. Once you understand the basics, [create your first multipage app](/get-started/tutorials/create-a-multipage-app)! - -## Custom components - -If you can't find the right component within the Streamlit library, try out custom components to extend Streamlit's built-in functionality. Explore and browse through popular, community-created components in the [Components gallery](https://streamlit.io/components). If you dabble in frontend development, you can build your own custom component with Streamlit's [components API](/develop/concepts/custom-components/intro). - -## Static file serving - -As you learned in Streamlit fundamentals, Streamlit runs a server that clients connect to. That means viewers of your app don't have direct access to the files which are local to your app. Most of the time, this doesn't matter because Streamlt commands handle that for you. When you use `st.image()` your Streamlit server will access the file and handle the necessary hosting so your app viewers can see it. However, if you want a direct URL to an image or file you'll need to host it. This requires setting the correct configuration and placing your hosted files in a directory named `static`. For example, your project could look like: - -```bash -your-project/ -โ”œโ”€โ”€ static/ -โ”‚ โ””โ”€โ”€ my_hosted-image.png -โ””โ”€โ”€ streamlit_app.py -``` - -To learn more, read our guide on [Static file serving](/develop/concepts/configuration/serving-static-files). - -## App testing - -Good development hygiene includes testing your code. Automated testing allows you to write higher quality code, faster! Streamlit has a built-in testing framework that let's you build tests easily. Use your favorite testing framework to run your tests. We like [`pytest`](https://pypi.org/project/pytest/). When you test a Streamlit app, you simulate running the app, declare user input, and inspect the results. You can use GitHub workflows to automate your tests and get instant alerts about breaking changes. Learn more in our guide to [App testing](/develop/concepts/app-testing). - ---- - -# App model summary - -Source: https://docs.streamlit.io/get-started/fundamentals/summary - - -Now that you know a little more about all the individual pieces, let's close -the loop and review how it works together: - -1. Streamlit apps are Python scripts that run from top to bottom. -1. Every time a user opens a browser tab pointing to your app, the script is executed and a new session starts. -1. As the script executes, Streamlit draws its output live in a browser. -1. Every time a user interacts with a widget, your script is re-executed and Streamlit redraws its output in the browser. - - The output value of that widget matches the new value during that rerun. -1. Scripts use the Streamlit cache to avoid recomputing expensive functions, so updates happen very fast. -1. Session State lets you save information that persists between reruns when you need more than a simple widget. -1. Streamlit apps can contain multiple pages, which are defined in separate `.py` files in a `pages` folder. - -![The Streamlit app model](/images/app_model.png) - ---- - -# First steps building Streamlit apps - -Source: https://docs.streamlit.io/get-started/tutorials - - -If you've just read through our [Basic concepts](/get-started/fundamentals/main-concepts) and want to get your hands on Streamlit. Check out these tutorials. Make sure you have [installed Streamlit](/get-started/installation) so you can execute the code yourself. - - -uses the concepts learned in Fundamentals along with caching to walk through making your first app. -walks through the easy steps to add pages to your app. - - ---- - -# Create an app - -Source: https://docs.streamlit.io/get-started/tutorials/create-an-app - - -If you've made it this far, chances are you've [installed Streamlit](/get-started/installation) and run through the basics in [Basic concepts](/get-started/fundamentals/main-concepts) and [Advanced concepts](/get-started/fundamentals/advanced-concepts). If not, now is a good time to take a look. - -The easiest way to learn how to use Streamlit is to try things out yourself. As you read through this guide, test each method. As long as your app is running, every time you add a new element to your script and save, Streamlit's UI will ask if you'd like to rerun the app and view the changes. This allows you to work in a fast interactive loop: you write some code, save it, review the output, write some more, and so on, until you're happy with the results. The goal is to use Streamlit to create an interactive app for your data or model and along the way to use Streamlit to review, debug, perfect, and share your code. - -In this guide, you're going to use Streamlit's core features to -create an interactive app; exploring a public Uber dataset for pickups and -drop-offs in New York City. When you're finished, you'll know how to fetch -and cache data, draw charts, plot information on a map, and use interactive -widgets, like a slider, to filter results. - - - -If you'd like to skip ahead and see everything at once, the [complete script -is available below](#lets-put-it-all-together). - - - -## Create your first app - -Streamlit is more than just a way to make data apps, itโ€™s also a community of creators that share their apps and ideas and help each other make their work better. Please come join us on the community forum. We love to hear your questions, ideas, and help you work through your bugs โ€” stop by today! - -1. The first step is to create a new Python script. Let's call it - `uber_pickups.py`. - -2. Open `uber_pickups.py` in your favorite IDE or text editor, then add these - lines: - - ```python - import streamlit as st - import pandas as pd - import numpy as np - ``` - -3. Every good app has a title, so let's add one: - - ```python - st.title('Uber pickups in NYC') - ``` - -4. Now it's time to run Streamlit from the command line: - - ```bash - streamlit run uber_pickups.py - ``` - - Running a Streamlit app is no different than any other Python script. Whenever you need to view the app, you can use this command. - - - - Did you know you can also pass a URL to `streamlit run`? This is great when combined with GitHub Gists. For example: - - ```bash - streamlit run https://raw.githubusercontent.com/streamlit/demo-uber-nyc-pickups/master/streamlit_app.py - ``` - - - -5. As usual, the app should automatically open in a new tab in your - browser. - -## Fetch some data - -Now that you have an app, the next thing you'll need to do is fetch the Uber -dataset for pickups and drop-offs in New York City. - -1. Let's start by writing a function to load the data. Add this code to your - script: - - ```python - DATE_COLUMN = 'date/time' - DATA_URL = ('https://s3-us-west-2.amazonaws.com/' - 'streamlit-demo-data/uber-raw-data-sep14.csv.gz') - - def load_data(nrows): - data = pd.read_csv(DATA_URL, nrows=nrows) - lowercase = lambda x: str(x).lower() - data.rename(lowercase, axis='columns', inplace=True) - data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN]) - return data - ``` - - You'll notice that `load_data` is a plain old function that downloads some - data, puts it in a Pandas dataframe, and converts the date column from text - to datetime. The function accepts a single parameter (`nrows`), which - specifies the number of rows that you want to load into the dataframe. - -2. Now let's test the function and review the output. Below your function, add - these lines: - - ```python - # Create a text element and let the reader know the data is loading. - data_load_state = st.text('Loading data...') - # Load 10,000 rows of data into the dataframe. - data = load_data(10000) - # Notify the reader that the data was successfully loaded. - data_load_state.text('Loading data...done!') - ``` - - You'll see a few buttons in the upper-right corner of your app asking if - you'd like to rerun the app. Choose **Always rerun**, and you'll see your - changes automatically each time you save. - -Ok, that's underwhelming... - -It turns out that it takes a long time to download data, and load 10,000 lines -into a dataframe. Converting the date column into datetime isnโ€™t a quick job -either. You donโ€™t want to reload the data each time the app is updated โ€“ -luckily Streamlit allows you to cache the data. - -## Effortless caching - -1. Try adding `@st.cache_data` before the `load_data` declaration: - - ```python - @st.cache_data - def load_data(nrows): - ``` - -2. Then save the script, and Streamlit will automatically rerun your app. Since - this is the first time youโ€™re running the script with `@st.cache_data`, you won't - see anything change. Letโ€™s tweak your file a little bit more so that you can - see the power of caching. - -3. Replace the line `data_load_state.text('Loading data...done!')` with this: - - ```python - data_load_state.text("Done! (using st.cache_data)") - ``` - -4. Now save. See how the line you added appeared immediately? If you take a - step back for a second, this is actually quite amazing. Something magical is - happening behind the scenes, and it only takes one line of code to activate - it. - -### How's it work? - -Let's take a few minutes to discuss how `@st.cache_data` actually works. - -When you mark a function with Streamlitโ€™s cache annotation, it tells Streamlit -that whenever the function is called that it should check two things: - -1. The input parameters you used for the function call. -2. The code inside the function. - -If this is the first time Streamlit has seen both these items, with these exact -values, and in this exact combination, it runs the function and stores the -result in a local cache. The next time the function is called, if the two -values haven't changed, then Streamlit knows it can skip executing the function -altogether. Instead, it reads the output from the local cache and passes it on -to the caller -- like magic. - -"But, wait a second," youโ€™re saying to yourself, "this sounds too good to be -true. What are the limitations of all this awesomesauce?" - -Well, there are a few: - -1. Streamlit will only check for changes within the current working directory. - If you upgrade a Python library, Streamlit's cache will only notice this if - that library is installed inside your working directory. -2. If your function is not deterministic (that is, its output depends on random - numbers), or if it pulls data from an external time-varying source (for - example, a live stock market ticker service) the cached value will be - none-the-wiser. -3. Lastly, you should avoid mutating the output of a function cached with `st.cache_data` since cached - values are stored by reference. - -While these limitations are important to keep in mind, they tend not to be an -issue a surprising amount of the time. Those times, this cache is really -transformational. - - - -Whenever you have a long-running computation in your code, consider -refactoring it so you can use `@st.cache_data`, if possible. Please read [Caching](/develop/concepts/architecture/caching) for more details. - - - -Now that you know how caching with Streamlit works, letโ€™s get back to the Uber -pickup data. - -## Inspect the raw data - -It's always a good idea to take a look at the raw data you're working with -before you start working with it. Let's add a subheader and a printout of the -raw data to the app: - -```python -st.subheader('Raw data') -st.write(data) -``` - -In the [Basic concepts](/get-started/fundamentals/main-concepts) guide you learned that -[`st.write`](/develop/api-reference/write-magic/st.write) will render almost anything you pass -to it. In this case, you're passing in a dataframe and it's rendering as an -interactive table. - -[`st.write`](/develop/api-reference/write-magic/st.write) tries to do the right thing based on -the data type of the input. If it isn't doing what you expect you can use a -specialized command like [`st.dataframe`](/develop/api-reference/data/st.dataframe) -instead. For a full list, see [API reference](/develop/api-reference). - -## Draw a histogram - -Now that you've had a chance to take a look at the dataset and observe what's -available, let's take things a step further and draw a histogram to see what -Uber's busiest hours are in New York City. - -1. To start, let's add a subheader just below the raw data section: - - ```python - st.subheader('Number of pickups by hour') - ``` - -2. Use NumPy to generate a histogram that breaks down pickup times binned by - hour: - - ```python - hist_values = np.histogram( - data[DATE_COLUMN].dt.hour, bins=24, range=(0,24))[0] - ``` - -3. Now, let's use Streamlit's - [`st.bar_chart()`](/develop/api-reference/charts/st.bar_chart) method to draw this - histogram. - - ```python - st.bar_chart(hist_values) - ``` - -4. Save your script. This histogram should show up in your app right away. - After a quick review, it looks like the busiest time is 17:00 (5 P.M.). - -To draw this diagram we used Streamlit's native `bar_chart()` method, but it's -important to know that Streamlit supports more complex charting libraries like -Altair, Bokeh, Plotly, Matplotlib and more. For a full list, see -[supported charting libraries](/develop/api-reference/charts). - -## Plot data on a map - -Using a histogram with Uber's dataset helped us determine what the busiest -times are for pickups, but what if we wanted to figure out where pickups were -concentrated throughout the city. While you could use a bar chart to show this -data, it wouldn't be easy to interpret unless you were intimately familiar with -latitudinal and longitudinal coordinates in the city. To show pickup -concentration, let's use Streamlit [`st.map()`](/develop/api-reference/charts/st.map) -function to overlay the data on a map of New York City. - -1. Add a subheader for the section: - - ```python - st.subheader('Map of all pickups') - ``` - -2. Use the `st.map()` function to plot the data: - - ```python - st.map(data) - ``` - -3. Save your script. The map is fully interactive. Give it a try by panning or - zooming in a bit. - -After drawing your histogram, you determined that the busiest hour for Uber -pickups was 17:00. Let's redraw the map to show the concentration of pickups -at 17:00. - -1. Locate the following code snippet: - - ```python - st.subheader('Map of all pickups') - st.map(data) - ``` - -2. Replace it with: - - ```python - hour_to_filter = 17 - filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter] - st.subheader(f'Map of all pickups at {hour_to_filter}:00') - st.map(filtered_data) - ``` - -3. You should see the data update instantly. - -To draw this map we used the [`st.map`](/develop/api-reference/charts/st.map) function that's built into Streamlit, but -if you'd like to visualize complex map data, we encourage you to take a look at -the [`st.pydeck_chart`](/develop/api-reference/charts/st.pydeck_chart). - -## Filter results with a slider - -In the last section, when you drew the map, the time used to filter results was -hardcoded into the script, but what if we wanted to let a reader dynamically -filter the data in real time? Using Streamlit's widgets you can. Let's add a -slider to the app with the `st.slider()` method. - -1. Locate `hour_to_filter` and replace it with this code snippet: - - ```python - hour_to_filter = st.slider('hour', 0, 23, 17) # min: 0h, max: 23h, default: 17h - ``` - -2. Use the slider and watch the map update in real time. - -## Use a button to toggle data - -Sliders are just one way to dynamically change the composition of your app. -Let's use the [`st.checkbox`](/develop/api-reference/widgets/st.checkbox) function to add a -checkbox to your app. We'll use this checkbox to show/hide the raw data -table at the top of your app. - -1. Locate these lines: - - ```python - st.subheader('Raw data') - st.write(data) - ``` - -2. Replace these lines with the following code: - - ```python - if st.checkbox('Show raw data'): - st.subheader('Raw data') - st.write(data) - ``` - -We're sure you've got your own ideas. When you're done with this tutorial, check out all the widgets that Streamlit exposes in our [API Reference](/develop/api-reference). - -## Let's put it all together - -That's it, you've made it to the end. Here's the complete script for our interactive app. - - - -If you've skipped ahead, after you've created your script, the command to run -Streamlit is `streamlit run [app name]`. - - - -```python -import streamlit as st -import pandas as pd -import numpy as np - -st.title('Uber pickups in NYC') - -DATE_COLUMN = 'date/time' -DATA_URL = ('https://s3-us-west-2.amazonaws.com/' - 'streamlit-demo-data/uber-raw-data-sep14.csv.gz') - -@st.cache_data -def load_data(nrows): - data = pd.read_csv(DATA_URL, nrows=nrows) - lowercase = lambda x: str(x).lower() - data.rename(lowercase, axis='columns', inplace=True) - data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN]) - return data - -data_load_state = st.text('Loading data...') -data = load_data(10000) -data_load_state.text("Done! (using st.cache_data)") - -if st.checkbox('Show raw data'): - st.subheader('Raw data') - st.write(data) - -st.subheader('Number of pickups by hour') -hist_values = np.histogram(data[DATE_COLUMN].dt.hour, bins=24, range=(0,24))[0] -st.bar_chart(hist_values) - -# Some number in the range 0-23 -hour_to_filter = st.slider('hour', 0, 23, 17) -filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter] - -st.subheader('Map of all pickups at %s:00' % hour_to_filter) -st.map(filtered_data) -``` - -## Share your app - -After youโ€™ve built a Streamlit app, it's time to share it! To show it off to the world you can use **Streamlit Community Cloud** to deploy, manage, and share your app for free. - -It works in 3 simple steps: - -1. Put your app in a public GitHub repo (and make sure it has a requirements.txt!) -2. Sign into [share.streamlit.io](https://share.streamlit.io) -3. Click 'Deploy an app' and then paste in your GitHub URL - -That's it! ๐ŸŽˆ You now have a publicly deployed app that you can share with the world. Click to learn more about [how to use Streamlit Community Cloud](/deploy/streamlit-community-cloud). - -## Get help - -That's it for getting started, now you can go and build your own apps! If you -run into difficulties here are a few things you can do. - -- Check out our [community forum](https://discuss.streamlit.io/) and post a question -- Quick help from command line with `streamlit help` -- Go through our [Knowledge Base](/knowledge-base) for tips, step-by-step tutorials, and articles that answer your questions about creating and deploying Streamlit apps. -- Read more documentation! Check out: - - [Concepts](/develop/concepts) for things like caching, theming, and adding statefulness to apps. - - [API reference](/develop/api-reference/) for examples of every Streamlit command. - ---- - -# Create a multipage app - -Source: https://docs.streamlit.io/get-started/tutorials/create-a-multipage-app - - -In [Additional features](/get-started/fundamentals/additional-features), we introduced multipage apps, including how to define pages, structure and run multipage apps, and navigate between pages in the user interface. You can read more details in our guide to [Multipage apps](/develop/concepts/multipage-apps) - -In this guide, letโ€™s put our understanding of multipage apps to use by converting the previous version of our `streamlit hello` app to a multipage app! - -## Motivation - -Before Streamlit 1.10.0, the streamlit hello command was a large single-page app. As there was no support for multiple pages, we resorted to splitting the app's content using `st.selectbox` in the sidebar to choose what content to run. The content is comprised of three demos for plotting, mapping, and dataframes. - -Here's what the code and single-page app looked like: - -
-hello.py (๐Ÿ‘ˆ Toggle to expand) -
- -```python -import streamlit as st - -def intro(): - import streamlit as st - - st.write("# Welcome to Streamlit! ๐Ÿ‘‹") - st.sidebar.success("Select a demo above.") - - st.markdown( - """ - Streamlit is an open-source app framework built specifically for - Machine Learning and Data Science projects. - - **๐Ÿ‘ˆ Select a demo from the dropdown on the left** to see some examples - of what Streamlit can do! - - ### Want to learn more? - - - Check out [streamlit.io](https://streamlit.io) - - Jump into our [documentation](https://docs.streamlit.io) - - Ask a question in our [community - forums](https://discuss.streamlit.io) - - ### See more complex demos - - - Use a neural net to [analyze the Udacity Self-driving Car Image - Dataset](https://github.com/streamlit/demo-self-driving) - - Explore a [New York City rideshare dataset](https://github.com/streamlit/demo-uber-nyc-pickups) - """ - ) - -def mapping_demo(): - import streamlit as st - import pandas as pd - import pydeck as pdk - - from urllib.error import URLError - - st.markdown(f"# {list(page_names_to_funcs.keys())[2]}") - st.write( - """ - This demo shows how to use -[`st.pydeck_chart`](https://docs.streamlit.io/develop/api-reference/charts/st.pydeck_chart) -to display geospatial data. -""" - ) - - @st.cache_data - def from_data_file(filename): - url = ( - "http://raw.githubusercontent.com/streamlit/" - "example-data/master/hello/v1/%s" % filename - ) - return pd.read_json(url) - - try: - ALL_LAYERS = { - "Bike Rentals": pdk.Layer( - "HexagonLayer", - data=from_data_file("bike_rental_stats.json"), - get_position=["lon", "lat"], - radius=200, - elevation_scale=4, - elevation_range=[0, 1000], - extruded=True, - ), - "Bart Stop Exits": pdk.Layer( - "ScatterplotLayer", - data=from_data_file("bart_stop_stats.json"), - get_position=["lon", "lat"], - get_color=[200, 30, 0, 160], - get_radius="[exits]", - radius_scale=0.05, - ), - "Bart Stop Names": pdk.Layer( - "TextLayer", - data=from_data_file("bart_stop_stats.json"), - get_position=["lon", "lat"], - get_text="name", - get_color=[0, 0, 0, 200], - get_size=15, - get_alignment_baseline="'bottom'", - ), - "Outbound Flow": pdk.Layer( - "ArcLayer", - data=from_data_file("bart_path_stats.json"), - get_source_position=["lon", "lat"], - get_target_position=["lon2", "lat2"], - get_source_color=[200, 30, 0, 160], - get_target_color=[200, 30, 0, 160], - auto_highlight=True, - width_scale=0.0001, - get_width="outbound", - width_min_pixels=3, - width_max_pixels=30, - ), - } - st.sidebar.markdown("### Map Layers") - selected_layers = [ - layer - for layer_name, layer in ALL_LAYERS.items() - if st.sidebar.checkbox(layer_name, True) - ] - if selected_layers: - st.pydeck_chart( - pdk.Deck( - map_style="mapbox://styles/mapbox/light-v9", - initial_view_state={ - "latitude": 37.76, - "longitude": -122.4, - "zoom": 11, - "pitch": 50, - }, - layers=selected_layers, - ) - ) - else: - st.error("Please choose at least one layer above.") - except URLError as e: - st.error( - """ - **This demo requires internet access.** - - Connection error: %s - """ - % e.reason - ) - -def plotting_demo(): - import streamlit as st - import time - import numpy as np - - st.markdown(f'# {list(page_names_to_funcs.keys())[1]}') - st.write( - """ - This demo illustrates a combination of plotting and animation with -Streamlit. We're generating a bunch of random numbers in a loop for around -5 seconds. Enjoy! -""" - ) - - progress_bar = st.sidebar.progress(0) - status_text = st.sidebar.empty() - last_rows = np.random.randn(1, 1) - chart = st.line_chart(last_rows) - - for i in range(1, 101): - new_rows = last_rows[-1, :] + np.random.randn(5, 1).cumsum(axis=0) - status_text.text("%i%% Complete" % i) - chart.add_rows(new_rows) - progress_bar.progress(i) - last_rows = new_rows - time.sleep(0.05) - - progress_bar.empty() - - # Streamlit widgets automatically run the script from top to bottom. Since - # this button is not connected to any other logic, it just causes a plain - # rerun. - st.button("Re-run") - - -def data_frame_demo(): - import streamlit as st - import pandas as pd - import altair as alt - - from urllib.error import URLError - - st.markdown(f"# {list(page_names_to_funcs.keys())[3]}") - st.write( - """ - This demo shows how to use `st.write` to visualize Pandas DataFrames. - -(Data courtesy of the [UN Data Explorer](http://data.un.org/Explorer.aspx).) -""" - ) - - @st.cache_data - def get_UN_data(): - AWS_BUCKET_URL = "http://streamlit-demo-data.s3-us-west-2.amazonaws.com" - df = pd.read_csv(AWS_BUCKET_URL + "/agri.csv.gz") - return df.set_index("Region") - - try: - df = get_UN_data() - countries = st.multiselect( - "Choose countries", list(df.index), ["China", "United States of America"] - ) - if not countries: - st.error("Please select at least one country.") - else: - data = df.loc[countries] - data /= 1000000.0 - st.write("### Gross Agricultural Production ($B)", data.sort_index()) - - data = data.T.reset_index() - data = pd.melt(data, id_vars=["index"]).rename( - columns={"index": "year", "value": "Gross Agricultural Product ($B)"} - ) - chart = ( - alt.Chart(data) - .mark_area(opacity=0.3) - .encode( - x="year:T", - y=alt.Y("Gross Agricultural Product ($B):Q", stack=None), - color="Region:N", - ) - ) - st.altair_chart(chart, use_container_width=True) - except URLError as e: - st.error( - """ - **This demo requires internet access.** - - Connection error: %s - """ - % e.reason - ) - -page_names_to_funcs = { - "โ€”": intro, - "Plotting Demo": plotting_demo, - "Mapping Demo": mapping_demo, - "DataFrame Demo": data_frame_demo -} - -demo_name = st.sidebar.selectbox("Choose a demo", page_names_to_funcs.keys()) -page_names_to_funcs[demo_name]() -``` - -
- - -Notice how large the file is! Each app โ€œpage" is written as a function, and the selectbox is used to pick which page to display. As our app grows, maintaining the code requires a lot of additional overhead. Moreover, weโ€™re limited by the `st.selectbox` UI to choose which โ€œpage" to run, we cannot customize individual page titles with `st.set_page_config`, and weโ€™re unable to navigate between pages using URLs. - -## Convert an existing app into a multipage app - -Now that we've identified the limitations of a single-page app, what can we do about it? Armed with our knowledge from the previous section, we can convert the existing app to be a multipage app, of course! At a high level, we need to perform the following steps: - -1. Create a new `pages` folder in the same folder where the โ€œentrypoint file" (`hello.py`) lives -2. Rename our entrypoint file to `Hello.py` , so that the title in the sidebar is capitalized -3. Create three new files inside of `pages`: - - `pages/1_๐Ÿ“ˆ_Plotting_Demo.py` - - `pages/2_๐ŸŒ_Mapping_Demo.py` - - `pages/3_๐Ÿ“Š_DataFrame_Demo.py` -4. Move the contents of the `plotting_demo`, `mapping_demo`, and `data_frame_demo` functions into their corresponding new files from Step 3 -5. Run `streamlit run Hello.py` to view your newly converted multipage app! - -Now, letโ€™s walk through each step of the process and view the corresponding changes in code. - -## Create the entrypoint file - -
-Hello.py - -```python -import streamlit as st - -st.set_page_config( - page_title="Hello", - page_icon="๐Ÿ‘‹", -) - -st.write("# Welcome to Streamlit! ๐Ÿ‘‹") - -st.sidebar.success("Select a demo above.") - -st.markdown( - """ - Streamlit is an open-source app framework built specifically for - Machine Learning and Data Science projects. - **๐Ÿ‘ˆ Select a demo from the sidebar** to see some examples - of what Streamlit can do! - ### Want to learn more? - - Check out [streamlit.io](https://streamlit.io) - - Jump into our [documentation](https://docs.streamlit.io) - - Ask a question in our [community - forums](https://discuss.streamlit.io) - ### See more complex demos - - Use a neural net to [analyze the Udacity Self-driving Car Image - Dataset](https://github.com/streamlit/demo-self-driving) - - Explore a [New York City rideshare dataset](https://github.com/streamlit/demo-uber-nyc-pickups) -""" -) -``` - -
-
- -We rename our entrypoint file to `Hello.py` , so that the title in the sidebar is capitalized and only the code for the intro page is included. Additionally, weโ€™re able to customize the page title and favicon โ€” as it appears in the browser tab with `st.set_page_config`. We can do so for each of our pages too! - - - -Notice how the sidebar does not contain page labels as we havenโ€™t created any pages yet. - -## Create multiple pages - -A few things to remember here: - -1. We can change the ordering of pages in our MPA by adding numbers to the beginning of each Python file. If we add a 1 to the front of our file name, Streamlit will put that file first in the list. -2. The name of each Streamlit app is determined by the file name, so to change the app name you need to change the file name! -3. We can add some fun to our app by adding emojis to our file names that will render in our Streamlit app. -4. Each page will have its own URL, defined by the name of the file. - -Check out how we do all this below! For each new page, we create a new file inside the pages folder, and add the appropriate demo code into it. - -
-
-pages/1_๐Ÿ“ˆ_Plotting_Demo.py - -```python -import streamlit as st -import time -import numpy as np - -st.set_page_config(page_title="Plotting Demo", page_icon="๐Ÿ“ˆ") - -st.markdown("# Plotting Demo") -st.sidebar.header("Plotting Demo") -st.write( - """This demo illustrates a combination of plotting and animation with -Streamlit. We're generating a bunch of random numbers in a loop for around -5 seconds. Enjoy!""" -) - -progress_bar = st.sidebar.progress(0) -status_text = st.sidebar.empty() -last_rows = np.random.randn(1, 1) -chart = st.line_chart(last_rows) - -for i in range(1, 101): - new_rows = last_rows[-1, :] + np.random.randn(5, 1).cumsum(axis=0) - status_text.text("%i%% Complete" % i) - chart.add_rows(new_rows) - progress_bar.progress(i) - last_rows = new_rows - time.sleep(0.05) - -progress_bar.empty() - -# Streamlit widgets automatically run the script from top to bottom. Since -# this button is not connected to any other logic, it just causes a plain -# rerun. -st.button("Re-run") -``` - -
- -
-pages/2_๐ŸŒ_Mapping_Demo.py - -```python -import streamlit as st -import pandas as pd -import pydeck as pdk -from urllib.error import URLError - -st.set_page_config(page_title="Mapping Demo", page_icon="๐ŸŒ") - -st.markdown("# Mapping Demo") -st.sidebar.header("Mapping Demo") -st.write( - """This demo shows how to use -[`st.pydeck_chart`](https://docs.streamlit.io/develop/api-reference/charts/st.pydeck_chart) -to display geospatial data.""" -) - - -@st.cache_data -def from_data_file(filename): - url = ( - "http://raw.githubusercontent.com/streamlit/" - "example-data/master/hello/v1/%s" % filename - ) - return pd.read_json(url) - - -try: - ALL_LAYERS = { - "Bike Rentals": pdk.Layer( - "HexagonLayer", - data=from_data_file("bike_rental_stats.json"), - get_position=["lon", "lat"], - radius=200, - elevation_scale=4, - elevation_range=[0, 1000], - extruded=True, - ), - "Bart Stop Exits": pdk.Layer( - "ScatterplotLayer", - data=from_data_file("bart_stop_stats.json"), - get_position=["lon", "lat"], - get_color=[200, 30, 0, 160], - get_radius="[exits]", - radius_scale=0.05, - ), - "Bart Stop Names": pdk.Layer( - "TextLayer", - data=from_data_file("bart_stop_stats.json"), - get_position=["lon", "lat"], - get_text="name", - get_color=[0, 0, 0, 200], - get_size=15, - get_alignment_baseline="'bottom'", - ), - "Outbound Flow": pdk.Layer( - "ArcLayer", - data=from_data_file("bart_path_stats.json"), - get_source_position=["lon", "lat"], - get_target_position=["lon2", "lat2"], - get_source_color=[200, 30, 0, 160], - get_target_color=[200, 30, 0, 160], - auto_highlight=True, - width_scale=0.0001, - get_width="outbound", - width_min_pixels=3, - width_max_pixels=30, - ), - } - st.sidebar.markdown("### Map Layers") - selected_layers = [ - layer - for layer_name, layer in ALL_LAYERS.items() - if st.sidebar.checkbox(layer_name, True) - ] - if selected_layers: - st.pydeck_chart( - pdk.Deck( - map_style="mapbox://styles/mapbox/light-v9", - initial_view_state={ - "latitude": 37.76, - "longitude": -122.4, - "zoom": 11, - "pitch": 50, - }, - layers=selected_layers, - ) - ) - else: - st.error("Please choose at least one layer above.") -except URLError as e: - st.error( - """ - **This demo requires internet access.** - Connection error: %s - """ - % e.reason - ) -``` - -
- -
-pages/3_๐Ÿ“Š_DataFrame_Demo.py - -```python -import streamlit as st -import pandas as pd -import altair as alt -from urllib.error import URLError - -st.set_page_config(page_title="DataFrame Demo", page_icon="๐Ÿ“Š") - -st.markdown("# DataFrame Demo") -st.sidebar.header("DataFrame Demo") -st.write( - """This demo shows how to use `st.write` to visualize Pandas DataFrames. -(Data courtesy of the [UN Data Explorer](http://data.un.org/Explorer.aspx).)""" -) - - -@st.cache_data -def get_UN_data(): - AWS_BUCKET_URL = "http://streamlit-demo-data.s3-us-west-2.amazonaws.com" - df = pd.read_csv(AWS_BUCKET_URL + "/agri.csv.gz") - return df.set_index("Region") - - -try: - df = get_UN_data() - countries = st.multiselect( - "Choose countries", list(df.index), ["China", "United States of America"] - ) - if not countries: - st.error("Please select at least one country.") - else: - data = df.loc[countries] - data /= 1000000.0 - st.write("### Gross Agricultural Production ($B)", data.sort_index()) - - data = data.T.reset_index() - data = pd.melt(data, id_vars=["index"]).rename( - columns={"index": "year", "value": "Gross Agricultural Product ($B)"} - ) - chart = ( - alt.Chart(data) - .mark_area(opacity=0.3) - .encode( - x="year:T", - y=alt.Y("Gross Agricultural Product ($B):Q", stack=None), - color="Region:N", - ) - ) - st.altair_chart(chart, use_container_width=True) -except URLError as e: - st.error( - """ - **This demo requires internet access.** - Connection error: %s - """ - % e.reason - ) -``` - -
- - -With our additional pages created, we can now put it all together in the final step below. - -## Run the multipage app - -To run your newly converted multipage app, run: - -```bash -streamlit run Hello.py -``` - -Thatโ€™s it! The `Hello.py` script now corresponds to the main page of your app, and other scripts that Streamlit finds in the pages folder will also be present in the new page selector that appears in the sidebar. - - - -## Next steps - -Congratulations! ๐ŸŽ‰ If you've read this far, chances are you've learned to create both single-page and multipage apps. Where you go from here is entirely up to your creativity! Weโ€™re excited to see what youโ€™ll build now that adding additional pages to your apps is easier than ever. Try adding more pages to the app we've just built as an exercise. Also, stop by the forum to show off your multipage apps with the Streamlit community! ๐ŸŽˆ - -Here are a few resources to help you get started: - -- Deploy your app for free on Streamlit's [Community Cloud](/deploy/streamlit-community-cloud). -- Post a question or share your multipage app on our [community forum](https://discuss.streamlit.io/c/streamlit-examples/9). -- Check out our documentation on [Multipage apps](/develop/concepts/multipage-apps). -- Read through [Concepts](/develop/concepts) for things like caching, theming, and adding statefulness to apps. -- Browse our [API reference](/develop/api-reference/) for examples of every Streamlit command. - ---- - -# Develop - -Source: https://docs.streamlit.io/develop - - -Get all the information you need to build beautiful, performant web apps with Streamlit! - - -Learn how Streamlit works with in-depth guides to our execution model and features. -Learn about our API with function definitions and examples. -Follow step-by-step instructions to build example apps and useful snippets. -Check out our quick references for easy access to convenient information like our changelog, cheat sheet, pre-release features, and roadmap. - - ---- - -# Development concepts - -Source: https://docs.streamlit.io/develop/concepts - - -This section gives you background on how different parts of Streamlit work. - - - -
Streamlit's architecture and execution model
- -Streamlit's execution model makes it easy to turn your scripts into beautiful, interactive web apps. - -- Understand how to run your app. -- Understand Streamlit's execution and client-server model. -- Understand the primary tools to work with Streamlit reruns. - -
- -
Multipage apps
- -Streamlit provides an automated way to build multipage apps through directory structure. - -- Learn how to structure and configure your multipage app. - -
- -
App design considerations
- -Bring together Streamlit's architecture and execution model to design your app. Work with Streamlit commands to render dynamic and -interactic content for your users. - -- Learn how to make your apps performant and easy-to-manage. -- Learn how to structure and design your project. - -
- -
Connections and secrets
- -- Learn how to manage connections and secrets with Streamlit's convenient, built-in features. - -
- -
Creating custom components
- -Custom components extend Streamlit's functionality. - -- Learn how to build your own custom component. -- Learn how install a third-party component. - -
- -
Configuration and theming
- -Streamlit provides a variety options to customize and configure your app. - -- Learn how to work with configuration options, including server settings, client settings, and theming. - -
- -
App testing
- -Streamlit app testing enables developers to build and run automated tests. Bring your favorite test automation software and enjoy simple syntax to simulate user input and inspect rendered output. - -
-
- ---- - -# Working with Streamlit's execution model - -Source: https://docs.streamlit.io/develop/concepts/architecture - - - - -
Run your app
- -Understand how to start your Streamlit app. - -
- -
Streamlit's architecture
- -Understand Streamlit's client-server architecture and related considerations. - -
- -
The app chrome
- -Every Streamlit app has a few widgets in the top right to help you as you develop your app and help your users as they view your app. This is called the app chrome. - -
- -
Caching
- -Make your app performant by caching results to avoid unecessary recomputation with each rerun. - -
- -
Session State
- -Manage your app's statefulness with Session State. - -
- -
Forms
- -Use forms to isolate user input and prevent unnecessary app reruns. - -
- -
Widget behavior
- -Understand how widgets work in detail. - -
-
- ---- - -# Run your Streamlit app - -Source: https://docs.streamlit.io/develop/concepts/architecture/run-your-app - - -Working with Streamlit is simple. First you sprinkle a few Streamlit commands into a normal Python script, and then you run it. We list few ways to run your script, depending on your use case. - -## Use streamlit run - -Once you've created your script, say `your_script.py`, the easiest way to run it is with `streamlit run`: - -```bash -streamlit run your_script.py -``` - -As soon as you run the script as shown above, a local Streamlit server will spin up and your app will open in a new tab in your default web browser. - -### Pass arguments to your script - -When passing your script some custom arguments, they must be passed after two dashes. Otherwise the arguments get interpreted as arguments to Streamlit itself: - -```bash -streamlit run your_script.py [-- script args] -``` - -### Pass a URL to streamlit run - -You can also pass a URL to `streamlit run`! This is great when your script is hosted remotely, such as a GitHub Gist. For example: - -```bash -streamlit run https://raw.githubusercontent.com/streamlit/demo-uber-nyc-pickups/master/streamlit_app.py -``` - -## Run Streamlit as a Python module - -Another way of running Streamlit is to run it as a Python module. This is useful when configuring an IDE like PyCharm to work with Streamlit: - -```bash -# Running -python -m streamlit run your_script.py -``` - -```bash -# is equivalent to: -streamlit run your_script.py -``` - ---- - -# Understanding Streamlit's client-server architecture - -Source: https://docs.streamlit.io/develop/concepts/architecture/architecture - - -Streamlit apps have a client-server structure. The Python backend of your app is the server. The frontend you view through a browser is the client. When you develop an app locally, your computer runs both the server and the client. If someone views your app across a local or global network, the server and client run on different machines. If you intend to share or deploy your app, it's important to understand this client-server structure to avoid common pitfalls. - -## Python backend (server) - -When you execute the command `streamlit run your_app.py`, your computer uses Python to start up a Streamlit server. This server is the brains of your app and performs the computations for all users who view your app. Whether users view your app across a local network or the internet, the Streamlit server runs on the one machine where the app was initialized with `streamlit run`. The machine running your Streamlit server is also called a host. - -## Browser frontend (client) - -When someone views your app through a browser, their device is a Streamlit client. When you view your app from the same computer where you are running or developing your app, then server and client are coincidentally running on the same machine. However, when users view your app across a local network or the internet, the client runs on a different machine from the server. - -## Server-client impact on app design - -Keep in mind the following considerations when building your Streamlit app: - -- The computer running or hosting your Streamlit app is responsible for providing the compute and storage necessary to run your app for all users and must be sized appropriately to handle concurrent users. -- Your app will not have access to a user's files, directories, or OS. Your app can only work with specific files a user has uploaded to your app through a widget like `st.file_uploader`. -- If your app communicates with any peripheral devices (like cameras), you must use Streamlit commands or custom components that will access those devices _through the user's browser_ and correctly communicate between the client (frontend) and server (backend). -- If your app opens or uses any program or process outside of Python, they will run on the server. For example, you may want to use `webrowser` to open a browser for the user, but this will not work as expected when viewing your app over a network; it will open a browser on the Streamlit server, unseen by the user. - ---- - -# The app chrome - -Source: https://docs.streamlit.io/develop/concepts/architecture/app-chrome - - -Your Streamlit app has a few widgets in the top right to help you as you develop. These widgets also help your viewers as they use your app. We call this things โ€œthe app chromeโ€. The chrome includes a status area, toolbar, and app menu. - -Your app menu is configurable. By default, you can access developer options from the app menu when viewing an app locally or on Streamlit Community Cloud while logged into an account with administrative access. While viewing an app, click the icon in the upper-right corner to access the menu. - -![App menu](/images/app-menu/app-menu-developer.png) - -## Menu options - -The menu is split into two sections. The upper section contains options available to all viewers and the lower section contains options for developers. Read more about [customizing this menu](#customize-the-menu) at the end of this page. - -### Rerun - -You can manually trigger a rerun of your app by clicking "**Rerun**" from the app menu. This rerun will not reset your session. Your widget states and values stored in [`st.session_state`](/develop/concepts/architecture/session-state) will be preserved. As a shortcut, without opening the app menu, you can rerun your app by pressing "**R**" on your keyboard (if you aren't currently focused on an input element). - -### Settings - -With the "**Settings**" option, you can control the appearance of your app while it is running. If viewing the app locally, you can set how your app responds to changes in your source code. See more about development flow in [Basic concepts](/get-started/fundamentals/main-concepts#development-flow). You can also force your app to appear in wide mode, even if not set within the script using [`st.set_page_config`](/develop/api-reference/configuration/st.set_page_config). - -#### Theme settings - -After clicking "**Settings**" from the app menu, you can choose between "**Light**", "**Dark**", or "**Use system setting**" for the app's base theme. Click "**Edit active theme**" to modify the theme, color-by-color. - -
{{ maxWidth: '90%', margin: '0 2em 0 2em' }}> - Settings -
- ---- - -# Caching overview - -Source: https://docs.streamlit.io/develop/concepts/architecture/caching - - -Streamlit runs your script from top to bottom at every user interaction or code change. This execution model makes development super easy. But it comes with two major challenges: - -1. Long-running functions run again and again, which slows down your app. -2. Objects get recreated again and again, which makes it hard to persist them across reruns or sessions. - -But don't worry! Streamlit lets you tackle both issues with its built-in caching mechanism. Caching stores the results of slow function calls, so they only need to run once. This makes your app much faster and helps with persisting objects across reruns. Cached values are available to all users of your app. If you need to save results that should only be accessible within a session, use [Session State](/develop/concepts/architecture/session-state) instead. - -{true}> - -1. [Minimal example](#minimal-example) -2. [Basic usage](#basic-usage) -3. [Advanced usage](#advanced-usage) -4. [Migrating from st.cache](#migrating-from-stcache) - - - ---- - -# Add statefulness to apps - -Source: https://docs.streamlit.io/develop/concepts/architecture/session-state - - -## What is State? - -We define access to a Streamlit app in a browser tab as a **session**. For each browser tab that connects to the Streamlit server, a new session is created. Streamlit reruns your script from top to bottom every time you interact with your app. Each reruns takes place in a blank slate: no variables are shared between runs. - -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 pages inside a [multipage app](/develop/concepts/multipage-apps). - -In this guide, we will illustrate the usage of **Session State** and **Callbacks** as we build a stateful Counter app. - -For details on the Session State and Callbacks API, please refer to our [Session State API Reference Guide](/develop/api-reference/caching-and-state/st.session_state). - -Also, check out this Session State basics tutorial video by Streamlit Developer Advocate Dr. Marisa Smith to get started: - - - -## Build a Counter - -Let's call our script `counter.py`. It initializes a `count` variable and has a button to increment the value stored in the `count` variable: - -```python -import streamlit as st - -st.title('Counter Example') -count = 0 - -increment = st.button('Increment') -if increment: - count += 1 - -st.write('Count = ', count) -``` - -No matter how many times we press the **_Increment_** button in the above app, the `count` remains at 1. Let's understand why: - -- Each time we press the **_Increment_** button, Streamlit reruns `counter.py` from top to bottom, and with every run, `count` gets initialized to `0` . -- Pressing **_Increment_** subsequently adds 1 to 0, thus `count=1` no matter how many times we press **_Increment_**. - -As we'll see later, we can avoid this issue by storing `count` as a Session State variable. By doing so, we're indicating to Streamlit that it should maintain the value stored inside a Session State variable across app reruns. - -Let's learn more about the API to use Session State. - -### Initialization - -The Session State API follows a field-based API, which is very similar to Python dictionaries: - -```python -import streamlit as st - -# Check if 'key' already exists in session_state -# If not, then initialize it -if 'key' not in st.session_state: - st.session_state['key'] = 'value' - -# Session State also supports the attribute based syntax -if 'key' not in st.session_state: - st.session_state.key = 'value' -``` - -### Reads and updates - -Read the value of an item in Session State by passing the item to `st.write` : - -```python -import streamlit as st - -if 'key' not in st.session_state: - st.session_state['key'] = 'value' - -# Reads -st.write(st.session_state.key) - -# Outputs: value -``` - -Update an item in Session State by assigning it a value: - -```python -import streamlit as st - -if 'key' not in st.session_state: - st.session_state['key'] = 'value' - -# Updates -st.session_state.key = 'value2' # Attribute API -st.session_state['key'] = 'value2' # Dictionary like API -``` - -Streamlit throws an exception if an uninitialized variable is accessed: - -```python -import streamlit as st - -st.write(st.session_state['value']) - -# Throws an exception! -``` - -![state-uninitialized-exception](/images/state_uninitialized_exception.png) - -Let's now take a look at a few examples that illustrate how to add Session State to our Counter app. - -### Example 1: Add Session State - -Now that we've got a hang of the Session State API, let's update our Counter app to use Session State: - -```python -import streamlit as st - -st.title('Counter Example') -if 'count' not in st.session_state: - st.session_state.count = 0 - -increment = st.button('Increment') -if increment: - st.session_state.count += 1 - -st.write('Count = ', st.session_state.count) -``` - -As you can see in the above example, pressing the **_Increment_** button updates the `count` each time. - -### Example 2: Session State and Callbacks - -Now that we've built a basic Counter app using Session State, let's move on to something a little more complex. The next example uses Callbacks with Session State. - -**Callbacks**: A callback is a Python function which gets called when an input widget changes. Callbacks can be used with widgets using the parameters `on_change` (or `on_click`), `args`, and `kwargs`. The full Callbacks API can be found in our [Session State API Reference Guide](/develop/api-reference/caching-and-state/st.session_state#use-callbacks-to-update-session-state). - -```python -import streamlit as st - -st.title('Counter Example using Callbacks') -if 'count' not in st.session_state: - st.session_state.count = 0 - -def increment_counter(): - st.session_state.count += 1 - -st.button('Increment', on_click=increment_counter) - -st.write('Count = ', st.session_state.count) -``` - -Now, pressing the **_Increment_** button updates the count each time by calling the `increment_counter()` function. - -### Example 3: Use args and kwargs in Callbacks - -Callbacks also support passing arguments using the `args` parameter in a widget: - -```python -import streamlit as st - -st.title('Counter Example using Callbacks with args') -if 'count' not in st.session_state: - st.session_state.count = 0 - -increment_value = st.number_input('Enter a value', value=0, step=1) - -def increment_counter(increment_value): - st.session_state.count += increment_value - -increment = st.button('Increment', on_click=increment_counter, - args=(increment_value, )) - -st.write('Count = ', st.session_state.count) -``` - -Additionally, we can also use the `kwargs` parameter in a widget to pass named arguments to the callback function as shown below: - -```python -import streamlit as st - -st.title('Counter Example using Callbacks with kwargs') -if 'count' not in st.session_state: - st.session_state.count = 0 - -def increment_counter(increment_value=0): - st.session_state.count += increment_value - -def decrement_counter(decrement_value=0): - st.session_state.count -= decrement_value - -st.button('Increment', on_click=increment_counter, - kwargs=dict(increment_value=5)) - -st.button('Decrement', on_click=decrement_counter, - kwargs=dict(decrement_value=1)) - -st.write('Count = ', st.session_state.count) -``` - -### Example 4: Forms and Callbacks - -Say we now want to not only increment the `count`, but also store when it was last updated. We illustrate doing this using Callbacks and `st.form`: - -```python -import streamlit as st -import datetime - -st.title('Counter Example') -if 'count' not in st.session_state: - st.session_state.count = 0 - st.session_state.last_updated = datetime.time(0,0) - -def update_counter(): - st.session_state.count += st.session_state.increment_value - st.session_state.last_updated = st.session_state.update_time - -with st.form(key='my_form'): - st.time_input(label='Enter the time', value=datetime.datetime.now().time(), key='update_time') - st.number_input('Enter a value', value=0, step=1, key='increment_value') - submit = st.form_submit_button(label='Update', on_click=update_counter) - -st.write('Current Count = ', st.session_state.count) -st.write('Last Updated = ', st.session_state.last_updated) -``` - -## Advanced concepts - -### Session State and Widget State association - -Session State provides the functionality to store variables across reruns. Widget state (i.e. the value of a widget) is also stored in a session. - -For simplicity, we have _unified_ this information in one place. i.e. the Session State. This convenience feature makes it super easy to read or write to the widget's state anywhere in the app's code. Session State variables mirror the widget value using the `key` argument. - -We illustrate this with the following example. Let's say we have an app with a slider to represent temperature in Celsius. We can **set** and **get** the value of the temperature widget by using the Session State API, as follows: - -```python -import streamlit as st - -if "celsius" not in st.session_state: - # set the initial default value of the slider widget - st.session_state.celsius = 50.0 - -st.slider( - "Temperature in Celsius", - min_value=-100.0, - max_value=100.0, - key="celsius" -) - -# This will get the value of the slider widget -st.write(st.session_state.celsius) -``` - -There is a limitation to setting widget values using the Session State API. - - - -Streamlit **does not allow** setting widget values via the Session State API for `st.button` and `st.file_uploader`. - - - -The following example will raise a `StreamlitAPIException` on trying to set the state of `st.button` via the Session State API: - -```python -import streamlit as st - -if 'my_button' not in st.session_state: - st.session_state.my_button = True - # Streamlit will raise an Exception on trying to set the state of button - -st.button('Submit', key='my_button') -``` - -state-button-exception - -### Serializable Session State - -Serialization refers to the process of converting an object or data structure into a format that can be persisted and shared, and allowing you to recover the dataโ€™s original structure. Pythonโ€™s built-in [pickle](https://docs.python.org/3/library/pickle.html) module serializes Python objects to a byte stream ("pickling") and deserializes the stream into an object ("unpickling"). - -By default, Streamlitโ€™s [Session State](/develop/concepts/architecture/session-state) allows you to persist any Python object for the duration of the session, irrespective of the objectโ€™s pickle-serializability. This property lets you store Python primitives such as integers, floating-point numbers, complex numbers and booleans, dataframes, and even [lambdas](https://docs.python.org/3/reference/expressions.html#lambda) returned by functions. However, some execution environments may require serializing all data in Session State, so it may be useful to detect incompatibility during development, or when the execution environment will stop supporting it in the future. - -To that end, Streamlit provides a `runner.enforceSerializableSessionState` [configuration option](/develop/concepts/configuration) that, when set to `true`, only allows pickle-serializable objects in Session State. To enable the option, either create a global or project config file with the following or use it as a command-line flag: - -```toml -# .streamlit/config.toml -[runner] -enforceSerializableSessionState = true -``` - -By "_pickle-serializable_", we mean calling `pickle.dumps(obj)` should not raise a [`PicklingError`](https://docs.python.org/3/library/pickle.html#pickle.PicklingError) exception. When the config option is enabled, adding unserializable data to session state should result in an exception. E.g., - -```python -import streamlit as st - -def unserializable_data(): - return lambda x: x - -#๐Ÿ‘‡ results in an exception when enforceSerializableSessionState is on -st.session_state.unserializable = unserializable_data() -``` - -UnserializableSessionStateError - - -When `runner.enforceSerializableSessionState` is set to `true`, Session State implicitly uses the `pickle` module, which is known to be insecure. Ensure all data saved and retrieved from Session State is trusted because it is possible to construct malicious pickle data that will execute arbitrary code during unpickling. Never load data that could have come from an untrusted source in an unsafe mode or that could have been tampered with. **Only load data you trust**. - - - -### Caveats and limitations - -Here are some limitations to keep in mind when using Session State: - -- Session State exists for as long as the tab is open and connected to the Streamlit server. As soon as you close the tab, everything stored in Session State is lost. -- Session State is not persisted. If the Streamlit server crashes, then everything stored in Session State gets wiped -- For caveats and limitations with the Session State API, please see the [API limitations](/develop/api-reference/caching-and-state/st.session_state#caveats-and-limitations). - ---- - -# Using forms - -Source: https://docs.streamlit.io/develop/concepts/architecture/forms - - -When you don't want to rerun your script with each input made by a user, [`st.form`](/develop/api-reference/execution-flow/st.form) is here to help! Forms make it easy to batch user input into a single rerun. This guide to using forms provides examples and explains how users interact with forms. - -## Example - -In the following example, a user can set multiple parameters to update the map. As the user changes the parameters, the script will not rerun and the map will not update. When the user submits the form with the button labeled "**Update map**", the script reruns and the map updates. - -If at any time the user clicks "**Generate new points**" which is outside of the form, the script will rerun. If the user has any unsubmitted changes within the form, these will _not_ be sent with the rerun. All changes made to a form will only be sent to the Python backend when the form itself is submitted. - -{false} > - -```python -import streamlit as st -import pandas as pd -import numpy as np - -def get_data(): - df = pd.DataFrame({ - "lat": np.random.randn(200) / 50 + 37.76, - "lon": np.random.randn(200) / 50 + -122.4, - "team": ['A','B']*100 - }) - return df - -if st.button('Generate new points'): - st.session_state.df = get_data() -if 'df' not in st.session_state: - st.session_state.df = get_data() -df = st.session_state.df - -with st.form("my_form"): - header = st.columns([1,2,2]) - header[0].subheader('Color') - header[1].subheader('Opacity') - header[2].subheader('Size') - - row1 = st.columns([1,2,2]) - colorA = row1[0].color_picker('Team A', '#0000FF') - opacityA = row1[1].slider('A opacity', 20, 100, 50, label_visibility='hidden') - sizeA = row1[2].slider('A size', 50, 200, 100, step=10, label_visibility='hidden') - - row2 = st.columns([1,2,2]) - colorB = row2[0].color_picker('Team B', '#FF0000') - opacityB = row2[1].slider('B opacity', 20, 100, 50, label_visibility='hidden') - sizeB = row2[2].slider('B size', 50, 200, 100, step=10, label_visibility='hidden') - - st.form_submit_button('Update map') - -alphaA = int(opacityA*255/100) -alphaB = int(opacityB*255/100) - -df['color'] = np.where(df.team=='A',colorA+f'{alphaA:02x}',colorB+f'{alphaB:02x}') -df['size'] = np.where(df.team=='A',sizeA, sizeB) - -st.map(df, size='size', color='color') -``` - - - ---- - -# Working with fragments - -Source: https://docs.streamlit.io/develop/concepts/architecture/fragments - - -Reruns are a central part of every Streamlit app. When users interact with widgets, your script reruns from top to bottom, and your app's frontend is updated. Streamlit provides several features to help you develop your app within this execution model. Streamlit version 1.37.0 introduced fragments to allow rerunning a portion of your code instead of your full script. As your app grows larger and more complex, these fragment reruns help your app be efficient and performant. Fragments give you finer, easy-to-understand control over your app's execution flow. - -Before you read about fragments, we recommend having a basic understanding of [caching](/develop/concepts/architecture/caching), [Session State](/concepts/architecture/session-state), and [forms](/develop/concepts/architecture/forms). - -## Use cases for fragments - -Fragments are versatile and applicable to a wide variety of circumstances. Here are just a few, common scenarios where fragments are useful: - -- Your app has multiple visualizations and each one takes time to load, but you have a filter input that only updates one of them. -- You have a dynamic form that doesn't need to update the rest of your app (until the form is complete). -- You want to automatically update a single component or group of components to stream data. - -## Defining and calling a fragment - -Streamlit provides a decorator ([`st.fragment`](/develop/api-reference/execution-flow/st.fragment)) to turn any function into a fragment function. When you call a fragment function that contains a widget function, a user triggers a _fragment rerun_ instead of a full rerun when they interact with that fragment's widget. During a fragment rerun, only your fragment function is re-executed. Anything within the main body of your fragment is updated on the frontend, while the rest of your app remains the same. We'll describe fragments written across multiple containers later on. - -Here is a basic example of defining and calling a fragment function. Just like with caching, remember to call your function after defining it. - -```python -import streamlit as st - -@st.fragment -def fragment_function(): - if st.button("Hi!"): - st.write("Hi back!") - -fragment_function() -``` - -If you want the main body of your fragment to appear in the sidebar or another container, call your fragment function inside a context manager. - -```python -with st.sidebar: - fragment_function() -``` - -### Fragment execution flow - -Consider the following code with the explanation and diagram below. - -```python -import streamlit as st - -st.title("My Awesome App") - -@st.fragment() -def toggle_and_text(): - cols = st.columns(2) - cols[0].toggle("Toggle") - cols[1].text_area("Enter text") - -@st.fragment() -def filter_and_file(): - cols = st.columns(2) - cols[0].checkbox("Filter") - cols[1].file_uploader("Upload image") - -toggle_and_text() -cols = st.columns(2) -cols[0].selectbox("Select", [1,2,3], None) -cols[1].button("Update") -filter_and_file() -``` - -When a user interacts with an input widget inside a fragment, only the fragment reruns instead of the full script. When a user interacts with an input widget outside a fragment, the full script reruns as usual. - -If you run the code above, the full script will run top to bottom on your app's initial load. If you flip the toggle button in your running app, the first fragment (`toggle_and_text()`) will rerun, redrawing the toggle and text area while leaving everything else unchanged. If you click the checkbox, the second fragment (`filter_and_file()`) will rerun and consequently redraw the checkbox and file uploader. Everything else remains unchanged. Finally, if you click the update button, the full script will rerun, and Streamlit will redraw everything. - -![Diagram of fragment execution flow](/images/concepts/fragment_diagram.png) - -## Fragment return values and interacting with the rest of your app - -Streamlit ignores fragment return values during fragment reruns, so defining return values for your fragment functions is not recommended. Instead, if your fragment needs to share data with the rest of your app, use Session State. Fragments are just functions in your script, so they can access Session State, imported modules, and other Streamlit elements like containers. If your fragment writes to any container created outside of itself, note the following difference in behavior: - -- Elements drawn in the main body of your fragment are cleared and redrawn in place during a fragment rerun. Repeated fragment reruns will not cause additional elements to appear. -- Elements drawn to containers outside the main body of fragment will not be cleared with each fragment rerun. Instead, Streamlit will draw them additively and these elements will accumulate until the next full-script rerun. -- A fragment can't draw widgets in containers outside of the main body of the fragment. Widgets can only go in the main body of a fragment. - -To prevent elements from accumulating in outside containers, use [`st.empty`](/develop/api-reference/layout/st.empty) containers. For a related tutorial, see [Create a fragment across multiple containers](/develop/tutorials/execution-flow/create-a-multiple-container-fragment). - -If you need to trigger a full-script rerun from inside a fragment, call [`st.rerun`](/develop/api-reference/execution-flow/st.rerun). For a related tutorial, see [Trigger a full-script rerun from inside a fragment](/develop/tutorials/execution-flow/trigger-a-full-script-rerun-from-a-fragment). - -## Automate fragment reruns - -`st.fragment` includes a convenient `run_every` parameter that causes the fragment to rerun automatically at the specified time interval. These reruns are in addition to any reruns (fragment or full-script) triggered by your user. The automatic fragment reruns will continue even if your user is not interacting with your app. This is a great way to show a live data stream or status on a running background job, efficiently updating your rendered data and _only_ your rendered data. - -```python -@st.fragment(run_every="10s") -def auto_function(): - # This will update every 10 seconds! - df = get_latest_updates() - st.line_chart(df) - -auto_function() -``` - -For a related tutorial, see [Start and stop a streaming fragment](/develop/tutorials/execution-flow/start-and-stop-fragment-auto-reruns). - -## Compare fragments to other Streamlit features - -### Fragments vs forms - -Here is a comparison between fragments and forms: - -- **Forms** allow users to interact with widgets without rerunning your app. Streamlit does not send user actions within a form to your app's Python backend until the form is submitted. Widgets within a form can not dynamically update other widgets (in or out of the form) in real-time. -- **Fragments** run independently from the rest of your code. As your users interact with fragment widgets, their actions are immediately processed by your app's Python backend and your fragment code is rerun. Widgets within a fragment can dynamically update other widgets within the same fragment in real-time. - -A form batches user input without interaction between any widgets. A fragment immediately processes user input but limits the scope of the rerun. - -### Fragments vs callbacks - -Here is a comparison between fragments and callbacks: - -- **Callbacks** allow you to execute a function at the beginning of a script rerun. A callback is a _single prefix_ to your script rerun. -- **Fragments** allow you to rerun a portion of your script. A fragment is a _repeatable postfix_ to your script, running each time a user interacts with a fragment widget, or automatically in sequence when `run_every` is set. - -When callbacks render elements to your page, they are rendered before the rest of your page elements. When fragments render elements to your page, they are updated with each fragment rerun (unless they are written to containers outside of the fragment, in which case they accumulate there). - -### Fragments vs custom components - -Here is a comparison between fragments and custom components: - -- **Components** are custom frontend code that can interact with the Python code, native elements, and widgets in your Streamlit app. Custom components extend whatโ€™s possible with Streamlit. They follow the normal Streamlit execution flow. -- **Fragments** are parts of your app that can rerun independently of the full app. Fragments can be composed of multiple Streamlit elements, widgets, or any Python code. - -A fragment can include one or more custom components. A custom component could not easily include a fragment! - -### Fragments vs caching - -Here is a comparison between fragments and caching: - -- **Caching:** allows you to skip over a function and return a previously computed value. When you use caching, you execute everything except the cached function (if you've already run it before). -- **Fragments:** allow you to freeze most of your app and just execute the fragment. When you use fragments, you execute only the fragment (when triggering a fragment rerun). - -Caching saves you from unnecessarily running a piece of your app while the rest runs. Fragments save you from running your full app when you only want to run one piece. - -## Limitations and unsupported behavior - -- Fragments can't detect a change in input values. It is best to use Session State for dynamic input and output for fragment functions. -- Using caching and fragments on the same function is unsupported. -- Fragments can't render widgets in externally-created containers; widgets can only be in the main body of a fragment. - ---- - -# Understanding widget behavior - -Source: https://docs.streamlit.io/develop/concepts/architecture/widget-behavior - - -Widgets (like `st.button`, `st.selectbox`, and `st.text_input`) are at the heart of Streamlit apps. They are the interactive elements of Streamlit that pass information from your users into your Python code. Widgets are magical and often work how you want, but they can have surprising behavior in some situations. Understanding the different parts of a widget and the precise order in which events occur helps you achieve your desired results. - -This guide covers advanced concepts about widgets. Generally, it begins with simpler concepts and increases in complexity. For most beginning users, these details won't be important to know right away. When you want to dynamically change widgets or preserve widget information between pages, these concepts will be important to understand. We recommend having a basic understanding of [Session State](/develop/api-reference/caching-and-state/st.session_state) before reading this guide. - -{false}> - -1. The actions of one user do not affect the widgets of any other user. -2. A widget function call returns the widget's current value, which is a simple Python type. (e.g. `st.button` returns a boolean value.) -3. Widgets return their default values on their first call before a user interacts with them. -4. A widget's identity depends on the arguments passed to the widget function. Changing a widget's label, min or max value, default value, placeholder text, help text, or key will cause it to reset. -5. If you don't call a widget function in a script run, Streamlit will delete the widget's information_including its key-value pair in Session State_. If you call the same widget function later, Streamlit treats it as a new widget. - -The last two points (widget identity and widget deletion) are the most relevant when dynamically changing widgets or working with multi-page applications. This is covered in detail later in this guide: [Statefulness of widgets](#statefulness-of-widgets) and [Widget life cycle](#widget-life-cycle). - - - ---- - -# Multipage apps - -Source: https://docs.streamlit.io/develop/concepts/multipage-apps - - - - -
Overview of multipage apps
- -Streamlit provides multiple ways to define multipage apps. Understand the terminology and basic comparison between methods. - -
- -
Define multipage apps with st.Page and st.navigation
- -Learn about the preferred method for defining multipage apps. `st.Page` and `st.navigation` give you flexibility to organize your project directory and label your pages as you please. - -
- -
Creating multipage apps using the pages/ directory
- -Define your multipage apps through directory structure. Place additional Python files in a `pages/` directory alongside your entrypoint file and pages are automatically shown in a navigation widget inside your app's sidebar. - -
- -
Working with widgets in multipage apps
- -Understand how widget identity is tied to pages. Learn strategies to get the behavior you want out of widgets. - -
-
- ---- - -# Overview of multipage apps - -Source: https://docs.streamlit.io/develop/concepts/multipage-apps/overview - - -Streamlit provides two built-in mechanisms for creating multipage apps. The simplest method is to use a `pages/` directory. However, the preferred and more customizable method is to use `st.navigation`. - -## `st.Page` and `st.navigation` - -If you want maximum flexibility in defining your multipage app, we recommend using `st.Page` and `st.navigation`. With `st.Page` you can declare any Python file or `Callable` as a page in your app. Furthermore, you can define common elements for your pages in your entrypoint file (the file you pass to `streamlit run`). With these methods, your entrypoint file becomes like a picture frame shared by all your pages. - -You must include `st.navigation` in your entrypoint file to configure your app's navigation menu. This is also how your entrypoint file serves as the router between your pages. - -## `pages/` directory - -If you're looking for a quick and simple solution, just place a `pages/` directory next to your entrypoint file. For every Python file in your `pages/` directory, Streamlit will create an additional page for your app. Streamlit determines the page labels and URLs from the file name and automatically populates a navigation menu at the top of your app's sidebar. - -``` -your_working_directory/ -โ”œโ”€โ”€ pages/ -โ”‚ โ”œโ”€โ”€ a_page.py -โ”‚ โ””โ”€โ”€ another_page.py -โ””โ”€โ”€ your_homepage.py -``` - -Streamlit determines the page order in navigation from the filenames. You can use numerical prefixes in the filenames to adjust page order. For more information, see [How pages are sorted in the sidebar](/develop/concepts/multipage-apps/pages-directory#how-pages-are-sorted-in-the-sidebar). If you want to customize your navigation menu with this option, you can deactivate the default navigation through [configuration](/develop/api-reference/configuration/config.toml) (`client.showSidebarNavigation = false`). Then, you can use `st.page_link` to manually contruct a custom navigation menu. With `st.page_link`, you can change the page label and icon in your navigation menu, but you can't change the URLs of your pages. - -## Page terminology - -A page has four identifying pieces as follows: - -- **Page source**: This is a Python file or callable function with the page's source code. -- **Page label**: This is how the page is identified within the navigation menu. See {{ verticalAlign: "-.25em" }} class="material-icons-sharp">looks_one - ---- - -# Define multipage apps with `st.Page` and `st.navigation` - -Source: https://docs.streamlit.io/develop/concepts/multipage-apps/page-and-navigation - - -`st.Page` and `st.navigation` are the preferred commands for defining multipage apps. With these commands, you have flexibility to organize your project files and customize your navigation menu. Simply initialize `StreamlitPage` objects with `st.Page`, then pass those `StreamlitPage` objects to `st.navigation` in your entrypoint file (i.e. the file you pass to `streamlit run`). - -This page assumes you understand the [Page terminology](/develop/concepts/multipage-apps/overview#page-terminology) presented in the overview. - -## App structure - -When using `st.navigation`, your entrypoint file acts like a page router. Each page is a script executed from your entrypoint file. You can define a page from a Python file or function. If you include elements or widgets in your entrypoint file, they become common elements between your pages. In this case, you can think of your entrypoint file like a picture frame around each of your pages. - -You can only call `st.navigation` once per app run and you must call it from your entrypoint file. When a user selects a page in navigation (or is routed through a command like `st.switch_page`), `st.navigation` returns the selected page. You must manually execute that page with the `.run()` method. The following example is a two-page app where each page is defined by a Python file. - -**Directory structure:** - -``` -your-repository/ -โ”œโ”€โ”€ page_1.py -โ”œโ”€โ”€ page_2.py -โ””โ”€โ”€ streamlit_app.py -``` - -**`streamlit_app.py`:** - -```python -import streamlit as st - -pg = st.navigation([st.Page("page_1.py"), st.Page("page_2.py")]) -pg.run() -``` - -## Defining pages - -`st.Page` lets you define a page. The first and only required argument defines your page source, which can be a Python file or function. When using Python files, your pages may be in a subdirectory (or superdirectory). The path to your page file must always be relative to the entrypoint file. Once you create your page objects, pass them to `st.navigation` to register them as pages in your app. - -If you don't define your page title or URL pathname, Streamlit will infer them from the file or function name as described in the multipage apps [Overview](/develop/concepts/multipage-apps/overview#automatic-page-labels-and-urls). However, `st.Page` lets you configure them manually. Within `st.Page`, Streamlit uses `title` to set the page label and title. Additionaly, Streamlit uses `icon` to set the page icon and favicon. If you want to have a different page title and label, or different page icon and favicon, you can use `st.set_page_config` to change the page title and/or favicon. Just call `st.set_page_config` after `st.navigation`, either in your entrypoint file or in your page source. - -The following example uses `st.set_page_config` to set a page title and favicon consistently across pages. Each page will have its own label and icon in the navigation menu, but the browser tab will show a consistent title and favicon on all pages. - -**Directory structure:** - -``` -your-repository/ -โ”œโ”€โ”€ create.py -โ”œโ”€โ”€ delete.py -โ””โ”€โ”€ streamlit_app.py -``` - -**`streamlit_app.py`:** - -```python -import streamlit as st - -create_page = st.Page("create.py", title="Create entry", icon=":material/add_circle:") -delete_page = st.Page("delete.py", title="Delete entry", icon=":material/delete:") - -pg = st.navigation([create_page, delete_page]) -st.set_page_config(page_title="Data manager", page_icon=":material/edit:") -pg.run() -``` - -
{{ maxWidth: '564px', margin: 'auto' }}> - -
- ---- - -# Creating multipage apps using the `pages/` directory - -Source: https://docs.streamlit.io/develop/concepts/multipage-apps/pages-directory - - -The most customizable method for declaring multipage apps is using [Page and navigation](/develop/concepts/multipage-apps/page-and-navigation). However, Streamlit also provides a frictionless way to create multipage apps where pages are automatically recognized and shown in a navigation widget inside your app's sidebar. This method uses the `pages/` directory. - -This page assumes you understand the [Page terminology](/develop/concepts/multipage-apps/overview#page-terminology) presented in the overview. - -## App structure - -When you use the `pages/` directory, Streamlit identifies pages in your multipage app by directory structure and filenames. Your entrypoint file (the file you pass to `streamlit run`), is your app's homepage. When you have a `pages/` directory next to your entrypoint file, Streamlit will identify each Python file within it as a page. The following example has three pages. `your_homepage.py` is the entrypoint file and homepage. - -``` -your_working_directory/ -โ”œโ”€โ”€ pages/ -โ”‚ โ”œโ”€โ”€ a_page.py -โ”‚ โ””โ”€โ”€ another_page.py -โ””โ”€โ”€ your_homepage.py -``` - -Run your multipage app just like you would for a single-page app. Pass your entrypoint file to `streamlit run`. - -``` -streamlit run your_homepage.py -``` - -Only `.py` files in the `pages/` directory will be identified as pages. Streamlit ignores all other files in the `pages/` directory and its subdirectories. Streamlit also ignores Python files in subdirectories of `pages/`. - - - -If you call `st.navigation` in your app (in any session), Streamlit will switch to using the newer, Page-and-navigation multipage structure. In this case, the `pages/` directory will be ignored across all sessions. You will not be able to revert back to the `pages/` directory unless you restart you app. - - - -### How pages are sorted in the sidebar - -See the overview to understand how Streamlit assigns [Automatic page labels and URLs](/develop/concepts/multipage-apps/overview#automatic-page-labels-and-urls) based on the `number`, `separator`, `identifier`, and `".py"` extension that constitute a filename. - -The entrypoint file is always displayed first. The remaining pages are sorted as follows: - -- Files that have a `number` appear before files without a `number`. -- Files are sorted based on the `number` (if any), followed by the `label` (if any). -- When files are sorted, Streamlit treats the `number` as an actual number rather than a string. So `03` is the same as `3`. - -This table shows examples of filenames and their corresponding labels, sorted by the order in which they appear in the sidebar. - -**Examples**: - -| **Filename** | **Rendered label** | -| :------------------------ | :----------------- | -| `1 - first page.py` | first page | -| `12 monkeys.py` | monkeys | -| `123.py` | 123 | -| `123_hello_dear_world.py` | hello dear world | -| `_12 monkeys.py` | 12 monkeys | - - - -Emojis can be used to make your page names more fun! For example, a file named `๐Ÿ _Home.py` will create a page titled "๐Ÿ  Home" in the sidebar. When adding emojis to filenames, itโ€™s best practice to include a numbered prefix to make autocompletion in your terminal easier. Terminal-autocomplete can get confused by unicode (which is how emojis are represented). - - - -## Notes and limitations - -- Pages support run-on-save. - - When you update a page while your app is running, this causes a rerun for users currently viewing that exact page. - - When you update a page while your app is running, the app will not automatically rerun for users currently viewing a different page. -- While your app is running, adding or deleting a page updates the sidebar navigation immediately. -- [`st.set_page_config`](/develop/api-reference/configuration/st.set_page_config) works at the page level. - - When you set `title` or `favicon` using `st.set_page_config`, this applies to the current page only. - - When you set `layout` using `st.set_page_config`, the setting will remain for the session until changed by another call to `st.set_page_config`. If you use `st.set_page_config` to set `layout`, it's recommended to call it on _all_ pages. -- Pages share the same Python modules globally: - - ```python - # page1.py - import foo - foo.hello = 123 - - # page2.py - import foo - st.write(foo.hello) # If page1 already executed, this writes 123 - ``` - -- Pages share the same [st.session_state](/develop/concepts/architecture/session-state): - - ```python - # page1.py - import streamlit as st - if "shared" not in st.session_state: - st.session_state["shared"] = True - - # page2.py - import streamlit as st - st.write(st.session_state["shared"]) # If page1 already executed, this writes True - ``` - -You now have a solid understanding of multipage apps. You've learned how to structure apps, define pages, and navigate between pages in the user interface. It's time to [create your first multipage app](/get-started/tutorials/create-a-multipage-app)! ๐Ÿฅณ - ---- - -# Working with widgets in multipage apps - -Source: https://docs.streamlit.io/develop/concepts/multipage-apps/widgets - - -When you create a widget in a Streamlit app, Streamlit generates a widget ID and uses it to make your widget stateful. As your app reruns with user interaction, Streamlit keeps track of the widget's value by associating its value to its ID. In particular, a widget's ID depends on the page where it's created. If you define an identical widget on two different pages, then the widget will reset to its default value when you switch pages. - -This guide explains three strategies to deal with the behavior if you'd like to have a widget remain stateful across all pages. If don't want a widget to appear on all pages, but you do want it to remain stateful when you navigate away from its page (and then back), Options 2 and 3 can be used. For detailed information about these strategies, see [Understanding widget behavior](/develop/concepts/architecture/widget-behavior). - -## Option 1 (preferred): Execute your widget command in your entrypoint file - -When you define your multipage app with `st.Page` and `st.navigation`, your entrypoint file becomes a frame of common elements around your pages. When you execute a widget command in your entrypoint file, Streamlit associates the widget to your entrypoint file instead of a particular page. Since your entrypoint file is executed in every app rerun, any widget in your entrypoint file will remain stateful as your users switch between pages. - -This method does not work if you define your app with the `pages/` directory. - -The following example includes a selectbox and slider in the sidebar that are rendered and stateful on all pages. The widgets each have an assigned key so you can access their values through Session State within a page. - -**Directory structure:** - -``` -your-repository/ -โ”œโ”€โ”€ page_1.py -โ”œโ”€โ”€ page_2.py -โ””โ”€โ”€ streamlit_app.py -``` - -**`streamlit_app.py`:** - -```python -import streamlit as st - -pg = st.navigation([st.Page("page_1.py"), st.Page("page_2.py")]) - -st.sidebar.selectbox("Group", ["A","B","C"], key="group") -st.sidebar.slider("Size", 1, 5, key="size") - -pg.run() -``` - -## Option 2: Save your widget values into a dummy key in Session State - -If you want to navigate away from a widget and return to it while keeping its value, or if you want to use the same widget on multiple pages, use a separate key in `st.session_state` to save the value independently from the widget. In this example, a temporary key is used with a widget. The temporary key uses an underscore prefix. Hence, `"_my_key"` is used as the widget key, but the data is copied to `"my_key"` to preserve it between pages. - -```python -import streamlit as st - -def store_value(): - # Copy the value to the permanent key - st.session_state["my_key"] = st.session_state["_my_key"] - -# Copy the saved value to the temporary key -st.session_state["_my_key"] = st.session_state["my_key"] -st.number_input("Number of filters", key="_my_key", on_change=store_value) -``` - -If this is functionalized to work with multiple widgets, it could look something like this: - -```python -import streamlit as st - -def store_value(key): - st.session_state[key] = st.session_state["_"+key] -def load_value(key): - st.session_state["_"+key] = st.session_state[key] - -load_value("my_key") -st.number_input("Number of filters", key="_my_key", on_change=store_value, args=["my_key"]) -``` - -## Option 3: Interrupt the widget clean-up process - -When Streamlit gets to the end of an app run, it will delete the data for any widgets that were not rendered. This includes data for any widget not associated to the current page. However, if you re-save a key-value pair in an app run, Streamlit will not associate the key-value pair to any widget until you execute a widget command again with that key. - -As a result, if you have the following code at the top of every page, any widget with the key `"my_key"` will retain its value wherever it's rendered (or not). Alternatively, if you are using `st.navigation` and `st.Page`, you can include this once in your entrypoint file before executing your page. - -```python -if "my_key" in st.session_state: - st.session_state.my_key = st.session_state.my_key -``` - ---- - -# App design concepts and considerations - -Source: https://docs.streamlit.io/develop/concepts/design - - - - -
Animate and update elements
- -Understand how to create dynamic, animated content or update elements without rerunning your app. - -
- -
Button behavior and examples
- -Understand how buttons work with explanations and examples to avoid common mistakes. - -
- -
Dataframes
- -Dataframes are a great way to display and edit data in a tabular format. Understand the UI and options available in Streamlit. - -
- -
Using custom Python classes in your Streamlit app
- -Understand the impact of defining your own Python classes within Streamlit's rerun model. - -
- -
Multithreading
- -Understand how to use multithreading within Streamlit apps. - -
- -
Working with timezones
- -Understand how to localize time to your users. - -
-
- ---- - -# Animate and update elements - -Source: https://docs.streamlit.io/develop/concepts/design/animate - - -Sometimes you display a chart or dataframe and want to modify it live as the app -runs (for example, in a loop). Some elements have built-in methods to allow you -to update them in-place without rerunning the app. - -Updatable elements include the following: - -- `st.empty` containers can be written to in sequence and will always show the last thing written. They can also be cleared with an - additional `.empty()` called like a method. -- `st.dataframe`, `st.table`, and many chart elements can be updated with the `.add_rows()` method which appends data. -- `st.progress` elements can be updated with additional `.progress()` calls. They can also be cleared with a `.empty()` method call. -- `st.status` containers have an `.update()` method to change their labels, expanded state, and status. -- `st.toast` messages can be updated in place with additional `.toast()` calls. - -## `st.empty` containers - -`st.empty` can hold a single element. When you write any element to an `st.empty` container, Streamlit discards its previous content -displays the new element. You can also `st.empty` containers by calling `.empty()` as a method. If you want to update a set of elements, use -a plain container (`st.container()`) inside `st.empty` and write contents to the plain container. Rewrite the plain container and its -contents as often as desired to update your app's display. - -## The `.add_rows()` method - -`st.dataframe`, `st.table`, and all chart functions can be mutated using the `.add_rows()` method on their output. In the following example, we use `my_data_element = st.line_chart(df)`. You can try the example with `st.table`, `st.dataframe`, and most of the other simple charts by just swapping out `st.line_chart`. Note that `st.dataframe` only shows the first ten rows by default and enables scrolling for additional rows. This means adding rows is not as visually apparent as it is with `st.table` or the chart elements. - -```python -import streamlit as st -import pandas as pd -import numpy as np -import time - -df = pd.DataFrame(np.random.randn(15, 3), columns=(["A", "B", "C"])) -my_data_element = st.line_chart(df) - -for tick in range(10): - time.sleep(.5) - add_df = pd.DataFrame(np.random.randn(1, 3), columns=(["A", "B", "C"])) - my_data_element.add_rows(add_df) - -st.button("Regenerate") -``` - ---- - -# Button behavior and examples - -Source: https://docs.streamlit.io/develop/concepts/design/buttons - - -## Summary - -Buttons created with [`st.button`](/develop/api-reference/widgets/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 a displayed element is nested inside `if st.button('Click me'):`, the element will be visible when the button is clicked and disappear as soon as the user takes their next action. This is because the script reruns and the button return value becomes `False`. - -In this guide, we will illustrate the use of buttons and explain common misconceptions. Read on to see a variety of examples that expand on `st.button` using [`st.session_state`](/develop/api-reference/caching-and-state/st.session_state). [Anti-patterns](#anti-patterns) are included at the end. Go ahead and pull up your favorite code editor so you can `streamlit run` the examples as you read. Check out Streamlit's [Basic concepts](/get-started/fundamentals/main-concepts) if you haven't run your own Streamlit scripts yet. - -## When to use `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: - -- Transient messages that immediately disappear. -- Once-per-click processes that saves data to session state, a file, or - a database. - -Bad to nest inside buttons: - -- Displayed items that should persist as the user continues. -- Other widgets which cause the script to rerun when used. -- Processes that neither modify session state nor write to a file/database.\* - -\* This can be appropriate when disposable results are desired. If you -have a "Validate" button, that could be a process conditioned directly on a -button. It could be used to create an alert to say 'Valid' or 'Invalid' with no -need to keep that info. - -## Common logic with buttons - -### Show a temporary message with a button - -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. - -In this example, a user can click a button to check if their `animal` string is in the `animal_shelter` list. When the user clicks "**Check availability**" they will see "We have that animal!" or "We don't have that animal." If they change the animal in [`st.text_input`](/develop/api-reference/widgets/st.text_input), the script reruns and the message disappears until they click "**Check availability**" again. - -```python -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.' -``` - -Note: The above example uses [magic](/develop/api-reference/write-magic/magic) to render the message on the frontend. - -### Stateful button - -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. - -```python -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') -``` - -### Toggle button - -If you want a button to work like a toggle switch, consider using [`st.checkbox`](/develop/api-reference/widgets/st.checkbox). Otherwise, you can use a button with a callback function to reverse a boolean value saved in `st.session_state`. - -In this example, we use `st.button` to toggle another widget on and off. By displaying [`st.slider`](/develop/api-reference/widgets/st.slider) conditionally on a value in `st.session_state`, the user can interact with the slider without it disappearing. - -```python -import streamlit as st - -if 'button' not in st.session_state: - st.session_state.button = False - -def click_button(): - st.session_state.button = not st.session_state.button - -st.button('Click me', on_click=click_button) - -if st.session_state.button: - # The message and nested widget will remain on the page - st.write('Button is on!') - st.slider('Select a value') -else: - st.write('Button is off!') -``` - -Alternatively, you can use the value in `st.session_state` on the slider's `disabled` parameter. - -```python -import streamlit as st - -if 'button' not in st.session_state: - st.session_state.button = False - -def click_button(): - st.session_state.button = not st.session_state.button - -st.button('Click me', on_click=click_button) - -st.slider('Select a value', disabled=st.session_state.button) -``` - -### Buttons to continue or control stages of a process - -Another alternative to nesting content inside a button is to use a value in `st.session_state` that designates the "step" or "stage" of a process. In this example, we have four stages in our script: - -0. Before the user begins. -1. User enters their name. -2. User chooses a color. -3. User gets a thank-you message. - -A button at the beginning advances the stage from 0 to 1. A button at the end resets the stage from 3 to 0. The other widgets used in stage 1 and 2 have callbacks to set the stage. If you have a process with dependant steps and want to keep previous stages visible, such a callback forces a user to retrace subsequent stages if they change an earlier widget. - -```python -import streamlit as st - -if 'stage' not in st.session_state: - st.session_state.stage = 0 - -def set_state(i): - st.session_state.stage = i - -if st.session_state.stage == 0: - st.button('Begin', on_click=set_state, args=[1]) - -if st.session_state.stage >= 1: - name = st.text_input('Name', on_change=set_state, args=[2]) - -if st.session_state.stage >= 2: - st.write(f'Hello {name}!') - color = st.selectbox( - 'Pick a Color', - [None, 'red', 'orange', 'green', 'blue', 'violet'], - on_change=set_state, args=[3] - ) - if color is None: - set_state(2) - -if st.session_state.stage >= 3: - st.write(f':{color}[Thank you!]') - st.button('Start Over', on_click=set_state, args=[0]) -``` - -### Buttons to modify `st.session_state` - -If you modify `st.session_state` inside of a button, you must consider where that button is within the script. - -#### A slight problem - -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. - -```python -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']) -``` - -#### Logic used in a callback - -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. - -```python -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']) -``` - -#### Logic nested in a button with a rerun - -Although callbacks are often preferred to avoid extra reruns, our first 'John Doe'/'Jane Doe' example can be modified by adding [`st.rerun`](/develop/api-reference/execution-flow/st.rerun) instead. If you need to acces data in `st.session_state` before the button that modifies it, you can include `st.rerun` to rerun the script after the change has been committed. This means the script will rerun twice when a button is clicked. - -```python -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' - st.rerun() - -if st.button('John'): - st.session_state['name'] = 'John Doe' - st.rerun() - -st.header(st.session_state['name']) -``` - -### Buttons to modify or reset other widgets - -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. - - - -Don't do this! - -```python -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') -``` - - - -#### Option 1: Use a key for the button and put the logic before the widget - -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. - -```python -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') -``` - -#### Option 2: Use a callback - -```python -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']) -``` - -#### Option 3: Use containers - -By using [`st.container`](/develop/api-reference/layout/st.container) you can have widgets appear in different orders in your script and frontend view (webpage). - -```python -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') -``` - -### Buttons to add other widgets dynamically - -When dynamically adding widgets to the page, make sure to use an index to keep the keys unique and avoid a `DuplicateWidgetID` error. In this example, we define a function `display_input_row` which renders a row of widgets. That function accepts an `index` as a parameter. The widgets rendered by `display_input_row` use `index` within their keys so that `display_input_row` can be executed multiple times on a single script rerun without repeating any widget keys. - -```python -import streamlit as st - -def display_input_row(index): - left, middle, right = st.columns(3) - left.text_input('First', key=f'first_{index}') - middle.text_input('Middle', key=f'middle_{index}') - right.text_input('Last', key=f'last_{index}') - -if 'rows' not in st.session_state: - st.session_state['rows'] = 0 - -def increase_rows(): - st.session_state['rows'] += 1 - -st.button('Add person', on_click=increase_rows) - -for i in range(st.session_state['rows']): - display_input_row(i) - -# Show the results -st.subheader('People') -for i in range(st.session_state['rows']): - st.write( - f'Person {i+1}:', - st.session_state[f'first_{i}'], - st.session_state[f'middle_{i}'], - st.session_state[f'last_{i}'] - ) -``` - -### Buttons to handle expensive or file-writing processes - -When you have expensive processes, set them to run upon clicking a button and save the results into `st.session_state`. This allows you to keep accessing the results of the process without re-executing it unnecessarily. This is especially helpful for processes that save to disk or write to a database. In this example, we have an `expensive_process` that depends on two parameters: `option` and `add`. Functionally, `add` changes the output, but `option` does not`option` is there to provide a parameter - -```python -import streamlit as st -import pandas as pd -import time - -def expensive_process(option, add): - with st.spinner('Processing...'): - time.sleep(5) - df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6], 'C':[7, 8, 9]}) + add - return (df, add) - -cols = st.columns(2) -option = cols[0].selectbox('Select a number', options=['1', '2', '3']) -add = cols[1].number_input('Add a number', min_value=0, max_value=10) - -if 'processed' not in st.session_state: - st.session_state.processed = {} - -# Process and save results -if st.button('Process'): - result = expensive_process(option, add) - st.session_state.processed[option] = result - st.write(f'Option {option} processed with add {add}') - result[0] -``` - -Astute observers may think, "This feels a little like caching." We are only saving results relative to one parameter, but the pattern could easily be expanded to save results relative to both parameters. In that sense, yes, it has some similarities to caching, but also some important differences. When you save results in `st.session_state`, the results are only available to the current user in their current session. If you use [`st.cache_data`](/develop/api-reference/caching-and-state/st.cache_data) instead, the results are available to all users across all sessions. Furthermore, if you want to update a saved result, you have to clear all saved results for that function to do so. - -## Anti-patterns - -Here are some simplified examples of how buttons can go wrong. Be on the lookout for these common mistakes. - -### Buttons nested inside buttons - -```python -import streamlit as st - -if st.button('Button 1'): - st.write('Button 1 was clicked') - if st.button('Button 2'): - # This will never be executed. - st.write('Button 2 was clicked') -``` - -### Other widgets nested inside buttons - -```python -import streamlit as st - -if st.button('Sign up'): - name = st.text_input('Name') - - if name: - # This will never be executed. - st.success(f'Welcome {name}') -``` - -### Nesting a process inside a button without saving to session state - -```python -import streamlit as st -import pandas as pd - -file = st.file_uploader("Upload a file", type="csv") - -if st.button('Get data'): - df = pd.read_csv(file) - # This display will go away with the user's next action. - st.write(df) - -if st.button('Save'): - # This will always error. - df.to_csv('data.csv') -``` - ---- - -# Dataframes - -Source: https://docs.streamlit.io/develop/concepts/design/dataframes - - -Dataframes are a great way to display and edit data in a tabular format. Working with Pandas DataFrames and other tabular data structures is key to data science workflows. If developers and data scientists want to display this data in Streamlit, they have multiple options: `st.dataframe` and `st.data_editor`. If you want to solely display data in a table-like UI, [st.dataframe](/develop/api-reference/data/st.dataframe) is the way to go. If you want to interactively edit data, use [st.data_editor](/develop/api-reference/data/st.data_editor). We explore the use cases and advantages of each option in the following sections. - -## Display dataframes with st.dataframe - -Streamlit can display dataframes in a table-like UI via `st.dataframe` : - -```python -import streamlit as st -import pandas as pd - -df = pd.DataFrame( - [ - {"command": "st.selectbox", "rating": 4, "is_widget": True}, - {"command": "st.balloons", "rating": 5, "is_widget": False}, - {"command": "st.time_input", "rating": 3, "is_widget": True}, - ] -) - -st.dataframe(df, use_container_width=True) -``` - - - -## `st.dataframe` UI features - -`st.dataframe` provides additional functionality by using [glide-data-grid](https://github.com/glideapps/glide-data-grid) under the hood: - -- **Column sorting**: To sort columns, select their headers, or select "**Sort ascending**" or "**Sort descending**" from the header menu ({{ verticalAlign: "-.25em" }} className={{ class: "material-icons-sharp" }}>more_vert - ---- - -# Multithreading in Streamlit - -Source: https://docs.streamlit.io/develop/concepts/design/multithreading - - -Multithreading is a type of concurrency, which improves the efficiency of computer programs. It's a way for processors to multitask. Streamlit uses threads within its architecture, which can make it difficult for app developers to include their own multithreaded processes. Streamlit does not officially support multithreading in app code, but this guide provides information on how it can be accomplished. - -## Prerequisites - -- You should have a basic understanding of Streamlit's [architecture](/develop/concepts/architecture/architecture). - -## When to use multithreading - -Multithreading is just one type of concurrency. Multiprocessing and coroutines are other forms of concurrency. You need to understand how your code is bottlenecked to choose the correct kind of concurrency. - -Multiprocessing is inherently parallel, meaning that resources are split and multiple tasks are performed simultaneously. Therefore, multiprocessing is helpful with compute-bound operations. In contrast, multithreading and coroutines are not inherently parallel and instead allow resource switching. This makes them good choices when your code is stuck _waiting_ for something, like an IO operation. AsyncIO uses coroutines and may be preferable with very slow IO operations. Threading may be preferable with faster IO operations. For a helpful guide to using AsyncIO with Streamlit, see this [Medium article by Sehmi-Conscious Thoughts](https://sehmi-conscious.medium.com/got-that-asyncio-feeling-f1a7c37cab8b). - -Don't forget that Streamlit has [fragments](/develop/concepts/architecture/fragments) and [caching](/develop/concepts/architecture/caching), too! Use caching to avoid unnecessarily repeating computations or IO operations. Use fragments to isolate a bit of code you want to update separately from the rest of the app. You can set fragments to rerun at a specified interval, so they can be used to stream updates to a chart or table. - -## Threads created by Streamlit - -Streamlit creates two types of threads in Python: - -- The **server thread** runs the Tornado web (HTTP + WebSocket) server. -- A **script thread** runs page code one thread for each script run in a session. - -When a user connects to your app, this creates a new session and runs a script thread to initialize the app for that user. As the script thread runs, it renders elements in the user's browser tab and reports state back to the server. When the user interacts with the app, another script thread runs, re-rendering the elements in the browser tab and updating state on the server. - -This is a simplifed illustration to show how Streamlit works: - -![Each user session uses script threads to communicate between the user's front end and the Streamlit server.](/images/concepts/Streamlit-threading.svg) - -## `streamlit.errors.NoSessionContext` - -Many Streamlit commands, including `st.session_state`, expect to be called from a script thread. When Streamlit is running as expected, such commands use the `ScriptRunContext` attached to the script thread to ensure they work within the intended session and update the correct user's view. When those Streamlit commands can't find any `ScriptRunContext`, they raise a `streamlit.errors.NoSessionContext` exception. Depending on your logger settings, you may also see a console message identifying a thread by name and warning, "missing ScriptRunContext!" - -## Creating custom threads - -When you work with IO-heavy operations like remote query or data loading, you may need to mitigate delays. A general programming strategy is to create threads and let them work concurrently. However, if you do this in a Streamlit app, these custom threads may have difficulty interacting with your Streamlit server. - -This section introduces two patterns to let you create custom threads in your Streamlit app. These are only patterns to provide a starting point rather than complete solutions. - -### Option 1: Do not use Streamlit commands within a custom thread - -If you don't call Streamlit commands from a custom thread, you can avoid the problem entirely. Luckily Python threading provides ways to start a thread and collect its result from another thread. - -In the following example, five custom threads are created from the script thread. After the threads are finished running, their results are displayed in the app. - -```python -import streamlit as st -import time -from threading import Thread - - -class WorkerThread(Thread): - def __init__(self, delay): - super().__init__() - self.delay = delay - self.return_value = None - - def run(self): - start_time = time.time() - time.sleep(self.delay) - end_time = time.time() - self.return_value = f"start: {start_time}, end: {end_time}" - - -delays = [5, 4, 3, 2, 1] -threads = [WorkerThread(delay) for delay in delays] -for thread in threads: - thread.start() -for thread in threads: - thread.join() -for i, thread in enumerate(threads): - st.header(f"Thread {i}") - st.write(thread.return_value) - -st.button("Rerun") -``` - - - -If you want to display results in your app as various custom threads finish running, use containers. In the following example, five custom threads are created similarly to the previous example. However, five containers are initialized before running the custom threads and a `while` loop is used to display results as they become available. Since the Streamlit `write` command is called outside of the custom threads, this does not raise an exception. - -```python -import streamlit as st -import time -from threading import Thread - - -class WorkerThread(Thread): - def __init__(self, delay): - super().__init__() - self.delay = delay - self.return_value = None - - def run(self): - start_time = time.time() - time.sleep(self.delay) - end_time = time.time() - self.return_value = f"start: {start_time}, end: {end_time}" - - -delays = [5, 4, 3, 2, 1] -result_containers = [] -for i, delay in enumerate(delays): - st.header(f"Thread {i}") - result_containers.append(st.container()) - -threads = [WorkerThread(delay) for delay in delays] -for thread in threads: - thread.start() -thread_lives = [True] * len(threads) - -while any(thread_lives): - for i, thread in enumerate(threads): - if thread_lives[i] and not thread.is_alive(): - result_containers[i].write(thread.return_value) - thread_lives[i] = False - time.sleep(0.5) - -for thread in threads: - thread.join() - -st.button("Rerun") -``` - - - -### Option 2: Expose `ScriptRunContext` to the thread - -If you want to call Streamlit commands from within your custom threads, you must attach the correct `ScriptRunContext` to the thread. - - - -- This is not officially supported and may change in a future version of Streamlit. -- This may not work with all Streamlit commands. -- Ensure custom threads do not outlive the script thread owning the `ScriptRunContext`. Leaking of `ScriptRunContext` may cause security vulnerabilities, fatal errors, or unexpected behavior. - - - -In the following example, a custom thread with `ScriptRunContext` attached can call `st.write` without a warning. - -```python -import streamlit as st -from streamlit.runtime.scriptrunner import add_script_run_ctx, get_script_run_ctx -import time -from threading import Thread - - -class WorkerThread(Thread): - def __init__(self, delay, target): - super().__init__() - self.delay = delay - self.target = target - - def run(self): - # runs in custom thread, but can call Streamlit APIs - start_time = time.time() - time.sleep(self.delay) - end_time = time.time() - self.target.write(f"start: {start_time}, end: {end_time}") - - -delays = [5, 4, 3, 2, 1] -result_containers = [] -for i, delay in enumerate(delays): - st.header(f"Thread {i}") - result_containers.append(st.container()) - -threads = [ - WorkerThread(delay, container) - for delay, container in zip(delays, result_containers) -] -for thread in threads: - add_script_run_ctx(thread, get_script_run_ctx()) - thread.start() - -for thread in threads: - thread.join() - -st.button("Rerun") -``` - - - ---- - -# Using custom Python classes in your Streamlit app - -Source: https://docs.streamlit.io/develop/concepts/design/custom-classes - - -If you are building a complex Streamlit app or working with existing code, you may have custom Python classes defined in your script. Common examples include the following: - -- Defining a `@dataclass` to store related data within your app. -- Defining an `Enum` class to represent a fixed set of options or values. -- Defining custom interfaces to external services or databases not covered by [`st.connection`](/develop/api-reference/connections/st.connection). - -Because Streamlit reruns your script after every user interaction, custom classes may be redefined multiple times within the same Streamlit session. This may result in unwanted effects, especially with class and instance comparisons. Read on to understand this common pitfall and how to avoid it. - -We begin by covering some general-purpose patterns you can use for different types of custom classes, and follow with a few more technical details explaining why this matters. Finally, we go into more detail about [Using `Enum` classes](#using-enum-classes-in-streamlit) specifically, and describe a configuration option which can make them more convenient. - -## Patterns to define your custom classes - -### Pattern 1: Define your class in a separate module - -This is the recommended, general solution. If possible, move class definitions into their own module file and import them into your app script. As long as you are not editing the files that define your app, Streamlit will not re-import those classes with each rerun. Therefore, if a class is defined in an external file and imported into your script, the class will not be redefined during the session, unless you are actively editing your app. - -#### Example: Move your class definition - -Try running the following Streamlit app where `MyClass` is defined within the page's script. `isinstance()` will return `True` on the first script run then return `False` on each rerun thereafter. - -```python -# app.py -import streamlit as st - -# MyClass gets redefined every time app.py reruns -class MyClass: - def __init__(self, var1, var2): - self.var1 = var1 - self.var2 = var2 - -if "my_instance" not in st.session_state: - st.session_state.my_instance = MyClass("foo", "bar") - -# Displays True on the first run then False on every rerun -st.write(isinstance(st.session_state.my_instance, MyClass)) - -st.button("Rerun") -``` - -If you move the class definition out of `app.py` into another file, you can make `isinstance()` consistently return `True`. Consider the following file structure: - -``` -myproject/ -โ”œโ”€โ”€ my_class.py -โ””โ”€โ”€ app.py -``` - -```python -# my_class.py -class MyClass: - def __init__(self, var1, var2): - self.var1 = var1 - self.var2 = var2 -``` - -```python -# app.py -import streamlit as st -from my_class import MyClass # MyClass doesn't get redefined with each rerun - -if "my_instance" not in st.session_state: - st.session_state.my_instance = MyClass("foo", "bar") - -# Displays True on every rerun -st.write(isinstance(st.session_state.my_instance, MyClass)) - -st.button("Rerun") -``` - -Streamlit only reloads code in imported modules when it detects the code has changed. Thus, if you are actively editing your app code, you may need to start a new session or restart your Streamlit server to avoid an undesirable class redefinition. - -### Pattern 2: Force your class to compare internal values - -For classes that store data (like [dataclasses](https://docs.python.org/3/library/dataclasses.html)), you may be more interested in comparing the internally stored values rather than the class itself. If you define a custom `__eq__` method, you can force comparisons to be made on the internally stored values. - -#### Example: Define `__eq__` - -Try running the following Streamlit app and observe how the comparison is `True` on the first run then `False` on every rerun thereafter. - -```python -import streamlit as st -from dataclasses import dataclass - -@dataclass -class MyDataclass: - var1: int - var2: float - -if "my_dataclass" not in st.session_state: - st.session_state.my_dataclass = MyDataclass(1, 5.5) - -# Displays True on the first run the False on every rerun -st.session_state.my_dataclass == MyDataclass(1, 5.5) - -st.button("Rerun") -``` - -Since `MyDataclass` gets redefined with each rerun, the instance stored in Session State will not be equal to any instance defined in a later script run. You can fix this by forcing a comparison of internal values as follows: - -```python -import streamlit as st -from dataclasses import dataclass - -@dataclass -class MyDataclass: - var1: int - var2: float - - def __eq__(self, other): - # An instance of MyDataclass is equal to another object if the object - # contains the same fields with the same values - return (self.var1, self.var2) == (other.var1, other.var2) - -if "my_dataclass" not in st.session_state: - st.session_state.my_dataclass = MyDataclass(1, 5.5) - -# Displays True on every rerun -st.session_state.my_dataclass == MyDataclass(1, 5.5) - -st.button("Rerun") -``` - -The default Python `__eq__` implementation for a regular class or `@dataclass` depends on the in-memory ID of the class or class instance. To avoid problems in Streamlit, your custom `__eq__` method should not depend the `type()` of `self` and `other`. - -### Pattern 3: Store your class as serialized data - -Another option for classes that store data is to define serialization and deserialization methods like `to_str` and `from_str` for your class. You can use these to store class instance data in `st.session_state` rather than storing the class instance itself. Similar to pattern 2, this is a way to force comparison of the internal data and bypass the changing in-memory IDs. - -#### Example: Save your class instance as a string - -Using the same example from pattern 2, this can be done as follows: - -```python -import streamlit as st -from dataclasses import dataclass - -@dataclass -class MyDataclass: - var1: int - var2: float - - def to_str(self): - return f"{self.var1},{self.var2}" - - @classmethod - def from_str(cls, serial_str): - values = serial_str.split(",") - var1 = int(values[0]) - var2 = float(values[1]) - return cls(var1, var2) - -if "my_dataclass" not in st.session_state: - st.session_state.my_dataclass = MyDataclass(1, 5.5).to_str() - -# Displays True on every rerun -MyDataclass.from_str(st.session_state.my_dataclass) == MyDataclass(1, 5.5) - -st.button("Rerun") -``` - -### Pattern 4: Use caching to preserve your class - -For classes that are used as resources (database connections, state managers, APIs), consider using the cached singleton pattern. Use `@st.cache_resource` to decorate a `@staticmethod` of your class to generate a single, cached instance of the class. For example: - -```python -import streamlit as st - -class MyResource: - def __init__(self, api_url: str): - self._url = api_url - - @st.cache_resource(ttl=300) - @staticmethod - def get_resource_manager(api_url: str): - return MyResource(api_url) - -# This is cached until Session State is cleared or 5 minutes has elapsed. -resource_manager = MyResource.get_resource_manager("http://example.com/api/") -``` - -When you use one of Streamlit's caching decorators on a function, Streamlit doesn't use the function object to look up cached values. Instead, Streamlit's caching decorators index return values using the function's qualified name and module. So, even though Streamlit redefines `MyResource` with each script run, `st.cache_resource` is unaffected by this. `get_resource_manager()` will return its cached value with each rerun, until the value expires. - -## Understanding how Python defines and compares classes - -So what's really happening here? We'll consider a simple example to illustrate why this is a pitfall. Feel free to skip this section if you don't want to deal more details. You can jump ahead to learn about [Using `Enum` classes](#using-enum-classes-in-streamlit). - -### Example: What happens when you define the same class twice? - -Set aside Streamlit for a moment and think about this simple Python script: - -```python -from dataclasses import dataclass - -@dataclass -class Student: - student_id: int - name: str - -Marshall_A = Student(1, "Marshall") -Marshall_B = Student(1, "Marshall") - -# This is True (because a dataclass will compare two of its instances by value) -Marshall_A == Marshall_B - -# Redefine the class -@dataclass -class Student: - student_id: int - name: str - -Marshall_C = Student(1, "Marshall") - -# This is False -Marshall_A == Marshall_C -``` - -In this example, the dataclass `Student` is defined twice. All three Marshalls have the same internal values. If you compare `Marshall_A` and `Marshall_B` they will be equal because they were both created from the first definition of `Student`. However, if you compare `Marshall_A` and `Marshall_C` they will not be equal because `Marshall_C` was created from the _second_ definition of `Student`. Even though both `Student` dataclasses are defined exactly the same, they have different in-memory IDs and are therefore different. - -### What's happening in Streamlit? - -In Streamlit, you probably don't have the same class written twice in your page script. However, the rerun logic of Streamlit creates the same effect. Let's use the above example for an analogy. If you define a class in one script run and save an instance in Session State, then a later rerun will redefine the class and you may end up comparing a `Mashall_C` in your rerun to a `Marshall_A` in Session State. Since widgets rely on Session State under the hood, this is where things can get confusing. - -## How Streamlit widgets store options - -Several Streamlit UI elements, such as `st.selectbox` or `st.radio`, accept multiple-choice options via an `options` argument. The user of your application can typically select one or more of these options. The selected value is returned by the widget function. For example: - -```python -number = st.selectbox("Pick a number, any number", options=[1, 2, 3]) -# number == whatever value the user has selected from the UI. -``` - -When you call a function like `st.selectbox` and pass an `Iterable` to `options`, the `Iterable` and current selection are saved into a hidden portion of [Session State](/develop/concepts/architecture/session-state) called the Widget Metadata. - -When the user of your application interacts with the `st.selectbox` widget, the broswer sends the index of their selection to your Streamlit server. This index is used to determine which values from the original `options` list, _saved in the Widget Metadata from the previous page execution_, are returned to your application. - -The key detail is that the value returned by `st.selectbox` (or similar widget function) is from an `Iterable` saved in Session State during a _previous_ execution of the page, NOT the values passed to `options` on the _current_ execution. There are a number of architectural reasons why Streamlit is designed this way, which we won't go into here. However, **this** is how we end up comparing instances of different classes when we think we are comparing instances of the same class. - -### A pathological example - -The above explanation might be a bit confusing, so here's a pathological example to illustrate the idea. - -```python -import streamlit as st -from dataclasses import dataclass - -@dataclass -class Student: - student_id: int - name: str - -Marshall_A = Student(1, "Marshall") -if "B" not in st.session_state: - st.session_state.B = Student(1, "Marshall") -Marshall_B = st.session_state.B - -options = [Marshall_A,Marshall_B] -selected = st.selectbox("Pick", options) - -# This comparison does not return expected results: -selected == Marshall_A -# This comparison evaluates as expected: -selected == Marshall_B -``` - -As a final note, we used `@dataclass` in the example for this section to illustrate a point, but in fact it is possible to encounter these same problems with classes, in general. Any class which checks class identity inside of a comparison operatorsuch as `__eq__` or `__gt__`can exhibit these issues. - -## Using `Enum` classes in Streamlit - -The [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum) class from the Python standard library is a powerful way to define custom symbolic names that can be used as options for `st.multiselect` or `st.selectbox` in place of `str` values. - -For example, you might add the following to your streamlit page: - -```python -from enum import Enum -import streamlit as st - -# class syntax -class Color(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 - -selected_colors = set(st.multiselect("Pick colors", options=Color)) - -if selected_colors == {Color.RED, Color.GREEN}: - st.write("Hooray, you found the color YELLOW!") -``` - -If you're using the latest version of Streamlit, this Streamlit page will work as it appears it should. When a user picks both `Color.RED` and `Color.GREEN`, they are shown the special message. - -However, if you've read the rest of this page you might notice something tricky going on. Specifically, the `Enum` class `Color` gets redefined every time this script is run. In Python, if you define two `Enum` classes with the same class name, members, and values, the classes and their members are still considered unique from each other. This _should_ cause the above `if` condition to always evaluate to `False`. In any script rerun, the `Color` values returned by `st.multiselect` would be of a different class than the `Color` defined in that script run. - -If you run the snippet above with Streamlit version 1.28.0 or less, you will not be able see the special message. Thankfully, as of version 1.29.0, Streamlit introduced a configuration option to greatly simplify the problem. That's where the enabled-by-default `enumCoercion` configuration option comes in. - -### Understanding the `enumCoercion` configuration option - -When `enumCoercion` is enabled, Streamlit tries to recognize when you are using an element like `st.multiselect` or `st.selectbox` with a set of `Enum` members as options. - -If Streamlit detects this, it will convert the widget's returned values to members of the `Enum` class defined in the latest script run. This is something we call automatic `Enum` coercion. - -This behavior is [configurable](/develop/concepts/configuration) via the `enumCoercion` setting in your Streamlit `config.toml` file. It is enabled by default, and may be disabled or set to a stricter set of matching criteria. - -If you find that you still encounter issues with `enumCoercion` enabled, consider using the [custom class patterns](#patterns-to-define-your-custom-classes) described above, such as moving your `Enum` class definition to a separate module file. - ---- - -# Working with timezones - -Source: https://docs.streamlit.io/develop/concepts/design/timezone-handling - - -In general, working with timezones can be tricky. Your Streamlit app users are not necessarily in the same timezone as the server running your app. It is especially true of public apps, where anyone in the world (in any timezone) can access your app. As such, it is crucial to understand how Streamlit handles timezones, so you can avoid unexpected behavior when displaying `datetime` information. - -## How Streamlit handles timezones - -Streamlit always shows `datetime` information on the frontend with the same information as its corresponding `datetime` instance in the backend. I.e., date or time information does not automatically adjust to the users' timezone. We distinguish between the following two cases: - -### **`datetime` instance without a timezone (naive)** - -When you provide a `datetime` instance _without specifying a timezone_, the frontend shows the `datetime` instance without timezone information. For example (this also applies to other widgets like [`st.dataframe`](/develop/api-reference/data/st.dataframe)): - -```python -import streamlit as st -from datetime import datetime - -st.write(datetime(2020, 1, 10, 10, 30)) -# Outputs: 2020-01-10 10:30:00 -``` - -Users of the above app always see the output as `2020-01-10 10:30:00`. - -### **`datetime` instance with a timezone** - -When you provide a `datetime` instance _and specify a timezone_, the frontend shows the `datetime` instance in that same timezone. For example (this also applies to other widgets like [`st.dataframe`](/develop/api-reference/data/st.dataframe)): - -```python -import streamlit as st -from datetime import datetime -import pytz - -st.write(datetime(2020, 1, 10, 10, 30, tzinfo=pytz.timezone("EST"))) -# Outputs: 2020-01-10 10:30:00-05:00 -``` - -Users of the above app always see the output as `2020-01-10 10:30:00-05:00`. - -In both cases, neither the date nor time information automatically adjusts to the users' timezone on the frontend. What users see is identical to the corresponding `datetime` instance in the backend. It is currently not possible to automatically adjust the date or time information to the timezone of the users viewing the app. - - - -The legacy version of the `st.dataframe` has issues with timezones. We do not plan to roll out additional fixes or enhancements for the legacy dataframe. If you need stable timezone support, please consider switching to the arrow serialization by changing the [config setting](/develop/concepts/configuration), _config.dataFrameSerialization = "arrow"_. - - - ---- - -# Working with connections, secrets, and user authentication - -Source: https://docs.streamlit.io/develop/concepts/connections - - - - -
Connecting to data
- -Connect your app to remote data or a third-party API. - -
- -
Secrets managements
- -Set up your development environement and design your app to handle secrets securely. - -
- -
Authentication and user information
- -Use an OpenID Connect provider to authenticate users and personalize your app. - -
- -
Security reminders
- -Check out a few reminders to follow best practices and avoid security mistakes. - -
-
- ---- - -# Connecting to data - -Source: https://docs.streamlit.io/develop/concepts/connections/connecting-to-data - - -Most Streamlit apps need some kind of data or API access to be useful - either retrieving data to view or saving the results of some user action. This data or API is often part of some remote service, database, or other data source. - -**Anything you can do with Python, including data connections, will generally work in Streamlit**. Streamlit's [tutorials](/develop/tutorials/databases) are a great starting place for many data sources. However: - -- Connecting to data in a Python application is often tedious and annoying. -- There are specific considerations for connecting to data from streamlit apps, such as caching and secrets management. - -**Streamlit provides [`st.connection()`](/develop/api-reference/connections/st.connection) to more easily connect your Streamlit apps to data and APIs with just a few lines of code**. This page provides a basic example of using the feature and then focuses on advanced usage. - -For a comprehensive overview of this feature, check out this video tutorial by Joshua Carroll, Streamlit's Product Manager for Developer Experience. You'll learn about the feature's utility in creating and managing data connections within your apps by using real-world examples. - - - -## Basic usage - -For basic startup and usage examples, read up on the relevant [data source tutorial](/develop/tutorials/databases). Streamlit has built-in connections to SQL dialects and Snowflake. We also maintain installable connections for [Cloud File Storage](https://github.com/streamlit/files-connection) and [Google Sheets](https://github.com/streamlit/gsheets-connection). - -If you are just starting, the best way to learn is to pick a data source you can access and get a minimal example working from one of the pages above ๐Ÿ‘†. Here, we will provide an ultra-minimal usage example for using a SQLite database. From there, the rest of this page will focus on advanced usage. - -### A simple starting point - using a local SQLite database - -A [local SQLite database](https://sqlite.org/index.html) could be useful for your app's semi-persistent data storage. - - - -Community Cloud apps do not guarantee the persistence of local file storage, so the platform may delete data stored using this technique at any time. - - - -To see the example below running live, check out the interactive demo below: - - - -#### Step 1: Install prerequisite library - SQLAlchemy - -All SQLConnections in Streamlit use SQLAlchemy. For most other SQL dialects, you also need to install the driver. But the [SQLite driver ships with python3](https://docs.python.org/3/develop/sqlite3.html), so it isn't necessary. - -```bash -pip install SQLAlchemy==1.4.0 -``` - -#### Step 2: Set a database URL in your Streamlit secrets.toml file - -Create a directory and file `.streamlit/secrets.toml` in the same directory your app will run from. Add the following to the file. - -```toml -# .streamlit/secrets.toml - -[connections.pets_db] -url = "sqlite:///pets.db" -``` - -#### Step 3: Use the connection in your app - -The following app creates a connection to the database, uses it to create a table and insert some data, then queries the data back and displays it in a data frame. - -```python -# streamlit_app.py - -import streamlit as st - -# Create the SQL connection to pets_db as specified in your secrets file. -conn = st.connection('pets_db', type='sql') - -# Insert some data with conn.session. -with conn.session as s: - s.execute('CREATE TABLE IF NOT EXISTS pet_owners (person TEXT, pet TEXT);') - s.execute('DELETE FROM pet_owners;') - pet_owners = {'jerry': 'fish', 'barbara': 'cat', 'alex': 'puppy'} - for k in pet_owners: - s.execute( - 'INSERT INTO pet_owners (person, pet) VALUES (:owner, :pet);', - params=dict(owner=k, pet=pet_owners[k]) - ) - s.commit() - -# Query and display the data you inserted -pet_owners = conn.query('select * from pet_owners') -st.dataframe(pet_owners) -``` - -In this example, we didn't set a `ttl=` value on the call to [`conn.query()`](/develop/api-reference/connections/st.connections.sqlconnection#sqlconnectionquery), meaning Streamlit caches the result indefinitely as long as the app server runs. - -Now, on to more advanced topics! ๐Ÿš€ - -## Advanced topics - -### Global secrets, managing multiple apps and multiple data stores - -Streamlit [supports a global secrets file](/develop/concepts/connections/secrets-management) specified in the user's home directory, such as `~/.streamlit/secrets.toml`. If you build or manage multiple apps, we recommend using a global credential or secret file for local development across apps. With this approach, you only need to set up and manage your credentials in one place, and connecting a new app to your existing data sources is effectively a one-liner. It also reduces the risk of accidentally checking in your credentials to git since they don't need to exist in the project repository. - -For cases where you have multiple similar data sources that you connect to during local development (such as a local vs. staging database), you can define different connection sections in your secrets or credentials file for different environments and then decide which to use at runtime. `st.connection` supports this with the _`name=env:`_ syntax. - -E.g., say I have a local and a staging MySQL database and want to connect my app to either at different times. I could create a global secrets file like this: - -```toml -# ~/.streamlit/secrets.toml - -[connections.local] -url = "mysql://me:****@localhost:3306/local_db" - -[connections.staging] -url = "mysql://jdoe:******@staging.acmecorp.com:3306/staging_db" -``` - -Then I can configure my app connection to take its name from a specified environment variable - -```python -# streamlit_app.py -import streamlit as st - -conn = st.connection("env:DB_CONN", "sql") -df = conn.query("select * from mytable") -# ... -``` - -Now I can specify whether to connect to local or staging at runtime by setting the `DB_CONN` environment variable. - -```bash -# connect to local -DB_CONN=local streamlit run streamlit_app.py - -# connect to staging -DB_CONN=staging streamlit run streamlit_app.py -``` - -### Advanced SQLConnection configuration - -The [SQLConnection](/develop/api-reference/connections/st.connections.sqlconnection) configuration uses SQLAlchemy `create_engine()` function. It will take a single URL argument or attempt to construct a URL from several parts (username, database, host, and so on) using [`SQLAlchemy.engine.URL.create()`](https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.engine.URL.create). - -Several popular SQLAlchemy dialects, such as Snowflake and Google BigQuery, can be configured using additional arguments to `create_engine()` besides the URL. These can be passed as `**kwargs` to the [st.connection](/develop/api-reference/connections/st.connection) call directly or specified in an additional secrets section called `create_engine_kwargs`. - -E.g. snowflake-sqlalchemy takes an additional [`connect_args`](https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine.params.connect_args) argument as a dictionary for configuration that isnโ€™t supported in the URL. These could be specified as follows: - -```toml -# .streamlit/secrets.toml - -[connections.snowflake] -url = "snowflake://@/" - -[connections.snowflake.create_engine_kwargs.connect_args] -authenticator = "externalbrowser" -warehouse = "xxx" -role = "xxx" -``` - -```python -# streamlit_app.py - -import streamlit as st - -# url and connect_args from secrets.toml above are picked up and used here -conn = st.connection("snowflake", "sql") -# ... -``` - -Alternatively, this could be specified entirely in `**kwargs`. - -```python -# streamlit_app.py - -import streamlit as st - -# secrets.toml is not needed -conn = st.connection( - "snowflake", - "sql", - url = "snowflake://@/", - connect_args = dict( - authenticator = "externalbrowser", - warehouse = "xxx", - role = "xxx", - ) -) -# ... -``` - -You can also provide both kwargs and secrets.toml values, and they will be merged (typically, kwargs take precedence). - -### Connection considerations in frequently used or long-running apps - -By default, connection objects are cached without expiration using [`st.cache_resource`](/develop/api-reference/caching-and-state/st.cache_resource). In most cases this is desired. You can do `st.connection('myconn', type=MyConnection, ttl=)` if you want the connection object to expire after some time. - -Many connection types are expected to be long-running or completely stateless, so expiration is unnecessary. Suppose a connection becomes stale (such as a cached token expiring or a server-side connection being closed). In that case, every connection has a `reset()` method, which will invalidate the cached version and cause Streamlit to recreate the connection the next time it is retrieved - -Convenience methods like `query()` and `read()` will typically cache results by default using [`st.cache_data`](/develop/api-reference/caching-and-state/st.cache_data) without an expiration. When an app can run many different read operations with large results, it can cause high memory usage over time and results to become stale in a long-running app, the same as with any other usage of `st.cache_data`. For production use cases, we recommend setting an appropriate `ttl` on these read operations, such as `conn.read('path/to/file', ttl="1d")`. Refer to [Caching](/develop/concepts/architecture/caching) for more information. - -For apps that could get significant concurrent usage, ensure that you understand any thread safety implications of your connection, particularly when using a connection built by a third party. Connections built by Streamlit should provide thread-safe operations by default. - -### Build your own connection - -Building your own basic connection implementation using an existing driver or SDK is quite straightforward in most cases. However, you can add more complex functionality with further effort. This custom implementation can be a great way to extend support to a new data source and contribute to the Streamlit ecosystem. - -Maintaining a tailored internal Connection implementation across many apps can be a powerful practice for organizations with frequently used access patterns and data sources. - -Check out the [Build your own Connection page](https://experimental-connection.streamlit.app/Build_your_own) in the st.experimental connection demo app below for a quick tutorial and working implementation. This demo builds a minimal but very functional Connection on top of DuckDB. - - - -The typical steps are: - -1. Declare the Connection class, extending [`ExperimentalBaseConnection`](/develop/api-reference/connections/st.connections.experimentalbaseconnection) with the type parameter bound to the underlying connection object: - - ```python - from streamlit.connections import ExperimentalBaseConnection - import duckdb - - class DuckDBConnection(ExperimentalBaseConnection[duckdb.DuckDBPyConnection]) - ``` - -2. Implement the `_connect` method that reads any kwargs, external config/credential locations, and Streamlit secrets to initialize the underlying connection: - - ```python - def _connect(self, **kwargs) -> duckdb.DuckDBPyConnection: - if 'database' in kwargs: - db = kwargs.pop('database') - else: - db = self._secrets['database'] - return duckdb.connect(database=db, **kwargs) - ``` - -3. Add useful helper methods that make sense for your connection (wrapping them in `st.cache_data` where caching is desired) - -### Connection-building best practices - -We recommend applying the following best practices to make your Connection consistent with the Connections built into Streamlit and the wider Streamlit ecosystem. These practices are especially important for Connections that you intend to distribute publicly. - -1. **Extend existing drivers or SDKs, and default to semantics that makes sense for their existing users.** - - You should rarely need to implement complex data access logic from scratch when building a Connection. Use existing popular Python drivers and clients whenever possible. Doing so makes your Connection easier to maintain, more secure, and enables users to get the latest features. E.g. [SQLConnection](/develop/api-reference/connections/st.connections.sqlconnection) extends SQLAlchemy, [FileConnection](https://github.com/streamlit/files-connection) extends [fsspec](https://filesystem-spec.readthedocs.io/en/latest/), [GsheetsConnection](https://github.com/streamlit/gsheets-connection) extends [gspread](https://docs.gspread.org/en/latest/), etc. - - Consider using access patterns, method/argument naming, and return values that are consistent with the underlying package and familiar to existing users of that package. - -2. **Intuitive, easy to use read methods.** - - Much of the power of st.connection is providing intuitive, easy-to-use read methods that enable app developers to get started quickly. Most connections should expose at least one read method that is: - - - Named with a simple verb, like `read()`, `query()`, or `get()` - - Wrapped by `st.cache_data` by default, with at least `ttl=` argument supported - - If the result is in a tabular format, it returns a pandas DataFrame - - Provides commonly used keyword arguments (such as paging or formatting) with sensible defaults - ideally, the common case requires only 1-2 arguments. - -3. **Config, secrets, and precedence in `_connect` method.** - - Every Connection should support commonly used connection parameters provided via Streamlit secrets and keyword arguments. The names should match the ones used when initializing or configuring the underlying package. - - Additionally, where relevant, Connections should support data source specific configuration through existing standard environment variables or config / credential files. In many cases, the underlying package provides constructors or factory functions that already handle this easily. - - When you can specify the same connection parameters in multiple places, we recommend using the following precedence order when possible (highest to lowest): - - - Keyword arguments specified in the code - - Streamlit secrets - - data source specific configuration (if relevant) - -4. **Handling thread safety and stale connections.** - - Connections should provide thread-safe operations when practical (which should be most of the time) and clearly document any considerations around this. Most underlying drivers or SDKs should provide thread-safe objects or methods - use these when possible. - - If the underlying driver or SDK has a risk of stateful connection objects becoming stale or invalid, consider building a low impact health check or reset/retry pattern into the access methods. The SQLConnection built into Streamlit has a good example of this pattern using [tenacity](https://tenacity.readthedocs.io/) and the built-in [Connection.reset()](/develop/api-reference/connections/st.connections.sqlconnection#sqlconnectionreset) method. An alternate approach is to encourage developers to set an appropriate TTL on the `st.connection()` call to ensure it periodically reinitializes the connection object. - ---- - -# Secrets management - -Source: https://docs.streamlit.io/develop/concepts/connections/secrets-management - - -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. - - - -Existing secrets management tools, such as [dotenv files](https://pypi.org/project/python-dotenv/), [AWS credentials files](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials), [Google Cloud Secret Manager](https://pypi.org/project/google-cloud-secret-manager/), or [Hashicorp Vault](https://www.vaultproject.io/use-cases/secrets-management), will work fine in Streamlit. We just add native secrets management for times when it's useful. - - - -## How to use secrets management - -### Develop locally and set up secrets - -Streamlit provides two ways to set up secrets locally usingย [TOML](https://toml.io/en/latest)ย format: - -1. In a **global secrets file** at `~/.streamlit/secrets.toml` for macOS/Linux or `%userprofile%/.streamlit/secrets.toml` for Windows: - - ```toml - # Everything in this section will be available as an environment variable - db_username = "Jane" - db_password = "mypassword" - - # 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 in this doc. - [my_other_secrets] - things_i_like = ["Streamlit", "Python"] - ``` - - If you use the global secrets file, you don't have to duplicate secrets across several project-level files if multiple Streamlit apps share the same secrets. - -2. In a **per-project secrets file** at `$CWD/.streamlit/secrets.toml`, where `$CWD` is the folder you're running Streamlit from. If both a global secrets file and a per-project secrets file exist, _secrets in the per-project file overwrite those defined in the global file_. - - - -Add this file to your `.gitignore` so you don't commit your secrets! - - - -### Use secrets in your app - -Access 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. - -```python -import streamlit as st - -# Everything is accessible via the st.secrets dict: - -st.write("DB username:", st.secrets["db_username"]) -st.write("DB password:", st.secrets["db_password"]) - -# And the root-level secrets are also accessible as environment variables: - -import os - -st.write( - "Has environment variables been set:", - os.environ["db_username"] == st.secrets["db_username"], -) -``` - - - -You can access `st.secrets` via attribute notation (e.g. `st.secrets.key`), in addition to key notation (e.g. `st.secrets["key"]`) โ€” like [st.session_state](/develop/api-reference/caching-and-state/st.session_state). - - - -You can even compactly use TOML sections to pass multiple secrets as a single attribute. Consider the following secrets: - -```toml -[db_credentials] -username = "my_username" -password = "my_password" -``` - -Rather than passing each secret as attributes in a function, you can more compactly pass the section to achieve the same result. See the notional code below, which uses the secrets above: - -```python -# Verbose version -my_db.connect(username=st.secrets.db_credentials.username, password=st.secrets.db_credentials.password) - -# Far more compact version! -my_db.connect(**st.secrets.db_credentials) -``` - -### Error handling - -Here are some common errors you might encounter when using secrets management. - -- If a `.streamlit/secrets.toml` is created _while_ the app is running, the server needs to be restarted for changes to be reflected in the app. -- If you try accessing a secret, but no `secrets.toml` file exists, Streamlit will raise a `FileNotFoundError` exception: - Secrets management FileNotFoundError -- If you try accessing a secret that doesn't exist, Streamlit will raise a `KeyError` exception: - - ```python - import streamlit as st - - st.write(st.secrets["nonexistent_key"]) - ``` - - Secrets management KeyError - -### Use secrets on Streamlit Community Cloud - -When you deploy your app to [Streamlit Community Cloud](https://streamlit.io/cloud), you can use the same secrets management workflow as you would locally. However, you'll need to also set up your secrets in the Community Cloud Secrets Management console. Learn how to do so via the Cloud-specific [Secrets management](/deploy/streamlit-community-cloud/deploy-your-app/secrets-management) documentation. - ---- - -# User authentication and information - -Source: https://docs.streamlit.io/develop/concepts/connections/authentication - - -Personalizing your app for your users is a great way to make your app more engaging. - -User authentication and personalization unlocks a plethora of use cases for developers, including controls for admins, a personalized stock ticker, or a chatbot app with a saved history between sessions. - -Before reading this guide, you should have a basic understanding of [secrets management](/develop/concepts/connections/secrets-management). - -## OpenID Connect - -Streamlit supports user authentication with OpenID Connect (OIDC), which is an authentication protocol built on top of OAuth 2.0. OIDC supports authentication, but not authorization: that is, OIDC connections tell you _who_ a user is (authentication), but don't give you the authority to _impersonate_ them (authorization). If you need to connect with a generic OAuth 2.0 provider or have your app to act on behalf of a user, consider using or creating a custom component. - -Some popular OIDC providers are: - -- [Google Identity](https://developers.google.com/identity/openid-connect/openid-connect) -- [Microsoft Entra ID](https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings) -- [Okta](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_oidc.htm) -- [Auth0](https://auth0.com/docs/get-started/auth0-overview/create-applications/regular-web-apps) - -## `st.login()`, `st.user`, and `st.logout()` - -There are three commands involved with user authentication: - -- [`st.login()`](/develop/api-reference/user/st.login) redirects the user to your identity provider. After they log in, Streamlit stores an identity cookie and then redirects them to the homepage of your app in a new session. -- [`st.user`](/develop/api-reference/user/st.user) is a dict-like object for accessing user information. It has a persistent attribute, `.is_logged_in`, which you can check for the user's login status. When they are logged in, other attributes are available per your identity provider's configuration. -- [`st.logout()`](/develop/api-reference/user/st.logout) removes the identity cookie from the user's browser and redirects them to the homepage of your app in a new session. - -## User cookies and logging out - -Streamlit checks for the identity cookie at the beginning of each new session. If a user logs in to your app in one tab and then opens a new tab, they will automatically be logged in to your app in the new tab. When you call `st.logout()` in a user session, Streamlit removes the identity cookie and starts a new session. This logs the user out from the current session. However, if they were logged in to other sessions already, they will remain logged in within those sessions. The information in `st.user` is updated at the beginning of a session (which is why `st.login()` and `st.logout()` both start new sessions after saving or deleting the identity cookie). - -If a user closes your app without logging out, the identity cookie will expire after 30 days. This expiration time is not configurable and is not tied to any expiration time that may be returned in your user's identity token. If you need to prevent persistent authentication in your app, check the expiration information returned by the identity provider in `st.user` and manually call `st.logout()` when needed. - -Streamlit does not modify or delete any cookies saved directly by your identity provider. For example, if you use Google as your identity provider and a user logs in to your app with Google, they will remain logged in to their Google account after they log out of your app with `st.logout()`. - -## Setting up an identity provider - -In order to use an identity provider, you must first configure your identity provider through an admin account. This typically involves setting up a client or application within the identity provider's system. Follow the documentation for your identity provider. As a general overview, an identity-provider client typically does the following: - -- Manages the list of your users. -- Optional: Allows users to add themselves to your user list. -- Declares the set of attributes passed from each user account to the client (which is then passed to your Streamlit app). -- Only allows authentication requests to come from your Streamlit app. -- Redirects users back to your Streamlit app after they authenticate. - -To configure your app, you'll need the following: - -- Your app's URL - For example, use `http://localhost:8501` for most local development cases. -- A redirect URL, which is your app's URL with the pathname `oauth2callback` - For example, `http://localhost:8501/oauth2callback` for most local development cases. -- A cookie secret, which should be a strong, randomly generated string - -After you use this information to configure your identity-provider client, you'll receive the following information from your identity provider: - -- Client ID -- Client secret -- Server metadata URL - -Examples for popular OIDC provider configurations are listed in the API reference for `st.login()`. - -## Configure your OIDC connection in Streamlit - -After you've configured your identity-provider client, you'll need to configure your Streamlit app, too. `st.login()` uses your app's `secrets.toml` file to configure your connection, similar to how `st.connection()` works. - -Whether you have one OIDC provider or many, you'll need to have an `[auth]` dictionary in `secrets.toml`. You must declare `redirect_uri` and `cookie_secret` in the `[auth]` dictionary. These two values are shared between all OIDC providers in your app. - -If you are only using one OIDC provider, you can put the remaining three properties (`client_id`, `client_secret`, and `server_metadata_url`) in `[auth]`. However, if you are using multiple providers, they should each have a unique name so you can declare their unique values in their own dictionaries. For example, if you name your connections `"connection_1"` and `"connection_2"`, put their remaining properties in dictionaries named `[auth.connection_1]` and `[auth.connection_2]`, respectively. - -## A simple example - -If you use Google Identity as your identity provider, a basic configuration for local development will look like the following TOML file: - -`.streamlit/secrets.toml`: - -```toml -[auth] -redirect_uri = "http://localhost:8501/oauth2callback" -cookie_secret = "xxx" -client_id = "xxx" -client_secret = "xxx" -server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration" -``` - -Make sure the port in `redirect_uri` matches the port you are using. The `cookie_secret` should be a strong, randomly generated secret. Both the `redirect_uri` and `cookie_secret` should have been entered into your client configuration on Google Cloud. You must copy the `client_id` and `client_secret` from Google Cloud after you create your client. For some identity providers, `server_metadata_url` may be unique to your client. However, for Google Cloud, a single URL is shared for OIDC clients. - -In your app, create a simple login flow: - -```python -import streamlit as st - -if not st.user.is_logged_in: - if st.button("Log in with Google"): - st.login() - st.stop() - -if st.button("Log out"): - st.logout() -st.markdown(f"Welcome! {st.user.name}") -``` - -When you use `st.stop()`, your script run ends as soon as the login button is displayed. This lets you avoid nesting your entire page within a conditional block. Additionally, you can use callbacks to simplify the code further: - -```python -import streamlit as st - -if not st.user.is_logged_in: - st.button("Log in with Google", on_click=st.login) - st.stop() - -st.button("Log out", on_click=st.logout) -st.markdown(f"Welcome! {st.user.name}") -``` - -## Using multiple OIDC providers - -If you use more than one OIDC provider, you'll need to declare a unique name for each. If you want to use Google Identity and Microsoft Entra ID as two providers for the same app, your configuration for local development will look like the following TOML file: - -`.streamlit/secrets.toml`: - -```toml -[auth] -redirect_uri = "http://localhost:8501/oauth2callback" -cookie_secret = "xxx" - -[auth.google] -client_id = "xxx" -client_secret = "xxx" -server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration" - -[auth.microsoft] -client_id = "xxx" -client_secret = "xxx" -server_metadata_url = "https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" -``` - -Microsoft's server metadata URL varies slightly depending on how your client is scoped. Replace `{tenant}` with the appropriate value described in Microsoft's documentation for [OpenID configuration](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri). - -Your app code: - -```python -import streamlit as st - -if not st.user.is_logged_in: - if st.button("Log in with Google"): - st.login("google") - if st.button("Log in with Microsoft"): - st.login("microsoft") - st.stop() - -if st.button("Log out"): - st.logout() -st.markdown(f"Welcome! {st.user.name}") -``` - -Using callbacks, this would look like: - -```python -import streamlit as st - -if not st.user.is_logged_in: - st.button("Log in with Google", on_click=st.login, args=["google"]) - st.button("Log in with Microsoft", on_click=st.login, args=["microsoft"]) - st.stop() - -st.button("Log out", on_click=st.logout) -st.markdown(f"Welcome! {st.user.name}") -``` - -## Passing keywords to your identity provider - -To customize the behavior of your identity provider, you may need to declare additional keywords. For a complete list of OIDC parameters, see [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) and your provider's documentation. By default, Streamlit sets `scope="openid profile email"` and `prompt="select_account"`. You can change these and other OIDC parameters by passing a dictionary of settings to `client_kwargs`. `state` and `nonce`, which are used for security, are handled automatically and don't need to be specified. - -For example,if you are using Auth0 and need to force users to log in every time, use `prompt="login"` as described in Auth0's [Customize Signup and Login Prompts](https://auth0.com/docs/customize/login-pages/universal-login/customize-signup-and-login-prompts). Your configuration will look like this: - -```toml -[auth] -redirect_uri = "http://localhost:8501/oauth2callback" -cookie_secret = "xxx" - -[auth.auth0] -client_id = "xxx" -client_secret = "xxx" -server_metadata_url = "https://{account}.{region}.auth0.com/.well-known/openid-configuration" -client_kwargs = { "prompt" = "login" } -``` - - - Hosted Code environments such as GitHub Codespaces have additional security controls in place preventing the login redirect to be handled properly. - - ---- - -# Security reminders - -Source: https://docs.streamlit.io/develop/concepts/connections/security-reminders - - -## Protect your secrets - -Never save usernames, passwords, or security keys directly in your code or commit them to your repository. - -### Use environment variables - -Avoid putting sensitve information in your code by using environment variables. Be sure to check out [`st.secrets`](/develop/concepts/connections/secrets-management). Research any platform you use to follow their security best practices. If you use Streamlit Community Cloud, [Secrets management](/deploy/streamlit-community-cloud/deploy-your-app/secrets-management) allows you save environment variables and store secrets outside of your code. - -### Keep `.gitignore` updated - -If you use any sensitive or private information during development, make sure that information is saved in separate files from your code. Ensure `.gitignore` is properly configured to prevent saving private information to your repository. - -## Pickle warning - -Streamlit's [`st.cache_data`](/develop/concepts/architecture/caching#stcache_data) and [`st.session_state`](/develop/concepts/architecture/session-state#serializable-session-state) implicitly use the `pickle` module, which is known to be insecure. It is possible to construct malicious pickle data that will execute arbitrary code during unpickling. Never load data that could have come from an untrusted source in an unsafe mode or that could have been tampered with. **Only load data you trust**. - -- When using `st.cache_data`, anything your function returns is pickled and stored, then unpickled on retrieval. Ensure your cached functions return trusted values. This warning also applies to [`st.cache`](/develop/api-reference/caching-and-state/st.cache) (deprecated). -- When the `runner.enforceSerializableSessionState` [configuration option]( - ---- - -# Custom Components - -Source: https://docs.streamlit.io/develop/concepts/custom-components - - -Components are third-party Python modules that extend what's possible with Streamlit. - -## How to use a Component - -Components are super easy to use: - -1. Start by finding the Component you'd like to use. Two great resources for this are: - - - The [Component gallery](https://streamlit.io/components) - - [This thread](https://discuss.streamlit.io/t/streamlit-components-community-tracker/4634), - by Fanilo A. from our forums. - -2. Install the Component using your favorite Python package manager. This step and all following - steps are described in your component's instructions. - - For example, to use the fantastic [AgGrid - Component](https://github.com/PablocFonseca/streamlit-aggrid), you first install it with: - - ```python - pip install streamlit-aggrid - ``` - -3. In your Python code, import the Component as described in its instructions. For AgGrid, this step - is: - - ```python - from st_aggrid import AgGrid - ``` - -4. ...now you're ready to use it! For AgGrid, that's: - - ```python - AgGrid(my_dataframe) - ``` - -## Making your own Component - -If you're interested in making your own component, check out the following resources: - -- [Create a Component](/develop/concepts/custom-components/create) -- [Publish a Component](/develop/concepts/custom-components/publish) -- [Components API](/develop/concepts/custom-components/intro) -- [Blog post for when we launched Components!](https://blog.streamlit.io/introducing-streamlit-components/) - -Alternatively, if you prefer to learn using videos, our engineer Tim Conkling has put together some -amazing tutorials: - -##### Video tutorial, part 1 - - - -##### Video tutorial, part 2 - - - ---- - -# Intro to custom components - -Source: https://docs.streamlit.io/develop/concepts/custom-components/intro - - -The first step in developing a Streamlit Component is deciding whether to create a static component (i.e. rendered once, controlled by Python) or to create a bi-directional component that can communicate from Python to JavaScript and back. - -## Create a static component - -If your goal in creating a Streamlit Component is solely to display HTML code or render a chart from a Python visualization library, Streamlit provides two methods that greatly simplify the process: `components.html()` and `components.iframe()`. - -If you are unsure whether you need bi-directional communication, **start here first**! - -### Render an HTML string - -While [`st.text`](/develop/api-reference/text/st.text), [`st.markdown`](/develop/api-reference/text/st.markdown) and [`st.write`](/develop/api-reference/write-magic/st.write) make it easy to write text to a Streamlit app, sometimes you'd rather implement a custom piece of HTML. Similarly, while Streamlit natively supports [many charting libraries](/develop/api-reference/charts#chart-elements), you may want to implement a specific HTML/JavaScript template for a new charting library. [`components.html`](/develop/api-reference/custom-components/st.components.v1.html) works by giving you the ability to embed an iframe inside of a Streamlit app that contains your desired output. - -**Example** - -```python -import streamlit as st -import streamlit.components.v1 as components - -# bootstrap 4 collapse example -components.html( - """ - -\n \n
\n
\n
\n
\n \n
\n
\n
\n
\n Collapsible Group Item #1 content\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n Collapsible Group Item #2 content\n
\n
\n
\n
\n \"\"\",\n height=600,\n)\n```\n\n### Render an iframe URL\n\n[`components.iframe`](/develop/api-reference/custom-components/st.components.v1.iframe) is similar in features to `components.html`, with the difference being that `components.iframe` takes a URL as its input. This is used for situations where you want to include an entire page within a Streamlit app.\n\n**Example**\n\n```python\nimport streamlit as st\nimport streamlit.components.v1 as components\n\n# embed streamlit docs in a streamlit app\ncomponents.iframe(\"https://example.com\", height=500)\n```\n\n## Create a bi-directional component\n\nA bi-directional Streamlit Component has two parts:\n\n1. A **frontend**, which is built out of HTML and any other web tech you like (JavaScript, React, Vue, etc.), and gets rendered in Streamlit apps via an iframe tag.\n2. A **Python API**, which Streamlit apps use to instantiate and talk to that frontend\n\nTo make the process of creating bi-directional Streamlit Components easier, we've created a React template and a TypeScript-only template in the [Streamlit Component-template GitHub repo](https://github.com/streamlit/component-template). We also provide some [example Components](https://github.com/streamlit/component-template/tree/master/examples) in the same repo.\n\n### Development Environment Setup\n\nTo build a Streamlit Component, you need the following installed in your development environment:\n\n- Python 3.9 - Python 3.13\n- Streamlit\n- [nodejs](https://nodejs.org/en/)\n- [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/)\n\nClone the [component-template GitHub repo](https://github.com/streamlit/component-template), then decide whether you want to use the React.js ([\"template\"](https://github.com/streamlit/component-template/tree/master/template)) or plain TypeScript ([\"template-reactless\"](https://github.com/streamlit/component-template/tree/master/template-reactless)) template.\n\n1. Initialize and build the component template frontend from the terminal:\n\n ```bash\n # React template\n template/my_component/frontend\n npm install # Initialize the project and install npm dependencies\n npm run start # Start the Vite dev server\n\n # or\n\n # TypeScript-only template\n template-reactless/my_component/frontend\n npm install # Initialize the project and install npm dependencies\n npm run start # Start the Vite dev server\n ```\n\n2. _From a separate terminal_, run the Streamlit app (Python) that declares and uses the component:\n\n ```bash\n # React template\n cd template\n . venv/bin/activate # or similar to activate the venv/conda environment where Streamlit is installed\n pip install -e . # install template as editable package\n streamlit run my_component/example.py # run the example\n\n # or\n\n # TypeScript-only template\n cd template-reactless\n . venv/bin/activate # or similar to activate the venv/conda environment where Streamlit is installed\n pip install -e . # install template as editable package\n streamlit run my_component/example.py # run the example\n ```\n\nAfter running the steps above, you should see a Streamlit app in your browser that looks like this:\n\n![Streamlit Component Example App](/images/component_demo_example.png)\n\nThe example app from the template shows how bi-directional communication is implemented. The Streamlit Component displays a button (`Python โ†’ JavaScript`), and the end-user can click the button. Each time the button is clicked, the JavaScript front-end increments the counter value and passes it back to Python (`JavaScript โ†’ Python`), which is then displayed by Streamlit (`Python โ†’ JavaScript`).\n\n### Frontend\n\nBecause each Streamlit Component is its own webpage that gets rendered into an `iframe`, you can use just about any web tech you'd like to create that web page. We provide two templates to get started with in the Streamlit [Components-template GitHub repo](https://github.com/streamlit/component-template/); one of those templates uses [React](https://reactjs.org/) and the other does not.\n\n\n\nEven if you're not already familiar with React, you may still want to check out the React-based\ntemplate. It handles most of the boilerplate required to send and receive data from Streamlit, and\nyou can learn the bits of React you need as you go.\n\nIf you'd rather not use React, please read this section anyway! It explains the fundamentals of\nStreamlit โ†” Component communication.\n\n\n#### React\n\nThe React-based template is in `template/my_component/frontend/src/MyComponent.tsx`.\n\n- `MyComponent.render()` is called automatically when the component needs to be re-rendered (just like in any React app)\n- Arguments passed from the Python script are available via the `this.props.args` dictionary:\n\n```python\n# Send arguments in Python:\nresult = my_component(greeting=\"Hello\", name=\"Streamlit\")\n```\n\n```javascript\n// Receive arguments in frontend:\nlet greeting = this.props.args[\"greeting\"]; // greeting = \"Hello\"\nlet name = this.props.args[\"name\"]; // name = \"Streamlit\"\n```\n\n- Use `Streamlit.setComponentValue()` to return data from the component to the Python script:\n\n```javascript\n// Set value in frontend:\nStreamlit.setComponentValue(3.14);\n```\n\n```python\n# Access value in Python:\nresult = my_component(greeting=\"Hello\", name=\"Streamlit\")\nst.write(\"result = \", result) # result = 3.14\n```\n\nWhen you call `Streamlit.setComponentValue(new_value)`, that new value is sent to Streamlit, which then _re-executes the Python script from top to bottom_. When the script is re-executed, the call to `my_component(...)` will return the new value.\n\nFrom a _code flow_ perspective, it appears that you're transmitting data synchronously with the frontend: Python sends the arguments to JavaScript, and JavaScript returns a value to Python, all in a single function call! But in reality this is all happening _asynchronously_, and it's the re-execution of the Python script that achieves the sleight of hand.\n\n- Use `Streamlit.setFrameHeight()` to control the height of your component. By default, the React template calls this automatically (see `StreamlitComponentBase.componentDidUpdate()`). You can override this behavior if you need more control.\n- There's a tiny bit of magic in the last line of the file: `export default withStreamlitConnection(MyComponent)` - this does some handshaking with Streamlit, and sets up the mechanisms for bi-directional data communication.\n\n#### TypeScript-only\n\nThe TypeScript-only template is in `template-reactless/my_component/frontend/src/MyComponent.tsx`.\n\nThis template has much more code than its React sibling, in that all the mechanics of handshaking, setting up event listeners, and updating the component's frame height are done manually. The React version of the template handles most of these details automatically.\n\n- Towards the bottom of the source file, the template calls `Streamlit.setComponentReady()` to tell Streamlit it's ready to start receiving data. (You'll generally want to do this after creating and loading everything that the Component relies on.)\n- It subscribes to `Streamlit.RENDER_EVENT` to be notified of when to redraw. (This event won't be fired until `setComponentReady` is called)\n- Within its `onRender` event handler, it accesses the arguments passed in the Python script via `event.detail.args`\n- It sends data back to the Python script in the same way that the React template doesโ€”clicking on the \"Click Me!\" button calls `Streamlit.setComponentValue()`\n- It informs Streamlit when its height may have changed via `Streamlit.setFrameHeight()`\n\n#### Working with Themes\n\n\n\nCustom component theme support requires streamlit-component-lib version 1.2.0 or higher.\n\n\n\nAlong with sending an `args` object to your component, Streamlit also sends\na `theme` object defining the active theme so that your component can adjust\nits styling in a compatible way. This object is sent in the same message as\n`args`, so it can be accessed via `this.props.theme` (when using the React\ntemplate) or `event.detail.theme` (when using the plain TypeScript template).\n\nThe `theme` object has the following shape:\n\n```json\n{\n \"base\": \"lightORdark\",\n \"primaryColor\": \"someColor1\",\n \"backgroundColor\": \"someColor2\",\n \"secondaryBackgroundColor\": \"someColor3\",\n \"textColor\": \"someColor4\",\n \"font\": \"someFont\"\n}\n```\n\nThe `base` option allows you to specify a preset Streamlit theme that your custom theme inherits from. Any theme config options not defined in your theme settings have their values set to those of the base theme. Valid values for `base` are `\"light\"` and `\"dark\"`.\n\nNote that the theme object has fields with the same names and semantics as the\noptions in the \"theme\" section of the config options printed with the command\n`streamlit config show`.\n\nWhen using the React template, the following CSS variables are also set\nautomatically.\n\n```css\n--base\n--primary-color\n--background-color\n--secondary-background-color\n--text-color\n--font\n```\n\nIf you're not familiar with\n[CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties),\nthe TLDR version is that you can use them like this:\n\n```css\n.mySelector {\n color: var(--text-color);\n}\n```\n\nThese variables match the fields defined in the `theme` object above, and\nwhether to use CSS variables or the theme object in your component is a matter\nof personal preference.\n\n#### Other frontend details\n\n- Because you're hosting your component from a dev server (via `npm run start`), any changes you make should be automatically reflected in the Streamlit app when you save.\n- If you want to add more packages to your component, run `npm add` to add them from within your component's `frontend/` directory.\n\n```bash\nnpm add baseui\n```\n\n- To build a static version of your component, run `npm run export`. See [Prepare your Component](publish#prepare-your-component) for more information\n\n### Python API\n\n`components.declare_component()` is all that's required to create your Component's Python API:\n\n```python\n import streamlit.components.v1 as components\n my_component = components.declare_component(\n \"my_component\",\n url=\"http://localhost:3001\"\n )\n```\n\nYou can then use the returned `my_component` function to send and receive data with your frontend code:\n\n```python\n# Send data to the frontend using named arguments.\nreturn_value = my_component(name=\"Blackbeard\", ship=\"Queen Anne's Revenge\")\n\n# `my_component`'s return value is the data returned from the frontend.\nst.write(\"Value = \", return_value)\n```\n\nWhile the above is all you need to define from the Python side to have a working Component, we recommend creating a \"wrapper\" function with named arguments and default values, input validation and so on. This will make it easier for end-users to understand what data values your function accepts and allows for defining helpful docstrings.\n\nPlease see [this example](https://github.com/streamlit/component-template/blob/master/template/my_component/__init__.py#L41-L77) from the Components-template for an example of creating a wrapper function.\n\n### Data serialization\n\n#### Python โ†’ Frontend\n\nYou send data from Python to the frontend by passing keyword args to your Component's invoke function (that is, the function returned from `declare_component`). You can send the following types of data from Python to the frontend:\n\n- Any JSON-serializable data\n- `numpy.array`\n- `pandas.DataFrame`\n\nAny JSON-serializable data gets serialized to a JSON string, and deserialized to its JavaScript equivalent. `numpy.array` and `pandas.DataFrame` get serialized using [Apache Arrow](https://arrow.apache.org/) and are deserialized as instances of `ArrowTable`, which is a custom type that wraps Arrow structures and provides a convenient API on top of them.\n\nCheck out the [CustomDataframe](https://github.com/streamlit/component-template/tree/master/examples/CustomDataframe) and [SelectableDataTable](https://github.com/streamlit/component-template/tree/master/examples/SelectableDataTable) Component example code for more context on how to use `ArrowTable`.\n\n#### Frontend โ†’ Python\n\nYou send data from the frontend to Python via the `Streamlit.setComponentValue()` API (which is part of the template code). Unlike arg-passing from Python โ†’ frontend, **this API takes a single value**. If you want to return multiple values, you'll need to wrap them in an `Array` or `Object`.\n\nCustom Components can send JSON-serializable data from the frontend to Python, as well as [Apache Arrow](http://arrow.apache.org/) `ArrowTable`s to represent dataframes." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/custom-components/create", - "content": "# Create a Component\n\n\n\nIf you are only interested in **using Streamlit Components**, then you can skip this section and\nhead over to the [Streamlit Components Gallery](https://streamlit.io/components) to find and install\ncomponents created by the community!\n\n\n\nDevelopers can write JavaScript and HTML \"components\" that can be rendered in Streamlit apps. Streamlit Components can receive data from, and also send data to, Streamlit Python scripts.\n\nStreamlit Components let you expand the functionality provided in the base Streamlit package. Use Streamlit Components to create the needed functionality for your use-case, then wrap it up in a Python package and share with the broader Streamlit community!\n\n**With Streamlit Components you can add new features to your app in the following ways:**\n\n- Create your own components to use in place of existing Streamlit elements and widgets.\n- Create completely new Streamlit elements and widgets by wrapping existing React.js, Vue.js, or other JavaScript widget toolkits.\n- Render Python objects by constructing HTML representations and styling them to fit your app's theme.\n- Create convenience functions to embed commonly-used web features like [GitHub gists and Pastebin](https://github.com/randyzwitch/streamlit-embedcode).\n\nCheck out these Streamlit Components tutorial videos by Streamlit engineer Tim Conkling to get started:\n\n## Part 1: Setup and Architecture\n\n\n\n## Part 2: Make a Slider Widget\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/custom-components", - "content": "# Custom Components\n\nComponents are third-party Python modules that extend what's possible with Streamlit.\n\n## How to use a Component\n\nComponents are super easy to use:\n\n1. Start by finding the Component you'd like to use. Two great resources for this are:\n\n - The [Component gallery](https://streamlit.io/components)\n - [This thread](https://discuss.streamlit.io/t/streamlit-components-community-tracker/4634),\n by Fanilo A. from our forums.\n\n2. Install the Component using your favorite Python package manager. This step and all following\n steps are described in your component's instructions.\n\n For example, to use the fantastic [AgGrid\n Component](https://github.com/PablocFonseca/streamlit-aggrid), you first install it with:\n\n ```python\n pip install streamlit-aggrid\n ```\n\n3. In your Python code, import the Component as described in its instructions. For AgGrid, this step\n is:\n\n ```python\n from st_aggrid import AgGrid\n ```\n\n4. ...now you're ready to use it! For AgGrid, that's:\n\n ```python\n AgGrid(my_dataframe)\n ```\n\n## Making your own Component\n\nIf you're interested in making your own component, check out the following resources:\n\n- [Create a Component](/develop/concepts/custom-components/create)\n- [Publish a Component](/develop/concepts/custom-components/publish)\n- [Components API](/develop/concepts/custom-components/intro)\n- [Blog post for when we launched Components!](https://blog.streamlit.io/introducing-streamlit-components/)\n\nAlternatively, if you prefer to learn using videos, our engineer Tim Conkling has put together some\namazing tutorials:\n\n##### Video tutorial, part 1\n\n\n\n##### Video tutorial, part 2\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/connections/authentication", - "content": "# User authentication and information\n\nPersonalizing your app for your users is a great way to make your app more engaging.\n\nUser authentication and personalization unlocks a plethora of use cases for developers, including controls for admins, a personalized stock ticker, or a chatbot app with a saved history between sessions.\n\nBefore reading this guide, you should have a basic understanding of [secrets management](/develop/concepts/connections/secrets-management).\n\n## OpenID Connect\n\nStreamlit supports user authentication with OpenID Connect (OIDC), which is an authentication protocol built on top of OAuth 2.0. OIDC supports authentication, but not authorization: that is, OIDC connections tell you _who_ a user is (authentication), but don't give you the authority to _impersonate_ them (authorization). If you need to connect with a generic OAuth 2.0 provider or have your app to act on behalf of a user, consider using or creating a custom component.\n\nSome popular OIDC providers are:\n\n- [Google Identity](https://developers.google.com/identity/openid-connect/openid-connect)\n- [Microsoft Entra ID](https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings)\n- [Okta](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_oidc.htm)\n- [Auth0](https://auth0.com/docs/get-started/auth0-overview/create-applications/regular-web-apps)\n\n## `st.login()`, `st.user`, and `st.logout()`\n\nThere are three commands involved with user authentication:\n\n- [`st.login()`](/develop/api-reference/user/st.login) redirects the user to your identity provider. After they log in, Streamlit stores an identity cookie and then redirects them to the homepage of your app in a new session.\n- [`st.user`](/develop/api-reference/user/st.user) is a dict-like object for accessing user information. It has a persistent attribute, `.is_logged_in`, which you can check for the user's login status. When they are logged in, other attributes are available per your identity provider's configuration.\n- [`st.logout()`](/develop/api-reference/user/st.logout) removes the identity cookie from the user's browser and redirects them to the homepage of your app in a new session.\n\n## User cookies and logging out\n\nStreamlit checks for the identity cookie at the beginning of each new session. If a user logs in to your app in one tab and then opens a new tab, they will automatically be logged in to your app in the new tab. When you call `st.logout()` in a user session, Streamlit removes the identity cookie and starts a new session. This logs the user out from the current session. However, if they were logged in to other sessions already, they will remain logged in within those sessions. The information in `st.user` is updated at the beginning of a session (which is why `st.login()` and `st.logout()` both start new sessions after saving or deleting the identity cookie).\n\nIf a user closes your app without logging out, the identity cookie will expire after 30 days. This expiration time is not configurable and is not tied to any expiration time that may be returned in your user's identity token. If you need to prevent persistent authentication in your app, check the expiration information returned by the identity provider in `st.user` and manually call `st.logout()` when needed.\n\nStreamlit does not modify or delete any cookies saved directly by your identity provider. For example, if you use Google as your identity provider and a user logs in to your app with Google, they will remain logged in to their Google account after they log out of your app with `st.logout()`.\n\n## Setting up an identity provider\n\nIn order to use an identity provider, you must first configure your identity provider through an admin account. This typically involves setting up a client or application within the identity provider's system. Follow the documentation for your identity provider. As a general overview, an identity-provider client typically does the following:\n\n- Manages the list of your users.\n- Optional: Allows users to add themselves to your user list.\n- Declares the set of attributes passed from each user account to the client (which is then passed to your Streamlit app).\n- Only allows authentication requests to come from your Streamlit app.\n- Redirects users back to your Streamlit app after they authenticate.\n\nTo configure your app, you'll need the following:\n\n- Your app's URL\n For example, use `http://localhost:8501` for most local development cases.\n- A redirect URL, which is your app's URL with the pathname `oauth2callback`\n For example, `http://localhost:8501/oauth2callback` for most local development cases.\n- A cookie secret, which should be a strong, randomly generated string\n\nAfter you use this information to configure your identity-provider client, you'll receive the following information from your identity provider:\n\n- Client ID\n- Client secret\n- Server metadata URL\n\nExamples for popular OIDC provider configurations are listed in the API reference for `st.login()`.\n\n## Configure your OIDC connection in Streamlit\n\nAfter you've configured your identity-provider client, you'll need to configure your Streamlit app, too. `st.login()` uses your app's `secrets.toml` file to configure your connection, similar to how `st.connection()` works.\n\nWhether you have one OIDC provider or many, you'll need to have an `[auth]` dictionary in `secrets.toml`. You must declare `redirect_uri` and `cookie_secret` in the `[auth]` dictionary. These two values are shared between all OIDC providers in your app.\n\nIf you are only using one OIDC provider, you can put the remaining three properties (`client_id`, `client_secret`, and `server_metadata_url`) in `[auth]`. However, if you are using multiple providers, they should each have a unique name so you can declare their unique values in their own dictionaries. For example, if you name your connections `\"connection_1\"` and `\"connection_2\"`, put their remaining properties in dictionaries named `[auth.connection_1]` and `[auth.connection_2]`, respectively.\n\n## A simple example\n\nIf you use Google Identity as your identity provider, a basic configuration for local development will look like the following TOML file:\n\n`.streamlit/secrets.toml`:\n\n```toml\n[auth]\nredirect_uri = \"http://localhost:8501/oauth2callback\"\ncookie_secret = \"xxx\"\nclient_id = \"xxx\"\nclient_secret = \"xxx\"\nserver_metadata_url = \"https://accounts.google.com/.well-known/openid-configuration\"\n```\n\nMake sure the port in `redirect_uri` matches the port you are using. The `cookie_secret` should be a strong, randomly generated secret. Both the `redirect_uri` and `cookie_secret` should have been entered into your client configuration on Google Cloud. You must copy the `client_id` and `client_secret` from Google Cloud after you create your client. For some identity providers, `server_metadata_url` may be unique to your client. However, for Google Cloud, a single URL is shared for OIDC clients.\n\nIn your app, create a simple login flow:\n\n```python\nimport streamlit as st\n\nif not st.user.is_logged_in:\n if st.button(\"Log in with Google\"):\n st.login()\n st.stop()\n\nif st.button(\"Log out\"):\n st.logout()\nst.markdown(f\"Welcome! {st.user.name}\")\n```\n\nWhen you use `st.stop()`, your script run ends as soon as the login button is displayed. This lets you avoid nesting your entire page within a conditional block. Additionally, you can use callbacks to simplify the code further:\n\n```python\nimport streamlit as st\n\nif not st.user.is_logged_in:\n st.button(\"Log in with Google\", on_click=st.login)\n st.stop()\n\nst.button(\"Log out\", on_click=st.logout)\nst.markdown(f\"Welcome! {st.user.name}\")\n```\n\n## Using multiple OIDC providers\n\nIf you use more than one OIDC provider, you'll need to declare a unique name for each. If you want to use Google Identity and Microsoft Entra ID as two providers for the same app, your configuration for local development will look like the following TOML file:\n\n`.streamlit/secrets.toml`:\n\n```toml\n[auth]\nredirect_uri = \"http://localhost:8501/oauth2callback\"\ncookie_secret = \"xxx\"\n\n[auth.google]\nclient_id = \"xxx\"\nclient_secret = \"xxx\"\nserver_metadata_url = \"https://accounts.google.com/.well-known/openid-configuration\"\n\n[auth.microsoft]\nclient_id = \"xxx\"\nclient_secret = \"xxx\"\nserver_metadata_url = \"https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration\"\n```\n\nMicrosoft's server metadata URL varies slightly depending on how your client is scoped. Replace `{tenant}` with the appropriate value described in Microsoft's documentation for [OpenID configuration](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri).\n\nYour app code:\n\n```python\nimport streamlit as st\n\nif not st.user.is_logged_in:\n if st.button(\"Log in with Google\"):\n st.login(\"google\")\n if st.button(\"Log in with Microsoft\"):\n st.login(\"microsoft\")\n st.stop()\n\nif st.button(\"Log out\"):\n st.logout()\nst.markdown(f\"Welcome! {st.user.name}\")\n```\n\nUsing callbacks, this would look like:\n\n```python\nimport streamlit as st\n\nif not st.user.is_logged_in:\n st.button(\"Log in with Google\", on_click=st.login, args=[\"google\"])\n st.button(\"Log in with Microsoft\", on_click=st.login, args=[\"microsoft\"])\n st.stop()\n\nst.button(\"Log out\", on_click=st.logout)\nst.markdown(f\"Welcome! {st.user.name}\")\n```\n\n## Passing keywords to your identity provider\n\nTo customize the behavior of your identity provider, you may need to declare additional keywords. For a complete list of OIDC parameters, see [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) and your provider's documentation. By default, Streamlit sets `scope=\"openid profile email\"` and `prompt=\"select_account\"`. You can change these and other OIDC parameters by passing a dictionary of settings to `client_kwargs`. `state` and `nonce`, which are used for security, are handled automatically and don't need to be specified.\n\nFor example,if you are using Auth0 and need to force users to log in every time, use `prompt=\"login\"` as described in Auth0's [Customize Signup and Login Prompts](https://auth0.com/docs/customize/login-pages/universal-login/customize-signup-and-login-prompts). Your configuration will look like this:\n\n```toml\n[auth]\nredirect_uri = \"http://localhost:8501/oauth2callback\"\ncookie_secret = \"xxx\"\n\n[auth.auth0]\nclient_id = \"xxx\"\nclient_secret = \"xxx\"\nserver_metadata_url = \"https://{account}.{region}.auth0.com/.well-known/openid-configuration\"\nclient_kwargs = { \"prompt\" = \"login\" }\n```\n\n\n Hosted Code environments such as GitHub Codespaces have additional security controls in place preventing the login redirect to be handled properly.\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/connections/connecting-to-data", - "content": "# Connecting to data\n\nMost Streamlit apps need some kind of data or API access to be useful - either retrieving data to view or saving the results of some user action. This data or API is often part of some remote service, database, or other data source.\n\n**Anything you can do with Python, including data connections, will generally work in Streamlit**. Streamlit's [tutorials](/develop/tutorials/databases) are a great starting place for many data sources. However:\n\n- Connecting to data in a Python application is often tedious and annoying.\n- There are specific considerations for connecting to data from streamlit apps, such as caching and secrets management.\n\n**Streamlit provides [`st.connection()`](/develop/api-reference/connections/st.connection) to more easily connect your Streamlit apps to data and APIs with just a few lines of code**. This page provides a basic example of using the feature and then focuses on advanced usage.\n\nFor a comprehensive overview of this feature, check out this video tutorial by Joshua Carroll, Streamlit's Product Manager for Developer Experience. You'll learn about the feature's utility in creating and managing data connections within your apps by using real-world examples.\n\n\n\n## Basic usage\n\nFor basic startup and usage examples, read up on the relevant [data source tutorial](/develop/tutorials/databases). Streamlit has built-in connections to SQL dialects and Snowflake. We also maintain installable connections for [Cloud File Storage](https://github.com/streamlit/files-connection) and [Google Sheets](https://github.com/streamlit/gsheets-connection).\n\nIf you are just starting, the best way to learn is to pick a data source you can access and get a minimal example working from one of the pages above ๐Ÿ‘†. Here, we will provide an ultra-minimal usage example for using a SQLite database. From there, the rest of this page will focus on advanced usage.\n\n### A simple starting point - using a local SQLite database\n\nA [local SQLite database](https://sqlite.org/index.html) could be useful for your app's semi-persistent data storage.\n\n\n\nCommunity Cloud apps do not guarantee the persistence of local file storage, so the platform may delete data stored using this technique at any time.\n\n\n\nTo see the example below running live, check out the interactive demo below:\n\n\n\n#### Step 1: Install prerequisite library - SQLAlchemy\n\nAll SQLConnections in Streamlit use SQLAlchemy. For most other SQL dialects, you also need to install the driver. But the [SQLite driver ships with python3](https://docs.python.org/3/develop/sqlite3.html), so it isn't necessary.\n\n```bash\npip install SQLAlchemy==1.4.0\n```\n\n#### Step 2: Set a database URL in your Streamlit secrets.toml file\n\nCreate a directory and file `.streamlit/secrets.toml` in the same directory your app will run from. Add the following to the file.\n\n```toml\n# .streamlit/secrets.toml\n\n[connections.pets_db]\nurl = \"sqlite:///pets.db\"\n```\n\n#### Step 3: Use the connection in your app\n\nThe following app creates a connection to the database, uses it to create a table and insert some data, then queries the data back and displays it in a data frame.\n\n```python\n# streamlit_app.py\n\nimport streamlit as st\n\n# Create the SQL connection to pets_db as specified in your secrets file.\nconn = st.connection('pets_db', type='sql')\n\n# Insert some data with conn.session.\nwith conn.session as s:\n s.execute('CREATE TABLE IF NOT EXISTS pet_owners (person TEXT, pet TEXT);')\n s.execute('DELETE FROM pet_owners;')\n pet_owners = {'jerry': 'fish', 'barbara': 'cat', 'alex': 'puppy'}\n for k in pet_owners:\n s.execute(\n 'INSERT INTO pet_owners (person, pet) VALUES (:owner, :pet);',\n params=dict(owner=k, pet=pet_owners[k])\n )\n s.commit()\n\n# Query and display the data you inserted\npet_owners = conn.query('select * from pet_owners')\nst.dataframe(pet_owners)\n```\n\nIn this example, we didn't set a `ttl=` value on the call to [`conn.query()`](/develop/api-reference/connections/st.connections.sqlconnection#sqlconnectionquery), meaning Streamlit caches the result indefinitely as long as the app server runs.\n\nNow, on to more advanced topics! ๐Ÿš€\n\n## Advanced topics\n\n### Global secrets, managing multiple apps and multiple data stores\n\nStreamlit [supports a global secrets file](/develop/concepts/connections/secrets-management) specified in the user's home directory, such as `~/.streamlit/secrets.toml`. If you build or manage multiple apps, we recommend using a global credential or secret file for local development across apps. With this approach, you only need to set up and manage your credentials in one place, and connecting a new app to your existing data sources is effectively a one-liner. It also reduces the risk of accidentally checking in your credentials to git since they don't need to exist in the project repository.\n\nFor cases where you have multiple similar data sources that you connect to during local development (such as a local vs. staging database), you can define different connection sections in your secrets or credentials file for different environments and then decide which to use at runtime. `st.connection` supports this with the _`name=env:`_ syntax.\n\nE.g., say I have a local and a staging MySQL database and want to connect my app to either at different times. I could create a global secrets file like this:\n\n```toml\n# ~/.streamlit/secrets.toml\n\n[connections.local]\nurl = \"mysql://me:****@localhost:3306/local_db\"\n\n[connections.staging]\nurl = \"mysql://jdoe:******@staging.acmecorp.com:3306/staging_db\"\n```\n\nThen I can configure my app connection to take its name from a specified environment variable\n\n```python\n# streamlit_app.py\nimport streamlit as st\n\nconn = st.connection(\"env:DB_CONN\", \"sql\")\ndf = conn.query(\"select * from mytable\")\n# ...\n```\n\nNow I can specify whether to connect to local or staging at runtime by setting the `DB_CONN` environment variable.\n\n```bash\n# connect to local\nDB_CONN=local streamlit run streamlit_app.py\n\n# connect to staging\nDB_CONN=staging streamlit run streamlit_app.py\n```\n\n### Advanced SQLConnection configuration\n\nThe [SQLConnection](/develop/api-reference/connections/st.connections.sqlconnection) configuration uses SQLAlchemy `create_engine()` function. It will take a single URL argument or attempt to construct a URL from several parts (username, database, host, and so on) using [`SQLAlchemy.engine.URL.create()`](https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.engine.URL.create).\n\nSeveral popular SQLAlchemy dialects, such as Snowflake and Google BigQuery, can be configured using additional arguments to `create_engine()` besides the URL. These can be passed as `**kwargs` to the [st.connection](/develop/api-reference/connections/st.connection) call directly or specified in an additional secrets section called `create_engine_kwargs`.\n\nE.g. snowflake-sqlalchemy takes an additional [`connect_args`](https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine.params.connect_args) argument as a dictionary for configuration that isnโ€™t supported in the URL. These could be specified as follows:\n\n```toml\n# .streamlit/secrets.toml\n\n[connections.snowflake]\nurl = \"snowflake://@/\"\n\n[connections.snowflake.create_engine_kwargs.connect_args]\nauthenticator = \"externalbrowser\"\nwarehouse = \"xxx\"\nrole = \"xxx\"\n```\n\n```python\n# streamlit_app.py\n\nimport streamlit as st\n\n# url and connect_args from secrets.toml above are picked up and used here\nconn = st.connection(\"snowflake\", \"sql\")\n# ...\n```\n\nAlternatively, this could be specified entirely in `**kwargs`.\n\n```python\n# streamlit_app.py\n\nimport streamlit as st\n\n# secrets.toml is not needed\nconn = st.connection(\n \"snowflake\",\n \"sql\",\n url = \"snowflake://@/\",\n connect_args = dict(\n authenticator = \"externalbrowser\",\n warehouse = \"xxx\",\n role = \"xxx\",\n )\n)\n# ...\n```\n\nYou can also provide both kwargs and secrets.toml values, and they will be merged (typically, kwargs take precedence).\n\n### Connection considerations in frequently used or long-running apps\n\nBy default, connection objects are cached without expiration using [`st.cache_resource`](/develop/api-reference/caching-and-state/st.cache_resource). In most cases this is desired. You can do `st.connection('myconn', type=MyConnection, ttl=)` if you want the connection object to expire after some time.\n\nMany connection types are expected to be long-running or completely stateless, so expiration is unnecessary. Suppose a connection becomes stale (such as a cached token expiring or a server-side connection being closed). In that case, every connection has a `reset()` method, which will invalidate the cached version and cause Streamlit to recreate the connection the next time it is retrieved\n\nConvenience methods like `query()` and `read()` will typically cache results by default using [`st.cache_data`](/develop/api-reference/caching-and-state/st.cache_data) without an expiration. When an app can run many different read operations with large results, it can cause high memory usage over time and results to become stale in a long-running app, the same as with any other usage of `st.cache_data`. For production use cases, we recommend setting an appropriate `ttl` on these read operations, such as `conn.read('path/to/file', ttl=\"1d\")`. Refer to [Caching](/develop/concepts/architecture/caching) for more information.\n\nFor apps that could get significant concurrent usage, ensure that you understand any thread safety implications of your connection, particularly when using a connection built by a third party. Connections built by Streamlit should provide thread-safe operations by default.\n\n### Build your own connection\n\nBuilding your own basic connection implementation using an existing driver or SDK is quite straightforward in most cases. However, you can add more complex functionality with further effort. This custom implementation can be a great way to extend support to a new data source and contribute to the Streamlit ecosystem.\n\nMaintaining a tailored internal Connection implementation across many apps can be a powerful practice for organizations with frequently used access patterns and data sources.\n\nCheck out the [Build your own Connection page](https://experimental-connection.streamlit.app/Build_your_own) in the st.experimental connection demo app below for a quick tutorial and working implementation. This demo builds a minimal but very functional Connection on top of DuckDB.\n\n\n\nThe typical steps are:\n\n1. Declare the Connection class, extending [`ExperimentalBaseConnection`](/develop/api-reference/connections/st.connections.experimentalbaseconnection) with the type parameter bound to the underlying connection object:\n\n ```python\n from streamlit.connections import ExperimentalBaseConnection\n import duckdb\n\n class DuckDBConnection(ExperimentalBaseConnection[duckdb.DuckDBPyConnection])\n ```\n\n2. Implement the `_connect` method that reads any kwargs, external config/credential locations, and Streamlit secrets to initialize the underlying connection:\n\n ```python\n def _connect(self, **kwargs) -> duckdb.DuckDBPyConnection:\n if 'database' in kwargs:\n db = kwargs.pop('database')\n else:\n db = self._secrets['database']\n return duckdb.connect(database=db, **kwargs)\n ```\n\n3. Add useful helper methods that make sense for your connection (wrapping them in `st.cache_data` where caching is desired)\n\n### Connection-building best practices\n\nWe recommend applying the following best practices to make your Connection consistent with the Connections built into Streamlit and the wider Streamlit ecosystem. These practices are especially important for Connections that you intend to distribute publicly.\n\n1. **Extend existing drivers or SDKs, and default to semantics that makes sense for their existing users.**\n\n You should rarely need to implement complex data access logic from scratch when building a Connection. Use existing popular Python drivers and clients whenever possible. Doing so makes your Connection easier to maintain, more secure, and enables users to get the latest features. E.g. [SQLConnection](/develop/api-reference/connections/st.connections.sqlconnection) extends SQLAlchemy, [FileConnection](https://github.com/streamlit/files-connection) extends [fsspec](https://filesystem-spec.readthedocs.io/en/latest/), [GsheetsConnection](https://github.com/streamlit/gsheets-connection) extends [gspread](https://docs.gspread.org/en/latest/), etc.\n\n Consider using access patterns, method/argument naming, and return values that are consistent with the underlying package and familiar to existing users of that package.\n\n2. **Intuitive, easy to use read methods.**\n\n Much of the power of st.connection is providing intuitive, easy-to-use read methods that enable app developers to get started quickly. Most connections should expose at least one read method that is:\n\n - Named with a simple verb, like `read()`, `query()`, or `get()`\n - Wrapped by `st.cache_data` by default, with at least `ttl=` argument supported\n - If the result is in a tabular format, it returns a pandas DataFrame\n - Provides commonly used keyword arguments (such as paging or formatting) with sensible defaults - ideally, the common case requires only 1-2 arguments.\n\n3. **Config, secrets, and precedence in `_connect` method.**\n\n Every Connection should support commonly used connection parameters provided via Streamlit secrets and keyword arguments. The names should match the ones used when initializing or configuring the underlying package.\n\n Additionally, where relevant, Connections should support data source specific configuration through existing standard environment variables or config / credential files. In many cases, the underlying package provides constructors or factory functions that already handle this easily.\n\n When you can specify the same connection parameters in multiple places, we recommend using the following precedence order when possible (highest to lowest):\n\n - Keyword arguments specified in the code\n - Streamlit secrets\n - data source specific configuration (if relevant)\n\n4. **Handling thread safety and stale connections.**\n\n Connections should provide thread-safe operations when practical (which should be most of the time) and clearly document any considerations around this. Most underlying drivers or SDKs should provide thread-safe objects or methods - use these when possible.\n\n If the underlying driver or SDK has a risk of stateful connection objects becoming stale or invalid, consider building a low impact health check or reset/retry pattern into the access methods. The SQLConnection built into Streamlit has a good example of this pattern using [tenacity](https://tenacity.readthedocs.io/) and the built-in [Connection.reset()](/develop/api-reference/connections/st.connections.sqlconnection#sqlconnectionreset) method. An alternate approach is to encourage developers to set an appropriate TTL on the `st.connection()` call to ensure it periodically reinitializes the connection object." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/connections/security-reminders", - "content": "# Security reminders\n\n## Protect your secrets\n\nNever save usernames, passwords, or security keys directly in your code or commit them to your repository.\n\n### Use environment variables\n\nAvoid putting sensitve information in your code by using environment variables. Be sure to check out [`st.secrets`](/develop/concepts/connections/secrets-management). Research any platform you use to follow their security best practices. If you use Streamlit Community Cloud, [Secrets management](/deploy/streamlit-community-cloud/deploy-your-app/secrets-management) allows you save environment variables and store secrets outside of your code.\n\n### Keep `.gitignore` updated\n\nIf you use any sensitive or private information during development, make sure that information is saved in separate files from your code. Ensure `.gitignore` is properly configured to prevent saving private information to your repository.\n\n## Pickle warning\n\nStreamlit's [`st.cache_data`](/develop/concepts/architecture/caching#stcache_data) and [`st.session_state`](/develop/concepts/architecture/session-state#serializable-session-state) implicitly use the `pickle` module, which is known to be insecure. It is possible to construct malicious pickle data that will execute arbitrary code during unpickling. Never load data that could have come from an untrusted source in an unsafe mode or that could have been tampered with. **Only load data you trust**.\n\n- When using `st.cache_data`, anything your function returns is pickled and stored, then unpickled on retrieval. Ensure your cached functions return trusted values. This warning also applies to [`st.cache`](/develop/api-reference/caching-and-state/st.cache) (deprecated).\n- When the `runner.enforceSerializableSessionState` [configuration option](<(/develop/concepts/configuration#runner)>) is set to `true`, ensure all data saved and retrieved from Session State is trusted." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/connections", - "content": "# Working with connections, secrets, and user authentication\n\n\n\n\n\n
Connecting to data
\n\nConnect your app to remote data or a third-party API.\n\n
\n\n\n\n
Secrets managements
\n\nSet up your development environement and design your app to handle secrets securely.\n\n
\n\n\n\n
Authentication and user information
\n\nUse an OpenID Connect provider to authenticate users and personalize your app.\n\n
\n\n\n\n
Security reminders
\n\nCheck out a few reminders to follow best practices and avoid security mistakes.\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/connections/secrets-management", - "content": "# Secrets management\n\nStoring 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.\n\nStreamlit provides native file-based secrets management to easily store and securely access your secrets in your Streamlit app.\n\n\n\nExisting secrets management tools, such as [dotenv files](https://pypi.org/project/python-dotenv/), [AWS credentials files](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials), [Google Cloud Secret Manager](https://pypi.org/project/google-cloud-secret-manager/), or [Hashicorp Vault](https://www.vaultproject.io/use-cases/secrets-management), will work fine in Streamlit. We just add native secrets management for times when it's useful.\n\n\n\n## How to use secrets management\n\n### Develop locally and set up secrets\n\nStreamlit provides two ways to set up secrets locally usingย [TOML](https://toml.io/en/latest)ย format:\n\n1. In a **global secrets file** at `~/.streamlit/secrets.toml` for macOS/Linux or `%userprofile%/.streamlit/secrets.toml` for Windows:\n\n ```toml\n # Everything in this section will be available as an environment variable\n db_username = \"Jane\"\n db_password = \"mypassword\"\n\n # You can also add other sections if you like.\n # The contents of sections as shown below will not become environment variables,\n # but they'll be easily accessible from within Streamlit anyway as we show\n # later in this doc.\n [my_other_secrets]\n things_i_like = [\"Streamlit\", \"Python\"]\n ```\n\n If you use the global secrets file, you don't have to duplicate secrets across several project-level files if multiple Streamlit apps share the same secrets.\n\n2. In a **per-project secrets file** at `$CWD/.streamlit/secrets.toml`, where `$CWD` is the folder you're running Streamlit from. If both a global secrets file and a per-project secrets file exist, _secrets in the per-project file overwrite those defined in the global file_.\n\n\n\nAdd this file to your `.gitignore` so you don't commit your secrets!\n\n\n\n### Use secrets in your app\n\nAccess 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.\n\n```python\nimport streamlit as st\n\n# Everything is accessible via the st.secrets dict:\n\nst.write(\"DB username:\", st.secrets[\"db_username\"])\nst.write(\"DB password:\", st.secrets[\"db_password\"])\n\n# And the root-level secrets are also accessible as environment variables:\n\nimport os\n\nst.write(\n \"Has environment variables been set:\",\n os.environ[\"db_username\"] == st.secrets[\"db_username\"],\n)\n```\n\n\n\nYou can access `st.secrets` via attribute notation (e.g. `st.secrets.key`), in addition to key notation (e.g. `st.secrets[\"key\"]`) โ€” like [st.session_state](/develop/api-reference/caching-and-state/st.session_state).\n\n\n\nYou can even compactly use TOML sections to pass multiple secrets as a single attribute. Consider the following secrets:\n\n```toml\n[db_credentials]\nusername = \"my_username\"\npassword = \"my_password\"\n```\n\nRather than passing each secret as attributes in a function, you can more compactly pass the section to achieve the same result. See the notional code below, which uses the secrets above:\n\n```python\n# Verbose version\nmy_db.connect(username=st.secrets.db_credentials.username, password=st.secrets.db_credentials.password)\n\n# Far more compact version!\nmy_db.connect(**st.secrets.db_credentials)\n```\n\n### Error handling\n\nHere are some common errors you might encounter when using secrets management.\n\n- If a `.streamlit/secrets.toml` is created _while_ the app is running, the server needs to be restarted for changes to be reflected in the app.\n- If you try accessing a secret, but no `secrets.toml` file exists, Streamlit will raise a `FileNotFoundError` exception:\n \"Secrets\n- If you try accessing a secret that doesn't exist, Streamlit will raise a `KeyError` exception:\n\n ```python\n import streamlit as st\n\n st.write(st.secrets[\"nonexistent_key\"])\n ```\n\n \"Secrets\n\n### Use secrets on Streamlit Community Cloud\n\nWhen you deploy your app to [Streamlit Community Cloud](https://streamlit.io/cloud), you can use the same secrets management workflow as you would locally. However, you'll need to also set up your secrets in the Community Cloud Secrets Management console. Learn how to do so via the Cloud-specific [Secrets management](/deploy/streamlit-community-cloud/deploy-your-app/secrets-management) documentation." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/session-state", - "content": "# Add statefulness to apps\n\n## What is State?\n\nWe define access to a Streamlit app in a browser tab as a **session**. For each browser tab that connects to the Streamlit server, a new session is created. Streamlit reruns your script from top to bottom every time you interact with your app. Each reruns takes place in a blank slate: no variables are shared between runs.\n\nSession 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 pages inside a [multipage app](/develop/concepts/multipage-apps).\n\nIn this guide, we will illustrate the usage of **Session State** and **Callbacks** as we build a stateful Counter app.\n\nFor details on the Session State and Callbacks API, please refer to our [Session State API Reference Guide](/develop/api-reference/caching-and-state/st.session_state).\n\nAlso, check out this Session State basics tutorial video by Streamlit Developer Advocate Dr. Marisa Smith to get started:\n\n\n\n## Build a Counter\n\nLet's call our script `counter.py`. It initializes a `count` variable and has a button to increment the value stored in the `count` variable:\n\n```python\nimport streamlit as st\n\nst.title('Counter Example')\ncount = 0\n\nincrement = st.button('Increment')\nif increment:\n count += 1\n\nst.write('Count = ', count)\n```\n\nNo matter how many times we press the **_Increment_** button in the above app, the `count` remains at 1. Let's understand why:\n\n- Each time we press the **_Increment_** button, Streamlit reruns `counter.py` from top to bottom, and with every run, `count` gets initialized to `0` .\n- Pressing **_Increment_** subsequently adds 1 to 0, thus `count=1` no matter how many times we press **_Increment_**.\n\nAs we'll see later, we can avoid this issue by storing `count` as a Session State variable. By doing so, we're indicating to Streamlit that it should maintain the value stored inside a Session State variable across app reruns.\n\nLet's learn more about the API to use Session State.\n\n### Initialization\n\nThe Session State API follows a field-based API, which is very similar to Python dictionaries:\n\n```python\nimport streamlit as st\n\n# Check if 'key' already exists in session_state\n# If not, then initialize it\nif 'key' not in st.session_state:\n st.session_state['key'] = 'value'\n\n# Session State also supports the attribute based syntax\nif 'key' not in st.session_state:\n st.session_state.key = 'value'\n```\n\n### Reads and updates\n\nRead the value of an item in Session State by passing the item to `st.write` :\n\n```python\nimport streamlit as st\n\nif 'key' not in st.session_state:\n st.session_state['key'] = 'value'\n\n# Reads\nst.write(st.session_state.key)\n\n# Outputs: value\n```\n\nUpdate an item in Session State by assigning it a value:\n\n```python\nimport streamlit as st\n\nif 'key' not in st.session_state:\n st.session_state['key'] = 'value'\n\n# Updates\nst.session_state.key = 'value2' # Attribute API\nst.session_state['key'] = 'value2' # Dictionary like API\n```\n\nStreamlit throws an exception if an uninitialized variable is accessed:\n\n```python\nimport streamlit as st\n\nst.write(st.session_state['value'])\n\n# Throws an exception!\n```\n\n![state-uninitialized-exception](/images/state_uninitialized_exception.png)\n\nLet's now take a look at a few examples that illustrate how to add Session State to our Counter app.\n\n### Example 1: Add Session State\n\nNow that we've got a hang of the Session State API, let's update our Counter app to use Session State:\n\n```python\nimport streamlit as st\n\nst.title('Counter Example')\nif 'count' not in st.session_state:\n st.session_state.count = 0\n\nincrement = st.button('Increment')\nif increment:\n st.session_state.count += 1\n\nst.write('Count = ', st.session_state.count)\n```\n\nAs you can see in the above example, pressing the **_Increment_** button updates the `count` each time.\n\n### Example 2: Session State and Callbacks\n\nNow that we've built a basic Counter app using Session State, let's move on to something a little more complex. The next example uses Callbacks with Session State.\n\n**Callbacks**: A callback is a Python function which gets called when an input widget changes. Callbacks can be used with widgets using the parameters `on_change` (or `on_click`), `args`, and `kwargs`. The full Callbacks API can be found in our [Session State API Reference Guide](/develop/api-reference/caching-and-state/st.session_state#use-callbacks-to-update-session-state).\n\n```python\nimport streamlit as st\n\nst.title('Counter Example using Callbacks')\nif 'count' not in st.session_state:\n st.session_state.count = 0\n\ndef increment_counter():\n st.session_state.count += 1\n\nst.button('Increment', on_click=increment_counter)\n\nst.write('Count = ', st.session_state.count)\n```\n\nNow, pressing the **_Increment_** button updates the count each time by calling the `increment_counter()` function.\n\n### Example 3: Use args and kwargs in Callbacks\n\nCallbacks also support passing arguments using the `args` parameter in a widget:\n\n```python\nimport streamlit as st\n\nst.title('Counter Example using Callbacks with args')\nif 'count' not in st.session_state:\n st.session_state.count = 0\n\nincrement_value = st.number_input('Enter a value', value=0, step=1)\n\ndef increment_counter(increment_value):\n st.session_state.count += increment_value\n\nincrement = st.button('Increment', on_click=increment_counter,\n args=(increment_value, ))\n\nst.write('Count = ', st.session_state.count)\n```\n\nAdditionally, we can also use the `kwargs` parameter in a widget to pass named arguments to the callback function as shown below:\n\n```python\nimport streamlit as st\n\nst.title('Counter Example using Callbacks with kwargs')\nif 'count' not in st.session_state:\n st.session_state.count = 0\n\ndef increment_counter(increment_value=0):\n st.session_state.count += increment_value\n\ndef decrement_counter(decrement_value=0):\n st.session_state.count -= decrement_value\n\nst.button('Increment', on_click=increment_counter,\n\tkwargs=dict(increment_value=5))\n\nst.button('Decrement', on_click=decrement_counter,\n\tkwargs=dict(decrement_value=1))\n\nst.write('Count = ', st.session_state.count)\n```\n\n### Example 4: Forms and Callbacks\n\nSay we now want to not only increment the `count`, but also store when it was last updated. We illustrate doing this using Callbacks and `st.form`:\n\n```python\nimport streamlit as st\nimport datetime\n\nst.title('Counter Example')\nif 'count' not in st.session_state:\n st.session_state.count = 0\n st.session_state.last_updated = datetime.time(0,0)\n\ndef update_counter():\n st.session_state.count += st.session_state.increment_value\n st.session_state.last_updated = st.session_state.update_time\n\nwith st.form(key='my_form'):\n st.time_input(label='Enter the time', value=datetime.datetime.now().time(), key='update_time')\n st.number_input('Enter a value', value=0, step=1, key='increment_value')\n submit = st.form_submit_button(label='Update', on_click=update_counter)\n\nst.write('Current Count = ', st.session_state.count)\nst.write('Last Updated = ', st.session_state.last_updated)\n```\n\n## Advanced concepts\n\n### Session State and Widget State association\n\nSession State provides the functionality to store variables across reruns. Widget state (i.e. the value of a widget) is also stored in a session.\n\nFor simplicity, we have _unified_ this information in one place. i.e. the Session State. This convenience feature makes it super easy to read or write to the widget's state anywhere in the app's code. Session State variables mirror the widget value using the `key` argument.\n\nWe illustrate this with the following example. Let's say we have an app with a slider to represent temperature in Celsius. We can **set** and **get** the value of the temperature widget by using the Session State API, as follows:\n\n```python\nimport streamlit as st\n\nif \"celsius\" not in st.session_state:\n # set the initial default value of the slider widget\n st.session_state.celsius = 50.0\n\nst.slider(\n \"Temperature in Celsius\",\n min_value=-100.0,\n max_value=100.0,\n key=\"celsius\"\n)\n\n# This will get the value of the slider widget\nst.write(st.session_state.celsius)\n```\n\nThere is a limitation to setting widget values using the Session State API.\n\n\n\nStreamlit **does not allow** setting widget values via the Session State API for `st.button` and `st.file_uploader`.\n\n\n\nThe following example will raise a `StreamlitAPIException` on trying to set the state of `st.button` via the Session State API:\n\n```python\nimport streamlit as st\n\nif 'my_button' not in st.session_state:\n st.session_state.my_button = True\n # Streamlit will raise an Exception on trying to set the state of button\n\nst.button('Submit', key='my_button')\n```\n\n\"state-button-exception\"\n\n### Serializable Session State\n\nSerialization refers to the process of converting an object or data structure into a format that can be persisted and shared, and allowing you to recover the dataโ€™s original structure. Pythonโ€™s built-in [pickle](https://docs.python.org/3/library/pickle.html) module serializes Python objects to a byte stream (\"pickling\") and deserializes the stream into an object (\"unpickling\").\n\nBy default, Streamlitโ€™s [Session State](/develop/concepts/architecture/session-state) allows you to persist any Python object for the duration of the session, irrespective of the objectโ€™s pickle-serializability. This property lets you store Python primitives such as integers, floating-point numbers, complex numbers and booleans, dataframes, and even [lambdas](https://docs.python.org/3/reference/expressions.html#lambda) returned by functions. However, some execution environments may require serializing all data in Session State, so it may be useful to detect incompatibility during development, or when the execution environment will stop supporting it in the future.\n\nTo that end, Streamlit provides a `runner.enforceSerializableSessionState` [configuration option](/develop/concepts/configuration) that, when set to `true`, only allows pickle-serializable objects in Session State. To enable the option, either create a global or project config file with the following or use it as a command-line flag:\n\n```toml\n# .streamlit/config.toml\n[runner]\nenforceSerializableSessionState = true\n```\n\nBy \"_pickle-serializable_\", we mean calling `pickle.dumps(obj)` should not raise a [`PicklingError`](https://docs.python.org/3/library/pickle.html#pickle.PicklingError) exception. When the config option is enabled, adding unserializable data to session state should result in an exception. E.g.,\n\n```python\nimport streamlit as st\n\ndef unserializable_data():\n\t\treturn lambda x: x\n\n#๐Ÿ‘‡ results in an exception when enforceSerializableSessionState is on\nst.session_state.unserializable = unserializable_data()\n```\n\n\"UnserializableSessionStateError\"\n\n\n\nWhen `runner.enforceSerializableSessionState` is set to `true`, Session State implicitly uses the `pickle` module, which is known to be insecure. Ensure all data saved and retrieved from Session State is trusted because it is possible to construct malicious pickle data that will execute arbitrary code during unpickling. Never load data that could have come from an untrusted source in an unsafe mode or that could have been tampered with. **Only load data you trust**.\n\n\n\n### Caveats and limitations\n\nHere are some limitations to keep in mind when using Session State:\n\n- Session State exists for as long as the tab is open and connected to the Streamlit server. As soon as you close the tab, everything stored in Session State is lost.\n- Session State is not persisted. If the Streamlit server crashes, then everything stored in Session State gets wiped\n- For caveats and limitations with the Session State API, please see the [API limitations](/develop/api-reference/caching-and-state/st.session_state#caveats-and-limitations)." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/architecture", - "content": "# Understanding Streamlit's client-server architecture\n\nStreamlit apps have a client-server structure. The Python backend of your app is the server. The frontend you view through a browser is the client. When you develop an app locally, your computer runs both the server and the client. If someone views your app across a local or global network, the server and client run on different machines. If you intend to share or deploy your app, it's important to understand this client-server structure to avoid common pitfalls.\n\n## Python backend (server)\n\nWhen you execute the command `streamlit run your_app.py`, your computer uses Python to start up a Streamlit server. This server is the brains of your app and performs the computations for all users who view your app. Whether users view your app across a local network or the internet, the Streamlit server runs on the one machine where the app was initialized with `streamlit run`. The machine running your Streamlit server is also called a host.\n\n## Browser frontend (client)\n\nWhen someone views your app through a browser, their device is a Streamlit client. When you view your app from the same computer where you are running or developing your app, then server and client are coincidentally running on the same machine. However, when users view your app across a local network or the internet, the client runs on a different machine from the server.\n\n## Server-client impact on app design\n\nKeep in mind the following considerations when building your Streamlit app:\n\n- The computer running or hosting your Streamlit app is responsible for providing the compute and storage necessary to run your app for all users and must be sized appropriately to handle concurrent users.\n- Your app will not have access to a user's files, directories, or OS. Your app can only work with specific files a user has uploaded to your app through a widget like `st.file_uploader`.\n- If your app communicates with any peripheral devices (like cameras), you must use Streamlit commands or custom components that will access those devices _through the user's browser_ and correctly communicate between the client (frontend) and server (backend).\n- If your app opens or uses any program or process outside of Python, they will run on the server. For example, you may want to use `webrowser` to open a browser for the user, but this will not work as expected when viewing your app over a network; it will open a browser on the Streamlit server, unseen by the user." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/forms", - "content": "# Using forms\n\nWhen you don't want to rerun your script with each input made by a user, [`st.form`](/develop/api-reference/execution-flow/st.form) is here to help! Forms make it easy to batch user input into a single rerun. This guide to using forms provides examples and explains how users interact with forms.\n\n## Example\n\nIn the following example, a user can set multiple parameters to update the map. As the user changes the parameters, the script will not rerun and the map will not update. When the user submits the form with the button labeled \"**Update map**\", the script reruns and the map updates.\n\nIf at any time the user clicks \"**Generate new points**\" which is outside of the form, the script will rerun. If the user has any unsubmitted changes within the form, these will _not_ be sent with the rerun. All changes made to a form will only be sent to the Python backend when the form itself is submitted.\n\n\n\n```python\nimport streamlit as st\nimport pandas as pd\nimport numpy as np\n\ndef get_data():\n df = pd.DataFrame({\n \"lat\": np.random.randn(200) / 50 + 37.76,\n \"lon\": np.random.randn(200) / 50 + -122.4,\n \"team\": ['A','B']*100\n })\n return df\n\nif st.button('Generate new points'):\n st.session_state.df = get_data()\nif 'df' not in st.session_state:\n st.session_state.df = get_data()\ndf = st.session_state.df\n\nwith st.form(\"my_form\"):\n header = st.columns([1,2,2])\n header[0].subheader('Color')\n header[1].subheader('Opacity')\n header[2].subheader('Size')\n\n row1 = st.columns([1,2,2])\n colorA = row1[0].color_picker('Team A', '#0000FF')\n opacityA = row1[1].slider('A opacity', 20, 100, 50, label_visibility='hidden')\n sizeA = row1[2].slider('A size', 50, 200, 100, step=10, label_visibility='hidden')\n\n row2 = st.columns([1,2,2])\n colorB = row2[0].color_picker('Team B', '#FF0000')\n opacityB = row2[1].slider('B opacity', 20, 100, 50, label_visibility='hidden')\n sizeB = row2[2].slider('B size', 50, 200, 100, step=10, label_visibility='hidden')\n\n st.form_submit_button('Update map')\n\nalphaA = int(opacityA*255/100)\nalphaB = int(opacityB*255/100)\n\ndf['color'] = np.where(df.team=='A',colorA+f'{alphaA:02x}',colorB+f'{alphaB:02x}')\ndf['size'] = np.where(df.team=='A',sizeA, sizeB)\n\nst.map(df, size='size', color='color')\n```\n\n\n\n\n\n## User interaction\n\nIf a widget is not in a form, that widget will trigger a script rerun whenever a user changes its value. For widgets with keyed input (`st.number_input`, `st.text_input`, `st.text_area`), a new value triggers a rerun when the user clicks or tabs out of the widget. A user can also submit a change by pressing `Enter` while their cursor is active in the widget.\n\nOn the other hand if a widget is inside of a form, the script will not rerun when a user clicks or tabs out of that widget. For widgets inside a form, the script will rerun when the form is submitted and all widgets within the form will send their updated values to the Python backend.\n\n![Forms](/images/forms.gif)\n\nA user can submit a form using **Enter** on their keyboard if their cursor active in a widget that accepts keyed input. Within `st.number_input` and `st.text_input` a user presses **Enter** to submit the form. Within `st.text_area` a user presses **Ctrl+Enter**/**โŒ˜+Enter** to submit the form.\n\n![Keyboard-submit forms](/images/form-submit-keyboard.png)\n\n## Widget values\n\nBefore a form is submitted, all widgets within that form will have default values, just like widgets outside of a form have default values.\n\n```python\nimport streamlit as st\n\nwith st.form(\"my_form\"):\n st.write(\"Inside the form\")\n my_number = st.slider('Pick a number', 1, 10)\n my_color = st.selectbox('Pick a color', ['red','orange','green','blue','violet'])\n st.form_submit_button('Submit my picks')\n\n# This is outside the form\nst.write(my_number)\nst.write(my_color)\n```\n\n\n\n## Forms are containers\n\nWhen `st.form` is called, a container is created on the frontend. You can write to that container like you do with other [container elements](/develop/api-reference/layout). That is, you can use Python's `with` statement as shown in the example above, or you can assign the form container to a variable and call methods on it directly. Additionally, you can place `st.form_submit_button` anywhere in the form container.\n\n```python\nimport streamlit as st\n\nanimal = st.form('my_animal')\n\n# This is writing directly to the main body. Since the form container is\n# defined above, this will appear below everything written in the form.\nsound = st.selectbox('Sounds like', ['meow','woof','squeak','tweet'])\n\n# These methods called on the form container, so they appear inside the form.\nsubmit = animal.form_submit_button(f'Say it with {sound}!')\nsentence = animal.text_input('Your sentence:', 'Where\\'s the tuna?')\nsay_it = sentence.rstrip('.,!?') + f', {sound}!'\nif submit:\n animal.subheader(say_it)\nelse:\n animal.subheader(' ')\n```\n\n\n\n## Processing form submissions\n\nThe purpose of a form is to override the default behavior of Streamlit which reruns a script as soon as the user makes a change. For widgets outside of a form, the logical flow is:\n\n1. The user changes a widget's value on the frontend.\n2. The widget's value in `st.session_state` and in the Python backend (server) is updated.\n3. The script rerun begins.\n4. If the widget has a callback, it is executed as a prefix to the page rerun.\n5. When the updated widget's function is executed during the rerun, it outputs the new value.\n\nFor widgets inside a form, any changes made by a user (step 1) do not get passed to the Python backend (step 2) until the form is submitted. Furthermore, the only widget inside a form that can have a callback function is the `st.form_submit_button`. If you need to execute a process using newly submitted values, you have three major patterns for doing so.\n\n### Execute the process after the form\n\nIf you need to execute a one-time process as a result of a form submission, you can condition that process on the `st.form_submit_button` and execute it after the form. If you need results from your process to display above the form, you can use containers to control where the form displays relative to your output.\n\n```python\nimport streamlit as st\n\ncol1,col2 = st.columns([1,2])\ncol1.title('Sum:')\n\nwith st.form('addition'):\n a = st.number_input('a')\n b = st.number_input('b')\n submit = st.form_submit_button('add')\n\nif submit:\n col2.title(f'{a+b:.2f}')\n```\n\n\n\n### Use a callback with session state\n\nYou can use a callback to execute a process as a prefix to the script rerunning.\n\n\n\nWhen processing newly updated values within a callback, do not pass those values to the callback directly through the `args` or `kwargs` parameters. You need to assign a key to any widget whose value you use within the callback. If you look up the value of that widget from `st.session_state` within the body of the callback, you will be able to access the newly submitted value. See the example below.\n\n\n\n```python\nimport streamlit as st\n\nif 'sum' not in st.session_state:\n st.session_state.sum = ''\n\ndef sum():\n result = st.session_state.a + st.session_state.b\n st.session_state.sum = result\n\ncol1,col2 = st.columns(2)\ncol1.title('Sum:')\nif isinstance(st.session_state.sum, float):\n col2.title(f'{st.session_state.sum:.2f}')\n\nwith st.form('addition'):\n st.number_input('a', key = 'a')\n st.number_input('b', key = 'b')\n st.form_submit_button('add', on_click=sum)\n```\n\n\n\n### Use `st.rerun`\n\nIf your process affects content above your form, another alternative is using an extra rerun. This can be less resource-efficient though, and may be less desirable that the above options.\n\n```python\nimport streamlit as st\n\nif 'sum' not in st.session_state:\n st.session_state.sum = ''\n\ncol1,col2 = st.columns(2)\ncol1.title('Sum:')\nif isinstance(st.session_state.sum, float):\n col2.title(f'{st.session_state.sum:.2f}')\n\nwith st.form('addition'):\n a = st.number_input('a')\n b = st.number_input('b')\n submit = st.form_submit_button('add')\n\n# The value of st.session_state.sum is updated at the end of the script rerun,\n# so the displayed value at the top in col2 does not show the new sum. Trigger\n# a second rerun when the form is submitted to update the value above.\nst.session_state.sum = a + b\nif submit:\n st.rerun()\n```\n\n\n\n## Limitations\n\n- Every form must contain a `st.form_submit_button`.\n- `st.button` and `st.download_button` cannot be added to a form.\n- `st.form` cannot be embedded inside another `st.form`.\n- Callback functions can only be assigned to `st.form_submit_button` within a form; no other widgets in a form can have a callback.\n- Interdependent widgets within a form are unlikely to be particularly useful. If you pass `widget1`'s value into `widget2` when they are both inside a form, then `widget2` will only update when the form is submitted." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/caching", - "content": "# Caching overview\n\nStreamlit runs your script from top to bottom at every user interaction or code change. This execution model makes development super easy. But it comes with two major challenges:\n\n1. Long-running functions run again and again, which slows down your app.\n2. Objects get recreated again and again, which makes it hard to persist them across reruns or sessions.\n\nBut don't worry! Streamlit lets you tackle both issues with its built-in caching mechanism. Caching stores the results of slow function calls, so they only need to run once. This makes your app much faster and helps with persisting objects across reruns. Cached values are available to all users of your app. If you need to save results that should only be accessible within a session, use [Session State](/develop/concepts/architecture/session-state) instead.\n\n\n\n1. [Minimal example](#minimal-example)\n2. [Basic usage](#basic-usage)\n3. [Advanced usage](#advanced-usage)\n4. [Migrating from st.cache](#migrating-from-stcache)\n\n\n\n## Minimal example\n\nTo cache a function in Streamlit, you must decorate it with one of two decorators (`st.cache_data` or `st.cache_resource`):\n\n```python\n@st.cache_data\ndef long_running_function(param1, param2):\n return โ€ฆ\n```\n\nIn this example, decorating `long_running_function` with `@st.cache_data` tells Streamlit that whenever the function is called, it checks two things:\n\n1. The values of the input parameters (in this case, `param1` and `param2`).\n2. The code inside the function.\n\nIf this is the first time Streamlit sees these parameter values and function code, it runs the function and stores the return value in a cache. The next time the function is called with the same parameters and code (e.g., when a user interacts with the app), Streamlit will skip executing the function altogether and return the cached value instead. During development, the cache updates automatically as the function code changes, ensuring that the latest changes are reflected in the cache.\n\nAs mentioned, there are two caching decorators:\n\n- `st.cache_data`ย is the recommended way to cache computations that return data: loading a DataFrame from CSV, transforming a NumPy array, querying an API, or any other function that returns a serializable data object (str, int, float, DataFrame, array, list, โ€ฆ). It creates a new copy of the data at each function call, making it safe against [mutations and race conditions](#mutation-and-concurrency-issues). The behavior of `st.cache_data` is what you want in most cases โ€“ so if you're unsure, start withย `st.cache_data`ย and see if it works!\n- `st.cache_resource`ย is the recommended way to cache global resources like ML models or database connections โ€“ unserializable objects that you don't want to load multiple times. Using it, you can share these resources across all reruns and sessions of an app without copying or duplication. Note that any mutations to the cached return value directly mutate the object in the cache (more details below).\n\n\"Streamlit's\n\n## Basic usage\n\n### st.cache_data\n\n`st.cache_data` is your go-to command for all functions that return data โ€“ whether DataFrames, NumPy arrays, str, int, float, or other serializable types. It's the right command for almost all use cases! Within each user session, an `@st.cache_data`-decorated function returns a _copy_ of the cached return value (if the value is already cached).\n\n#### Usage\n\n
\n\nLet's look at an example of usingย `st.cache_data`. Suppose your app loads the [Uber ride-sharing dataset](https://github.com/plotly/datasets/blob/master/uber-rides-data1.csv) โ€“ a CSV file of 50 MB โ€“ from the internet into a DataFrame:\n\n```python\ndef load_data(url):\n df = pd.read_csv(url) # ๐Ÿ‘ˆ Download the data\n return df\n\ndf = load_data(\"https://github.com/plotly/datasets/raw/master/uber-rides-data1.csv\")\nst.dataframe(df)\n\nst.button(\"Rerun\")\n```\n\nRunning the `load_data` function takes 2 to 30 seconds, depending on your internet connection. (Tip: if you are on a slow connection, use [this 5 MB dataset instead](https://github.com/plotly/datasets/blob/master/26k-consumer-complaints.csv)). Without caching, the download is rerun each time the app is loaded or with user interaction. Try it yourself by clicking the button we added! Not a great experienceโ€ฆ ๐Ÿ˜•\n\nNow let's add theย `@st.cache_data`ย decorator on `load_data`:\n\n```python\n@st.cache_data # ๐Ÿ‘ˆ Add the caching decorator\ndef load_data(url):\n df = pd.read_csv(url)\n return df\n\ndf = load_data(\"https://github.com/plotly/datasets/raw/master/uber-rides-data1.csv\")\nst.dataframe(df)\n\nst.button(\"Rerun\")\n```\n\nRun the app again. You'll notice that the slow download only happens on the first run. Every subsequent rerun should be almost instant! ๐Ÿ’จ\n\n#### Behavior\n\n
\n\nHow does this work? Let's go through the behavior of `st.cache_data` step by step:\n\n- On the first run, Streamlit recognizes that it has never called the `load_data` function with the specified parameter value (the URL of the CSV file) So it runs the function and downloads the data.\n- Now our caching mechanism becomes active: the returned DataFrame is serialized (converted to bytes) viaย [pickle](https://docs.python.org/3/library/pickle.html)ย and stored in the cache (together with the value of the `url` parameter).\n- On the next run, Streamlit checks the cache for an entry of `load_data` with the specific `url`. There is one! So it retrieves the cached object, deserializes it to a DataFrame, and returns it instead of re-running the function and downloading the data again.\n\nThis process of serializing and deserializing the cached object creates a copy of our original DataFrame. While this copying behavior may seem unnecessary, it's what we want when caching data objects since it effectively prevents mutation and concurrency issues. Read the section โ€œ[Mutation and concurrency issues](#mutation-and-concurrency-issues)\" below to understand this in more detail.\n\n\n\n`st.cache_data` implicitly uses the `pickle` module, which is known to be insecure. Anything your cached function returns is pickled and stored, then unpickled on retrieval. Ensure your cached functions return trusted values because it is possible to construct malicious pickle data that will execute arbitrary code during unpickling. Never load data that could have come from an untrusted source in an unsafe mode or that could have been tampered with. **Only load data you trust**.\n\n\n\n#### Examples\n\n
\n\n**DataFrame transformations**\n\nIn the example above, we already showed how to cache loading a DataFrame. It can also be useful to cache DataFrame transformations such as `df.filter`, `df.apply`, or `df.sort_values`. Especially with large DataFrames, these operations can be slow.\n\n```python\n@st.cache_data\ndef transform(df):\n df = df.filter(items=['one', 'three'])\n df = df.apply(np.sum, axis=0)\n\treturn df\n```\n\n**Array computations**\n\nSimilarly, it can make sense to cache computations on NumPy arrays:\n\n```python\n@st.cache_data\ndef add(arr1, arr2):\n\treturn arr1 + arr2\n```\n\n**Database queries**\n\nYou usually make SQL queries to load data into your app when working with databases. Repeatedly running these queries can be slow, cost money, and degrade the performance of your database. We strongly recommend caching any database queries in your app. See also [our guides on connecting Streamlit to different databases](/develop/tutorials/databases) for in-depth examples.\n\n```python\nconnection = database.connect()\n\n@st.cache_data\ndef query():\n return pd.read_sql_query(\"SELECT * from table\", connection)\n```\n\n\n\nYou should set a `ttl` (time to live) to get new results from your database. If you set `st.cache_data(ttl=3600)`, Streamlit invalidates any cached values after 1 hour (3600 seconds) and runs the cached function again. See details in [Controlling cache size and duration](#controlling-cache-size-and-duration).\n\n\n\n**API calls**\n\nSimilarly, it makes sense to cache API calls. Doing so also avoids rate limits.\n\n```python\n@st.cache_data\ndef api_call():\n response = requests.get('https://jsonplaceholder.typicode.com/posts/1')\n return response.json()\n```\n\n**Running ML models (inference)**\n\nRunning complex machine learning models can use significant time and memory. To avoid rerunning the same computations over and over, use caching.\n\n```python\n@st.cache_data\ndef run_model(inputs):\n return model(inputs)\n```\n\n### st.cache_resource\n\n`st.cache_resource` is the right command to cache โ€œresources\" that should be available globally across all users, sessions, and reruns. It has more limited use cases than `st.cache_data`, especially for caching database connections and ML models. Within each user session, an `@st.cache_resource`-decorated function returns the cached instance of the return value (if the value is already cached). Therefore, objects cached by `st.cache_resource` act like singletons and can mutate.\n\n#### Usage\n\nAs an example for `st.cache_resource`, let's look at a typical machine learning app. As a first step, we need to load an ML model. We do this with [Hugging Face's transformers library](https://huggingface.co/docs/transformers/index):\n\n```python\nfrom transformers import pipeline\nmodel = pipeline(\"sentiment-analysis\") # ๐Ÿ‘ˆ Load the model\n```\n\nIf we put this code into a Streamlit app directly, the app will load the model at each rerun or user interaction. Repeatedly loading the model poses two problems:\n\n- Loading the model takes time and slows down the app.\n- Each session loads the model from scratch, which takes up a huge amount of memory.\n\nInstead, it would make much more sense to load the model once and use that same object across all users and sessions. That's exactly the use case for `st.cache_resource`! Let's add it to our app and process some text the user entered:\n\n```python\nfrom transformers import pipeline\n\n@st.cache_resource # ๐Ÿ‘ˆ Add the caching decorator\ndef load_model():\n return pipeline(\"sentiment-analysis\")\n\nmodel = load_model()\n\nquery = st.text_input(\"Your query\", value=\"I love Streamlit! ๐ŸŽˆ\")\nif query:\n result = model(query)[0] # ๐Ÿ‘ˆ Classify the query text\n st.write(result)\n```\n\nIf you run this app, you'll see that the app calls `load_model` only once โ€“ right when the app starts. Subsequent runs will reuse that same model stored in the cache, saving time and memory!\n\n#### Behavior\n\n
\n\nUsing `st.cache_resource` is very similar to using `st.cache_data`. But there are a few important differences in behavior:\n\n- `st.cache_resource` does **not** create a copy of the cached return value but instead stores the object itself in the cache. All mutations on the function's return value directly affect the object in the cache, so you must ensure that mutations from multiple sessions do not cause problems. In short, the return value must be thread-safe.\n\n \n\n Using `st.cache_resource` on objects that are not thread-safe might lead to crashes or corrupted data. Learn more below under [Mutation and concurrency issues](#mutation-and-concurrency-issues).\n \n\n- Not creating a copy means there's just one global instance of the cached return object, which saves memory, e.g. when using a large ML model. In computer science terms, we create a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern).\n- Return values of functions do not need to be serializable. This behavior is great for types not serializable by nature, e.g., database connections, file handles, or threads. Caching these objects with `st.cache_data` is not possible.\n\n#### Examples\n\n
\n\n**Database connections**\n\n`st.cache_resource` is useful for connecting to databases. Usually, you're creating a connection object that you want to reuse globally for every query. Creating a new connection object at each run would be inefficient and might lead to connection errors. That's exactly what `st.cache_resource` can do, e.g., for a Postgres database:\n\n```python\n@st.cache_resource\ndef init_connection():\n host = \"hh-pgsql-public.ebi.ac.uk\"\n database = \"pfmegrnargs\"\n user = \"reader\"\n password = \"NWDMCE5xdipIjRrp\"\n return psycopg2.connect(host=host, database=database, user=user, password=password)\n\nconn = init_connection()\n```\n\nOf course, you can do the same for any other database. Have a look at [our guides on how to connect Streamlit to databases](/develop/tutorials/databases) for in-depth examples.\n\n**Loading ML models**\n\nYour app should always cache ML models, so they are not loaded into memory again for every new session. See the [example](#usage-1) above for how this works with ๐Ÿค—ย Hugging Face models. You can do the same thing for PyTorch, TensorFlow, etc. Here's an example for PyTorch:\n\n```python\n@st.cache_resource\ndef load_model():\n model = torchvision.models.resnet50(weights=ResNet50_Weights.DEFAULT)\n model.eval()\n return model\n\nmodel = load_model()\n```\n\n### Deciding which caching decorator to use\n\n
\n\nThe sections above showed many common examples for each caching decorator. But there are edge cases for which it's less trivial to decide which caching decorator to use. Eventually, it all comes down to the difference between โ€œdata\" and โ€œresource\":\n\n- Data are serializable objects (objects that can be converted to bytes viaย [pickle](https://docs.python.org/3/library/pickle.html)) that you could easily save to disk. Imagine all the types you would usually store in a database or on a file system โ€“ basic types like str, int, and float, but also arrays, DataFrames, images, or combinations of these types (lists, tuples, dicts, and so on).\n- Resources are unserializable objects that you usually would not save to disk or a database. They are often more complex, non-permanent objects like database connections, ML models, file handles, threads, etc.\n\nFrom the types listed above, it should be obvious that most objects in Python are โ€œdata.\" That's also why `st.cache_data` is the correct command for almost all use cases. `st.cache_resource` is a more exotic command that you should only use in specific situations.\n\nOr if you're lazy and don't want to think too much, look up your use case or return type in the table below ๐Ÿ˜‰:\n\n| Use case | Typical return types | Caching decorator |\n| :----------------------------------- | -------------------------------------------------------------------------------------------------------------------------: | -----------------------------------------------------------------------------------------------------------------------------------------------------------: |\n| Reading a CSV file with pd.read_csv | pandas.DataFrame | st.cache_data |\n| Reading a text file | str, list of str | st.cache_data |\n| Transforming pandas dataframes | pandas.DataFrame, pandas.Series | st.cache_data |\n| Computing with numpy arrays | numpy.ndarray | st.cache_data |\n| Simple computations with basic types | str, int, float, โ€ฆ | st.cache_data |\n| Querying a database | pandas.DataFrame | st.cache_data |\n| Querying an API | pandas.DataFrame, str, dict | st.cache_data |\n| Running an ML model (inference) | pandas.DataFrame, str, int, dict, list | st.cache_data |\n| Creating or processing images | PIL.Image.Image, numpy.ndarray | st.cache_data |\n| Creating charts | matplotlib.figure.Figure, plotly.graph_objects.Figure, altair.Chart | st.cache_data (but some libraries require st.cache_resource, since the chart object is not serializable โ€“ make sure not to mutate the chart after creation!) |\n| Lazy computations | polars.LazyFrame | st.cache_resource (but may be better to use st.cache_data on the collected results) |\n| Loading ML models | transformers.Pipeline, torch.nn.Module, tensorflow.keras.Model | st.cache_resource |\n| Initializing database connections | pyodbc.Connection, sqlalchemy.engine.base.Engine, psycopg2.connection, mysql.connector.MySQLConnection, sqlite3.Connection | st.cache_resource |\n| Opening persistent file handles | \\_io.TextIOWrapper | st.cache_resource |\n| Opening persistent threads | threading.thread | st.cache_resource |\n\n## Advanced usage\n\n### Controlling cache size and duration\n\nIf your app runs for a long time and constantly caches functions, you might run into two problems:\n\n1. The app runs out of memory because the cache is too large.\n2. Objects in the cache become stale, e.g. because you cached old data from a database.\n\nYou can combat these problems with the `ttl` and `max_entries` parameters, which are available for both caching decorators.\n\n**The `ttl` (time-to-live) parameter**\n\n`ttl` sets a time to live on a cached function. If that time is up and you call the function again, the app will discard any old, cached values, and the function will be rerun. The newly computed value will then be stored in the cache. This behavior is useful for preventing stale data (problem 2) and the cache from growing too large (problem 1). Especially when pulling data from a database or API, you should always set a `ttl` so you are not using old data. Here's an example:\n\n```python\n@st.cache_data(ttl=3600) # ๐Ÿ‘ˆ Cache data for 1 hour (=3600 seconds)\ndef get_api_data():\n data = api.get(...)\n return data\n```\n\n\n\nYou can also set `ttl` values using `timedelta`, e.g., `ttl=datetime.timedelta(hours=1)`.\n\n\n\n**The `max_entries` parameter**\n\n`max_entries` sets the maximum number of entries in the cache. An upper bound on the number of cache entries is useful for limiting memory (problem 1), especially when caching large objects. The oldest entry will be removed when a new entry is added to a full cache. Here's an example:\n\n```python\n@st.cache_data(max_entries=1000) # ๐Ÿ‘ˆ Maximum 1000 entries in the cache\ndef get_large_array(seed):\n np.random.seed(seed)\n arr = np.random.rand(100000)\n return arr\n```\n\n### Customizing the spinner\n\nBy default, Streamlit shows a small loading spinner in the app when a cached function is running. You can modify it easily with the `show_spinner` parameter, which is available for both caching decorators:\n\n```python\n@st.cache_data(show_spinner=False) # ๐Ÿ‘ˆ Disable the spinner\ndef get_api_data():\n data = api.get(...)\n return data\n\n@st.cache_data(show_spinner=\"Fetching data from API...\") # ๐Ÿ‘ˆ Use custom text for spinner\ndef get_api_data():\n data = api.get(...)\n return data\n```\n\n### Excluding input parameters\n\nIn a cached function, all input parameters must be hashable. Let's quickly explain why and what it means. When the function is called, Streamlit looks at its parameter values to determine if it was cached before. Therefore, it needs a reliable way to compare the parameter values across function calls. Trivial for a string or int โ€“ but complex for arbitrary objects! Streamlit uses [hashing](https://en.wikipedia.org/wiki/Hash_function) to solve that. It converts the parameter to a stable key and stores that key. At the next function call, it hashes the parameter again and compares it with the stored hash key.\n\nUnfortunately, not all parameters are hashable! E.g., you might pass an unhashable database connection or ML model to your cached function. In this case, you can exclude input parameters from caching. Simply prepend the parameter name with an underscore (e.g., `_param1`), and it will not be used for caching. Even if it changes, Streamlit will return a cached result if all the other parameters match up.\n\nHere's an example:\n\n```python\n@st.cache_data\ndef fetch_data(_db_connection, num_rows): # ๐Ÿ‘ˆ Don't hash _db_connection\n data = _db_connection.fetch(num_rows)\n return data\n\nconnection = init_connection()\nfetch_data(connection, 10)\n```\n\nBut what if you want to cache a function that takes an unhashable parameter? For example, you might want to cache a function that takes an ML model as input and returns the layer names of that model. Since the model is the only input parameter, you cannot exclude it from caching. In this case you can use the `hash_funcs` parameter to specify a custom hashing function for the model.\n\n### The `hash_funcs` parameter\n\nAs described above, Streamlit's caching decorators hash the input parameters and cached function's signature to determine whether the function has been run before and has a return value stored (\"cache hit\") or needs to be run (\"cache miss\"). Input parameters that are not hashable by Streamlit's hashing implementation can be ignored by prepending an underscore to their name. But there two rare cases where this is undesirable. i.e. where you want to hash the parameter that Streamlit is unable to hash:\n\n1. When Streamlit's hashing mechanism fails to hash a parameter, resulting in a `UnhashableParamError` being raised.\n2. When you want to override Streamlit's default hashing mechanism for a parameter.\n\nLet's discuss each of these cases in turn with examples.\n\n#### Example 1: Hashing a custom class\n\nStreamlit does not know how to hash custom classes. If you pass a custom class to a cached function, Streamlit will raise a `UnhashableParamError`. For example, let's define a custom class `MyCustomClass` that accepts an initial integer score. Let's also define a cached function `multiply_score` that multiplies the score by a multiplier:\n\n```python\nimport streamlit as st\n\nclass MyCustomClass:\n def __init__(self, initial_score: int):\n self.my_score = initial_score\n\n@st.cache_data\ndef multiply_score(obj: MyCustomClass, multiplier: int) -> int:\n return obj.my_score * multiplier\n\ninitial_score = st.number_input(\"Enter initial score\", value=15)\n\nscore = MyCustomClass(initial_score)\nmultiplier = 2\n\nst.write(multiply_score(score, multiplier))\n```\n\nIf you run this app, you'll see that Streamlit raises a `UnhashableParamError` since it does not know how to hash `MyCustomClass`:\n\n```python\nUnhashableParamError: Cannot hash argument 'obj' (of type __main__.MyCustomClass) in 'multiply_score'.\n```\n\nTo fix this, we can use the `hash_funcs` parameter to tell Streamlit how to hash `MyCustomClass`. We do this by passing a dictionary to `hash_funcs` that maps the name of the parameter to a hash function. The choice of hash function is up to the developer. In this case, let's define a custom hash function `hash_func` that takes the custom class as input and returns the score. We want the score to be the unique identifier of the object, so we can use it to deterministically hash the object:\n\n```python\nimport streamlit as st\n\nclass MyCustomClass:\n def __init__(self, initial_score: int):\n self.my_score = initial_score\n\ndef hash_func(obj: MyCustomClass) -> int:\n return obj.my_score # or any other value that uniquely identifies the object\n\n@st.cache_data(hash_funcs={MyCustomClass: hash_func})\ndef multiply_score(obj: MyCustomClass, multiplier: int) -> int:\n return obj.my_score * multiplier\n\ninitial_score = st.number_input(\"Enter initial score\", value=15)\n\nscore = MyCustomClass(initial_score)\nmultiplier = 2\n\nst.write(multiply_score(score, multiplier))\n```\n\nNow if you run the app, you'll see that Streamlit no longer raises a `UnhashableParamError` and the app runs as expected.\n\nLet's now consider the case where `multiply_score` is an attribute of `MyCustomClass` and we want to hash the entire object:\n\n```python\nimport streamlit as st\n\nclass MyCustomClass:\n def __init__(self, initial_score: int):\n self.my_score = initial_score\n\n @st.cache_data\n def multiply_score(self, multiplier: int) -> int:\n return self.my_score * multiplier\n\ninitial_score = st.number_input(\"Enter initial score\", value=15)\n\nscore = MyCustomClass(initial_score)\nmultiplier = 2\n\nst.write(score.multiply_score(multiplier))\n```\n\nIf you run this app, you'll see that Streamlit raises a `UnhashableParamError` since it cannot hash the argument `'self' (of type __main__.MyCustomClass) in 'multiply_score'`. A simple fix here could be to use Python's `hash()` function to hash the object:\n\n```python\nimport streamlit as st\n\nclass MyCustomClass:\n def __init__(self, initial_score: int):\n self.my_score = initial_score\n\n @st.cache_data(hash_funcs={\"__main__.MyCustomClass\": lambda x: hash(x.my_score)})\n def multiply_score(self, multiplier: int) -> int:\n return self.my_score * multiplier\n\ninitial_score = st.number_input(\"Enter initial score\", value=15)\n\nscore = MyCustomClass(initial_score)\nmultiplier = 2\n\nst.write(score.multiply_score(multiplier))\n```\n\nAbove, the hash function is defined as `lambda x: hash(x.my_score)`. This creates a hash based on the `my_score` attribute of the `MyCustomClass` instance. As long as `my_score` remains the same, the hash remains the same. Thus, the result of `multiply_score` can be retrieved from the cache without recomputation.\n\nAs an astute Pythonista, you may have been tempted to use Python's `id()` function to hash the object like so:\n\n```python\nimport streamlit as st\n\nclass MyCustomClass:\n def __init__(self, initial_score: int):\n self.my_score = initial_score\n\n @st.cache_data(hash_funcs={\"__main__.MyCustomClass\": id})\n def multiply_score(self, multiplier: int) -> int:\n return self.my_score * multiplier\n\ninitial_score = st.number_input(\"Enter initial score\", value=15)\n\nscore = MyCustomClass(initial_score)\nmultiplier = 2\n\nst.write(score.multiply_score(multiplier))\n```\n\nIf you run the app, you'll notice that Streamlit recomputes `multiply_score` each time even if `my_score` hasn't changed! Puzzled? In Python, `id()` returns the identity of an object, which is unique and constant for the object during its lifetime. This means that even if the `my_score` value is the same between two instances of `MyCustomClass`, `id()` will return different values for these two instances, leading to different hash values. As a result, Streamlit considers these two different instances as needing separate cached values, thus it recomputes the `multiply_score` each time even if `my_score` hasn't changed.\n\nThis is why we discourage using it as hash func, and instead encourage functions that return deterministic, true hash values. That said, if you know what you're doing, you can use `id()` as a hash function. Just be aware of the consequences. For example, `id` is often the _correct_ hash func when you're passing the result of an `@st.cache_resource` function as the input param to another cached function. There's a whole class of object types that arenโ€™t otherwise hashable.\n\n#### Example 2: Hashing a Pydantic model\n\nLet's consider another example where we want to hash a Pydantic model:\n\n```python\nimport streamlit as st\nfrom pydantic import BaseModel\n\nclass Person(BaseModel):\n name: str\n\n@st.cache_data\ndef identity(person: Person):\n return person\n\nperson = identity(Person(name=\"Lee\"))\nst.write(f\"The person is {person.name}\")\n```\n\nAbove, we define a custom class `Person` using Pydantic's `BaseModel` with a single attribute name. We also define an `identity` function which accepts an instance of `Person` as an arg and returns it without modification. This function is intended to cache the result, therefore, if called multiple times with the same `Person` instance, it won't recompute but return the cached instance.\n\nIf you run the app, however, you'll run into a `UnhashableParamError: Cannot hash argument 'person' (of type __main__.Person) in 'identity'.` error. This is because Streamlit does not know how to hash the `Person` class. To fix this, we can use the `hash_funcs` kwarg to tell Streamlit how to hash `Person`.\n\nIn the version below, we define a custom hash function `hash_func` that takes the `Person` instance as input and returns the name attribute. We want the name to be the unique identifier of the object, so we can use it to deterministically hash the object:\n\n```python\nimport streamlit as st\nfrom pydantic import BaseModel\n\nclass Person(BaseModel):\n name: str\n\n@st.cache_data(hash_funcs={Person: lambda p: p.name})\ndef identity(person: Person):\n return person\n\nperson = identity(Person(name=\"Lee\"))\nst.write(f\"The person is {person.name}\")\n```\n\n#### Example 3: Hashing a ML model\n\nThere may be cases where you want to pass your favorite machine learning model to a cached function. For example, let's say you want to pass a TensorFlow model to a cached function, based on what model the user selects in the app. You might try something like this:\n\n```python\nimport streamlit as st\nimport tensorflow as tf\n\n@st.cache_resource\ndef load_base_model(option):\n if option == 1:\n return tf.keras.applications.ResNet50(include_top=False, weights=\"imagenet\")\n else:\n return tf.keras.applications.MobileNetV2(include_top=False, weights=\"imagenet\")\n\n@st.cache_resource\ndef load_layers(base_model):\n return [layer.name for layer in base_model.layers]\n\noption = st.radio(\"Model 1 or 2\", [1, 2])\n\nbase_model = load_base_model(option)\n\nlayers = load_layers(base_model)\n\nst.write(layers)\n```\n\nIn the above app, the user can select one of two models. Based on the selection, the app loads the corresponding model and passes it to `load_layers`. This function then returns the names of the layers in the model. If you run the app, you'll see that Streamlit raises a `UnhashableParamError` since it cannot hash the argument `'base_model' (of type keras.engine.functional.Functional) in 'load_layers'`.\n\nIf you disable hashing for `base_model` by prepending an underscore to its name, you'll observe that regardless of which base model is chosen, the layers displayed are same. This subtle bug is due to the fact that the `load_layers` function is not re-run when the base model changes. This is because Streamlit does not hash the `base_model` argument, so it does not know that the function needs to be re-run when the base model changes.\n\nTo fix this, we can use the `hash_funcs` kwarg to tell Streamlit how to hash the `base_model` argument. In the version below, we define a custom hash function `hash_func`: `Functional: lambda x: x.name`. Our choice of hash func is informed by our knowledge that the `name` attribute of a `Functional` object or model uniquely identifies it. As long as the `name` attribute remains the same, the hash remains the same. Thus, the result of `load_layers` can be retrieved from the cache without recomputation.\n\n```python\nimport streamlit as st\nimport tensorflow as tf\nfrom keras.engine.functional import Functional\n\n@st.cache_resource\ndef load_base_model(option):\n if option == 1:\n return tf.keras.applications.ResNet50(include_top=False, weights=\"imagenet\")\n else:\n return tf.keras.applications.MobileNetV2(include_top=False, weights=\"imagenet\")\n\n@st.cache_resource(hash_funcs={Functional: lambda x: x.name})\ndef load_layers(base_model):\n return [layer.name for layer in base_model.layers]\n\noption = st.radio(\"Model 1 or 2\", [1, 2])\n\nbase_model = load_base_model(option)\n\nlayers = load_layers(base_model)\n\nst.write(layers)\n```\n\nIn the above case, we could also have used `hash_funcs={Functional: id}` as the hash function. This is because `id` is often the _correct_ hash func when you're passing the result of an `@st.cache_resource` function as the input param to another cached function.\n\n#### Example 4: Overriding Streamlit's default hashing mechanism\n\nLet's consider another example where we want to override Streamlit's default hashing mechanism for a pytz-localized datetime object:\n\n```python\nfrom datetime import datetime\nimport pytz\nimport streamlit as st\n\ntz = pytz.timezone(\"Europe/Berlin\")\n\n@st.cache_data\ndef load_data(dt):\n return dt\n\nnow = datetime.now()\nst.text(load_data(dt=now))\n\nnow_tz = tz.localize(datetime.now())\nst.text(load_data(dt=now_tz))\n```\n\nIt may be surprising to see that although `now` and `now_tz` are of the same `` type, Streamlit does not how to hash `now_tz` and raises a `UnhashableParamError`. In this case, we can override Streamlit's default hashing mechanism for `datetime` objects by passing a custom hash function to the `hash_funcs` kwarg:\n\n```python\nfrom datetime import datetime\n\nimport pytz\nimport streamlit as st\n\ntz = pytz.timezone(\"Europe/Berlin\")\n\n@st.cache_data(hash_funcs={datetime: lambda x: x.strftime(\"%a %d %b %Y, %I:%M%p\")})\ndef load_data(dt):\n return dt\n\nnow = datetime.now()\nst.text(load_data(dt=now))\n\nnow_tz = tz.localize(datetime.now())\nst.text(load_data(dt=now_tz))\n```\n\nLet's now consider a case where we want to override Streamlit's default hashing mechanism for NumPy arrays. While Streamlit natively hashes Pandas and NumPy objects, there may be cases where you want to override Streamlit's default hashing mechanism for these objects.\n\nFor example, let's say we create a cache-decorated `show_data` function that accepts a NumPy array and returns it without modification. In the bellow app, `data = df[\"str\"].unique()` (which is a NumPy array) is passed to the `show_data` function.\n\n```python\nimport time\nimport numpy as np\nimport pandas as pd\nimport streamlit as st\n\n@st.cache_data\ndef get_data():\n df = pd.DataFrame({\"num\": [112, 112, 2, 3], \"str\": [\"be\", \"a\", \"be\", \"c\"]})\n return df\n\n@st.cache_data\ndef show_data(data):\n time.sleep(2) # This makes the function take 2s to run\n return data\n\ndf = get_data()\ndata = df[\"str\"].unique()\n\nst.dataframe(show_data(data))\nst.button(\"Re-run\")\n```\n\nSince `data` is always the same, we expect the `show_data` function to return the cached value. However, if you run the app, and click the `Re-run` button, you'll notice that the `show_data` function is re-run each time. We can assume this behavior is a consequence of Streamlit's default hashing mechanism for NumPy arrays.\n\nTo work around this, let's define a custom hash function `hash_func` that takes a NumPy array as input and returns a string representation of the array:\n\n```python\nimport time\nimport numpy as np\nimport pandas as pd\nimport streamlit as st\n\n@st.cache_data\ndef get_data():\n df = pd.DataFrame({\"num\": [112, 112, 2, 3], \"str\": [\"be\", \"a\", \"be\", \"c\"]})\n return df\n\n@st.cache_data(hash_funcs={np.ndarray: str})\ndef show_data(data):\n time.sleep(2) # This makes the function take 2s to run\n return data\n\ndf = get_data()\ndata = df[\"str\"].unique()\n\nst.dataframe(show_data(data))\nst.button(\"Re-run\")\n```\n\nNow if you run the app, and click the `Re-run` button, you'll notice that the `show_data` function is no longer re-run each time. It's important to note here that our choice of hash function was very naive and not necessarily the best choice. For example, if the NumPy array is large, converting it to a string representation may be expensive. In such cases, it is up to you as the developer to define what a good hash function is for your use case.\n\n#### Static elements\n\nSince version 1.16.0, cached functions can contain Streamlit commands! For example, you can do this:\n\n```python\n@st.cache_data\ndef get_api_data():\n data = api.get(...)\n st.success(\"Fetched data from API!\") # ๐Ÿ‘ˆ Show a success message\n return data\n```\n\nAs we know, Streamlit only runs this function if it hasn't been cached before. On this first run, the `st.success` message will appear in the app. But what happens on subsequent runs? It still shows up! Streamlit realizes that there is an `st.` command inside the cached function, saves it during the first run, and replays it on subsequent runs. Replaying static elements works for both caching decorators.\n\nYou can also use this functionality to cache entire parts of your UI:\n\n```python\n@st.cache_data\ndef show_data():\n st.header(\"Data analysis\")\n data = api.get(...)\n st.success(\"Fetched data from API!\")\n st.write(\"Here is a plot of the data:\")\n st.line_chart(data)\n st.write(\"And here is the raw data:\")\n st.dataframe(data)\n```\n\n#### Input widgets\n\nYou can also use [interactive input widgets](/develop/api-reference/widgets) like `st.slider` or `st.text_input` in cached functions. Widget replay is an experimental feature at the moment. To enable it, you need to set the `experimental_allow_widgets` parameter:\n\n```python\n@st.cache_data(experimental_allow_widgets=True) # ๐Ÿ‘ˆ Set the parameter\ndef get_data():\n num_rows = st.slider(\"Number of rows to get\") # ๐Ÿ‘ˆ Add a slider\n data = api.get(..., num_rows)\n return data\n```\n\nStreamlit treats the slider like an additional input parameter to the cached function. If you change the slider position, Streamlit will see if it has already cached the function for this slider value. If yes, it will return the cached value. If not, it will rerun the function using the new slider value.\n\nUsing widgets in cached functions is extremely powerful because it lets you cache entire parts of your app. But it can be dangerous! Since Streamlit treats the widget value as an additional input parameter, it can easily lead to excessive memory usage. Imagine your cached function has five sliders and returns a 100 MB DataFrame. Then we'll add 100 MB to the cache for _every permutation_ of these five slider values โ€“ even if the sliders do not influence the returned data! These additions can make your cache explode very quickly. Please be aware of this limitation if you use widgets in cached functions. We recommend using this feature only for isolated parts of your UI where the widgets directly influence the cached return value.\n\n\n\nSupport for widgets in cached functions is experimental. We may change or remove it anytime without warning. Please use it with care!\n\n\n\n\nTwo widgets are currently not supported in cached functions: `st.file_uploader` and `st.camera_input`. We may support them in the future. Feel free to [open a GitHub issue](https://github.com/streamlit/streamlit/issues) if you need them!\n\n\n### Dealing with large data\n\nAs we explained, you should cache data objects with `st.cache_data`. But this can be slow for extremely large data, e.g., DataFrames or arrays with >100 million rows. That's because of the [copying behavior](#copying-behavior) of `st.cache_data`: on the first run, it serializes the return value to bytes and deserializes it on subsequent runs. Both operations take time.\n\nIf you're dealing with extremely large data, it can make sense to use `st.cache_resource` instead. It does not create a copy of the return value via serialization/deserialization and is almost instant. But watch out: any mutation to the function's return value (such as dropping a column from a DataFrame or setting a value in an array) directly manipulates the object in the cache. You must ensure this doesn't corrupt your data or lead to crashes. See the section on [Mutation and concurrency issues](#mutation-and-concurrency-issues) below.\n\nWhen benchmarking `st.cache_data` on pandas DataFrames with four columns, we found that it becomes slow when going beyond 100 million rows. The table shows runtimes for both caching decorators at different numbers of rows (all with four columns):\n\n| | | 10M rows | 50M rows | 100M rows | 200M rows |\n| ----------------- | --------------- | :------: | :------: | :-------: | :-------: |\n| st.cache_data | First run\\* | 0.4 s | 3 s | 14 s | 28 s |\n| | Subsequent runs | 0.2 s | 1 s | 2 s | 7 s |\n| st.cache_resource | First run\\* | 0.01 s | 0.1 s | 0.2 s | 1 s |\n| | Subsequent runs | 0 s | 0 s | 0 s | 0 s |\n\n| |\n| :----------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| _\\*For the first run, the table only shows the overhead time of using the caching decorator. It does not include the runtime of the cached function itself._ |\n\n### Mutation and concurrency issues\n\nIn the sections above, we talked a lot about issues when mutating return objects of cached functions. This topic is complicated! But it's central to understanding the behavior differences between `st.cache_data` and `st.cache_resource`. So let's dive in a bit deeper.\n\nFirst, we should clearly define what we mean by mutations and concurrency:\n\n- By **mutations**, we mean any changes made to a cached function's return value _after_ that function has been called. I.e. something like this:\n\n ```python\n @st.cache_data\n def create_list():\n l = [1, 2, 3]\n\n l = create_list() # ๐Ÿ‘ˆ Call the function\n l[0] = 2 # ๐Ÿ‘ˆ Mutate its return value\n ```\n\n- By **concurrency**, we mean that multiple sessions can cause these mutations at the same time. Streamlit is a web framework that needs to handle many users and sessions connecting to an app. If two people view an app at the same time, they will both cause the Python script to rerun, which may manipulate cached return objects at the same time โ€“ concurrently.\n\nMutating cached return objects can be dangerous. It can lead to exceptions in your app and even corrupt your data (which can be worse than a crashed app!). Below, we'll first explain the copying behavior of `st.cache_data` and show how it can avoid mutation issues. Then, we'll show how concurrent mutations can lead to data corruption and how to prevent it.\n\n#### Copying behavior\n\n`st.cache_data` creates a copy of the cached return value each time the function is called. This avoids most mutations and concurrency issues. To understand it in detail, let's go back to the [Uber ridesharing example](#usage) from the section on `st.cache_data` above. We are making two modifications to it:\n\n1. We are using `st.cache_resource` instead of `st.cache_data`. `st.cache_resource` does **not** create a copy of the cached object, so we can see what happens without the copying behavior.\n2. After loading the data, we manipulate the returned DataFrame (in place!) by dropping the column `\"Lat\"`.\n\nHere's the code:\n\n```python\n@st.cache_resource # ๐Ÿ‘ˆ Turn off copying behavior\ndef load_data(url):\n df = pd.read_csv(url)\n return df\n\ndf = load_data(\"https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data1.csv\")\nst.dataframe(df)\n\ndf.drop(columns=['Lat'], inplace=True) # ๐Ÿ‘ˆ Mutate the dataframe inplace\n\nst.button(\"Rerun\")\n```\n\nLet's run it and see what happens! The first run should work fine. But in the second run, you see an exception: `KeyError: \"['Lat'] not found in axis\"`. Why is that happening? Let's go step by step:\n\n- On the first run, Streamlit runs `load_data` and stores the resulting DataFrame in the cache. Since we're using `st.cache_resource`, it does **not** create a copy but stores the original DataFrame.\n- Then we drop the column `\"Lat\"` from the DataFrame. Note that this is dropping the column from the _original_ DataFrame stored in the cache. We are manipulating it!\n- On the second run, Streamlit returns that exact same manipulated DataFrame from the cache. It does not have the column `\"Lat\"` anymore! So our call to `df.drop` results in an exception. Pandas cannot drop a column that doesn't exist.\n\nThe copying behavior of `st.cache_data` prevents this kind of mutation error. Mutations can only affect a specific copy and not the underlying object in the cache. The next rerun will get its own, unmutated copy of the DataFrame. You can try it yourself, just replace `st.cache_resource` with `st.cache_data` above, and you'll see that everything works.\n\nBecause of this copying behavior,ย `st.cache_data`ย is the recommended way to cache data transforms and computations โ€“ anything that returns a serializable object.\n\n#### Concurrency issues\n\nNow let's look at what can happen when multiple users concurrently mutate an object in the cache. Let's say you have a function that returns a list. Again, we are using `st.cache_resource` to cache it so that we are not creating a copy:\n\n```python\n@st.cache_resource\ndef create_list():\n l = [1, 2, 3]\n return l\n\nl = create_list()\nfirst_list_value = l[0]\nl[0] = first_list_value + 1\n\nst.write(\"l[0] is:\", l[0])\n```\n\nLet's say user A runs the app. They will see the following output:\n\n```python\nl[0] is: 2\n```\n\nLet's say another user, B, visits the app right after. In contrast to user A, they will see the following output:\n\n```python\nl[0] is: 3\n```\n\nNow, user A reruns the app immediately after user B. They will see the following output:\n\n```python\nl[0] is: 4\n```\n\nWhat is happening here? Why are all outputs different?\n\n- When user A visits the app,ย `create_list()`ย is called, and the listย `[1, 2, 3]`ย is stored in the cache. This list is then returned to user A. The first value of the list, `1`, is assigned to `first_list_value` , and `l[0]`ย is changed to `2`.\n- When user B visits the app,ย `create_list()`ย returns the mutated list from the cache:ย `[2, 2, 3]`. The first value of the list, `2`, is assigned to `first_list_value` and `l[0]`ย is changed to `3`.\n- When user A reruns the app,ย `create_list()`ย returns the mutated list again:ย `[3, 2, 3]`. The first value of the list, `3`, is assigned to `first_list_value,` and `l[0]`ย is changed to 4.\n\nIf you think about it, this makes sense. Users A and B use the same list object (the one stored in the cache). And since the list object is mutated, user A's change to the list object is also reflected in user B's app.\n\nThis is why you must be careful about mutating objects cached with `st.cache_resource`, especially when multiple users access the app concurrently. If we had usedย `st.cache_data`ย instead ofย `st.cache_resource`, the app would have copied the list object for each user, and the above example would have worked as expected โ€“ users A and B would have both seen:\n\n```python\nl[0] is: 2\n```\n\n\n\nThis toy example might seem benign. But data corruption can be extremely dangerous! Imagine we had worked with the financial records of a large bank here. You surely don't want to wake up with less money on your account just because someone used the wrong caching decorator ๐Ÿ˜‰\n\n\n\n## Migrating from st.cache\n\nWe introduced the caching commands described above in Streamlit 1.18.0. Before that, we had one catch-all command `st.cache`. Using it was often confusing, resulted in weird exceptions, and was slow. That's why we replaced `st.cache` with the new commands in 1.18.0 (read more in this [blog post](https://blog.streamlit.io/introducing-two-new-caching-commands-to-replace-st-cache/)). The new commands provide a more intuitive and efficient way to cache your data and resources and are intended to replace `st.cache` in all new development.\n\nIf your app is still using `st.cache`, don't despair! Here are a few notes on migrating:\n\n- Streamlit will show a deprecation warning if your app uses `st.cache`.\n- We will not remove `st.cache` soon, so you don't need to worry about your 2-year-old app breaking. But we encourage you to try the new commands going forward โ€“ they will be way less annoying!\n- Switching code to the new commands should be easy in most cases. To decide whether to use `st.cache_data` or `st.cache_resource`, read [Deciding which caching decorator to use](#deciding-which-caching-decorator-to-use). Streamlit will also recognize common use cases and show hints right in the deprecation warnings.\n- Most parameters from `st.cache` are also present in the new commands, with a few exceptions:\n - `allow_output_mutation` does not exist anymore. You can safely delete it. Just make sure you use the right caching command for your use case.\n - `suppress_st_warning` does not exist anymore. You can safely delete it. Cached functions can now contain Streamlit commands and will replay them. If you want to use widgets inside cached functions, set `experimental_allow_widgets=True`. See [Input widgets](#input-widgets) for an example.\n\nIf you have any questions or issues during the migration process, please contact us on the [forum](https://discuss.streamlit.io/), and we will be happy to assist you. ๐ŸŽˆ" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/fragments", - "content": "# Working with fragments\n\nReruns are a central part of every Streamlit app. When users interact with widgets, your script reruns from top to bottom, and your app's frontend is updated. Streamlit provides several features to help you develop your app within this execution model. Streamlit version 1.37.0 introduced fragments to allow rerunning a portion of your code instead of your full script. As your app grows larger and more complex, these fragment reruns help your app be efficient and performant. Fragments give you finer, easy-to-understand control over your app's execution flow.\n\nBefore you read about fragments, we recommend having a basic understanding of [caching](/develop/concepts/architecture/caching), [Session State](/concepts/architecture/session-state), and [forms](/develop/concepts/architecture/forms).\n\n## Use cases for fragments\n\nFragments are versatile and applicable to a wide variety of circumstances. Here are just a few, common scenarios where fragments are useful:\n\n- Your app has multiple visualizations and each one takes time to load, but you have a filter input that only updates one of them.\n- You have a dynamic form that doesn't need to update the rest of your app (until the form is complete).\n- You want to automatically update a single component or group of components to stream data.\n\n## Defining and calling a fragment\n\nStreamlit provides a decorator ([`st.fragment`](/develop/api-reference/execution-flow/st.fragment)) to turn any function into a fragment function. When you call a fragment function that contains a widget function, a user triggers a _fragment rerun_ instead of a full rerun when they interact with that fragment's widget. During a fragment rerun, only your fragment function is re-executed. Anything within the main body of your fragment is updated on the frontend, while the rest of your app remains the same. We'll describe fragments written across multiple containers later on.\n\nHere is a basic example of defining and calling a fragment function. Just like with caching, remember to call your function after defining it.\n\n```python\nimport streamlit as st\n\n@st.fragment\ndef fragment_function():\n if st.button(\"Hi!\"):\n st.write(\"Hi back!\")\n\nfragment_function()\n```\n\nIf you want the main body of your fragment to appear in the sidebar or another container, call your fragment function inside a context manager.\n\n```python\nwith st.sidebar:\n fragment_function()\n```\n\n### Fragment execution flow\n\nConsider the following code with the explanation and diagram below.\n\n```python\nimport streamlit as st\n\nst.title(\"My Awesome App\")\n\n@st.fragment()\ndef toggle_and_text():\n cols = st.columns(2)\n cols[0].toggle(\"Toggle\")\n cols[1].text_area(\"Enter text\")\n\n@st.fragment()\ndef filter_and_file():\n cols = st.columns(2)\n cols[0].checkbox(\"Filter\")\n cols[1].file_uploader(\"Upload image\")\n\ntoggle_and_text()\ncols = st.columns(2)\ncols[0].selectbox(\"Select\", [1,2,3], None)\ncols[1].button(\"Update\")\nfilter_and_file()\n```\n\nWhen a user interacts with an input widget inside a fragment, only the fragment reruns instead of the full script. When a user interacts with an input widget outside a fragment, the full script reruns as usual.\n\nIf you run the code above, the full script will run top to bottom on your app's initial load. If you flip the toggle button in your running app, the first fragment (`toggle_and_text()`) will rerun, redrawing the toggle and text area while leaving everything else unchanged. If you click the checkbox, the second fragment (`filter_and_file()`) will rerun and consequently redraw the checkbox and file uploader. Everything else remains unchanged. Finally, if you click the update button, the full script will rerun, and Streamlit will redraw everything.\n\n![Diagram of fragment execution flow](/images/concepts/fragment_diagram.png)\n\n## Fragment return values and interacting with the rest of your app\n\nStreamlit ignores fragment return values during fragment reruns, so defining return values for your fragment functions is not recommended. Instead, if your fragment needs to share data with the rest of your app, use Session State. Fragments are just functions in your script, so they can access Session State, imported modules, and other Streamlit elements like containers. If your fragment writes to any container created outside of itself, note the following difference in behavior:\n\n- Elements drawn in the main body of your fragment are cleared and redrawn in place during a fragment rerun. Repeated fragment reruns will not cause additional elements to appear.\n- Elements drawn to containers outside the main body of fragment will not be cleared with each fragment rerun. Instead, Streamlit will draw them additively and these elements will accumulate until the next full-script rerun.\n- A fragment can't draw widgets in containers outside of the main body of the fragment. Widgets can only go in the main body of a fragment.\n\nTo prevent elements from accumulating in outside containers, use [`st.empty`](/develop/api-reference/layout/st.empty) containers. For a related tutorial, see [Create a fragment across multiple containers](/develop/tutorials/execution-flow/create-a-multiple-container-fragment).\n\nIf you need to trigger a full-script rerun from inside a fragment, call [`st.rerun`](/develop/api-reference/execution-flow/st.rerun). For a related tutorial, see [Trigger a full-script rerun from inside a fragment](/develop/tutorials/execution-flow/trigger-a-full-script-rerun-from-a-fragment).\n\n## Automate fragment reruns\n\n`st.fragment` includes a convenient `run_every` parameter that causes the fragment to rerun automatically at the specified time interval. These reruns are in addition to any reruns (fragment or full-script) triggered by your user. The automatic fragment reruns will continue even if your user is not interacting with your app. This is a great way to show a live data stream or status on a running background job, efficiently updating your rendered data and _only_ your rendered data.\n\n```python\n@st.fragment(run_every=\"10s\")\ndef auto_function():\n\t\t# This will update every 10 seconds!\n\t\tdf = get_latest_updates()\n\t\tst.line_chart(df)\n\nauto_function()\n```\n\nFor a related tutorial, see [Start and stop a streaming fragment](/develop/tutorials/execution-flow/start-and-stop-fragment-auto-reruns).\n\n## Compare fragments to other Streamlit features\n\n### Fragments vs forms\n\nHere is a comparison between fragments and forms:\n\n- **Forms** allow users to interact with widgets without rerunning your app. Streamlit does not send user actions within a form to your app's Python backend until the form is submitted. Widgets within a form can not dynamically update other widgets (in or out of the form) in real-time.\n- **Fragments** run independently from the rest of your code. As your users interact with fragment widgets, their actions are immediately processed by your app's Python backend and your fragment code is rerun. Widgets within a fragment can dynamically update other widgets within the same fragment in real-time.\n\nA form batches user input without interaction between any widgets. A fragment immediately processes user input but limits the scope of the rerun.\n\n### Fragments vs callbacks\n\nHere is a comparison between fragments and callbacks:\n\n- **Callbacks** allow you to execute a function at the beginning of a script rerun. A callback is a _single prefix_ to your script rerun.\n- **Fragments** allow you to rerun a portion of your script. A fragment is a _repeatable postfix_ to your script, running each time a user interacts with a fragment widget, or automatically in sequence when `run_every` is set.\n\nWhen callbacks render elements to your page, they are rendered before the rest of your page elements. When fragments render elements to your page, they are updated with each fragment rerun (unless they are written to containers outside of the fragment, in which case they accumulate there).\n\n### Fragments vs custom components\n\nHere is a comparison between fragments and custom components:\n\n- **Components** are custom frontend code that can interact with the Python code, native elements, and widgets in your Streamlit app. Custom components extend whatโ€™s possible with Streamlit. They follow the normal Streamlit execution flow.\n- **Fragments** are parts of your app that can rerun independently of the full app. Fragments can be composed of multiple Streamlit elements, widgets, or any Python code.\n\nA fragment can include one or more custom components. A custom component could not easily include a fragment!\n\n### Fragments vs caching\n\nHere is a comparison between fragments and caching:\n\n- **Caching:** allows you to skip over a function and return a previously computed value. When you use caching, you execute everything except the cached function (if you've already run it before).\n- **Fragments:** allow you to freeze most of your app and just execute the fragment. When you use fragments, you execute only the fragment (when triggering a fragment rerun).\n\nCaching saves you from unnecessarily running a piece of your app while the rest runs. Fragments save you from running your full app when you only want to run one piece.\n\n## Limitations and unsupported behavior\n\n- Fragments can't detect a change in input values. It is best to use Session State for dynamic input and output for fragment functions.\n- Using caching and fragments on the same function is unsupported.\n- Fragments can't render widgets in externally-created containers; widgets can only be in the main body of a fragment." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/app-chrome", - "content": "# The app chrome\n\nYour Streamlit app has a few widgets in the top right to help you as you develop. These widgets also help your viewers as they use your app. We call this things โ€œthe app chromeโ€. The chrome includes a status area, toolbar, and app menu.\n\nYour app menu is configurable. By default, you can access developer options from the app menu when viewing an app locally or on Streamlit Community Cloud while logged into an account with administrative access. While viewing an app, click the icon in the upper-right corner to access the menu.\n\n![App menu](/images/app-menu/app-menu-developer.png)\n\n## Menu options\n\nThe menu is split into two sections. The upper section contains options available to all viewers and the lower section contains options for developers. Read more about [customizing this menu](#customize-the-menu) at the end of this page.\n\n### Rerun\n\nYou can manually trigger a rerun of your app by clicking \"**Rerun**\" from the app menu. This rerun will not reset your session. Your widget states and values stored in [`st.session_state`](/develop/concepts/architecture/session-state) will be preserved. As a shortcut, without opening the app menu, you can rerun your app by pressing \"**R**\" on your keyboard (if you aren't currently focused on an input element).\n\n### Settings\n\nWith the \"**Settings**\" option, you can control the appearance of your app while it is running. If viewing the app locally, you can set how your app responds to changes in your source code. See more about development flow in [Basic concepts](/get-started/fundamentals/main-concepts#development-flow). You can also force your app to appear in wide mode, even if not set within the script using [`st.set_page_config`](/develop/api-reference/configuration/st.set_page_config).\n\n#### Theme settings\n\nAfter clicking \"**Settings**\" from the app menu, you can choose between \"**Light**\", \"**Dark**\", or \"**Use system setting**\" for the app's base theme. Click \"**Edit active theme**\" to modify the theme, color-by-color.\n\n
\n \"Settings\"\n
\n\n
\n\n
\n \"Theme\"\n
\n\n### Print\n\nClick \"**Print**\" or use keyboard shortcuts (`โŒ˜+P`ย orย `Ctrl+P`) to open a print dialog. This option uses your browser's built-in print-to-pdf function. To modify the appearance of your print, you can do the following:\n\n- Expand or collapse the sidebar before printing to respectively include or exclude it from the print.\n- Resize the sidebar in your app by clicking and dragging its right border to achieve your desired width.\n- You may need to enable \"**Background graphics**\" in your print dialog if you are printing in dark mode.\n- You may need to disable wide mode in [Settings](#settings) or adjust the print scale to prevent elements from clipping off the page.\n\n### Record a screencast\n\nYou can easily make screen recordings right from your app! Screen recording is supported in the latest versions of Chrome, Edge, and Firefox. Ensure your browser is up-to-date for compatibility. Depending on your current settings, you may need to grant permission to your browser to record your screen or to use your microphone if recording a voiceover.\n\n1. While viewing your app, open the app menu from the upper-right corner.\n2. Click \"**Record a screencast**.\"\n3. If you want to record audio through your microphone, check \"**Also record audio**.\"\n4. Click \"**Start recording**.\" (You may be prompted by your OS to permit your browser to record your screen or use your microphone.)\n\n
\n \"Record\"\n
\n\n5. Select which tab, window, or monitor you want to record from the listed options. The interface will vary depending on your browser.\n\n
\n \"Record\"\n
\n\n6. Click \"**Share**.\"\n\n
\n \"Record\"\n
\n\n7. While recording, you will see a red circle on your app's tab and on the app menu icon. If you want to cancel the recording, click \"**Stop sharing**\" at the bottom of your app.\n\n
\n \"Record\"\n
\n\n8. When you are done recording, press \"**Esc**\" on your keyboard or click \"**Stop recording**\" from your app's menu.\n\n
\n \"Record\"\n
\n\n9. Follow your browser's instructions to save your recording. Your saved recording will be available where your browser saves downloads.\n\nThe whole process looks like this:\n\n
\n \"Record\"\n
\n\n### About\n\nYou can conveniently check what version of Streamlit is running from the \"**About**\" option. Developers also have the option to customize the message shown here using [`st.set_page_config`](/develop/api-reference/configuration/st.set_page_config).\n\n## Developer options\n\nBy default, developer options only show when viewing an app locally or when viewing a Community Cloud app while logged in with administrative permission. You can [customize the menu](#customize-the-menu) if you want to make these options available for all users.\n\n### Clear cache\n\nReset your app's cache by clicking \"**Clear cache**\" from the app's menu or by pressing \"**C**\" on your keyboard while not focused on an input element. This will remove all cached entries for [`@st.cache_data`](/develop/api-reference/caching-and-state/st.cache_data) and [`@st.cache_resource`](/develop/api-reference/caching-and-state/st.cache_resource).\n\n### Deploy this app\n\nIf you are running an app locally from within a git repo, you can deploy your app to Streamlit Community Cloud in a few easy clicks! Make sure your work has been pushed to your online GitHub repository before beginning. For the greatest convenience, make sure you have already created your [Community Cloud account](/deploy/streamlit-community-cloud/get-started/create-your-account) and are signed in.\n\n1. Click \"**Deploy**\" next to the app menu icon (more_vert).\n\n
\n \"Settings\"\n
\n\n2. Click \"**Deploy now**.\"\n\n
\n \"Settings\"\n
\n\n3. You will be taken to Community Cloud's \"Deploy an app\" page. Your app's repository, branch, and file name will be prefilled to match your current app! Learn more about [deploying an app](/deploy/streamlit-community-cloud/deploy-your-app) on Streamlit Community Cloud.\n\nThe whole process looks like this:\n\n
\n \"Settings\"\n
\n\n## Customize the menu\n\nUsing `client.toolbarMode` in your app's [configuration](/develop/concepts/configuration), you can make the app menu appear in the following ways:\n\n- `\"developer\"` — Show the developer options to all viewers.\n- `\"viewer\"` — Hide the developer options from all viewers.\n- `\"minimal\"` — Show only those options set externally. These options can be declared through [`st.set_page_config`](/develop/api-reference/configuration/st.set_page_config) or populated through Streamlit Community Cloud.\n- `\"auto\"` — This is the default and will show the developer options when accessed through localhost or through Streamlit Community Cloud when logged into an administrative account for the app. Otherwise, the developer options will not show." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/run-your-app", - "content": "# Run your Streamlit app\n\nWorking with Streamlit is simple. First you sprinkle a few Streamlit commands into a normal Python script, and then you run it. We list few ways to run your script, depending on your use case.\n\n## Use streamlit run\n\nOnce you've created your script, say `your_script.py`, the easiest way to run it is with `streamlit run`:\n\n```bash\nstreamlit run your_script.py\n```\n\nAs soon as you run the script as shown above, a local Streamlit server will spin up and your app will open in a new tab in your default web browser.\n\n### Pass arguments to your script\n\nWhen passing your script some custom arguments, they must be passed after two dashes. Otherwise the arguments get interpreted as arguments to Streamlit itself:\n\n```bash\nstreamlit run your_script.py [-- script args]\n```\n\n### Pass a URL to streamlit run\n\nYou can also pass a URL to `streamlit run`! This is great when your script is hosted remotely, such as a GitHub Gist. For example:\n\n```bash\nstreamlit run https://raw.githubusercontent.com/streamlit/demo-uber-nyc-pickups/master/streamlit_app.py\n```\n\n## Run Streamlit as a Python module\n\nAnother way of running Streamlit is to run it as a Python module. This is useful when configuring an IDE like PyCharm to work with Streamlit:\n\n```bash\n# Running\npython -m streamlit run your_script.py\n```\n\n```bash\n# is equivalent to:\nstreamlit run your_script.py\n```" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture", - "content": "# Working with Streamlit's execution model\n\n\n\n\n\n
Run your app
\n\nUnderstand how to start your Streamlit app.\n\n
\n\n\n\n
Streamlit's architecture
\n\nUnderstand Streamlit's client-server architecture and related considerations.\n\n
\n\n\n\n
The app chrome
\n\nEvery Streamlit app has a few widgets in the top right to help you as you develop your app and help your users as they view your app. This is called the app chrome.\n\n
\n\n\n\n
Caching
\n\nMake your app performant by caching results to avoid unecessary recomputation with each rerun.\n\n
\n\n\n\n
Session State
\n\nManage your app's statefulness with Session State.\n\n
\n\n\n\n
Forms
\n\nUse forms to isolate user input and prevent unnecessary app reruns.\n\n
\n\n\n\n
Widget behavior
\n\nUnderstand how widgets work in detail.\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/architecture/widget-behavior", - "content": "# Understanding widget behavior\n\nWidgets (like `st.button`, `st.selectbox`, and `st.text_input`) are at the heart of Streamlit apps. They are the interactive elements of Streamlit that pass information from your users into your Python code. Widgets are magical and often work how you want, but they can have surprising behavior in some situations. Understanding the different parts of a widget and the precise order in which events occur helps you achieve your desired results.\n\nThis guide covers advanced concepts about widgets. Generally, it begins with simpler concepts and increases in complexity. For most beginning users, these details won't be important to know right away. When you want to dynamically change widgets or preserve widget information between pages, these concepts will be important to understand. We recommend having a basic understanding of [Session State](/develop/api-reference/caching-and-state/st.session_state) before reading this guide.\n\n\n\n1. The actions of one user do not affect the widgets of any other user.\n2. A widget function call returns the widget's current value, which is a simple Python type. (e.g. `st.button` returns a boolean value.)\n3. Widgets return their default values on their first call before a user interacts with them.\n4. A widget's identity depends on the arguments passed to the widget function. Changing a widget's label, min or max value, default value, placeholder text, help text, or key will cause it to reset.\n5. If you don't call a widget function in a script run, Streamlit will delete the widget's information—_including its key-value pair in Session State_. If you call the same widget function later, Streamlit treats it as a new widget.\n\nThe last two points (widget identity and widget deletion) are the most relevant when dynamically changing widgets or working with multi-page applications. This is covered in detail later in this guide: [Statefulness of widgets](#statefulness-of-widgets) and [Widget life cycle](#widget-life-cycle).\n\n\n\n## Anatomy of a widget\n\nThere are four parts to keep in mind when using widgets:\n\n1. The frontend component as seen by the user.\n2. The backend value or value as seen through `st.session_state`.\n3. The key of the widget used to access its value via `st.session_state`.\n4. The return value given by the widget's function.\n\n### Widgets are session dependent\n\nWidget states are dependent on a particular session (browser connection). The actions of one user do not affect the widgets of any other user. Furthermore, if a user opens up multiple tabs to access an app, each tab will be a unique session. Changing a widget in one tab will not affect the same widget in another tab.\n\n### Widgets return simple Python data types\n\nThe value of a widget as seen through `st.session_state` and returned by the widget function are of simple Python types. For example, `st.button` returns a boolean value and will have the same boolean value saved in `st.session_state` if using a key. The first time a widget function is called (before a user interacts with it), it will return its default value. (e.g. `st.selectbox` returns the first option by default.) Default values are configurable for all widgets with a few special exceptions like `st.button` and `st.file_uploader`.\n\n### Keys help distinguish widgets and access their values\n\nWidget keys serve two purposes:\n\n1. Distinguishing two otherwise identical widgets.\n2. Creating a means to access and manipulate the widget's value through `st.session_state`.\n\nWhenever possible, Streamlit updates widgets incrementally on the frontend instead of rebuilding them with each rerun. This means Streamlit assigns an ID to each widget from the arguments passed to the widget function. A widget's ID is based on parameters such as label, min or max value, default value, placeholder text, help text, and key. The page where the widget appears also factors into a widget's ID. If you have two widgets of the same type with the same arguments on the same page, you will get a `DuplicateWidgetID` error. In this case, assign unique keys to the two widgets.\n\n#### Streamlit can't understand two identical widgets on the same page\n\n```python\n# This will cause a DuplicateWidgetID error.\nst.button(\"OK\")\nst.button(\"OK\")\n```\n\n#### Use keys to distinguish otherwise identical widgets\n\n```python\nst.button(\"OK\", key=\"privacy\")\nst.button(\"OK\", key=\"terms\")\n```\n\n## Order of operations\n\nWhen a user interacts with a widget, the order of logic is:\n\n1. Its value in `st.session_state` is updated.\n2. The callback function (if any) is executed.\n3. The page reruns with the widget function returning its new value.\n\nIf the callback function writes anything to the screen, that content will appear above the rest of the page. A callback function runs as a _prefix_ to the script rerunning. Consequently, that means anything written via a callback function will disappear as soon as the user performs their next action. Other widgets should generally not be created within a callback function.\n\n\n\nIf a callback function is passed any args or kwargs, those arguments will be established when the widget is rendered. In particular, if you want to use a widget's new value in its own callback function, you cannot pass that value to the callback function via the `args` parameter; you will have to assign a key to the widget and look up its new value using a call to `st.session_state` _within the callback function_.\n\n\n\n### Using callback functions with forms\n\nUsing a callback function with a form requires consideration of this order of operations.\n\n```python\nimport streamlit as st\n\nif \"attendance\" not in st.session_state:\n st.session_state.attendance = set()\n\n\ndef take_attendance():\n if st.session_state.name in st.session_state.attendance:\n st.info(f\"{st.session_state.name} has already been counted.\")\n else:\n st.session_state.attendance.add(st.session_state.name)\n\n\nwith st.form(key=\"my_form\"):\n st.text_input(\"Name\", key=\"name\")\n st.form_submit_button(\"I'm here!\", on_click=take_attendance)\n```\n\n\n\n## Statefulness of widgets\n\nAs long as the defining parameters of a widget remain the same and that widget is continuously rendered on the frontend, then it will be stateful and remember user input.\n\n### Changing parameters of a widget will reset it\n\nIf any of the defining parameters of a widget change, Streamlit will see it as a new widget and it will reset. The use of manually assigned keys and default values is particularly important in this case. _Note that callback functions, callback args and kwargs, label visibility, and disabling a widget do not affect a widget's identity._\n\nIn this example, we have a slider whose min and max values are changed. Try interacting with each slider to change its value then change the min or max setting to see what happens.\n\n```python\nimport streamlit as st\n\ncols = st.columns([2, 1, 2])\nminimum = cols[0].number_input(\"Minimum\", 1, 5)\nmaximum = cols[2].number_input(\"Maximum\", 6, 10, 10)\n\nst.slider(\"No default, no key\", minimum, maximum)\nst.slider(\"No default, with key\", minimum, maximum, key=\"a\")\nst.slider(\"With default, no key\", minimum, maximum, value=5)\nst.slider(\"With default, with key\", minimum, maximum, value=5, key=\"b\")\n```\n\n\n\n#### Updating a slider with no default value\n\nFor the first two sliders above, as soon as the min or max value is changed, the sliders reset to the min value. The changing of the min or max value makes them \"new\" widgets from Streamlit's perspective and so they are recreated from scratch when the app reruns with the changed parameters. Since no default value is defined, each widget will reset to its min value. This is the same with or without a key since it's seen as a new widget either way. There is a subtle point to understand about pre-existing keys connecting to widgets. This will be explained further down in [Widget life cycle](#widget-life-cycle).\n\n#### Updating a slider with a default value\n\nFor the last two sliders above, a change to the min or max value will result in the widgets being seen as \"new\" and thus recreated like before. Since a default value of 5 is defined, each widget will reset to 5 whenever the min or max is changed. This is again the same (with or without a key).\n\nA solution to [Retain statefulness when changing a widget's parameters](#retain-statefulness-when-changing-a-widgets-parameters) is provided further on.\n\n### Widgets do not persist when not continually rendered\n\nIf a widget's function is not called during a script run, then none of its parts will be retained, including its value in `st.session_state`. If a widget has a key and you navigate away from that widget, its key and associated value in `st.session_state` will be deleted. Even temporarily hiding a widget will cause it to reset when it reappears; Streamlit will treat it like a new widget. You can either interrupt the [Widget clean-up process](#widget-clean-up-process) (described at the end of this page) or save the value to another key.\n\n#### Save widget values in Session State to preserve them between pages\n\nIf you want to navigate away from a widget and return to it while keeping its value, use a separate key in `st.session_state` to save the information independently from the widget. In this example, a temporary key is used with a widget. The temporary key uses an underscore prefix. Hence, `\"_my_key\"` is used as the widget key, but the data is copied to `\"my_key\"` to preserve it between pages.\n\n```python\nimport streamlit as st\n\ndef store_value():\n # Copy the value to the permanent key\n st.session_state[\"my_key\"] = st.session_state[\"_my_key\"]\n\n# Copy the saved value to the temporary key\nst.session_state[\"_my_key\"] = st.session_state[\"my_key\"]\nst.number_input(\"Number of filters\", key=\"_my_key\", on_change=store_value)\n```\n\nIf this is functionalized to work with multiple widgets, it could look something like this:\n\n```python\nimport streamlit as st\n\ndef store_value(key):\n st.session_state[key] = st.session_state[\"_\"+key]\ndef load_value(key):\n st.session_state[\"_\"+key] = st.session_state[key]\n\nload_value(\"my_key\")\nst.number_input(\"Number of filters\", key=\"_my_key\", on_change=store_value, args=[\"my_key\"])\n```\n\n## Widget life cycle\n\nWhen a widget function is called, Streamlit will check if it already has a widget with the same parameters. Streamlit will reconnect if it thinks the widget already exists. Otherwise, it will make a new one.\n\nAs mentioned earlier, Streamlit determines a widget's ID based on parameters such as label, min or max value, default value, placeholder text, help text, and key. The page name also factors into a widget's ID. On the other hand, callback functions, callback args and kwargs, label visibility, and disabling a widget do not affect a widget's identity.\n\n### Calling a widget function when the widget doesn't already exist\n\nIf your script rerun calls a widget function with changed parameters or calls a widget function that wasn't used on the last script run:\n\n1. Streamlit will build the frontend and backend parts of the widget, using its default value.\n2. If the widget has been assigned a key, Streamlit will check if that key already exists in Session State. \n a. If it exists and is not currently associated with another widget, Streamlit will assign that key's value to the widget.\n b. Otherwise, it will assign the default value to the key in `st.session_state` (creating a new key-value pair or overwriting an existing one).\n3. If there are args or kwargs for a callback function, they are computed and saved at this point in time.\n4. The widget value is then returned by the function.\n\nStep 2 can be tricky. If you have a widget:\n\n```python\nst.number_input(\"Alpha\",key=\"A\")\n```\n\nand you change it on a page rerun to:\n\n```python\nst.number_input(\"Beta\",key=\"A\")\n```\n\nStreamlit will see that as a new widget because of the label change. The key `\"A\"` will be considered part of the widget labeled `\"Alpha\"` and will not be attached as-is to the new widget labeled `\"Beta\"`. Streamlit will destroy `st.session_state.A` and recreate it with the default value.\n\nIf a widget attaches to a pre-existing key when created and is also manually assigned a default value, you will get a warning if there is a disparity. If you want to control a widget's value through `st.session_state`, initialize the widget's value through `st.session_state` and avoid the default value argument to prevent conflict.\n\n### Calling a widget function when the widget already exists\n\nWhen rerunning a script without changing a widget's parameters:\n\n1. Streamlit will connect to the existing frontend and backend parts.\n2. If the widget has a key that was deleted from `st.session_state`, then Streamlit will recreate the key using the current frontend value. (e.g Deleting a key will not revert the widget to a default value.)\n3. It will return the current value of the widget.\n\n### Widget clean-up process\n\nWhen Streamlit gets to the end of a script run, it will delete the data for any widgets it has in memory that were not rendered on the screen. Most importantly, that means Streamlit will delete all key-value pairs in `st.session_state` associated with a widget not currently on screen.\n\n## Additional examples\n\nAs promised, let's address how to retain the statefulness of widgets when changing pages or modifying their parameters. There are two ways to do this.\n\n1. Use dummy keys to duplicate widget values in `st.session_state` and protect the data from being deleted along with the widget.\n2. Interrupt the widget clean-up process.\n\nThe first method was shown above in [Save widget values in Session State to preserve them between pages](#save-widget-values-in-session-state-to-preserve-them-between-pages)\n\n### Interrupting the widget clean-up process\n\nTo retain information for a widget with `key=\"my_key\"`, just add this to the top of every page:\n\n```python\nst.session_state.my_key = st.session_state.my_key\n```\n\nWhen you manually save data to a key in `st.session_state`, it will become detached from any widget as far as the clean-up process is concerned. If you navigate away from a widget with some key `\"my_key\"` and save data to `st.session_state.my_key` on the new page, you will interrupt the widget clean-up process and prevent the key-value pair from being deleted or overwritten if another widget with the same key exists.\n\n### Retain statefulness when changing a widget's parameters\n\nHere is a solution to our earlier example of changing a slider's min and max values. This solution interrupts the clean-up process as described above.\n\n```python\nimport streamlit as st\n\n# Set default value\nif \"a\" not in st.session_state:\n st.session_state.a = 5\n\ncols = st.columns(2)\nminimum = cols[0].number_input(\"Min\", 1, 5, key=\"min\")\nmaximum = cols[1].number_input(\"Max\", 6, 10, 10, key=\"max\")\n\n\ndef update_value():\n # Helper function to ensure consistency between widget parameters and value\n st.session_state.a = min(st.session_state.a, maximum)\n st.session_state.a = max(st.session_state.a, minimum)\n\n\n# Validate the slider value before rendering\nupdate_value()\nst.slider(\"A\", minimum, maximum, key=\"a\")\n```\n\n\n\nThe `update_value()` helper function is actually doing two things. On the surface, it's making sure there are no inconsistent changes to the parameters values as described. Importantly, it's also interrupting the widget clean-up process. When the min or max value of the widget changes, Streamlit sees it as a new widget on rerun. Without saving a value to `st.session_state.a`, the value would be thrown out and replaced by the \"new\" widget's default value." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/app-testing/examples", - "content": "# App testing example\n\n## Testing a login page\n\nLet's consider a login page. In this example, `secrets.toml` is not present. We'll manually declare dummy secrets directly in the tests. To avoid [timing attacks](https://en.wikipedia.org/wiki/Timing_attack), the login script uses `hmac` to compare a user's password to the secret value as a security best practice.\n\n### Project summary\n\n#### Login page behavior\n\nBefore diving into the app's code, let's think about what this page is supposed to do. Whether you use test-driven development or you write unit tests after your code, it's a good idea to think about the functionality that needs to be tested. The login page should behave as follows:\n\n- Before a user interacts with the app:\n - Their status is \"unverified.\"\n - A password prompt is displayed.\n- If a user types an incorrect password:\n - Their status is \"incorrect.\"\n - An error message is displayed.\n - The password attempt is cleared from the input.\n- If a user types a correct password:\n - Their status is \"verified.\"\n - A confirmation message is displayed.\n - A logout button is displayed (without a login prompt).\n- If a logged-in user clicks the **Log out** button:\n - Their status is \"unverified.\"\n - A password prompt is displayed.\n\n#### Login page project structure\n\n```none\nmyproject/\nโ”œโ”€โ”€ app.py\nโ””โ”€โ”€ tests/\n โ””โ”€โ”€ test_app.py\n```\n\n#### Login page Python file\n\nThe user's status mentioned in the page's specifications are encoded in `st.session_state.status`. This value is initialized at the beginning of the script as \"unverified\" and is updated through a callback when the password prompt receives a new entry.\n\n```python\n\"\"\"app.py\"\"\"\nimport streamlit as st\nimport hmac\n\nst.session_state.status = st.session_state.get(\"status\", \"unverified\")\nst.title(\"My login page\")\n\n\ndef check_password():\n if hmac.compare_digest(st.session_state.password, st.secrets.password):\n st.session_state.status = \"verified\"\n else:\n st.session_state.status = \"incorrect\"\n st.session_state.password = \"\"\n\ndef login_prompt():\n st.text_input(\"Enter password:\", key=\"password\", on_change=check_password)\n if st.session_state.status == \"incorrect\":\n st.warning(\"Incorrect password. Please try again.\")\n\ndef logout():\n st.session_state.status = \"unverified\"\n\ndef welcome():\n st.success(\"Login successful.\")\n st.button(\"Log out\", on_click=logout)\n\n\nif st.session_state.status != \"verified\":\n login_prompt()\n st.stop()\nwelcome()\n```\n\n#### Login page test file\n\nThese tests closely follow the app's specifications above. In each test, a dummy secret is set before running the app and proceeding with further simulations and checks.\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\ndef test_no_interaction():\n at = AppTest.from_file(\"app.py\")\n at.secrets[\"password\"] = \"streamlit\"\n at.run()\n assert at.session_state[\"status\"] == \"unverified\"\n assert len(at.text_input) == 1\n assert len(at.warning) == 0\n assert len(at.success) == 0\n assert len(at.button) == 0\n assert at.text_input[0].value == \"\"\n\ndef test_incorrect_password():\n at = AppTest.from_file(\"app.py\")\n at.secrets[\"password\"] = \"streamlit\"\n at.run()\n at.text_input[0].input(\"balloon\").run()\n assert at.session_state[\"status\"] == \"incorrect\"\n assert len(at.text_input) == 1\n assert len(at.warning) == 1\n assert len(at.success) == 0\n assert len(at.button) == 0\n assert at.text_input[0].value == \"\"\n assert \"Incorrect password\" in at.warning[0].value\n\ndef test_correct_password():\n at = AppTest.from_file(\"app.py\")\n at.secrets[\"password\"] = \"streamlit\"\n at.run()\n at.text_input[0].input(\"streamlit\").run()\n assert at.session_state[\"status\"] == \"verified\"\n assert len(at.text_input) == 0\n assert len(at.warning) == 0\n assert len(at.success) == 1\n assert len(at.button) == 1\n assert \"Login successful\" in at.success[0].value\n assert at.button[0].label == \"Log out\"\n\ndef test_log_out():\n at = AppTest.from_file(\"app.py\")\n at.secrets[\"password\"] = \"streamlit\"\n at.session_state[\"status\"] = \"verified\"\n at.run()\n at.button[0].click().run()\n assert at.session_state[\"status\"] == \"unverified\"\n assert len(at.text_input) == 1\n assert len(at.warning) == 0\n assert len(at.success) == 0\n assert len(at.button) == 0\n assert at.text_input[0].value == \"\"\n```\n\nSee how Session State was modified in the last test? Instead of fully simulating a user logging in, the test jumps straight to a logged-in state by setting `at.session_state[\"status\"] = \"verified\"`. After running the app, the test proceeds to simulate the user logging out.\n\n### Automating your tests\n\nIf `myproject/` was pushed to GitHub as a repository, you could add GitHub Actions test automation with [Streamlit App Action](https://github.com/marketplace/actions/streamlit-app-action). This is as simple as adding a workflow file at `myproject/.github/workflows/`:\n\n```yaml\n# .github/workflows/streamlit-app.yml\nname: Streamlit app\n\non:\n push:\n branches: [\"main\"]\n pull_request:\n branches: [\"main\"]\n\npermissions:\n contents: read\n\njobs:\n streamlit:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n - uses: streamlit/streamlit-app-action@v0.0.3\n with:\n app-path: app.py\n```" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/app-testing/cheat-sheet", - "content": "# App testing cheat sheet\n\n## Text elements\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\nat = AppTest.from_file(\"cheatsheet_app.py\")\n\n# Headers\nassert \"My app\" in at.title[0].value\nassert \"New topic\" in at.header[0].value\nassert \"Interesting sub-topic\" in at.subheader[0].value\nassert len(at.divider) == 2\n\n# Body / code\nassert \"Hello, world!\" in at.markdown[0].value\nassert \"import streamlit as st\" in at.code[0].value\nassert \"A cool diagram\" in at.caption[0].value\nassert \"Hello again, world!\" in at.text[0].value\nassert \"\\int a x^2 \\,dx\" in at.latex[0].value\n```\n\n## Input widgets\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\nat = AppTest.from_file(\"cheatsheet_app.py\")\n\n# button\nassert at.button[0].value == False\nat.button[0].click().run()\nassert at.button[0].value == True\n\n# checkbox\nassert at.checkbox[0].value == False\nat.checkbox[0].check().run() # uncheck() is also supported\nassert at.checkbox[0].value == True\n\n# color_picker\nassert at.color_picker[0].value == \"#FFFFFF\"\nat.color_picker[0].pick(\"#000000\").run()\n\n# date_input\nassert at.date_input[0].value == datetime.date(2019, 7, 6)\nat.date_input[0].set_value(datetime.date(2022, 12, 21)).run()\n\n# form_submit_button - shows up just like a button\nassert at.button[0].value == False\nat.button[0].click().run()\nassert at.button[0].value == True\n\n# multiselect\nassert at.multiselect[0].value == [\"foo\", \"bar\"]\nat.multiselect[0].select(\"baz\").unselect(\"foo\").run()\n\n# number_input\nassert at.number_input[0].value == 5\nat.number_input[0].increment().run()\n\n# radio\nassert at.radio[0].value == \"Bar\"\nassert at.radio[0].index == 3\nat.radio[0].set_value(\"Foo\").run()\n\n# selectbox\nassert at.selectbox[0].value == \"Bar\"\nassert at.selectbox[0].index == 3\nat.selectbox[0].set_value(\"Foo\").run()\n\n# select_slider\nassert at.select_slider[0].value == \"Feb\"\nat.select_slider[0].set_value(\"Mar\").run()\nat.select_slider[0].set_range(\"Apr\", \"Jun\").run()\n\n# slider\nassert at.slider[0].value == 2\nat.slider[0].set_value(3).run()\nat.slider[0].set_range(4, 6).run()\n\n# text_area\nassert at.text_area[0].value == \"Hello, world!\"\nat.text_area[0].set_value(\"Hello, yourself!\").run()\n\n# text_input\nassert at.text_input[0].value == \"Hello, world!\")\nat.text_input[0].set_value(\"Hello, yourself!\").run()\n\n# time_input\nassert at.time_input[0].value == datetime.time(8, 45)\nat.time_input[0].set_value(datetime.time(12, 30))\n\n# toggle\nassert at.toggle[0].value == False\nassert at.toggle[0].label == \"Debug mode\"\nat.toggle[0].set_value(True).run()\nassert at.toggle[0].value == True\n```\n\n## Data elements\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\nat = AppTest.from_file(\"cheatsheet_app.py\")\n\n# dataframe\nexpected_df = pd.DataFrame([1, 2, 3])\nassert at.dataframe[0].value.equals(expected_df)\n\n# metric\nassert at.metric[0].value == \"9500\"\nassert at.metric[0].delta == \"1000\"\n\n# json\nassert at.json[0].value == '[\"hi\", {\"foo\": \"bar\"}]'\n\n# table\ntable_df = pd.DataFrame([1, 2, 3])\nassert at.table[0].value.equals(table_df)\n```\n\n## Layouts and containers\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\nat = AppTest.from_file(\"cheatsheet_app.py\")\n\n# sidebar\nat.sidebar.text_input[0].set_value(\"Jane Doe\")\n\n# columns\nat.columns[1].markdown[0].value == \"Hello, world!\"\n\n# tabs\nat.tabs[2].markdown[0].value == \"Hello, yourself!\"\n```\n\n## Chat elements\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\nat = AppTest.from_file(\"cheatsheet_app.py\")\n\n# chat_input\nat.chat_input[0].set_value(\"Do you know any jokes?\").run()\n# Note: chat_input value clears after every re-run (like in a real app)\n\n# chat_message\nassert at.chat_message[0].markdown[0].value == \"Do you know any jokes?\"\nassert at.chat_message[0].avatar == \"user\"\n```\n\n## Status elements\n\n```python\nfrom streamlit.testing.v1 import AppTest\n\nat = AppTest.from_file(\"cheatsheet_app.py\")\n\n# exception\nassert len(at.exception) == 1\nassert \"TypeError\" in at.exception[0].value\n\n# Other in-line alerts: success, info, warning, error\nassert at.success[0].value == \"Great job!\"\nassert at.info[0].value == \"Please enter an API key to continue\"\nassert at.warning[0].value == \"Sorry, the passwords didn't match\"\nassert at.error[0].value == \"Something went wrong :(\"\n\n# toast\nassert at.toast[0].value == \"That was lit!\" and at.toast[0].icon == \"๐Ÿ”ฅ\"\n```\n\n## Limitations\n\nAs of Streamlit 1.28, the following Streamlit features are not natively supported by `AppTest`. However, workarounds are possible for many of them by inspecting the underlying proto directly using `AppTest.get()`. We plan to regularly add support for missing elements until all features are supported.\n\n- Chart elements (`st.bar_chart`, `st.line_chart`, etc)\n- Media elements (`st.image`, `st.video`, `st.audio`)\n- `st.file_uploader`\n- `st.data_editor`\n- `st.expander`\n- `st.status`\n- `st.camera_input`\n- `st.download_button`\n- `st.link_button`" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/app-testing/beyond-the-basics", - "content": "# Beyond the basics of app testing\n\nNow that you're comfortable with executing a basic test for a Streamlit app let's cover the mutable attributes of [`AppTest`](/develop/api-reference/app-testing/st.testing.v1.apptest):\n\n- `AppTest.secrets`\n- `AppTest.session_state`\n- `AppTest.query_params`\n\nYou can read and update values using dict-like syntax for all three attributes. For `.secrets` and `.query_params`, you can use key notation but not attribute notation. For example, the `.secrets` attribute for `AppTest` accepts `at.secrets[\"my_key\"]` but **_not_** `at.secrets.my_key`. This differs from how you can use the associated command in the main library. On the other hand, `.session_state` allows both key notation and attribute notation.\n\nFor these attributes, the typical pattern is to declare any values before executing the app's first run. Values can be inspected at any time in a test. There are a few extra considerations for secrets and Session State, which we'll cover now.\n\n## Using secrets with app testing\n\nBe careful not to include secrets directly in your tests. Consider this simple project with `pytest` executed in the project's root directory:\n\n```none\nmyproject/\nโ”œโ”€โ”€ .streamlit/\nโ”‚ โ”œโ”€โ”€ config.toml\nโ”‚ โ””โ”€โ”€ secrets.toml\nโ”œโ”€โ”€ app.py\nโ””โ”€โ”€ tests/\n โ””โ”€โ”€ test_app.py\n```\n\n```bash\ncd myproject\npytest tests/\n```\n\nIn the above scenario, your simulated app will have access to your `secrets.toml` file. However, since you don't want to commit your secrets to your repository, you may need to write tests where you securely pull your secrets into memory or use dummy secrets.\n\n### Example: declaring secrets in a test\n\nWithin a test, declare each secret after initializing your `AppTest` instance but before the first run. (A missing secret may result in an app that doesn't run!) For example, consider the following secrets file and corresponding test initialization to assign the same secrets manually:\n\nSecrets file:\n\n```toml\ndb_username = \"Jane\"\ndb_password = \"mypassword\"\n\n[my_other_secrets]\nthings_i_like = [\"Streamlit\", \"Python\"]\n```\n\nTesting file with equivalent secrets:\n\n```python\n# Initialize an AppTest instance.\nat = AppTest.from_file(\"app.py\")\n# Declare the secrets.\nat.secrets[\"db_username\"] = \"Jane\"\nat.secrets[\"db_password\"] = \"mypassword\"\nat.secrets[\"my_other_secrets.things_i_like\"] = [\"Streamlit\", \"Python\"]\n# Run the app.\nat.run()\n```\n\nGenerally, you want to avoid typing your secrets directly into your test. If you don't need your real secrets for your test, you can declare dummy secrets as in the example above. If your app uses secrets to connect to an external service like a database or API, consider mocking that service in your app tests. If you need to use the real secrets and actually connect, you should use an API to pass them securely and anonymously. If you are automating your tests with GitHub actions, check out their [Security guide](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).\n\n```python\nat.secrets[\"my_key\"] = \n```\n\n## Working with Session State in app testing\n\nThe `.session_state` attribute for `AppTest` lets you read and update Session State values using key notation (`at.session_state[\"my_key\"]`) and attribute notation (`at.session_state.my_key`). By manually declaring values in Session State, you can directly jump to a specific state instead of simulating many steps to get there. Additionally, the testing framework does not provide native support for multipage apps. An instance of `AppTest` can only test one page. You must manually declare Session State values to simulate a user carrying data from another page.\n\n### Example: testing a multipage app\n\nConsider a simple multipage app where the first page can modify a value in Session State. To test the second page, set Session State manually and run the simulated app within the test:\n\nProject structure:\n\n```none\nmyproject/\nโ”œโ”€โ”€ pages/\nโ”‚ โ””โ”€โ”€ second.py\nโ”œโ”€โ”€ first.py\nโ””โ”€โ”€ tests/\n โ””โ”€โ”€ test_second.py\n```\n\nFirst app page:\n\n```python\n\"\"\"first.py\"\"\"\nimport streamlit as st\n\nst.session_state.magic_word = st.session_state.get(\"magic_word\", \"Streamlit\")\n\nnew_word = st.text_input(\"Magic word:\")\n\nif st.button(\"Set the magic word\"):\n st.session_state.magic_word = new_word\n```\n\nSecond app page:\n\n```python\n\"\"\"second.py\"\"\"\nimport streamlit as st\n\nst.session_state.magic_word = st.session_state.get(\"magic_word\", \"Streamlit\")\n\nif st.session_state.magic_word == \"Balloons\":\n st.markdown(\":balloon:\")\n```\n\nTesting file:\n\n```python\n\"\"\"test_second.py\"\"\"\nfrom streamlit.testing.v1 import AppTest\n\ndef test_balloons():\n at = AppTest.from_file(\"pages/second.py\")\n at.session_state[\"magic_word\"] = \"Balloons\"\n at.run()\n assert at.markdown[0].value == \":balloon:\"\n```\n\nBy setting the value `at.session_state[\"magic_word\"] = \"Balloons\"` within the test, you can simulate a user navigating to `second.py` after entering and saving \"Balloons\" on `first.py`." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/app-testing/automate-tests", - "content": "# Automate your tests with CI\n\nOne of the key benefits of app testing is that tests can be automated using Continuous Integration (CI). By running tests automatically during development, you can validate that changes to your app don't break existing functionality. You can verify app code as you commit, catch bugs early, and prevent accidental breaks before deployment.\n\nThere are many popular CI tools, including GitHub Actions, Jenkins, GitLab CI, Azure DevOps, and Circle CI. Streamlit app testing will integrate easily with any of them similar to any other Python tests.\n\n## GitHub Actions\n\nSince many Streamlit apps (and all Community Cloud apps) are built in GitHub, this page uses examples from [GitHub Actions](https://docs.github.com/en/actions). For more information about GitHub Actions, see:\n\n- [Quickstart for GitHub Actions](https://docs.github.com/en/actions/quickstart)\n- [GitHub Actions: About continuous integration](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration)\n- [GitHub Actions: Build & test Python](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python)\n\n## Streamlit App Action\n\n[Streamlit App Action](https://github.com/marketplace/actions/streamlit-app-action) provides an easy way to add automated testing to your app repository in GitHub. It also includes basic smoke testing for each page of your app without you writing any test code.\n\nTo install Streamlit App Action, add a workflow `.yml` file to your repository's `.github/workflows/` folder. For example:\n\n```yaml\n# .github/workflows/streamlit-app.yml\nname: Streamlit app\n\non:\n push:\n branches: [\"main\"]\n pull_request:\n branches: [\"main\"]\n\npermissions:\n contents: read\n\njobs:\n streamlit:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n - uses: streamlit/streamlit-app-action@v0.0.3\n with:\n app-path: streamlit_app.py\n```\n\nLet's take a look in more detail at what this action workflow is doing.\n\n### Triggering the workflow\n\n```yaml\non:\n push:\n branches: [\"main\"]\n pull_request:\n branches: [\"main\"]\n```\n\nThis workflow will be triggered and execute tests on pull requests targeting the `main` branch, as well as any new commits pushed to the `main` branch. Note that it will also execute the tests on subsequent commits to any open pull requests. See [GitHub Actions: Triggering a workflow](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow) for more information and examples.\n\n### Setting up the test environment\n\n```yaml\njobs:\n streamlit:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n```\n\nThe workflow has a `streamlit` job that executes a series of steps. The job runs on a Docker container with the `ubuntu-latest` image.\n\n- `actions/checkout@v4` checks out the current repository code from GitHub and copies the code to the job environment.\n- `actions/setup-python@v5` installs Python version 3.11.\n\n### Running the app tests\n\n```yaml\n- uses: streamlit/streamlit-app-action@v0.0.3\n with:\n app-path: streamlit_app.py\n```\n\nStreamlit App Action does the following:\n\n- Install `pytest` and install any dependencies specified in `requirements.txt`.\n- Run the built-in app smoke tests.\n- Run any other Python tests found in the repository.\n\n\n\nIf your app doesn't include `requirements.txt` in the repository root directory, you will need to add a step to install dependencies with your chosen package manager before running Streamlit App Action.\n\n\n\nThe built-in smoke tests have the following behavior:\n\n- Run the app specified at `app-path` as an AppTest.\n- Validate that it completes successfully and does not result in an uncaught exception.\n- Do the same for any additional `pages/` of the app relative to `app-path`.\n\nIf you want to run Streamlit App Action without the smoke tests, you can set `skip-smoke: true`.\n\n### Linting your app code\n\nLinting is the automated checking of source code for programmatic and stylistic errors. This is done by using a lint tool (otherwise known as a linter). Linting is important to reduce errors and improve the overall quality of your code, especially for repositories with multiple developers or public repositories.\n\nYou can add automated linting with [Ruff](https://docs.astral.sh/ruff/) by passing `ruff: true` to Streamlit App Action.\n\n```yaml\n- uses: streamlit/streamlit-app-action@v0.0.3\n with:\n app-path: streamlit_app.py\n ruff: true\n```\n\n\n\nYou may want to add a pre-commit hook like [ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit) in your local development environment to fix linting errors before they get to CI.\n\n\n\n### Viewing results\n\nIf tests fail, the CI workflow will fail and you will see the results in GitHub. Console logs are available by clicking into the workflow run [as described here](https://docs.github.com/en/actions/using-workflows/about-workflows#viewing-the-activity-for-a-workflow-run).\n\n![](/images/test-results-logs.png)\n\nFor higher-level test results, you can use [pytest-results-action](https://github.com/marketplace/actions/pytest-results-actions). You can combine this with Streamlit App Action as follows:\n\n```yaml\n# ... setup as above ...\n- uses: streamlit/streamlit-app-action@v0.0.3\n with:\n app-path: streamlit_app.py\n # Add pytest-args to output junit xml\n pytest-args: -v --junit-xml=test-results.xml\n- if: always()\n uses: pmeier/pytest-results-action@v0.6.0\n with:\n path: test-results.xml\n summary: true\n display-options: fEX\n```\n\n![](/images/test-results-summary.png)\n\n## Writing your own actions\n\nThe above is just provided as an example. Streamlit App Action is a quick way to get started. Once you learn the basics of your CI tool of choice, it's easy to build and customize your own automated workflows. This is a great way to improve your overall productivity as a developer and the quality of your apps.\n\n## Working example\n\nAs a final working example example, take a look at our [`streamlit/llm-examples` Actions](https://github.com/streamlit/llm-examples/actions), defined in [this workflow file](https://github.com/streamlit/llm-examples/blob/main/.github/workflows/app-testing.yml)." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/app-testing/get-started", - "content": "# Get started with app testing\n\nThis guide will cover a simple example of how tests are structured within a project and how to execute them with `pytest`. After seeing the big picture, keep reading to learn about the [Fundamentals of app testing](#fundamentals-of-app-testing):\n\n- Initializing and running a simulated app\n- Retrieving elements\n- Manipulating widgets\n- Inspecting the results\n\nStreamlit's app testing framework is not tied to any particular testing tool, but we'll use `pytest` for our examples since it is one of the most common Python test frameworks. To try out the examples in this guide, be sure to install `pytest` into your Streamlit development environment before you begin:\n\n```bash\npip install pytest\n```\n\n## A simple testing example with `pytest`\n\nThis section explains how a simple test is structured and executed with `pytest`. For a comprehensive introduction to `pytest`, check out Real Python's guide to [Effective Python testing with pytest](https://realpython.com/pytest-python-testing/).\n\n### How `pytest` is structured\n\n`pytest` uses a naming convention for files and functions to execute tests conveniently. Name your test scripts of the form `test_.py` or `_test.py`. For example, you can use `test_myapp.py` or `myapp_test.py`. Within your test scripts, each test is written as a function. Each function is named to begin or end with `test`. We will prefix all our test scripts and test functions with `test_` for our examples in this guide.\n\nYou can write as many tests (functions) within a single test script as you want. When calling `pytest` in a directory, all `test_.py` files within it will be used for testing. This includes files within subdirectories. Each `test_` function within those files will be executed as a test. You can place test files anywhere in your project directory, but it is common to collect tests into a designated `tests/` directory. For other ways to structure and execute tests, check out [How to invoke pytest](https://docs.pytest.org/how-to/usage.html) in the `pytest` docs.\n\n### Example project with app testing\n\nConsider the following project:\n\n```none\nmyproject/\nโ”œโ”€โ”€ app.py\nโ””โ”€โ”€ tests/\n โ””โ”€โ”€ test_app.py\n```\n\nMain app file:\n\n```python\n\"\"\"app.py\"\"\"\nimport streamlit as st\n\n# Initialize st.session_state.beans\nst.session_state.beans = st.session_state.get(\"beans\", 0)\n\nst.title(\"Bean counter :paw_prints:\")\n\naddend = st.number_input(\"Beans to add\", 0, 10)\nif st.button(\"Add\"):\n st.session_state.beans += addend\nst.markdown(f\"Beans counted: {st.session_state.beans}\")\n```\n\nTesting file:\n\n```python\n\"\"\"test_app.py\"\"\"\nfrom streamlit.testing.v1 import AppTest\n\ndef test_increment_and_add():\n \"\"\"A user increments the number input, then clicks Add\"\"\"\n at = AppTest.from_file(\"app.py\").run()\n at.number_input[0].increment().run()\n at.button[0].click().run()\n assert at.markdown[0].value == \"Beans counted: 1\"\n```\n\nLet's take a quick look at what's in this app and test before we run it. The main app file (`app.py`) contains four elements when rendered: `st.title`, `st.number_input`, `st.button`, and `st.markdown`. The test script (`test_app.py`) includes a single test (the function named `test_increment_and_add`). We'll cover test syntax in more detail in the latter half of this guide, but here's a brief explanation of what this test does:\n\n1. Initialize the simulated app and execute the first script run.\n ```python\n at = AppTest.from_file(\"app.py\").run()\n ```\n2. Simulate a user clicking the plus icon (add) to increment the number input (and the resulting script rerun).\n ```python\n at.number_input[0].increment().run()\n ```\n3. Simulate a user clicking the \"**Add**\" button (and the resulting script rerun).\n ```python\n at.button[0].click().run()\n ```\n4. Check if the correct message is displayed at the end.\n ```python\n assert at.markdown[0].value == \"Beans counted: 1\"\n ```\n\nAssertions are the heart of tests. When the assertion is true, the test passes. When the assertion is false, the test fails. A test can have multiple assertions, but keeping tests tightly focused is good practice. When tests focus on a single behavior, it is easier to understand and respond to failure.\n\n### Try out a simple test with `pytest`\n\n1. Copy the files above into a new \"myproject\" directory.\n2. Open a terminal and change directory to your project.\n ```bash\n cd myproject\n ```\n3. Execute `pytest`:\n ```bash\n pytest\n ```\n\nThe test should execute successfully. Your terminal should show something like this:\n\n![A successfully completed test using pytest](/images/app-testing-pytest-intro.png)\n\nBy executing `pytest` at the root of your project directory, all Python files with the test prefix (`test_.py`) will be scanned for test functions. Within each test file, each function with the test prefix will be executed as a test. `pytest` then counts successes and itemizes failures. You can also direct `pytest` to only scan your testing directory. For example, from the root of your project directory, execute:\n\n```bash\npytest tests/\n```\n\n### Handling file paths and imports with `pytest`\n\nImports and paths within a test script should be relative to the directory where `pytest` is called. That is why the test function uses the path `app.py` instead of `../app.py` even though the app file is one directory up from the test script. You'll usually call `pytest` from the directory containing your main app file. This is typically the root of your project directory.\n\nAdditionally, if `.streamlit/` is present in the directory where you call `pytest`, any `config.toml` and `secrets.toml` within it will be accessible to your simulated app. For example, your simulated app will have access to the `config.toml` and `secrets.toml` files in this common setup:\n\nProject structure:\n\n```none\nmyproject/\nโ”œโ”€โ”€ .streamlit/\nโ”‚ โ”œโ”€โ”€ config.toml\nโ”‚ โ””โ”€โ”€ secrets.toml\nโ”œโ”€โ”€ app.py\nโ””โ”€โ”€ tests/\n โ””โ”€โ”€ test_app.py\n```\n\nInitialization within `test_app.py`:\n\n```python\n# Path to app file is relative to myproject/\nat = AppTest.from_file(\"app.py\").run()\n```\n\nCommand to execute tests:\n\n```bash\ncd myproject\npytest tests/\n```\n\n## Fundamentals of app testing\n\nNow that you understand the basics of `pytest` let's dive into using Streamlit's app testing framework. Every test begins with initializing and running your simulated app. Additional commands are used to retrieve, manipulate, and inspect elements.\n\nOn the next page, we'll go [Beyond the basics](/develop/concepts/app-testing/beyond-the-basics) and cover more advanced scenarios like working with secrets, Session State, or multipage apps.\n\n### How to initialize and run a simulated app\n\nTo test a Streamlit app, you must first initialize an instance of [`AppTest`](/develop/api-reference/app-testing/st.testing.v1.apptest) with the code for one page of your app. There are three methods for initializing a simulated app. These are provided as class methods to `AppTest`. We will focus on `AppTest.from_file()` which allows you to provide a path to a page of your app. This is the most common scenario for building automated tests during app development. `AppTest.from_string()` and `AppTest.from_function()` may be helpful for some simple or experimental scenarios.\n\nLet's continue with the [example from above](#example-project-with-app-testing).\n\nRecall the testing file:\n\n```python\n\"\"\"test_app.py\"\"\"\nfrom streamlit.testing.v1 import AppTest\n\ndef test_increment_and_add():\n \"\"\"A user increments the number input, then clicks Add\"\"\"\n at = AppTest.from_file(\"app.py\").run()\n at.number_input[0].increment().run()\n at.button[0].click().run()\n assert at.markdown[0].value == \"Beans counted: 1\"\n```\n\nLook at the first line in the test function:\n\n```python\nat = AppTest.from_file(\"app.py\").run()\n```\n\nThis is doing two things and is equivalent to:\n\n```python\n# Initialize the app.\nat = AppTest.from_file(\"app.py\")\n# Run the app.\nat.run()\n```\n\n`AppTest.from_file()` returns an instance of `AppTest`, initialized with the contents of `app.py`. The `.run()` method is used to run the app for the first time. Looking at the test, notice that the `.run()` method manually executes each script run. A test must explicitly run the app each time. This applies to the app's first run and any rerun resulting from simulated user input.\n\n### How to retrieve elements\n\nThe attributes of the `AppTest` class return sequences of elements. The elements are sorted according to display order in the rendered app. Specific elements can be retrieved by index. Additionally, widgets with keys can be retrieved by key.\n\n#### Retrieve elements by index\n\nEach attribute of `AppTest` returns a sequence of the associated element type. Specific elements can be retrieved by index. In the above example, `at.number_input` returns a sequence of all `st.number_input` elements in the app. Thus, `at.number_input[0]` is the first such element in the app. Similarly, `at.markdown` returns a collection of all `st.markdown` elements where `at.markdown[0]` is the first such element.\n\nCheck out the current list of supported elements in the \"Attributes\" section of the [`AppTest`](/develop/api-reference/app-testing/st.testing.v1.apptest) class or the [App testing cheat sheet](/develop/concepts/app-testing/cheat-sheet). You can also use the `.get()` method and pass the attribute's name. `at.get(\"number_input\")` and `at.get(\"markdown\")` are equivalent to `at.number_input` and `at.markdown`, respectively.\n\nThe returned sequence of elements is ordered by appearance on the page. If containers are used to insert elements in a different order, these sequences may not match the order within your code. Consider the following example where containers are used to switch the order of two buttons on the page:\n\n```python\nimport streamlit as st\n\nfirst = st.container()\nsecond = st.container()\n\nsecond.button(\"A\")\nfirst.button(\"B\")\n```\n\nIf the above app was tested, the first button (`at.button[0]`) would be labeled \"B\" and the second button (`at.button[1]`) would be labeled \"A.\" As true assertions, these would be:\n\n```python\nassert at.button[0].label == \"B\"\nassert at.button[1].label == \"A\"\n```\n\n#### Retrieve widgets by key\n\nYou can retrieve keyed widgets by their keys instead of their order on the page. The key of the widget is passed as either an arg or kwarg. For example, look at this app and the following (true) assertions:\n\n```python\nimport streamlit as st\n\nst.button(\"Next\", key=\"submit\")\nst.button(\"Back\", key=\"cancel\")\n```\n\n```python\nassert at.button(key=\"submit\").label == \"Next\"\nassert at.button(\"cancel\").label == \"Back\"\n```\n\n#### Retrieve containers\n\nYou can also narrow down your sequences of elements by retrieving specific containers. Each retrieved container has the same attributes as `AppTest`. For example, `at.sidebar.checkbox` returns a sequence of all checkboxes in the sidebar. `at.main.selectbox` returns the sequence of all selectboxes in the main body of the app (not in the sidebar).\n\nFor `AppTest.columns` and `AppTest.tabs`, a sequence of containers is returned. So `at.columns[0].button` would be the sequence of all buttons in the first column appearing in the app.\n\n### How to manipulate widgets\n\nAll widgets have a universal `.set_value()` method. Additionally, many widgets have specific methods for manipulating their value. The names of [Testing element classes](/develop/api-reference/app-testing/testing-element-classes) closely match the names of the `AppTest` attributes. For example, look at the return type of [`AppTest.button`](/develop/api-reference/app-testing/st.testing.v1.apptest#apptestbutton) to see the corresponding class of [`Button`](/develop/api-reference/app-testing/testing-element-classes#sttestingv1element_treebutton). Aside from setting the value of a button with `.set_value()`, you can also use `.click()`. Check out each testing element class for its specific methods.\n\n### How to inspect elements\n\nAll elements, including widgets, have a universal `.value` property. This returns the contents of the element. For widgets, this is the same as the return value or value in Session State. For non-input elements, this will be the value of the primary contents argument. For example, `.value` returns the value of `body` for `st.markdown` or `st.error`. It returns the value of `data` for `st.dataframe` or `st.table`.\n\nAdditionally, you can check many other details for widgets like labels or disabled status. Many parameters are available for inspection, but not all. Use linting software to see what is currently supported. Here's an example:\n\n```python\nimport streamlit as st\n\nst.selectbox(\"A\", [1,2,3], None, help=\"Pick a number\", placeholder=\"Pick me\")\n```\n\n```python\nassert at.selectbox[0].value == None\nassert at.selectbox[0].label == \"A\"\nassert at.selectbox[0].options == [\"1\",\"2\",\"3\"]\nassert at.selectbox[0].index == None\nassert at.selectbox[0].help == \"Pick a number\"\nassert at.selectbox[0].placeholder == \"Pick me\"\nassert at.selectbox[0].disabled == False\n```\n\n\n\nNote that the `options` for `st.selectbox` were declared as integers but asserted as strings. As noted in the documentation for [`st.selectbox`](/develop/api-reference/widgets/st.selectbox), options are cast internally to strings. If you ever find yourself getting unexpected results, check the documentation carefully for any notes about recasting types internally.\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/app-testing", - "content": "# Streamlit's native app testing framework\n\nStreamlit app testing enables developers to build and run automated tests. Bring your favorite test automation software and enjoy simple syntax to simulate user input and inspect rendered output.\n\nThe provided class, AppTest, simulates a running app and provides methods to set up, manipulate, and inspect the app contents via API instead of a browser UI. AppTest provides similar functionality to browser automation tools like Selenium or Playwright, but with less overhead to write and execute tests. Use our testing framework with a tool like [pytest](https://docs.pytest.org/) to execute or automate your tests. A typical pattern is to build a suite of tests for an app to ensure consistent functionality as the app evolves. The tests run locally and/or in a CI environment like GitHub Actions.\n\n\n introduces you to the app testing framework and how to execute tests using pytest. Learn how to initialize and run simulated apps, including how to retrieve, manipulate, and inspect app elements.\n explains how to work with secrets and Session State within app tests, including how to test multipage apps.\n with Continuous Integration (CI) to validate app changes over time.\n puts together the concepts explained above. Check out an app with multiple tests in place.\n is a compact reference summarizing the available syntax.\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design/timezone-handling", - "content": "# Working with timezones\n\nIn general, working with timezones can be tricky. Your Streamlit app users are not necessarily in the same timezone as the server running your app. It is especially true of public apps, where anyone in the world (in any timezone) can access your app. As such, it is crucial to understand how Streamlit handles timezones, so you can avoid unexpected behavior when displaying `datetime` information.\n\n## How Streamlit handles timezones\n\nStreamlit always shows `datetime` information on the frontend with the same information as its corresponding `datetime` instance in the backend. I.e., date or time information does not automatically adjust to the users' timezone. We distinguish between the following two cases:\n\n### **`datetime` instance without a timezone (naive)**\n\nWhen you provide a `datetime` instance _without specifying a timezone_, the frontend shows the `datetime` instance without timezone information. For example (this also applies to other widgets like [`st.dataframe`](/develop/api-reference/data/st.dataframe)):\n\n```python\nimport streamlit as st\nfrom datetime import datetime\n\nst.write(datetime(2020, 1, 10, 10, 30))\n# Outputs: 2020-01-10 10:30:00\n```\n\nUsers of the above app always see the output as `2020-01-10 10:30:00`.\n\n### **`datetime` instance with a timezone**\n\nWhen you provide a `datetime` instance _and specify a timezone_, the frontend shows the `datetime` instance in that same timezone. For example (this also applies to other widgets like [`st.dataframe`](/develop/api-reference/data/st.dataframe)):\n\n```python\nimport streamlit as st\nfrom datetime import datetime\nimport pytz\n\nst.write(datetime(2020, 1, 10, 10, 30, tzinfo=pytz.timezone(\"EST\")))\n# Outputs: 2020-01-10 10:30:00-05:00\n```\n\nUsers of the above app always see the output as `2020-01-10 10:30:00-05:00`.\n\nIn both cases, neither the date nor time information automatically adjusts to the users' timezone on the frontend. What users see is identical to the corresponding `datetime` instance in the backend. It is currently not possible to automatically adjust the date or time information to the timezone of the users viewing the app.\n\n\n\nThe legacy version of the `st.dataframe` has issues with timezones. We do not plan to roll out additional fixes or enhancements for the legacy dataframe. If you need stable timezone support, please consider switching to the arrow serialization by changing the [config setting](/develop/concepts/configuration), _config.dataFrameSerialization = \"arrow\"_.\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design/multithreading", - "content": "# Multithreading in Streamlit\n\nMultithreading is a type of concurrency, which improves the efficiency of computer programs. It's a way for processors to multitask. Streamlit uses threads within its architecture, which can make it difficult for app developers to include their own multithreaded processes. Streamlit does not officially support multithreading in app code, but this guide provides information on how it can be accomplished.\n\n## Prerequisites\n\n- You should have a basic understanding of Streamlit's [architecture](/develop/concepts/architecture/architecture).\n\n## When to use multithreading\n\nMultithreading is just one type of concurrency. Multiprocessing and coroutines are other forms of concurrency. You need to understand how your code is bottlenecked to choose the correct kind of concurrency.\n\nMultiprocessing is inherently parallel, meaning that resources are split and multiple tasks are performed simultaneously. Therefore, multiprocessing is helpful with compute-bound operations. In contrast, multithreading and coroutines are not inherently parallel and instead allow resource switching. This makes them good choices when your code is stuck _waiting_ for something, like an IO operation. AsyncIO uses coroutines and may be preferable with very slow IO operations. Threading may be preferable with faster IO operations. For a helpful guide to using AsyncIO with Streamlit, see this [Medium article by Sehmi-Conscious Thoughts](https://sehmi-conscious.medium.com/got-that-asyncio-feeling-f1a7c37cab8b).\n\nDon't forget that Streamlit has [fragments](/develop/concepts/architecture/fragments) and [caching](/develop/concepts/architecture/caching), too! Use caching to avoid unnecessarily repeating computations or IO operations. Use fragments to isolate a bit of code you want to update separately from the rest of the app. You can set fragments to rerun at a specified interval, so they can be used to stream updates to a chart or table.\n\n## Threads created by Streamlit\n\nStreamlit creates two types of threads in Python:\n\n- The **server thread** runs the Tornado web (HTTP + WebSocket) server.\n- A **script thread** runs page code — one thread for each script run in a session.\n\nWhen a user connects to your app, this creates a new session and runs a script thread to initialize the app for that user. As the script thread runs, it renders elements in the user's browser tab and reports state back to the server. When the user interacts with the app, another script thread runs, re-rendering the elements in the browser tab and updating state on the server.\n\nThis is a simplifed illustration to show how Streamlit works:\n\n![Each user session uses script threads to communicate between the user's front end and the Streamlit server.](/images/concepts/Streamlit-threading.svg)\n\n## `streamlit.errors.NoSessionContext`\n\nMany Streamlit commands, including `st.session_state`, expect to be called from a script thread. When Streamlit is running as expected, such commands use the `ScriptRunContext` attached to the script thread to ensure they work within the intended session and update the correct user's view. When those Streamlit commands can't find any `ScriptRunContext`, they raise a `streamlit.errors.NoSessionContext` exception. Depending on your logger settings, you may also see a console message identifying a thread by name and warning, \"missing ScriptRunContext!\"\n\n## Creating custom threads\n\nWhen you work with IO-heavy operations like remote query or data loading, you may need to mitigate delays. A general programming strategy is to create threads and let them work concurrently. However, if you do this in a Streamlit app, these custom threads may have difficulty interacting with your Streamlit server.\n\nThis section introduces two patterns to let you create custom threads in your Streamlit app. These are only patterns to provide a starting point rather than complete solutions.\n\n### Option 1: Do not use Streamlit commands within a custom thread\n\nIf you don't call Streamlit commands from a custom thread, you can avoid the problem entirely. Luckily Python threading provides ways to start a thread and collect its result from another thread.\n\nIn the following example, five custom threads are created from the script thread. After the threads are finished running, their results are displayed in the app.\n\n```python\nimport streamlit as st\nimport time\nfrom threading import Thread\n\n\nclass WorkerThread(Thread):\n def __init__(self, delay):\n super().__init__()\n self.delay = delay\n self.return_value = None\n\n def run(self):\n start_time = time.time()\n time.sleep(self.delay)\n end_time = time.time()\n self.return_value = f\"start: {start_time}, end: {end_time}\"\n\n\ndelays = [5, 4, 3, 2, 1]\nthreads = [WorkerThread(delay) for delay in delays]\nfor thread in threads:\n thread.start()\nfor thread in threads:\n thread.join()\nfor i, thread in enumerate(threads):\n st.header(f\"Thread {i}\")\n st.write(thread.return_value)\n\nst.button(\"Rerun\")\n```\n\n\n\nIf you want to display results in your app as various custom threads finish running, use containers. In the following example, five custom threads are created similarly to the previous example. However, five containers are initialized before running the custom threads and a `while` loop is used to display results as they become available. Since the Streamlit `write` command is called outside of the custom threads, this does not raise an exception.\n\n```python\nimport streamlit as st\nimport time\nfrom threading import Thread\n\n\nclass WorkerThread(Thread):\n def __init__(self, delay):\n super().__init__()\n self.delay = delay\n self.return_value = None\n\n def run(self):\n start_time = time.time()\n time.sleep(self.delay)\n end_time = time.time()\n self.return_value = f\"start: {start_time}, end: {end_time}\"\n\n\ndelays = [5, 4, 3, 2, 1]\nresult_containers = []\nfor i, delay in enumerate(delays):\n st.header(f\"Thread {i}\")\n result_containers.append(st.container())\n\nthreads = [WorkerThread(delay) for delay in delays]\nfor thread in threads:\n thread.start()\nthread_lives = [True] * len(threads)\n\nwhile any(thread_lives):\n for i, thread in enumerate(threads):\n if thread_lives[i] and not thread.is_alive():\n result_containers[i].write(thread.return_value)\n thread_lives[i] = False\n time.sleep(0.5)\n\nfor thread in threads:\n thread.join()\n\nst.button(\"Rerun\")\n```\n\n\n\n### Option 2: Expose `ScriptRunContext` to the thread\n\nIf you want to call Streamlit commands from within your custom threads, you must attach the correct `ScriptRunContext` to the thread.\n\n\n\n- This is not officially supported and may change in a future version of Streamlit.\n- This may not work with all Streamlit commands.\n- Ensure custom threads do not outlive the script thread owning the `ScriptRunContext`. Leaking of `ScriptRunContext` may cause security vulnerabilities, fatal errors, or unexpected behavior.\n\n\n\nIn the following example, a custom thread with `ScriptRunContext` attached can call `st.write` without a warning.\n\n```python\nimport streamlit as st\nfrom streamlit.runtime.scriptrunner import add_script_run_ctx, get_script_run_ctx\nimport time\nfrom threading import Thread\n\n\nclass WorkerThread(Thread):\n def __init__(self, delay, target):\n super().__init__()\n self.delay = delay\n self.target = target\n\n def run(self):\n # runs in custom thread, but can call Streamlit APIs\n start_time = time.time()\n time.sleep(self.delay)\n end_time = time.time()\n self.target.write(f\"start: {start_time}, end: {end_time}\")\n\n\ndelays = [5, 4, 3, 2, 1]\nresult_containers = []\nfor i, delay in enumerate(delays):\n st.header(f\"Thread {i}\")\n result_containers.append(st.container())\n\nthreads = [\n WorkerThread(delay, container)\n for delay, container in zip(delays, result_containers)\n]\nfor thread in threads:\n add_script_run_ctx(thread, get_script_run_ctx())\n thread.start()\n\nfor thread in threads:\n thread.join()\n\nst.button(\"Rerun\")\n```\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design/dataframes", - "content": "# Dataframes\n\nDataframes are a great way to display and edit data in a tabular format. Working with Pandas DataFrames and other tabular data structures is key to data science workflows. If developers and data scientists want to display this data in Streamlit, they have multiple options: `st.dataframe` and `st.data_editor`. If you want to solely display data in a table-like UI, [st.dataframe](/develop/api-reference/data/st.dataframe) is the way to go. If you want to interactively edit data, use [st.data_editor](/develop/api-reference/data/st.data_editor). We explore the use cases and advantages of each option in the following sections.\n\n## Display dataframes with st.dataframe\n\nStreamlit can display dataframes in a table-like UI via `st.dataframe` :\n\n```python\nimport streamlit as st\nimport pandas as pd\n\ndf = pd.DataFrame(\n [\n {\"command\": \"st.selectbox\", \"rating\": 4, \"is_widget\": True},\n {\"command\": \"st.balloons\", \"rating\": 5, \"is_widget\": False},\n {\"command\": \"st.time_input\", \"rating\": 3, \"is_widget\": True},\n ]\n)\n\nst.dataframe(df, use_container_width=True)\n```\n\n\n\n## `st.dataframe` UI features\n\n`st.dataframe` provides additional functionality by using [glide-data-grid](https://github.com/glideapps/glide-data-grid) under the hood:\n\n- **Column sorting**: To sort columns, select their headers, or select \"**Sort ascending**\" or \"**Sort descending**\" from the header menu (more_vert).\n- **Column resizing**: To resize columns, drag and drop column header borders, or select \"**Autosize**\" from the header menu.\n- **Column hiding**: To hide columns, select \"**Hide column**\" from the header menu.\n- **Reorder and pin columns**: To reorder columns or pin them on the left, drag and drop column headers or select \"**Pin column**\" from the header menu, respectively.\n- **Format numbers, dates, and times**: To change the format of numeric columns, select an option under \"**Format**\" in the header menu.\n- **Dataframe resizing**: To resize dataframes, drag and drop the bottom right corner.\n- **Fullscreen view**: To enlarge dataframes to fullscreen, select the fullscreen icon (fullscreen) in the toolbar.\n- **Search**: To search through the data, select the search icon (search) in the toolbar or use hotkeys (`โŒ˜+F`ย orย `Ctrl+F`).\n- **Download**: To download the data as a CSV file, select the download icon (download) in the toolbar.\n- **Copy to clipboard**: To copy the data to the clipboard, select one or multiple cells, use the hotkeys (`โŒ˜+C`ย orย `Ctrl+C`), and paste them into your favorite spreadsheet software.\n\n\n\nTry out all the UI features using the embedded app from the prior section.\n\nIn addition to Pandas DataFrames, `st.dataframe` also supports other common Python types, e.g., list, dict, or numpy array. It also supports [Snowpark](https://docs.snowflake.com/en/developer-guide/snowpark/index) and [PySpark](https://spark.apache.org/docs/latest/api/python/) DataFrames, which allow you to lazily evaluate and pull data from databases. This can be useful for working with large datasets.\n\n## Edit data with st.data_editor\n\nStreamlit supports editable dataframes via the `st.data_editor` command. Check out its API in [st.data_editor](/develop/api-reference/data/st.data_editor). It shows the dataframe in a table, similar to `st.dataframe`. But in contrast to `st.dataframe`, this table isn't static! The user can click on cells and edit them. The edited data is then returned on the Python side. Here's an example:\n\n```python\ndf = pd.DataFrame(\n [\n {\"command\": \"st.selectbox\", \"rating\": 4, \"is_widget\": True},\n {\"command\": \"st.balloons\", \"rating\": 5, \"is_widget\": False},\n {\"command\": \"st.time_input\", \"rating\": 3, \"is_widget\": True},\n ]\n)\n\nedited_df = st.data_editor(df) # ๐Ÿ‘ˆ An editable dataframe\n\nfavorite_command = edited_df.loc[edited_df[\"rating\"].idxmax()][\"command\"]\nst.markdown(f\"Your favorite command is **{favorite_command}** ๐ŸŽˆ\")\n```\n\n\n\nTry it out by double-clicking on any cell. You'll notice you can edit all cell values. Try editing the values in the rating column and observe how the text output at the bottom changes:\n\n## `st.data_editor` UI features\n\n`st.data_editor` also supports a few additional things:\n\n- [**Add and delete rows**](#add-and-delete-rows): You can do this by setting `num_rows= \"dynamic\"` when calling `st.data_editor`. This will allow users to add and delete rows as needed.\n- [**Copy and paste support**](#copy-and-paste-support): Copy and paste both between `st.data_editor` and spreadsheet software like Google Sheets and Excel.\n- [**Access edited data**](#access-edited-data): Access only the individual edits instead of the entire edited data structure via Session State.\n- [**Bulk edits**](#bulk-edits): Similar to Excel, just drag a handle to edit neighboring cells.\n- [**Automatic input validation**](#automatic-input-validation): Column Configuration provides strong data type support and other configurable options. For example, there's no way to enter letters into a number cell. Number cells can have a designated min and max.\n- [**Edit common data structures**](#edit-common-data-structures): `st.data_editor` supports lists, dicts, NumPy ndarray, and more!\n\n\n\n### Add and delete rows\n\nWith `st.data_editor`, viewers can add or delete rows via the table UI. This mode can be activated by setting theย `num_rows` parameter toย `\"dynamic\"`:\n\n```python\nedited_df = st.data_editor(df, num_rows=\"dynamic\")\n```\n\n- To add new rows, click the plus icon (add) in the toolbar. Alternatively, click inside a shaded cell below the bottom row of the table.\n- To delete rows, select one or more rows using the checkboxes on the left. Click the delete icon (delete) or press the `delete` key on your keyboard.\n\n\n\n### Copy and paste support\n\nThe data editor supports pasting in tabular data from Google Sheets, Excel, Notion, and many other similar tools. You can also copy-paste data betweenย `st.data_editor` instances. This functionality, powered by the [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API), can be a huge time saver for users who need to work with data across multiple platforms. To try it out:\n\n1. Copy data fromย [this Google Sheets document](https://docs.google.com/spreadsheets/d/1Z0zd-5dF_HfqUaDDq4BWAOnsdlGCjkbTNwDZMBQ1dOY/edit?usp=sharing)ย to your clipboard.\n2. Single click any cell in theย `name`ย column in the app above. Paste it in using hotkeys (`โŒ˜+V`ย orย `Ctrl+V`).\n\n\n\nEvery cell of the pasted data will be evaluated individually and inserted into the cells if the data is compatible with the column type. For example, pasting in non-numerical text data into a number column will be ignored.\n\n\n\n\n\nIf you embed your apps with iframes, you'll need to allow the iframe to access the clipboard if you want to use the copy-paste functionality. To do so, give the iframe [`clipboard-write`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write) and [`clipboard-read`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/read) permissions. E.g.\n\n```javascript\n\n```\n\nAs developers, ensure the app is served with a valid, trusted certificate when using TLS. If users encounter issues with copying and pasting data, direct them to check if their browser has activated clipboard access permissions for the Streamlit application, either when prompted or through the browser's site settings.\n\n\n\n### Access edited data\n\nSometimes, it is more convenient to know which cells have been changed rather than getting the entire edited dataframe back. Streamlit makes this easy through the use of [Session State](/develop/concepts/architecture/session-state). If a `key` parameter is set, Streamlit will store any changes made to the dataframe in Session State.\n\nThis snippet shows how you can access changed data using Session State:\n\n```python\nst.data_editor(df, key=\"my_key\", num_rows=\"dynamic\") # ๐Ÿ‘ˆ Set a key\nst.write(\"Here's the value in Session State:\")\nst.write(st.session_state[\"my_key\"]) # ๐Ÿ‘ˆ Show the value in Session State\n```\n\nIn this code snippet, the `key` parameter is set to `\"my_key\"`. After the data editor is created, the value associated to `\"my_key\"` in Session State is displayed in the app using `st.write`. This shows the additions, edits, and deletions that were made.\n\nThis can be useful when working with large dataframes and you only need to know which cells have changed, rather than access the entire edited dataframe.\n\n\n\nUse all we've learned so far and apply them to the above embedded app. Try editing cells, adding new rows, and deleting rows.\n\nNotice how edits to the table are reflected in Session State. When you make any edits, a rerun is triggered which sends the edits to the backend. The widget's state is a JSON object containing three properties: **edited_rows**, **added_rows**, and **deleted rows:**.\n\n\n\nWhen going from `st.experimental_data_editor` to `st.data_editor` in 1.23.0, the data editor's representation in `st.session_state` was changed. The `edited_cells` dictionary is now called `edited_rows` and uses a different format (`{0: {\"column name\": \"edited value\"}}` instead of `{\"0:1\": \"edited value\"}`). You may need to adjust your code if your app uses `st.experimental_data_editor` in combination with `st.session_state`.\"\n\n\n\n- `edited_rows` is a dictionary containing all edits. Keys are zero-based row indices and values are dictionaries that map column names to edits (e.g. `{0: {\"col1\": ..., \"col2\": ...}}`).\n- `added_rows` is a list of newly added rows. Each value is a dictionary with the same format as above (e.g. `[{\"col1\": ..., \"col2\": ...}]`).\n- `deleted_rows` is a list of row numbers that have been deleted from the table (e.g. `[0, 2]`).\n\n`st.data_editor` does not support reordering rows, so added rows will always be appended to the end of the dataframe with any edits and deletions applicable to the original rows.\n\n### Bulk edits\n\nThe data editor includes a feature that allows for bulk editing of cells. Similar to Excel, you can drag a handle across a selection of cells to edit their values in bulk. You can even apply commonly used [keyboard shortcuts](https://github.com/glideapps/glide-data-grid/blob/main/packages/core/API.md#keybindings) in spreadsheet software. This is useful when you need to make the same change across multiple cells, rather than editing each cell individually.\n\n### Edit common data structures\n\nEditing doesn't just work for Pandas DataFrames! You can also edit lists, tuples, sets, dictionaries, NumPy arrays, or Snowpark & PySpark DataFrames. Most data types will be returned in their original format. But some types (e.g. Snowpark and PySpark) are converted to Pandas DataFrames. To learn about all the supported types, read the [st.data_editor](/develop/api-reference/data/st.data_editor) API.\n\nFor example, you can easily let the user add items to a list:\n\n```python\nedited_list = st.data_editor([\"red\", \"green\", \"blue\"], num_rows= \"dynamic\")\nst.write(\"Here are all the colors you entered:\")\nst.write(edited_list)\n```\n\nOr numpy arrays:\n\n```python\nimport numpy as np\n\nst.data_editor(np.array([\n\t[\"st.text_area\", \"widget\", 4.92],\n\t[\"st.markdown\", \"element\", 47.22]\n]))\n```\n\nOr lists of records:\n\n```python\nst.data_editor([\n {\"name\": \"st.text_area\", \"type\": \"widget\"},\n {\"name\": \"st.markdown\", \"type\": \"element\"},\n])\n```\n\nOr dictionaries and many more types!\n\n```python\nst.data_editor({\n\t\"st.text_area\": \"widget\",\n\t\"st.markdown\": \"element\"\n})\n```\n\n### Automatic input validation\n\nThe data editor includes automatic input validation to help prevent errors when editing cells. For example, if you have a column that contains numerical data, the input field will automatically restrict the user to only entering numerical data. This helps to prevent errors that could occur if the user were to accidentally enter a non-numerical value. Additional input validation can be configured through the [Column configuration API](/develop/api-reference/data/st.column_config). Keep reading below for an overview of column configuration, including validation options.\n\n## Configuring columns\n\nYou can configure the display and editing behavior of columns in `st.dataframe` and `st.data_editor` via the [Column configuration API](/develop/api-reference/data/st.column_config). We have developed the API to let you add images, charts, and clickable URLs in dataframe and data editor columns. Additionally, you can make individual columns editable, set columns as categorical and specify which options they can take, hide the index of the dataframe, and much more.\n\nColumn configuration includes the following column types: Text, Number, Checkbox, Selectbox, Date, Time, Datetime, List, Link, Image, Line chart, Bar chart, and Progress. There is also a generic Column option. See the embedded app below to view these different column types. Each column type is individually previewed in the [Column configuration API](/develop/api-reference/data/st.column_config) documentation.\n\n\n\n### Format values\n\nA `format` parameter is available in column configuration for [Text](/develop/api-reference/data/st.column_config/st.column_config.textcolumn), [Date](/develop/api-reference/data/st.column_config/st.column_config.datecolumn), [Time](/develop/api-reference/data/st.column_config/st.column_config.timecolumn), and [Datetime](/develop/api-reference/data/st.column_config/st.column_config.datetimecolumn) columns. Chart-like columns can also be formatted. [Line chart](/develop/api-reference/data/st.column_config/st.column_config.linechartcolumn) and [Bar chart](/develop/api-reference/data/st.column_config/st.column_config.barchartcolumn) columns have a `y_min` and `y_max` parameters to set the vertical bounds. For a [Progress column](/develop/api-reference/data/st.column_config/st.column_config.progresscolumn), you can declare the horizontal bounds with `min_value` and `max_value`.\n\n### Validate input\n\nWhen specifying a column configuration, you can declare not only the data type of the column but also value restrictions. All column configuration elements allow you to make a column required with the keyword parameter `required=True`.\n\nFor Text and Link columns, you can specify the maximum number of characters with `max_chars` or use regular expressions to validate entries through `validate`. Numerical columns, including Number, Date, Time, and Datetime have `min_value` and `max_value` parameters. Selectbox columns have a configurable list of `options`.\n\nThe data type for Number columns is `float` by default. Passing a value of type `int` to any of `min_value`, `max_value`, `step`, or `default` will set the type for the column as `int`.\n\n### Configure an empty dataframe\n\nYou can use `st.data_editor` to collect tabular input from a user. When starting from an empty dataframe, default column types are text. Use column configuration to specify the data types you want to collect from users.\n\n```python\nimport streamlit as st\nimport pandas as pd\n\ndf = pd.DataFrame(columns=['name','age','color'])\ncolors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']\nconfig = {\n 'name' : st.column_config.TextColumn('Full Name (required)', width='large', required=True),\n 'age' : st.column_config.NumberColumn('Age (years)', min_value=0, max_value=122),\n 'color' : st.column_config.SelectboxColumn('Favorite Color', options=colors)\n}\n\nresult = st.data_editor(df, column_config = config, num_rows='dynamic')\n\nif st.button('Get results'):\n st.write(result)\n```\n\n\n\n## Additional formatting options\n\nIn addition to column configuration, `st.dataframe` and `st.data_editor` have a few more parameters to customize the display of your dataframe.\n\n- `hide_index` : Set to `True` to hide the dataframe's index.\n- `column_order` : Pass a list of column labels to specify the order of display.\n- `disabled` : Pass a list of column labels to disable them from editing. This let's you avoid disabling them individually.\n\n## Handling large datasets\n\n`st.dataframe` and `st.data_editor` have been designed to theoretically handle tables with millions of rows thanks to their highly performant implementation using the glide-data-grid library and HTML canvas. However, the maximum amount of data that an app can realistically handle will depend on several other factors, including:\n\n1. The maximum size of WebSocket messages: Streamlit's WebSocket messages are configurable via the `server.maxMessageSize` [config option](https://docs.streamlit.io/develop/concepts/configuration#view-all-configuration-options), which limits the amount of data that can be transferred via the WebSocket connection at once.\n2. The server memory: The amount of data that your app can handle will also depend on the amount of memory available on your server. If the server's memory is exceeded, the app may become slow or unresponsive.\n3. The user's browser memory: Since all the data needs to be transferred to the user's browser for rendering, the amount of memory available on the user's device can also affect the app's performance. If the browser's memory is exceeded, it may crash or become unresponsive.\n\nIn addition to these factors, a slow network connection can also significantly slow down apps that handle large datasets.\n\nWhen handling large datasets with more than 150,000 rows, Streamlit applies additional optimizations and disables column sorting. This can help to reduce the amount of data that needs to be processed at once and improve the app's performance.\n\n## Limitations\n\n- Streamlit casts all column names to strings internally, so `st.data_editor` will return a DataFrame where all column names are strings.\n- The dataframe toolbar is not currently configurable.\n- While Streamlit's data editing capabilities offer a lot of functionality, editing is enabled for a limited set of column types ([TextColumn](/develop/api-reference/data/st.column_config/st.column_config.textcolumn), [NumberColumn](/develop/api-reference/data/st.column_config/st.column_config.numbercolumn), [LinkColumn](/develop/api-reference/data/st.column_config/st.column_config.linkcolumn), [CheckboxColumn](/develop/api-reference/data/st.column_config/st.column_config.checkboxcolumn), [SelectboxColumn](/develop/api-reference/data/st.column_config/st.column_config.selectboxcolumn), [DateColumn](/develop/api-reference/data/st.column_config/st.column_config.datecolumn), [TimeColumn](/develop/api-reference/data/st.column_config/st.column_config.timecolumn), and [DatetimeColumn](/develop/api-reference/data/st.column_config/st.column_config.datetimecolumn)). We are actively working on supporting editing for other column types as well, such as images, lists, and charts.\n- Almost all editable datatypes are supported for index editing. However, `pandas.CategoricalIndex` and `pandas.MultiIndex` are not supported for editing.\n- Sorting is not supported for `st.data_editor` when `num_rows=\"dynamic\"`.\n- Sorting is deactivated to optimize performance on large datasets with more than 150,000 rows.\n\nWe are continually working to improve Streamlit's handling of DataFrame and add functionality to data editing, so keep an eye out for updates." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design/animate", - "content": "# Animate and update elements\n\nSometimes you display a chart or dataframe and want to modify it live as the app\nruns (for example, in a loop). Some elements have built-in methods to allow you\nto update them in-place without rerunning the app.\n\nUpdatable elements include the following:\n\n- `st.empty` containers can be written to in sequence and will always show the last thing written. They can also be cleared with an\n additional `.empty()` called like a method.\n- `st.dataframe`, `st.table`, and many chart elements can be updated with the `.add_rows()` method which appends data.\n- `st.progress` elements can be updated with additional `.progress()` calls. They can also be cleared with a `.empty()` method call.\n- `st.status` containers have an `.update()` method to change their labels, expanded state, and status.\n- `st.toast` messages can be updated in place with additional `.toast()` calls.\n\n## `st.empty` containers\n\n`st.empty` can hold a single element. When you write any element to an `st.empty` container, Streamlit discards its previous content\ndisplays the new element. You can also `st.empty` containers by calling `.empty()` as a method. If you want to update a set of elements, use\na plain container (`st.container()`) inside `st.empty` and write contents to the plain container. Rewrite the plain container and its\ncontents as often as desired to update your app's display.\n\n## The `.add_rows()` method\n\n`st.dataframe`, `st.table`, and all chart functions can be mutated using the `.add_rows()` method on their output. In the following example, we use `my_data_element = st.line_chart(df)`. You can try the example with `st.table`, `st.dataframe`, and most of the other simple charts by just swapping out `st.line_chart`. Note that `st.dataframe` only shows the first ten rows by default and enables scrolling for additional rows. This means adding rows is not as visually apparent as it is with `st.table` or the chart elements.\n\n```python\nimport streamlit as st\nimport pandas as pd\nimport numpy as np\nimport time\n\ndf = pd.DataFrame(np.random.randn(15, 3), columns=([\"A\", \"B\", \"C\"]))\nmy_data_element = st.line_chart(df)\n\nfor tick in range(10):\n time.sleep(.5)\n add_df = pd.DataFrame(np.random.randn(1, 3), columns=([\"A\", \"B\", \"C\"]))\n my_data_element.add_rows(add_df)\n\nst.button(\"Regenerate\")\n```" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design/custom-classes", - "content": "# Using custom Python classes in your Streamlit app\n\nIf you are building a complex Streamlit app or working with existing code, you may have custom Python classes defined in your script. Common examples include the following:\n\n- Defining a `@dataclass` to store related data within your app.\n- Defining an `Enum` class to represent a fixed set of options or values.\n- Defining custom interfaces to external services or databases not covered by [`st.connection`](/develop/api-reference/connections/st.connection).\n\nBecause Streamlit reruns your script after every user interaction, custom classes may be redefined multiple times within the same Streamlit session. This may result in unwanted effects, especially with class and instance comparisons. Read on to understand this common pitfall and how to avoid it.\n\nWe begin by covering some general-purpose patterns you can use for different types of custom classes, and follow with a few more technical details explaining why this matters. Finally, we go into more detail about [Using `Enum` classes](#using-enum-classes-in-streamlit) specifically, and describe a configuration option which can make them more convenient.\n\n## Patterns to define your custom classes\n\n### Pattern 1: Define your class in a separate module\n\nThis is the recommended, general solution. If possible, move class definitions into their own module file and import them into your app script. As long as you are not editing the files that define your app, Streamlit will not re-import those classes with each rerun. Therefore, if a class is defined in an external file and imported into your script, the class will not be redefined during the session, unless you are actively editing your app.\n\n#### Example: Move your class definition\n\nTry running the following Streamlit app where `MyClass` is defined within the page's script. `isinstance()` will return `True` on the first script run then return `False` on each rerun thereafter.\n\n```python\n# app.py\nimport streamlit as st\n\n# MyClass gets redefined every time app.py reruns\nclass MyClass:\n def __init__(self, var1, var2):\n self.var1 = var1\n self.var2 = var2\n\nif \"my_instance\" not in st.session_state:\n st.session_state.my_instance = MyClass(\"foo\", \"bar\")\n\n# Displays True on the first run then False on every rerun\nst.write(isinstance(st.session_state.my_instance, MyClass))\n\nst.button(\"Rerun\")\n```\n\nIf you move the class definition out of `app.py` into another file, you can make `isinstance()` consistently return `True`. Consider the following file structure:\n\n```\nmyproject/\nโ”œโ”€โ”€ my_class.py\nโ””โ”€โ”€ app.py\n```\n\n```python\n# my_class.py\nclass MyClass:\n def __init__(self, var1, var2):\n self.var1 = var1\n self.var2 = var2\n```\n\n```python\n# app.py\nimport streamlit as st\nfrom my_class import MyClass # MyClass doesn't get redefined with each rerun\n\nif \"my_instance\" not in st.session_state:\n st.session_state.my_instance = MyClass(\"foo\", \"bar\")\n\n# Displays True on every rerun\nst.write(isinstance(st.session_state.my_instance, MyClass))\n\nst.button(\"Rerun\")\n```\n\nStreamlit only reloads code in imported modules when it detects the code has changed. Thus, if you are actively editing your app code, you may need to start a new session or restart your Streamlit server to avoid an undesirable class redefinition.\n\n### Pattern 2: Force your class to compare internal values\n\nFor classes that store data (like [dataclasses](https://docs.python.org/3/library/dataclasses.html)), you may be more interested in comparing the internally stored values rather than the class itself. If you define a custom `__eq__` method, you can force comparisons to be made on the internally stored values.\n\n#### Example: Define `__eq__`\n\nTry running the following Streamlit app and observe how the comparison is `True` on the first run then `False` on every rerun thereafter.\n\n```python\nimport streamlit as st\nfrom dataclasses import dataclass\n\n@dataclass\nclass MyDataclass:\n var1: int\n var2: float\n\nif \"my_dataclass\" not in st.session_state:\n st.session_state.my_dataclass = MyDataclass(1, 5.5)\n\n# Displays True on the first run the False on every rerun\nst.session_state.my_dataclass == MyDataclass(1, 5.5)\n\nst.button(\"Rerun\")\n```\n\nSince `MyDataclass` gets redefined with each rerun, the instance stored in Session State will not be equal to any instance defined in a later script run. You can fix this by forcing a comparison of internal values as follows:\n\n```python\nimport streamlit as st\nfrom dataclasses import dataclass\n\n@dataclass\nclass MyDataclass:\n var1: int\n var2: float\n\n def __eq__(self, other):\n # An instance of MyDataclass is equal to another object if the object\n # contains the same fields with the same values\n return (self.var1, self.var2) == (other.var1, other.var2)\n\nif \"my_dataclass\" not in st.session_state:\n st.session_state.my_dataclass = MyDataclass(1, 5.5)\n\n# Displays True on every rerun\nst.session_state.my_dataclass == MyDataclass(1, 5.5)\n\nst.button(\"Rerun\")\n```\n\nThe default Python `__eq__` implementation for a regular class or `@dataclass` depends on the in-memory ID of the class or class instance. To avoid problems in Streamlit, your custom `__eq__` method should not depend the `type()` of `self` and `other`.\n\n### Pattern 3: Store your class as serialized data\n\nAnother option for classes that store data is to define serialization and deserialization methods like `to_str` and `from_str` for your class. You can use these to store class instance data in `st.session_state` rather than storing the class instance itself. Similar to pattern 2, this is a way to force comparison of the internal data and bypass the changing in-memory IDs.\n\n#### Example: Save your class instance as a string\n\nUsing the same example from pattern 2, this can be done as follows:\n\n```python\nimport streamlit as st\nfrom dataclasses import dataclass\n\n@dataclass\nclass MyDataclass:\n var1: int\n var2: float\n\n def to_str(self):\n return f\"{self.var1},{self.var2}\"\n\n @classmethod\n def from_str(cls, serial_str):\n values = serial_str.split(\",\")\n var1 = int(values[0])\n var2 = float(values[1])\n return cls(var1, var2)\n\nif \"my_dataclass\" not in st.session_state:\n st.session_state.my_dataclass = MyDataclass(1, 5.5).to_str()\n\n# Displays True on every rerun\nMyDataclass.from_str(st.session_state.my_dataclass) == MyDataclass(1, 5.5)\n\nst.button(\"Rerun\")\n```\n\n### Pattern 4: Use caching to preserve your class\n\nFor classes that are used as resources (database connections, state managers, APIs), consider using the cached singleton pattern. Use `@st.cache_resource` to decorate a `@staticmethod` of your class to generate a single, cached instance of the class. For example:\n\n```python\nimport streamlit as st\n\nclass MyResource:\n def __init__(self, api_url: str):\n self._url = api_url\n\n @st.cache_resource(ttl=300)\n @staticmethod\n def get_resource_manager(api_url: str):\n return MyResource(api_url)\n\n# This is cached until Session State is cleared or 5 minutes has elapsed.\nresource_manager = MyResource.get_resource_manager(\"http://example.com/api/\")\n```\n\nWhen you use one of Streamlit's caching decorators on a function, Streamlit doesn't use the function object to look up cached values. Instead, Streamlit's caching decorators index return values using the function's qualified name and module. So, even though Streamlit redefines `MyResource` with each script run, `st.cache_resource` is unaffected by this. `get_resource_manager()` will return its cached value with each rerun, until the value expires.\n\n## Understanding how Python defines and compares classes\n\nSo what's really happening here? We'll consider a simple example to illustrate why this is a pitfall. Feel free to skip this section if you don't want to deal more details. You can jump ahead to learn about [Using `Enum` classes](#using-enum-classes-in-streamlit).\n\n### Example: What happens when you define the same class twice?\n\nSet aside Streamlit for a moment and think about this simple Python script:\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass Student:\n student_id: int\n name: str\n\nMarshall_A = Student(1, \"Marshall\")\nMarshall_B = Student(1, \"Marshall\")\n\n# This is True (because a dataclass will compare two of its instances by value)\nMarshall_A == Marshall_B\n\n# Redefine the class\n@dataclass\nclass Student:\n student_id: int\n name: str\n\nMarshall_C = Student(1, \"Marshall\")\n\n# This is False\nMarshall_A == Marshall_C\n```\n\nIn this example, the dataclass `Student` is defined twice. All three Marshalls have the same internal values. If you compare `Marshall_A` and `Marshall_B` they will be equal because they were both created from the first definition of `Student`. However, if you compare `Marshall_A` and `Marshall_C` they will not be equal because `Marshall_C` was created from the _second_ definition of `Student`. Even though both `Student` dataclasses are defined exactly the same, they have different in-memory IDs and are therefore different.\n\n### What's happening in Streamlit?\n\nIn Streamlit, you probably don't have the same class written twice in your page script. However, the rerun logic of Streamlit creates the same effect. Let's use the above example for an analogy. If you define a class in one script run and save an instance in Session State, then a later rerun will redefine the class and you may end up comparing a `Mashall_C` in your rerun to a `Marshall_A` in Session State. Since widgets rely on Session State under the hood, this is where things can get confusing.\n\n## How Streamlit widgets store options\n\nSeveral Streamlit UI elements, such as `st.selectbox` or `st.radio`, accept multiple-choice options via an `options` argument. The user of your application can typically select one or more of these options. The selected value is returned by the widget function. For example:\n\n```python\nnumber = st.selectbox(\"Pick a number, any number\", options=[1, 2, 3])\n# number == whatever value the user has selected from the UI.\n```\n\nWhen you call a function like `st.selectbox` and pass an `Iterable` to `options`, the `Iterable` and current selection are saved into a hidden portion of [Session State](/develop/concepts/architecture/session-state) called the Widget Metadata.\n\nWhen the user of your application interacts with the `st.selectbox` widget, the broswer sends the index of their selection to your Streamlit server. This index is used to determine which values from the original `options` list, _saved in the Widget Metadata from the previous page execution_, are returned to your application.\n\nThe key detail is that the value returned by `st.selectbox` (or similar widget function) is from an `Iterable` saved in Session State during a _previous_ execution of the page, NOT the values passed to `options` on the _current_ execution. There are a number of architectural reasons why Streamlit is designed this way, which we won't go into here. However, **this** is how we end up comparing instances of different classes when we think we are comparing instances of the same class.\n\n### A pathological example\n\nThe above explanation might be a bit confusing, so here's a pathological example to illustrate the idea.\n\n```python\nimport streamlit as st\nfrom dataclasses import dataclass\n\n@dataclass\nclass Student:\n student_id: int\n name: str\n\nMarshall_A = Student(1, \"Marshall\")\nif \"B\" not in st.session_state:\n st.session_state.B = Student(1, \"Marshall\")\nMarshall_B = st.session_state.B\n\noptions = [Marshall_A,Marshall_B]\nselected = st.selectbox(\"Pick\", options)\n\n# This comparison does not return expected results:\nselected == Marshall_A\n# This comparison evaluates as expected:\nselected == Marshall_B\n```\n\nAs a final note, we used `@dataclass` in the example for this section to illustrate a point, but in fact it is possible to encounter these same problems with classes, in general. Any class which checks class identity inside of a comparison operator—such as `__eq__` or `__gt__`—can exhibit these issues.\n\n## Using `Enum` classes in Streamlit\n\nThe [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum) class from the Python standard library is a powerful way to define custom symbolic names that can be used as options for `st.multiselect` or `st.selectbox` in place of `str` values.\n\nFor example, you might add the following to your streamlit page:\n\n```python\nfrom enum import Enum\nimport streamlit as st\n\n# class syntax\nclass Color(Enum):\n RED = 1\n GREEN = 2\n BLUE = 3\n\nselected_colors = set(st.multiselect(\"Pick colors\", options=Color))\n\nif selected_colors == {Color.RED, Color.GREEN}:\n st.write(\"Hooray, you found the color YELLOW!\")\n```\n\nIf you're using the latest version of Streamlit, this Streamlit page will work as it appears it should. When a user picks both `Color.RED` and `Color.GREEN`, they are shown the special message.\n\nHowever, if you've read the rest of this page you might notice something tricky going on. Specifically, the `Enum` class `Color` gets redefined every time this script is run. In Python, if you define two `Enum` classes with the same class name, members, and values, the classes and their members are still considered unique from each other. This _should_ cause the above `if` condition to always evaluate to `False`. In any script rerun, the `Color` values returned by `st.multiselect` would be of a different class than the `Color` defined in that script run.\n\nIf you run the snippet above with Streamlit version 1.28.0 or less, you will not be able see the special message. Thankfully, as of version 1.29.0, Streamlit introduced a configuration option to greatly simplify the problem. That's where the enabled-by-default `enumCoercion` configuration option comes in.\n\n### Understanding the `enumCoercion` configuration option\n\nWhen `enumCoercion` is enabled, Streamlit tries to recognize when you are using an element like `st.multiselect` or `st.selectbox` with a set of `Enum` members as options.\n\nIf Streamlit detects this, it will convert the widget's returned values to members of the `Enum` class defined in the latest script run. This is something we call automatic `Enum` coercion.\n\nThis behavior is [configurable](/develop/concepts/configuration) via the `enumCoercion` setting in your Streamlit `config.toml` file. It is enabled by default, and may be disabled or set to a stricter set of matching criteria.\n\nIf you find that you still encounter issues with `enumCoercion` enabled, consider using the [custom class patterns](#patterns-to-define-your-custom-classes) described above, such as moving your `Enum` class definition to a separate module file." - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design/buttons", - "content": "# Button behavior and examples\n\n## Summary\n\nButtons created with [`st.button`](/develop/api-reference/widgets/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 a displayed element is nested inside `if st.button('Click me'):`, the element will be visible when the button is clicked and disappear as soon as the user takes their next action. This is because the script reruns and the button return value becomes `False`.\n\nIn this guide, we will illustrate the use of buttons and explain common misconceptions. Read on to see a variety of examples that expand on `st.button` using [`st.session_state`](/develop/api-reference/caching-and-state/st.session_state). [Anti-patterns](#anti-patterns) are included at the end. Go ahead and pull up your favorite code editor so you can `streamlit run` the examples as you read. Check out Streamlit's [Basic concepts](/get-started/fundamentals/main-concepts) if you haven't run your own Streamlit scripts yet.\n\n## When to use `if st.button()`\n\nWhen 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).\n\nGood to nest inside buttons:\n\n- Transient messages that immediately disappear.\n- Once-per-click processes that saves data to session state, a file, or\n a database.\n\nBad to nest inside buttons:\n\n- Displayed items that should persist as the user continues.\n- Other widgets which cause the script to rerun when used.\n- Processes that neither modify session state nor write to a file/database.\\*\n\n\\* This can be appropriate when disposable results are desired. If you\nhave a \"Validate\" button, that could be a process conditioned directly on a\nbutton. It could be used to create an alert to say 'Valid' or 'Invalid' with no\nneed to keep that info.\n\n## Common logic with buttons\n\n### Show a temporary message with a button\n\nIf 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.\n\nIn this example, a user can click a button to check if their `animal` string is in the `animal_shelter` list. When the user clicks \"**Check availability**\" they will see \"We have that animal!\" or \"We don't have that animal.\" If they change the animal in [`st.text_input`](/develop/api-reference/widgets/st.text_input), the script reruns and the message disappears until they click \"**Check availability**\" again.\n\n```python\nimport streamlit as st\n\nanimal_shelter = ['cat', 'dog', 'rabbit', 'bird']\n\nanimal = st.text_input('Type an animal')\n\nif st.button('Check availability'):\n have_it = animal.lower() in animal_shelter\n 'We have that animal!' if have_it else 'We don\\'t have that animal.'\n```\n\nNote: The above example uses [magic](/develop/api-reference/write-magic/magic) to render the message on the frontend.\n\n### Stateful button\n\nIf 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.\n\n```python\nimport streamlit as st\n\nif 'clicked' not in st.session_state:\n st.session_state.clicked = False\n\ndef click_button():\n st.session_state.clicked = True\n\nst.button('Click me', on_click=click_button)\n\nif st.session_state.clicked:\n # The message and nested widget will remain on the page\n st.write('Button clicked!')\n st.slider('Select a value')\n```\n\n### Toggle button\n\nIf you want a button to work like a toggle switch, consider using [`st.checkbox`](/develop/api-reference/widgets/st.checkbox). Otherwise, you can use a button with a callback function to reverse a boolean value saved in `st.session_state`.\n\nIn this example, we use `st.button` to toggle another widget on and off. By displaying [`st.slider`](/develop/api-reference/widgets/st.slider) conditionally on a value in `st.session_state`, the user can interact with the slider without it disappearing.\n\n```python\nimport streamlit as st\n\nif 'button' not in st.session_state:\n st.session_state.button = False\n\ndef click_button():\n st.session_state.button = not st.session_state.button\n\nst.button('Click me', on_click=click_button)\n\nif st.session_state.button:\n # The message and nested widget will remain on the page\n st.write('Button is on!')\n st.slider('Select a value')\nelse:\n st.write('Button is off!')\n```\n\nAlternatively, you can use the value in `st.session_state` on the slider's `disabled` parameter.\n\n```python\nimport streamlit as st\n\nif 'button' not in st.session_state:\n st.session_state.button = False\n\ndef click_button():\n st.session_state.button = not st.session_state.button\n\nst.button('Click me', on_click=click_button)\n\nst.slider('Select a value', disabled=st.session_state.button)\n```\n\n### Buttons to continue or control stages of a process\n\nAnother alternative to nesting content inside a button is to use a value in `st.session_state` that designates the \"step\" or \"stage\" of a process. In this example, we have four stages in our script:\n\n0. Before the user begins.\n1. User enters their name.\n2. User chooses a color.\n3. User gets a thank-you message.\n\nA button at the beginning advances the stage from 0 to 1. A button at the end resets the stage from 3 to 0. The other widgets used in stage 1 and 2 have callbacks to set the stage. If you have a process with dependant steps and want to keep previous stages visible, such a callback forces a user to retrace subsequent stages if they change an earlier widget.\n\n```python\nimport streamlit as st\n\nif 'stage' not in st.session_state:\n st.session_state.stage = 0\n\ndef set_state(i):\n st.session_state.stage = i\n\nif st.session_state.stage == 0:\n st.button('Begin', on_click=set_state, args=[1])\n\nif st.session_state.stage >= 1:\n name = st.text_input('Name', on_change=set_state, args=[2])\n\nif st.session_state.stage >= 2:\n st.write(f'Hello {name}!')\n color = st.selectbox(\n 'Pick a Color',\n [None, 'red', 'orange', 'green', 'blue', 'violet'],\n on_change=set_state, args=[3]\n )\n if color is None:\n set_state(2)\n\nif st.session_state.stage >= 3:\n st.write(f':{color}[Thank you!]')\n st.button('Start Over', on_click=set_state, args=[0])\n```\n\n### Buttons to modify `st.session_state`\n\nIf you modify `st.session_state` inside of a button, you must consider where that button is within the script.\n\n#### A slight problem\n\nIn 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.\n\n```python\nimport streamlit as st\nimport pandas as pd\n\nif 'name' not in st.session_state:\n st.session_state['name'] = 'John Doe'\n\nst.header(st.session_state['name'])\n\nif st.button('Jane'):\n st.session_state['name'] = 'Jane Doe'\n\nif st.button('John'):\n st.session_state['name'] = 'John Doe'\n\nst.header(st.session_state['name'])\n```\n\n#### Logic used in a callback\n\nCallbacks 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.\n\n```python\nimport streamlit as st\nimport pandas as pd\n\nif 'name' not in st.session_state:\n st.session_state['name'] = 'John Doe'\n\ndef change_name(name):\n st.session_state['name'] = name\n\nst.header(st.session_state['name'])\n\nst.button('Jane', on_click=change_name, args=['Jane Doe'])\nst.button('John', on_click=change_name, args=['John Doe'])\n\nst.header(st.session_state['name'])\n```\n\n#### Logic nested in a button with a rerun\n\nAlthough callbacks are often preferred to avoid extra reruns, our first 'John Doe'/'Jane Doe' example can be modified by adding [`st.rerun`](/develop/api-reference/execution-flow/st.rerun) instead. If you need to acces data in `st.session_state` before the button that modifies it, you can include `st.rerun` to rerun the script after the change has been committed. This means the script will rerun twice when a button is clicked.\n\n```python\nimport streamlit as st\nimport pandas as pd\n\nif 'name' not in st.session_state:\n st.session_state['name'] = 'John Doe'\n\nst.header(st.session_state['name'])\n\nif st.button('Jane'):\n st.session_state['name'] = 'Jane Doe'\n st.rerun()\n\nif st.button('John'):\n st.session_state['name'] = 'John Doe'\n st.rerun()\n\nst.header(st.session_state['name'])\n```\n\n### Buttons to modify or reset other widgets\n\nWhen 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.\n\n\n\nDon't do this!\n\n```python\nimport streamlit as st\n\nst.text_input('Name', key='name')\n\n# These buttons will error because their nested code changes\n# a widget's state after that widget within the script.\nif st.button('Clear name'):\n st.session_state.name = ''\nif st.button('Streamlit!'):\n st.session_state.name = ('Streamlit')\n```\n\n\n\n#### Option 1: Use a key for the button and put the logic before the widget\n\nIf 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.\n\n```python\nimport streamlit as st\n\n# Use the get method since the keys won't be in session_state\n# on the first script run\nif st.session_state.get('clear'):\n st.session_state['name'] = ''\nif st.session_state.get('streamlit'):\n st.session_state['name'] = 'Streamlit'\n\nst.text_input('Name', key='name')\n\nst.button('Clear name', key='clear')\nst.button('Streamlit!', key='streamlit')\n```\n\n#### Option 2: Use a callback\n\n```python\nimport streamlit as st\n\nst.text_input('Name', key='name')\n\ndef set_name(name):\n st.session_state.name = name\n\nst.button('Clear name', on_click=set_name, args=[''])\nst.button('Streamlit!', on_click=set_name, args=['Streamlit'])\n```\n\n#### Option 3: Use containers\n\nBy using [`st.container`](/develop/api-reference/layout/st.container) you can have widgets appear in different orders in your script and frontend view (webpage).\n\n```python\nimport streamlit as st\n\nbegin = st.container()\n\nif st.button('Clear name'):\n st.session_state.name = ''\nif st.button('Streamlit!'):\n st.session_state.name = ('Streamlit')\n\n# The widget is second in logic, but first in display\nbegin.text_input('Name', key='name')\n```\n\n### Buttons to add other widgets dynamically\n\nWhen dynamically adding widgets to the page, make sure to use an index to keep the keys unique and avoid a `DuplicateWidgetID` error. In this example, we define a function `display_input_row` which renders a row of widgets. That function accepts an `index` as a parameter. The widgets rendered by `display_input_row` use `index` within their keys so that `display_input_row` can be executed multiple times on a single script rerun without repeating any widget keys.\n\n```python\nimport streamlit as st\n\ndef display_input_row(index):\n left, middle, right = st.columns(3)\n left.text_input('First', key=f'first_{index}')\n middle.text_input('Middle', key=f'middle_{index}')\n right.text_input('Last', key=f'last_{index}')\n\nif 'rows' not in st.session_state:\n st.session_state['rows'] = 0\n\ndef increase_rows():\n st.session_state['rows'] += 1\n\nst.button('Add person', on_click=increase_rows)\n\nfor i in range(st.session_state['rows']):\n display_input_row(i)\n\n# Show the results\nst.subheader('People')\nfor i in range(st.session_state['rows']):\n st.write(\n f'Person {i+1}:',\n st.session_state[f'first_{i}'],\n st.session_state[f'middle_{i}'],\n st.session_state[f'last_{i}']\n )\n```\n\n### Buttons to handle expensive or file-writing processes\n\nWhen you have expensive processes, set them to run upon clicking a button and save the results into `st.session_state`. This allows you to keep accessing the results of the process without re-executing it unnecessarily. This is especially helpful for processes that save to disk or write to a database. In this example, we have an `expensive_process` that depends on two parameters: `option` and `add`. Functionally, `add` changes the output, but `option` does not—`option` is there to provide a parameter\n\n```python\nimport streamlit as st\nimport pandas as pd\nimport time\n\ndef expensive_process(option, add):\n with st.spinner('Processing...'):\n time.sleep(5)\n df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6], 'C':[7, 8, 9]}) + add\n return (df, add)\n\ncols = st.columns(2)\noption = cols[0].selectbox('Select a number', options=['1', '2', '3'])\nadd = cols[1].number_input('Add a number', min_value=0, max_value=10)\n\nif 'processed' not in st.session_state:\n st.session_state.processed = {}\n\n# Process and save results\nif st.button('Process'):\n result = expensive_process(option, add)\n st.session_state.processed[option] = result\n st.write(f'Option {option} processed with add {add}')\n result[0]\n```\n\nAstute observers may think, \"This feels a little like caching.\" We are only saving results relative to one parameter, but the pattern could easily be expanded to save results relative to both parameters. In that sense, yes, it has some similarities to caching, but also some important differences. When you save results in `st.session_state`, the results are only available to the current user in their current session. If you use [`st.cache_data`](/develop/api-reference/caching-and-state/st.cache_data) instead, the results are available to all users across all sessions. Furthermore, if you want to update a saved result, you have to clear all saved results for that function to do so.\n\n## Anti-patterns\n\nHere are some simplified examples of how buttons can go wrong. Be on the lookout for these common mistakes.\n\n### Buttons nested inside buttons\n\n```python\nimport streamlit as st\n\nif st.button('Button 1'):\n st.write('Button 1 was clicked')\n if st.button('Button 2'):\n # This will never be executed.\n st.write('Button 2 was clicked')\n```\n\n### Other widgets nested inside buttons\n\n```python\nimport streamlit as st\n\nif st.button('Sign up'):\n name = st.text_input('Name')\n\n if name:\n # This will never be executed.\n st.success(f'Welcome {name}')\n```\n\n### Nesting a process inside a button without saving to session state\n\n```python\nimport streamlit as st\nimport pandas as pd\n\nfile = st.file_uploader(\"Upload a file\", type=\"csv\")\n\nif st.button('Get data'):\n df = pd.read_csv(file)\n # This display will go away with the user's next action.\n st.write(df)\n\nif st.button('Save'):\n # This will always error.\n df.to_csv('data.csv')\n```" - }, - { - "url": "https://docs.streamlit.io/develop/concepts/design", - "content": "# App design concepts and considerations\n\n\n\n\n\n
Animate and update elements
\n\nUnderstand how to create dynamic, animated content or update elements without rerunning your app.\n\n
\n\n\n\n
Button behavior and examples
\n\nUnderstand how buttons work with explanations and examples to avoid common mistakes.\n\n
\n\n\n\n
Dataframes
\n\nDataframes are a great way to display and edit data in a tabular format. Understand the UI and options available in Streamlit.\n\n
\n\n\n\n
Using custom Python classes in your Streamlit app
\n\nUnderstand the impact of defining your own Python classes within Streamlit's rerun model.\n\n
\n\n\n\n
Multithreading
\n\nUnderstand how to use multithreading within Streamlit apps.\n\n
\n\n\n\n
Working with timezones
\n\nUnderstand how to localize time to your users.\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/configuration/st.get_option", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/configuration/st.set_page_config", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/configuration/config.toml", - "content": "## config.toml\n\n`config.toml` is an optional file you can define for your working directory or global development environment. When `config.toml` is defined both globally and in your working directory, Streamlit combines the configuration options and gives precedence to the working-directory configuration. Additionally, you can use environment variables and command-line options to override additional configuration options. For more information, see [Configuration options](/develop/concepts/configuration/options).\n\n### File location\n\nTo define your configuration locally or per-project, add `.streamlit/config.toml` to your working directory. Your working directory is wherever you call `streamlit run`. If you haven't previously created the `.streamlit` directory, you will need to add it.\n\nTo define your configuration globally, you must first locate your global `.streamlit` directory. Streamlit adds this hidden directory to your OS user profile during installation. For MacOS/Linux, this will be `~/.streamlit/config.toml`. For Windows, this will be `%userprofile%/.streamlit/config.toml`.\n\n### File format\n\n`config.toml` is a [TOML](https://toml.io/en/) file.\n\n#### Example\n\n```toml\n[client]\nshowErrorDetails = \"none\"\n\n[theme]\nprimaryColor = \"#F63366\"\nbackgroundColor = \"black\"\n```\n\n### Available configuration options\n\nBelow are all the sections and options you can have in your `.streamlit/config.toml` file. To see all configurations, use the following command in your terminal or CLI:\n\n```bash\nstreamlit config show\n```\n\n#### Global\n\n```toml\n[global]\n\n# By default, Streamlit displays a warning when a user sets both a widget\n# default value in the function defining the widget and a widget value via\n# the widget's key in `st.session_state`.\n# If you'd like to turn off this warning, set this to True.\n# Default: false\ndisableWidgetStateDuplicationWarning = false\n\n# If True, will show a warning when you run a Streamlit-enabled script\n# via \"python my_script.py\".\n# Default: true\nshowWarningOnDirectExecution = true\n```\n\n#### Logger\n\n```toml\n[logger]\n\n# Level of logging for Streamlit's internal logger: \"error\", \"warning\",\n# \"info\", or \"debug\".\n# Default: \"info\"\nlevel = \"info\"\n\n# String format for logging messages. If logger.datetimeFormat is set,\n# logger messages will default to `%(asctime)s.%(msecs)03d %(message)s`.\n# See Python's documentation for available attributes:\n# https://docs.python.org/3/library/logging.html#formatter-objects\n# Default: \"%(asctime)s %(message)s\"\nmessageFormat = \"%(asctime)s %(message)s\"\n```\n\n#### Client\n\n```toml\n[client]\n\n# Controls whether uncaught app exceptions and deprecation warnings\n# are displayed in the browser. This can be one of the following:\n# - \"full\" : In the browser, Streamlit displays app deprecation\n# warnings and exceptions, including exception types,\n# exception messages, and associated tracebacks.\n# - \"stacktrace\" : In the browser, Streamlit displays exceptions,\n# including exception types, generic exception messages,\n# and associated tracebacks. Deprecation warnings and\n# full exception messages will only print to the\n# console.\n# - \"type\" : In the browser, Streamlit displays exception types and\n# generic exception messages. Deprecation warnings, full\n# exception messages, and associated tracebacks only\n# print to the console.\n# - \"none\" : In the browser, Streamlit displays generic exception\n# messages. Deprecation warnings, full exception\n# messages, associated tracebacks, and exception types\n# will only print to the console.\n# - True : This is deprecated. Streamlit displays \"full\"\n# error details.\n# - False : This is deprecated. Streamlit displays \"stacktrace\"\n# error details.\n# Default: \"full\"\nshowErrorDetails = \"full\"\n\n# Change the visibility of items in the toolbar, options menu,\n# and settings dialog (top right of the app).\n# Allowed values:\n# - \"auto\" : Show the developer options if the app is accessed through\n# localhost or through Streamlit Community Cloud as a developer.\n# Hide them otherwise.\n# - \"developer\" : Show the developer options.\n# - \"viewer\" : Hide the developer options.\n# - \"minimal\" : Show only options set externally (e.g. through\n# Streamlit Community Cloud) or through st.set_page_config.\n# If there are no options left, hide the menu.\n# Default: \"auto\"\ntoolbarMode = \"auto\"\n\n# Controls whether to display the default sidebar page navigation in a\n# multi-page app. This only applies when app's pages are defined by the\n# `pages/` directory.\n# Default: true\nshowSidebarNavigation = true\n```\n\n#### Runner\n\n```toml\n[runner]\n\n# Allows you to type a variable or string by itself in a single line of\n# Python code to write it to the app.\n# Default: true\nmagicEnabled = true\n\n# Handle script rerun requests immediately, rather than waiting for\n# script execution to reach a yield point.\n# This makes Streamlit much more responsive to user interaction, but it\n# can lead to race conditions in apps that mutate session_state data\n# outside of explicit session_state assignment statements.\n# Default: true\nfastReruns = true\n\n# Raise an exception after adding unserializable data to Session State.\n# Some execution environments may require serializing all data in Session\n# State, so it may be useful to detect incompatibility during development,\n# or when the execution environment will stop supporting it in the future.\n# Default: false\nenforceSerializableSessionState = false\n\n# Adjust how certain 'options' widgets like radio, selectbox, and\n# multiselect coerce Enum members.\n# This is useful when the Enum class gets re-defined during a script\n# re-run. For more information, check out the docs:\n# https://docs.streamlit.io/develop/concepts/design/custom-classes#enums\n# Allowed values:\n# - \"off\" : Disables Enum coercion.\n# - \"nameOnly\" : Enum classes can be coerced if their member names match.\n# - \"nameAndValue\" : Enum classes can be coerced if their member names AND\n# member values match.\n# Default: \"nameOnly\"\nenumCoercion = \"nameOnly\"\n```\n\n#### Server\n\n```toml\n[server]\n\n# List of directories to watch for changes.\n# By default, Streamlit watches files in the current working directory\n# and its subdirectories. Use this option to specify additional\n# directories to watch. Paths must be absolute.\n# Default: []\nfolderWatchList = []\n\n# List of directories to ignore for changes.\n# By default, Streamlit watches files in the current working directory\n# and its subdirectories. Use this option to specify exceptions within\n# watched directories. Paths can be absolute or relative to the current\n# working directory.\n# Example: ['/home/user1/env', 'relative/path/to/folder']\n# Default: []\nfolderWatchBlacklist = []\n\n# Change the type of file watcher used by Streamlit, or turn it off\n# completely.\n# Allowed values:\n# - \"auto\" : Streamlit will attempt to use the watchdog module, and\n# falls back to polling if watchdog is not available.\n# - \"watchdog\" : Force Streamlit to use the watchdog module.\n# - \"poll\" : Force Streamlit to always use polling.\n# - \"none\" : Streamlit will not watch files.\n# Default: \"auto\"\nfileWatcherType = \"auto\"\n\n# Symmetric key used to produce signed cookies. If deploying on multiple\n# replicas, this should be set to the same value across all replicas to ensure\n# they all share the same secret.\n# Default: randomly generated secret key.\ncookieSecret = \"a-random-key-appears-here\"\n\n# If false, will attempt to open a browser window on start.\n# Default: false unless (1) we are on a Linux box where DISPLAY is unset, or\n# (2) we are running in the Streamlit Atom plugin.\nheadless = false\n\n# Automatically rerun script when the file is modified on disk.\n# Default: false\nrunOnSave = false\n\n# The address where the server will listen for client and browser\n# connections.\n# Use this if you want to bind the server to a specific address.\n# If set, the server will only be accessible from this address, and not from\n# any aliases (like localhost).\n# Default: (unset)\naddress =\n\n# The port where the server will listen for browser connections.\n# Default: 8501\nport = 8501\n\n# The base path for the URL where Streamlit should be served from.\n# Default: \"\"\nbaseUrlPath = \"\"\n\n# Enables support for Cross-Origin Resource Sharing (CORS) protection,\n# for added security.\n# If XSRF protection is enabled and CORS protection is disabled at the\n# same time, Streamlit will enable them both instead.\n# Default: true\nenableCORS = true\n\n# Allowed list of origins.\n# If CORS protection is enabled (`server.enableCORS=True`), use this\n# option to set a list of allowed origins that the Streamlit server will\n# accept traffic from.\n# This config option does nothing if CORS protection is disabled.\n# Example: ['http://example.com', 'https://streamlit.io']\n# Default: []\ncorsAllowedOrigins = []\n\n# Enables support for Cross-Site Request Forgery (XSRF) protection, for\n# added security.\n# If XSRF protection is enabled and CORS protection is disabled at the\n# same time, Streamlit will enable them both instead.\n# Default: true\nenableXsrfProtection = true\n\n# Max size, in megabytes, for files uploaded with the file_uploader.\n# Default: 200\nmaxUploadSize = 200\n\n# Max size, in megabytes, of messages that can be sent via the WebSocket\n# connection.\n# Default: 200\nmaxMessageSize = 200\n\n# Enables support for websocket compression.\n# Default: false\nenableWebsocketCompression = false\n\n# Enable serving files from a `static` directory in the running app's\n# directory.\n# Default: false\nenableStaticServing = false\n\n# TTL in seconds for sessions whose websockets have been disconnected.\n# The server may choose to clean up session state, uploaded files, etc\n# for a given session with no active websocket connection at any point\n# after this time has passed.\n# Default: 120\ndisconnectedSessionTTL = 120\n\n# Server certificate file for connecting via HTTPS.\n# Must be set at the same time as \"server.sslKeyFile\".\n# ['DO NOT USE THIS OPTION IN A PRODUCTION ENVIRONMENT. It has not gone through\n# security audits or performance tests. For the production environment, we\n# recommend performing SSL termination by the load balancer or the reverse\n# proxy.']\nsslCertFile =\n\n# Cryptographic key file for connecting via HTTPS.\n# Must be set at the same time as \"server.sslCertFile\".\n# ['DO NOT USE THIS OPTION IN A PRODUCTION ENVIRONMENT. It has not gone through\n# security audits or performance tests. For the production environment, we\n# recommend performing SSL termination by the load balancer or the reverse\n# proxy.']\nsslKeyFile =\n```\n\n#### Browser\n\n```toml\n[browser]\n\n# Internet address where users should point their browsers in order to\n# connect to the app. Can be IP address or DNS name and path.\n# This is used to:\n# - Set the correct URL for CORS and XSRF protection purposes.\n# - Show the URL on the terminal\n# - Open the browser\n# Default: \"localhost\"\nserverAddress = \"localhost\"\n\n# Whether to send usage statistics to Streamlit.\n# Default: true\ngatherUsageStats = true\n\n# Port where users should point their browsers in order to connect to the\n# app.\n# This is used to:\n# - Set the correct URL for XSRF protection purposes.\n# - Show the URL on the terminal (part of `streamlit run`).\n# - Open the browser automatically (part of `streamlit run`).\n# This option is for advanced use cases. To change the port of your app, use\n# `server.Port` instead.\n# Default: whatever value is set in server.port.\nserverPort = 8501\n```\n\n#### Mapbox\n\n```toml\n[mapbox]\n\n# If you'd like to show maps using Mapbox rather than Carto, use this\n# to pass the Mapbox API token.\n# THIS IS DEPRECATED.\n# Instead of this, you should use either the MAPBOX_API_KEY environment\nvariable or PyDeck's `api_keys` argument.\n# This option will be removed on or after 2026-05-01.\n# Default: \"\"\ntoken = \"\"\n```\n\n#### Theme\n\n```toml\n[theme]\n\n# The preset Streamlit theme that your custom theme inherits from.\n# This can be one of the following: \"light\" or \"dark\".\nbase =\n\n# Primary accent color.\nprimaryColor =\n\n# Background color of the app.\nbackgroundColor =\n\n# Background color used for most interactive widgets.\nsecondaryBackgroundColor =\n\n# Color used for almost all text.\ntextColor =\n\n# Color used for all links.\nlinkColor =\n\n# Background color used for code blocks.\ncodeBackgroundColor =\n\n# The font family for all text, except code blocks.\n# This can be one of the following:\n# - \"sans-serif\"\n# - \"serif\"\n# - \"monospace\"\n# - The `family` value for a custom font table under [[theme.fontFaces]]\n# - A comma-separated list of these (as a single string) to specify\n# fallbacks\n# For example, you can use the following:\n# font = \"cool-font, fallback-cool-font, sans-serif\"\nfont =\n\n# The font family to use for code (monospace) in the sidebar.\n# This can be one of the following:\n# - \"sans-serif\"\n# - \"serif\"\n# - \"monospace\"\n# - The `family` value for a custom font table under [[theme.fontFaces]]\n# - A comma-separated list of these (as a single string) to specify\n# fallbacks\ncodeFont =\n\n# Sets the font size (in pixels or rem) for code blocks and code text.\n# This applies to `st.code`, `st.json`, and `st.help`.\n# It does not apply to inline code, which is set by default to 0.75em.\n# When unset, the code font size will be 0.875rem.\ncodeFontSize =\n\n# The font family to use for headings.\n# This can be one of the following:\n# - \"sans-serif\"\n# - \"serif\"\n# - \"monospace\"\n# - The `family` value for a custom font table under [[theme.fontFaces]]\n# - A comma-separated list of these (as a single string) to specify\n# fallbacks\n# If no heading font is set, Streamlit uses `theme.font` for headings.\nheadingFont =\n\n# An array of fonts to use in your app.\n# Each font in the array is a table (dictionary) that can have the\n# following attributes, closely resembling CSS font-face definitions:\n# - family\n# - url\n# - weight (optional)\n# - style (optional)\n# - unicodeRange (optional)\n# To host a font with your app, enable static file serving with\n# `server.enableStaticServing=true`.\n# You can define multiple [[theme.fontFaces]] tables, including multiple\n# tables with the same family if your font is defined by multiple files.\n# For example, a font hosted with your app may have a [[theme.fontFaces]]\n# table as follows:\n# [[theme.fontFaces]]\n# family = \"font_name\"\n# url = \"app/static/font_file.woff\"\n# weight = \"400\"\n# style = \"normal\"\nfontFaces =\n\n# The radius used as basis for the corners of most UI elements.\n# This can be one of the following:\n# - \"none\"\n# - \"small\"\n# - \"medium\"\n# - \"large\"\n# - \"full\"\n# - The number in pixels or rem.\n# For example, you can use \"10px\", \"0.5rem\", or \"2rem\". To follow best\n# practices, use rem instead of pixels when specifying a numeric size.\nbaseRadius =\n\n# The radius used as basis for the corners of buttons.\n# This can be one of the following:\n# - \"none\"\n# - \"small\"\n# - \"medium\"\n# - \"large\"\n# - \"full\"\n# - The number in pixels or rem.\n# For example, you can use \"10px\", \"0.5rem\", or \"2rem\". To follow best\n# practices, use rem instead of pixels when specifying a numeric size.\n# If no button radius is set, Streamlit uses `theme.baseRadius` instead.\nbuttonRadius =\n\n# The color of the border around elements.\nborderColor =\n\n# The color of the border around dataframes and tables.\n# If no dataframe border color is set, Streamlit uses `theme.borderColor`\n# instead.\ndataframeBorderColor =\n\n# Whether to show a border around input widgets.\nshowWidgetBorder =\n\n# Sets the root font size (in pixels) for the app.\n# This determines the overall scale of text and UI elements.\n# When unset, the font size will be 16px.\nbaseFontSize =\n\n# Whether to show a vertical separator between the sidebar and the main\n# content area.\nshowSidebarBorder =\n```\n\n#### Sidebar theme\n\n```toml\n[theme.sidebar]\n\n# Primary accent color.\nprimaryColor =\n\n# Background color of the app.\nbackgroundColor =\n\n# Background color used for most interactive widgets.\nsecondaryBackgroundColor =\n\n# Color used for almost all text.\ntextColor =\n\n# Color used for all links.\nlinkColor =\n\n# Background color used for code blocks.\ncodeBackgroundColor =\n\n# The font family for all text, except code blocks.\n# This can be one of the following:\n# - \"sans-serif\"\n# - \"serif\"\n# - \"monospace\"\n# - The `family` value for a custom font table under [[theme.fontFaces]]\n# - A comma-separated list of these (as a single string) to specify\n# fallbacks\n# For example, you can use the following:\n# font = \"cool-font, fallback-cool-font, sans-serif\"\nfont =\n\n# The font family to use for code (monospace) in the sidebar.\n# This can be one of the following:\n# - \"sans-serif\"\n# - \"serif\"\n# - \"monospace\"\n# - The `family` value for a custom font table under [[theme.fontFaces]]\n# - A comma-separated list of these (as a single string) to specify\n# fallbacks\ncodeFont =\n\n# Sets the font size (in pixels or rem) for code blocks and code text.\n# This applies to `st.code`, `st.json`, and `st.help`.\n# It does not apply to inline code, which is set by default to 0.75em.\n# When unset, the code font size will be 0.875rem.\ncodeFontSize =\n\n# The font family to use for headings.\n# This can be one of the following:\n# - \"sans-serif\"\n# - \"serif\"\n# - \"monospace\"\n# - The `family` value for a custom font table under [[theme.fontFaces]]\n# - A comma-separated list of these (as a single string) to specify\n# fallbacks\n# If no heading font is set, Streamlit uses `theme.font` for headings.\nheadingFont =\n\n# The radius used as basis for the corners of most UI elements.\n# This can be one of the following:\n# - \"none\"\n# - \"small\"\n# - \"medium\"\n# - \"large\"\n# - \"full\"\n# - The number in pixels or rem.\n# For example, you can use \"10px\", \"0.5rem\", or \"2rem\". To follow best\n# practices, use rem instead of pixels when specifying a numeric size.\nbaseRadius =\n\n# The radius used as basis for the corners of buttons.\n# This can be one of the following:\n# - \"none\"\n# - \"small\"\n# - \"medium\"\n# - \"large\"\n# - \"full\"\n# - The number in pixels or rem.\n# For example, you can use \"10px\", \"0.5rem\", or \"2rem\". To follow best\n# practices, use rem instead of pixels when specifying a numeric size.\n# If no button radius is set, Streamlit uses `theme.baseRadius` instead.\nbuttonRadius =\n\n# The color of the border around elements.\nborderColor =\n\n# The color of the border around dataframes and tables.\n# If no dataframe border color is set, Streamlit uses `theme.borderColor`\n# instead.\ndataframeBorderColor =\n\n# Whether to show a border around input widgets.\nshowWidgetBorder =\n```\n\n#### Secrets\n\n```toml\n[secrets]\n\n# List of locations where secrets are searched.\n# An entry can be a path to a TOML file or directory path where\n# Kubernetes style secrets are saved. Order is important, import is\n# first to last, so secrets in later files will take precedence over\n# earlier ones.\n# Default: [ , ,]\nfiles = [ \"~/.streamlit/secrets.toml\", \"~/project directory/.streamlit/secrets.toml\",]\n```" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/configuration/st.set_option", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/configuration", - "content": "# Configuration\n\n\n\n\n

Configuration file

\n\nConfigures the default settings for your app.\n\n```\nyour-project/\nโ”œโ”€โ”€ .streamlit/\nโ”‚ โ””โ”€โ”€ config.toml\nโ””โ”€โ”€ your_app.py\n```\n\n
\n\n\n

Get config option

\n\nRetrieve a single configuration option.\n\n```python\nst.get_option(\"theme.primaryColor\")\n```\n\n
\n\n\n

Set config option

\n\nSet a single configuration option. (This is very limited.)\n\n```python\nst.set_option(\"deprecation.showPyplotGlobalUse\", False)\n```\n\n
\n\n\n

Set page title, favicon, and more

\n\nConfigures the default settings of the page.\n\n```python\nst.set_page_config(\n page_title=\"My app\",\n page_icon=\":shark:\",\n)\n```\n\n
\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/chat/st.chat_message", - "content": "\n\nRead the [Build a basic LLM chat app](/develop/tutorials/llms/build-conversational-apps) tutorial to learn how to use `st.chat_message` and `st.chat_input` to build chat-based apps.\n\n\n\n\n\nFor an overview of the `st.chat_message` and `st.chat_input` API, check out this video tutorial by Chanin Nantasenamat ([@dataprofessor](https://www.youtube.com/dataprofessor)), a Senior Developer Advocate at Streamlit.\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/chat/st.chat_input", - "content": "\n\nRead the [Build a basic LLM chat app](/develop/tutorials/llms/build-conversational-apps) tutorial to learn how to use `st.chat_message` and `st.chat_input` to build chat-based apps.\n\n\n\n\n\nFor an overview of the `st.chat_input` and `st.chat_message` API, check out this video tutorial by Chanin Nantasenamat ([@dataprofessor](https://www.youtube.com/dataprofessor)), a Senior Developer Advocate at Streamlit.\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/chat", - "content": "# Chat elements\n\nStreamlit provides a few commands to help you build conversational apps. These chat elements are designed to be used in conjunction with each other, but you can also use them separately.\n\n`st.chat_message` lets you insert a chat message container into the app so you can display messages from the user or the app. Chat containers can contain other Streamlit elements, including charts, tables, text, and more. `st.chat_input` lets you display a chat input widget so the user can type in a message. Remember to check out `st.status` to display output from long-running processes and external API calls.\n\n\n\n\n\"screenshot\"\n\n

Chat input

\n\nDisplay a chat input widget.\n\n```python\nprompt = st.chat_input(\"Say something\")\nif prompt:\n st.write(f\"The user has sent: {prompt}\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Chat message

\n\nInsert a chat message container.\n\n```python\nimport numpy as np\nwith st.chat_message(\"user\"):\n st.write(\"Hello ๐Ÿ‘‹\")\n st.line_chart(np.random.randn(30, 3))\n```\n\n
\n\n\n\"screenshot\"\n\n

Status container

\n\nDisplay output of long-running tasks in a container.\n\n```python\nwith st.status('Running'):\n do_something_slow()\n```\n\n
\n\n\n

st.write_stream

\n\nWrite generators or streams to the app with a typewriter effect.\n\n```python\nst.write_stream(my_generator)\nst.write_stream(my_llm_stream)\n```\n\n
\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.expander", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.columns", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.container", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.popover", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.tabs", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.sidebar", - "content": "## st.sidebar\n\n## Add widgets to sidebar\n\nNot only can you add interactivity to your app with widgets, you can organize them into a sidebar. Elements can be passed to `st.sidebar` using object notation and `with` notation.\n\nThe following two snippets are equivalent:\n\n```python\n# Object notation\nst.sidebar.[element_name]\n```\n\n```python\n# \"with\" notation\nwith st.sidebar:\n st.[element_name]\n```\n\nEach element that's passed to `st.sidebar` is pinned to the left, allowing users to focus on the content in your app.\n\n\n\nThe sidebar is resizable! Drag and drop the right border of the sidebar to resize it! โ†”๏ธ\n\n\n\nHere's an example of how you'd add a selectbox and a radio button to your sidebar:\n\n```python\nimport streamlit as st\n\n# Using object notation\nadd_selectbox = st.sidebar.selectbox(\n \"How would you like to be contacted?\",\n (\"Email\", \"Home phone\", \"Mobile phone\")\n)\n\n# Using \"with\" notation\nwith st.sidebar:\n add_radio = st.radio(\n \"Choose a shipping method\",\n (\"Standard (5-15 days)\", \"Express (2-5 days)\")\n )\n```\n\n\n\nThe only elements that aren't supported using object notation are `st.echo`, `st.spinner`, and `st.toast`. To use these elements, you must use `with` notation.\n\n\n\nHere's an example of how you'd add [`st.echo`](/develop/api-reference/text/st.echo) and [`st.spinner`](/develop/api-reference/status/st.spinner) to your sidebar:\n\n```python\nimport streamlit as st\nimport time\n\nwith st.sidebar:\n with st.echo():\n st.write(\"This code will be printed to the sidebar.\")\n\n with st.spinner(\"Loading...\"):\n time.sleep(5)\n st.success(\"Done!\")\n```" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout/st.empty", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/layout", - "content": "# Layouts and Containers\n\n## Complex layouts\n\nStreamlit provides several options for controlling how different elements are laid out on the screen.\n\n\n\n\n\"screenshot\"\n\n

Columns

\n\nInsert containers laid out as side-by-side columns.\n\n```python\ncol1, col2 = st.columns(2)\ncol1.write(\"this is column 1\")\ncol2.write(\"this is column 2\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Container

\n\nInsert a multi-element container.\n\n```python\nc = st.container()\nst.write(\"This will show last\")\nc.write(\"This will show first\")\nc.write(\"This will show second\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Modal dialog

\n\nInsert a modal dialog that can rerun independently from the rest of the script.\n\n```python\n@st.dialog(\"Sign up\")\ndef email_form():\n name = st.text_input(\"Name\")\n email = st.text_input(\"Email\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Empty

\n\nInsert a single-element container.\n\n```python\nc = st.empty()\nst.write(\"This will show last\")\nc.write(\"This will be replaced\")\nc.write(\"This will show first\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Expander

\n\nInsert a multi-element container that can be expanded/collapsed.\n\n```python\nwith st.expander(\"Open to see more\"):\n st.write(\"This is more content\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Popover

\n\nInsert a multi-element popover container that can be opened/closed.\n\n```python\nwith st.popover(\"Settings\"):\n st.checkbox(\"Show completed\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Sidebar

\n\nDisplay items in a sidebar.\n\n```python\nst.sidebar.write(\"This lives in the sidebar\")\nst.sidebar.button(\"Click me!\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Tabs

\n\nInsert containers separated into tabs.\n\n```python\ntab1, tab2 = st.tabs([\"Tab 1\", \"Tab2\"])\ntab1.write(\"this is tab 1\")\ntab2.write(\"this is tab 2\")\n```\n\n
\n
\n\n\n\n\n\n\"screenshot\"\n\n

Streamlit Elements

\n\nCreate a draggable and resizable dashboard in Streamlit. Created by [@okls](https://github.com/okls).\n\n```python\nfrom streamlit_elements import elements, mui, html\n\nwith elements(\"new_element\"):\n mui.Typography(\"Hello world\")\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Pydantic

\n\nAuto-generate Streamlit UI from Pydantic Models and Dataclasses. Created by [@lukasmasuch](https://github.com/lukasmasuch).\n\n```python\nimport streamlit_pydantic as sp\n\nsp.pydantic_form(key=\"my_form\",\n model=ExampleModel)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Streamlit Pages

\n\nAn experimental version of Streamlit Multi-Page Apps. Created by [@blackary](https://github.com/blackary).\n\n```python\nfrom st_pages import Page, show_pages, add_page_title\n\nshow_pages([ Page(\"streamlit_app.py\", \"Home\", \"๐Ÿ \"),\n Page(\"other_pages/page2.py\", \"Page 2\", \":books:\"), ])\n```\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/navigation/st.navigation", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/navigation/st.switch_page", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/navigation/st.page", - "content": "\n\n\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/navigation", - "content": "# Navigation and pages\n\n\n\n\n\n\"screenshot\"\n\n

Navigation

\n\nConfigure the available pages in a multipage app.\n\n```python\nst.navigation({\n \"Your account\" : [log_out, settings],\n \"Reports\" : [overview, usage],\n \"Tools\" : [search]\n})\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Page

\n\nDefine a page in a multipage app.\n\n```python\nhome = st.Page(\n \"home.py\",\n title=\"Home\",\n icon=\":material/home:\"\n)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Page link

\n\nDisplay a link to another page in a multipage app.\n\n```python\nst.page_link(\"app.py\", label=\"Home\", icon=\"๐Ÿ \")\nst.page_link(\"pages/profile.py\", label=\"Profile\")\n```\n\n
\n\n\n\n

Switch page

\n\nProgrammatically navigates to a specified page.\n\n```python\nst.switch_page(\"pages/my_page.py\")\n```\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.line_chart", - "content": "\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.bar_chart", - "content": "\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.map", - "content": "\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.vega_lite_chart", - "content": "\n\n## Chart selections\n\n\n\n\n\n## Theming\n\nVega-Lite charts are displayed using the Streamlit theme by default. This theme is sleek, user-friendly, and incorporates Streamlit's color palette. The added benefit is that your charts better integrate with the rest of your app's design.\n\nThe Streamlit theme is available from Streamlit 1.16.0 through the `theme=\"streamlit\"` keyword argument. To disable it, and use Vega-Lite's native theme, use `theme=None` instead.\n\nLet's look at an example of charts with the Streamlit theme and the native Vega-Lite theme:\n\n```python\nimport streamlit as st\nfrom vega_datasets import data\n\nsource = data.cars()\n\nchart = {\n \"mark\": \"point\",\n \"encoding\": {\n \"x\": {\n \"field\": \"Horsepower\",\n \"type\": \"quantitative\",\n },\n \"y\": {\n \"field\": \"Miles_per_Gallon\",\n \"type\": \"quantitative\",\n },\n \"color\": {\"field\": \"Origin\", \"type\": \"nominal\"},\n \"shape\": {\"field\": \"Origin\", \"type\": \"nominal\"},\n },\n}\n\ntab1, tab2 = st.tabs([\"Streamlit theme (default)\", \"Vega-Lite native theme\"])\n\nwith tab1:\n # Use the Streamlit theme.\n # This is the default. So you can also omit the theme argument.\n st.vega_lite_chart(\n source, chart, theme=\"streamlit\", use_container_width=True\n )\nwith tab2:\n st.vega_lite_chart(\n source, chart, theme=None, use_container_width=True\n )\n```\n\nClick the tabs in the interactive app below to see the charts with the Streamlit theme enabled and disabled.\n\n\n\nIf you're wondering if your own customizations will still be taken into account, don't worry! You can still make changes to your chart configurations. In other words, although we now enable the Streamlit theme by default, you can overwrite it with custom colors or fonts. For example, if you want a chart line to be green instead of the default red, you can do it!" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.scatter_chart", - "content": "\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.bokeh_chart", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.area_chart", - "content": "\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.pydeck_chart", - "content": "\n\n## Chart selections\n\n\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.pyplot", - "content": "\n\n\n Matplotlibย [doesn't work well with threads](https://matplotlib.org/3.3.2/faq/howto_faq.html#working-with-threads). So if you're using Matplotlib you should wrap your code with locks. This Matplotlib bug is more prominent when you deploy and share your apps because you're more likely to get concurrent users then. The following example uses [`Rlock`](https://docs.python.org/3/library/threading.html#rlock-objects) from the `threading` module.\n\n ```python\n import streamlit as st\n import matplotlib.pyplot as plt\n import numpy as np\n from threading import RLock\n\n _lock = RLock()\n\n x = np.random.normal(1, 1, 100)\n y = np.random.normal(1, 1, 100)\n\n with _lock:\n fig, ax = plt.subplots()\n ax.scatter(x, y)\n st.pyplot(fig)\n ```\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.plotly_chart", - "content": "\n\n## Chart selections\n\n\n\n\n\n## Theming\n\nPlotly charts are displayed using the Streamlit theme by default. This theme is sleek, user-friendly, and incorporates Streamlit's color palette. The added benefit is that your charts better integrate with the rest of your app's design.\n\nThe Streamlit theme is available from Streamlit 1.16.0 through the `theme=\"streamlit\"` keyword argument. To disable it, and use Plotly's native theme, use `theme=None` instead.\n\nLet's look at an example of charts with the Streamlit theme and the native Plotly theme:\n\n```python\nimport plotly.express as px\nimport streamlit as st\n\ndf = px.data.gapminder()\n\nfig = px.scatter(\n df.query(\"year==2007\"),\n x=\"gdpPercap\",\n y=\"lifeExp\",\n size=\"pop\",\n color=\"continent\",\n hover_name=\"country\",\n log_x=True,\n size_max=60,\n)\n\ntab1, tab2 = st.tabs([\"Streamlit theme (default)\", \"Plotly native theme\"])\nwith tab1:\n # Use the Streamlit theme.\n # This is the default. So you can also omit the theme argument.\n st.plotly_chart(fig, theme=\"streamlit\", use_container_width=True)\nwith tab2:\n # Use the native Plotly theme.\n st.plotly_chart(fig, theme=None, use_container_width=True)\n```\n\nClick the tabs in the interactive app below to see the charts with the Streamlit theme enabled and disabled.\n\n\n\nIf you're wondering if your own customizations will still be taken into account, don't worry! You can still make changes to your chart configurations. In other words, although we now enable the Streamlit theme by default, you can overwrite it with custom colors or fonts. For example, if you want a chart line to be green instead of the default red, you can do it!\n\nHere's an example of an Plotly chart where a custom color scale is defined and reflected:\n\n```python\nimport plotly.express as px\nimport streamlit as st\n\nst.subheader(\"Define a custom colorscale\")\ndf = px.data.iris()\nfig = px.scatter(\n df,\n x=\"sepal_width\",\n y=\"sepal_length\",\n color=\"sepal_length\",\n color_continuous_scale=\"reds\",\n)\n\ntab1, tab2 = st.tabs([\"Streamlit theme (default)\", \"Plotly native theme\"])\nwith tab1:\n st.plotly_chart(fig, theme=\"streamlit\", use_container_width=True)\nwith tab2:\n st.plotly_chart(fig, theme=None, use_container_width=True)\n```\n\nNotice how the custom color scale is still reflected in the chart, even when the Streamlit theme is enabled ๐Ÿ‘‡\n\n\n\nFor many more examples of Plotly charts with and without the Streamlit theme, check out the [plotly.streamlit.app](https://plotly.streamlit.app)." - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.graphviz_chart", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts/st.altair_chart", - "content": "\n\n## Chart selections\n\n\n\n\n\n## Theming\n\nAltair charts are displayed using the Streamlit theme by default. This theme is sleek, user-friendly, and incorporates Streamlit's color palette. The added benefit is that your charts better integrate with the rest of your app's design.\n\nThe Streamlit theme is available from Streamlit 1.16.0 through the `theme=\"streamlit\"` keyword argument. To disable it, and use Altair's native theme, use `theme=None` instead.\n\nLet's look at an example of charts with the Streamlit theme and the native Altair theme:\n\n```python\nimport altair as alt\nfrom vega_datasets import data\n\nsource = data.cars()\n\nchart = alt.Chart(source).mark_circle().encode(\n x='Horsepower',\n y='Miles_per_Gallon',\n color='Origin',\n).interactive()\n\ntab1, tab2 = st.tabs([\"Streamlit theme (default)\", \"Altair native theme\"])\n\nwith tab1:\n # Use the Streamlit theme.\n # This is the default. So you can also omit the theme argument.\n st.altair_chart(chart, theme=\"streamlit\", use_container_width=True)\nwith tab2:\n # Use the native Altair theme.\n st.altair_chart(chart, theme=None, use_container_width=True)\n```\n\nClick the tabs in the interactive app below to see the charts with the Streamlit theme enabled and disabled.\n\n\n\nIf you're wondering if your own customizations will still be taken into account, don't worry! You can still make changes to your chart configurations. In other words, although we now enable the Streamlit theme by default, you can overwrite it with custom colors or fonts. For example, if you want a chart line to be green instead of the default red, you can do it!\n\nHere's an example of an Altair chart where manual color passing is done and reflected:\n\n\n\n```python\nimport altair as alt\nimport streamlit as st\nfrom vega_datasets import data\n\nsource = data.seattle_weather()\n\nscale = alt.Scale(\n domain=[\"sun\", \"fog\", \"drizzle\", \"rain\", \"snow\"],\n range=[\"#e7ba52\", \"#a7a7a7\", \"#aec7e8\", \"#1f77b4\", \"#9467bd\"],\n)\ncolor = alt.Color(\"weather:N\", scale=scale)\n\n# We create two selections:\n# - a brush that is active on the top panel\n# - a multi-click that is active on the bottom panel\nbrush = alt.selection_interval(encodings=[\"x\"])\nclick = alt.selection_multi(encodings=[\"color\"])\n\n# Top panel is scatter plot of temperature vs time\npoints = (\n alt.Chart()\n .mark_point()\n .encode(\n alt.X(\"monthdate(date):T\", title=\"Date\"),\n alt.Y(\n \"temp_max:Q\",\n title=\"Maximum Daily Temperature (C)\",\n scale=alt.Scale(domain=[-5, 40]),\n ),\n color=alt.condition(brush, color, alt.value(\"lightgray\")),\n size=alt.Size(\"precipitation:Q\", scale=alt.Scale(range=[5, 200])),\n )\n .properties(width=550, height=300)\n .add_selection(brush)\n .transform_filter(click)\n)\n\n# Bottom panel is a bar chart of weather type\nbars = (\n alt.Chart()\n .mark_bar()\n .encode(\n x=\"count()\",\n y=\"weather:N\",\n color=alt.condition(click, color, alt.value(\"lightgray\")),\n )\n .transform_filter(brush)\n .properties(\n width=550,\n )\n .add_selection(click)\n)\n\nchart = alt.vconcat(points, bars, data=source, title=\"Seattle Weather: 2012-2015\")\n\ntab1, tab2 = st.tabs([\"Streamlit theme (default)\", \"Altair native theme\"])\n\nwith tab1:\n st.altair_chart(chart, theme=\"streamlit\", use_container_width=True)\nwith tab2:\n st.altair_chart(chart, theme=None, use_container_width=True)\n```\n\n\n\nNotice how the custom colors are still reflected in the chart, even when the Streamlit theme is enabled ๐Ÿ‘‡\n\n\n\nFor many more examples of Altair charts with and without the Streamlit theme, check out the [altair.streamlit.app](https://altair.streamlit.app)." - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/charts", - "content": "# Chart elements\n\nStreamlit supports several different charting libraries, and our goal is to\ncontinually add support for more. Right now, the most basic library in our\narsenal is [Matplotlib](https://matplotlib.org/). Then there are also\ninteractive charting libraries like [Vega\nLite](https://vega.github.io/vega-lite/) (2D charts) and\n[deck.gl](https://github.com/uber/deck.gl) (maps and 3D charts). And\nfinally we also provide a few chart types that are \"native\" to Streamlit,\nlike `st.line_chart` and `st.area_chart`.\n\n## Simple chart elements\n\n\n\n\"screenshot\"\n\n

Simple area charts

\n\nDisplay an area chart.\n\n```python\nst.area_chart(my_data_frame)\n```\n\n
\n\n\"screenshot\"\n\n

Simple bar charts

\n\nDisplay a bar chart.\n\n```python\nst.bar_chart(my_data_frame)\n```\n\n
\n\n\"screenshot\"\n\n

Simple line charts

\n\nDisplay a line chart.\n\n```python\nst.line_chart(my_data_frame)\n```\n\n
\n\n\"screenshot\"\n\n

Simple scatter charts

\n\nDisplay a line chart.\n\n```python\nst.scatter_chart(my_data_frame)\n```\n\n
\n\n\"screenshot\"\n\n

Scatterplots on maps

\n\nDisplay a map with points on it.\n\n```python\nst.map(my_data_frame)\n```\n\n
\n
\n\n## Advanced chart elements\n\n\n\n\"screenshot\"\n\n

Matplotlib

\n\nDisplay a matplotlib.pyplot figure.\n\n```python\nst.pyplot(my_mpl_figure)\n```\n\n
\n\n\"screenshot\"\n\n

Altair

\n\nDisplay a chart using the Altair library.\n\n```python\nst.altair_chart(my_altair_chart)\n```\n\n
\n\n\"screenshot\"\n\n

Vega-Lite

\n\nDisplay a chart using the Vega-Lite library.\n\n```python\nst.vega_lite_chart(my_vega_lite_chart)\n```\n\n
\n\n\"screenshot\"\n\n

Plotly

\n\nDisplay an interactive Plotly chart.\n\n```python\nst.plotly_chart(my_plotly_chart)\n```\n\n
\n\n\"screenshot\"\n\n

Bokeh

\n\nDisplay an interactive Bokeh chart.\n\n```python\nst.bokeh_chart(my_bokeh_chart)\n```\n\n
\n\n\"screenshot\"\n\n

PyDeck

\n\nDisplay a chart using the PyDeck library.\n\n```python\nst.pydeck_chart(my_pydeck_chart)\n```\n\n
\n\n\"screenshot\"\n\n

GraphViz

\n\nDisplay a graph using the dagre-d3 library.\n\n```python\nst.graphviz_chart(my_graphviz_spec)\n```\n\n
\n
\n\n\n\n\n\n\"screenshot\"\n\n

Plost

\n\nA deceptively simple plotting library for Streamlit. Created by [@tvst](https://github.com/tvst).\n\n```python\nimport plost\nplost.line_chart(my_dataframe, x='time', y='stock_value', color='stock_name',)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

HiPlot

\n\nHigh dimensional Interactive Plotting. Created by [@facebookresearch](https://github.com/facebookresearch).\n\n```python\ndata = [{'dropout':0.1, 'lr': 0.001, 'loss': 10.0, 'optimizer': 'SGD'}, {'dropout':0.15, 'lr': 0.01, 'loss': 3.5, 'optimizer': 'Adam'}, {'dropout':0.3, 'lr': 0.1, 'loss': 4.5, 'optimizer': 'Adam'}]\nhip.Experiment.from_iterable(data).display()\n```\n\n
\n\n\n\n\"screenshot\"\n\n

ECharts

\n\nHigh dimensional Interactive Plotting. Created by [@andfanilo](https://github.com/andfanilo).\n\n```python\nfrom streamlit_echarts import st_echarts\nst_echarts(options=options)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Streamlit Folium

\n\nStreamlit Component for rendering Folium maps. Created by [@randyzwitch](https://github.com/randyzwitch).\n\n```python\nm = folium.Map(location=[39.949610, -75.150282], zoom_start=16)\nst_data = st_folium(m, width=725)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Spacy-Streamlit

\n\nspaCy building blocks and visualizers for Streamlit apps. Created by [@explosion](https://github.com/explosion).\n\n```python\nmodels = [\"en_core_web_sm\", \"en_core_web_md\"]\nspacy_streamlit.visualize(models, \"Sundar Pichai is the CEO of Google.\")\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Streamlit Agraph

\n\nA Streamlit Graph Vis, based on [react-grah-vis](https://github.com/crubier/react-graph-vis). Created by [@ChrisDelClea](https://github.com/ChrisDelClea).\n\n```python\nfrom streamlit_agraph import agraph, Node, Edge, Config\nagraph(nodes=nodes, edges=edges, config=config)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Streamlit Lottie

\n\nIntegrate [Lottie](https://lottiefiles.com/) animations inside your Streamlit app. Created by [@andfanilo](https://github.com/andfanilo).\n\n```python\nlottie_hello = load_lottieurl(\"https://assets5.lottiefiles.com/packages/lf20_V9t630.json\")\nst_lottie(lottie_hello, key=\"hello\")\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Plotly Events

\n\nMake Plotly charts interactive!. Created by [@null-jones](https://github.com/null-jones/).\n\n```python\nfig = px.line(x=[1], y=[1])\nselected_points = plotly_events(fig)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Streamlit Extras

\n\nA library with useful Streamlit extras. Created by [@arnaudmiribel](https://github.com/arnaudmiribel/).\n\n```python\nchart += get_annotations_chart(annotations=[(\"Mar 01, 2008\", \"Pretty good day for GOOG\"), (\"Dec 01, 2007\", \"Something's going wrong for GOOG & AAPL\"), (\"Nov 01, 2008\", \"Market starts again thanks to...\"), (\"Dec 01, 2009\", \"Small crash for GOOG after...\"),],)\nst.altair_chart(chart, use_container_width=True)\n```\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/custom-components/st.components.v1.iframe", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/custom-components/st.components.v1.declare_component", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/custom-components", - "content": "# Custom components\n\n\n\n\n\n

Declare a component

\n\nCreate and register a custom component.\n\n```python\nfrom st.components.v1 import declare_component\ndeclare_component(\n \"custom_slider\",\n \"/frontend\",\n)\n```\n\n
\n\n\n\n

HTML

\n\nDisplay an HTML string in an iframe.\n\n```python\nfrom st.components.v1 import html\nhtml(\n \"

Foo bar.

\"\n)\n```\n\n
\n\n\n\n

iframe

\n\nLoad a remote URL in an iframe.\n\n```python\nfrom st.components.v1 import iframe\niframe(\n \"docs.streamlit.io\"\n)\n```\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/custom-components/st.components.v1.html", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/user/st.login", - "content": "\n\nLearn more in [User authentication and information](/develop/concepts/connections/authentication).\n\n\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/user/st.logout", - "content": "\n\nLearn more in [User authentication and information](/develop/concepts/connections/authentication).\n\n\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/user/st.user", - "content": "\n\n### Community Cloud\n\nStarting from Streamlit version 1.42.0, you can't use `st.user` to retrieve a user's Community Cloud account email. To access user information, you must set up an identity provider and configure authentication (`[auth]`) in your app's secrets. Remember to update your identity provider's configuration and your app's secrets to allow your new domain. A list of [IP addresses](/deploy/streamlit-community-cloud/status#ip-addresses) used by Community Cloud is available if needed. An authentication-configured app counts as your single allowed private app.\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/user", - "content": "# Authentication and user info\n\nStreamlit provides native support for user authentication so you can personalize your apps. You can also directly read headers and cookies.\n\n\n\n\n

Log in a user

\n\n`st.login()` starts an authentication flow with an identity provider.\n\n```python\nst.login()\n```\n\n
\n\n\n

Log out a user

\n\n`st.logout()` removes a user's identity information.\n\n```python\nst.logout()\n```\n\n
\n\n\n

User info

\n\n`st.user` returns information about a logged-in user.\n\n```python\nif st.user.is_logged_in:\n st.write(f\"Welcome back, {st.user.name}!\")\n```\n\n
\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.toast", - "content": "\n\nWhen multiple toasts are generated, they will stack. Hovering over a toast will\nstop it from disappearing. When hovering ends, the toast will disappear after\nfour more seconds.\n\n```python\nimport streamlit as st\nimport time\n\nif st.button('Three cheers'):\n st.toast('Hip!')\n time.sleep(.5)\n st.toast('Hip!')\n time.sleep(.5)\n st.toast('Hooray!', icon='๐ŸŽ‰')\n```\n\n\n\nToast messages can also be updated. Assign `st.toast(my_message)` to a variable\nand use the `.toast()` method to update it. Note: if a toast has already disappeared\nor been dismissed, the update will not be seen.\n\n```python\nimport streamlit as st\nimport time\n\ndef cook_breakfast():\n msg = st.toast('Gathering ingredients...')\n time.sleep(1)\n msg.toast('Cooking...')\n time.sleep(1)\n msg.toast('Ready!', icon = \"๐Ÿฅž\")\n\nif st.button('Cook breakfast'):\n cook_breakfast()\n```\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.success", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.status", - "content": "\n\n" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.progress", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.balloons", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.info", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.spinner", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.error", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.exception", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.warning", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status/st.snow", - "content": "" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/status", - "content": "# Display progress and status\n\nStreamlit provides a few methods that allow you to add animation to your\napps. These animations include progress bars, status messages (like\nwarnings), and celebratory balloons.\n\n## Animated status elements\n\n\n\n\n\"screenshot\"\n\n

Progress bar

\n\nDisplay a progress bar.\n\n```python\nfor i in range(101):\n st.progress(i)\n do_something_slow()\n```\n\n
\n\n\n\"screenshot\"\n\n

Spinner

\n\nTemporarily displays a message while executing a block of code.\n\n```python\nwith st.spinner(\"Please wait...\"):\n do_something_slow()\n```\n\n
\n\n\n\"screenshot\"\n\n

Status container

\n\nDisplay output of long-running tasks in a container.\n\n```python\nwith st.status('Running'):\n do_something_slow()\n```\n\n
\n\n\n\"screenshot\"\n\n

Toast

\n\nBriefly displays a toast message in the bottom-right corner.\n\n```python\nst.toast('Butter!', icon='๐Ÿงˆ')\n```\n\n
\n\n\n\"screenshot\"\n\n

Balloons

\n\nDisplay celebratory balloons!\n\n```python\nst.balloons()\n```\n\n
\n\n\n\"screenshot\"\n\n

Snowflakes

\n\nDisplay celebratory snowflakes!\n\n```python\nst.snow()\n```\n\n
\n
\n\n## Simple callout messages\n\n\n\n\n\"screenshot\"\n\n

Success box

\n\nDisplay a success message.\n\n```python\nst.success(\"Match found!\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Info box

\n\nDisplay an informational message.\n\n```python\nst.info(\"Dataset is updated every day at midnight.\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Warning box

\n\nDisplay warning message.\n\n```python\nst.warning(\"Unable to fetch image. Skipping...\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Error box

\n\nDisplay error message.\n\n```python\nst.error(\"We encountered an error\")\n```\n\n
\n\n\n\"screenshot\"\n\n

Exception output

\n\nDisplay an exception.\n\n```python\ne = RuntimeError(\"This is an exception of type RuntimeError\")\nst.exception(e)\n```\n\n
\n
\n\n\n\n\n\n\"screenshot\"\n\n

Stqdm

\n\nThe simplest way to handle a progress bar in streamlit app. Created by [@Wirg](https://github.com/Wirg).\n\n```python\nfrom stqdm import stqdm\n\nfor _ in stqdm(range(50)):\n sleep(0.5)\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Custom notification box

\n\nA custom notification box with the ability to close it out. Created by [@Socvest](https://github.com/Socvest).\n\n```python\nfrom streamlit_custom_notification_box import custom_notification_box\n\nstyles = {'material-icons':{'color': 'red'}, 'text-icon-link-close-container': {'box-shadow': '#3896de 0px 4px'}, 'notification-text': {'':''}, 'close-button':{'':''}, 'link':{'':''}}\ncustom_notification_box(icon='info', textDisplay='We are almost done with your registration...', externalLink='more info', url='#', styles=styles, key=\"foo\")\n```\n\n
\n\n\n\n\"screenshot\"\n\n

Streamlit Extras

\n\nA library with useful Streamlit extras. Created by [@arnaudmiribel](https://github.com/arnaudmiribel/).\n\n```python\nfrom streamlit_extras.let_it_rain import rain\n\nrain(emoji=\"๐ŸŽˆ\", font_size=54,\n falling_speed=5, animation_length=\"infinite\",)\n```\n\n
\n\n
" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/cli/help", - "content": "## `$ streamlit help`\n\nPrint the available commands for the Streamlit CLI tool. This command is equivalent to executing `streamlit --help`.\n\n### Syntax\n\n```\nstreamlit help\n```" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/cli/init", - "content": "## `$ streamlit init`\n\nThis command creates the files for a new Streamlit app.\n\n### Syntax\n\n```\nstreamlit init \n```\n\n### Arguments\n\n`` (Optional): The directory location of the new project. If no directory is provided, the current working directory will be used.\n\n### Examples\n\n#### Example 1: Create project files the current working directory\n\n1. In your current working directory (CWD), execute the following:\n\n ```bash\n streamlit init\n ```\n\n Streamlit creates the following files:\n ```\n CWD/\n โ”œโ”€โ”€ requirements.txt\n โ””โ”€โ”€ streamlit_app.py\n ```\n\n2. In your terminal, Streamlit prompts, `โ“ Run the app now? [Y/n]`. Enter `Y` for yes.\n\n This is equivalent to executing `streamlit run streamlit_app.py` from your current working directory.\n\n3. Begin editing your `streamlit_app.py` file and save your changes.\n\n#### Example 2: Create project files in another directory\n\n1. In your current working directory (CWD), execute the following:\n\n ```bash\n streamlit init project\n ```\n\n Streamlit creates the following files:\n ```\n CWD/\n โ””โ”€โ”€ project/\n โ”œโ”€โ”€ requirements.txt\n โ””โ”€โ”€ streamlit_app.py\n ```\n\n2. In your terminal, Streamlit prompts, `โ“ Run the app now? [Y/n]`. Enter `Y` for yes.\n\n This is equivalent to executing `streamlit run project/streamlit_app.py` from your current working directory.\n\n3. Begin editing your `streamlit_app.py` file and save your changes." - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/cli/config", - "content": "## `$ streamlit config show`\n\nPrint all the available configuration options, including their descriptions, default values, and current values. For more information about configuration options, see [`config.toml`](/develop/api-reference/configuration/config.toml).\n\n### Syntax\n\n```\nstreamlit config show\n```" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/cli/version", - "content": "## `$ streamlit version`\n\nPrint Streamlit's version number. This command is equivalent to executing `streamlit --version`.\n\n### Syntax\n\n```\nstreamlit version\n```" - }, - { - "url": "https://docs.streamlit.io/develop/api-reference/cli/hello", - "content": "## `$ streamlit hello`\n\nRun the Hello app, an example Streamlit app included with the Streamlit library.\n\n### Syntax\n\n```\nstreamlit hello\n```\n\n### Options\n\nThe `hello` command accepts configuration options (just like the `run` command does). Configuration options are passed in the form of `--
.