mirror of
https://github.com/verigak/progress.git
synced 2025-12-08 19:33:24 +00:00
Initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.pyc
|
||||
*.egg-info
|
||||
build/
|
||||
dist/
|
||||
13
LICENSE
Normal file
13
LICENSE
Normal 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
91
README.rst
Normal 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
118
progress/__init__.py
Normal 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
85
progress/bar.py
Normal 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
46
progress/counter.py
Normal 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
57
progress/helpers.py
Normal 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
40
progress/spinner.py
Normal 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
26
setup.py
Executable 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
36
test_progress.py
Executable 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()
|
||||
Reference in New Issue
Block a user