Coverage for /Volumes/workspace/python-progressbar/.tox/py310/lib/python3.10/site-packages/progressbar/bar.py: 99%

366 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-01 16:14 +0100

1from __future__ import annotations 

2 

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 

13 

14import math 

15from python_utils import converters, types 

16 

17from . import ( 

18 base, 

19 utils, 

20 widgets, 

21 widgets as widgets_module, # Avoid name collision 

22) 

23 

24logger = logging.getLogger(__name__) 

25 

26# float also accepts integers and longs but we don't want an explicit union 

27# due to type checking complexity 

28T = float 

29 

30 

31class ProgressBarMixinBase(abc.ABC): 

32 _started = False 

33 _finished = False 

34 _last_update_time: types.Optional[float] = None 

35 

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 

65 

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 

80 

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] 

85 

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 

91 

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 

97 

98 last_update_time = property(get_last_update_time, set_last_update_time) 

99 

100 def __init__(self, **kwargs): 

101 pass 

102 

103 def start(self, **kwargs): 

104 self._started = True 

105 

106 def update(self, value=None): 

107 pass 

108 

109 def finish(self): # pragma: no cover 

110 self._finished = True 

111 

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 ) 

122 

123 def __getstate__(self): 

124 return self.__dict__ 

125 

126 def data(self) -> types.Dict[str, types.Any]: 

127 raise NotImplementedError() 

128 

129 

130class ProgressBarBase(types.Iterable, ProgressBarMixinBase): 

131 pass 

132 

133 

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 

146 

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 

157 

158 elif fd is sys.stderr: 

159 fd = utils.streams.original_stderr 

160 

161 self.fd = fd 

162 self.is_ansi_terminal = utils.is_ansi_terminal(fd) 

163 

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 ) 

168 

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) 

176 

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 ) 

183 

184 self.enable_colors = bool(enable_colors) 

185 

186 ProgressBarMixinBase.__init__(self, **kwargs) 

187 

188 def update(self, *args, **kwargs): 

189 ProgressBarMixinBase.update(self, *args, **kwargs) 

190 

191 line: str = converters.to_unicode(self._format_line()) 

192 if not self.enable_colors: 

193 line = utils.no_color(line) 

194 

195 if self.line_breaks: 

196 line = line.rstrip() + '\n' 

197 else: 

198 line = '\r' + line 

199 

200 try: # pragma: no cover 

201 self.fd.write(line) 

202 except UnicodeEncodeError: # pragma: no cover 

203 self.fd.write(line.encode('ascii', 'replace')) 

204 

205 def finish(self, *args, **kwargs): # pragma: no cover 

206 if self._finished: 

207 return 

208 

209 end = kwargs.pop('end', '\n') 

210 ProgressBarMixinBase.finish(self, *args, **kwargs) 

211 

212 if end and not self.line_breaks: 

213 self.fd.write(end) 

214 

215 self.fd.flush() 

216 

217 def _format_line(self): 

218 'Joins the widgets and justifies the line' 

219 

220 widgets = ''.join(self._to_unicode(self._format_widgets())) 

221 

222 if self.left_justify: 

223 return widgets.ljust(self.term_width) 

224 else: 

225 return widgets.rjust(self.term_width) 

226 

227 def _format_widgets(self): 

228 result = [] 

229 expanding = [] 

230 width = self.term_width 

231 data = self.data() 

232 

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 

248 

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 

255 

256 widget_output = widget(self, data, portion) 

257 width -= self.custom_len(widget_output) # type: ignore 

258 result[index] = widget_output 

259 

260 return result 

261 

262 @classmethod 

263 def _to_unicode(cls, args): 

264 for arg in args: 

265 yield converters.to_unicode(arg) 

266 

267 

268class ResizableMixin(ProgressBarMixinBase): 

269 def __init__(self, term_width: int | None = None, **kwargs): 

270 ProgressBarMixinBase.__init__(self, **kwargs) 

271 

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 

279 

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 

285 

286 def _handle_resize(self, signum=None, frame=None): 

287 'Tries to catch resize signals sent from the terminal.' 

288 

289 w, h = utils.get_terminal_size() 

290 self.term_width = w 

291 

292 def finish(self): # pragma: no cover 

293 ProgressBarMixinBase.finish(self) 

294 if self.signal_set: 

295 try: 

296 import signal 

297 

298 signal.signal(signal.SIGWINCH, self._prev_handle) 

299 except Exception: # pragma no cover 

300 pass 

301 

302 

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 

310 

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 

322 

323 def start(self, *args, **kwargs): 

324 if self.redirect_stdout: 

325 utils.streams.wrap_stdout() 

326 

327 if self.redirect_stderr: 

328 utils.streams.wrap_stderr() 

329 

330 self._stdout = utils.streams.original_stdout 

331 self._stderr = utils.streams.original_stderr 

332 

333 self.stdout = utils.streams.stdout 

334 self.stderr = utils.streams.stderr 

335 

336 utils.streams.start_capturing(self) 

337 DefaultFdMixin.start(self, *args, **kwargs) 

338 

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') 

342 

343 utils.streams.flush() 

