Skip to content

Commit ac98595

Browse files
committed
Added basic multiple progressbar support
1 parent 203c487 commit ac98595

4 files changed

Lines changed: 87 additions & 33 deletions

File tree

README.rst

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -238,55 +238,72 @@ Bar with wide Chinese (or other multibyte) characters
238238
for i in bar(range(10)):
239239
time.sleep(0.1)
240240
241-
Showing multiple (threaded) independent progress bars in parallel
241+
Showing multiple independent progress bars in parallel
242242
==============================================================================
243243

244-
While this method works fine and will continue to work fine, a smarter and
245-
fully automatic version of this is currently being made:
246-
https://github.com/WoLpH/python-progressbar/issues/176
247-
248244
.. code:: python
249245
250246
import random
251247
import sys
252-
import threading
253248
import time
254249
255250
import progressbar
256251
257-
output_lock = threading.Lock()
252+
BARS = 5
253+
N = 100
258254
255+
# Construct the list of progress bars with the `line_offset` so they draw
256+
# below each other
257+
bars = []
258+
for i in range(BARS):
259+
bars.append(
260+
progressbar.ProgressBar(
261+
max_value=N,
262+
# We add 1 to the line offset to account for the `print_fd`
263+
line_offset=i + 1,
264+
max_error=False,
265+
)
266+
)
259267
260-
class LineOffsetStreamWrapper:
261-
UP = '\033[F'
262-
DOWN = '\033[B'
268+
# Create a file descriptor for regular printing as well
269+
print_fd = progressbar.LineOffsetStreamWrapper(sys.stdout, 0)
263270
264-
def __init__(self, lines=0, stream=sys.stderr):
265-
self.stream = stream
266-
self.lines = lines
271+
# The progress bar updates, normally you would do something useful here
272+
for i in range(N * BARS):
273+
time.sleep(0.005)
267274
268-
def write(self, data):
269-
with output_lock:
270-
self.stream.write(self.UP * self.lines)
271-
self.stream.write(data)
272-
self.stream.write(self.DOWN * self.lines)
273-
self.stream.flush()
275+
# Increment one of the progress bars at random
276+
bars[random.randrange(0, BARS)].increment()
274277
275-
def __getattr__(self, name):
276-
return getattr(self.stream, name)
278+
# Print a status message to the `print_fd` below the progress bars
279+
print(f'Hi, we are at update {i+1} of {N * BARS}', file=print_fd)
277280
281+
# Cleanup the bars
282+
for bar in bars:
283+
bar.finish()
278284
279-
bars = []
280-
for i in range(5):
281-
bars.append(
282-
progressbar.ProgressBar(
283-
fd=LineOffsetStreamWrapper(i),
284-
max_value=1000,
285-
)
286-
)
285+
# Add a newline to make sure the next print starts on a new line
286+
print()
287287
288-
if i:
289-
print('Reserve a line for the progressbar')
288+
******************************************************************************
289+
290+
Naturally we can do this from separate threads as well:
291+
292+
.. code:: python
293+
294+
import random
295+
import threading
296+
import time
297+
298+
import progressbar
299+
300+
BARS = 5
301+
N = 100
302+
303+
# Create the bars with the given line offset
304+
bars = []
305+
for line_offset in range(BARS):
306+
bars.append(progressbar.ProgressBar(line_offset=line_offset, max_value=N))
290307
291308
292309
class Worker(threading.Thread):
@@ -295,10 +312,12 @@ https://github.com/WoLpH/python-progressbar/issues/176
295312
self.bar = bar
296313
297314
def run(self):
298-
for i in range(1000):
299-
time.sleep(random.random() / 100)
315+
for i in range(N):
316+
time.sleep(random.random() / 25)
300317
self.bar.update(i)
301318
302319
303320
for bar in bars:
304321
Worker(bar).start()
322+
323+
print()

progressbar/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from .widgets import Timer
3636
from .widgets import Variable
3737
from .widgets import VariableMixin
38+
from .multi import LineOffsetStreamWrapper
3839

3940
__date__ = str(date.today())
4041
__all__ = [
@@ -73,4 +74,5 @@
7374
'NullBar',
7475
'__author__',
7576
'__version__',
77+
'LineOffsetStreamWrapper',
7678
]

progressbar/bar.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from . import (
1818
base,
19+
multi,
1920
utils,
2021
widgets,
2122
widgets as widgets_module, # Avoid name collision
@@ -143,6 +144,7 @@ def __init__(
143144
is_terminal: bool | None = None,
144145
line_breaks: bool | None = None,
145146
enable_colors: bool | None = None,
147+
line_offset: int = 0,
146148
**kwargs,
147149
):
148150
if fd is sys.stdout:
@@ -151,6 +153,9 @@ def __init__(
151153
elif fd is sys.stderr:
152154
fd = utils.streams.original_stderr
153155

156+
if line_offset:
157+
fd = multi.LineOffsetStreamWrapper(line_offset, fd)
158+
154159
self.fd = fd
155160
self.is_ansi_terminal = utils.is_ansi_terminal(fd)
156161

@@ -385,6 +390,9 @@ class ProgressBar(
385390
from a label using `format='{variables.my_var}'`. These values can
386391
be updated using `bar.update(my_var='newValue')` This can also be
387392
used to set initial values for variables' widgets
393+
line_offset (int): The number of lines to offset the progressbar from
394+
your current line. This is useful if you have other output or
395+
multiple progressbars
388396
389397
A common way of using it is like:
390398

progressbar/multi.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import sys
2+
3+
4+
class LineOffsetStreamWrapper:
5+
UP = '\033[F'
6+
DOWN = '\033[B'
7+
8+
def __init__(self, lines=0, stream=sys.stderr):
9+
self.stream = stream
10+
self.lines = lines
11+
12+
def write(self, data):
13+
# Move the cursor up
14+
self.stream.write(self.UP * self.lines)
15+
# Print a carriage return to reset the cursor position
16+
self.stream.write('\r')
17+
# Print the data without newlines so we don't change the position
18+
self.stream.write(data.rstrip('\n'))
19+
# Move the cursor down
20+
self.stream.write(self.DOWN * self.lines)
21+
22+
self.stream.flush()
23+
24+
def __getattr__(self, name):
25+
return getattr(self.stream, name)

0 commit comments

Comments
 (0)