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