1238 lines
47 KiB
Python
1238 lines
47 KiB
Python
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||
#
|
||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
# You may obtain a copy of the License at
|
||
#
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
|
||
from __future__ import annotations
|
||
|
||
import io
|
||
import os
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
from textwrap import dedent
|
||
from typing import (
|
||
TYPE_CHECKING,
|
||
BinaryIO,
|
||
Final,
|
||
Literal,
|
||
TextIO,
|
||
TypeAlias,
|
||
cast,
|
||
)
|
||
|
||
from streamlit import runtime
|
||
from streamlit.elements.lib.form_utils import current_form_id, is_in_form
|
||
from streamlit.elements.lib.layout_utils import LayoutConfig, Width, validate_width
|
||
from streamlit.elements.lib.policies import check_widget_policies
|
||
from streamlit.elements.lib.utils import (
|
||
Key,
|
||
compute_and_register_element_id,
|
||
save_for_app_testing,
|
||
to_key,
|
||
)
|
||
from streamlit.errors import (
|
||
StreamlitAPIException,
|
||
StreamlitMissingPageLabelError,
|
||
StreamlitPageNotFoundError,
|
||
)
|
||
from streamlit.file_util import get_main_script_directory, normalize_path_join
|
||
from streamlit.navigation.page import StreamlitPage
|
||
from streamlit.proto.Button_pb2 import Button as ButtonProto
|
||
from streamlit.proto.DownloadButton_pb2 import DownloadButton as DownloadButtonProto
|
||
from streamlit.proto.LinkButton_pb2 import LinkButton as LinkButtonProto
|
||
from streamlit.proto.PageLink_pb2 import PageLink as PageLinkProto
|
||
from streamlit.runtime.metrics_util import gather_metrics
|
||
from streamlit.runtime.pages_manager import PagesManager
|
||
from streamlit.runtime.scriptrunner import ScriptRunContext, get_script_run_ctx
|
||
from streamlit.runtime.state import (
|
||
WidgetArgs,
|
||
WidgetCallback,
|
||
WidgetKwargs,
|
||
register_widget,
|
||
)
|
||
from streamlit.string_util import validate_icon_or_emoji
|
||
from streamlit.url_util import is_url
|
||
from streamlit.util import in_sidebar
|
||
|
||
if TYPE_CHECKING:
|
||
from streamlit.delta_generator import DeltaGenerator
|
||
|
||
FORM_DOCS_INFO: Final = """
|
||
|
||
For more information, refer to the
|
||
[documentation for forms](https://docs.streamlit.io/develop/api-reference/execution-flow/st.form).
|
||
"""
|
||
|
||
DownloadButtonDataType: TypeAlias = str | bytes | TextIO | BinaryIO | io.RawIOBase
|
||
|
||
|
||
@dataclass
|
||
class ButtonSerde:
|
||
def serialize(self, v: bool) -> bool:
|
||
return bool(v)
|
||
|
||
def deserialize(self, ui_value: bool | None) -> bool:
|
||
return ui_value or False
|
||
|
||
|
||
class ButtonMixin:
|
||
@gather_metrics("button")
|
||
def button(
|
||
self,
|
||
label: str,
|
||
key: Key | None = None,
|
||
help: str | None = None,
|
||
on_click: WidgetCallback | None = None,
|
||
args: WidgetArgs | None = None,
|
||
kwargs: WidgetKwargs | None = None,
|
||
*, # keyword-only arguments:
|
||
type: Literal["primary", "secondary", "tertiary"] = "secondary",
|
||
icon: str | None = None,
|
||
disabled: bool = False,
|
||
use_container_width: bool | None = None,
|
||
width: Width = "content",
|
||
) -> bool:
|
||
r"""Display a button widget.
|
||
|
||
Parameters
|
||
----------
|
||
label : str
|
||
A short label explaining to the user what this button is for.
|
||
The label can optionally contain GitHub-flavored Markdown of the
|
||
following types: Bold, Italics, Strikethroughs, Inline Code, Links,
|
||
and Images. Images display like icons, with a max height equal to
|
||
the font height.
|
||
|
||
Unsupported Markdown elements are unwrapped so only their children
|
||
(text contents) render. Display unsupported elements as literal
|
||
characters by backslash-escaping them. E.g.,
|
||
``"1\. Not an ordered list"``.
|
||
|
||
See the ``body`` parameter of |st.markdown|_ for additional,
|
||
supported Markdown directives.
|
||
|
||
.. |st.markdown| replace:: ``st.markdown``
|
||
.. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
|
||
|
||
key : str or int
|
||
An optional string or integer to use as the unique key for the widget.
|
||
If this is omitted, a key will be generated for the widget
|
||
based on its content. No two widgets may have the same key.
|
||
|
||
help : str or None
|
||
A tooltip that gets displayed when the button is hovered over. If
|
||
this is ``None`` (default), no tooltip is displayed.
|
||
|
||
The tooltip can optionally contain GitHub-flavored Markdown,
|
||
including the Markdown directives described in the ``body``
|
||
parameter of ``st.markdown``.
|
||
|
||
on_click : callable
|
||
An optional callback invoked when this button is clicked.
|
||
|
||
args : list or tuple
|
||
An optional list or tuple of args to pass to the callback.
|
||
|
||
kwargs : dict
|
||
An optional dict of kwargs to pass to the callback.
|
||
|
||
type : "primary", "secondary", or "tertiary"
|
||
An optional string that specifies the button type. This can be one
|
||
of the following:
|
||
|
||
- ``"primary"``: The button's background is the app's primary color
|
||
for additional emphasis.
|
||
- ``"secondary"`` (default): The button's background coordinates
|
||
with the app's background color for normal emphasis.
|
||
- ``"tertiary"``: The button is plain text without a border or
|
||
background for subtlety.
|
||
|
||
icon : str or None
|
||
An optional emoji or icon to display next to the button label. If ``icon``
|
||
is ``None`` (default), no icon is displayed. If ``icon`` is a
|
||
string, the following options are valid:
|
||
|
||
- A single-character emoji. For example, you can set ``icon="🚨"``
|
||
or ``icon="🔥"``. Emoji short codes are not supported.
|
||
|
||
- An icon from the Material Symbols library (rounded style) in the
|
||
format ``":material/icon_name:"`` where "icon_name" is the name
|
||
of the icon in snake case.
|
||
|
||
For example, ``icon=":material/thumb_up:"`` will display the
|
||
Thumb Up icon. Find additional icons in the `Material Symbols \
|
||
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
||
font library.
|
||
|
||
disabled : bool
|
||
An optional boolean that disables the button if set to ``True``.
|
||
The default is ``False``.
|
||
|
||
use_container_width : bool
|
||
Whether to expand the button's width to fill its parent container.
|
||
If ``use_container_width`` is ``False`` (default), Streamlit sizes
|
||
the button to fit its contents. If ``use_container_width`` is
|
||
``True``, the width of the button matches its parent container.
|
||
|
||
In both cases, if the contents of the button are wider than the
|
||
parent container, the contents will line wrap.
|
||
|
||
.. deprecated::
|
||
``use_container_width`` is deprecated and will be removed in a
|
||
future release. For ``use_container_width=True``, use
|
||
``width="stretch"``. For ``use_container_width=False``, use
|
||
``width="content"``.
|
||
|
||
width : "content", "stretch", or int
|
||
The width of the button. This can be one of the following:
|
||
|
||
- ``"content"`` (default): The width of the button matches the
|
||
width of its content, but doesn't exceed the width of the parent
|
||
container.
|
||
- ``"stretch"``: The width of the button matches the width of the
|
||
parent container.
|
||
- An integer specifying the width in pixels: The button has a
|
||
fixed width. If the specified width is greater than the width of
|
||
the parent container, the width of the button matches the width
|
||
of the parent container.
|
||
|
||
Returns
|
||
-------
|
||
bool
|
||
True if the button was clicked on the last run of the app,
|
||
False otherwise.
|
||
|
||
Examples
|
||
--------
|
||
**Example 1: Customize your button type**
|
||
|
||
>>> import streamlit as st
|
||
>>>
|
||
>>> st.button("Reset", type="primary")
|
||
>>> if st.button("Say hello"):
|
||
... st.write("Why hello there")
|
||
... else:
|
||
... st.write("Goodbye")
|
||
>>>
|
||
>>> if st.button("Aloha", type="tertiary"):
|
||
... st.write("Ciao")
|
||
|
||
.. output::
|
||
https://doc-buton.streamlit.app/
|
||
height: 300px
|
||
|
||
**Example 2: Add icons to your button**
|
||
|
||
Although you can add icons to your buttons through Markdown, the
|
||
``icon`` parameter is a convenient and consistent alternative.
|
||
|
||
>>> import streamlit as st
|
||
>>>
|
||
>>> left, middle, right = st.columns(3)
|
||
>>> if left.button("Plain button", width="stretch"):
|
||
... left.markdown("You clicked the plain button.")
|
||
>>> if middle.button("Emoji button", icon="😃", width="stretch"):
|
||
... middle.markdown("You clicked the emoji button.")
|
||
>>> if right.button("Material button", icon=":material/mood:", width="stretch"):
|
||
... right.markdown("You clicked the Material button.")
|
||
|
||
.. output::
|
||
https://doc-button-icons.streamlit.app/
|
||
height: 220px
|
||
|
||
"""
|
||
key = to_key(key)
|
||
ctx = get_script_run_ctx()
|
||
|
||
if use_container_width is not None:
|
||
width = "stretch" if use_container_width else "content"
|
||
|
||
# Checks whether the entered button type is one of the allowed options
|
||
if type not in ["primary", "secondary", "tertiary"]:
|
||
raise StreamlitAPIException(
|
||
'The type argument to st.button must be "primary", "secondary", or "tertiary". '
|
||
f'\nThe argument passed was "{type}".'
|
||
)
|
||
|
||
return self.dg._button(
|
||
label,
|
||
key,
|
||
help,
|
||
is_form_submitter=False,
|
||
on_click=on_click,
|
||
args=args,
|
||
kwargs=kwargs,
|
||
disabled=disabled,
|
||
type=type,
|
||
icon=icon,
|
||
ctx=ctx,
|
||
width=width,
|
||
)
|
||
|
||
@gather_metrics("download_button")
|
||
def download_button(
|
||
self,
|
||
label: str,
|
||
data: DownloadButtonDataType,
|
||
file_name: str | None = None,
|
||
mime: str | None = None,
|
||
key: Key | None = None,
|
||
help: str | None = None,
|
||
on_click: WidgetCallback | Literal["rerun", "ignore"] | None = "rerun",
|
||
args: WidgetArgs | None = None,
|
||
kwargs: WidgetKwargs | None = None,
|
||
*, # keyword-only arguments:
|
||
type: Literal["primary", "secondary", "tertiary"] = "secondary",
|
||
icon: str | None = None,
|
||
disabled: bool = False,
|
||
use_container_width: bool | None = None,
|
||
width: Width = "content",
|
||
) -> bool:
|
||
r"""Display a download button widget.
|
||
|
||
This is useful when you would like to provide a way for your users
|
||
to download a file directly from your app.
|
||
|
||
Note that the data to be downloaded is stored in-memory while the
|
||
user is connected, so it's a good idea to keep file sizes under a
|
||
couple hundred megabytes to conserve memory.
|
||
|
||
If you want to prevent your app from rerunning when a user clicks the
|
||
download button, wrap the download button in a `fragment
|
||
<https://docs.streamlit.io/develop/concepts/architecture/fragments>`_.
|
||
|
||
Parameters
|
||
----------
|
||
label : str
|
||
A short label explaining to the user what this button is for.
|
||
The label can optionally contain GitHub-flavored Markdown of the
|
||
following types: Bold, Italics, Strikethroughs, Inline Code, Links,
|
||
and Images. Images display like icons, with a max height equal to
|
||
the font height.
|
||
|
||
Unsupported Markdown elements are unwrapped so only their children
|
||
(text contents) render. Display unsupported elements as literal
|
||
characters by backslash-escaping them. E.g.,
|
||
``"1\. Not an ordered list"``.
|
||
|
||
See the ``body`` parameter of |st.markdown|_ for additional,
|
||
supported Markdown directives.
|
||
|
||
.. |st.markdown| replace:: ``st.markdown``
|
||
.. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
|
||
|
||
data : str, bytes, or file
|
||
The contents of the file to be downloaded.
|
||
|
||
To prevent unncecessary recomputation, use caching when converting
|
||
your data for download. For more information, see the Example 1
|
||
below.
|
||
|
||
file_name: str
|
||
An optional string to use as the name of the file to be downloaded,
|
||
such as ``"my_file.csv"``. If not specified, the name will be
|
||
automatically generated.
|
||
|
||
mime : str or None
|
||
The MIME type of the data. If this is ``None`` (default), Streamlit
|
||
sets the MIME type depending on the value of ``data`` as follows:
|
||
|
||
- If ``data`` is a string or textual file (i.e. ``str`` or
|
||
``io.TextIOWrapper`` object), Streamlit uses the "text/plain"
|
||
MIME type.
|
||
- If ``data`` is a binary file or bytes (i.e. ``bytes``,
|
||
``io.BytesIO``, ``io.BufferedReader``, or ``io.RawIOBase``
|
||
object), Streamlit uses the "application/octet-stream" MIME type.
|
||
|
||
For more information about MIME types, see
|
||
https://www.iana.org/assignments/media-types/media-types.xhtml.
|
||
|
||
key : str or int
|
||
An optional string or integer to use as the unique key for the widget.
|
||
If this is omitted, a key will be generated for the widget
|
||
based on its content. No two widgets may have the same key.
|
||
|
||
help : str or None
|
||
A tooltip that gets displayed when the button is hovered over. If
|
||
this is ``None`` (default), no tooltip is displayed.
|
||
|
||
The tooltip can optionally contain GitHub-flavored Markdown,
|
||
including the Markdown directives described in the ``body``
|
||
parameter of ``st.markdown``.
|
||
|
||
on_click : callable, "rerun", "ignore", or None
|
||
How the button should respond to user interaction. This controls
|
||
whether or not the button triggers a rerun and if a callback
|
||
function is called. This can be one of the following values:
|
||
|
||
- ``"rerun"`` (default): The user downloads the file and the app
|
||
reruns. No callback function is called.
|
||
- ``"ignore"``: The user downloads the file and the app doesn't
|
||
rerun. No callback function is called.
|
||
- A ``callable``: The user downloads the file and app reruns. The
|
||
callable is called before the rest of the app.
|
||
- ``None``: This is same as ``on_click="rerun"``. This value exists
|
||
for backwards compatibility and shouldn't be used.
|
||
|
||
args : list or tuple
|
||
An optional list or tuple of args to pass to the callback.
|
||
|
||
kwargs : dict
|
||
An optional dict of kwargs to pass to the callback.
|
||
|
||
type : "primary", "secondary", or "tertiary"
|
||
An optional string that specifies the button type. This can be one
|
||
of the following:
|
||
|
||
- ``"primary"``: The button's background is the app's primary color
|
||
for additional emphasis.
|
||
- ``"secondary"`` (default): The button's background coordinates
|
||
with the app's background color for normal emphasis.
|
||
- ``"tertiary"``: The button is plain text without a border or
|
||
background for subtlety.
|
||
|
||
icon : str or None
|
||
An optional emoji or icon to display next to the button label. If ``icon``
|
||
is ``None`` (default), no icon is displayed. If ``icon`` is a
|
||
string, the following options are valid:
|
||
|
||
- A single-character emoji. For example, you can set ``icon="🚨"``
|
||
or ``icon="🔥"``. Emoji short codes are not supported.
|
||
|
||
- An icon from the Material Symbols library (rounded style) in the
|
||
format ``":material/icon_name:"`` where "icon_name" is the name
|
||
of the icon in snake case.
|
||
|
||
For example, ``icon=":material/thumb_up:"`` will display the
|
||
Thumb Up icon. Find additional icons in the `Material Symbols \
|
||
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
||
font library.
|
||
|
||
disabled : bool
|
||
An optional boolean that disables the download button if set to
|
||
``True``. The default is ``False``.
|
||
|
||
use_container_width : bool
|
||
Whether to expand the button's width to fill its parent container.
|
||
If ``use_container_width`` is ``False`` (default), Streamlit sizes
|
||
the button to fit its contents. If ``use_container_width`` is
|
||
``True``, the width of the button matches its parent container.
|
||
|
||
In both cases, if the contents of the button are wider than the
|
||
parent container, the contents will line wrap.
|
||
|
||
.. deprecated::
|
||
``use_container_width`` is deprecated and will be removed in a
|
||
future release. For ``use_container_width=True``, use
|
||
``width="stretch"``. For ``use_container_width=False``, use
|
||
``width="content"``.
|
||
|
||
width : "content", "stretch", or int
|
||
The width of the download button. This can be one of the following:
|
||
|
||
- ``"content"`` (default): The width of the button matches the
|
||
width of its content, but doesn't exceed the width of the parent
|
||
container.
|
||
- ``"stretch"``: The width of the button matches the width of the
|
||
parent container.
|
||
- An integer specifying the width in pixels: The button has a
|
||
fixed width. If the specified width is greater than the width of
|
||
the parent container, the width of the button matches the width
|
||
of the parent container.
|
||
|
||
Returns
|
||
-------
|
||
bool
|
||
True if the button was clicked on the last run of the app,
|
||
False otherwise.
|
||
|
||
Examples
|
||
--------
|
||
**Example 1: Download a dataframe as a CSV file**
|
||
|
||
When working with a large dataframe, it's recommended to fetch your
|
||
data with a cached function. When working with a download button, it's
|
||
similarly recommended to convert your data into a downloadable format
|
||
with a cached function. Caching ensures that the app reruns
|
||
efficiently.
|
||
|
||
>>> import streamlit as st
|
||
>>> import pandas as pd
|
||
>>> import numpy as np
|
||
>>>
|
||
>>> @st.cache_data
|
||
>>> def get_data():
|
||
>>> df = pd.DataFrame(
|
||
... np.random.randn(50, 20), columns=("col %d" % i for i in range(20))
|
||
... )
|
||
>>> return df
|
||
>>>
|
||
>>> @st.cache_data
|
||
>>> def convert_for_download(df):
|
||
>>> return df.to_csv().encode("utf-8")
|
||
>>>
|
||
>>> df = get_data()
|
||
>>> csv = convert_for_download(df)
|
||
>>>
|
||
>>> st.download_button(
|
||
... label="Download CSV",
|
||
... data=csv,
|
||
... file_name="data.csv",
|
||
... mime="text/csv",
|
||
... icon=":material/download:",
|
||
... )
|
||
|
||
.. output::
|
||
https://doc-download-button-csv.streamlit.app/
|
||
height: 200px
|
||
|
||
**Example 2: Download a string as a text file**
|
||
|
||
If you pass a string to the ``data`` argument, Streamlit will
|
||
automatically use the "text/plain" MIME type.
|
||
|
||
When you have a widget (like a text area) affecting the value of your
|
||
download, it's recommended to use another button to prepare the
|
||
download. In this case, use ``on_click="ignore"`` in your download
|
||
button to prevent the download button from rerunning your app. This
|
||
turns the download button into a frontend-only element that can be
|
||
nested in another button.
|
||
|
||
Without a preparation button, a user can type something into the text
|
||
area and immediately click the download button. Because a download is
|
||
initiated concurrently with the app rerun, this can create a race-like
|
||
condition where the user doesn't see the updated data in their
|
||
download.
|
||
|
||
.. important::
|
||
Even when you prevent your download button from triggering a rerun,
|
||
another widget with a pending change can still trigger a rerun. For
|
||
example, if a text area has a pending change when a user clicks a
|
||
download button, the text area will trigger a rerun.
|
||
|
||
>>> import streamlit as st
|
||
>>>
|
||
>>> message = st.text_area("Message", value="Lorem ipsum.\nStreamlit is cool.")
|
||
>>>
|
||
>>> if st.button("Prepare download"):
|
||
>>> st.download_button(
|
||
... label="Download text",
|
||
... data=message,
|
||
... file_name="message.txt",
|
||
... on_click="ignore",
|
||
... type="primary",
|
||
... icon=":material/download:",
|
||
... )
|
||
|
||
.. output::
|
||
https://doc-download-button-text.streamlit.app/
|
||
height: 250px
|
||
|
||
**Example 3: Download a file**
|
||
|
||
Use a context manager to open and read a local file on your Streamlit
|
||
server. Pass the ``io.BufferedReader`` object directly to ``data``.
|
||
Remember to specify the MIME type if you don't want the default
|
||
type of ``"application/octet-stream"`` for generic binary data. In the
|
||
example below, the MIME type is set to ``"image/png"`` for a PNG file.
|
||
|
||
>>> import streamlit as st
|
||
>>>
|
||
>>> with open("flower.png", "rb") as file:
|
||
... st.download_button(
|
||
... label="Download image",
|
||
... data=file,
|
||
... file_name="flower.png",
|
||
... mime="image/png",
|
||
... )
|
||
|
||
.. output::
|
||
https://doc-download-button-file.streamlit.app/
|
||
height: 200px
|
||
|
||
"""
|
||
ctx = get_script_run_ctx()
|
||
|
||
if use_container_width is not None:
|
||
width = "stretch" if use_container_width else "content"
|
||
|
||
if type not in ["primary", "secondary", "tertiary"]:
|
||
raise StreamlitAPIException(
|
||
'The type argument to st.download_button must be "primary", "secondary", or "tertiary". \n'
|
||
f'The argument passed was "{type}".'
|
||
)
|
||
|
||
return self._download_button(
|
||
label=label,
|
||
data=data,
|
||
file_name=file_name,
|
||
mime=mime,
|
||
key=key,
|
||
help=help,
|
||
on_click=on_click,
|
||
args=args,
|
||
kwargs=kwargs,
|
||
type=type,
|
||
icon=icon,
|
||
disabled=disabled,
|
||
ctx=ctx,
|
||
width=width,
|
||
)
|
||
|
||
@gather_metrics("link_button")
|
||
def link_button(
|
||
self,
|
||
label: str,
|
||
url: str,
|
||
*,
|
||
help: str | None = None,
|
||
type: Literal["primary", "secondary", "tertiary"] = "secondary",
|
||
icon: str | None = None,
|
||
disabled: bool = False,
|
||
use_container_width: bool | None = None,
|
||
width: Width = "content",
|
||
) -> DeltaGenerator:
|
||
r"""Display a link button element.
|
||
|
||
When clicked, a new tab will be opened to the specified URL. This will
|
||
create a new session for the user if directed within the app.
|
||
|
||
Parameters
|
||
----------
|
||
label : str
|
||
A short label explaining to the user what this button is for.
|
||
The label can optionally contain GitHub-flavored Markdown of the
|
||
following types: Bold, Italics, Strikethroughs, Inline Code, Links,
|
||
and Images. Images display like icons, with a max height equal to
|
||
the font height.
|
||
|
||
Unsupported Markdown elements are unwrapped so only their children
|
||
(text contents) render. Display unsupported elements as literal
|
||
characters by backslash-escaping them. E.g.,
|
||
``"1\. Not an ordered list"``.
|
||
|
||
See the ``body`` parameter of |st.markdown|_ for additional,
|
||
supported Markdown directives.
|
||
|
||
.. |st.markdown| replace:: ``st.markdown``
|
||
.. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
|
||
|
||
url : str
|
||
The url to be opened on user click
|
||
|
||
help : str or None
|
||
A tooltip that gets displayed when the button is hovered over. If
|
||
this is ``None`` (default), no tooltip is displayed.
|
||
|
||
The tooltip can optionally contain GitHub-flavored Markdown,
|
||
including the Markdown directives described in the ``body``
|
||
parameter of ``st.markdown``.
|
||
|
||
type : "primary", "secondary", or "tertiary"
|
||
An optional string that specifies the button type. This can be one
|
||
of the following:
|
||
|
||
- ``"primary"``: The button's background is the app's primary color
|
||
for additional emphasis.
|
||
- ``"secondary"`` (default): The button's background coordinates
|
||
with the app's background color for normal emphasis.
|
||
- ``"tertiary"``: The button is plain text without a border or
|
||
background for subtlety.
|
||
|
||
icon : str or None
|
||
An optional emoji or icon to display next to the button label. If ``icon``
|
||
is ``None`` (default), no icon is displayed. If ``icon`` is a
|
||
string, the following options are valid:
|
||
|
||
- A single-character emoji. For example, you can set ``icon="🚨"``
|
||
or ``icon="🔥"``. Emoji short codes are not supported.
|
||
|
||
- An icon from the Material Symbols library (rounded style) in the
|
||
format ``":material/icon_name:"`` where "icon_name" is the name
|
||
of the icon in snake case.
|
||
|
||
For example, ``icon=":material/thumb_up:"`` will display the
|
||
Thumb Up icon. Find additional icons in the `Material Symbols \
|
||
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
||
font library.
|
||
|
||
disabled : bool
|
||
An optional boolean that disables the link button if set to
|
||
``True``. The default is ``False``.
|
||
|
||
use_container_width : bool
|
||
Whether to expand the button's width to fill its parent container.
|
||
If ``use_container_width`` is ``False`` (default), Streamlit sizes
|
||
the button to fit its contents. If ``use_container_width`` is
|
||
``True``, the width of the button matches its parent container.
|
||
|
||
In both cases, if the contents of the button are wider than the
|
||
parent container, the contents will line wrap.
|
||
|
||
.. deprecated::
|
||
``use_container_width`` is deprecated and will be removed in a
|
||
future release. For ``use_container_width=True``, use
|
||
``width="stretch"``. For ``use_container_width=False``, use
|
||
``width="content"``.
|
||
|
||
width : "content", "stretch", or int
|
||
The width of the link button. This can be one of the following:
|
||
|
||
- ``"content"`` (default): The width of the button matches the
|
||
width of its content, but doesn't exceed the width of the parent
|
||
container.
|
||
- ``"stretch"``: The width of the button matches the width of the
|
||
parent container.
|
||
- An integer specifying the width in pixels: The button has a
|
||
fixed width. If the specified width is greater than the width of
|
||
the parent container, the width of the button matches the width
|
||
of the parent container.
|
||
|
||
Example
|
||
-------
|
||
>>> import streamlit as st
|
||
>>>
|
||
>>> st.link_button("Go to gallery", "https://streamlit.io/gallery")
|
||
|
||
.. output::
|
||
https://doc-link-button.streamlit.app/
|
||
height: 200px
|
||
|
||
"""
|
||
# Checks whether the entered button type is one of the allowed options - either "primary" or "secondary"
|
||
if type not in ["primary", "secondary", "tertiary"]:
|
||
raise StreamlitAPIException(
|
||
'The type argument to st.link_button must be "primary", "secondary", or "tertiary". '
|
||
f'\nThe argument passed was "{type}".'
|
||
)
|
||
|
||
if use_container_width is not None:
|
||
width = "stretch" if use_container_width else "content"
|
||
|
||
return self._link_button(
|
||
label=label,
|
||
url=url,
|
||
help=help,
|
||
disabled=disabled,
|
||
type=type,
|
||
icon=icon,
|
||
width=width,
|
||
)
|
||
|
||
@gather_metrics("page_link")
|
||
def page_link(
|
||
self,
|
||
page: str | Path | StreamlitPage,
|
||
*,
|
||
label: str | None = None,
|
||
icon: str | None = None,
|
||
help: str | None = None,
|
||
disabled: bool = False,
|
||
use_container_width: bool | None = None,
|
||
width: Width = "content",
|
||
) -> DeltaGenerator:
|
||
r"""Display a link to another page in a multipage app or to an external page.
|
||
|
||
If another page in a multipage app is specified, clicking ``st.page_link``
|
||
stops the current page execution and runs the specified page as if the
|
||
user clicked on it in the sidebar navigation.
|
||
|
||
If an external page is specified, clicking ``st.page_link`` opens a new
|
||
tab to the specified page. The current script run will continue if not
|
||
complete.
|
||
|
||
Parameters
|
||
----------
|
||
page : str, Path, or StreamlitPage
|
||
The file path (relative to the main script) or a ``StreamlitPage``
|
||
indicating the page to switch to. Alternatively, this can be the
|
||
URL to an external page (must start with "http://" or "https://").
|
||
|
||
label : str
|
||
The label for the page link. Labels are required for external pages.
|
||
The label can optionally contain GitHub-flavored Markdown of the
|
||
following types: Bold, Italics, Strikethroughs, Inline Code, Links,
|
||
and Images. Images display like icons, with a max height equal to
|
||
the font height.
|
||
|
||
Unsupported Markdown elements are unwrapped so only their children
|
||
(text contents) render. Display unsupported elements as literal
|
||
characters by backslash-escaping them. E.g.,
|
||
``"1\. Not an ordered list"``.
|
||
|
||
See the ``body`` parameter of |st.markdown|_ for additional,
|
||
supported Markdown directives.
|
||
|
||
.. |st.markdown| replace:: ``st.markdown``
|
||
.. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
|
||
|
||
icon : str or None
|
||
An optional emoji or icon to display next to the button label. If
|
||
``icon`` is ``None`` (default), the icon is inferred from the
|
||
``StreamlitPage`` object or no icon is displayed. If ``icon`` is a
|
||
string, the following options are valid:
|
||
|
||
- A single-character emoji. For example, you can set ``icon="🚨"``
|
||
or ``icon="🔥"``. Emoji short codes are not supported.
|
||
|
||
- An icon from the Material Symbols library (rounded style) in the
|
||
format ``":material/icon_name:"`` where "icon_name" is the name
|
||
of the icon in snake case.
|
||
|
||
For example, ``icon=":material/thumb_up:"`` will display the
|
||
Thumb Up icon. Find additional icons in the `Material Symbols \
|
||
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
||
font library.
|
||
|
||
help : str or None
|
||
A tooltip that gets displayed when the link is hovered over. If
|
||
this is ``None`` (default), no tooltip is displayed.
|
||
|
||
The tooltip can optionally contain GitHub-flavored Markdown,
|
||
including the Markdown directives described in the ``body``
|
||
parameter of ``st.markdown``.
|
||
|
||
disabled : bool
|
||
An optional boolean that disables the page link if set to ``True``.
|
||
The default is ``False``.
|
||
|
||
use_container_width : bool
|
||
Whether to expand the link's width to fill its parent container.
|
||
The default is ``True`` for page links in the sidebar and ``False``
|
||
for those in the main app.
|
||
|
||
.. deprecated::
|
||
``use_container_width`` is deprecated and will be removed in a
|
||
future release. For ``use_container_width=True``, use
|
||
``width="stretch"``. For ``use_container_width=False``, use
|
||
``width="content"``.
|
||
|
||
width : "content", "stretch", or int
|
||
The width of the page-link button. This can be one of the following:
|
||
|
||
- ``"content"`` (default): The width of the button matches the
|
||
width of its content, but doesn't exceed the width of the parent
|
||
container.
|
||
- ``"stretch"``: The width of the button matches the width of the
|
||
parent container.
|
||
- An integer specifying the width in pixels: The button has a
|
||
fixed width. If the specified width is greater than the width of
|
||
the parent container, the width of the button matches the width
|
||
of the parent container.
|
||
|
||
Example
|
||
-------
|
||
Consider the following example given this file structure:
|
||
|
||
>>> your-repository/
|
||
>>> ├── pages/
|
||
>>> │ ├── page_1.py
|
||
>>> │ └── page_2.py
|
||
>>> └── your_app.py
|
||
|
||
>>> import streamlit as st
|
||
>>>
|
||
>>> st.page_link("your_app.py", label="Home", icon="🏠")
|
||
>>> st.page_link("pages/page_1.py", label="Page 1", icon="1️⃣")
|
||
>>> st.page_link("pages/page_2.py", label="Page 2", icon="2️⃣", disabled=True)
|
||
>>> st.page_link("http://www.google.com", label="Google", icon="🌎")
|
||
|
||
The default navigation is shown here for comparison, but you can hide
|
||
the default navigation using the |client.showSidebarNavigation|_
|
||
configuration option. This allows you to create custom, dynamic
|
||
navigation menus for your apps!
|
||
|
||
.. |client.showSidebarNavigation| replace:: ``client.showSidebarNavigation``
|
||
.. _client.showSidebarNavigation: https://docs.streamlit.io/develop/api-reference/configuration/config.toml#client
|
||
|
||
.. output ::
|
||
https://doc-page-link.streamlit.app/
|
||
height: 350px
|
||
|
||
"""
|
||
if use_container_width is not None:
|
||
width = "stretch" if use_container_width else "content"
|
||
|
||
if in_sidebar(self.dg):
|
||
# Sidebar page links should always be stretch width.
|
||
width = "stretch"
|
||
|
||
return self._page_link(
|
||
page=page,
|
||
label=label,
|
||
icon=icon,
|
||
help=help,
|
||
disabled=disabled,
|
||
width=width,
|
||
)
|
||
|
||
def _download_button(
|
||
self,
|
||
label: str,
|
||
data: DownloadButtonDataType,
|
||
file_name: str | None = None,
|
||
mime: str | None = None,
|
||
key: Key | None = None,
|
||
help: str | None = None,
|
||
on_click: WidgetCallback | Literal["rerun", "ignore"] | None = "rerun",
|
||
args: WidgetArgs | None = None,
|
||
kwargs: WidgetKwargs | None = None,
|
||
*, # keyword-only arguments:
|
||
type: Literal["primary", "secondary", "tertiary"] = "secondary",
|
||
icon: str | None = None,
|
||
disabled: bool = False,
|
||
ctx: ScriptRunContext | None = None,
|
||
width: Width = "content",
|
||
) -> bool:
|
||
key = to_key(key)
|
||
|
||
on_click_callback: WidgetCallback | None = (
|
||
None
|
||
if on_click is None or on_click in {"ignore", "rerun"}
|
||
else cast("WidgetCallback", on_click)
|
||
)
|
||
|
||
check_widget_policies(
|
||
self.dg,
|
||
key,
|
||
on_change=on_click_callback,
|
||
default_value=None,
|
||
writes_allowed=False,
|
||
)
|
||
|
||
element_id = compute_and_register_element_id(
|
||
"download_button",
|
||
user_key=key,
|
||
key_as_main_identity=True,
|
||
dg=self.dg,
|
||
label=label,
|
||
icon=icon,
|
||
file_name=file_name,
|
||
mime=mime,
|
||
help=help,
|
||
type=type,
|
||
width=width,
|
||
)
|
||
|
||
if is_in_form(self.dg):
|
||
raise StreamlitAPIException(
|
||
f"`st.download_button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}"
|
||
)
|
||
|
||
download_button_proto = DownloadButtonProto()
|
||
download_button_proto.id = element_id
|
||
download_button_proto.label = label
|
||
download_button_proto.default = False
|
||
download_button_proto.type = type
|
||
marshall_file(
|
||
self.dg._get_delta_path_str(), data, download_button_proto, mime, file_name
|
||
)
|
||
download_button_proto.disabled = disabled
|
||
|
||
if help is not None:
|
||
download_button_proto.help = dedent(help)
|
||
|
||
if icon is not None:
|
||
download_button_proto.icon = validate_icon_or_emoji(icon)
|
||
|
||
if on_click == "ignore":
|
||
download_button_proto.ignore_rerun = True
|
||
else:
|
||
download_button_proto.ignore_rerun = False
|
||
|
||
serde = ButtonSerde()
|
||
|
||
button_state = register_widget(
|
||
download_button_proto.id,
|
||
on_change_handler=on_click_callback,
|
||
args=args,
|
||
kwargs=kwargs,
|
||
deserializer=serde.deserialize,
|
||
serializer=serde.serialize,
|
||
ctx=ctx,
|
||
value_type="trigger_value",
|
||
)
|
||
|
||
validate_width(width, allow_content=True)
|
||
layout_config = LayoutConfig(width=width)
|
||
self.dg._enqueue(
|
||
"download_button", download_button_proto, layout_config=layout_config
|
||
)
|
||
return button_state.value
|
||
|
||
def _link_button(
|
||
self,
|
||
label: str,
|
||
url: str,
|
||
help: str | None,
|
||
*, # keyword-only arguments:
|
||
type: Literal["primary", "secondary", "tertiary"] = "secondary",
|
||
icon: str | None = None,
|
||
disabled: bool = False,
|
||
width: Width = "content",
|
||
) -> DeltaGenerator:
|
||
link_button_proto = LinkButtonProto()
|
||
link_button_proto.label = label
|
||
link_button_proto.url = url
|
||
link_button_proto.type = type
|
||
link_button_proto.disabled = disabled
|
||
|
||
if help is not None:
|
||
link_button_proto.help = dedent(help)
|
||
|
||
if icon is not None:
|
||
link_button_proto.icon = validate_icon_or_emoji(icon)
|
||
|
||
validate_width(width, allow_content=True)
|
||
layout_config = LayoutConfig(width=width)
|
||
return self.dg._enqueue(
|
||
"link_button", link_button_proto, layout_config=layout_config
|
||
)
|
||
|
||
def _page_link(
|
||
self,
|
||
page: str | Path | StreamlitPage,
|
||
*, # keyword-only arguments:
|
||
label: str | None = None,
|
||
icon: str | None = None,
|
||
help: str | None = None,
|
||
disabled: bool = False,
|
||
width: Width = "content",
|
||
) -> DeltaGenerator:
|
||
page_link_proto = PageLinkProto()
|
||
validate_width(width, allow_content=True)
|
||
|
||
ctx = get_script_run_ctx()
|
||
if not ctx:
|
||
layout_config = LayoutConfig(width=width)
|
||
return self.dg._enqueue(
|
||
"page_link", page_link_proto, layout_config=layout_config
|
||
)
|
||
|
||
page_link_proto.disabled = disabled
|
||
|
||
if label is not None:
|
||
page_link_proto.label = label
|
||
|
||
if icon is not None:
|
||
page_link_proto.icon = validate_icon_or_emoji(icon)
|
||
|
||
if help is not None:
|
||
page_link_proto.help = dedent(help)
|
||
|
||
if isinstance(page, StreamlitPage):
|
||
page_link_proto.page_script_hash = page._script_hash
|
||
page_link_proto.page = page.url_path
|
||
if label is None:
|
||
page_link_proto.label = page.title
|
||
if icon is None:
|
||
page_link_proto.icon = page.icon
|
||
# Here the StreamlitPage's icon is already validated
|
||
# (using validate_icon_or_emoji) during its initialization
|
||
else:
|
||
# Convert Path to string if necessary
|
||
if isinstance(page, Path):
|
||
page = str(page)
|
||
|
||
# Handle external links:
|
||
if is_url(page):
|
||
if label is None or label == "":
|
||
raise StreamlitMissingPageLabelError()
|
||
page_link_proto.page = page
|
||
page_link_proto.external = True
|
||
layout_config = LayoutConfig(width=width)
|
||
return self.dg._enqueue(
|
||
"page_link", page_link_proto, layout_config=layout_config
|
||
)
|
||
|
||
ctx_main_script = ""
|
||
all_app_pages = {}
|
||
ctx_main_script = ctx.main_script_path
|
||
all_app_pages = ctx.pages_manager.get_pages()
|
||
|
||
main_script_directory = get_main_script_directory(ctx_main_script)
|
||
requested_page = os.path.realpath(
|
||
normalize_path_join(main_script_directory, page)
|
||
)
|
||
|
||
# Handle retrieving the page_script_hash & page
|
||
for page_data in all_app_pages.values():
|
||
full_path = page_data["script_path"]
|
||
page_name = page_data["page_name"]
|
||
url_pathname = page_data["url_pathname"]
|
||
if requested_page == full_path:
|
||
if label is None:
|
||
page_link_proto.label = page_name
|
||
page_link_proto.page_script_hash = page_data["page_script_hash"]
|
||
page_link_proto.page = url_pathname
|
||
break
|
||
|
||
if page_link_proto.page_script_hash == "":
|
||
raise StreamlitPageNotFoundError(
|
||
page=page,
|
||
main_script_directory=main_script_directory,
|
||
uses_pages_directory=bool(PagesManager.uses_pages_directory),
|
||
)
|
||
|
||
layout_config = LayoutConfig(width=width)
|
||
return self.dg._enqueue(
|
||
"page_link", page_link_proto, layout_config=layout_config
|
||
)
|
||
|
||
def _button(
|
||
self,
|
||
label: str,
|
||
key: str | None,
|
||
help: str | None,
|
||
is_form_submitter: bool,
|
||
on_click: WidgetCallback | None = None,
|
||
args: WidgetArgs | None = None,
|
||
kwargs: WidgetKwargs | None = None,
|
||
*, # keyword-only arguments:
|
||
type: Literal["primary", "secondary", "tertiary"] = "secondary",
|
||
icon: str | None = None,
|
||
disabled: bool = False,
|
||
ctx: ScriptRunContext | None = None,
|
||
width: Width = "content",
|
||
) -> bool:
|
||
key = to_key(key)
|
||
|
||
check_widget_policies(
|
||
self.dg,
|
||
key,
|
||
on_click,
|
||
default_value=None,
|
||
writes_allowed=False,
|
||
enable_check_callback_rules=not is_form_submitter,
|
||
)
|
||
|
||
# Only the form submitter button needs a form ID at the moment.
|
||
form_id = current_form_id(self.dg) if is_form_submitter else ""
|
||
element_id = compute_and_register_element_id(
|
||
"form_submit_button" if is_form_submitter else "button",
|
||
user_key=key,
|
||
key_as_main_identity=True,
|
||
dg=self.dg,
|
||
label=label,
|
||
icon=icon,
|
||
help=help,
|
||
is_form_submitter=is_form_submitter,
|
||
type=type,
|
||
width=width,
|
||
)
|
||
|
||
# It doesn't make sense to create a button inside a form (except
|
||
# for the "Form Submitter" button that's automatically created in
|
||
# every form). We throw an error to warn the user about this.
|
||
# We omit this check for scripts running outside streamlit, because
|
||
# they will have no script_run_ctx.
|
||
if runtime.exists():
|
||
if is_in_form(self.dg) and not is_form_submitter:
|
||
raise StreamlitAPIException(
|
||
f"`st.button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}"
|
||
)
|
||
if not is_in_form(self.dg) and is_form_submitter:
|
||
raise StreamlitAPIException(
|
||
f"`st.form_submit_button()` must be used inside an `st.form()`.{FORM_DOCS_INFO}"
|
||
)
|
||
|
||
button_proto = ButtonProto()
|
||
button_proto.id = element_id
|
||
button_proto.label = label
|
||
button_proto.default = False
|
||
button_proto.is_form_submitter = is_form_submitter
|
||
button_proto.form_id = form_id
|
||
button_proto.type = type
|
||
button_proto.disabled = disabled
|
||
|
||
if help is not None:
|
||
button_proto.help = dedent(help)
|
||
|
||
if icon is not None:
|
||
button_proto.icon = validate_icon_or_emoji(icon)
|
||
|
||
serde = ButtonSerde()
|
||
|
||
button_state = register_widget(
|
||
button_proto.id,
|
||
on_change_handler=on_click,
|
||
args=args,
|
||
kwargs=kwargs,
|
||
deserializer=serde.deserialize,
|
||
serializer=serde.serialize,
|
||
ctx=ctx,
|
||
value_type="trigger_value",
|
||
)
|
||
|
||
if ctx:
|
||
save_for_app_testing(ctx, element_id, button_state.value)
|
||
|
||
validate_width(width, allow_content=True)
|
||
layout_config = LayoutConfig(width=width)
|
||
self.dg._enqueue("button", button_proto, layout_config=layout_config)
|
||
|
||
return button_state.value
|
||
|
||
@property
|
||
def dg(self) -> DeltaGenerator:
|
||
"""Get our DeltaGenerator."""
|
||
return cast("DeltaGenerator", self)
|
||
|
||
|
||
def marshall_file(
|
||
coordinates: str,
|
||
data: DownloadButtonDataType,
|
||
proto_download_button: DownloadButtonProto,
|
||
mimetype: str | None,
|
||
file_name: str | None = None,
|
||
) -> None:
|
||
data_as_bytes: bytes
|
||
if isinstance(data, str):
|
||
data_as_bytes = data.encode()
|
||
mimetype = mimetype or "text/plain"
|
||
elif isinstance(data, io.TextIOWrapper):
|
||
string_data = data.read()
|
||
data_as_bytes = string_data.encode()
|
||
mimetype = mimetype or "text/plain"
|
||
# Assume bytes; try methods until we run out.
|
||
elif isinstance(data, bytes):
|
||
data_as_bytes = data
|
||
mimetype = mimetype or "application/octet-stream"
|
||
elif isinstance(data, io.BytesIO):
|
||
data.seek(0)
|
||
data_as_bytes = data.getvalue()
|
||
mimetype = mimetype or "application/octet-stream"
|
||
elif isinstance(data, io.BufferedReader):
|
||
data.seek(0)
|
||
data_as_bytes = data.read()
|
||
mimetype = mimetype or "application/octet-stream"
|
||
elif isinstance(data, io.RawIOBase):
|
||
data.seek(0)
|
||
data_as_bytes = data.read() or b""
|
||
mimetype = mimetype or "application/octet-stream"
|
||
else:
|
||
raise StreamlitAPIException(f"Invalid binary data format: {type(data)}")
|
||
|
||
if runtime.exists():
|
||
file_url = runtime.get_instance().media_file_mgr.add(
|
||
data_as_bytes,
|
||
mimetype,
|
||
coordinates,
|
||
file_name=file_name,
|
||
is_for_static_download=True,
|
||
)
|
||
else:
|
||
# When running in "raw mode", we can't access the MediaFileManager.
|
||
file_url = ""
|
||
|
||
proto_download_button.url = file_url
|