59 Commits
1.0.1 ... 1.5

Author SHA1 Message Date
Georgios Verigakis
efeb57282b Bump version 2019-03-06 09:25:32 +02:00
Georgios Verigakis
61627a8642 Merge pull request #61 from pquentin/throttle-eta
Update avg/eta/eta_td only once per second
2019-02-01 16:24:21 +02:00
Quentin Pradet
c1e07104e1 Update avg/eta/eta_td less frequently
When the progress bar is updated frequently (every few milliseconds),
the eta can change so quickly that it's impossible to read.

This commit updates the average while sma_window is being filled, then
after every second.

This means we're calling monotonic/time often, but those calls take less
than 100 nsec per loop on Linux, Windows and macOS [0], which is
equivalent to one attribute lookup or two.

[0]: https://github.com/python-trio/trio/issues/33
2019-02-01 18:16:14 +04:00
Quentin Pradet
326413a271 Use time.monotonic if available 2018-11-16 17:22:45 +04:00
Georgios Verigakis
1b326504ce Merge pull request #57 from Kilo59/patch-1
Add Pypi link and instuctions to ReadMe
2018-10-12 14:00:07 +03:00
Georgios Verigakis
b95b764e37 Merge pull request #54 from dotlambda/patch-1
Include tests in PyPI tarball
2018-10-12 13:59:02 +03:00
Gabriel
2eeff94083 Add Pypi link and instuctions to ReadMe
Made Pypi badge link to Pypi page.

Added Installation instructions
2018-10-11 10:48:10 -04:00
Robert Schütz
8cf7faa4e3 Include tests in PyPI tarball 2018-10-10 09:32:21 +02:00
Georgios Verigakis
4b7c9388f4 Remove SigIntMixin
This was meant mostly as an example, it doesn't justify its own file.
2018-09-13 10:37:21 +03:00
Georgios Verigakis
d9d40736d6 Fold the mixins in __init__.py
The 2 mixins were almost the same, simplify the codebase by merging them in.
2018-09-13 10:34:24 +03:00
Georgios Verigakis
d407334bcf Merge pull request #29 from TobiX/contextmanager
Allow usage as a context manager.
2018-08-01 13:08:02 +03:00
Tobias Gruetzmacher
086cfd5599 Allow usage as a context manager. 2018-08-01 12:01:56 +02:00
Georgios Verigakis
6553b7b207 Implement write using \r instead of \b
This is to appease PyCharm that doesn't implement \b.
2018-07-10 13:32:05 +03:00
Georgios Verigakis
1f19b5b61c Add flag to override tty check 2018-07-10 13:23:43 +03:00
Georgios Verigakis
f5c911ed83 Bump 2018-06-25 09:06:26 +03:00
Georgios Verigakis
5d52c5b299 Avoid unprintable chars on Windows
Fixes #36
2018-05-31 09:47:00 +03:00
Georgios Verigakis
a83f91f4b8 Handles the case where self.file is None
Fixes #46
2018-03-13 15:53:27 +02:00
Georgios Verigakis
292a031c4b Revert "Fixed broken character in windows" 2018-01-31 10:22:22 +02:00
Georgios Verigakis
83f3b79137 Merge pull request #44 from LiamGow/patch-1
Fixed broken character in windows
2018-01-30 09:43:54 +02:00
LiamGow
84c3b9197a Fixed broken character in windows 2018-01-29 21:39:28 -08:00
Georgios Verigakis
715a2e130f Fix copy paste typo 2017-04-10 14:40:06 +03:00
Georgios Verigakis
33ab0be1ec Bump 2017-04-10 14:34:56 +03:00
Georgios Verigakis
91d9d3cb05 Merge pull request #38 from moreati/pixels
PixelBar and PixelSpinner, based on Braille characters
2017-04-01 11:50:38 +03:00
Georgios Verigakis
6661bcbe1d Merge branch 'master' into pixels 2017-04-01 11:50:05 +03:00
Georgios Verigakis
d440d5bbaf Revert to simplistic eta estimation but make it easier to plug different algorithms 2017-04-01 11:44:02 +03:00
Alex Willmer
7ecf7594a4 Add PixelSpinner, based on braille characters 2017-03-16 20:33:50 +00:00
Alex Willmer
92665ef189 Add PixelBar, based on braille characters 2017-03-16 20:31:35 +00:00
Georgios Verigakis
c5043685c5 Merge pull request #32 from aduriseti/master
Reduce I/O Cost - no change to ETA algorithm
2016-10-30 10:25:00 +02:00
aduriseti
f1f6ea57da prevents I/O from dominating computation time when using progress 2016-09-26 21:17:01 -07:00
Georgios Verigakis
0b668811f9 Use xput for the calculation of eta
Refer to #24 for the discussion.
2016-03-08 09:04:18 +02:00
Georgios Verigakis
e61e49bbf4 Add some variance 2016-03-06 17:46:29 +02:00
Georgios Verigakis
61ac9b6980 Improve stats when progressing too quickly
Do not update stats if time between data points is less than
`time_threshold`.  This should fix the issue reported in #24.
2016-03-06 17:37:49 +02:00
Georgios Verigakis
0435756cf7 Add a demo gif 2016-01-28 20:47:23 +02:00
Georgios Verigakis
ded975221b Merge branch 'pr/15' 2016-01-27 11:42:30 +02:00
Georgios Verigakis
5e45abaa95 Merge branch 'pr/19' 2016-01-27 11:38:43 +02:00
Georgios Verigakis
80b54c1228 Merge branch 'pr/17' 2016-01-27 11:36:22 +02:00
Georgios Verigakis
36ba012ffb Fix rounding error
Reported by paparomeo
2016-01-27 11:32:36 +02:00
Georgios Verigakis
f1bfb28df9 Merge branch 'pr/10' 2016-01-27 11:14:24 +02:00
Marc Abramowitz
ee7831362c README.rst: Display class names in monospace font 2014-12-14 16:50:13 -08:00
Marc Abramowitz
85b59e0a1d README.rst: Syntax highlight Python code
Works on both GitHub and PyPI (after you do `python setup.py register`)
2014-12-14 15:51:04 -08:00
Sindre Johansen
94ff8dd979 Now iter() calls finish even on an exception
As I reported in issue #14 raising an exception in a loop over a
iterator got from a iter() call will not finalize the bar.

