From cc68e0a2784f5d6a11be9ed17fc712b57dbb2ed9 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Fri, 9 Apr 2021 00:55:48 +0200 Subject: [PATCH] Allow 'total' to be updated during runtime Use-Case: I have a multi-threaded program, where one thread traverses a large directory tee to searches for files to work on. A second thread works on these files, even when the first thread is still searching. So while there is already work happening, the list of files to process can still continue to grow. This patch adds the functionality to update the total value to handle this use case. How: I adapted the mechanism as used in the 'bar.current()' attribute and applied it to the variables 'total', 'logic_total' and 'gen_eta'. Also a function bar.update_total(nn) has been added, that regenerates all 3 values appropriately. --- README.md | 22 ++++++++++++++++++++++ alive_progress/core/progress.py | 25 ++++++++++++++++--------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e73fb9f..22aefbb 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ In general lines, just retrieve the items, enter the `alive_bar` context manager - the first argument of the `alive_bar` is the expected total, so it can be anything that returns an integer, like `qs.count()` for querysets, `len(items)` for iterables that support it, or even a static integer; - the `bar()` call is what makes the bar go forward -- you usually call it in every iteration after consuming an item, but you can get creative! Remember the bar is counting for you _independently of the iteration process_, only when you call `bar()` (something no other progress bar have), so you can use it to count anything you want! For example, you could call `bar()` only when you find something expected and then know how many of those there were, including the percentage that it represents! Or call it more than once in the same iteration, no problem at all, you choose what you are monitoring! The ETA will not be that useful unfortunately; - to retrieve the current `bar()` count/percentage, you can call `bar.current()`. +- to update `total` during runtime, use the `bar.update_total(nn)` function. So, you could even use it without any loops, like for example: @@ -375,6 +376,27 @@ And if you want to do even more, exciting stuff lies ahead! > --- +
+You need to update total in runtime? + +> ### Dynamic total +> +> Imagine a multi-threaded application where one thread collects items in the +> file system, on which a second thread does work. +> +> In this case, the `total` is still growing while work is already done on the +> available data. +> +> You can use `bar.update_total(new_total)` to cope with that. +> ``` +> with alive_bar(1) as bar: +> bar.update_total(check_current_total()) +> bar() +> ``` +> +> --- +
+ ## Interesting facts diff --git a/alive_progress/core/progress.py b/alive_progress/core/progress.py index 6b79744..a9eb2f3 100644 --- a/alive_progress/core/progress.py +++ b/alive_progress/core/progress.py @@ -205,16 +205,23 @@ def pause_monitoring(): thread.start() if total or not config.manual: # we can count items. - logic_total, rate_spec, factor, current = total, 'f', 1.e6, lambda: run.count # noqa + run.total, run.logic_total, rate_spec, factor, current = total, total, 'f', 1.e6, lambda: run.count # noqa else: # there's only a manual percentage. - logic_total, rate_spec, factor, current = 1., '%', 1., lambda: run.percent # noqa + run.logic_total, rate_spec, factor, current = 1., '%', 1., lambda: run.percent # noqa + + def update_total(n): + run.total = n + run.logic_total = n + run.gen_eta = gen_simple_exponential_smoothing_eta(.5, run.logic_total) + run.gen_eta.send(None) + + bar_handle.text, bar_handle.current, bar_handle.update_total = set_text, current, update_total - bar_handle.text, bar_handle.current = set_text, current if total or config.manual: # we can track progress and therefore eta. spec = '({{:.1{}}}/s, eta: {{}})'.format(rate_spec) - gen_eta = gen_simple_exponential_smoothing_eta(.5, logic_total) - gen_eta.send(None) - stats = lambda: spec.format(run.rate, to_eta_text(gen_eta.send((current(), run.rate)))) + run.gen_eta = gen_simple_exponential_smoothing_eta(.5, run.logic_total) + run.gen_eta.send(None) + stats = lambda: spec.format(run.rate, to_eta_text(run.gen_eta.send((current(), run.rate)))) bar_repr = config.bar(config.length) else: # unknown progress. bar_repr = config.unknown(config.length, config.bar) @@ -248,13 +255,13 @@ def fps(): if total: if config.manual: def update_hook(): - run.count = int(math.ceil(run.percent * total)) + run.count = int(math.ceil(run.percent * run.total)) else: def update_hook(): - run.percent = run.count / total + run.percent = run.count / run.total monitor = lambda: '{}{}/{} [{:.0%}]'.format( # noqa - '(!) ' if end and run.count != total else '', run.count, total, run.percent + '(!) ' if end and run.count != run.total else '', run.count, run.total, run.percent ) elif config.manual: update_hook = lambda: None # noqa