diff --git a/.gitignore b/.gitignore index 2d7f31fab..9e4ed1ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,6 @@ out/ public/pdf/pages/ .idea/ secrets.toml + +# No need to store this in Git. This gets autogenerated on export. +public/llms-full.txt diff --git a/README.md b/README.md index 23e7727a3..1a02adb75 100644 --- a/README.md +++ b/README.md @@ -265,9 +265,9 @@ To preview your changes, refresh your browser tab and visit the edited page! ### Add a new docstring to the API Reference -Any time a new version of Streamlit is released, the docstrings stored in `python/streamlit.json` have to be updated by running `make docstrings` . This will build the necessary Docker image, and update the file with the documentation for the latest release on PyPi. +Any time a new version of Streamlit is released, the docstrings stored in `python/streamlit_api.json` have to be updated by running `make docstrings` . This will build the necessary Docker image, and update the file with the documentation for the latest release on PyPi. -If you need to regenerate all function signatures, across all versions, delete the content in `python/streamlit.json`, leaving the file in place, and run `make docstrings`. This will systematically install each version of streamlit, and generate the necessary function signatures in `streamlit.json`. +If you need to regenerate all function signatures, across all versions, delete the content in `python/streamlit_api.json`, leaving the file in place, and run `make docstrings`. This will systematically install each version of streamlit, and generate the necessary function signatures in `streamlit_api.json`. Suppose a new Streamlit release includes a `st.my_chart` function that you want to include in the "Chart elements" section of the API Reference: diff --git a/makefile b/makefile index 60b7854ab..b030db127 100644 --- a/makefile +++ b/makefile @@ -11,11 +11,11 @@ start: npm run start .PHONY: export -export: llms +export: llms-full npm run export -.PHONY: llms -llms: +.PHONY: llms-full +llms-full: uv run python/generate_llms_full_txt.py .PHONY: lint diff --git a/next.config.js b/next.config.js index 659c804b8..2102f3d38 100644 --- a/next.config.js +++ b/next.config.js @@ -7,11 +7,11 @@ const IS_DEV = process.env.NODE_ENV === "development"; const PYTHON_DIRECTORY = path.join(process.cwd(), "python/"); const jsonDocstrings = fs.readFileSync( - path.join(PYTHON_DIRECTORY, "streamlit.json"), + path.join(PYTHON_DIRECTORY, "streamlit_api.json"), "utf8", ); const jsonPlatformNotes = fs.readFileSync( - path.join(PYTHON_DIRECTORY, "snowflake.json"), + path.join(PYTHON_DIRECTORY, "snowflake_api.json"), "utf8", ); @@ -78,7 +78,7 @@ const CSP_HEADER = [ "default-src 'none';", "font-src 'self';", "form-action 'self';", - "img-src", + "img-src", "'self'", "data:", "https:", diff --git a/public/llms-full.txt b/public/llms-full.txt deleted file mode 100644 index cfa8d0030..000000000 --- a/public/llms-full.txt +++ /dev/null @@ -1,25007 +0,0 @@ -# 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( - """ - -