diff --git a/tests/test_repository.py b/tests/test_repository.py index e44dc966..9b47c4be 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -233,6 +233,27 @@ def dictfunc(): default_repo.upload(package) +@pytest.mark.parametrize("finished", [False, True]) +@pytest.mark.parametrize( + "task_time, formatted", + [ + (None, "--:--"), + (0, "00:00"), + (59, "00:59"), + (71, "01:11"), + (4210, "1:10:10"), + ], +) +def test_time_column_renders_condensed_time(finished, task_time, formatted): + if finished: + task = pretend.stub(finished=finished, finished_time=task_time) + else: + task = pretend.stub(finished=finished, time_remaining=task_time) + + column = repository.CondensedTimeColumn() + assert str(column.render(task)) == formatted + + def test_upload_retry(tmpdir, default_repo, capsys): """Print retry messages when the upload response indicates a server error.""" default_repo.disable_progress_bar = True diff --git a/twine/repository.py b/twine/repository.py index 4e80f2b6..e8b9e387 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -17,6 +17,7 @@ import requests import requests_toolbelt import rich.progress +import rich.text import urllib3 from requests import adapters from requests_toolbelt.utils import user_agent @@ -36,6 +37,30 @@ logger = logging.getLogger(__name__) +class CondensedTimeColumn(rich.progress.ProgressColumn): + """Renders estimated time remaining, or elapsed time when the task is finished.""" + + # Only refresh twice a second to prevent jitter + max_refresh = 0.5 + + def render(self, task: rich.progress.Task) -> rich.text.Text: + """Show time.""" + style = "progress.elapsed" if task.finished else "progress.remaining" + task_time = task.finished_time if task.finished else task.time_remaining + if task_time is None: + return rich.text.Text("--:--", style=style) + + # Based on https://github.com/tqdm/tqdm/blob/master/tqdm/std.py + minutes, seconds = divmod(int(task_time), 60) + hours, minutes = divmod(minutes, 60) + if hours: + formatted = f"{hours:d}:{minutes:02d}:{seconds:02d}" + else: + formatted = f"{minutes:02d}:{seconds:02d}" + + return rich.text.Text(formatted, style=style) + + class Repository: def __init__( self, @@ -154,8 +179,7 @@ def _upload(self, package: package_file.PackageFile) -> requests.Response: rich.progress.BarColumn(), rich.progress.DownloadColumn(), "•", - rich.progress.TimeElapsedColumn(), - rich.progress.TimeRemainingColumn(), + CondensedTimeColumn(), "•", rich.progress.TransferSpeedColumn(), disable=self.disable_progress_bar,