├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── demo.gif ├── progress ├── __init__.py ├── bar.py ├── colors.py ├── counter.py └── spinner.py ├── setup.py └── test_progress.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | build/ 4 | dist/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 Georgios Verigakis 2 | # 3 | # Permission to use, copy, modify, and distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE 2 | include test_*.py 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Easy progress reporting for Python 2 | ================================== 3 | 4 | |pypi| 5 | 6 | |demo| 7 | 8 | .. |pypi| image:: https://img.shields.io/pypi/v/progress.svg 9 | :target: https://pypi.org/project/progress/ 10 | .. |demo| image:: https://raw.github.com/verigak/progress/master/demo.gif 11 | :alt: Demo 12 | 13 | Bars 14 | ---- 15 | 16 | There are 7 progress bars to choose from: 17 | 18 | - ``Bar`` 19 | - ``ChargingBar`` 20 | - ``FillingSquaresBar`` 21 | - ``FillingCirclesBar`` 22 | - ``IncrementalBar`` 23 | - ``PixelBar`` 24 | - ``ShadyBar`` 25 | 26 | To use them, just call ``next`` to advance and ``finish`` to finish: 27 | 28 | .. code-block:: python 29 | 30 | from progress.bar import Bar 31 | 32 | bar = Bar('Processing', max=20) 33 | for i in range(20): 34 | # Do some work 35 | bar.next() 36 | bar.finish() 37 | 38 | or use any bar of this class as a context manager: 39 | 40 | .. code-block:: python 41 | 42 | from progress.bar import Bar 43 | 44 | with Bar('Processing', max=20) as bar: 45 | for i in range(20): 46 | # Do some work 47 | bar.next() 48 | 49 | The result will be a bar like the following: :: 50 | 51 | Processing |############# | 42/100 52 | 53 | To simplify the common case where the work is done in an iterator, you can 54 | use the ``iter`` method: 55 | 56 | .. code-block:: python 57 | 58 | for i in Bar('Processing').iter(it): 59 | # Do some work 60 | 61 | Progress bars are very customizable, you can change their width, their fill 62 | character, their suffix and more: 63 | 64 | .. code-block:: python 65 | 66 | bar = Bar('Loading', fill='@', suffix='%(percent)d%%') 67 | 68 | This will produce a bar like the following: :: 69 | 70 | Loading |@@@@@@@@@@@@@ | 42% 71 | 72 | You can use a number of template arguments in ``message`` and ``suffix``: 73 | 74 | ========== ================================ 75 | Name Value 76 | ========== ================================ 77 | index current value 78 | max maximum value 79 | remaining max - index 80 | progress index / max 81 | percent progress * 100 82 | avg simple moving average time per item (in seconds) 83 | elapsed elapsed time in seconds 84 | elapsed_td elapsed as a timedelta (useful for printing as a string) 85 | eta avg * remaining 86 | eta_td eta as a timedelta (useful for printing as a string) 87 | ========== ================================ 88 | 89 | Instead of passing all configuration options on instantiation, you can create 90 | your custom subclass: 91 | 92 | .. code-block:: python 93 | 94 | class FancyBar(Bar): 95 | message = 'Loading' 96 | fill = '*' 97 | suffix = '%(percent).1f%% - %(eta)ds' 98 | 99 | You can also override any of the arguments or create your own: 100 | 101 | .. code-block:: python 102 | 103 | class SlowBar(Bar): 104 | suffix = '%(remaining_hours)d hours remaining' 105 | @property 106 | def remaining_hours(self): 107 | return self.eta // 3600 108 | 109 | 110 | Spinners 111 | ======== 112 | 113 | For actions with an unknown number of steps you can use a spinner: 114 | 115 | .. code-block:: python 116 | 117 | from progress.spinner import Spinner 118 | 119 | spinner = Spinner('Loading ') 120 | while state != 'FINISHED': 121 | # Do some work 122 | spinner.next() 123 | 124 | There are 5 predefined spinners: 125 | 126 | - ``Spinner`` 127 | - ``PieSpinner`` 128 | - ``MoonSpinner`` 129 | - ``LineSpinner`` 130 | - ``PixelSpinner`` 131 | 132 | Installation 133 | ============ 134 | 135 | Download from PyPi 136 | 137 | .. code-block:: shell 138 | 139 | pip install progress 140 | 141 | 142 | Other 143 | ===== 144 | 145 | There are a number of other classes available too, please check the source or 146 | subclass one of them to create your own. 147 | 148 | 149 | License 150 | ======= 151 | 152 | progress is licensed under ISC 153 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verigak/progress/1f658bc6277d607c7bfadb33680105fa5c7c27f2/demo.gif -------------------------------------------------------------------------------- /progress/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 Georgios Verigakis 2 | # 3 | # Permission to use, copy, modify, and distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | from __future__ import division, print_function 16 | 17 | from collections import deque 18 | from datetime import timedelta 19 | from math import ceil 20 | from sys import stderr 21 | try: 22 | from time import monotonic 23 | except ImportError: 24 | from time import time as monotonic 25 | 26 | 27 | __version__ = '1.6' 28 | 29 | HIDE_CURSOR = '\x1b[?25l' 30 | SHOW_CURSOR = '\x1b[?25h' 31 | 32 | 33 | class Infinite(object): 34 | file = stderr 35 | sma_window = 10 # Simple Moving Average window 36 | check_tty = True 37 | hide_cursor = True 38 | 39 | def __init__(self, message='', **kwargs): 40 | self.index = 0 41 | self.start_ts = monotonic() 42 | self.avg = 0 43 | self._avg_update_ts = self.start_ts 44 | self._ts = self.start_ts 45 | self._xput = deque(maxlen=self.sma_window) 46 | for key, val in kwargs.items(): 47 | setattr(self, key, val) 48 | 49 | self._max_width = 0 50 | self._hidden_cursor = False 51 | self.message = message 52 | 53 | if self.file and self.is_tty(): 54 | if self.hide_cursor: 55 | print(HIDE_CURSOR, end='', file=self.file) 56 | self._hidden_cursor = True 57 | self.writeln('') 58 | 59 | def __del__(self): 60 | if self._hidden_cursor: 61 | print(SHOW_CURSOR, end='', file=self.file) 62 | 63 | def __getitem__(self, key): 64 | if key.startswith('_'): 65 | return None 66 | return getattr(self, key, None) 67 | 68 | @property 69 | def elapsed(self): 70 | return int(monotonic() - self.start_ts) 71 | 72 | @property 73 | def elapsed_td(self): 74 | return timedelta(seconds=self.elapsed) 75 | 76 | def update_avg(self, n, dt): 77 | if n > 0: 78 | xput_len = len(self._xput) 79 | self._xput.append(dt / n) 80 | now = monotonic() 81 | # update when we're still filling _xput, then after every second 82 | if (xput_len < self.sma_window or 83 | now - self._avg_update_ts > 1): 84 | self.avg = sum(self._xput) / len(self._xput) 85 | self._avg_update_ts = now 86 | 87 | def update(self): 88 | pass 89 | 90 | def start(self): 91 | pass 92 | 93 | def writeln(self, line): 94 | if self.file and self.is_tty(): 95 | width = len(line) 96 | if width < self._max_width: 97 | # Add padding to cover previous contents 98 | line += ' ' * (self._max_width - width) 99 | else: 100 | self._max_width = width 101 | print('\r' + line, end='', file=self.file) 102 | self.file.flush() 103 | 104 | def finish(self): 105 | if self.file and self.is_tty(): 106 | print(file=self.file) 107 | if self._hidden_cursor: 108 | print(SHOW_CURSOR, end='', file=self.file) 109 | self._hidden_cursor = False 110 | 111 | def is_tty(self): 112 | try: 113 | return self.file.isatty() if self.check_tty else True 114 | except AttributeError: 115 | msg = "%s has no attribute 'isatty'. Try setting check_tty=False." % self 116 | raise AttributeError(msg) 117 | 118 | def next(self, n=1): 119 | now = monotonic() 120 | dt = now - self._ts 121 | self.update_avg(n, dt) 122 | self._ts = now 123 | self.index = self.index + n 124 | self.update() 125 | 126 | def iter(self, it): 127 | self.iter_value = None 128 | with self: 129 | for x in it: 130 | self.iter_value = x 131 | yield x 132 | self.next() 133 | del self.iter_value 134 | 135 | def __enter__(self): 136 | self.start() 137 | return self 138 | 139 | def __exit__(self, exc_type, exc_val, exc_tb): 140 | self.finish() 141 | 142 | 143 | class Progress(Infinite): 144 | def __init__(self, *args, **kwargs): 145 | super(Progress, self).__init__(*args, **kwargs) 146 | self.max = kwargs.get('max', 100) 147 | 148 | @property 149 | def eta(self): 150 | return int(ceil(self.avg * self.remaining)) 151 | 152 | @property 153 | def eta_td(self): 154 | return timedelta(seconds=self.eta) 155 | 156 | @property 157 | def percent(self): 158 | return self.progress * 100 159 | 160 | @property 161 | def progress(self): 162 | if self.max == 0: 163 | return 0 164 | return min(1, self.index / self.max) 165 | 166 | @property 167 | def remaining(self): 168 | return max(self.max - self.index, 0) 169 | 170 | def start(self): 171 | self.update() 172 | 173 | def goto(self, index): 174 | incr = index - self.index 175 | self.next(incr) 176 | 177 | def iter(self, it): 178 | try: 179 | self.max = len(it) 180 | except TypeError: 181 | pass 182 | 183 | self.iter_value = None 184 | with self: 185 | for x in it: 186 | self.iter_value = x 187 | yield x 188 | self.next() 189 | del self.iter_value 190 | -------------------------------------------------------------------------------- /progress/bar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) 2012 Georgios Verigakis 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | from __future__ import unicode_literals 18 | 19 | import sys 20 | 21 | from . import Progress 22 | from .colors import color 23 | 24 | 25 | class Bar(Progress): 26 | width = 32 27 | suffix = '%(index)d/%(max)d' 28 | bar_prefix = ' |' 29 | bar_suffix = '| ' 30 | empty_fill = ' ' 31 | fill = '#' 32 | color = None 33 | 34 | def update(self): 35 | filled_length = int(self.width * self.progress) 36 | empty_length = self.width - filled_length 37 | 38 | message = self.message % self 39 | bar = color(self.fill * filled_length, fg=self.color) 40 | empty = self.empty_fill * empty_length 41 | suffix = self.suffix % self 42 | line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix, 43 | suffix]) 44 | self.writeln(line) 45 | 46 | 47 | class ChargingBar(Bar): 48 | suffix = '%(percent)d%%' 49 | bar_prefix = ' ' 50 | bar_suffix = ' ' 51 | empty_fill = '∙' 52 | fill = '█' 53 | 54 | 55 | class FillingSquaresBar(ChargingBar): 56 | empty_fill = '▢' 57 | fill = '▣' 58 | 59 | 60 | class FillingCirclesBar(ChargingBar): 61 | empty_fill = '◯' 62 | fill = '◉' 63 | 64 | 65 | class IncrementalBar(Bar): 66 | if sys.platform.startswith('win'): 67 | phases = (u' ', u'▌', u'█') 68 | else: 69 | phases = (' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█') 70 | 71 | def update(self): 72 | nphases = len(self.phases) 73 | filled_len = self.width * self.progress 74 | nfull = int(filled_len) # Number of full chars 75 | phase = int((filled_len - nfull) * nphases) # Phase of last char 76 | nempty = self.width - nfull # Number of empty chars 77 | 78 | message = self.message % self 79 | bar = color(self.phases[-1] * nfull, fg=self.color) 80 | current = self.phases[phase] if phase > 0 else '' 81 | empty = self.empty_fill * max(0, nempty - len(current)) 82 | suffix = self.suffix % self 83 | line = ''.join([message, self.bar_prefix, bar, current, empty, 84 | self.bar_suffix, suffix]) 85 | self.writeln(line) 86 | 87 | 88 | class PixelBar(IncrementalBar): 89 | phases = ('⡀', '⡄', '⡆', '⡇', '⣇', '⣧', '⣷', '⣿') 90 | 91 | 92 | class ShadyBar(IncrementalBar): 93 | phases = (' ', '░', '▒', '▓', '█') 94 | -------------------------------------------------------------------------------- /progress/colors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) 2020 Georgios Verigakis 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | from functools import partial 18 | 19 | 20 | COLORS = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 21 | 'white') 22 | STYLES = ('bold', 'faint', 'italic', 'underline', 'blink', 'blink2', 23 | 'negative', 'concealed', 'crossed') 24 | 25 | 26 | def color(s, fg=None, bg=None, style=None): 27 | sgr = [] 28 | 29 | if fg: 30 | if fg in COLORS: 31 | sgr.append(str(30 + COLORS.index(fg))) 32 | elif isinstance(fg, int) and 0 <= fg <= 255: 33 | sgr.append('38;5;%d' % int(fg)) 34 | else: 35 | raise Exception('Invalid color "%s"' % fg) 36 | 37 | if bg: 38 | if bg in COLORS: 39 | sgr.append(str(40 + COLORS.index(bg))) 40 | elif isinstance(bg, int) and 0 <= bg <= 255: 41 | sgr.append('48;5;%d' % bg) 42 | else: 43 | raise Exception('Invalid color "%s"' % bg) 44 | 45 | if style: 46 | for st in style.split('+'): 47 | if st in STYLES: 48 | sgr.append(str(1 + STYLES.index(st))) 49 | else: 50 | raise Exception('Invalid style "%s"' % st) 51 | 52 | if sgr: 53 | prefix = '\x1b[' + ';'.join(sgr) + 'm' 54 | suffix = '\x1b[0m' 55 | return prefix + s + suffix 56 | else: 57 | return s 58 | 59 | 60 | # Foreground shortcuts 61 | black = partial(color, fg='black') 62 | red = partial(color, fg='red') 63 | green = partial(color, fg='green') 64 | yellow = partial(color, fg='yellow') 65 | blue = partial(color, fg='blue') 66 | magenta = partial(color, fg='magenta') 67 | cyan = partial(color, fg='cyan') 68 | white = partial(color, fg='white') 69 | 70 | # Style shortcuts 71 | bold = partial(color, style='bold') 72 | faint = partial(color, style='faint') 73 | italic = partial(color, style='italic') 74 | underline = partial(color, style='underline') 75 | blink = partial(color, style='blink') 76 | blink2 = partial(color, style='blink2') 77 | negative = partial(color, style='negative') 78 | concealed = partial(color, style='concealed') 79 | crossed = partial(color, style='crossed') 80 | -------------------------------------------------------------------------------- /progress/counter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) 2012 Georgios Verigakis 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | from __future__ import unicode_literals 18 | from . import Infinite, Progress 19 | 20 | 21 | class Counter(Infinite): 22 | def update(self): 23 | message = self.message % self 24 | line = ''.join([message, str(self.index)]) 25 | self.writeln(line) 26 | 27 | 28 | class Countdown(Progress): 29 | def update(self): 30 | message = self.message % self 31 | line = ''.join([message, str(self.remaining)]) 32 | self.writeln(line) 33 | 34 | 35 | class Stack(Progress): 36 | phases = (' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█') 37 | 38 | def update(self): 39 | nphases = len(self.phases) 40 | i = min(nphases - 1, int(self.progress * nphases)) 41 | message = self.message % self 42 | line = ''.join([message, self.phases[i]]) 43 | self.writeln(line) 44 | 45 | 46 | class Pie(Stack): 47 | phases = ('○', '◔', '◑', '◕', '●') 48 | -------------------------------------------------------------------------------- /progress/spinner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) 2012 Georgios Verigakis 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | from __future__ import unicode_literals 18 | from . import Infinite 19 | 20 | 21 | class Spinner(Infinite): 22 | phases = ('-', '\\', '|', '/') 23 | hide_cursor = True 24 | 25 | def update(self): 26 | i = self.index % len(self.phases) 27 | message = self.message % self 28 | line = ''.join([message, self.phases[i]]) 29 | self.writeln(line) 30 | 31 | 32 | class PieSpinner(Spinner): 33 | phases = ['◷', '◶', '◵', '◴'] 34 | 35 | 36 | class MoonSpinner(Spinner): 37 | phases = ['◑', '◒', '◐', '◓'] 38 | 39 | 40 | class LineSpinner(Spinner): 41 | phases = ['⎺', '⎻', '⎼', '⎽', '⎼', '⎻'] 42 | 43 | 44 | class PixelSpinner(Spinner): 45 | phases = ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽'] 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | import progress 6 | 7 | 8 | setup( 9 | name='progress', 10 | version=progress.__version__, 11 | description='Easy to use progress bars', 12 | long_description=open('README.rst').read(), 13 | author='Georgios Verigakis', 14 | author_email='verigak@gmail.com', 15 | url='http://github.com/verigak/progress/', 16 | license='ISC', 17 | packages=['progress'], 18 | classifiers=[ 19 | 'Environment :: Console', 20 | 'Intended Audience :: Developers', 21 | 'License :: OSI Approved :: ISC License (ISCL)', 22 | 'Programming Language :: Python :: 2.6', 23 | 'Programming Language :: Python :: 2.7', 24 | 'Programming Language :: Python :: 3.3', 25 | 'Programming Language :: Python :: 3.4', 26 | 'Programming Language :: Python :: 3.5', 27 | 'Programming Language :: Python :: 3.6', 28 | 'Programming Language :: Python :: 3.7', 29 | 'Programming Language :: Python :: 3.8', 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /test_progress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | 5 | import random 6 | import time 7 | 8 | from progress.bar import (Bar, ChargingBar, FillingSquaresBar, 9 | FillingCirclesBar, IncrementalBar, PixelBar, 10 | ShadyBar) 11 | from progress.spinner import (Spinner, PieSpinner, MoonSpinner, LineSpinner, 12 | PixelSpinner) 13 | from progress.counter import Counter, Countdown, Stack, Pie 14 | from progress.colors import bold 15 | 16 | 17 | def sleep(): 18 | t = 0.01 19 | t += t * random.uniform(-0.1, 0.1) # Add some variance 20 | time.sleep(t) 21 | 22 | 23 | for bar_cls in (Bar, ChargingBar, FillingSquaresBar, FillingCirclesBar): 24 | suffix = '%(index)d/%(max)d [%(elapsed)d / %(eta)d / %(eta_td)s] (%(iter_value)s)' 25 | bar = bar_cls(bar_cls.__name__, suffix=suffix) 26 | for i in bar.iter(range(200, 400)): 27 | sleep() 28 | 29 | for bar_cls in (IncrementalBar, PixelBar, ShadyBar): 30 | suffix = '%(percent)d%% [%(elapsed_td)s / %(eta)d / %(eta_td)s]' 31 | with bar_cls(bar_cls.__name__, suffix=suffix, max=200) as bar: 32 | for i in range(200): 33 | bar.next() 34 | sleep() 35 | 36 | bar = IncrementalBar(bold('Colored'), color='green') 37 | for i in bar.iter(range(200)): 38 | sleep() 39 | 40 | for spin in (Spinner, PieSpinner, MoonSpinner, LineSpinner, PixelSpinner): 41 | for i in spin(spin.__name__ + ' %(index)d ').iter(range(100)): 42 | sleep() 43 | 44 | for singleton in (Counter, Countdown, Stack, Pie): 45 | for i in singleton(singleton.__name__ + ' ').iter(range(100)): 46 | sleep() 47 | 48 | bar = IncrementalBar('Random', suffix='%(index)d') 49 | for i in range(100): 50 | bar.goto(random.randint(0, 100)) 51 | sleep() 52 | bar.finish() 53 | --------------------------------------------------------------------------------