This commit uses a try, finally block to ensure that finish is always
called
2014-12-10 14:23:42 +01:00
Alireza Nourian
288ddf1ec5 use unicode_literals instead of old unicode specifier
this brings Python 3.2 compatibility
2013-12-25 09:15:48 +03:30
Giorgos Verigakis
723024a296 Bump 2013-11-28 14:49:16 +08:00
Giorgos Verigakis
a5981103cf Update documentation 2013-11-28 13:49:12 +08:00
Giorgos Verigakis
120b3e53b1 Refactor 2013-11-28 13:34:14 +08:00
Giorgos Verigakis
8b02a5b59f Allow custom properties in format strings 2013-11-28 13:05:05 +08:00
Giorgos Verigakis
e5cffc8c72 Add elapsed seconds 2013-11-25 12:53:30 +08:00
Giorgos Verigakis
41d5f916c1 Fix Python 3 output 2013-11-25 12:52:20 +08:00
Giorgos Verigakis
32dc3db6f1 Bump version 2013-01-22 10:57:06 +08:00
Giorgos Verigakis
fc605a7217 Encode to UTF8 before printing
Fixes issues with unicode in OSX 10.8
2013-01-22 10:53:41 +08:00
Giorgos Verigakis
84a67ab6cf Optionally increment by more than 1 in next
Fixes #4
2013-01-22 10:47:43 +08:00
Giorgos Verigakis
8f69000ab6 Add a SIGINT handling mixin
Inspired by @glenbot's pull request
2013-01-22 10:21:10 +08:00
Giorgos Verigakis
ddab8c7a2b Merge pull request #2 from shawnsi/master
ETA Logic
2013-01-21 17:54:00 -08:00
Shawn Siefkas
22dcfc24a7 Fixing the eta logic 2012-08-26 13:52:04 -05:00
Giorgos Verigakis
153733662f Bump version 2012-07-28 09:26:53 +08:00
Giorgos Verigakis
a3fb1b4fcc Add a start method
Use it to show progress without advancing
2012-07-18 10:50:56 +08:00
Giorgos Verigakis
3a3dea7069 Add hide/show cursor support 2012-05-09 16:57:08 +03:00
Giorgos Verigakis
c76da39d1a Use ANSI clear line instead of tracking line width 2012-05-09 16:56:17 +03:00
Giorgos Verigakis
32afe021ea Remove logic from update of base classes, so subclasses don't have to call super 2012-04-20 18:09:29 +03:00
10 changed files with 295 additions and 213 deletions

