printbuddies.printbuddies
1from os import get_terminal_size 2from time import sleep 3from typing import Any 4 5from noiftimer import Timer 6 7 8def clear(): 9 """Erase the current line from the terminal.""" 10 print(" " * (get_terminal_size().columns - 1), flush=True, end="\r") 11 12 13def print_in_place(string: str, animate: bool = False, animate_refresh: float = 0.01): 14 """Calls to print_in_place will overwrite 15 the previous line of text in the terminal 16 with the 'string' param. 17 18 :param animate: Will cause the string 19 to be printed to the terminal 20 one character at a time. 21 22 :param animate_refresh: Number of seconds 23 between the addition of characters 24 when 'animate' is True.""" 25 clear() 26 string = str(string) 27 width = get_terminal_size().columns 28 string = string[: width - 2] 29 if animate: 30 for i in range(len(string)): 31 print(f"{string[:i+1]}", flush=True, end=" \r") 32 sleep(animate_refresh) 33 else: 34 print(string, flush=True, end="\r") 35 36 37def ticker(info: list[str]): 38 """Prints info to terminal with 39 top and bottom padding so that repeated 40 calls print info without showing previous 41 outputs from ticker calls. 42 43 Similar visually to print_in_place, 44 but for multiple lines.""" 45 width = get_terminal_size().columns 46 info = [str(line)[: width - 1] for line in info] 47 height = get_terminal_size().lines - len(info) 48 print("\n" * (height * 2), end="") 49 print(*info, sep="\n", end="") 50 print("\n" * (int((height) / 2)), end="") 51 52 53class ProgBar: 54 """Self incrementing, dynamically sized progress bar. 55 56 Includes an internal timer that starts when this object is created. 57 It can be easily added to the progress bar display by adding 58 the 'runtime' property to display's prefix or suffix param: 59 60 >>> bar = ProgBar(total=100) 61 >>> time.sleep(30) 62 >>> bar.display(prefix=bar.runtime) 63 >>> "runtime: 30s [_///////////////////]1.00%" """ 64 65 def __init__( 66 self, 67 total: float, 68 update_frequency: int = 1, 69 fill_ch: str = "_", 70 unfill_ch: str = "/", 71 width_ratio: float = 0.75, 72 new_line_after_completion: bool = True, 73 clear_after_completion: bool = False, 74 ): 75 """:param total: The number of calls to reach 100% completion. 76 77 :param update_frequency: The progress bar will only update once every this number of calls to display(). 78 The larger the value, the less performance impact ProgBar has on the loop in which it is called. 79 e.g. 80 >>> bar = ProgBar(100, update_frequency=10) 81 >>> for _ in range(100): 82 >>> bar.display() 83 84 ^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. 85 Note: If 'total' is not a multiple of 'update_frequency', the display will not show 100% completion when the loop finishes. 86 87 :param fill_ch: The character used to represent the completed part of the bar. 88 89 :param unfill_ch: The character used to represent the uncompleted part of the bar. 90 91 :param width_ratio: The width of the progress bar relative to the width of the terminal window. 92 93 :param new_line_after_completion: Make a call to print() once self.counter >= self.total. 94 95 :param clear_after_completion: Make a call to printbuddies.clear() once self.counter >= self.total. 96 97 Note: if new_line_after_completion and clear_after_completion are both True, the line will be cleared 98 then a call to print() will be made.""" 99 self.total = total 100 self.update_frequency = update_frequency 101 self.fill_ch = fill_ch[0] 102 self.unfill_ch = unfill_ch[0] 103 self.width_ratio = width_ratio 104 self.new_line_after_completion = new_line_after_completion 105 self.clear_after_completion = clear_after_completion 106 self.reset() 107 self.with_context = False 108 109 def __enter__(self): 110 self.with_context = True 111 return self 112 113 def __exit__(self, *args, **kwargs): 114 if self.clear_after_completion: 115 clear() 116 else: 117 print() 118 119 def reset(self): 120 self.counter = 1 121 self.percent = "" 122 self.prefix = "" 123 self.suffix = "" 124 self.filled = "" 125 self.unfilled = "" 126 self.bar = "" 127 self.timer = Timer(subsecond_resolution=False).start() 128 129 @property 130 def runtime(self) -> str: 131 return f"runtime:{self.timer.elapsed_str}" 132 133 def get_percent(self) -> str: 134 """Returns the percentage complete to two decimal places 135 as a string without the %.""" 136 percent = str(round(100.0 * self.counter / self.total, 2)) 137 if len(percent.split(".")[1]) == 1: 138 percent = percent + "0" 139 if len(percent.split(".")[0]) == 1: 140 percent = "0" + percent 141 return percent 142 143 def _prepare_bar(self): 144 self.terminal_width = get_terminal_size().columns - 1 145 bar_length = int(self.terminal_width * self.width_ratio) 146 progress = int(bar_length * min(self.counter / self.total, 1.0)) 147 self.filled = self.fill_ch * progress 148 self.unfilled = self.unfill_ch * (bar_length - progress) 149 self.percent = self.get_percent() 150 self.bar = self.get_bar() 151 152 def _trim_bar(self): 153 original_width = self.width_ratio 154 while len(self.bar) > self.terminal_width and self.width_ratio > 0: 155 self.width_ratio -= 0.01 156 self._prepare_bar() 157 self.width_ratio = original_width 158 159 def get_bar(self): 160 return f"{self.prefix}{' '*bool(self.prefix)}[{self.filled}{self.unfilled}]-{self.percent}% {self.suffix}" 161 162 def display( 163 self, 164 prefix: str = "", 165 suffix: str = "", 166 counter_override: float | None = None, 167 total_override: float | None = None, 168 return_object: Any | None = None, 169 ) -> Any: 170 """Writes the progress bar to the terminal. 171 172 :param prefix: String affixed to the front of the progress bar. 173 174 :param suffix: String appended to the end of the progress bar. 175 176 :param counter_override: When an externally incremented completion counter is needed. 177 178 :param total_override: When an externally controlled bar total is needed. 179 180 :param return_object: An object to be returned by display(). 181 182 Allows display() to be called within a comprehension: 183 184 e.g. 185 186 >>> bar = ProgBar(10) 187 >>> def square(x: int | float)->int|float: 188 >>> return x * x 189 >>> myList = [bar.display(return_object=square(i)) for i in range(10)] 190 >>> <progress bar gets displayed> 191 >>> myList 192 >>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]""" 193 if not self.timer.started: 194 self.timer.start() 195 if counter_override is not None: 196 self.counter = counter_override 197 if total_override: 198 self.total = total_override 199 # Don't wanna divide by 0 there, pal 200 while self.total <= 0: 201 self.total += 1 202 if self.counter % self.update_frequency == 0: 203 self.prefix = prefix 204 self.suffix = suffix 205 self._prepare_bar() 206 self._trim_bar() 207 pad = " " * (self.terminal_width - len(self.bar)) 208 width = get_terminal_size().columns 209 print(f"{self.bar}{pad}"[: width - 2], flush=True, end="\r") 210 if self.counter >= self.total: 211 self.timer.stop() 212 if not self.with_context: 213 if self.clear_after_completion: 214 clear() 215 if self.new_line_after_completion: 216 print() 217 self.counter += 1 218 return return_object 219 220 221class Spinner: 222 """Prints one of a sequence of characters in order everytime display() is called. 223 The display function writes the new character to the same line, overwriting the previous character. 224 The sequence will be cycled through indefinitely. 225 If used as a context manager, the last printed character will be cleared upon exiting.""" 226 227 def __init__(self, sequence: list[Any] | None = None): 228 """ 229 :param sequence: Override the built in spin sequence.""" 230 if sequence: 231 self.sequence = sequence 232 else: 233 self.sequence = ["/", "-", "\\"] 234 235 def __enter__(self): 236 return self 237 238 def __exit__(self, *args, **kwargs): 239 clear() 240 241 @property 242 def sequence(self) -> list[Any]: 243 return self._sequence 244 245 @sequence.setter 246 def sequence(self, character_list: list[Any]): 247 # Buffer each element with a leading space 248 # so that the character isn't obscured by the cursor 249 self._sequence = [" " + str(ch) for ch in character_list] 250 251 def _get_next(self) -> str: 252 """Pop the first element of self._sequence, 253 append it to the end, and return the element.""" 254 ch = self.sequence.pop(0) 255 self.sequence.append(ch) 256 return ch 257 258 def display(self): 259 """Print the next character in the sequence.""" 260 print_in_place(self._get_next())
9def clear(): 10 """Erase the current line from the terminal.""" 11 print(" " * (get_terminal_size().columns - 1), flush=True, end="\r")
Erase the current line from the terminal.
14def print_in_place(string: str, animate: bool = False, animate_refresh: float = 0.01): 15 """Calls to print_in_place will overwrite 16 the previous line of text in the terminal 17 with the 'string' param. 18 19 :param animate: Will cause the string 20 to be printed to the terminal 21 one character at a time. 22 23 :param animate_refresh: Number of seconds 24 between the addition of characters 25 when 'animate' is True.""" 26 clear() 27 string = str(string) 28 width = get_terminal_size().columns 29 string = string[: width - 2] 30 if animate: 31 for i in range(len(string)): 32 print(f"{string[:i+1]}", flush=True, end=" \r") 33 sleep(animate_refresh) 34 else: 35 print(string, flush=True, end="\r")
Calls to print_in_place will overwrite the previous line of text in the terminal with the 'string' param.
Parameters
animate: Will cause the string to be printed to the terminal one character at a time.
animate_refresh: Number of seconds between the addition of characters when 'animate' is True.
38def ticker(info: list[str]): 39 """Prints info to terminal with 40 top and bottom padding so that repeated 41 calls print info without showing previous 42 outputs from ticker calls. 43 44 Similar visually to print_in_place, 45 but for multiple lines.""" 46 width = get_terminal_size().columns 47 info = [str(line)[: width - 1] for line in info] 48 height = get_terminal_size().lines - len(info) 49 print("\n" * (height * 2), end="") 50 print(*info, sep="\n", end="") 51 print("\n" * (int((height) / 2)), end="")
Prints info to terminal with top and bottom padding so that repeated calls print info without showing previous outputs from ticker calls.
Similar visually to print_in_place, but for multiple lines.
54class ProgBar: 55 """Self incrementing, dynamically sized progress bar. 56 57 Includes an internal timer that starts when this object is created. 58 It can be easily added to the progress bar display by adding 59 the 'runtime' property to display's prefix or suffix param: 60 61 >>> bar = ProgBar(total=100) 62 >>> time.sleep(30) 63 >>> bar.display(prefix=bar.runtime) 64 >>> "runtime: 30s [_///////////////////]1.00%" """ 65 66 def __init__( 67 self, 68 total: float, 69 update_frequency: int = 1, 70 fill_ch: str = "_", 71 unfill_ch: str = "/", 72 width_ratio: float = 0.75, 73 new_line_after_completion: bool = True, 74 clear_after_completion: bool = False, 75 ): 76 """:param total: The number of calls to reach 100% completion. 77 78 :param update_frequency: The progress bar will only update once every this number of calls to display(). 79 The larger the value, the less performance impact ProgBar has on the loop in which it is called. 80 e.g. 81 >>> bar = ProgBar(100, update_frequency=10) 82 >>> for _ in range(100): 83 >>> bar.display() 84 85 ^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. 86 Note: If 'total' is not a multiple of 'update_frequency', the display will not show 100% completion when the loop finishes. 87 88 :param fill_ch: The character used to represent the completed part of the bar. 89 90 :param unfill_ch: The character used to represent the uncompleted part of the bar. 91 92 :param width_ratio: The width of the progress bar relative to the width of the terminal window. 93 94 :param new_line_after_completion: Make a call to print() once self.counter >= self.total. 95 96 :param clear_after_completion: Make a call to printbuddies.clear() once self.counter >= self.total. 97 98 Note: if new_line_after_completion and clear_after_completion are both True, the line will be cleared 99 then a call to print() will be made.""" 100 self.total = total 101 self.update_frequency = update_frequency 102 self.fill_ch = fill_ch[0] 103 self.unfill_ch = unfill_ch[0] 104 self.width_ratio = width_ratio 105 self.new_line_after_completion = new_line_after_completion 106 self.clear_after_completion = clear_after_completion 107 self.reset() 108 self.with_context = False 109 110 def __enter__(self): 111 self.with_context = True 112 return self 113 114 def __exit__(self, *args, **kwargs): 115 if self.clear_after_completion: 116 clear() 117 else: 118 print() 119 120 def reset(self): 121 self.counter = 1 122 self.percent = "" 123 self.prefix = "" 124 self.suffix = "" 125 self.filled = "" 126 self.unfilled = "" 127 self.bar = "" 128 self.timer = Timer(subsecond_resolution=False).start() 129 130 @property 131 def runtime(self) -> str: 132 return f"runtime:{self.timer.elapsed_str}" 133 134 def get_percent(self) -> str: 135 """Returns the percentage complete to two decimal places 136 as a string without the %.""" 137 percent = str(round(100.0 * self.counter / self.total, 2)) 138 if len(percent.split(".")[1]) == 1: 139 percent = percent + "0" 140 if len(percent.split(".")[0]) == 1: 141 percent = "0" + percent 142 return percent 143 144 def _prepare_bar(self): 145 self.terminal_width = get_terminal_size().columns - 1 146 bar_length = int(self.terminal_width * self.width_ratio) 147 progress = int(bar_length * min(self.counter / self.total, 1.0)) 148 self.filled = self.fill_ch * progress 149 self.unfilled = self.unfill_ch * (bar_length - progress) 150 self.percent = self.get_percent() 151 self.bar = self.get_bar() 152 153 def _trim_bar(self): 154 original_width = self.width_ratio 155 while len(self.bar) > self.terminal_width and self.width_ratio > 0: 156 self.width_ratio -= 0.01 157 self._prepare_bar() 158 self.width_ratio = original_width 159 160 def get_bar(self): 161 return f"{self.prefix}{' '*bool(self.prefix)}[{self.filled}{self.unfilled}]-{self.percent}% {self.suffix}" 162 163 def display( 164 self, 165 prefix: str = "", 166 suffix: str = "", 167 counter_override: float | None = None, 168 total_override: float | None = None, 169 return_object: Any | None = None, 170 ) -> Any: 171 """Writes the progress bar to the terminal. 172 173 :param prefix: String affixed to the front of the progress bar. 174 175 :param suffix: String appended to the end of the progress bar. 176 177 :param counter_override: When an externally incremented completion counter is needed. 178 179 :param total_override: When an externally controlled bar total is needed. 180 181 :param return_object: An object to be returned by display(). 182 183 Allows display() to be called within a comprehension: 184 185 e.g. 186 187 >>> bar = ProgBar(10) 188 >>> def square(x: int | float)->int|float: 189 >>> return x * x 190 >>> myList = [bar.display(return_object=square(i)) for i in range(10)] 191 >>> <progress bar gets displayed> 192 >>> myList 193 >>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]""" 194 if not self.timer.started: 195 self.timer.start() 196 if counter_override is not None: 197 self.counter = counter_override 198 if total_override: 199 self.total = total_override 200 # Don't wanna divide by 0 there, pal 201 while self.total <= 0: 202 self.total += 1 203 if self.counter % self.update_frequency == 0: 204 self.prefix = prefix 205 self.suffix = suffix 206 self._prepare_bar() 207 self._trim_bar() 208 pad = " " * (self.terminal_width - len(self.bar)) 209 width = get_terminal_size().columns 210 print(f"{self.bar}{pad}"[: width - 2], flush=True, end="\r") 211 if self.counter >= self.total: 212 self.timer.stop() 213 if not self.with_context: 214 if self.clear_after_completion: 215 clear() 216 if self.new_line_after_completion: 217 print() 218 self.counter += 1 219 return return_object
Self incrementing, dynamically sized progress bar.
Includes an internal timer that starts when this object is created. It can be easily added to the progress bar display by adding the 'runtime' property to display's prefix or suffix param:
>>> bar = ProgBar(total=100)
>>> time.sleep(30)
>>> bar.display(prefix=bar.runtime)
>>> "runtime: 30s [_///////////////////]1.00%"
66 def __init__( 67 self, 68 total: float, 69 update_frequency: int = 1, 70 fill_ch: str = "_", 71 unfill_ch: str = "/", 72 width_ratio: float = 0.75, 73 new_line_after_completion: bool = True, 74 clear_after_completion: bool = False, 75 ): 76 """:param total: The number of calls to reach 100% completion. 77 78 :param update_frequency: The progress bar will only update once every this number of calls to display(). 79 The larger the value, the less performance impact ProgBar has on the loop in which it is called. 80 e.g. 81 >>> bar = ProgBar(100, update_frequency=10) 82 >>> for _ in range(100): 83 >>> bar.display() 84 85 ^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. 86 Note: If 'total' is not a multiple of 'update_frequency', the display will not show 100% completion when the loop finishes. 87 88 :param fill_ch: The character used to represent the completed part of the bar. 89 90 :param unfill_ch: The character used to represent the uncompleted part of the bar. 91 92 :param width_ratio: The width of the progress bar relative to the width of the terminal window. 93 94 :param new_line_after_completion: Make a call to print() once self.counter >= self.total. 95 96 :param clear_after_completion: Make a call to printbuddies.clear() once self.counter >= self.total. 97 98 Note: if new_line_after_completion and clear_after_completion are both True, the line will be cleared 99 then a call to print() will be made.""" 100 self.total = total 101 self.update_frequency = update_frequency 102 self.fill_ch = fill_ch[0] 103 self.unfill_ch = unfill_ch[0] 104 self.width_ratio = width_ratio 105 self.new_line_after_completion = new_line_after_completion 106 self.clear_after_completion = clear_after_completion 107 self.reset() 108 self.with_context = False
Parameters
total: The number of calls to reach 100% completion.
update_frequency: The progress bar will only update once every this number of calls to display(). The larger the value, the less performance impact ProgBar has on the loop in which it is called. e.g.
>>> bar = ProgBar(100, update_frequency=10) >>> for _ in range(100): >>> bar.display()
^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. Note: If 'total' is not a multiple of 'update_frequency', the display will not show 100% completion when the loop finishes.
fill_ch: The character used to represent the completed part of the bar.
unfill_ch: The character used to represent the uncompleted part of the bar.
width_ratio: The width of the progress bar relative to the width of the terminal window.
new_line_after_completion: Make a call to print() once self.counter >= self.total.
clear_after_completion: Make a call to printbuddies.clear() once self.counter >= self.total.
Note: if new_line_after_completion and clear_after_completion are both True, the line will be cleared then a call to print() will be made.
134 def get_percent(self) -> str: 135 """Returns the percentage complete to two decimal places 136 as a string without the %.""" 137 percent = str(round(100.0 * self.counter / self.total, 2)) 138 if len(percent.split(".")[1]) == 1: 139 percent = percent + "0" 140 if len(percent.split(".")[0]) == 1: 141 percent = "0" + percent 142 return percent
Returns the percentage complete to two decimal places as a string without the %.
163 def display( 164 self, 165 prefix: str = "", 166 suffix: str = "", 167 counter_override: float | None = None, 168 total_override: float | None = None, 169 return_object: Any | None = None, 170 ) -> Any: 171 """Writes the progress bar to the terminal. 172 173 :param prefix: String affixed to the front of the progress bar. 174 175 :param suffix: String appended to the end of the progress bar. 176 177 :param counter_override: When an externally incremented completion counter is needed. 178 179 :param total_override: When an externally controlled bar total is needed. 180 181 :param return_object: An object to be returned by display(). 182 183 Allows display() to be called within a comprehension: 184 185 e.g. 186 187 >>> bar = ProgBar(10) 188 >>> def square(x: int | float)->int|float: 189 >>> return x * x 190 >>> myList = [bar.display(return_object=square(i)) for i in range(10)] 191 >>> <progress bar gets displayed> 192 >>> myList 193 >>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]""" 194 if not self.timer.started: 195 self.timer.start() 196 if counter_override is not None: 197 self.counter = counter_override 198 if total_override: 199 self.total = total_override 200 # Don't wanna divide by 0 there, pal 201 while self.total <= 0: 202 self.total += 1 203 if self.counter % self.update_frequency == 0: 204 self.prefix = prefix 205 self.suffix = suffix 206 self._prepare_bar() 207 self._trim_bar() 208 pad = " " * (self.terminal_width - len(self.bar)) 209 width = get_terminal_size().columns 210 print(f"{self.bar}{pad}"[: width - 2], flush=True, end="\r") 211 if self.counter >= self.total: 212 self.timer.stop() 213 if not self.with_context: 214 if self.clear_after_completion: 215 clear() 216 if self.new_line_after_completion: 217 print() 218 self.counter += 1 219 return return_object
Writes the progress bar to the terminal.
Parameters
prefix: String affixed to the front of the progress bar.
suffix: String appended to the end of the progress bar.
counter_override: When an externally incremented completion counter is needed.
total_override: When an externally controlled bar total is needed.
return_object: An object to be returned by display().
Allows display() to be called within a comprehension:
e.g.
>>> bar = ProgBar(10)
>>> def square(x: int | float)->int|float:
>>> return x * x
>>> myList = [bar.display(return_object=square(i)) for i in range(10)]
>>> <progress bar gets displayed>
>>> myList
>>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
222class Spinner: 223 """Prints one of a sequence of characters in order everytime display() is called. 224 The display function writes the new character to the same line, overwriting the previous character. 225 The sequence will be cycled through indefinitely. 226 If used as a context manager, the last printed character will be cleared upon exiting.""" 227 228 def __init__(self, sequence: list[Any] | None = None): 229 """ 230 :param sequence: Override the built in spin sequence.""" 231 if sequence: 232 self.sequence = sequence 233 else: 234 self.sequence = ["/", "-", "\\"] 235 236 def __enter__(self): 237 return self 238 239 def __exit__(self, *args, **kwargs): 240 clear() 241 242 @property 243 def sequence(self) -> list[Any]: 244 return self._sequence 245 246 @sequence.setter 247 def sequence(self, character_list: list[Any]): 248 # Buffer each element with a leading space 249 # so that the character isn't obscured by the cursor 250 self._sequence = [" " + str(ch) for ch in character_list] 251 252 def _get_next(self) -> str: 253 """Pop the first element of self._sequence, 254 append it to the end, and return the element.""" 255 ch = self.sequence.pop(0) 256 self.sequence.append(ch) 257 return ch 258 259 def display(self): 260 """Print the next character in the sequence.""" 261 print_in_place(self._get_next())
Prints one of a sequence of characters in order everytime display() is called. The display function writes the new character to the same line, overwriting the previous character. The sequence will be cycled through indefinitely. If used as a context manager, the last printed character will be cleared upon exiting.
228 def __init__(self, sequence: list[Any] | None = None): 229 """ 230 :param sequence: Override the built in spin sequence.""" 231 if sequence: 232 self.sequence = sequence 233 else: 234 self.sequence = ["/", "-", "\\"]
Parameters
- sequence: Override the built in spin sequence.