Last active
April 10, 2023 09:28
-
-
Save mosquito/9e2d9ddb82de6abe4c048f27768509f1 to your computer and use it in GitHub Desktop.
Pure python progressbar without dependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from os import get_terminal_size | |
from typing import Sequence, TypeVar, Generator, Any | |
__license__ = "MIT" | |
__doc__ = """ | |
The point of this project is to give you some code you can easily copy and paste | |
into your own stuff so you don't have to go through the trouble of dragging in | |
an any dependency like tqdm or rich. | |
""" | |
class ProgressBar: | |
NO_TTY_COLUMNS: int = 80 | |
TTY_DONE_SYMBOL: str = "\u2588" | |
TTY_LEFT_SYMBOL: str = "\u2591" | |
NO_TTY_DONE_SYMBOL: str = '#' | |
NO_TTY_LEFT_SYMBOL: str = '.' | |
TTY_FILENO: int = sys.stderr.fileno() | |
BAR: str = "{done}{left} {percent:>3}% {suffix}" | |
def __init__( | |
self, total: int = 0, suffix: str = '', use_tty: bool = True, | |
updates_delay: float = 0.15 | |
): | |
""" | |
Simple progressbar implementation | |
:param total: The value that will mean 100% | |
:param suffix: The additional information which printed | |
right or percents | |
:param use_tty: use interactive tty mode instead prints progress line | |
by line | |
:param updates_delay: the minimum time between updates to avoid | |
updating too often | |
""" | |
self.total: int = total | |
self.suffix: str = suffix | |
self.value: int = 0 | |
self.delay = updates_delay | |
self._last_update: float = 0. | |
self._last_percent: int = 0 | |
self.interactive: bool = False | |
self.tty_columns: int = self.NO_TTY_COLUMNS | |
self.done_symbol: str = self.NO_TTY_DONE_SYMBOL | |
self.left_symbol: str = self.NO_TTY_LEFT_SYMBOL | |
if use_tty: | |
try: | |
self.tty_columns, _ = get_terminal_size(self.TTY_FILENO) | |
self.done_symbol = self.TTY_DONE_SYMBOL | |
self.left_symbol = self.TTY_LEFT_SYMBOL | |
self.interactive = True | |
except OSError: | |
self.interactive = False | |
self.offset: int = 6 | |
def draw(self) -> None: | |
# simple optimization not redraw the bar when percentage was not changed | |
if self._last_percent == self.percent: | |
return | |
now = time() | |
if ( | |
self._last_update + self.delay > now and | |
self.value < self.total | |
): | |
return | |
self._last_percent = self.percent | |
self._last_update = now | |
sys.stderr.write("\r" if self.interactive else "\n") | |
sys.stderr.write(self.line) | |
if self.interactive: | |
sys.stderr.flush() | |
def update(self, value: int) -> None: | |
self.value = value | |
self.draw() | |
def inc(self, value: int) -> None: | |
self.value += value | |
self.draw() | |
@property | |
def percent(self) -> int: | |
return round(self.value / self.total * 100) | |
@property | |
def columns(self) -> int: | |
return self.tty_columns - len(self.suffix) - self.offset | |
@property | |
def line(self) -> str: | |
columns = self.columns | |
percent = self.percent | |
done = round((percent / 100) * columns) | |
left = columns - done | |
return self.BAR.format( | |
done=self.done_symbol * done, | |
left=self.left_symbol * left, | |
percent=percent, suffix=self.suffix, | |
).strip() | |
T = TypeVar("T") | |
def progress(seq: Sequence[T], **kwargs) -> Generator[T, Any, None]: | |
if 'total' not in kwargs: | |
kwargs['total'] = len(seq) | |
bar: ProgressBar = ProgressBar(**kwargs) | |
for item in seq: | |
bar.inc(1) | |
yield item | |
if __name__ == '__main__': | |
from time import sleep, time | |
progress_bar = ProgressBar(total=100, use_tty=True, | |
suffix="[Use interactive tty mode]") | |
for _ in range(100): | |
progress_bar.inc(1) | |
sleep(0.01) | |
progress_bar = ProgressBar(total=100, use_tty=False, | |
suffix="[Print line by line]") | |
for _ in range(100): | |
progress_bar.inc(1) | |
sleep(0.01) | |
asterisks = ["*"] * 100 | |
for asterisk in progress(asterisks, suffix='asterisks'): | |
sleep(0.01) |
Author
mosquito
commented
Apr 10, 2023
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment