Skip to content

Commit

Permalink
Streamlit migration Guide (#5027)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcSkovMadsen authored Jul 2, 2023
1 parent 7029343 commit f7f9ea9
Show file tree
Hide file tree
Showing 35 changed files with 1,078 additions and 18 deletions.
Binary file added doc/_static/images/panel_dynamic_layout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/panel_hello_world.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/panel_layout_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/panel_markdown_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/panel_mpl_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/panel_widgets_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/slides_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/slides_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_dymamic_layout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_hello_world.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_layout_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_mpl_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_widgets_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/how_to/caching/manual.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Manually Cache

This guide addresses how to manually cache data and objects on global object - `pn.state.cache`.
This guide addresses how to cache data and objects globally across user sessions - `pn.state.cache`.

---

Expand Down
20 changes: 20 additions & 0 deletions doc/how_to/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,25 @@ How to run Panel applications entirely in the browser using WebAssembly (Wasm),

::::

## Migrate to Panel

::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} Migrate from Streamlit
:link: migrate/streamlit/index
:link-type: doc

```{image} https://assets.holoviz.org/panel/background/comparisons/streamlit_logo.png
:width: 125px
:align: center
:name: Streamlit
```

:::

::::

```{toctree}
:titlesonly:
:hidden:
Expand All @@ -237,4 +256,5 @@ manage_session_tasks
test_and_debug
prepare_to_share
share_your_work
migrate_to_panel
```
8 changes: 6 additions & 2 deletions doc/how_to/interactivity/bind_generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import panel as pn
pn.extension()
```

Let us say we have some action that is triggered by a widget, such as a button, and while we are computing the results we want to provide feedback to the user. Using imperative programming this involves writing callbacks that update the current state of our components. This is complex and really we prefer to write reactive components. This is where reactive generators come in.
Let us say we have some action that is triggered by a widget, such as a button, and while we are computing the results we want to provide feedback to the user. Using imperative programming this involves writing callbacks that update the current state of our components. This is complex and really we prefer to write reactive components. This is where *generator functions* come in.

:::{important}
A *generator* function is a function that use `yield` to *return* results as they are produced during the execution. It is not allowed to `return` anything, but can use `return` to *break* the execution. For an introduction to *generator functions* check out [Real Python | Introduction to generator functions](https://realpython.com/introduction-to-python-generators/).
:::

In the example below we add a `Button` to trigger some calculation. Initially the calculation hasn't yet run, so we check the value provided by the `Button` indicating whether a calculation has been triggered and while it is `False` we `yield` some text and `return`. However, when the `Button` is clicked the function is called again with `run=True` and we kick off some calculation. As this calculation progresses we can `yield` updates and then once the calculation is successful we `yield` again with the final result:

Expand All @@ -33,7 +37,7 @@ def runner(run):
pn.Row(run, pn.bind(runner, run))
```

This provides a powerful mechanism for providing incrememental updates as we load some data, perform some data processing processing.
This provides a powerful mechanism for providing incrememental updates as we load some data, perform some data processing, etc.

This can also be combined with asynchronous processing, e.g. to dynamically stream in new data as it arrives:

Expand Down
71 changes: 71 additions & 0 deletions doc/how_to/migrate/streamlit/activity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Show Activity

Panel supports many ways of indicating activity

