Merge branch 'develop' into blanks · commit-0/python-progressbar@5b648bd · GitHub
Skip to content

Commit 5b648bd

Browse files
committed
Merge branch 'develop' into blanks
2 parents c2b7b02 + 92e5d74 commit 5b648bd

12 files changed

Lines changed: 277 additions & 40 deletions

File tree

.coveragerc

Lines changed: 1 addition & 0 deletions

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ python:
99
- '3.7'
1010
- pypy
1111
install:
12-
- pip install .
13-
- pip install -r tests/requirements.txt
14-
before_script: flake8 progressbar tests
12+
- pip install -U .
13+
- pip install -U -r tests/requirements.txt
14+
before_script: flake8 progressbar tests examples
1515
script:
1616
- python setup.py test
1717
- python examples.py

examples.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ def example(fn):
1717
'''Wrap the examples so they generate readable output'''
1818

1919
@functools.wraps(fn)
20-
def wrapped():
20+
def wrapped(*args, **kwargs):
2121
try:
2222
sys.stdout.write('Running: %s\n' % fn.__name__)
23-
fn()
23+
fn(*args, **kwargs)
2424
sys.stdout.write('\n')
2525
except KeyboardInterrupt:
2626
sys.stdout.write('\nSkipping example.\n\n')
@@ -86,7 +86,8 @@ def basic_widget_example():
8686

8787
@example
8888
def color_bar_example():
89-
widgets = ['\x1b[33mColorful example\x1b[39m', progressbar.Percentage(), progressbar.Bar(marker='\x1b[32m#\x1b[39m')]
89+
widgets = ['\x1b[33mColorful example\x1b[39m', progressbar.Percentage(),
90+
progressbar.Bar(marker='\x1b[32m#\x1b[39m')]
9091
bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start()
9192
for i in range(10):
9293
# do something
@@ -95,6 +96,66 @@ def color_bar_example():
9596
bar.finish()
9697

9798

99+
@example
100+
def multi_range_bar_example():
101+
markers = [
102+
'\x1b[32m█\x1b[39m', # Done
103+
'\x1b[33m#\x1b[39m', # Processing
104+
'\x1b[31m.\x1b[39m', # Scheduling
105+
' ' # Not started
106+
]
107+
widgets = [progressbar.MultiRangeBar("amounts", markers=markers)]
108+
amounts = [0] * (len(markers) - 1) + [25]
109+
110+
with progressbar.ProgressBar(widgets=widgets, max_value=10).start() as bar:
111+
while True:
112+
incomplete_items = [
113+
idx
114+
for idx, amount in enumerate(amounts)
115+
for i in range(amount)
116+
if idx != 0
117+
]
118+
if not incomplete_items:
119+
break
120+
which = random.choice(incomplete_items)
121+
amounts[which] -= 1
122+
amounts[which - 1] += 1
123+
124+
bar.update(amounts=amounts, force=True)
125+
time.sleep(0.02)
126+
127+
128+
@example
129+
def multi_progress_bar_example(left=True):
130+
jobs = [
131+
# Each job takes between 1 and 10 steps to complete
132+
[0, random.randint(1, 10)]
133+
for i in range(25) # 25 jobs total
134+
]
135+
136+
widgets = [
137+
progressbar.Percentage(),
138+
' ', progressbar.MultiProgressBar('jobs', fill_left=left),
139+
]
140+
141+
max_value = sum([total for progress, total in jobs])
142+
with progressbar.ProgressBar(widgets=widgets, max_value=max_value) as bar:
143+
while True:
144+
incomplete_jobs = [
145+
idx
146+
for idx, (progress, total) in enumerate(jobs)
147+
if progress < total
148+
]
149+
if not incomplete_jobs:
150+
break
151+
which = random.choice(incomplete_jobs)
152+
jobs[which][0] += 1
153+
progress = sum([progress for progress, total in jobs])
154+
155+
bar.update(progress, jobs=jobs, force=True)
156+
time.sleep(0.02)
157+
158+
98159
@example
99160
def file_transfer_example():
100161
widgets = [
@@ -520,11 +581,12 @@ def user_variables():
520581
with progressbar.ProgressBar(
521582
prefix='{variables.task} >> {variables.subtask}',
522583
variables={'task': '--', 'subtask': '--'},
523-
max_value=10*num_subtasks) as bar:
584+
max_value=10 * num_subtasks) as bar:
524585
for tasks_name, subtasks in tasks.items():
525586
for subtask_name in subtasks:
526587
for i in range(10):
527-
bar.update(bar.value+1, task=tasks_name, subtask=subtask_name)
588+
bar.update(bar.value + 1, task=tasks_name,
589+
subtask=subtask_name)
528590
time.sleep(0.1)
529591

