Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reposition bars below a closed bar #1502

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 63 additions & 6 deletions tests/tests_tqdm.py
Expand Up @@ -1281,20 +1281,78 @@ def test_position():
t3.update(1)
t4.update(1)
res = [m[0] for m in RE_pos.findall(our_file.getvalue())]
exres = ['\r1.pos0 bar: 0%',
'\n\r2.pos1 bar: 0%',
'\n\n\r3.pos2 bar: 0%',
exres = [*exres,
'\r2.pos1 bar: 0%',
'\n\n\r3.pos2 bar: 0%',
'\n\n\r4.pos2 bar: 0%',
'\r1.pos0 bar: 10%',
'\n\n\r3.pos2 bar: 10%',
'\n\r4.pos2 bar: 10%']
'\n\r3.pos2 bar: 10%',
'\n\n\r4.pos2 bar: 10%']
pos_line_diff(res, exres)
t4.close()
t3.close()
t1.close()


@mark.skipif(nt_and_no_colorama, reason="Windows without colorama")
@mark.parametrize('leave', [
True, # all bars remain
False, # no bars remain
None # only first bar remains
])
def test_position_leave(leave: bool):
"""Test leaving of nested positioned progress bars"""
our_file = StringIO()
kwargs = {
'file': our_file,
'miniters': 1,
'mininterval': 0,
'maxinterval': 0,
'leave': leave,
}
for _ in trange(2, desc='pos0 bar', position=0, **kwargs):
t2 = tqdm(total=2, desc='pos1 bar', position=1, **kwargs)
t2.update()
t3 = tqdm(total=2, desc='pos2 bar', position=2, **kwargs)
t3.update()
# complete t2 before t3
t2.update()
t2.close()
t3.update()
t3.close()

out = our_file.getvalue()
res = [m[0] for m in RE_pos.findall(out)]
# Bar 2 being left from the screen means bar 3 needs extra newline when
# positioning. If it is not left, then bar 3 needs to be cleared in its old
# position and redrawn in gap left by bar 2.
if leave:
bar2left, bar3move = '\n', []
else:
bar2left, bar3move = '', ['\n\n\r ', '\r\x1b[A\x1b[A']
innerex = ['\n\rpos1 bar: 0%',
'\n\rpos1 bar: 50%',
'\n\n\rpos2 bar: 0%',
'\n\n\rpos2 bar: 50%',
'\n\rpos1 bar: 100%',
'\rpos1 bar: 100%' if leave else '\n\r ',
*bar3move,
bar2left + '\n\rpos2 bar: 50%',
'\n\rpos2 bar: 100%',
'\rpos2 bar: 100%' if leave else '\n\r ']
# Bar 1 being left on screen adds an extra newline to the output
# that then shows up as part of the next res line.
bar1left = '\n' if leave else ''
exres = ['\rpos0 bar: 0%',
*innerex,
bar1left + '\rpos0 bar: 50%',
*innerex,
bar1left + '\rpos0 bar: 100%',
'\rpos0 bar: 100%' if leave is not False else '\r ',
'\n' if leave is not False else '\r']
pos_line_diff(res, exres)


def test_set_description():
"""Test set description"""
with closing(StringIO()) as our_file:
Expand Down Expand Up @@ -1895,7 +1953,6 @@ def test_screen_shape():
assert "one" in res
assert "two" in res
assert "three" in res
assert "\n\n" not in res
assert "more hidden" in res
# double-check ncols
assert all(len(i) == 50 for i in get_bar(res)
Expand Down
81 changes: 52 additions & 29 deletions tqdm/std.py
Expand Up @@ -715,6 +715,27 @@ def _decr_instances(cls, instance):
inst = min(instances, key=lambda i: i.pos)
inst.clear(nolock=True)
inst.pos = abs(instance.pos)
else:
# renumber remaining bars with positions below this bar so
# they maintain their positions
apos = abs(instance.pos)
readjust = [
(inst.pos, inst)
for inst in cls._instances
if not inst.disable and abs(getattr(inst, "pos", apos)) > apos
]
for pos, inst in sorted(readjust, key=lambda pi: -abs(pi[0])):
newpos = inst.pos + (1 if pos < 0 else -1)
if newpos == 0 and inst.leave is None:
# any bars now moving to pos=0 should not be left on
# screen if `leave` was set to `None`.
inst.leave = False
if not inst.leave:
# Clear the old position before moving the bar so we
# don't leave any artefacts on screen.
inst.clear(nolock=True)
inst.pos = newpos
inst.display()

@classmethod
def write(cls, s, file=None, end="\n", nolock=False):
Expand Down Expand Up @@ -1271,41 +1292,43 @@ def close(self):
# Prevent multiple closures
self.disable = True

# decrement instance pos and remove from internal set
pos = abs(self.pos)
self._decr_instances(self)
try:
if self.last_print_t < self.start_t + self.delay:
# haven't ever displayed; nothing to clear
return

if self.last_print_t < self.start_t + self.delay:
# haven't ever displayed; nothing to clear
return
# GUI mode
if getattr(self, 'sp', None) is None:
return

# GUI mode
if getattr(self, 'sp', None) is None:
return
# annoyingly, _supports_unicode isn't good enough
def fp_write(s):
self.fp.write(str(s))

# annoyingly, _supports_unicode isn't good enough
def fp_write(s):
self.fp.write(str(s))
try:
fp_write('')
except ValueError as e:
if 'closed' in str(e):
return
raise # pragma: no cover

try:
fp_write('')
except ValueError as e:
if 'closed' in str(e):
return
raise # pragma: no cover
pos = abs(self.pos)
leave = pos == 0 if self.leave is None else self.leave

leave = pos == 0 if self.leave is None else self.leave
with self._lock:
if leave:
# stats for overall rate (no weighted average)
self._ema_dt = lambda: None
self.display(pos=0)
fp_write('\n')
else:
# clear previous display
if self.display(msg='', pos=pos) and not pos:
fp_write('\r')

with self._lock:
if leave:
# stats for overall rate (no weighted average)
self._ema_dt = lambda: None
self.display(pos=0)
fp_write('\n')
else:
# clear previous display
if self.display(msg='', pos=pos) and not pos:
fp_write('\r')
finally:
# decrement instance pos and remove from internal set
self._decr_instances(self)

def clear(self, nolock=False):
"""Clear current bar display."""
Expand Down