pytree2 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pytree/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # pytree/__init__.py
@@ -0,0 +1,514 @@
1
+ # ProgressTracker module
2
+
3
+ # Code destined to defining
4
+ # ProgressTracker class and
5
+ # related attributes/methods.
6
+
7
+ ######################################################################
8
+ # imports
9
+
10
+ # importing required libraries
11
+ from time import time
12
+ from time import sleep
13
+ from sys import stdout
14
+ from threading import Lock
15
+ from threading import Event
16
+ from os import _exit # noqa
17
+ from threading import Thread
18
+ from pytree.utils.aux_funcs import flush_string
19
+ from pytree.utils.aux_funcs import get_time_str
20
+ from pytree.utils.global_vars import UPDATE_TIME
21
+ from pytree.utils.aux_funcs import get_number_string
22
+
23
+ #####################################################################
24
+ # ProgressTracker definition
25
+
26
+
27
+ class ProgressTracker:
28
+ """
29
+ Defines ProgressTracker class.
30
+ """
31
+ def __init__(self) -> None:
32
+ """
33
+ Initializes a ProgressTracker instance
34
+ and defines class attributes.
35
+ """
36
+ # defining class attributes (shared by all subclasses)
37
+
38
+ # time
39
+ self.start_time = self.get_current_time()
40
+ self.current_time = self.get_current_time()
41
+ self.elapsed_time = 0
42
+ self.elapsed_time_str = ''
43
+ self.etc = 0
44
+ self.etc_str = ''
45
+
46
+ # iteration
47
+ self.iterations_num = 0
48
+ self.current_iteration = 0
49
+ self.totals_updated = Event()
50
+
51
+ # progress
52
+ self.progress_percentage = 0
53
+ self.progress_percentage_str = ''
54
+ self.progress_string = ''
55
+
56
+ # threads
57
+ self.progress_thread = Thread(target=self.update_progress) # used to start separate thread for monitoring progress
58
+ self.lock = Lock() # used to prevent race conditions
59
+ self.process_complete = Event() # used to signal end or break, offering a clean shutdown
60
+
61
+ # wheel
62
+ self.wheel_symbol_list = ['\\',
63
+ '|',
64
+ '/',
65
+ '-']
66
+ self.wheel_index = 0
67
+ self.wheel_symbol = ''
68
+
69
+ # totals
70
+ self.totals_string = ''
71
+
72
+ # end string
73
+ self.end_string = 'analysis complete!'
74
+
75
+ @staticmethod
76
+ def wait(seconds: float = 0.005) -> None:
77
+ """
78
+ Waits given time in seconds
79
+ before proceeding with execution.
80
+ """
81
+ # sleeping
82
+ sleep(seconds)
83
+
84
+ def print_totals(self) -> None:
85
+ """
86
+ Prints iterations totals.
87
+ """
88
+ # clearing console
89
+ self.clear_progress()
90
+
91
+ # printing totals string
92
+ print(self.totals_string)
93
+
94
+ def signal_totals_updated(self) -> None:
95
+ """
96
+ Sets threading.Event as set,
97
+ signaling totals updated.
98
+ """
99
+ # printing totals
100
+ self.print_totals()
101
+
102
+ # signaling the progress tracker to stop
103
+ self.totals_updated.set()
104
+
105
+ # waiting some time to ensure signal is perceived
106
+ self.wait(seconds=0.8)
107
+
108
+ def update_totals(self,
109
+ args_dict: dict
110
+ ) -> None:
111
+ """
112
+ Defines base method to obtain
113
+ and update total iterations num.
114
+ """
115
+ # updating attributes
116
+ self.iterations_num = 1
117
+
118
+ # updating totals string
119
+ totals_string = f'totals...'
120
+ totals_string += f' | iterations: {self.iterations_num}'
121
+ self.totals_string = totals_string
122
+
123
+ # signaling totals updated
124
+ self.signal_totals_updated()
125
+
126
+ def update_wheel_symbol(self) -> None:
127
+ """
128
+ Updates wheel symbol.
129
+ """
130
+ # getting updated wheel index
131
+ if self.wheel_index == 3:
132
+ self.wheel_index = 0
133
+ else:
134
+ self.wheel_index += 1
135
+
136
+ # updating current wheel symbol
137
+ self.wheel_symbol = self.wheel_symbol_list[self.wheel_index]
138
+
139
+ # checking if process complete event is set
140
+ if self.process_complete.is_set():
141
+
142
+ # overwriting if final event is set
143
+ self.wheel_symbol = '\b'
144
+
145
+ @staticmethod
146
+ def get_current_time() -> int:
147
+ """
148
+ Gets current UTC time, in seconds.
149
+ """
150
+ # getting current time
151
+ current_time = time()
152
+
153
+ # getting seconds
154
+ current_seconds = int(current_time)
155
+
156
+ # returning current time in seconds
157
+ return current_seconds
158
+
159
+ def reset_timer(self) -> None:
160
+ """
161
+ Resets start time to be more
162
+ reliable when after user inputs
163
+ or iterations calculations.
164
+ """
165
+ # resetting start time
166
+ self.start_time = self.get_current_time()
167
+
168
+ def get_elapsed_time(self) -> int:
169
+ """
170
+ Returns time difference
171
+ between start time and
172
+ current time, in seconds.
173
+ """
174
+ # getting elapsed time (time difference)
175
+ elapsed_time = self.current_time - self.start_time
176
+
177
+ # returning elapsed time
178
+ return elapsed_time
179
+
180
+ def get_etc(self) -> int:
181
+ """
182
+ Based on iteration and time
183
+ attributes, returns estimated time
184
+ of completion (ETC).
185
+ """
186
+ # defining base value for etc
187
+ etc = 3600
188
+
189
+ # getting iterations to go
190
+ iterations_to_go = self.iterations_num - self.current_iteration
191
+
192
+ # checking if first iteration is running
193
+ if self.current_iteration >= 1:
194
+
195
+ # calculating estimated time of completion
196
+ etc = iterations_to_go * self.elapsed_time / self.current_iteration
197
+
198
+ # rounding time
199
+ etc = round(etc)
200
+
201
+ # converting estimated time of completion to int
202
+ etc = int(etc)
203
+
204
+ # returning estimated time of completion
205
+ return etc
206
+
207
+ def update_time_attributes(self) -> None:
208
+ """
209
+ Updates time related attributes.
210
+ """
211
+ # updating time attributes
212
+ self.current_time = self.get_current_time()
213
+ self.elapsed_time = self.get_elapsed_time()
214
+ self.elapsed_time_str = get_time_str(time_in_seconds=self.elapsed_time)
215
+ self.etc = self.get_etc()
216
+ self.etc_str = get_time_str(time_in_seconds=self.etc)
217
+
218
+ @staticmethod
219
+ def get_percentage_string(percentage: int) -> str:
220
+ """
221
+ Given a value in percentage,
222
+ returns value as a string,
223
+ adding '%' to the right side.
224
+ """
225
+ # updating value to be in range of 2 digits
226
+ percentage_str = get_number_string(num=percentage,
227
+ digits=2)
228
+
229
+ # assembling percentage string
230
+ percentage_string = f'{percentage_str}%'
231
+
232
+ # checking if percentage is 100%
233
+ if percentage == 100:
234
+
235
+ # updating percentage string
236
+ percentage_string = '100%'
237
+
238
+ # returning percentage string
239
+ return percentage_string
240
+
241
+ def get_progress_percentage(self) -> int:
242
+ """
243
+ Returns a formated progress
244
+ string based on current iteration
245
+ and iterations num.
246
+ """
247
+ # getting percentage progress
248
+ try:
249
+ progress_ratio = self.current_iteration / self.iterations_num
250
+ except ZeroDivisionError:
251
+ progress_ratio = 0
252
+ progress_percentage = progress_ratio * 100
253
+
254
+ # rounding value for pretty print
255
+ progress_percentage = round(progress_percentage)
256
+
257
+ # returning progress percentage
258
+ return progress_percentage
259
+
260
+ def get_progress_string(self) -> str:
261
+ """
262
+ Returns a formated progress
263
+ string, based on current progress
264
+ attributes.
265
+ !Provides a generalist progress bar.
266
+ Can be overwritten to consider module
267
+ specific attributes!
268
+ """
269
+ # assembling current progress string
270
+ progress_string = f''
271
+
272
+ # checking if iterations total has already been obtained
273
+ if not self.totals_updated.is_set():
274
+
275
+ # updating progress string based on attributes
276
+ progress_string += f'calculating iterations...'
277
+ progress_string += f' | total iterations: {self.iterations_num}'
278
+ progress_string += f' | elapsed time: {self.elapsed_time_str}'
279
+
280
+ # if total iterations already obtained
281
+ else:
282
+
283
+ # updating progress string based on attributes
284
+ progress_string += f'running analysis...'
285
+ progress_string += f' {self.wheel_symbol}'
286
+ progress_string += f' | iteration: {self.current_iteration}/{self.iterations_num}'
287
+ progress_string += f' | progress: {self.progress_percentage}'
288
+ progress_string += f' | elapsed time: {self.elapsed_time_str}'
289
+ progress_string += f' | ETC: {self.etc_str}'
290
+
291
+ # returning progress string
292
+ return progress_string
293
+
294
+ def update_progress_string(self) -> None:
295
+ """
296
+ Updates progress string related attributes.
297
+ """
298
+ # updating progress percentage attributes
299
+ self.progress_percentage = self.get_progress_percentage()
300
+ self.progress_percentage_str = self.get_percentage_string(percentage=self.progress_percentage)
301
+ self.progress_string = self.get_progress_string()
302
+
303
+ def flush_progress(self) -> None:
304
+ """
305
+ Gets updated progress string and
306
+ flushes it on the console.
307
+ """
308
+ # updating wheel symbol attributes
309
+ self.update_wheel_symbol()
310
+
311
+ # updating progress string
312
+ self.update_progress_string()
313
+
314
+ # showing progress message
315
+ flush_string(string=self.progress_string)
316
+
317
+ def clear_progress(self) -> None:
318
+ """
319
+ Given a string, writes empty space
320
+ to cover string size in console.
321
+ """
322
+ # getting current progress string
323
+ string = self.progress_string
324
+
325
+ # getting string length
326
+ string_len = len(string)
327
+
328
+ # creating empty line
329
+ empty_line = ' ' * string_len
330
+
331
+ # creating backspace line
332
+ backspace_line = '\b' * string_len
333
+
334
+ # writing string
335
+ stdout.write(empty_line)
336
+
337
+ # flushing console
338
+ stdout.flush()
339
+
340
+ # resetting cursor to start of the line
341
+ stdout.write(backspace_line)
342
+
343
+ def signal_stop(self) -> None:
344
+ """
345
+ Sets threading.Event as set,
346
+ signaling progress tracker
347
+ to stop.
348
+ """
349
+ # signaling the progress tracker to stop
350
+ self.process_complete.set()
351
+
352
+ # waiting some time to ensure signal is perceived
353
+ self.wait(seconds=0.8)
354
+
355
+ @staticmethod
356
+ def force_quit() -> None:
357
+ """
358
+ Uses os _exit to force
359
+ quit all running threads.
360
+ """
361
+ # using os exit to exit program
362
+ _exit(1)
363
+
364
+ def exit(self,
365
+ message: str = 'DebugExit'
366
+ ) -> None:
367
+ """
368
+ Prints message and
369
+ kills all threads.
370
+ """
371
+ # printing spacer
372
+ print()
373
+
374
+ # printing debug message
375
+ print(message)
376
+
377
+ # quitting code
378
+ self.force_quit()
379
+
380
+ def update_progress(self) -> None:
381
+ """
382
+ Runs progress tracking loop, updating
383
+ progress attributes and printing
384
+ progress message on each iteration.
385
+ """
386
+ # checking stop condition and running loop until stop event is set
387
+ while not self.process_complete.is_set():
388
+
389
+ # checking lock to avoid race conditions
390
+ with self.lock:
391
+
392
+ # updating time attributes
393
+ self.update_time_attributes()
394
+
395
+ # printing progress
396
+ self.flush_progress()
397
+
398
+ # sleeping for a short period of time to avoid too many prints
399
+ self.wait(seconds=UPDATE_TIME)
400
+
401
+ def start_thread(self) -> None:
402
+ """
403
+ Starts progress thread.
404
+ """
405
+ # starting progress tracker in a separate thread
406
+ self.progress_thread.start()
407
+
408
+ def stop_thread(self) -> None:
409
+ """
410
+ Stops progress bar monitoring
411
+ and finished execution thread.
412
+ """
413
+ # joining threads to ensure progress thread finished cleanly
414
+ self.progress_thread.join()
415
+
416
+ def normal_exit(self) -> None:
417
+ """
418
+ Prints process complete message
419
+ before terminating execution.
420
+ """
421
+ # defining final message
422
+ f_string = f'\n'
423
+ f_string += f'{self.end_string}'
424
+
425
+ # printing final message
426
+ print(f_string)
427
+
428
+ def keyboard_interrupt_exit(self) -> None:
429
+ """
430
+ Prints exception message
431
+ before terminating execution.
432
+ """
433
+ # defining error message
434
+ e_string = f'\n'
435
+ e_string += f'Keyboard Interrupt!'
436
+
437
+ # quitting with error message
438
+ self.exit(message=e_string)
439
+
440
+ def exception_exit(self,
441
+ exception: Exception
442
+ ) -> None:
443
+ """
444
+ Prints exception message
445
+ before terminating execution.
446
+ """
447
+ # defining error message
448
+ e_string = f'\n'
449
+ e_string += f'Code break!\n'
450
+ e_string += f'Error:\n'
451
+ e_string += f'{exception}'
452
+
453
+ # quitting with error message
454
+ self.exit(message=e_string)
455
+
456
+ def run(self,
457
+ function: callable,
458
+ args_parser: callable
459
+ ) -> None:
460
+ """
461
+ Runs given function monitoring
462
+ progress in a separate thread.
463
+ """
464
+ # getting args dict
465
+ args_dict = args_parser()
466
+
467
+ # starting progress thread
468
+ self.start_thread()
469
+
470
+ # running function in try/except block to catch breaks/errors!
471
+ try:
472
+
473
+ # updating iterations total
474
+ self.update_totals(args_dict=args_dict)
475
+
476
+ # resetting timer
477
+ self.reset_timer()
478
+
479
+ # running function with given args dict and progress tracker
480
+ function(args_dict,
481
+ self)
482
+
483
+ # signaling stop
484
+ self.signal_stop()
485
+
486
+ # printing final progress string
487
+ self.flush_progress()
488
+
489
+ # printing final message
490
+ self.normal_exit()
491
+
492
+ # catching Ctrl+C events
493
+ except KeyboardInterrupt:
494
+
495
+ # signaling stop
496
+ self.signal_stop()
497
+
498
+ # quitting
499
+ self.keyboard_interrupt_exit()
500
+
501
+ # catching every other exception
502
+ except Exception as exception:
503
+
504
+ # signaling stop
505
+ self.signal_stop()
506
+
507
+ # printing error message
508
+ self.exception_exit(exception=exception)
509
+
510
+ # terminating thread
511
+ self.stop_thread()
512
+
513
+ ######################################################################
514
+ # end of current module