Coverage for /Volumes/workspace/python-progressbar/.tox/py38/lib/python3.8/site-packages/progressbar/bar.py: 99%
366 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-01 16:14 +0100
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-01 16:14 +0100
1from __future__ import annotations
3import abc
4import logging
5import os
6import sys
7import time
8import timeit
9import warnings
10from copy import deepcopy
11from datetime import datetime
12from typing import Type
14import math
15from python_utils import converters, types
17from . import (
18 base,
19 utils,
20 widgets,
21 widgets as widgets_module, # Avoid name collision
22)
24logger = logging.getLogger(__name__)
26# float also accepts integers and longs but we don't want an explicit union
27# due to type checking complexity
28T = float
31class ProgressBarMixinBase(abc.ABC):
32 _started = False
33 _finished = False
34 _last_update_time: types.Optional[float] = None
36 #: The terminal width. This should be automatically detected but will
37 #: fall back to 80 if auto detection is not possible.
38 term_width: int = 80
39 #: The widgets to render, defaults to the result of `default_widget()`
40 widgets: types.List[widgets_module.WidgetBase]
41 #: When going beyond the max_value, raise an error if True or silently
42 #: ignore otherwise
43 max_error: bool
44 #: Prefix the progressbar with the given string
45 prefix: types.Optional[str]
46 #: Suffix the progressbar with the given string
47 suffix: types.Optional[str]
48 #: Justify to the left if `True` or the right if `False`
49 left_justify: bool
50 #: The default keyword arguments for the `default_widgets` if no widgets
51 #: are configured
52 widget_kwargs: types.Dict[str, types.Any]
53 #: Custom length function for multibyte characters such as CJK
54 # mypy and pyright can't agree on what the correct one is... so we'll
55 # need to use a helper function :(
56 # custom_len: types.Callable[['ProgressBarMixinBase', str], int]
57 custom_len: types.Callable[[str], int]
58 #: The time the progress bar was started
59 initial_start_time: types.Optional[datetime]
60 #: The interval to poll for updates in seconds if there are updates
61 poll_interval: types.Optional[float]
62 #: The minimum interval to poll for updates in seconds even if there are
63 #: no updates
64 min_poll_interval: float
66 #: Current progress (min_value <= value <= max_value)
67 value: T
68 #: The minimum/start value for the progress bar
69 min_value: T
70 #: Maximum (and final) value. Beyond this value an error will be raised
71 #: unless the `max_error` parameter is `False`.
72 max_value: T | types.Type[base.UnknownLength]
73 #: The time the progressbar reached `max_value` or when `finish()` was
74 #: called.
75 end_time: types.Optional[datetime]
76 #: The time `start()` was called or iteration started.
77 start_time: types.Optional[datetime]
78 #: Seconds between `start_time` and last call to `update()`
79 seconds_elapsed: float
81 #: Extra data for widgets with persistent state. This is used by
82 #: sampling widgets for example. Since widgets can be shared between
83 #: multiple progressbars we need to store the state with the progressbar.
84 extra: types.Dict[str, types.Any]
86 def get_last_update_time(self) -> types.Optional[datetime]:
87 if self._last_update_time:
88 return datetime.fromtimestamp(self._last_update_time)
89 else:
90 return None
92 def set_last_update_time(self, value: types.Optional[datetime]):
93 if value:
94 self._last_update_time = time.mktime(value.timetuple())
95 else:
96 self._last_update_time = None
98 last_update_time = property(get_last_update_time, set_last_update_time)
100 def __init__(self, **kwargs):
101 pass
103 def start(self, **kwargs):
104 self._started = True
106 def update(self, value=None):
107 pass
109 def finish(self): # pragma: no cover
110 self._finished = True
112 def __del__(self):
113 if not self._finished and self._started: # pragma: no cover
114 try:
115 self.finish()
116 except Exception:
117 # Never raise during cleanup. We're too late now
118 logging.debug(
119 'Exception raised during ProgressBar cleanup',
120 exc_info=True,
121 )
123 def __getstate__(self):
124 return self.__dict__
126 def data(self) -> types.Dict[str, types.Any]:
127 raise NotImplementedError()
130class ProgressBarBase(types.Iterable, ProgressBarMixinBase):
131 pass
134class DefaultFdMixin(ProgressBarMixinBase):
135 # The file descriptor to write to. Defaults to `sys.stderr`
136 fd: base.IO = sys.stderr
137 #: Set the terminal to be ANSI compatible. If a terminal is ANSI
138 #: compatible we will automatically enable `colors` and disable
139 #: `line_breaks`.
140 is_ansi_terminal: bool = False
141 #: Whether to print line breaks. This is useful for logging the
142 #: progressbar. When disabled the current line is overwritten.
143 line_breaks: bool = True
144 #: Enable or disable colors. Defaults to auto detection
145 enable_colors: bool = False
147 def __init__(
148 self,
149 fd: base.IO = sys.stderr,
150 is_terminal: bool | None = None,
151 line_breaks: bool | None = None,
152 enable_colors: bool | None = None,
153 **kwargs,
154 ):
155 if fd is sys.stdout:
156 fd = utils.streams.original_stdout
158 elif fd is sys.stderr:
159 fd = utils.streams.original_stderr
161 self.fd = fd
162 self.is_ansi_terminal = utils.is_ansi_terminal(fd)
164 # Check if this is an interactive terminal
165 self.is_terminal = utils.is_terminal(
166 fd, is_terminal or self.is_ansi_terminal
167 )
169 # Check if it should overwrite the current line (suitable for
170 # iteractive terminals) or write line breaks (suitable for log files)
171 if line_breaks is None:
172 line_breaks = utils.env_flag(
173 'PROGRESSBAR_LINE_BREAKS', not self.is_terminal
174 )
175 self.line_breaks = bool(line_breaks)
177 # Check if ANSI escape characters are enabled (suitable for iteractive
178 # terminals), or should be stripped off (suitable for log files)
179 if enable_colors is None:
180 enable_colors = utils.env_flag(
181 'PROGRESSBAR_ENABLE_COLORS', self.is_ansi_terminal
182 )
184 self.enable_colors = bool(enable_colors)
186 ProgressBarMixinBase.__init__(self, **kwargs)
188 def update(self, *args, **kwargs):
189 ProgressBarMixinBase.update(self, *args, **kwargs)
191 line: str = converters.to_unicode(self._format_line())
192 if not self.enable_colors:
193 line = utils.no_color(line)
195 if self.line_breaks:
196 line = line.rstrip() + '\n'
197 else:
198 line = '\r' + line
200 try: # pragma: no cover
201 self.fd.write(line)
202 except UnicodeEncodeError: # pragma: no cover
203 self.fd.write(line.encode('ascii', 'replace'))
205 def finish(self, *args, **kwargs): # pragma: no cover
206 if self._finished:
207 return
209 end = kwargs.pop('end', '\n')
210 ProgressBarMixinBase.finish(self, *args, **kwargs)
212 if end and not self.line_breaks:
213 self.fd.write(end)
215 self.fd.flush()
217 def _format_line(self):
218 'Joins the widgets and justifies the line'
220 widgets = ''.join(self._to_unicode(self._format_widgets()))
222 if self.left_justify:
223 return widgets.ljust(self.term_width)
224 else:
225 return widgets.rjust(self.term_width)
227 def _format_widgets(self):
228 result = []
229 expanding = []
230 width = self.term_width
231 data = self.data()
233 for index, widget in enumerate(self.widgets):
234 if isinstance(
235 widget, widgets.WidgetBase
236 ) and not widget.check_size(self):
237 continue
238 elif isinstance(widget, widgets.AutoWidthWidgetBase):
239 result.append(widget)
240 expanding.insert(0, index)
241 elif isinstance(widget, str):
242 result.append(widget)
243 width -= self.custom_len(widget) # type: ignore
244 else:
245 widget_output = converters.to_unicode(widget(self, data))
246 result.append(widget_output)
247 width -= self.custom_len(widget_output) # type: ignore
249 count = len(expanding)
250 while expanding:
251 portion = max(int(math.ceil(width * 1.0 / count)), 0)
252 index = expanding.pop()
253 widget = result[index]
254 count -= 1
256 widget_output = widget(self, data, portion)
257 width -= self.custom_len(widget_output) # type: ignore
258 result[index] = widget_output
260 return result
262 @classmethod
263 def _to_unicode(cls, args):
264 for arg in args:
265 yield converters.to_unicode(arg)
268class ResizableMixin(ProgressBarMixinBase):
269 def __init__(self, term_width: int | None = None, **kwargs):
270 ProgressBarMixinBase.__init__(self, **kwargs)
272 self.signal_set = False
273 if term_width:
274 self.term_width = term_width
275 else: # pragma: no cover
276 try:
277 self._handle_resize()
278 import signal
280 self._prev_handle = signal.getsignal(signal.SIGWINCH)
281 signal.signal(signal.SIGWINCH, self._handle_resize)
282 self.signal_set = True
283 except Exception:
284 pass
286 def _handle_resize(self, signum=None, frame=None):
287 'Tries to catch resize signals sent from the terminal.'
289 w, h = utils.get_terminal_size()
290 self.term_width = w
292 def finish(self): # pragma: no cover
293 ProgressBarMixinBase.finish(self)
294 if self.signal_set:
295 try:
296 import signal
298 signal.signal(signal.SIGWINCH, self._prev_handle)
299 except Exception: # pragma no cover
300 pass
303class StdRedirectMixin(DefaultFdMixin):
304 redirect_stderr: bool = False
305 redirect_stdout: bool = False
306 stdout: base.IO
307 stderr: base.IO
308 _stdout: base.IO
309 _stderr: base.IO
311 def __init__(
312 self,
313 redirect_stderr: bool = False,
314 redirect_stdout: bool = False,
315 **kwargs,
316 ):
317 DefaultFdMixin.__init__(self, **kwargs)
318 self.redirect_stderr = redirect_stderr
319 self.redirect_stdout = redirect_stdout
320 self._stdout = self.stdout = sys.stdout
321 self._stderr = self.stderr = sys.stderr
323 def start(self, *args, **kwargs):
324 if self.redirect_stdout:
325 utils.streams.wrap_stdout()
327 if self.redirect_stderr:
328 utils.streams.wrap_stderr()
330 self._stdout = utils.streams.original_stdout
331 self._stderr = utils.streams.original_stderr
333 self.stdout = utils.streams.stdout
334 self.stderr = utils.streams.stderr
336 utils.streams.start_capturing(self)
337 DefaultFdMixin.start(self, *args, **kwargs)
339 def update(self, value: types.Optional[float] = None):
340 if not self.line_breaks and utils.streams.needs_clear():
341 self.fd.write('\r' + ' ' * self.term_width + '\r')
343 utils.streams.flush()
344 DefaultFdMixin.update(self, value=value)
346 def finish(self, end='\n'):
347 DefaultFdMixin.finish(self, end=end)
348 utils.streams.stop_capturing(self)
349 if self.redirect_stdout:
350 utils.streams.unwrap_stdout()
352 if self.redirect_stderr:
353 utils.streams.unwrap_stderr()
356class ProgressBar(
357 StdRedirectMixin,
358 ResizableMixin,
359 ProgressBarBase,
360):
361 '''The ProgressBar class which updates and prints the bar.
363 Args:
364 min_value (int): The minimum/start value for the progress bar
365 max_value (int): The maximum/end value for the progress bar.
366 Defaults to `_DEFAULT_MAXVAL`
367 widgets (list): The widgets to render, defaults to the result of
368 `default_widget()`
369 left_justify (bool): Justify to the left if `True` or the right if
370 `False`
371 initial_value (int): The value to start with
372 poll_interval (float): The update interval in seconds.
373 Note that if your widgets include timers or animations, the actual
374 interval may be smaller (faster updates). Also note that updates
375 never happens faster than `min_poll_interval` which can be used for
376 reduced output in logs
377 min_poll_interval (float): The minimum update interval in seconds.
378 The bar will _not_ be updated faster than this, despite changes in
379 the progress, unless `force=True`. This is limited to be at least
380 `_MINIMUM_UPDATE_INTERVAL`. If available, it is also bound by the
381 environment variable PROGRESSBAR_MINIMUM_UPDATE_INTERVAL
382 widget_kwargs (dict): The default keyword arguments for widgets
383 custom_len (function): Method to override how the line width is
384 calculated. When using non-latin characters the width
385 calculation might be off by default
386 max_error (bool): When True the progressbar will raise an error if it
387 goes beyond it's set max_value. Otherwise the max_value is simply
388 raised when needed
389 prefix (str): Prefix the progressbar with the given string
390 suffix (str): Prefix the progressbar with the given string
391 variables (dict): User-defined variables variables that can be used
392 from a label using `format='{variables.my_var}'`. These values can
393 be updated using `bar.update(my_var='newValue')` This can also be
394 used to set initial values for variables' widgets
396 A common way of using it is like:
398 >>> progress = ProgressBar().start()
399 >>> for i in range(100):
400 ... progress.update(i + 1)
401 ... # do something
402 ...
403 >>> progress.finish()
405 You can also use a ProgressBar as an iterator:
407 >>> progress = ProgressBar()
408 >>> some_iterable = range(100)
409 >>> for i in progress(some_iterable):
410 ... # do something
411 ... pass
412 ...
414 Since the progress bar is incredibly customizable you can specify
415 different widgets of any type in any order. You can even write your own
416 widgets! However, since there are already a good number of widgets you
417 should probably play around with them before moving on to create your own
418 widgets.
420 The term_width parameter represents the current terminal width. If the
421 parameter is set to an integer then the progress bar will use that,
422 otherwise it will attempt to determine the terminal width falling back to
423 80 columns if the width cannot be determined.
425 When implementing a widget's update method you are passed a reference to
426 the current progress bar. As a result, you have access to the
427 ProgressBar's methods and attributes. Although there is nothing preventing
428 you from changing the ProgressBar you should treat it as read only.
429 '''
431 _iterable: types.Optional[types.Iterator]
433 _DEFAULT_MAXVAL: Type[base.UnknownLength] = base.UnknownLength
434 # update every 50 milliseconds (up to a 20 times per second)
435 _MINIMUM_UPDATE_INTERVAL: float = 0.050
436 _last_update_time: types.Optional[float] = None
438 def __init__(
439 self,
440 min_value: T = 0,
441 max_value: T | types.Type[base.UnknownLength] | None = None,
442 widgets: types.Optional[types.List[widgets_module.WidgetBase]] = None,
443 left_justify: bool = True,
444 initial_value: T = 0,
445 poll_interval: types.Optional[float] = None,
446 widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None,
447 custom_len: types.Callable[[str], int] = utils.len_color,
448 max_error=True,
449 prefix=None,
450 suffix=None,
451 variables=None,
452 min_poll_interval=None,
453 **kwargs,
454 ):
455 '''
456 Initializes a progress bar with sane defaults
457 '''
458 StdRedirectMixin.__init__(self, **kwargs)
459 ResizableMixin.__init__(self, **kwargs)
460 ProgressBarBase.__init__(self, **kwargs)
461 if not max_value and kwargs.get('maxval') is not None:
462 warnings.warn(
463 'The usage of `maxval` is deprecated, please use '
464 '`max_value` instead',
465 DeprecationWarning,
466 )
467 max_value = kwargs.get('maxval')
469 if not poll_interval and kwargs.get('poll'):
470 warnings.warn(
471 'The usage of `poll` is deprecated, please use '
472 '`poll_interval` instead',
473 DeprecationWarning,
474 )
475 poll_interval = kwargs.get('poll')
477 if max_value:
478 # mypy doesn't understand that a boolean check excludes
479 # `UnknownLength`
480 if min_value > max_value: # type: ignore
481 raise ValueError(
482 'Max value needs to be bigger than the min ' 'value'
483 )
484 self.min_value = min_value
485 # Legacy issue, `max_value` can be `None` before execution. After
486 # that it either has a value or is `UnknownLength`
487 self.max_value = max_value # type: ignore
488 self.max_error = max_error
490 # Only copy the widget if it's safe to copy. Most widgets are so we
491 # assume this to be true
492 self.widgets = []
493 for widget in widgets or []:
494 if getattr(widget, 'copy', True):
495 widget = deepcopy(widget)
496 self.widgets.append(widget)
498 self.prefix = prefix
499 self.suffix = suffix
500 self.widget_kwargs = widget_kwargs or {}
501 self.left_justify = left_justify
502 self.value = initial_value
503 self._iterable = None
504 self.custom_len = custom_len # type: ignore
505 self.initial_start_time = kwargs.get('start_time')
506 self.init()
508 # Convert a given timedelta to a floating point number as internal
509 # interval. We're not using timedelta's internally for two reasons:
510 # 1. Backwards compatibility (most important one)
511 # 2. Performance. Even though the amount of time it takes to compare a
512 # timedelta with a float versus a float directly is negligible, this
513 # comparison is run for _every_ update. With billions of updates
514 # (downloading a 1GiB file for example) this adds up.
515 poll_interval = utils.deltas_to_seconds(poll_interval, default=None)
516 min_poll_interval = utils.deltas_to_seconds(
517 min_poll_interval, default=None
518 )
519 self._MINIMUM_UPDATE_INTERVAL = (
520 utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL)
521 or self._MINIMUM_UPDATE_INTERVAL
522 )
524 # Note that the _MINIMUM_UPDATE_INTERVAL sets the minimum in case of
525 # low values.
526 self.poll_interval = poll_interval
527 self.min_poll_interval = max(
528 min_poll_interval or self._MINIMUM_UPDATE_INTERVAL,
529 self._MINIMUM_UPDATE_INTERVAL,
530 float(os.environ.get('PROGRESSBAR_MINIMUM_UPDATE_INTERVAL', 0)),
531 ) # type: ignore
533 # A dictionary of names that can be used by Variable and FormatWidget
534 self.variables = utils.AttributeDict(variables or {})
535 for widget in self.widgets:
536 if isinstance(widget, widgets_module.VariableMixin):
537 if widget.name not in self.variables:
538 self.variables[widget.name] = None
540 @property
541 def dynamic_messages(self): # pragma: no cover
542 return self.variables
544 @dynamic_messages.setter
545 def dynamic_messages(self, value): # pragma: no cover
546 self.variables = value
548 def init(self):
549 '''
550 (re)initialize values to original state so the progressbar can be
551 used (again)
552 '''
553 self.previous_value = None
554 self.last_update_time = None
555 self.start_time = None
556 self.updates = 0
557 self.end_time = None
558 self.extra = dict()
559 self._last_update_timer = timeit.default_timer()
561 @property
562 def percentage(self):
563 '''Return current percentage, returns None if no max_value is given
565 >>> progress = ProgressBar()
566 >>> progress.max_value = 10
567 >>> progress.min_value = 0
568 >>> progress.value = 0
569 >>> progress.percentage
570 0.0
571 >>>
572 >>> progress.value = 1
573 >>> progress.percentage
574 10.0
575 >>> progress.value = 10
576 >>> progress.percentage
577 100.0
578 >>> progress.min_value = -10
579 >>> progress.percentage
580 100.0
581 >>> progress.value = 0
582 >>> progress.percentage
583 50.0
584 >>> progress.value = 5
585 >>> progress.percentage
586 75.0
587 >>> progress.value = -5
588 >>> progress.percentage
589 25.0
590 >>> progress.max_value = None
591 >>> progress.percentage
592 '''
593 if self.max_value is None or self.max_value is base.UnknownLength:
594 return None
595 elif self.max_value:
596 todo = self.value - self.min_value
597 total = self.max_value - self.min_value # type: ignore
598 percentage = 100.0 * todo / total
599 else:
600 percentage = 100.0
602 return percentage
604 def data(self) -> types.Dict[str, types.Any]:
605 '''
607 Returns:
608 dict:
609 - `max_value`: The maximum value (can be None with
610 iterators)
611 - `start_time`: Start time of the widget
612 - `last_update_time`: Last update time of the widget
613 - `end_time`: End time of the widget
614 - `value`: The current value
615 - `previous_value`: The previous value
616 - `updates`: The total update count
617 - `total_seconds_elapsed`: The seconds since the bar started
618 - `seconds_elapsed`: The seconds since the bar started modulo
619 60
620 - `minutes_elapsed`: The minutes since the bar started modulo
621 60
622 - `hours_elapsed`: The hours since the bar started modulo 24
623 - `days_elapsed`: The hours since the bar started
624 - `time_elapsed`: The raw elapsed `datetime.timedelta` object
625 - `percentage`: Percentage as a float or `None` if no max_value
626 is available
627 - `dynamic_messages`: Deprecated, use `variables` instead.
628 - `variables`: Dictionary of user-defined variables for the
629 :py:class:`~progressbar.widgets.Variable`'s
631 '''
632 self._last_update_time = time.time()
633 self._last_update_timer = timeit.default_timer()
634 elapsed = self.last_update_time - self.start_time # type: ignore
635 # For Python 2.7 and higher we have _`timedelta.total_seconds`, but we
636 # want to support older versions as well
637 total_seconds_elapsed = utils.deltas_to_seconds(elapsed)
638 return dict(
639 # The maximum value (can be None with iterators)
640 max_value=self.max_value,
641 # Start time of the widget
642 start_time=self.start_time,
643 # Last update time of the widget
644 last_update_time=self.last_update_time,
645 # End time of the widget
646 end_time=self.end_time,
647 # The current value
648 value=self.value,
649 # The previous value
650 previous_value=self.previous_value,
651 # The total update count
652 updates=self.updates,
653 # The seconds since the bar started
654 total_seconds_elapsed=total_seconds_elapsed,
655 # The seconds since the bar started modulo 60
656 seconds_elapsed=(elapsed.seconds % 60)
657 + (elapsed.microseconds / 1000000.0),
658 # The minutes since the bar started modulo 60
659 minutes_elapsed=(elapsed.seconds / 60) % 60,
660 # The hours since the bar started modulo 24
661 hours_elapsed=(elapsed.seconds / (60 * 60)) % 24,
662 # The hours since the bar started
663 days_elapsed=(elapsed.seconds / (60 * 60 * 24)),
664 # The raw elapsed `datetime.timedelta` object
665 time_elapsed=elapsed,
666 # Percentage as a float or `None` if no max_value is available
667 percentage=self.percentage,
668 # Dictionary of user-defined
669 # :py:class:`progressbar.widgets.Variable`'s
670 variables=self.variables,
671 # Deprecated alias for `variables`
672 dynamic_messages=self.variables,
673 )
675 def default_widgets(self):
676 if self.max_value:
677 return [
678 widgets.Percentage(**self.widget_kwargs),
679 ' ',
680 widgets.SimpleProgress(
681 format='(%s)' % widgets.SimpleProgress.DEFAULT_FORMAT,
682 **self.widget_kwargs,
683 ),
684 ' ',
685 widgets.Bar(**self.widget_kwargs),
686 ' ',
687 widgets.Timer(**self.widget_kwargs),
688 ' ',
689 widgets.AdaptiveETA(**self.widget_kwargs),
690 ]
691 else:
692 return [
693 widgets.AnimatedMarker(**self.widget_kwargs),
694 ' ',
695 widgets.BouncingBar(**self.widget_kwargs),
696 ' ',
697 widgets.Counter(**self.widget_kwargs),
698 ' ',
699 widgets.Timer(**self.widget_kwargs),
700 ]
702 def __call__(self, iterable, max_value=None):
703 'Use a ProgressBar to iterate through an iterable'
704 if max_value is not None:
705 self.max_value = max_value
706 elif self.max_value is None:
707 try:
708 self.max_value = len(iterable)
709 except TypeError: # pragma: no cover
710 self.max_value = base.UnknownLength
712 self._iterable = iter(iterable)
713 return self
715 def __iter__(self):
716 return self
718 def __next__(self):
719 try:
720 if self._iterable is None: # pragma: no cover
721 value = self.value
722 else:
723 value = next(self._iterable)
725 if self.start_time is None:
726 self.start()
727 else:
728 self.update(self.value + 1)
730 return value
731 except StopIteration:
732 self.finish()
733 raise
734 except GeneratorExit: # pragma: no cover
735 self.finish(dirty=True)
736 raise
738 def __exit__(self, exc_type, exc_value, traceback):
739 self.finish(dirty=bool(exc_type))
741 def __enter__(self):
742 return self
744 # Create an alias so that Python 2.x won't complain about not being
745 # an iterator.
746 next = __next__
748 def __iadd__(self, value):
749 'Updates the ProgressBar by adding a new value.'
750 return self.increment(value)
752 def increment(self, value=1, *args, **kwargs):
753 self.update(self.value + value, *args, **kwargs)
754 return self
756 def _needs_update(self):
757 'Returns whether the ProgressBar should redraw the line.'
758 delta = timeit.default_timer() - self._last_update_timer
759 if delta < self.min_poll_interval:
760 # Prevent updating too often
761 return False
762 elif self.poll_interval and delta > self.poll_interval:
763 # Needs to redraw timers and animations
764 return True
766 # Update if value increment is not large enough to
767 # add more bars to progressbar (according to current
768 # terminal width)
769 try:
770 divisor: float = self.max_value / self.term_width # type: ignore
771 value_divisor = self.value // divisor # type: ignore
772 pvalue_divisor = self.previous_value // divisor # type: ignore
773 if value_divisor != pvalue_divisor:
774 return True
775 except Exception:
776 # ignore any division errors
777 pass
779 # No need to redraw yet
780 return False
782 def update(self, value=None, force=False, **kwargs):
783 'Updates the ProgressBar to a new value.'
784 if self.start_time is None:
785 self.start()
786 return self.update(value, force=force, **kwargs)
788 if value is not None and value is not base.UnknownLength:
789 if self.max_value is base.UnknownLength:
790 # Can't compare against unknown lengths so just update
791 pass
792 elif self.min_value <= value <= self.max_value: # pragma: no cover
793 # Correct value, let's accept
794 pass
795 elif self.max_error:
796 raise ValueError(
797 'Value %s is out of range, should be between %s and %s'
798 % (value, self.min_value, self.max_value)
799 )
800 else:
801 self.max_value = value
803 self.previous_value = self.value
804 self.value = value
806 # Save the updated values for dynamic messages
807 variables_changed = False
808 for key in kwargs:
809 if key not in self.variables:
810 raise TypeError(
811 'update() got an unexpected keyword '
812 + 'argument {0!r}'.format(key)
813 )
814 elif self.variables[key] != kwargs[key]:
815 self.variables[key] = kwargs[key]
816 variables_changed = True
818 if self._needs_update() or variables_changed or force:
819 self.updates += 1
820 ResizableMixin.update(self, value=value)
821 ProgressBarBase.update(self, value=value)
822 StdRedirectMixin.update(self, value=value)
824 # Only flush if something was actually written
825 self.fd.flush()
827 def start(self, max_value=None, init=True):
828 '''Starts measuring time, and prints the bar at 0%.
830 It returns self so you can use it like this:
832 Args:
833 max_value (int): The maximum value of the progressbar
834 reinit (bool): Initialize the progressbar, this is useful if you
835 wish to reuse the same progressbar but can be disabled if
836 data needs to be passed along to the next run
838 >>> pbar = ProgressBar().start()
839 >>> for i in range(100):
840 ... # do something
841 ... pbar.update(i+1)
842 ...
843 >>> pbar.finish()
844 '''
845 if init:
846 self.init()
848 # Prevent multiple starts
849 if self.start_time is not None: # pragma: no cover
850 return self
852 if max_value is not None:
853 self.max_value = max_value
855 if self.max_value is None:
856 self.max_value = self._DEFAULT_MAXVAL
858 StdRedirectMixin.start(self, max_value=max_value)
859 ResizableMixin.start(self, max_value=max_value)
860 ProgressBarBase.start(self, max_value=max_value)
862 # Constructing the default widgets is only done when we know max_value
863 if not self.widgets:
864 self.widgets = self.default_widgets()
866 if self.prefix:
867 self.widgets.insert(
868 0, widgets.FormatLabel(self.prefix, new_style=True)
869 )
870 # Unset the prefix variable after applying so an extra start()
871 # won't keep copying it
872 self.prefix = None
874 if self.suffix:
875 self.widgets.append(
876 widgets.FormatLabel(self.suffix, new_style=True)
877 )
878 # Unset the suffix variable after applying so an extra start()
879 # won't keep copying it
880 self.suffix = None
882 for widget in self.widgets:
883 interval: int | float | None = utils.deltas_to_seconds(
884 getattr(widget, 'INTERVAL', None),
885 default=None,
886 )
887 if interval is not None:
888 self.poll_interval = min(
889 self.poll_interval or interval,
890 interval,
891 )
893 self.num_intervals = max(100, self.term_width)
894 # The `next_update` is kept for compatibility with external libs:
895 # https://github.com/WoLpH/python-progressbar/issues/207
896 self.next_update = 0
898 if (
899 self.max_value is not base.UnknownLength
900 and self.max_value is not None
901 and self.max_value < 0 # type: ignore
902 ):
903 raise ValueError('max_value out of range, got %r' % self.max_value)
905 now = datetime.now()
906 self.start_time = self.initial_start_time or now
907 self.last_update_time = now
908 self._last_update_timer = timeit.default_timer()
909 self.update(self.min_value, force=True)
911 return self
913 def finish(self, end='\n', dirty=False):
914 '''
915 Puts the ProgressBar bar in the finished state.
917 Also flushes and disables output buffering if this was the last
918 progressbar running.
920 Args:
921 end (str): The string to end the progressbar with, defaults to a
922 newline
923 dirty (bool): When True the progressbar kept the current state and
924 won't be set to 100 percent
925 '''
927 if not dirty:
928 self.end_time = datetime.now()
929 self.update(self.max_value, force=True)
931 StdRedirectMixin.finish(self, end=end)
932 ResizableMixin.finish(self)
933 ProgressBarBase.finish(self)
935 @property
936 def currval(self):
937 '''
938 Legacy method to make progressbar-2 compatible with the original
939 progressbar package
940 '''
941 warnings.warn(
942 'The usage of `currval` is deprecated, please use '
943 '`value` instead',
944 DeprecationWarning,
945 )
946 return self.value
949class DataTransferBar(ProgressBar):
950 '''A progress bar with sensible defaults for downloads etc.
952 This assumes that the values its given are numbers of bytes.
953 '''
955 def default_widgets(self):
956 if self.max_value:
957 return [
958 widgets.Percentage(),
959 ' of ',
960 widgets.DataSize('max_value'),
961 ' ',
962 widgets.Bar(),
963 ' ',
964 widgets.Timer(),
965 ' ',
966 widgets.AdaptiveETA(),
967 ]
968 else:
969 return [
970 widgets.AnimatedMarker(),
971 ' ',
972 widgets.DataSize(),
973 ' ',
974 widgets.Timer(),
975 ]
978class NullBar(ProgressBar):
979 '''
980 Progress bar that does absolutely nothing. Useful for single verbosity
981 flags
982 '''
984 def start(self, *args, **kwargs):
985 return self
987 def update(self, *args, **kwargs):
988 return self
990 def finish(self, *args, **kwargs):
991 return self