Initial commit

This commit is contained in:
Giorgos Verigakis
2012-04-18 19:26:56 +03:00
commit c8d37e0ee2
10 changed files with 516 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.pyc
*.egg-info
build/
dist/

13
LICENSE Normal file
View File

@@ -0,0 +1,13 @@
# 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.

91
README.rst Normal file
View File

@@ -0,0 +1,91 @@
Easy progress reporting for Python
==================================
Bars
----
There are 6 progress bars to choose from:
- Bar
- ChargingBar
- FillingSquaresBar
- FillingCirclesBar
- IncrementalBar
- ShadyBar
To use them, just call ``next`` to advance and ``finish`` to finish. ::
from progress.bar import Bar
bar = Bar('Working', max=20)
for i in range(20):
# Do some work
bar.next()
bar.finish()
The result will be a bar like the following: ::
Processing |############# | 42/100
To simplify the common case where the work is done in an iterator, you can
use the ``iter`` method. ::
for i in Bar('Processing').iter(it):
# Do some work
Progress bars are very customizable, you can change their width, their fill
character, their suffix and more. ::
bar = Bar('Loading', fill='@', suffix='%(percent).1f%%')
This will produce a bar like the following: ::
Processing |@@@@@@@@@@@@@ | 42%
You can use a number of template arguments in ``message`` and ``suffix``:
========= =============================
Name Value
========= =============================
index current value
max maximum value
remaining max - index
progress index / max
percent progress * 100
avg rolling average time per item (in seconds)
eta avg * remaining
========= =============================
Instead of passing all configuration options on instatiation, you can create
your custom subclass. ::
class FancyBar(Bar):
message = 'Loading'
fill = '*'
suffix = '%(percent).1f%% - %(eta)ds'
Spinners
========
For actions with an unknown number of steps you can use a spinner. ::
from progress.spinner import Spinner
spinner = Spinner('Loading ')
while state != 'FINISHED':
# Do some work
spinner.next()
There are 4 predefined spinners:
- Spinner
- PieSpinner
- MoonSpinner
- LineSpinner
Other
=====
Thera are a number of other classes available too, please check the source or
subclass one of them to create your own.

118
progress/__init__.py Normal file
View File

@@ -0,0 +1,118 @@
# 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 division
from math import ceil
from sys import stderr
from time import time
__version__ = '1.0'
class Infinite(object):
file = stderr
avg_window = 10
def __init__(self, *args, **kwargs):
self.ctx = {}
for key, val in kwargs.items():
if hasattr(self, key):
setattr(self, key, val)
else:
self.ctx[key] = val
self.index = 0
self.avg = None
self._ts = time()
def update_stats(self):
# Calculate moving average
now = time()
dt = now - self._ts
self._ts = now
w = self.avg_window
self.avg = dt if self.avg is None else (dt + w * self.avg) / (w + 1)
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
def next(self):
self.index = self.index + 1
self.update()
def iter(self, it):
for x in it:
yield x
self.next()
self.finish()
class Progress(Infinite):
backtrack = False
def __init__(self, *args, **kwargs):
super(Progress, self).__init__(*args, **kwargs)
self.max = kwargs.get('max', 100)
self.eta = None
def update_stats(self):
if self.delta <= 0:
return
# Calculate moving average
now = time()
dt = (now - self._ts) / self.delta
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)
self.percent = self.progress * 100
self.remaining = self.max - self.index
self.eta = int(ceil(self.avg * self.remaining))
def next(self):
prev = self.index
self.index = min(self.index + 1, self.max)
self.delta = self.index - prev
self.update()
def goto(self, index):
index = min(index, self.max)
delta = index - self.index
if delta <= 0 and not self.backtrack:
return
self.index = index
self.delta = delta
self.update()
def iter(self, it):
try:
self.max = len(it)
except TypeError:
pass
for x in it:
yield x
self.next()
self.finish()

85
progress/bar.py Normal file
View File

