Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
verigak committed Apr 18, 2012
0 parents commit c8d37e0
Show file tree
Hide file tree
Showing 10 changed files with 516 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.pyc
*.egg-info
build/
dist/
13 changes: 13 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2012 Giorgos Verigakis <[email protected]>
#
# 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 changes: 91 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -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 changes: 118 additions & 0 deletions progress/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright (c) 2012 Giorgos Verigakis <[email protected]>
#
# 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 changes: 85 additions & 0 deletions progress/bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2012 Giorgos Verigakis <[email protected]>
#
# 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 changes: 46 additions & 0 deletions progress/counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2012 Giorgos Verigakis <[email protected]>
#
# 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'●')
Loading

0 comments on commit c8d37e0

Please sign in to comment.