# 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()