530592

progressbar/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
long running operations.
2020
'''.strip().split())
2121
__email__ = 'wolph@wol.ph'
22-
__version__ = '3.45.0'
22+
__version__ = '3.46.1'
2323
__license__ = 'BSD'
2424
__copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)'
2525
__url__ = 'https://github.com/WoLpH/python-progressbar'

progressbar/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
ReverseBar,
2424
BouncingBar,
2525
RotatingMarker,
26+
VariableMixin,
27+
MultiRangeBar,
28+
MultiProgressBar,
2629
Variable,
2730
DynamicMessage,
2831
FormatCustomText,
@@ -66,6 +69,9 @@
6669
'ProgressBar',
6770
'DataTransferBar',
6871
'RotatingMarker',
72+
'VariableMixin',
73+
'MultiRangeBar',
74+
'MultiProgressBar',
6975
'Variable',
7076
'DynamicMessage',
7177
'FormatCustomText',

progressbar/bar.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,7 @@ def __init__(self, fd=sys.stderr, is_terminal=None, line_breaks=None,
6868
self.fd = fd
6969

7070
# Check if this is an interactive terminal
71-
if is_terminal is None:
72-
is_terminal = utils.env_flag('PROGRESSBAR_IS_TERMINAL', None)
73-
if is_terminal is None: # pragma: no cover
74-
try:
75-
is_terminal = fd.isatty()
76-
except Exception:
77-
is_terminal = False
78-
self.is_terminal = is_terminal
71+
self.is_terminal = is_terminal = utils.is_terminal(fd, is_terminal)
7972

8073
# Check if it should overwrite the current line (suitable for
8174
# iteractive terminals) or write line breaks (suitable for log files)
@@ -229,15 +222,15 @@ class ProgressBar(StdRedirectMixin, ResizableMixin, ProgressBarBase):
229222
prefix (str): Prefix the progressbar with the given string
230223
suffix (str): Prefix the progressbar with the given string
231224
variables (dict): User-defined variables variables that can be used
232-
from a label using `format="{variables.my_var}"`. These values can
233-
be updated using `bar.update(my_var="newValue")` This can also be
234-
used to set initial values for `Variable`s widgets
225+
from a label using `format='{variables.my_var}'`. These values can
226+
be updated using `bar.update(my_var='newValue')` This can also be
227+
used to set initial values for variables' widgets
235228
236229
A common way of using it is like:
237230
238231
>>> progress = ProgressBar().start()
239232
>>> for i in range(100):
240-
... progress.update(i+1)
233+
... progress.update(i + 1)
241234
... # do something
242235
...
243236
>>> progress.finish()
@@ -343,7 +336,7 @@ def __init__(self, min_value=0, max_value=None, widgets=None,
343336
# A dictionary of names that can be used by Variable and FormatWidget
344337
self.variables = utils.AttributeDict(variables or {})
345338
for widget in (self.widgets or []):
346-
if isinstance(widget, widgets_module.Variable):
339+
if isinstance(widget, widgets_module.VariableMixin):
347340
if widget.name not in self.variables:
348341
self.variables[widget.name] = None
349342

@@ -413,7 +406,7 @@ def percentage(self):
413406

414407
def get_last_update_time(self):
415408
if self._last_update_time:
416-
return datetime.utcfromtimestamp(self._last_update_time)
409+
return datetime.fromtimestamp(self._last_update_time)
417410

418411
def set_last_update_time(self, value):
419412
if value:

progressbar/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@
2020
assert epoch
2121

2222

23+
def is_terminal(fd, is_terminal=None):
24+
if is_terminal is None:
25+
if 'JPY_PARENT_PID' in os.environ:
26+
is_terminal = True
27+
else:
28+
is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None)
29+
30+
if is_terminal is None: # pragma: no cover
31+
try:
32+
is_terminal = fd.isatty()
33+
except Exception:
34+
is_terminal = False
35+
36+
return is_terminal
37+
38+
2339
def deltas_to_seconds(*deltas, **kwargs): # default=ValueError):
2440
'''
2541
Convert timedeltas and seconds as int to seconds as float while coalescing

progressbar/widgets.py

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
12
from __future__ import absolute_import
23
from __future__ import division
34
from __future__ import print_function
@@ -743,7 +744,109 @@ def __call__(self, progress, data):
743744
self, progress, self.mapping, self.format)
744745