View File

@@ -1 +1,2 @@
include README.rst LICENSE include README.rst LICENSE
include test_*.py

View File

@@ -1,19 +1,31 @@
Easy progress reporting for Python Easy progress reporting for Python
================================== ==================================
|pypi|
|demo|
.. |pypi| image:: https://img.shields.io/pypi/v/progress.svg
:target: https://pypi.org/project/progress/
.. |demo| image:: https://raw.github.com/verigak/progress/master/demo.gif
:alt: Demo
Bars Bars
---- ----
There are 6 progress bars to choose from: There are 7 progress bars to choose from:
- Bar - ``Bar``
- ChargingBar - ``ChargingBar``
- FillingSquaresBar - ``FillingSquaresBar``
- FillingCirclesBar - ``FillingCirclesBar``
- IncrementalBar - ``IncrementalBar``
- ShadyBar - ``PixelBar``
- ``ShadyBar``
To use them, just call ``next`` to advance and ``finish`` to finish. :: To use them, just call ``next`` to advance and ``finish`` to finish:
.. code-block:: python
from progress.bar import Bar from progress.bar import Bar
@@ -23,18 +35,33 @@ To use them, just call ``next`` to advance and ``finish`` to finish. ::
bar.next() bar.next()
bar.finish() bar.finish()
or use any bar of this class as a context manager:
.. code-block:: python
from progress.bar import Bar
with Bar('Processing', max=20) as bar:
for i in range(20):
# Do some work
bar.next()
The result will be a bar like the following: :: The result will be a bar like the following: ::
Processing |############# | 42/100 Processing |############# | 42/100
To simplify the common case where the work is done in an iterator, you can To simplify the common case where the work is done in an iterator, you can
use the ``iter`` method. :: use the ``iter`` method:
.. code-block:: python
for i in Bar('Processing').iter(it): for i in Bar('Processing').iter(it):
# Do some work # Do some work
Progress bars are very customizable, you can change their width, their fill Progress bars are very customizable, you can change their width, their fill
character, their suffix and more. :: character, their suffix and more:
.. code-block:: python
bar = Bar('Loading', fill='@', suffix='%(percent)d%%') bar = Bar('Loading', fill='@', suffix='%(percent)d%%')
@@ -44,48 +71,82 @@ This will produce a bar like the following: ::
You can use a number of template arguments in ``message`` and ``suffix``: You can use a number of template arguments in ``message`` and ``suffix``:
========= ============================= ========== ================================
Name Value Name Value
========= ============================= ========== ================================
index current value index current value
max maximum value max maximum value
remaining max - index remaining max - index
progress index / max progress index / max
percent progress * 100 percent progress * 100
avg rolling average time per item (in seconds) avg simple moving average time per item (in seconds)
eta avg * remaining elapsed elapsed time in seconds
========= ============================= elapsed_td elapsed as a timedelta (useful for printing as a string)
eta avg * remaining
eta_td eta as a timedelta (useful for printing as a string)
========== ================================
Instead of passing all configuration options on instatiation, you can create Instead of passing all configuration options on instatiation, you can create
your custom subclass. :: your custom subclass:
.. code-block:: python
class FancyBar(Bar): class FancyBar(Bar):
message = 'Loading' message = 'Loading'
fill = '*' fill = '*'
suffix = '%(percent).1f%% - %(eta)ds' suffix = '%(percent).1f%% - %(eta)ds'
You can also override any of the arguments or create your own:
.. code-block:: python
class SlowBar(Bar):
suffix = '%(remaining_hours)d hours remaining'
@property
def remaining_hours(self):
return self.eta // 3600
Spinners Spinners
======== ========
For actions with an unknown number of steps you can use a spinner. :: For actions with an unknown number of steps you can use a spinner:
.. code-block:: python
from progress.spinner import Spinner from progress.spinner import Spinner
spinner = Spinner('Loading ') spinner = Spinner('Loading ')
while state != 'FINISHED': while state != 'FINISHED':
# Do some work # Do some work
spinner.next() spinner.next()
There are 4 predefined spinners: There are 5 predefined spinners:
- ``Spinner``
- ``PieSpinner``
- ``MoonSpinner``
- ``LineSpinner``
- ``PixelSpinner``
Installation
============
Download from PyPi
.. code-block:: shell
pip install progress
- Spinner
- PieSpinner
- MoonSpinner
- LineSpinner
Other Other
===== =====
Thera are a number of other classes available too, please check the source or There are a number of other classes available too, please check the source or
subclass one of them to create your own. subclass one of them to create your own.
License
=======
progress is licensed under ISC