@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# 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 . import Progress
from .helpers import WritelnMixin
class Bar(WritelnMixin, Progress):
width = 32
message = ''
suffix = '%(index)d/%(max)d'
bar_prefix = ' |'
bar_suffix = '| '
empty_fill = ' '
fill = '#'
def update(self):
super(Bar, self).update()
filled_length = int(self.width * self.progress)
empty_length = self.width - filled_length
message = self.message % self.ctx
bar = self.fill * filled_length
empty = self.empty_fill * empty_length
suffix = self.suffix % self.ctx
line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
suffix])
self.writeln(line)
class ChargingBar(Bar):
suffix = '%(percent)d%%'
bar_prefix = ' '
bar_suffix = ' '
empty_fill = u''
fill = u''
class FillingSquaresBar(ChargingBar):
empty_fill = u''
fill = u''
class FillingCirclesBar(ChargingBar):
empty_fill = u''
fill = u''
class IncrementalBar(Bar):
phases = (u' ', u'', u'', u'', u'', u'', u'', u'', u'')
def update(self):
super(IncrementalBar, self).update()
nphases = len(self.phases)
expanded_length = int(nphases * self.width * self.progress)
filled_length = int(self.width * self.progress)
empty_length = self.width - filled_length
phase = expanded_length - (filled_length * nphases)
message = self.message % self.ctx
bar = self.phases[-1] * filled_length
current = self.phases[phase] if phase > 0 else ''
empty = self.empty_fill * max(0, empty_length - len(current))
suffix = self.suffix % self.ctx
line = ''.join([message, self.bar_prefix, bar, current, empty,
self.bar_suffix, suffix])
self.writeln(line)
class ShadyBar(IncrementalBar):
phases = (u' ', u'', u'', u'', u'')

46
progress/counter.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# 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 . import Infinite, Progress
from .helpers import WriteMixin
class Counter(WriteMixin, Infinite):
message = ''
def update(self):
super(Counter, self).update()
self.write(str(self.index))
class Countdown(WriteMixin, Progress):
def update(self):
super(Countdown, self).update()
self.write(str(self.remaining))
class Stack(WriteMixin, Progress):
phases = (u' ', u'', u'', u'', u'', u'', u'', u'', u'')
def update(self):
super(Stack, self).update()
nphases = len(self.phases)
i = min(nphases - 1, int(self.progress * nphases))
self.write(self.phases[i])
class Pie(Stack):
phases = (u'', u'', u'', u'', u'')

57
progress/helpers.py Normal file
View File

@@ -0,0 +1,57 @@
# 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)

40
progress/spinner.py Normal file
View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# 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 . import Infinite
from .helpers import WriteMixin
class Spinner(WriteMixin, Infinite):
message = ''
phases = ('-', '\\', '|', '/')
def update(self):
super(Spinner, self).update()
i = self.index % len(self.phases)
self.write(self.phases[i])
class PieSpinner(Spinner):
phases = [u'', u'', u'', u'']
class MoonSpinner(Spinner):
phases = [u'', u'', u'', u'']
class LineSpinner(Spinner):
phases = [u'', u'', u'', u'', u'', u'']

26
setup.py Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
from setuptools import setup
import progress
setup(
name='progress',
version=progress.__version__,
description='Easy to use progress bars',
long_description=open('README.rst').read(),
author='Giorgos Verigakis',
author_email='verigak@gmail.com',
url='http://github.com/verigak/progress/',
license='ISC',
packages=['progress'],
classifiers=[
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3'
]
)

36
test_progress.py Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python
from __future__ import print_function
from random import randint
from time import sleep
from progress.bar import (Bar, ChargingBar, FillingSquaresBar,
FillingCirclesBar, IncrementalBar, ShadyBar)
from progress.spinner import Spinner, PieSpinner, MoonSpinner, LineSpinner
from progress.counter import Counter, Countdown, Stack, Pie
for bar in (Bar, ChargingBar, FillingSquaresBar, FillingCirclesBar):
for i in bar(bar.__name__).iter(range(100)):
sleep(0.04)
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 i in spin(spin.__name__ + ' ').iter(range(30)):
sleep(0.1)
print()
for singleton in (Counter, Countdown, Stack, Pie):
for i in singleton(singleton.__name__ + ' ').iter(range(100)):
sleep(0.04)
print()
bar = IncrementalBar('Random', backtrack=True, suffix='')
for i in range(100):
bar.goto(randint(0, 100))
sleep(0.1)
bar.finish()