Track several jobs at once with MultiBar

A download, an extraction, a build, and a test run – four jobs, each progressing at its own pace, finishing at different times – need more than one bar, laid out and cleaned up as a group rather than by hand.

MultiBar jobs finishing at different times
"""Several named jobs, in one `MultiBar`, finishing at different times.

Reach for `MultiBar` instead of hand-rolled `line_offset` bars when the jobs
are a single logical group -- it lays them out for you, and its context
manager waits for every job to finish before letting the `with` block exit.
Add a job with a subscript (`multibar[label]`); each one here reaches its
own target and calls `finish()` independently, so you can watch them
complete one at a time. Finished bars stay on screen rather than
disappearing -- `remove_finished` defaults to an hour.
"""

import random
import sys
import time

import progressbar

random.seed(0)

JOBS = {
    'download': 18,
    'extract': 10,
    'build': 26,
    'test': 22,
}


def main() -> None:
    with progressbar.MultiBar(fd=sys.stdout) as multibar:
        for name, total in JOBS.items():
            multibar[name].max_value = total

        remaining = dict(JOBS)
        while remaining:
            name = random.choice(list(remaining))
            multibar[name].increment()
            if multibar[name].value >= remaining[name]:
                multibar[name].finish()
                del remaining[name]
            time.sleep(0.01)


if __name__ == '__main__':
    main()

MultiBar is a dict of label to ProgressBar: subscripting it with a new label – multibar[name] – creates that job’s bar on first access, so there’s no separate registration step. A background thread renders every job’s bar together, redrawing whichever rows changed. The with block waits for that thread on exit. Each job calls its own finish() independently once it reaches its own target (as every job here does), so its row freezes at its own finished state – elapsed time, final count – while the others keep moving, rather than one shared bar where everything completes in lockstep.

Caveats

By default, exiting the with block waits forever for every job to finish. Pass join_timeout= (seconds, or a datetime.timedelta) to MultiBar() to bound that wait (details: MultiBar).

Rendering multiple bars in place relies on the terminal understanding cursor-movement escapes. JetBrains IDEs (PyCharm, IntelliJ) need “Enable terminal in output console” turned on in the run configuration for this to render correctly. IDLE’s output pane doesn’t support it at all.