BIN
demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 KiB

View File

@@ -12,99 +12,158 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import division from __future__ import division, print_function
from collections import deque
from datetime import timedelta
from math import ceil from math import ceil
from sys import stderr from sys import stderr
from time import time try:
from time import monotonic
except ImportError:
from time import time as monotonic
__version__ = '1.0.1' __version__ = '1.5'
HIDE_CURSOR = '\x1b[?25l'
SHOW_CURSOR = '\x1b[?25h'
class Infinite(object): class Infinite(object):
file = stderr file = stderr
avg_window = 10 sma_window = 10 # Simple Moving Average window
check_tty = True
def __init__(self, *args, **kwargs): hide_cursor = True
self.ctx = {}
for key, val in kwargs.items():
if hasattr(self, key):
setattr(self, key, val)
else:
self.ctx[key] = val
def __init__(self, message='', **kwargs):
self.index = 0 self.index = 0
self.avg = None self.start_ts = monotonic()
self._ts = time() self.avg = 0
self._avg_update_ts = self.start_ts
self._ts = self.start_ts
self._xput = deque(maxlen=self.sma_window)
for key, val in kwargs.items():
setattr(self, key, val)
def update_stats(self): self._width = 0
# Calculate moving average self.message = message
now = time()
dt = now - self._ts if self.file and self.is_tty():
self._ts = now if self.hide_cursor:
w = self.avg_window print(HIDE_CURSOR, end='', file=self.file)
self.avg = dt if self.avg is None else (dt + w * self.avg) / (w + 1) print(self.message, end='', file=self.file)
self.file.flush()
def __getitem__(self, key):
if key.startswith('_'):
return None
return getattr(self, key, None)
@property
def elapsed(self):
return int(monotonic() - self.start_ts)
@property
def elapsed_td(self):
return timedelta(seconds=self.elapsed)
def update_avg(self, n, dt):
if n > 0:
xput_len = len(self._xput)
self._xput.append(dt / n)
now = monotonic()
# update when we're still filling _xput, then after every second
if (xput_len < self.sma_window or
now - self._avg_update_ts > 1):
self.avg = sum(self._xput) / len(self._xput)
self._avg_update_ts = now
def update(self): def update(self):
self.update_stats()
kv = [(key, val) for key, val in self.__dict__.items()
if not key.startswith('_')]
self.ctx.update(kv)
def finish(self):
pass pass
def next(self): def start(self):
self.index = self.index + 1 pass
def clearln(self):
if self.file and self.is_tty():
print('\r\x1b[K', end='', file=self.file)
def write(self, s):
if self.file and self.is_tty():
line = self.message + s.ljust(self._width)
print('\r' + line, end='', file=self.file)
self._width = max(self._width, len(s))
self.file.flush()
def writeln(self, line):
if self.file and self.is_tty():
self.clearln()
print(line, end='', file=self.file)
self.file.flush()
def finish(self):
if self.file and self.is_tty():
print(file=self.file)
if self.hide_cursor:
print(SHOW_CURSOR, end='', file=self.file)
def is_tty(self):
return self.file.isatty() if self.check_tty else True
def next(self, n=1):
now = monotonic()
dt = now - self._ts
self.update_avg(n, dt)
self._ts = now
self.index = self.index + n
self.update() self.update()
def iter(self, it): def iter(self, it):
for x in it: with self:
yield x for x in it:
self.next() yield x
self.next()
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.finish() self.finish()
class Progress(Infinite): class Progress(Infinite):
backtrack = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Progress, self).__init__(*args, **kwargs) super(Progress, self).__init__(*args, **kwargs)
self.max = kwargs.get('max', 100) self.max = kwargs.get('max', 100)
self.eta = None
def update_stats(self): @property
if self.delta <= 0: def eta(self):
return return int(ceil(self.avg * self.remaining))
# Calculate moving average @property
now = time() def eta_td(self):
dt = (now - self._ts) / self.delta return timedelta(seconds=self.eta)
self._ts = now
w = self.avg_window
self.avg = dt if self.avg is None else (dt + w * self.avg) / (w + 1)
self.progress = min(1, self.index / self.max) @property
self.percent = self.progress * 100 def percent(self):
self.remaining = self.max - self.index return self.progress * 100
self.eta = int(ceil(self.avg * self.remaining))
def next(self): @property
prev = self.index def progress(self):
self.index = min(self.index + 1, self.max) return min(1, self.index / self.max)
self.delta = self.index - prev
@property
def remaining(self):
return max(self.max - self.index, 0)
def start(self):
self.update() self.update()
def goto(self, index): def goto(self, index):
index = min(index, self.max) incr = index - self.index
delta = index - self.index self.next(incr)
if delta <= 0 and not self.backtrack:
return
self.index = index
self.delta = delta
self.update()
def iter(self, it): def iter(self, it):
try: try:
@@ -112,7 +171,7 @@ class Progress(Infinite):
except TypeError: except TypeError:
pass pass
for x in it: with self:
yield x for x in it:
self.next() yield x
self.finish() self.next()

