Repository: pallets/click
Pull request #487 (2015-12-13 13:00:22)

Show progressbar only if total execution time is noticable
Currently `progressbar` displays its output immediately, even if the total execution time of what is being monitored is small. (Optionally) progress bar should only print something, if the total execution time is longer than e.g half a second or something equally unnoticeable.

**Reasoning:**
A user doesn't need progress displayed, if the whole execution monitored by the progress bar finishes within say half a second after its start. Here is an illustration:

```
import time
import click

if __name__ == '__main__':
    # Fast, should not generate output
    click.echo('fast:')
    with click.progressbar(range(10)) as bar:
        for data in bar:
            pass

    # Slow should generate output
    click.echo('slow:')
    with click.progressbar(range(100)) as bar:
        for data in bar:
            time.sleep(1)
```

**Output before pull:**

```
fast:
[####################################]  100% 
slow:
[####################################]  100% 
```

**Output after pull:**

```
fast:
slow:
[####################################]  100% 
```

**Discussion:**

In this proposed pull request, 0.5s are hard-coded as the time threshold. This is of course an arbitrary value and debatable. One could introduce another optional argument to `progressbar()` that lets the user specify the time threshold with 0.5s as default value (where 0 forces the display of progress bar no matter what = old behavior). Or zero could be the default value. I opted against an optional threshold, because I feel it is unnecessary clutter and 0.5-1s is what most people want, but you might disagree.

Since the added `time.time()` statements in `is_fast()` are quite quick and only executed conditionally, the impact on performance is negligible.



Patch:
diff --git a/CHANGES b/CHANGES
index 0b444fd..cb2d35a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,10 +23,10 @@ Version 7.0
 - ``launch`` now works properly under Cygwin. See #650.
 - `CliRunner.invoke` now may receive `args` as a string representing
   a Unix shell command. See #664.
-- Fix bug that caused bashcompletion to give improper completions on 
+- Fix bug that caused bashcompletion to give improper completions on
   chained commands. See #774.
 - Add support for bright colors.
-- 't' and 'f' are now converted to True and False. 
+- 't' and 'f' are now converted to True and False.
 - Fix bug that caused bashcompletion to give improper completions on
   chained commands when a required option/argument was being completed.
   See #790.
diff --git a/click/_termui_impl.py b/click/_termui_impl.py
index 5cb942d..a4e421e 100644
--- a/click/_termui_impl.py
+++ b/click/_termui_impl.py
@@ -90,6 +90,7 @@ class ProgressBar(object):
         self.current_item = None
         self.is_hidden = not isatty(self.file)
         self._last_line = None
+        self.short_limit = 0.5
 
     def __enter__(self):
         self.entered = True
@@ -105,8 +106,11 @@ class ProgressBar(object):
         self.render_progress()
         return self.generator()
 
+    def is_fast(self):
+        return time.time() - self.start <= self.short_limit
+
     def render_finish(self):
-        if self.is_hidden:
+        if self.is_hidden or self.is_fast():
             return
         self.file.write(AFTER_BAR)
         self.file.flush()
@@ -224,7 +228,8 @@ class ProgressBar(object):
         buf.append(' ' * (clear_width - line_len))
         line = ''.join(buf)
         # Render the line only if it changed.
-        if line != self._last_line:
+
+        if line != self._last_line and not self.is_fast():
             self._last_line = line
             echo(line, file=self.file, color=self.color, nl=True)
             self.file.flush()


Test patch:
diff --git a/tests/test_termui.py b/tests/test_termui.py
index 23f064f..9e855d3 100644
--- a/tests/test_termui.py
+++ b/tests/test_termui.py
@@ -2,15 +2,28 @@ import click
 import time
 
 
+class FakeClock(object):
+    def __init__(self):
+        self.now = time.time()
+
+    def advance_time(self, seconds=1):
+        self.now += seconds
+
+    def time(self):
+        return self.now
+
+
 def test_progressbar_strip_regression(runner, monkeypatch):
+    fake_clock = FakeClock()
     label = '    padded line'
 
     @click.command()
     def cli():
         with click.progressbar(tuple(range(10)), label=label) as progress:
             for thing in progress:
-                pass
+                fake_clock.advance_time()
 
+    monkeypatch.setattr(time, 'time', fake_clock.time)
     monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
     assert label in runner.invoke(cli, []).output
 
@@ -34,26 +47,31 @@ def test_progressbar_length_hint(runner, monkeypatch):
 
         next = __next__
 
+    fake_clock = FakeClock()
+
     @click.command()
     def cli():
         with click.progressbar(Hinted(10), label='test') as progress:
             for thing in progress:
-                pass
+                fake_clock.advance_time()
 
+    monkeypatch.setattr(time, 'time', fake_clock.time)
     monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: True)
     result = runner.invoke(cli, [])
     assert result.exception is None
 
 
 def test_progressbar_hidden(runner, monkeypatch):
+    fake_clock = FakeClock()
     label = 'whatever'
 
     @click.command()
     def cli():
         with click.progressbar(tuple(range(10)), label=label) as progress:
             for thing in progress:
-                pass
+                fake_clock.advance_time()
 
+    monkeypatch.setattr(time, 'time', fake_clock.time)
     monkeypatch.setattr(click._termui_impl, 'isatty', lambda _: False)
     assert runner.invoke(cli, []).output == ''
 
@@ -91,16 +109,6 @@ def test_progressbar_yields_all_items(runner):
 
 
 def test_progressbar_update(runner, monkeypatch):
-    class FakeClock(object):
-        def __init__(self):
-            self.now = time.time()
-
-        def advance_time(self, seconds=1):
-            self.now += seconds
-
-        def time(self):
-            return self.now
-
     fake_clock = FakeClock()
 
     @click.command()
@@ -116,8 +124,7 @@ def test_progressbar_update(runner, monkeypatch):
 
     lines = [line for line in output.split('\n') if '[' in line]
 
-    assert '  0%' in lines[0]
-    assert ' 25%  00:00:03' in lines[1]
-    assert ' 50%  00:00:02' in lines[2]
-    assert ' 75%  00:00:01' in lines[3]
-    assert '100%          ' in lines[4]
+    assert ' 25%  00:00:03' in lines[0]
+    assert ' 50%  00:00:02' in lines[1]
+    assert ' 75%  00:00:01' in lines[2]
+    assert '100%          ' in lines[3]