745746

746-
class Variable(FormatWidgetMixin, WidgetBase):
747+
class VariableMixin(object):
748+
'''Mixin to display a custom user variable '''
749+
750+
def __init__(self, name, **kwargs):
751+
if not isinstance(name, str):
752+
raise TypeError('Variable(): argument must be a string')
753+
if len(name.split()) > 1:
754+
raise ValueError('Variable(): argument must be single word')
755+
self.name = name
756+
757+
758+
class MultiRangeBar(Bar, VariableMixin):
759+
'''
760+
A bar with multiple sub-ranges, each represented by a different symbol
761+
762+
The various ranges are represented on a user-defined variable, formatted as
763+
764+
.. code-block:: python
765+
766+
[
767+
['Symbol1', amount1],
768+
['Symbol2', amount2],
769+
...
770+
]
771+
'''
772+
773+
def __init__(self, name, markers, **kwargs):
774+
VariableMixin.__init__(self, name)
775+
Bar.__init__(self, **kwargs)
776+
self.markers = [
777+
string_or_lambda(marker)
778+
for marker in markers
779+
]
780+
781+
def get_values(self, progress, data):
782+
return data['variables'][self.name] or []
783+
784+
def __call__(self, progress, data, width):
785+
'''Updates the progress bar and its subcomponents'''
786+
787+
left = converters.to_unicode(self.left(progress, data, width))
788+
right = converters.to_unicode(self.right(progress, data, width))
789+
width -= progress.custom_len(left) + progress.custom_len(right)
790+
values = self.get_values(progress, data)
791+
792+
values_sum = sum(values)
793+
if width and values_sum:
794+
middle = ''
795+
values_accumulated = 0
796+
width_accumulated = 0
797+
for marker, value in zip(self.markers, values):
798+
marker = converters.to_unicode(marker(progress, data, width))
799+
assert utils.len_color(marker) == 1
800+
801+
values_accumulated += value
802+
item_width = int(values_accumulated / values_sum * width)
803+
item_width -= width_accumulated
804+
width_accumulated += item_width
805+
middle += item_width * marker
806+
else:
807+
fill = converters.to_unicode(self.fill(progress, data, width))
808+
assert utils.len_color(fill) == 1
809+
middle = fill * width
810+
811+
return left + middle + right
812+
813+
814+
class MultiProgressBar(MultiRangeBar):
815+
def __init__(self,
816+
name,
817+
# NOTE: the markers are not whitespace even though some
818+
# terminals don't show the characters correctly!
819+
markers=' ▁▂▃▄▅▆▇█',
820+
**kwargs):
821+
MultiRangeBar.__init__(self, name=name,
822+
markers=list(reversed(markers)), **kwargs)
823+
824+
def get_values(self, progress, data):
825+
ranges = [0] * len(self.markers)
826+
for progress in data['variables'][self.name] or []:
827+
if not isinstance(progress, (int, float)):
828+
# Progress is (value, max)
829+
progress_value, progress_max = progress
830+
progress = float(progress_value) / float(progress_max)
831+
832+
if progress < 0 or progress > 1:
833+
raise ValueError(
834+
'Range value needs to be in the range [0..1], got %s' %
835+
progress)
836+
837+
range_ = progress * (len(ranges) - 1)
838+
pos = int(range_)
839+
frac = range_ % 1
840+
ranges[pos] += (1 - frac)
841+
if (frac):
842+
ranges[pos + 1] += (frac)
843+
844+
if self.fill_left:
845+
ranges = list(reversed(ranges))
846+
return ranges
847+
848+
849+
class Variable(FormatWidgetMixin, VariableMixin, WidgetBase):
747850
'''Displays a custom variable.'''
748851

749852
def __init__(self, name, format='{name}: {formatted_value}',
@@ -752,14 +855,9 @@ def __init__(self, name, format='{name}: {formatted_value}',
752855
self.format = format
753856
self.width = width
754857
self.precision = precision
755-
if not isinstance(name, str):
756-
raise TypeError('Variable(): argument must be a string')
757-
if len(name.split()) > 1:
758-
raise ValueError('Variable(): argument must be single word')
858+
VariableMixin.__init__(self, name=name)
759859
WidgetBase.__init__(self, **kwargs)
760860

761-
self.name = name
762-
763861
def __call__(self, progress, data):
764862
value = data['variables'][self.name]
765863
context = data.copy()

tests/conftest.py

Lines changed: 7 additions & 1 deletion

0 commit comments

Comments
 (0)