View File

@@ -14,13 +14,15 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import unicode_literals
import sys
from . import Progress from . import Progress
from .helpers import WritelnMixin
class Bar(WritelnMixin, Progress): class Bar(Progress):
width = 32 width = 32
message = ''
suffix = '%(index)d/%(max)d' suffix = '%(index)d/%(max)d'
bar_prefix = ' |' bar_prefix = ' |'
bar_suffix = '| ' bar_suffix = '| '
@@ -28,14 +30,13 @@ class Bar(WritelnMixin, Progress):
fill = '#' fill = '#'
def update(self): def update(self):
super(Bar, self).update()
filled_length = int(self.width * self.progress) filled_length = int(self.width * self.progress)
empty_length = self.width - filled_length empty_length = self.width - filled_length
message = self.message % self.ctx message = self.message % self
bar = self.fill * filled_length bar = self.fill * filled_length
empty = self.empty_fill * empty_length empty = self.empty_fill * empty_length
suffix = self.suffix % self.ctx suffix = self.suffix % self
line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix, line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
suffix]) suffix])
self.writeln(line) self.writeln(line)
@@ -45,41 +46,46 @@ class ChargingBar(Bar):
suffix = '%(percent)d%%' suffix = '%(percent)d%%'
bar_prefix = ' ' bar_prefix = ' '
bar_suffix = ' ' bar_suffix = ' '
empty_fill = u'' empty_fill = ''
fill = u'' fill = ''
class FillingSquaresBar(ChargingBar): class FillingSquaresBar(ChargingBar):
empty_fill = u'' empty_fill = ''
fill = u'' fill = ''
class FillingCirclesBar(ChargingBar): class FillingCirclesBar(ChargingBar):
empty_fill = u'' empty_fill = ''
fill = u'' fill = ''
class IncrementalBar(Bar): class IncrementalBar(Bar):
phases = (u' ', u'', u'', u'', u'', u'', u'', u'', u'') if sys.platform.startswith('win'):
phases = (u' ', u'', u'')
else:
phases = (' ', '', '', '', '', '', '', '', '')
def update(self): def update(self):
super(IncrementalBar, self).update()
nphases = len(self.phases) nphases = len(self.phases)
expanded_length = int(nphases * self.width * self.progress) filled_len = self.width * self.progress
filled_length = int(self.width * self.progress) nfull = int(filled_len) # Number of full chars
empty_length = self.width - filled_length phase = int((filled_len - nfull) * nphases) # Phase of last char
phase = expanded_length - (filled_length * nphases) nempty = self.width - nfull # Number of empty chars
message = self.message % self.ctx message = self.message % self
bar = self.phases[-1] * filled_length bar = self.phases[-1] * nfull
current = self.phases[phase] if phase > 0 else '' current = self.phases[phase] if phase > 0 else ''
empty = self.empty_fill * max(0, empty_length - len(current)) empty = self.empty_fill * max(0, nempty - len(current))
suffix = self.suffix % self.ctx suffix = self.suffix % self
line = ''.join([message, self.bar_prefix, bar, current, empty, line = ''.join([message, self.bar_prefix, bar, current, empty,
self.bar_suffix, suffix]) self.bar_suffix, suffix])
self.writeln(line) self.writeln(line)
class PixelBar(IncrementalBar):
phases = ('', '', '', '', '', '', '', '')
class ShadyBar(IncrementalBar): class ShadyBar(IncrementalBar):
phases = (u' ', u'', u'', u'', u'') phases = (' ', '', '', '', '')

