Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions lib/streamlit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@
status_container_cls=_StatusContainer,
dialog_container_cls=_Dialog,
)
_main = _dg_singleton._main_dg
sidebar = _dg_singleton._sidebar_dg
_event = _dg_singleton._event_dg
_bottom = _dg_singleton._bottom_dg
_main: _DeltaGenerator = _dg_singleton._main_dg
sidebar: _DeltaGenerator = _dg_singleton._sidebar_dg
_event: _DeltaGenerator = _dg_singleton._event_dg
_bottom: _DeltaGenerator = _dg_singleton._bottom_dg


from streamlit.elements.dialog_decorator import dialog_decorator as _dialog_decorator
Expand Down Expand Up @@ -135,7 +135,6 @@
from streamlit.commands.logo import logo as logo
from streamlit.commands.navigation import navigation as navigation
from streamlit.navigation.page import Page as Page
from streamlit.elements.spinner import spinner as spinner

from streamlit.commands.page_config import set_page_config as set_page_config
from streamlit.commands.execution_control import (
Expand Down Expand Up @@ -222,6 +221,7 @@
slider = _main.slider
snow = _main.snow
space = _main.space
spinner = _main.spinner
subheader = _main.subheader
success = _main.success
table = _main.table
Expand Down
2 changes: 2 additions & 0 deletions lib/streamlit/delta_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
from streamlit.elements.pyplot import PyplotMixin
from streamlit.elements.snow import SnowMixin
from streamlit.elements.space import SpaceMixin
from streamlit.elements.spinner import SpinnerMixin
from streamlit.elements.text import TextMixin
from streamlit.elements.toast import ToastMixin
from streamlit.elements.vega_charts import VegaChartsMixin
Expand Down Expand Up @@ -210,6 +211,7 @@ class DeltaGenerator(
SliderMixin,
SnowMixin,
SpaceMixin,
SpinnerMixin,
JsonMixin,
TextMixin,
TextWidgetsMixin,
Expand Down
216 changes: 112 additions & 104 deletions lib/streamlit/elements/spinner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@

import contextlib
import threading
from typing import TYPE_CHECKING, Final
from typing import TYPE_CHECKING, Final, cast

import streamlit as st
from streamlit.elements.lib.layout_utils import (
LayoutConfig,
Width,
Expand All @@ -29,110 +28,119 @@
if TYPE_CHECKING:
from collections.abc import Iterator

from streamlit.delta_generator import DeltaGenerator

# Set the message 0.5 seconds in the future to avoid annoying
# flickering if this spinner runs too quickly.
DELAY_SECS: Final = 0.5


@contextlib.contextmanager
def spinner(
text: str = "In progress...",
*,
show_time: bool = False,
_cache: bool = False,
width: Width = "content",
) -> Iterator[None]:
"""Display a loading spinner while executing a block of code.

Parameters
----------
text : str
The text to display next to the spinner. This defaults to
``"In progress..."``.

The text can optionally contain GitHub-flavored Markdown. Syntax
information can be found at: https://github.github.com/gfm.

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

show_time : bool
Whether to show the elapsed time next to the spinner text. If this is
``False`` (default), no time is displayed. If this is ``True``,
elapsed time is displayed with a precision of 0.1 seconds. The time
format is not configurable.

width : "content", "stretch", or int
The width of the spinner element. This can be one of the following:

- ``"content"`` (default): The width of the element matches the
width of its content, but doesn't exceed the width of the parent
container.
- ``"stretch"``: The width of the element matches the width of the
parent container.
- An integer specifying the width in pixels: The element has a
fixed width. If the specified width is greater than the width of
the parent container, the width of the element matches the width
of the parent container.

Example
-------
>>> import streamlit as st
>>> import time
>>>
>>> with st.spinner("Wait for it...", show_time=True):
>>> time.sleep(5)
>>> st.success("Done!")
>>> st.button("Rerun")

.. output ::
https://doc-spinner.streamlit.app/
height: 210px

"""
from streamlit.proto.Spinner_pb2 import Spinner as SpinnerProto
from streamlit.string_util import clean_text

validate_width(width, allow_content=True)
layout_config = LayoutConfig(width=width)

message = st.empty()

display_message = True
display_message_lock = threading.Lock()

try:

def set_message() -> None:
with display_message_lock:
if display_message:
spinner_proto = SpinnerProto()
spinner_proto.text = clean_text(text)
spinner_proto.cache = _cache
spinner_proto.show_time = show_time
message._enqueue(
"spinner", spinner_proto, layout_config=layout_config
)

add_script_run_ctx(threading.Timer(DELAY_SECS, set_message)).start()

# Yield control back to the context.
yield
finally:
if display_message_lock:
with display_message_lock:
display_message = False
if "chat_message" in set(message._active_dg._ancestor_block_types):
# Temporary stale element fix:
# For chat messages, we are resetting the spinner placeholder to an
# empty container instead of an empty placeholder (st.empty) to have
# it removed from the delta path. Empty containers are ignored in the
# frontend since they are configured with allow_empty=False. This
# prevents issues with stale elements caused by the spinner being
# rendered only in some situations (e.g. for caching).
message.container()
else:
message.empty()
class SpinnerMixin:
@contextlib.contextmanager
def spinner(
self,
text: str = "In progress...",
*,
show_time: bool = False,
_cache: bool = False,
width: Width = "content",
) -> Iterator[None]:
"""Display a loading spinner while executing a block of code.

Parameters
----------
text : str
The text to display next to the spinner. This defaults to
``"In progress..."``.

The text can optionally contain GitHub-flavored Markdown. Syntax
information can be found at: https://github.github.com/gfm.

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

show_time : bool
Whether to show the elapsed time next to the spinner text. If this is
``False`` (default), no time is displayed. If this is ``True``,
elapsed time is displayed with a precision of 0.1 seconds. The time
format is not configurable.

width : "content", "stretch", or int
The width of the spinner element. This can be one of the following:

- ``"content"`` (default): The width of the element matches the
width of its content, but doesn't exceed the width of the parent
container.
- ``"stretch"``: The width of the element matches the width of the
parent container.
- An integer specifying the width in pixels: The element has a
fixed width. If the specified width is greater than the width of
the parent container, the width of the element matches the width
of the parent container.

Example
-------
>>> import streamlit as st
>>> import time
>>>
>>> with st.spinner("Wait for it...", show_time=True):
>>> time.sleep(5)
>>> st.success("Done!")
>>> st.button("Rerun")

.. output ::
https://doc-spinner.streamlit.app/
height: 210px

"""
from streamlit.proto.Spinner_pb2 import Spinner as SpinnerProto
from streamlit.string_util import clean_text

validate_width(width, allow_content=True)
layout_config = LayoutConfig(width=width)

message = self.dg.empty()

display_message = True
display_message_lock = threading.Lock()

try:

def set_message() -> None:
with display_message_lock:
if display_message:
spinner_proto = SpinnerProto()
spinner_proto.text = clean_text(text)
spinner_proto.cache = _cache
spinner_proto.show_time = show_time
message._enqueue(
"spinner", spinner_proto, layout_config=layout_config
)

add_script_run_ctx(threading.Timer(DELAY_SECS, set_message)).start()

# Yield control back to the context.
yield
finally:
if display_message_lock:
with display_message_lock:
display_message = False
if "chat_message" in set(message._active_dg._ancestor_block_types):
# Temporary stale element fix:
# For chat messages, we are resetting the spinner placeholder to an
# empty container instead of an empty placeholder (st.empty) to have
# it removed from the delta path. Empty containers are ignored in the
# frontend since they are configured with allow_empty=False. This
# prevents issues with stale elements caused by the spinner being
# rendered only in some situations (e.g. for caching).
message.container()
else:
message.empty()

@property
def dg(self) -> DeltaGenerator:
"""Get our DeltaGenerator."""
return cast("DeltaGenerator", self)
7 changes: 5 additions & 2 deletions lib/streamlit/runtime/caching/cache_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

from streamlit import type_util
from streamlit.dataframe_util import is_unevaluated_data_object
from streamlit.elements.spinner import spinner
from streamlit.delta_generator_singletons import get_dg_singleton_instance
from streamlit.logger import get_logger
from streamlit.runtime.caching.cache_errors import (
CacheError,
Expand Down Expand Up @@ -261,8 +261,11 @@ def _get_or_create_cached_value(
# basically like auto-setting "show_spinner=False" on the @st.cache decorators
# on behalf of the user.
is_nested_cache_function = in_cached_function.get()

spinner_or_no_context = (
spinner(spinner_message, _cache=True, show_time=self._info.show_time)
get_dg_singleton_instance().main_dg.spinner(
spinner_message, _cache=True, show_time=self._info.show_time
)
if spinner_message is not None and not is_nested_cache_function
else contextlib.nullcontext()
)
Expand Down
1 change: 0 additions & 1 deletion lib/tests/streamlit/delta_generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ def test_public_api(self):
# Remove commands that are only exposed in the top-level namespace (st.*)
# and cannot be called on a DeltaGenerator object.
expected_api = expected_api - {
"spinner",
"dialog",
"echo",
"logo",
Expand Down
18 changes: 8 additions & 10 deletions lib/tests/streamlit/spinner_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import pytest

from streamlit.elements.spinner import spinner
import streamlit as st
from streamlit.errors import StreamlitAPIException
from tests.delta_generator_test_case import DeltaGeneratorTestCase
from tests.streamlit.elements.layout_test_utils import WidthConfigFields
Expand All @@ -27,7 +27,7 @@
class SpinnerTest(DeltaGeneratorTestCase):
def test_spinner(self):
"""Test st.spinner."""
with spinner("some text"):
with st.spinner("some text"):
# Without the timeout, the spinner is sometimes not available
time.sleep(0.7)
el = self.get_delta_from_queue().new_element
Expand All @@ -41,9 +41,7 @@ def test_spinner(self):

def test_spinner_within_chat_message(self):
"""Test st.spinner in st.chat_message resets to empty container block."""
import streamlit as st

with st.chat_message("user"), spinner("some text"):
with st.chat_message("user"), st.spinner("some text"):
# Without the timeout, the spinner is sometimes not available
time.sleep(0.7)
el = self.get_delta_from_queue().new_element
Expand All @@ -60,7 +58,7 @@ def test_spinner_within_chat_message(self):

def test_spinner_for_caching(self):
"""Test st.spinner in cache functions."""
with spinner("some text", _cache=True):
with st.spinner("some text", _cache=True):
# Without the timeout, the spinner is sometimes not available
time.sleep(0.7)
el = self.get_delta_from_queue().new_element
Expand All @@ -73,7 +71,7 @@ def test_spinner_for_caching(self):

def test_spinner_time(self):
"""Test st.spinner with show_time."""
with spinner("some text", show_time=True):
with st.spinner("some text", show_time=True):
time.sleep(0.7)
el = self.get_delta_from_queue().new_element
assert el.spinner.text == "some text"
Expand All @@ -98,7 +96,7 @@ def test_spinner_with_width(self):
field_value,
) in enumerate(test_cases):
with self.subTest(width_value=width_value):
with spinner(f"test text {index}", width=width_value):
with st.spinner(f"test text {index}", width=width_value):
time.sleep(0.7)
el = self.get_delta_from_queue().new_element
assert el.spinner.text == f"test text {index}"
Expand Down Expand Up @@ -132,13 +130,13 @@ def test_spinner_with_invalid_width(self):
for width_value, expected_error_message in test_cases:
with self.subTest(width_value=width_value):
with pytest.raises(StreamlitAPIException) as exc:
with spinner("test text", width=width_value):
with st.spinner("test text", width=width_value):
time.sleep(0.1)
assert str(exc.value) == expected_error_message

def test_spinner_default_width(self):
"""Test that st.spinner defaults to content width."""
with spinner("test text"):
with st.spinner("test text"):
time.sleep(0.7)
el = self.get_delta_from_queue().new_element
assert el.spinner.text == "test text"
Expand Down
Loading