Steve R
Steve R

Reputation: 151

Python CLI Progress bar/spinner WITHOUT iteration

There are numerous existing questions regarding the display of progress bars in the terminal while a Python script executes, but every one of them is based on a loop where you perform an operation and then update the progress graphic.

Unfortunately, the function whose progress I want to show--or at least a spinner object to show that it's working--is a black-box that I can't (at least really, really shouldn't) alter. Essentially, what I want to do is:

#pseudocode input
print('Loading')
spinner.begin()
blackbox() #a few thousand operations happen in here
spinner.end()
print('Finished')

#pseudocode output
Loading.
Loading..
Loading...
Loading.
Loading..
Loading...
Finished

Although ideally that would be an animation of the ellipsis instead of printing multiple lines. Before I can even start building silly ascii animations though, there's the main hurdle:

Is there a way to run spinner and blackbox() at the same time? Alternately, is there a hack to pause blackbox(), regardless of its content, every few hundred milliseconds, update the spinner graphic, and then resume where it left off?

I've tried this with the progress module but had no luck... I couldn't even get the example code to work, it just hung up after I started iterating until I Ctrl+C'd out.

Upvotes: 4

Views: 5292

Answers (3)

crypdick
crypdick

Reputation: 19904

I like using alive_progress for this.

alive_bar spinner

from typing import ContextManager, Optional
from alive_progress import alive_bar

def spinner(title: Optional[str] = None) -> ContextManager:
    """
    Context manager to display a spinner while a long-running process is running.

    Usage:
        with spinner("Fetching data..."):
            fetch_data()

    Args:
        title: The title of the spinner. If None, no title will be displayed.
    """
    return alive_bar(monitor=None, stats=None, title=title)

To install: pip install alive-progress

Upvotes: 7

FamousJameous
FamousJameous

Reputation: 1583

Threads is probably the easiest way to make this work. Here is a vastly simplified version that should get the point across. I wasn't sure whether you actually have the spinner function or not, so I made my own.

import threading
import time

def blackbox():
    time.sleep(10)

thread = threading.Thread(target=blackbox)
thread.start()

eli_count = 0
while thread.is_alive():
    print('Loading', '.'*(eli_count+1), ' '*(2-eli_count), end='\r')
    eli_count = (eli_count + 1) % 3
    time.sleep(0.1)
thread.join()
print('Done      ')

So, while blackbox runs, the loading message is updated periodically. Once it finishes, the thread is joined and the loading message is replaced with a completed message.

Upvotes: 2

Re.po
Re.po

Reputation: 234

You probably want to use threads (import threading). Have spinner.begin() start a thread that prints your messages, then let your blackbox run, and then have spinner.end() send a finish message to the thread using a Queue (from Queue import Queue) or something, join() the thread and keep doing whatever it is you do.

As a design choice, hide the prints somewhere deeper, not in the same block of code as the begin and end calls.

Upvotes: 1

Related Questions