View File

@@ -14,33 +14,28 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import unicode_literals
from . import Infinite, Progress from . import Infinite, Progress
from .helpers import WriteMixin
class Counter(WriteMixin, Infinite): class Counter(Infinite):
message = ''
def update(self): def update(self):
super(Counter, self).update()
self.write(str(self.index)) self.write(str(self.index))
class Countdown(WriteMixin, Progress): class Countdown(Progress):
def update(self): def update(self):
super(Countdown, self).update()
self.write(str(self.remaining)) self.write(str(self.remaining))
class Stack(WriteMixin, Progress): class Stack(Progress):
phases = (u' ', u'', u'', u'', u'', u'', u'', u'', u'') phases = (' ', '', '', '', '', '', '', '', '')
def update(self): def update(self):
super(Stack, self).update()
nphases = len(self.phases) nphases = len(self.phases)
i = min(nphases - 1, int(self.progress * nphases)) i = min(nphases - 1, int(self.progress * nphases))
self.write(self.phases[i]) self.write(self.phases[i])
class Pie(Stack): class Pie(Stack):
phases = (u'', u'', u'', u'', u'') phases = ('', '', '', '', '')

View File

@@ -1,57 +0,0 @@
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import print_function
class WriteMixin(object):
def __init__(self, message=None, **kwargs):
super(WriteMixin, self).__init__(**kwargs)
self._width = 0
if message:
self.message = message
if self.file.isatty():
print(self.message, end='', file=self.file)
self.file.flush()
def write(self, s):
if self.file.isatty():
b = '\b' * self._width
print(b + s.ljust(self._width), end='', file=self.file)
self._width = max(self._width, len(s))
self.file.flush()
class WritelnMixin(object):
def __init__(self, message=None, **kwargs):
super(WritelnMixin, self).__init__(**kwargs)
self.max_line_width = 0
if message:
self.message = message
def writeln(self, line):
if not self.file.isatty():
return
if len(line) > self.max_line_width:
self.max_line_width = len(line)
else:
line += ' ' * (self.max_line_width - len(line)) # Add padding
print('\r' + line, end='', file=self.file)
self.file.flush()
def finish(self):
if self.file.isatty():
print(file=self.file)

