This is a small python API to be able to display a busy spinner using emojis for a textual bot that run lengthy async jobs without access to a percentage of completion and need to provide feedback so the user knows the bot isn't stuck. I'm looking for any remarks (general, code quality, ease of use, ease of use of the API, code smell, ... ?)

How the code is used:

for progress_bar_emoji in SlackBusySpinner(timeout=3600):
    # Do something and react with progress_bar_emoji 
    # until job.is_finished or it timeout after 3600 s
    if job.is_finished:


from __future__ import annotations

class SlackBusySpinner:
    def __init__(self, timeout: int | None = None):
        self.__progress_bar_index = 0
        self.timeout = timeout

    def __iter__(self):
        return self

    def __next__(self) -> str:
        if self.timeout is not None and self.__progress_bar_index > self.timeout:
            raise StopIteration
        minutes = "30" if self.__progress_bar_index % 2 else ""
        clock_number = (self.__progress_bar_index % 24 // 2) + 1
        emoji = f"clock{clock_number}{minutes}"
        self.__progress_bar_index += 1
        return emoji


from emoji_progress_bar import SlackBusySpinner

def test_clock_emoji() -> None:
    expected = [
        "clock1",  # πŸ•
        "clock130", # πŸ•œ
        "clock2",  # πŸ•‘
        "clock230", # etc.
    emojis = []
    slack_answer = SlackBusySpinner()
    for _ in range(48):
    assert emojis == expected * 2


πŸ• then πŸ•œ, then πŸ•‘, etc. ...back to πŸ• then πŸ•œ, then πŸ•‘, etc.


1 Answer 1


I don't think it's a good idea to conflate your display spinner with a timeout. These should be separated.

Don't double-underscore your index; that's reserved for a different purpose.

clock_number is just "hour".

Your test can be simplified by using islice instead of successive concatenation:

import itertools

class SlackBusySpinner:
    def __init__(self) -> None:
        self._index = 0

    def __iter__(self) -> 'SlackBusySpinner':
        return self

    def __next__(self) -> str:
        minute = '30' if self._index % 2 else ''
        hour = (self._index % 24 // 2) + 1
        emoji = f'clock{hour}{minute}'
        self._index += 1
        return emoji

def test_clock_emoji() -> None:
    expected = (
        "clock1",  "clock130",
        "clock2",  "clock230",
        "clock3",  "clock330",
        "clock4",  "clock430",
        "clock5",  "clock530",
        "clock6",  "clock630",
        "clock7",  "clock730",
        "clock8",  "clock830",
        "clock9",  "clock930",
        "clock10", "clock1030",
        "clock11", "clock1130",
        "clock12", "clock1230",
    emojis = tuple(itertools.islice(SlackBusySpinner(), 48))
    assert emojis == expected * 2

if __name__ == '__main__':


There is an alternative with trade-offs: write out the clock face unicode code points, and put them in a cycle. This might be able to render better for text-only clients (not that those are common for Slack), and it might also place a smaller performance burden than Slack emoji lookup and render; but it also carries a risk that the client doesn't have a good font.

import itertools

    '\U0001F55B' '\U0001F567'  # 1200
    '\U0001F550' '\U0001F55C'  # 0100
    '\U0001F551' '\U0001F55D'  # 0200
    '\U0001F552' '\U0001F55E'  # 0300
    '\U0001F553' '\U0001F55F'  # 0400
    '\U0001F554' '\U0001F560'  # 0500
    '\U0001F555' '\U0001F561'  # 0600
    '\U0001F556' '\U0001F562'  # 0700
    '\U0001F557' '\U0001F563'  # 0800
    '\U0001F558' '\U0001F564'  # 0900
    '\U0001F559' '\U0001F565'  # 1000
    '\U0001F55A' '\U0001F566'  # 1100

SlackBusySpinner = itertools.cycle(CLOCK_POINTS)
print(' '.join(itertools.islice(SlackBusySpinner, 48)))


πŸ•› πŸ•§ πŸ• πŸ•œ πŸ•‘ πŸ• πŸ•’ πŸ•ž πŸ•“ πŸ•Ÿ πŸ•” πŸ•  πŸ•• πŸ•‘ πŸ•– πŸ•’ πŸ•— πŸ•£ πŸ•˜ πŸ•€ πŸ•™ πŸ•₯ πŸ•š πŸ•¦ πŸ•› πŸ•§ πŸ• πŸ•œ πŸ•‘ πŸ• πŸ•’ πŸ•ž πŸ•“ πŸ•Ÿ πŸ•” πŸ•  πŸ•• πŸ•‘ πŸ•– πŸ•’ πŸ•— πŸ•£ πŸ•˜ πŸ•€ πŸ•™ πŸ•₯ πŸ•š πŸ•¦

  • \$\begingroup\$ Thank you for the insightful answer. I wanted to make the index private to hide the "implementation details" of the class. What would be the purpose of the double underscore if not that ? But I guess it can be useful to access it to know the number of tick if used with next. \$\endgroup\$ Sep 29 at 20:58
  • 1
    \$\begingroup\$ (a) no amount of underscores will completely prevent someone from accessing the member; "private" is only a suggestion in Python; but \$\endgroup\$
    – Reinderien
    Sep 29 at 21:15
  • \$\begingroup\$ (b) read docs.python.org/3.11/reference/… . \$\endgroup\$
    – Reinderien
    Sep 29 at 21:16
  • \$\begingroup\$ By convention, one underscore means "private". Two underscores means "name-mangle". But the whole concept of private variables in Python is rather flimsy. \$\endgroup\$
    – Reinderien
    Sep 29 at 21:28
  • \$\begingroup\$ I always considered one underscore as protected because the attribute keep being accessible in child classes, whereas two leading underscores is private. According to the doc you linked "two or more underscore characters (...) is considered a private name of that class". \$\endgroup\$ Sep 30 at 19:05

Your Answer

By clicking β€œPost Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.