Skip to content

Commit

Permalink
Add fragment tutorials and release follow ups (#1033)
Browse files Browse the repository at this point in the history
* Add fragement-rerun tutorial and stubs for others

* Update fragment rerun tutorial

* Format image

* Add multiple-container-fragment tutorial

* Update multiple-container-fragment tutorial

* Update auto-rerun fragment tutorial

* Update tutorial index

* Update fragment tutorial images

* Edits from tutorial review

* Embed the example apps into the fragment tutorials

* Link fragments overview to fragment tutorials
  • Loading branch information
sfc-gh-dmatthews authored Apr 12, 2024
1 parent 992535a commit f1f3a68
Show file tree
Hide file tree
Showing 10 changed files with 1,183 additions and 2 deletions.
8 changes: 6 additions & 2 deletions content/develop/concepts/architecture/fragments.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ Streamlit ignores fragment return values during fragment reruns, so defining ret
- 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.

To prevent elements from accumulating in outside containers, use [`st.empty`](/develop/api-reference/layout/st.empty) containers. If you need to trigger a full-script rerun from inside a fragment, call [`st.rerun`](/develop/api-reference/execution-flow/st.rerun).
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

Expand All @@ -98,6 +100,8 @@ def auto_function():
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
Expand All @@ -114,7 +118,7 @@ A form batches user input without interaction between any widgets. A fragment im
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. Fragment reruns happen at the end of a script rerun. A fragment is a _repeatable postfix_ to your script.
- **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).

Expand Down
8 changes: 8 additions & 0 deletions content/develop/tutorials/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ Our tutorials include step-by-step examples of building different types of apps

<TileContainer layout="list">

<RefCard href="/develop/tutorials/execution-flow">

<h5>Use core features to work with Streamlit's execution model</h5>

Build simple apps and walk through examples to learn about Streamlit's core features and execution model.

</RefCard>

<RefCard href="/develop/tutorials/databases">

<h5>Connect to data sources</h5>
Expand Down
36 changes: 36 additions & 0 deletions content/develop/tutorials/execution-flow/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Use core features to work with Streamlit's execution model
slug: /develop/tutorials/execution-flow
---

# Use core features to work with Streamlit's execution model

## Fragments

<TileContainer layout="list">

<RefCard href="/develop/tutorials/execution-flow/trigger-a-full-script-rerun-from-a-fragment">

<h5>Trigger a full-script rerun from inside a fragment</h5>

Call `st.rerun` from inside a fragment to trigger a full-script rerun when a condition is met.

</RefCard>

<RefCard href="/develop/tutorials/execution-flow/create-a-multiple-container-fragment">

<h5>Create a fragment across multiple containers</h5>

Use a fragment to write to multiple containers across your app.

</RefCard>

<RefCard href="/develop/tutorials/execution-flow/start-and-stop-fragment-auto-reruns">

<h5>Start and stop a streaming fragment</h5>

Use a fragment to live-stream data. Use a button to start and stop the live-streaming.

</RefCard>

</TileContainer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
---
title: Create a fragment across multiple containers
slug: /develop/tutorials/execution-flow/create-a-multiple-container-fragment
---

# Create a fragment across multiple containers

Streamlit lets you turn functions into [fragments](/develop/concepts/architecture/fragments), which can rerun independently from the full script. If your fragment doesn't write to outside containers, Streamlit will clear and redraw all the fragment elements with each fragment rerun. However, if your fragment _does_ write elements to outside containers, Streamlit will not clear those elements during a fragment rerun. Instead, those elements accumulate with each fragment rerun until the next full-script rerun. If you want a fragment to update multiple containers in your app, use [`st.empty()`](/develop/api-reference/layout/st.empty) containers to prevent accumulating elements.

## Applied concepts

- Use fragments to run two independent processes separately.
- Distribute a fragment across multiple containers.

## Prerequisites

**`streamlit>=1.33.0`**

- This tutorial uses fragments, which require Streamlit version 1.33.0 or later.
- This tutorial assumes you have a clean working directory called `your-repository`.
- You should have a basic understanding of fragments and `st.empty()`.

## Summary