View File

@@ -14,27 +14,30 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from __future__ import unicode_literals
from . import Infinite from . import Infinite
from .helpers import WriteMixin
class Spinner(WriteMixin, Infinite): class Spinner(Infinite):
message = ''
phases = ('-', '\\', '|', '/') phases = ('-', '\\', '|', '/')
hide_cursor = True
def update(self): def update(self):
super(Spinner, self).update()
i = self.index % len(self.phases) i = self.index % len(self.phases)
self.write(self.phases[i]) self.write(self.phases[i])
class PieSpinner(Spinner): class PieSpinner(Spinner):
phases = [u'', u'', u'', u''] phases = ['', '', '', '']
class MoonSpinner(Spinner): class MoonSpinner(Spinner):
phases = [u'', u'', u'', u''] phases = ['', '', '', '']
class LineSpinner(Spinner): class LineSpinner(Spinner):
phases = [u'', u'', u'', u'', u'', u''] phases = ['', '', '', '', '', '']
class PixelSpinner(Spinner):
phases = ['', '', '', '', '', '', '', '']

View File

@@ -21,6 +21,9 @@ setup(
'License :: OSI Approved :: ISC License (ISCL)', 'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3' 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
] ]
) )

View File

@@ -2,35 +2,46 @@
from __future__ import print_function from __future__ import print_function
from random import randint import random
from time import sleep import time
from progress.bar import (Bar, ChargingBar, FillingSquaresBar, from progress.bar import (Bar, ChargingBar, FillingSquaresBar,
FillingCirclesBar, IncrementalBar, ShadyBar) FillingCirclesBar, IncrementalBar, PixelBar,
from progress.spinner import Spinner, PieSpinner, MoonSpinner, LineSpinner ShadyBar)
from progress.spinner import (Spinner, PieSpinner, MoonSpinner, LineSpinner,
PixelSpinner)
from progress.counter import Counter, Countdown, Stack, Pie from progress.counter import Counter, Countdown, Stack, Pie
for bar in (Bar, ChargingBar, FillingSquaresBar, FillingCirclesBar): def sleep():
for i in bar(bar.__name__).iter(range(100)): t = 0.01
sleep(0.04) t += t * random.uniform(-0.1, 0.1) # Add some variance
time.sleep(t)
for bar in (IncrementalBar, ShadyBar):
for i in bar(bar.__name__).iter(range(200)):
sleep(0.02)
for spin in (Spinner, PieSpinner, MoonSpinner, LineSpinner): for bar_cls in (Bar, ChargingBar, FillingSquaresBar, FillingCirclesBar):
for i in spin(spin.__name__ + ' ').iter(range(30)): suffix = '%(index)d/%(max)d [%(elapsed)d / %(eta)d / %(eta_td)s]'
sleep(0.1) bar = bar_cls(bar_cls.__name__, suffix=suffix)
print() for i in bar.iter(range(200)):
sleep()
for bar_cls in (IncrementalBar, PixelBar, ShadyBar):
suffix = '%(percent)d%% [%(elapsed_td)s / %(eta)d / %(eta_td)s]'
with bar_cls(bar_cls.__name__, suffix=suffix, max=200) as bar:
for i in range(200):
bar.next()
sleep()
for spin in (Spinner, PieSpinner, MoonSpinner, LineSpinner, PixelSpinner):
for i in spin(spin.__name__ + ' ').iter(range(100)):
sleep()
for singleton in (Counter, Countdown, Stack, Pie): for singleton in (Counter, Countdown, Stack, Pie):
for i in singleton(singleton.__name__ + ' ').iter(range(100)): for i in singleton(singleton.__name__ + ' ').iter(range(100)):
sleep(0.04) sleep()
print()
bar = IncrementalBar('Random', backtrack=True, suffix='') bar = IncrementalBar('Random', suffix='%(index)d')
for i in range(100): for i in range(100):
bar.goto(randint(0, 100)) bar.goto(random.randint(0, 100))
sleep(0.1) sleep()
bar.finish() bar.finish()