printbuddies.printbuddies
1from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor 2from os import get_terminal_size 3from time import sleep 4from typing import Any, Callable 5 6from noiftimer import Timer 7 8 9def clear(): 10 """Erase the current line from the terminal.""" 11 try: 12 print(" " * (get_terminal_size().columns - 1), flush=True, end="\r") 13 except OSError: 14 ... 15 except Exception as e: 16 raise e 17 18 19def print_in_place(string: str, animate: bool = False, animate_refresh: float = 0.01): 20 """Calls to `print_in_place` will overwrite the previous line of text in the terminal with `string`. 21 22 #### :params: 23 24 `animate`: Will cause `string` to be printed to the terminal one character at a time. 25 26 `animate_refresh`: Number of seconds between the addition of characters when `animate` is `True`.""" 27 clear() 28 string = str(string) 29 try: 30 width = get_terminal_size().columns 31 string = string[: width - 2] 32 if animate: 33 for i in range(len(string)): 34 print(f"{string[:i+1]}", flush=True, end=" \r") 35 sleep(animate_refresh) 36 else: 37 print(string, flush=True, end="\r") 38 except OSError: 39 ... 40 except Exception as e: 41 raise e 42 43 44def ticker(info: list[str]): 45 """Prints `info` to terminal with top and bottom padding so that previous text is not visible. 46 47 Similar visually to `print_in_place`, but for multiple lines.""" 48 try: 49 width = get_terminal_size().columns 50 info = [str(line)[: width - 1] for line in info] 51 height = get_terminal_size().lines - len(info) 52 print("\n" * (height * 2), end="") 53 print(*info, sep="\n", end="") 54 print("\n" * (int((height) / 2)), end="") 55 except OSError: 56 ... 57 except Exception as e: 58 raise e 59 60 61class ProgBar: 62 """Self incrementing, dynamically sized progress bar. 63 64 Includes an internal timer that starts when this object is created. 65 66 Easily add runtime to progress display: 67 68 >>> bar = ProgBar(total=100) 69 >>> time.sleep(30) 70 >>> bar.display(prefix=f"Doin stuff ~ {bar.runtime}") 71 >>> "Doin stuff ~ runtime: 30s [_///////////////////]-1.00%" """ 72 73 def __init__( 74 self, 75 total: float, 76 update_frequency: int = 1, 77 fill_ch: str = "_", 78 unfill_ch: str = "/", 79 width_ratio: float = 0.5, 80 new_line_after_completion: bool = True, 81 clear_after_completion: bool = False, 82 ): 83 """ 84 #### :params: 85 86 `total`: The number of calls to reach 100% completion. 87 88 `update_frequency`: The progress bar will only update once every this number of calls to `display()`. 89 The larger the value, the less performance impact `ProgBar` has on the loop in which it is called. 90 e.g. 91 >>> bar = ProgBar(100, update_frequency=10) 92 >>> for _ in range(100): 93 >>> bar.display() 94 95 ^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. 96 Note: If `total` is not a multiple of `update_frequency`, the display will not show 100% completion when the loop finishes. 97 98 `fill_ch`: The character used to represent the completed part of the bar. 99 100 `unfill_ch`: The character used to represent the incomplete part of the bar. 101 102 `width_ratio`: The width of the progress bar relative to the width of the terminal window. 103 104 `new_line_after_completion`: Make a call to `print()` once `self.counter >= self.total`. 105 106 `clear_after_completion`: Make a call to `printbuddies.clear()` once `self.counter >= self.total`. 107 108 Note: if `new_line_after_completion` and `clear_after_completion` are both `True`, the line will be cleared 109 then a call to `print()` will be made.""" 110 self.total = total 111 self.update_frequency = update_frequency 112 self.fill_ch = fill_ch[0] 113 self.unfill_ch = unfill_ch[0] 114 self.width_ratio = width_ratio 115 self.new_line_after_completion = new_line_after_completion 116 self.clear_after_completion = clear_after_completion 117 self.reset() 118 self.with_context = False 119 120 def __enter__(self): 121 self.with_context = True 122 return self 123 124 def __exit__(self, *args, **kwargs): 125 if self.clear_after_completion: 126 clear() 127 else: 128 print() 129 130 def reset(self): 131 self.counter = 1 132 self.percent = "" 133 self.prefix = "" 134 self.suffix = "" 135 self.filled = "" 136 self.unfilled = "" 137 self.timer = Timer(subsecond_resolution=False).start() 138 139 @property 140 def runtime(self) -> str: 141 return f"runtime:{self.timer.elapsed_str}" 142 143 @property 144 def bar(self) -> str: 145 return f"{self.prefix}{' '*bool(self.prefix)}[{self.filled}{self.unfilled}]-{self.percent}% {self.suffix}" 146 147 def get_percent(self) -> str: 148 """Returns the percentage completed to two decimal places as a string without the `%`.""" 149 percent = str(round(100.0 * self.counter / self.total, 2)) 150 if len(percent.split(".")[1]) == 1: 151 percent = percent + "0" 152 if len(percent.split(".")[0]) == 1: 153 percent = "0" + percent 154 return percent 155 156 def _prepare_bar(self): 157 self.terminal_width = get_terminal_size().columns - 1 158 bar_length = int(self.terminal_width * self.width_ratio) 159 progress = int(bar_length * min(self.counter / self.total, 1.0)) 160 self.filled = self.fill_ch * progress 161 self.unfilled = self.unfill_ch * (bar_length - progress) 162 self.percent = self.get_percent() 163 164 def _trim_bar(self): 165 original_width = self.width_ratio 166 while len(self.bar) > self.terminal_width and self.width_ratio > 0: 167 self.width_ratio -= 0.01 168 self._prepare_bar() 169 self.width_ratio = original_width 170 171 def get_bar(self): 172 return f"{self.prefix}{' '*bool(self.prefix)}[{self.filled}{self.unfilled}]-{self.percent}% {self.suffix}" 173 174 def display( 175 self, 176 prefix: str = "", 177 suffix: str = "", 178 counter_override: float | None = None, 179 total_override: float | None = None, 180 return_object: Any | None = None, 181 ) -> Any: 182 """Writes the progress bar to the terminal. 183 184 #### :params: 185 186 `prefix`: String affixed to the front of the progress bar. 187 188 `suffix`: String appended to the end of the progress bar. 189 190 `counter_override`: When an externally incremented completion counter is needed. 191 192 `total_override`: When an externally controlled bar total is needed. 193 194 `return_object`: An object to be returned by display(). 195 Allows `display()` to be called within a comprehension: 196 197 e.g. 198 199 >>> bar = ProgBar(10) 200 >>> def square(x: int | float)->int|float: 201 >>> return x * x 202 >>> myList = [bar.display(return_object=square(i)) for i in range(10)] 203 >>> <progress bar gets displayed> 204 >>> myList 205 >>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]""" 206 if not self.timer.started: 207 self.timer.start() 208 if counter_override is not None: 209 self.counter = counter_override 210 if total_override: 211 self.total = total_override 212 # Don't wanna divide by 0 there, pal 213 while self.total <= 0: 214 self.total += 1 215 try: 216 if self.counter % self.update_frequency == 0: 217 self.prefix = prefix 218 self.suffix = suffix 219 self._prepare_bar() 220 self._trim_bar() 221 pad = " " * (self.terminal_width - len(self.bar)) 222 width = get_terminal_size().columns 223 print(f"{self.bar}{pad}"[: width - 2], flush=True, end="\r") 224 if self.counter >= self.total: 225 self.timer.stop() 226 if not self.with_context: 227 if self.clear_after_completion: 228 clear() 229 if self.new_line_after_completion: 230 print() 231 self.counter += 1 232 except OSError: 233 ... 234 except Exception as e: 235 raise e 236 return return_object 237 238 239class PoolBar: 240 def __init__( 241 self, 242 pool_type: str, 243 funcs: list[Callable[[Any], Any]], 244 args: list[tuple[Any, ...]] | None = None, 245 ): 246 """Integrates multi thread/process execution with `ProgBar`. 247 248 #### :params: 249 250 `pool_type`: Should be either `thread` or `process`. 251 252 `funcs`: List of functions to be executed. 253 254 `args`: A list of tuples where each tuple is the args to be used with each function. 255 256 Returns a list of whatever results the functions in `funcs` return. 257 258 >>> def my_func(page: int)->str: 259 >>> return requests.get(f"https://somesite.com/pages/{page}").text 260 >>> pool = PoolBar("thread", [my_func for _ in range(10)], [(i,) for i in range(10)]) 261 >>> pages = pool.execute()""" 262 263 self.pool_type = pool_type 264 self.funcs = funcs 265 self.args = args 266 267 @property 268 def pool_type(self) -> str: 269 return self._pool_type 270 271 @pool_type.setter 272 def pool_type(self, pool_type: str): 273 if pool_type not in ["thread", "process"]: 274 raise ValueError(f"pool_type '{pool_type}' must be 'thread' or 'process'.") 275 self._pool_type = pool_type 276 277 def _get_executor(self) -> ThreadPoolExecutor | ProcessPoolExecutor: 278 match self.pool_type: 279 case "thread": 280 return ThreadPoolExecutor 281 case "process": 282 return ProcessPoolExecutor 283 284 def execute(self, *progbar_args, **progbar_kwargs) -> list[Any]: 285 """Execute the supplied functions with their supplied arguments, if any. 286 287 `*progbar_args` and `**progbar_kwargs` can be any arguments the `ProgBar` constructor takes besides `total`.""" 288 num_workers = len(self.funcs) 289 with ProgBar(num_workers, *progbar_args, **progbar_kwargs) as bar: 290 with self._get_executor()() as executor: 291 if self.args: 292 workers = [ 293 executor.submit(func, *args) 294 for func, args in zip(self.funcs, self.args) 295 ] 296 else: 297 workers = [executor.submit(func) for func in self.funcs] 298 while ( 299 num_complete := len([worker for worker in workers if worker.done()]) 300 ) < num_workers: 301 bar.display(f"{bar.runtime}", counter_override=num_complete) 302 sleep(0.1) 303 bar.display(f"{bar.runtime}", counter_override=num_complete) 304 return [worker.result() for worker in workers] 305 306 307class Spinner: 308 """Prints one of a sequence of characters in order everytime `display()` is called. 309 310 The `display` function writes the new character to the same line, overwriting the previous character. 311 312 The sequence will be cycled through indefinitely. 313 314 If used as a context manager, the last printed character will be cleared upon exiting. 315 """ 316 317 def __init__( 318 self, sequence: list[str] = ["/", "-", "\\"], width_ratio: float = 0.25 319 ): 320 """ 321 #### params: 322 323 `sequence`: Override the built in spin sequence. 324 325 `width_ratio`: The fractional amount of the terminal for characters to move across.""" 326 self._base_sequence = sequence 327 self.width_ratio = width_ratio 328 self.sequence = self._base_sequence 329 330 def __enter__(self): 331 return self 332 333 def __exit__(self, *args, **kwargs): 334 clear() 335 336 @property 337 def width_ratio(self) -> float: 338 return self._width_ratio 339 340 @width_ratio.setter 341 def width_ratio(self, ratio: float): 342 self._width_ratio = ratio 343 self._update_width() 344 345 def _update_width(self): 346 self._current_terminal_width = get_terminal_size().columns 347 self._width = int((self._current_terminal_width - 1) * self.width_ratio) 348 349 @property 350 def sequence(self) -> list[Any]: 351 return self._sequence 352 353 @sequence.setter 354 def sequence(self, character_list: list[Any]): 355 self._sequence = [ 356 ch.rjust(i + j) 357 for i in range(1, self._width, len(character_list)) 358 for j, ch in enumerate(character_list) 359 ] 360 self._sequence += self._sequence[::-1] 361 362 def _get_next(self) -> str: 363 """Pop the first element of `self._sequence`, append it to the end, and return the element.""" 364 ch = self.sequence.pop(0) 365 self.sequence.append(ch) 366 return ch 367 368 def display(self): 369 """Print the next character in the sequence.""" 370 try: 371 if get_terminal_size().columns != self._current_terminal_width: 372 self._update_width() 373 self.sequence = self._base_sequence 374 print_in_place(self._get_next()) 375 except OSError: 376 ... 377 except Exception as e: 378 raise e
10def clear(): 11 """Erase the current line from the terminal.""" 12 try: 13 print(" " * (get_terminal_size().columns - 1), flush=True, end="\r") 14 except OSError: 15 ... 16 except Exception as e: 17 raise e
Erase the current line from the terminal.
20def print_in_place(string: str, animate: bool = False, animate_refresh: float = 0.01): 21 """Calls to `print_in_place` will overwrite the previous line of text in the terminal with `string`. 22 23 #### :params: 24 25 `animate`: Will cause `string` to be printed to the terminal one character at a time. 26 27 `animate_refresh`: Number of seconds between the addition of characters when `animate` is `True`.""" 28 clear() 29 string = str(string) 30 try: 31 width = get_terminal_size().columns 32 string = string[: width - 2] 33 if animate: 34 for i in range(len(string)): 35 print(f"{string[:i+1]}", flush=True, end=" \r") 36 sleep(animate_refresh) 37 else: 38 print(string, flush=True, end="\r") 39 except OSError: 40 ... 41 except Exception as e: 42 raise e
Calls to print_in_place
will overwrite the previous line of text in the terminal with string
.
:params:
animate
: Will cause 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
.
45def ticker(info: list[str]): 46 """Prints `info` to terminal with top and bottom padding so that previous text is not visible. 47 48 Similar visually to `print_in_place`, but for multiple lines.""" 49 try: 50 width = get_terminal_size().columns 51 info = [str(line)[: width - 1] for line in info] 52 height = get_terminal_size().lines - len(info) 53 print("\n" * (height * 2), end="") 54 print(*info, sep="\n", end="") 55 print("\n" * (int((height) / 2)), end="") 56 except OSError: 57 ... 58 except Exception as e: 59 raise e
Prints info
to terminal with top and bottom padding so that previous text is not visible.
Similar visually to print_in_place
, but for multiple lines.
62class ProgBar: 63 """Self incrementing, dynamically sized progress bar. 64 65 Includes an internal timer that starts when this object is created. 66 67 Easily add runtime to progress display: 68 69 >>> bar = ProgBar(total=100) 70 >>> time.sleep(30) 71 >>> bar.display(prefix=f"Doin stuff ~ {bar.runtime}") 72 >>> "Doin stuff ~ runtime: 30s [_///////////////////]-1.00%" """ 73 74 def __init__( 75 self, 76 total: float, 77 update_frequency: int = 1, 78 fill_ch: str = "_", 79 unfill_ch: str = "/", 80 width_ratio: float = 0.5, 81 new_line_after_completion: bool = True, 82 clear_after_completion: bool = False, 83 ): 84 """ 85 #### :params: 86 87 `total`: The number of calls to reach 100% completion. 88 89 `update_frequency`: The progress bar will only update once every this number of calls to `display()`. 90 The larger the value, the less performance impact `ProgBar` has on the loop in which it is called. 91 e.g. 92 >>> bar = ProgBar(100, update_frequency=10) 93 >>> for _ in range(100): 94 >>> bar.display() 95 96 ^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. 97 Note: If `total` is not a multiple of `update_frequency`, the display will not show 100% completion when the loop finishes. 98 99 `fill_ch`: The character used to represent the completed part of the bar. 100 101 `unfill_ch`: The character used to represent the incomplete part of the bar. 102 103 `width_ratio`: The width of the progress bar relative to the width of the terminal window. 104 105 `new_line_after_completion`: Make a call to `print()` once `self.counter >= self.total`. 106 107 `clear_after_completion`: Make a call to `printbuddies.clear()` once `self.counter >= self.total`. 108 109 Note: if `new_line_after_completion` and `clear_after_completion` are both `True`, the line will be cleared 110 then a call to `print()` will be made.""" 111 self.total = total 112 self.update_frequency = update_frequency 113 self.fill_ch = fill_ch[0] 114 self.unfill_ch = unfill_ch[0] 115 self.width_ratio = width_ratio 116 self.new_line_after_completion = new_line_after_completion 117 self.clear_after_completion = clear_after_completion 118 self.reset() 119 self.with_context = False 120 121 def __enter__(self): 122 self.with_context = True 123 return self 124 125 def __exit__(self, *args, **kwargs): 126 if self.clear_after_completion: 127 clear() 128 else: 129 print() 130 131 def reset(self): 132 self.counter = 1 133 self.percent = "" 134 self.prefix = "" 135 self.suffix = "" 136 self.filled = "" 137 self.unfilled = "" 138 self.timer = Timer(subsecond_resolution=False).start() 139 140 @property 141 def runtime(self) -> str: 142 return f"runtime:{self.timer.elapsed_str}" 143 144 @property 145 def bar(self) -> str: 146 return f"{self.prefix}{' '*bool(self.prefix)}[{self.filled}{self.unfilled}]-{self.percent}% {self.suffix}" 147 148 def get_percent(self) -> str: 149 """Returns the percentage completed to two decimal places as a string without the `%`.""" 150 percent = str(round(100.0 * self.counter / self.total, 2)) 151 if len(percent.split(".")[1]) == 1: 152 percent = percent + "0" 153 if len(percent.split(".")[0]) == 1: 154 percent = "0" + percent 155 return percent 156 157 def _prepare_bar(self): 158 self.terminal_width = get_terminal_size().columns - 1 159 bar_length = int(self.terminal_width * self.width_ratio) 160 progress = int(bar_length * min(self.counter / self.total, 1.0)) 161 self.filled = self.fill_ch * progress 162 self.unfilled = self.unfill_ch * (bar_length - progress) 163 self.percent = self.get_percent() 164 165 def _trim_bar(self): 166 original_width = self.width_ratio 167 while len(self.bar) > self.terminal_width and self.width_ratio > 0: 168 self.width_ratio -= 0.01 169 self._prepare_bar() 170 self.width_ratio = original_width 171 172 def get_bar(self): 173 return f"{self.prefix}{' '*bool(self.prefix)}[{self.filled}{self.unfilled}]-{self.percent}% {self.suffix}" 174 175 def display( 176 self, 177 prefix: str = "", 178 suffix: str = "", 179 counter_override: float | None = None, 180 total_override: float | None = None, 181 return_object: Any | None = None, 182 ) -> Any: 183 """Writes the progress bar to the terminal. 184 185 #### :params: 186 187 `prefix`: String affixed to the front of the progress bar. 188 189 `suffix`: String appended to the end of the progress bar. 190 191 `counter_override`: When an externally incremented completion counter is needed. 192 193 `total_override`: When an externally controlled bar total is needed. 194 195 `return_object`: An object to be returned by display(). 196 Allows `display()` to be called within a comprehension: 197 198 e.g. 199 200 >>> bar = ProgBar(10) 201 >>> def square(x: int | float)->int|float: 202 >>> return x * x 203 >>> myList = [bar.display(return_object=square(i)) for i in range(10)] 204 >>> <progress bar gets displayed> 205 >>> myList 206 >>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]""" 207 if not self.timer.started: 208 self.timer.start() 209 if counter_override is not None: 210 self.counter = counter_override 211 if total_override: 212 self.total = total_override 213 # Don't wanna divide by 0 there, pal 214 while self.total <= 0: 215 self.total += 1 216 try: 217 if self.counter % self.update_frequency == 0: 218 self.prefix = prefix 219 self.suffix = suffix 220 self._prepare_bar() 221 self._trim_bar() 222 pad = " " * (self.terminal_width - len(self.bar)) 223 width = get_terminal_size().columns 224 print(f"{self.bar}{pad}"[: width - 2], flush=True, end="\r") 225 if self.counter >= self.total: 226 self.timer.stop() 227 if not self.with_context: 228 if self.clear_after_completion: 229 clear() 230 if self.new_line_after_completion: 231 print() 232 self.counter += 1 233 except OSError: 234 ... 235 except Exception as e: 236 raise e 237 return return_object
Self incrementing, dynamically sized progress bar.
Includes an internal timer that starts when this object is created.
Easily add runtime to progress display:
>>> bar = ProgBar(total=100)
>>> time.sleep(30)
>>> bar.display(prefix=f"Doin stuff ~ {bar.runtime}")
>>> "Doin stuff ~ runtime: 30s [_///////////////////]-1.00%"
74 def __init__( 75 self, 76 total: float, 77 update_frequency: int = 1, 78 fill_ch: str = "_", 79 unfill_ch: str = "/", 80 width_ratio: float = 0.5, 81 new_line_after_completion: bool = True, 82 clear_after_completion: bool = False, 83 ): 84 """ 85 #### :params: 86 87 `total`: The number of calls to reach 100% completion. 88 89 `update_frequency`: The progress bar will only update once every this number of calls to `display()`. 90 The larger the value, the less performance impact `ProgBar` has on the loop in which it is called. 91 e.g. 92 >>> bar = ProgBar(100, update_frequency=10) 93 >>> for _ in range(100): 94 >>> bar.display() 95 96 ^The progress bar in the terminal will only update once every ten calls, going from 0%->100% in 10% increments. 97 Note: If `total` is not a multiple of `update_frequency`, the display will not show 100% completion when the loop finishes. 98 99 `fill_ch`: The character used to represent the completed part of the bar. 100 101 `unfill_ch`: The character used to represent the incomplete part of the bar. 102 103 `width_ratio`: The width of the progress bar relative to the width of the terminal window. 104 105 `new_line_after_completion`: Make a call to `print()` once `self.counter >= self.total`. 106 107 `clear_after_completion`: Make a call to `printbuddies.clear()` once `self.counter >= self.total`. 108 109 Note: if `new_line_after_completion` and `clear_after_completion` are both `True`, the line will be cleared 110 then a call to `print()` will be made.""" 111 self.total = total 112 self.update_frequency = update_frequency 113 self.fill_ch = fill_ch[0] 114 self.unfill_ch = unfill_ch[0] 115 self.width_ratio = width_ratio 116 self.new_line_after_completion = new_line_after_completion 117 self.clear_after_completion = clear_after_completion 118 self.reset() 119 self.with_context = False
:params:
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 incomplete 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.
148 def get_percent(self) -> str: 149 """Returns the percentage completed to two decimal places as a string without the `%`.""" 150 percent = str(round(100.0 * self.counter / self.total, 2)) 151 if len(percent.split(".")[1]) == 1: 152 percent = percent + "0" 153 if len(percent.split(".")[0]) == 1: 154 percent = "0" + percent 155 return percent
Returns the percentage completed to two decimal places as a string without the %
.
175 def display( 176 self, 177 prefix: str = "", 178 suffix: str = "", 179 counter_override: float | None = None, 180 total_override: float | None = None, 181 return_object: Any | None = None, 182 ) -> Any: 183 """Writes the progress bar to the terminal. 184 185 #### :params: 186 187 `prefix`: String affixed to the front of the progress bar. 188 189 `suffix`: String appended to the end of the progress bar. 190 191 `counter_override`: When an externally incremented completion counter is needed. 192 193 `total_override`: When an externally controlled bar total is needed. 194 195 `return_object`: An object to be returned by display(). 196 Allows `display()` to be called within a comprehension: 197 198 e.g. 199 200 >>> bar = ProgBar(10) 201 >>> def square(x: int | float)->int|float: 202 >>> return x * x 203 >>> myList = [bar.display(return_object=square(i)) for i in range(10)] 204 >>> <progress bar gets displayed> 205 >>> myList 206 >>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]""" 207 if not self.timer.started: 208 self.timer.start() 209 if counter_override is not None: 210 self.counter = counter_override 211 if total_override: 212 self.total = total_override 213 # Don't wanna divide by 0 there, pal 214 while self.total <= 0: 215 self.total += 1 216 try: 217 if self.counter % self.update_frequency == 0: 218 self.prefix = prefix 219 self.suffix = suffix 220 self._prepare_bar() 221 self._trim_bar() 222 pad = " " * (self.terminal_width - len(self.bar)) 223 width = get_terminal_size().columns 224 print(f"{self.bar}{pad}"[: width - 2], flush=True, end="\r") 225 if self.counter >= self.total: 226 self.timer.stop() 227 if not self.with_context: 228 if self.clear_after_completion: 229 clear() 230 if self.new_line_after_completion: 231 print() 232 self.counter += 1 233 except OSError: 234 ... 235 except Exception as e: 236 raise e 237 return return_object
Writes the progress bar to the terminal.
:params:
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]
240class PoolBar: 241 def __init__( 242 self, 243 pool_type: str, 244 funcs: list[Callable[[Any], Any]], 245 args: list[tuple[Any, ...]] | None = None, 246 ): 247 """Integrates multi thread/process execution with `ProgBar`. 248 249 #### :params: 250 251 `pool_type`: Should be either `thread` or `process`. 252 253 `funcs`: List of functions to be executed. 254 255 `args`: A list of tuples where each tuple is the args to be used with each function. 256 257 Returns a list of whatever results the functions in `funcs` return. 258 259 >>> def my_func(page: int)->str: 260 >>> return requests.get(f"https://somesite.com/pages/{page}").text 261 >>> pool = PoolBar("thread", [my_func for _ in range(10)], [(i,) for i in range(10)]) 262 >>> pages = pool.execute()""" 263 264 self.pool_type = pool_type 265 self.funcs = funcs 266 self.args = args 267 268 @property 269 def pool_type(self) -> str: 270 return self._pool_type 271 272 @pool_type.setter 273 def pool_type(self, pool_type: str): 274 if pool_type not in ["thread", "process"]: 275 raise ValueError(f"pool_type '{pool_type}' must be 'thread' or 'process'.") 276 self._pool_type = pool_type 277 278 def _get_executor(self) -> ThreadPoolExecutor | ProcessPoolExecutor: 279 match self.pool_type: 280 case "thread": 281 return ThreadPoolExecutor 282 case "process": 283 return ProcessPoolExecutor 284 285 def execute(self, *progbar_args, **progbar_kwargs) -> list[Any]: 286 """Execute the supplied functions with their supplied arguments, if any. 287 288 `*progbar_args` and `**progbar_kwargs` can be any arguments the `ProgBar` constructor takes besides `total`.""" 289 num_workers = len(self.funcs) 290 with ProgBar(num_workers, *progbar_args, **progbar_kwargs) as bar: 291 with self._get_executor()() as executor: 292 if self.args: 293 workers = [ 294 executor.submit(func, *args) 295 for func, args in zip(self.funcs, self.args) 296 ] 297 else: 298 workers = [executor.submit(func) for func in self.funcs] 299 while ( 300 num_complete := len([worker for worker in workers if worker.done()]) 301 ) < num_workers: 302 bar.display(f"{bar.runtime}", counter_override=num_complete) 303 sleep(0.1) 304 bar.display(f"{bar.runtime}", counter_override=num_complete) 305 return [worker.result() for worker in workers]
241 def __init__( 242 self, 243 pool_type: str, 244 funcs: list[Callable[[Any], Any]], 245 args: list[tuple[Any, ...]] | None = None, 246 ): 247 """Integrates multi thread/process execution with `ProgBar`. 248 249 #### :params: 250 251 `pool_type`: Should be either `thread` or `process`. 252 253 `funcs`: List of functions to be executed. 254 255 `args`: A list of tuples where each tuple is the args to be used with each function. 256 257 Returns a list of whatever results the functions in `funcs` return. 258 259 >>> def my_func(page: int)->str: 260 >>> return requests.get(f"https://somesite.com/pages/{page}").text 261 >>> pool = PoolBar("thread", [my_func for _ in range(10)], [(i,) for i in range(10)]) 262 >>> pages = pool.execute()""" 263 264 self.pool_type = pool_type 265 self.funcs = funcs 266 self.args = args
Integrates multi thread/process execution with ProgBar
.
:params:
pool_type
: Should be either thread
or process
.
funcs
: List of functions to be executed.
args
: A list of tuples where each tuple is the args to be used with each function.
Returns a list of whatever results the functions in funcs
return.
>>> def my_func(page: int)->str:
>>> return requests.get(f"https://somesite.com/pages/{page}").text
>>> pool = PoolBar("thread", [my_func for _ in range(10)], [(i,) for i in range(10)])
>>> pages = pool.execute()
285 def execute(self, *progbar_args, **progbar_kwargs) -> list[Any]: 286 """Execute the supplied functions with their supplied arguments, if any. 287 288 `*progbar_args` and `**progbar_kwargs` can be any arguments the `ProgBar` constructor takes besides `total`.""" 289 num_workers = len(self.funcs) 290 with ProgBar(num_workers, *progbar_args, **progbar_kwargs) as bar: 291 with self._get_executor()() as executor: 292 if self.args: 293 workers = [ 294 executor.submit(func, *args) 295 for func, args in zip(self.funcs, self.args) 296 ] 297 else: 298 workers = [executor.submit(func) for func in self.funcs] 299 while ( 300 num_complete := len([worker for worker in workers if worker.done()]) 301 ) < num_workers: 302 bar.display(f"{bar.runtime}", counter_override=num_complete) 303 sleep(0.1) 304 bar.display(f"{bar.runtime}", counter_override=num_complete) 305 return [worker.result() for worker in workers]
Execute the supplied functions with their supplied arguments, if any.
*progbar_args
and **progbar_kwargs
can be any arguments the ProgBar
constructor takes besides total
.
308class Spinner: 309 """Prints one of a sequence of characters in order everytime `display()` is called. 310 311 The `display` function writes the new character to the same line, overwriting the previous character. 312 313 The sequence will be cycled through indefinitely. 314 315 If used as a context manager, the last printed character will be cleared upon exiting. 316 """ 317 318 def __init__( 319 self, sequence: list[str] = ["/", "-", "\\"], width_ratio: float = 0.25 320 ): 321 """ 322 #### params: 323 324 `sequence`: Override the built in spin sequence. 325 326 `width_ratio`: The fractional amount of the terminal for characters to move across.""" 327 self._base_sequence = sequence 328 self.width_ratio = width_ratio 329 self.sequence = self._base_sequence 330 331 def __enter__(self): 332 return self 333 334 def __exit__(self, *args, **kwargs): 335 clear() 336 337 @property 338 def width_ratio(self) -> float: 339 return self._width_ratio 340 341 @width_ratio.setter 342 def width_ratio(self, ratio: float): 343 self._width_ratio = ratio 344 self._update_width() 345 346 def _update_width(self): 347 self._current_terminal_width = get_terminal_size().columns 348 self._width = int((self._current_terminal_width - 1) * self.width_ratio) 349 350 @property 351 def sequence(self) -> list[Any]: 352 return self._sequence 353 354 @sequence.setter 355 def sequence(self, character_list: list[Any]): 356 self._sequence = [ 357 ch.rjust(i + j) 358 for i in range(1, self._width, len(character_list)) 359 for j, ch in enumerate(character_list) 360 ] 361 self._sequence += self._sequence[::-1] 362 363 def _get_next(self) -> str: 364 """Pop the first element of `self._sequence`, append it to the end, and return the element.""" 365 ch = self.sequence.pop(0) 366 self.sequence.append(ch) 367 return ch 368 369 def display(self): 370 """Print the next character in the sequence.""" 371 try: 372 if get_terminal_size().columns != self._current_terminal_width: 373 self._update_width() 374 self.sequence = self._base_sequence 375 print_in_place(self._get_next()) 376 except OSError: 377 ... 378 except Exception as e: 379 raise e
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.
318 def __init__( 319 self, sequence: list[str] = ["/", "-", "\\"], width_ratio: float = 0.25 320 ): 321 """ 322 #### params: 323 324 `sequence`: Override the built in spin sequence. 325 326 `width_ratio`: The fractional amount of the terminal for characters to move across.""" 327 self._base_sequence = sequence 328 self.width_ratio = width_ratio 329 self.sequence = self._base_sequence
params:
sequence
: Override the built in spin sequence.
width_ratio
: The fractional amount of the terminal for characters to move across.
369 def display(self): 370 """Print the next character in the sequence.""" 371 try: 372 if get_terminal_size().columns != self._current_terminal_width: 373 self._update_width() 374 self.sequence = self._base_sequence 375 print_in_place(self._get_next()) 376 except OSError: 377 ... 378 except Exception as e: 379 raise e
Print the next character in the sequence.