Animated Terminal Progress Bar in Python
In my previous article python terminal controller, I explained a simple way to control display in the terminal and print in colors. In this article I'm going to use the terminal module to draw a fancy animated progress bar.
The following module simply draws an animated progress bar in the terminal along with a message. The progress bar will auto adjust itself to fit. And the message can spread over multiple lines.
# -*- coding: utf-8 -*- # Copyright: 2009 Nadia Alramli # License: BSD """Draws an animated terminal progress bar Usage: p = ProgressBar("blue") p.render(percentage, message) """ import terminal import sys class ProgressBar(object): """Terminal progress bar class""" TEMPLATE = ( '%(percent)-2s%% %(color)s%(progress)s%(normal)s%(empty)s %(message)s\n' ) PADDING = 7 def __init__(self, color=None, width=None, block='█', empty=' '): """ color -- color name (BLUE GREEN CYAN RED MAGENTA YELLOW WHITE BLACK) width -- bar width (optinal) block -- progress display character (default '█') empty -- bar display character (default ' ') """ if color: self.color = getattr(terminal, color.upper()) else: self.color = '' if width and width < terminal.COLUMNS - self.PADDING: self.width = width else: # Adjust to the width of the terminal self.width = terminal.COLUMNS - self.PADDING self.block = block self.empty = empty self.progress = None self.lines = 0 def render(self, percent, message = ''): """Print the progress bar percent -- the progress percentage % message -- message string (optional) """ inline_msg_len = 0 if message: # The length of the first line in the message inline_msg_len = len(message.splitlines()[0]) if inline_msg_len + self.width + self.PADDING > terminal.COLUMNS: # The message is too long to fit in one line. # Adjust the bar width to fit. bar_width = terminal.COLUMNS - inline_msg_len -self.PADDING else: bar_width = self.width # Check if render is called for the first time if self.progress != None: self.clear() self.progress = (bar_width * percent) / 100 data = self.TEMPLATE % { 'percent': percent, 'color': self.color, 'progress': self.block * self.progress, 'normal': terminal.NORMAL, 'empty': self.empty * (bar_width - self.progress), 'message': message } sys.stdout.write(data) sys.stdout.flush() # The number of lines printed self.lines = len(data.splitlines()) def clear(self): """Clear all printed lines""" sys.stdout.write( self.lines * (terminal.UP + terminal.BOL + terminal.CLEAR_EOL) )
This module is very easy to use, for example:
from progressbar import ProgressBar import time p = ProgressBar() for i in range(101): p.render(i, 'step %s' % i) time.sleep(0.1)

You can pass in any character to be used as a progress display. You can change the color and width as well. Here is another fancy example:
from progressbar import ProgressBar import time p = ProgressBar('green', width=20, block='▣', empty='□') for i in range(101): p.render(i, 'step %s\nProcessing...\nDescription: write something.' % i) time.sleep(0.1)

Here is a useful list of unicode characters. Any of these characters can be used to draw the progress bar if needed.
| Attachment | Size |
|---|---|
| Progress Bar Module | 2.49 KB |









Comments
Using as GPL
Hello
Can I use your code as GPL?
My project(PyGlossary) is under GPLv3 and I need to relicense your code to GPL and use in my own project.
Sure
Sure, no problem at all.
FYI You don't need to
FYI You don't need to relicense to include BSD code in a GPL project.
Errors on Leopard 10.5.8
Hi, thanks for making this. I'm using a Mac and I got this error when I used it:
Warning: setupterm: could not find terminfo database
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'
function __init__ in progressbar.py at line 35
self.width = terminal.COLUMNS - self.PADDING
I'm using Python 2.5.1
Ooops
Sorry, I was running the app on Textmate. I ran it using the terminal and it worked perfectly! However, the number of records I'm processing is in the range of 20,000+. How do I make the display more or less accurate?
p = ProgressBar()
while count < len(contents):
if contents[count][0] != '':
cursor.execute("""
UPDATE accountscf SET cf_580 = %s, cf_568 = %s, cf_569 = %s, cf_571 = %s, cf_572 = %s
WHERE cf_535 = %s""" % (contents[count][1], contents[count][2], contents[count][3], contents[count][4], contents[count][5], contents[count][0]))
p.render(count, 'step %s' % count)
time.sleep(0.1)
count += 1
Use the percentage instead.
Use the percentage instead. For example:
total = 20000
p.render((count * 100) / total, 'step %s' % count)
animated spinner
Very nice. I love the filled in squares version. On a related note here is a spinner I did for when you don't know how long it will take:
http://www.pixelbeat.org/talks/python/spinner.py
I just discovered this today
I just discovered this today and I absolutely love the simplicity of it. I have implemented progress bars for command-line tools at work in the past but none were this elegant. I am already looking into how I can integrate it and rip out my old kludgy implementation. Thank you for sharing!
progressbar module in python archives
Nice script! I dont know if this is common knowledge. But, there is a well covered python module at http://pypi.python.org/pypi/progressbar/2.2
Interesting I didn't know
Interesting I didn't know about this module before. I'll check it out.
I was just about to post that
I was just about to post that link, but Ros beat me to it.
What about merging your additional features back into the progressbar package in PyPi?
I didn't know about this
I didn't know about this package before so I need to try it first. I wrote this module for personal use at first, then I decided to share it here.
BTW
You made it on reddit:
http://www.reddit.com/r/Python/comments/b94dt/animated_terminal_progress...
It is great to see my page in
It is great to see my blog on my favorite website :) - A fellow redditor
Redirection
I'd love to use this in a project, but I noticed that when I export the stdout to a file, it just looks all messed up. Is there some way to detect if they are doing that and then print something else instead of using the Progress Bar?
Thanks.
I don't think it is possible.
I don't think it is possible. A workaround is to modify the progressbar module to use sys.stderr instead of sys.stdout. But it's not a good idea since the progress is not an error.
I only just read this
I only just read this article, so this is a bit late but thought I'd add it anyway.
You can use the following code to determine if stdout is a terminal and only display the progress bar if it is.
os.isatty(sys.stdout.fileno())Nice one!
Nice work, Nadia.
Love the customizability and the code. Thank you for this :)
Post new comment