- Indicators. See the [Indicators Section](../../../reference/index.md#indicators) of the [Component Gallery](../../../reference/index.md).
- `disabled`/ `loading` parameters on Panel components
- `loading_indicator` parameter for `pn.panel` or `pn.config`. If `True` a loading indicator will be shown on your *bound functions* when they are re-run.

## Example

The example below showcases some of the ways Panel can show activity.

```python
import panel as pn

pn.extension(sizing_mode="stretch_width", template="bootstrap")

SPIN_CSS = """
@keyframes icon-rotation {
from {transform: rotate(0deg);} to {transform: rotate(359deg);}
}
.bk-TablerIcon {animation: icon-rotation 2s infinite linear;}
"""

pn.Row(
pn.Column(
"## Loading Spinner",
pn.Column(
pn.indicators.LoadingSpinner(value=False, height=25, width=25),
pn.indicators.LoadingSpinner(
value=True, height=25, width=25, color="secondary"
),
),
),
pn.Column(
"## Progress",
pn.Column(
pn.indicators.Progress(
name="Progress", value=20, width=150, bar_color="secondary"
),
pn.indicators.Progress(
name="Progress", active=True, width=150, bar_color="secondary"
),
),
),
pn.Column(
"## Disabled",
pn.Column(
pn.widgets.Button(name="Loading", icon="progress", disabled=True),
pn.widgets.Button(
name="Loading", icon="progress", disabled=True, stylesheets=[SPIN_CSS]
),
),
),
pn.Column(
"## Loading",
pn.Column(
pn.widgets.Button(name="Loading", loading=True, button_type="primary"),
pn.WidgetBox(
pn.widgets.Checkbox(name="Checked", value=True),
pn.widgets.Button(name="Submit", button_type="primary"),
loading=True, margin=(10,10),
),
),
),
).servable()
```

![Show Activity](https://user-images.githubusercontent.com/42288570/246325570-11484dd6-4523-401f-b709-6c0cc7996410.gif)

To learn more about migrating activity indicators check out the [Migrate Streamlit Interactivity Guide](interactivity.md).
108 changes: 108 additions & 0 deletions doc/how_to/migrate/streamlit/caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Improve the performance with Caching

One of the key concepts in Streamlit is *caching*.

In Streamlit

- your script is run once when a user visits the page.
- your script is rerun *top to bottom* on user interactions.

Thus with Streamlit you *must use* caching to make the user experience nice and fast.

In Panel

- your script is run once when a user visits the page.
- only *specific, bound functions* are rerun on user interactions.

Thus with Panel you *may use* caching to to make the user experience nice and fast.

In Panel you use `pn.cache` to speed up your apps. Check out the [Cache How-To Guides](../caching/index.md) for more details.

---

## Migration Steps

To migrate

- replace `st.cache_data` and `st.cache_resource` with `pn.cache` on long running
- functions that are run when your page loads
- *bound functions*

## Example

### Cache Example

#### Streamlit Cache Example

```python
from time import sleep

import numpy as np
import streamlit as st
from matplotlib.figure import Figure

@st.cache_data
def get_data():
print("get_data func")
sleep(1.0)
return np.random.normal(1, 1, size=100)

@st.cache_data(hash_funcs={Figure: lambda _: None})
def plot(data, bins):
print("plot func", bins)
sleep(2)
fig = Figure(figsize=(8,4))
ax = fig.subplots()
ax.hist(data, bins=bins)
return fig

data = get_data()
bins = st.slider(value=20, min_value=10, max_value=30, step=1, label="Bins")
st.pyplot(plot(data, bins))
```

I've added `sleep` statements to make the functions more *expensive*.

#### Panel Cache Example

```python
from time import sleep

import numpy as np
import panel as pn
from matplotlib.figure import Figure

@pn.cache
def get_data():
print("get_data func")
sleep(1.0)
return np.random.normal(1, 1, size=100)

@pn.cache
def plot(data, bins):
print("plot func", bins)
sleep(2)
fig = Figure(figsize=(8,4))
ax = fig.subplots()
ax.hist(data, bins=bins)
return fig

pn.extension(sizing_mode="stretch_width", template="bootstrap")

data = get_data()
bins = pn.widgets.IntSlider(value=20, start=10, end=30, step=1)
bplot = pn.bind(plot, data, bins)
pn.Column(bins, pn.panel(bplot, loading_indicator=True)).servable()
```

![Panel Cache Example](https://assets.holoviz.org/panel/gifs/panel_cache_example.gif)

You can also use `pn.cache` as an function. I.e. as

```python
plot = pn.cache(plot)
```

Using `pn.cache` as a function can help you keep your business logic
(`data` and `plot` function) and your caching logic (when and how to apply caching) separate. This
can help you reusable and maintainable code.
62 changes: 62 additions & 0 deletions doc/how_to/migrate/streamlit/get_started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Get Started migrating from Streamlit to Panel

This guide addresses the basics of migrating from Streamlit to Panel.

---

## Migration Steps

You should replace:

- `import streamlit as st` with `import panel as pn` and
- `st.write` with `pn.panel`.

You will have to:

- add `pn.extension` to configure your Panel application via optional arguments like `sizing_mode` and `template`.
- add `.servable` to the Panel objects you want to include in your apps *template* when served as
a web app.

For production you will also have to migrate some of your app configuration to `panel serve` [command line options](../server/commandline.md) or environment variables.

You **won't** have to provide your email or opt out of telemetry data collection. We have never collected or had plans to collect telemetry data from our users apps.

## Examples

### Hello World

Lets show how to convert a *Hello World* application.

### Streamlit Hello World Example

```python
import streamlit as st

st.write("Hello World")
```

You *run* and *show* the app with *autoreload* via

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

![Streamlit Hello World Example](../../../_static/images/streamlit_hello_world.png)

### Panel Hello World Example

```python
import panel as pn

pn.extension(sizing_mode="stretch_width", template="bootstrap")

pn.panel("Hello World").servable()
```

You *serve* and *show* (i.e. open) the app in your browser with *autoreload* via

```bash
panel serve app.py --autoreload --show
```

![Panel Hello World Example](../../../_static/images/panel_hello_world.png)
Loading

0 comments on commit f7f9ea9

Please sign in to comment.