# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
#
# 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 contextlib
import threading
from typing import Iterator
import streamlit as st
from streamlit.runtime.scriptrunner import add_script_run_ctx
@contextlib.contextmanager
def spinner(text: str = "In progress...", *, _cache: bool = False) -> Iterator[None]:
"""Temporarily displays a message while executing a block of code.
Parameters
----------
text : str
A message to display while executing that block
Example
-------
>>> import time
>>> import streamlit as st
>>>
>>> with st.spinner('Wait for it...'):
>>> time.sleep(5)
>>> st.success('Done!')
"""
from streamlit.proto.Spinner_pb2 import Spinner as SpinnerProto
from streamlit.string_util import clean_text
message = st.empty()
# Set the message 0.5 seconds in the future to avoid annoying
# flickering if this spinner runs too quickly.
DELAY_SECS = 0.5
display_message = True
display_message_lock = threading.Lock()
try:
def set_message():
with display_message_lock:
if display_message:
spinner_proto = SpinnerProto()
spinner_proto.text = clean_text(text)
spinner_proto.cache = _cache
message._enqueue("spinner", spinner_proto)
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()