Write a custom widget¶
The built-in widgets cover percentages, timers, and transfer speeds, but a job may also need a named phase. The custom widget below shows “preparing”, “processing”, and “finishing” as the work advances.
"""Name each phase of a job with a custom widget.
A widget returns the text for one redraw from the bar's data snapshot.
Place the phase before the stretching bar so it stays easy to find.
"""
import time
import progressbar
from progressbar.bar import ProgressBarMixinBase
from progressbar.widgets import Data, WidgetBase
STEPS: int = 100
class Stage(WidgetBase):
"""Show the phase that corresponds to the current percentage."""
def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str:
percentage: float = data['percentage'] or 0.0
phase: str
if percentage < 20:
phase = 'preparing'
elif percentage < 85:
phase = 'processing'
else:
phase = 'finishing'
return f'Phase: {phase:10}'
def main() -> None:
widgets: list[str | WidgetBase] = [
Stage(),
' ',
progressbar.Percentage(),
' ',
progressbar.Bar(),
]
bar: progressbar.ProgressBar
step: int
with progressbar.ProgressBar(max_value=STEPS, widgets=widgets) as bar:
for step in range(STEPS):
time.sleep(0.02)
bar.update(step + 1)
if __name__ == '__main__':
main()
The phase appears before the bar. Stage returns a label padded to
ten characters, so switching from “preparing” to “processing” keeps
the percentage and bar aligned.
A widget is any callable matching WidgetBase.__call__(self, progress,
data), returning the text to render for one redraw. Subclass
WidgetBase and implement __call__: progress is the bar itself
(read from it, don’t mutate it), and data is the same snapshot dict
the built-in widgets read – data['value'], data['percentage'],
and so on. Drop the instance straight into a widgets= list alongside
the built-ins. Nothing distinguishes a custom widget from a shipped one
at that point.