Display a progress animation while performing a time consuming task. Example: >>> with ShowProgress(logger, 0.15): ... do_some_time_consuming_task()
| 1034 | |
| 1035 | |
| 1036 | class ShowProgress: |
| 1037 | """Display a progress animation while performing a time consuming task. |
| 1038 | |
| 1039 | Example: |
| 1040 | >>> with ShowProgress(logger, 0.15): |
| 1041 | ... do_some_time_consuming_task() |
| 1042 | """ |
| 1043 | |
| 1044 | # animation frames: a sequence of chars or strings to display. any sequence of string elements |
| 1045 | # may be used as long as they are of the same length. |
| 1046 | # |
| 1047 | # for example: ['> ', '>> ', ' >> ', ' >>', ' >', ' '] |
| 1048 | _frames_ = r'/-\|' |
| 1049 | |
| 1050 | # animation marker: this is used to tell animation log records from the rest. |
| 1051 | _marker_ = r'$__ql_anim__' |
| 1052 | |
| 1053 | def __init__(self, logger: Logger, interval: float) -> None: |
| 1054 | from typing import List, Callable |
| 1055 | from threading import Thread, Event |
| 1056 | |
| 1057 | def show_animation(): |
| 1058 | i = 0 |
| 1059 | |
| 1060 | while not self.stopped.wait(interval): |
| 1061 | frame = self._frames_[i % len(self._frames_)] |
| 1062 | logger.info(f'{self._marker_}{frame}') |
| 1063 | |
| 1064 | i += 1 |
| 1065 | |
| 1066 | self.stopped = Event() |
| 1067 | self.thread = Thread(target=show_animation) |
| 1068 | |
| 1069 | self.logger = logger |
| 1070 | self.handlers_restorers: List[Callable[[], None]] = [] |
| 1071 | |
| 1072 | def __setup_handlers(self): |
| 1073 | from logging import Filter, Formatter, LogRecord, StreamHandler |
| 1074 | |
| 1075 | # while progress animation is useful on tty streams, it is not very useful on log files |
| 1076 | # and most probably just flood the log files with animation frames. |
| 1077 | # |
| 1078 | # to avoid such flooding an animation filter is added to the non-tty stream handlers to |
| 1079 | # filter out the animation records. in addition, tty stream handlers are assigned with |
| 1080 | # an animation formatter to display the animation frames nicely. |
| 1081 | # |
| 1082 | # when the animation context exits, all the changes made to the handlers are reverted. |
| 1083 | |
| 1084 | def has_anim_marker(rec: LogRecord) -> bool: |
| 1085 | """Tell whether a log record is an animation record or not. |
| 1086 | """ |
| 1087 | |
| 1088 | return rec.getMessage().startswith(ShowProgress._marker_) |
| 1089 | |
| 1090 | def strip_anim_marker(rec: LogRecord) -> None: |
| 1091 | """Remove animation marker from log record. |
| 1092 | """ |
| 1093 |