Source code for progressbar.fast

from __future__ import annotations

import typing
from datetime import datetime, timedelta

from . import (
    bar as bar_module,
    base,
)

#: Optional native line formatter, provided by the `speedups` package. When
#: present it replaces the pure-Python formatter below. Wired in a later task.
_format_fast_line: typing.Callable[[FastProgressBar], str] | None = None


def _format_seconds(seconds: float) -> str:
    """Render elapsed/ETA seconds as H:MM:SS, matching the Timer widget."""
    return str(timedelta(seconds=int(seconds)))


def _pure_format_fast_line(bar: FastProgressBar) -> str:
    """Build the whole status line directly (no widgets, no data() dict)."""
    value = bar.value
    min_value = bar.min_value
    max_value = bar.max_value
    width = bar.term_width
    elapsed = bar._fast_elapsed()
    elapsed_text = _format_seconds(elapsed)
    prefix = bar.prefix or ''
    suffix = bar.suffix or ''

    known = max_value not in (None, base.UnknownLength)
    if known:
        total = max_value - min_value  # type: ignore[operator]
        # Clamp progress to the total so an over-shooting value (e.g. a forced
        # render past max_value with max_error=False) can't produce a negative
        # ETA or a bar that overflows its width.
        done = min(value - min_value, total)
        pct = 100.0 * done / total if total else 100.0
        count = f'({value} of {max_value})'
        if done > 0 and elapsed > 0:
            eta = _format_seconds(elapsed * (total - done) / done)
        else:
            eta = '--:--:--'
        left = f'{pct:3.0f}% {count} '
        right = f' Elapsed Time: {elapsed_text} ETA: {eta}'
        inner = max(width - len(left) - len(right) - 2, 0)
        filled = int(inner * done / total) if total else inner
        barstr = '|' + '#' * filled + ' ' * (inner - filled) + '|'
        return f'{prefix}{left}{barstr}{right}{suffix}'

    # Unknown length: spinner + count + elapsed (no bar/eta).
    spinner = r'|/-\\'[int(elapsed * 4) % 4]
    item_count = value - min_value + 1
    return (
        f'{prefix}{spinner} {item_count} Elapsed Time: {elapsed_text}{suffix}'
    )


[docs] class FastProgressBar(bar_module.ProgressBar): """A lean ProgressBar whose render bypasses the widget system. Reuses the full ProgressBar lifecycle (the next-update gate, the native iterator, stream redirect, resize, start/update/finish) and overrides only the render with a fixed formatter, so the common case is import- and render-cheap. Output stays close to the default look without the gradient. """
[docs] def default_widgets(self) -> list: # No widgets: the fixed formatter renders everything. return []
def _fast_elapsed(self) -> float: if self.start_time is None: return 0.0 end = self.end_time or self._fast_now() return max((end - self.start_time).total_seconds(), 0.0) def _fast_now(self) -> datetime: return datetime.now() def _format_line(self) -> str: formatter = _format_fast_line or _pure_format_fast_line return formatter(self) def _init_prefix(self) -> None: # Label is rendered inline by the formatter; don't inject a widget. pass def _init_suffix(self) -> None: # Label is rendered inline by the formatter; don't inject a widget. pass