In this toy example, you'll build an app with six containers. Three containers will have orange cats. The other three containers will have black cats. You'll have three buttons in the sidebar: "**Herd the black cats**," "**Herd the orange cats**," and "**Herd all the cats**." Since herding cats is slow, you'll use fragments to help Streamlit run the associated processes efficiently. You'll create two fragments, one for the black cats and one for the orange cats. Since the buttons will be in the sidebar and the fragments will update containers in the main body, you'll use a trick with `st.empty()` to ensure you don't end up with too many cats in your app (if it's even possible to have too many cats). 😻

Here's a look at what you'll build:

<Collapse title="Complete code" expanded={false}>

```python
import streamlit as st
import time

st.title("Cats!")

row1 = st.columns(3)
row2 = st.columns(3)

grid = [col.container(height=200) for col in row1 + row2]
safe_grid = [card.empty() for card in grid]


def black_cats():
time.sleep(1)
st.title("🐈‍⬛ 🐈‍⬛")
st.markdown("🐾 🐾 🐾 🐾")


def orange_cats():
time.sleep(1)
st.title("🐈 🐈")
st.markdown("🐾 🐾 🐾 🐾")


@st.experimental_fragment
def herd_black_cats(card_a, card_b, card_c):
st.button("Herd the black cats")
container_a = card_a.container()
container_b = card_b.container()
container_c = card_c.container()
with container_a:
black_cats()
with container_b:
black_cats()
with container_c:
black_cats()


@st.experimental_fragment
def herd_orange_cats(card_a, card_b, card_c):
st.button("Herd the orange cats")
container_a = card_a.container()
container_b = card_b.container()
container_c = card_c.container()
with container_a:
orange_cats()
with container_b:
orange_cats()
with container_c:
orange_cats()


with st.sidebar:
herd_black_cats(grid[0].empty(), grid[2].empty(), grid[4].empty())
herd_orange_cats(grid[1].empty(), grid[3].empty(), grid[5].empty())
st.button("Herd all the cats")
```

</Collapse>

<Cloud src="https://doc-tutorial-fragment-multiple-container.streamlit.app/?embed=true" height="650" />

## Build the example

### Initialize your app

1. In `your_repository`, create a file named `app.py`.
1. In a terminal, change directories to `your_repository` and start your app.

```bash
streamlit run app.py
```

Your app will be blank since you still need to add code.

1. In `app.py`, write the following:

```python
import streamlit as st
import time
```

You'll use `time.sleep()` to slow things down and see the fragments working.

1. Save your `app.py` file and view your running app.
1. Click "**Always rerun**" or hit your "**A**" key in your running app.

Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code.

### Frame out your app's containers

1. Add a title to your app and two rows of three containers.

```python
st.title("Cats!")

row1 = st.columns(3)
row2 = st.columns(3)

grid = [col.container(height=200) for col in row1 + row2]
```

Save your file to see your updated preview.

1. Define a helper function to draw two black cats.

```python
def black_cats():
time.sleep(1)
st.title("🐈‍⬛ 🐈‍⬛")
st.markdown("🐾 🐾 🐾 🐾")
```

This function represents "herding two cats" and uses `time.sleep()` to simulate a slower process. You will use this to draw two cats in one of your grid cards later on.

1. Define another helper function to draw two orange cats.

```python
def orange_cats():
time.sleep(1)
st.title("🐈 🐈")
st.markdown("🐾 🐾 🐾 🐾")
```

1. (Optional) Test out your functions by calling each one within a grid card.

```python
with grid[0]:
black_cats()
with grid[1]:
orange_cats()
```

Save your `app.py` file to see the preview. Delete these four lines when finished.

### Define your fragments

Since each fragment will span across the sidebar and three additional containers, you'll use the sidebar to hold the main body of the fragment and pass the three containers as function arguments.

1. Use an [`@st.experimental_fragment`](/develop/api-reference/execution-flow/st.fragment) decorator and start your black-cat fragment definition.

```python
@st.experimental_fragment
def herd_black_cats(card_a, card_b, card_c):
```

1. Add a button for rerunning this fragment.

```python
st.button("Herd the black cats")
```

1. Write to each container using your helper function.

```python
with card_a:
black_cats()
with card_b:
black_cats()
with card_c:
black_cats()
```

**This code above will not behave as desired, but you'll explore and correct this in the following steps.**

1. Test out your code. Call your fragment function in the sidebar.

```python
with st.sidebar:
herd_black_cats(grid[0], grid[2], grid[4])
```

Save your file and try using the button in the sidebar. More and more cats are appear in the cards with each fragment rerun! This is the expected behavior when fragments write to outside containers. To fix this, you will pass `st.empty()` containers to your fragment function.

![Example Streamlit app showing accumulating elements when a fragment writes to outside containers](/images/tutorials/fragment-multiple-containers-tutorial-app-duplicates.jpg)

1. Delete the lines of code from the previous two steps.

1. To prepare for using `st.empty()` containers, correct your cat-herding function as follows. After the button, define containers to place in the `st.empty()` cards you'll pass to your fragment.

```python
container_a = card_a.container()
container_b = card_b.container()
container_c = card_c.container()
with container_a:
black_cats()
with container_b:
black_cats()
with container_c:
black_cats()
```

In this new version, `card_a`, `card_b`, and `card_c` will be `st.empty()` containers. You create `container_a`, `container_b`, and `container_c` to allow Streamlit to draw multiple elements on each grid card.

1. Similarly define your orange-cat fragment function.

```python
@st.experimental_fragment
def herd_orange_cats(card_a, card_b, card_c):
st.button("Herd the orange cats")
container_a = card_a.container()
container_b = card_b.container()
container_c = card_c.container()
with container_a:
orange_cats()
with container_b:
orange_cats()
with container_c:
orange_cats()
```

### Put the functions together together to create an app

1. Call both of your fragments in the sidebar.

```python
with st.sidebar:
herd_black_cats(grid[0].empty(), grid[2].empty(), grid[4].empty())
herd_orange_cats(grid[1].empty(), grid[3].empty(), grid[5].empty())
```

By creating `st.empty()` containers in each card and passing them to your fragments, you prevent elements from accumulating in the cards with each fragment rerun. If you create the `st.empty()` containers earlier in your app, full-script reruns will clear the orange-cat cards while (first) rendering the black-cat cards.

1. Include a button outside of your fragments. When clicked, the button will trigger a full-script rerun since you're calling its widget function outside of any fragment.

```python
st.button("Herd all the cats")
```

1. Save your file and try out the app! When you click "**Herd the black cats**" or "**Herd the orange cats**," Streamlit will only redraw the three related cards. When you click "**Herd all the cats**," Streamlit redraws all six cards.
Loading

0 comments on commit f1f3a68

Please sign in to comment.