344 DefaultFdMixin.update(self, value=value) 

345 

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() 

351 

352 if self.redirect_stderr: 

353 utils.streams.unwrap_stderr() 

354 

355 

356class ProgressBar( 

357 StdRedirectMixin, 

358 ResizableMixin, 

359 ProgressBarBase, 

360): 

361 '''The ProgressBar class which updates and prints the bar. 

362 

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 

395 

396 A common way of using it is like: 

397 

398 >>> progress = ProgressBar().start() 

399 >>> for i in range(100): 

400 ... progress.update(i + 1) 

401 ... # do something 

402 ... 

403 >>> progress.finish() 

404 

405 You can also use a ProgressBar as an iterator: 

406 

407 >>> progress = ProgressBar() 

408 >>> some_iterable = range(100) 

409 >>> for i in progress(some_iterable): 

410 ... # do something 

411 ... pass 

412 ... 

413 

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. 

419 

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. 

424 

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 ''' 

430 

431 _iterable: types.Optional[types.Iterator] 

432 

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 

437 

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') 

468 

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') 

476 

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 

489 

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) 

497 

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() 

507 

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 ) 

523 

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 

532 

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 

539 

540 @property 

541 def dynamic_messages(self): # pragma: no cover 

542 return self.variables 

543 

544 @dynamic_messages.setter 

545 def dynamic_messages(self, value): # pragma: no cover 

546 self.variables = value 

547 

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() 

560 

561 @property 

562 def percentage(self): 

563 '''Return current percentage, returns None if no max_value is given 

564 

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 

601 

602 return percentage 

603 

604 def data(self) -> types.Dict[str, types.Any]: 

605 ''' 

606 

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 

630 

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 ) 

674 

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 ] 

701 

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 

711 

712 self._iterable = iter(iterable) 

713 return self 

714 

715 def __iter__(self): 

716 return self 

717 

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) 

724 

725 if self.start_time is None: 

726 self.start() 

727 else: 

728 self.update(self.value + 1) 

729 

730 return value 

731 except StopIteration: 

732 self.finish() 

733 raise 

734 except GeneratorExit: # pragma: no cover 

735 self.finish(dirty=True) 

736 raise 

737 

738 def __exit__(self, exc_type, exc_value, traceback): 

739 self.finish(dirty=bool(exc_type)) 

740 

741 def __enter__(self): 

742 return self 

743 

744 # Create an alias so that Python 2.x won't complain about not being 

745 # an iterator. 

746 next = __next__ 

747 

748 def __iadd__(self, value): 

749 'Updates the ProgressBar by adding a new value.' 

750 return self.increment(value) 

751 

752 def increment(self, value=1, *args, **kwargs): 

753 self.update(self.value + value, *args, **kwargs) 

754 return self 

755 

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 

765 

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 

778 

779 # No need to redraw yet 

780 return False 

781 

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) 

787 

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 

802 

803 self.previous_value = self.value 

804 self.value = value 

805 

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 

817 

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) 

823 

824 # Only flush if something was actually written 

825 self.fd.flush() 

826 

827 def start(self, max_value=None, init=True): 

828 '''Starts measuring time, and prints the bar at 0%. 

829 

830 It returns self so you can use it like this: 

831 

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 

837 

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() 

847 

848 # Prevent multiple starts 

849 if self.start_time is not None: # pragma: no cover 

850 return self 

851 

852 if max_value is not None: 

853 self.max_value = max_value 

854 

855 if self.max_value is None: 

856 self.max_value = self._DEFAULT_MAXVAL 

857 

858 StdRedirectMixin.start(self, max_value=max_value) 

859 ResizableMixin.start(self, max_value=max_value) 

860 ProgressBarBase.start(self, max_value=max_value) 

861 

862 # Constructing the default widgets is only done when we know max_value 

863 if not self.widgets: 

864 self.widgets = self.default_widgets() 

865 

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 

873 

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 

881 

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 ) 

892 

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 

897 

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) 

904 

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) 

910 

911 return self 

912 

913 def finish(self, end='\n', dirty=False): 

914 ''' 

915 Puts the ProgressBar bar in the finished state. 

916 

917 Also flushes and disables output buffering if this was the last 

918 progressbar running. 

919 

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 ''' 

926 

927 if not dirty: 

928 self.end_time = datetime.now() 

929 self.update(self.max_value, force=True) 

930 

931 StdRedirectMixin.finish(self, end=end) 

932 ResizableMixin.finish(self) 

933 ProgressBarBase.finish(self) 

934 

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 

947 

948 

949class DataTransferBar(ProgressBar): 

950 '''A progress bar with sensible defaults for downloads etc. 

951 

952 This assumes that the values its given are numbers of bytes. 

953 ''' 

954 

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 ] 

976 

977 

978class NullBar(ProgressBar): 

979 ''' 

980 Progress bar that does absolutely nothing. Useful for single verbosity 

981 flags 

982 ''' 

983 

984 def start(self, *args, **kwargs): 

985 return self 

986 

987 def update(self, *args, **kwargs): 

988 return self 

989 

990 def finish(self, *args, **kwargs): 

991 return self