diff --git a/.meta/.readme.rst b/.meta/.readme.rst index dd98f49bd..75d83a6aa 100644 --- a/.meta/.readme.rst +++ b/.meta/.readme.rst @@ -272,6 +272,16 @@ Or done on a file level using 7-zip: | grep -v Compressing 100%|██████████████████████████▉| 15327/15327 [01:00<00:00, 712.96files/s] +Pre-existing CLI programs already outputting basic progress information will +benefit from ``tqdm``'s ``--update`` and ``--update_to`` flags: + +.. code:: sh + + seq 3 0.1 5 | tqdm --total 5 --update_to --null + 100%|████████████████████████████████████| 5.0/5 [00:00<00:00, 9673.21it/s] + seq 10 | tqdm --update --null # 1 + 2 + ... + 10 = 55 iterations + 55it [00:00, 90006.52it/s] + FAQ and Known Issues -------------------- @@ -298,7 +308,7 @@ of a neat one-line progress bar. - Unicode: * Environments which report that they support unicode will have solid smooth - progressbars. The fallback is an ```ascii``-only bar. + progressbars. The fallback is an ``ascii``-only bar. * Windows consoles often only partially support unicode and thus `often require explicit ascii=True `__ (also `here `__). This is due to @@ -646,7 +656,7 @@ Here's an example with ``urllib``: """ if tsize is not None: self.total = tsize - self.update(b * bsize - self.n) # will also set self.n = b * bsize + return self.update(b * bsize - self.n) # also sets self.n = b * bsize eg_link = "https://caspersci.uk.to/matryoshka.zip" with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=1, @@ -713,6 +723,26 @@ The ``requests`` equivalent is nearly identical, albeit with a ``total``: for chunk in response.iter_content(chunk_size=4096): fout.write(chunk) +**Custom callback** + +``tqdm`` is known for intelligently skipping unnecessary displays. To make a +custom callback take advantage of this, simply use the return value of +``update()``. This is set to ``True`` if a ``display()`` was triggered. + +.. code:: python + + from tqdm.auto import tqdm as std_tqdm + + def external_callback(*args, **kwargs): + ... + + class TqdmExt(std_tqdm): + def update(self, n=1): + displayed = super(TqdmExt, self).update(n): + if displayed: + external_callback(**self.format_dict) + return displayed + Pandas Integration ~~~~~~~~~~~~~~~~~~ @@ -842,10 +872,13 @@ For further customisation, Consider overloading ``display()`` to use e.g. ``self.frontend(**self.format_dict)`` instead of ``self.sp(repr(self))``. -`tqdm/notebook.py `__ -and `tqdm/gui.py `__ -submodules are examples of inheritance which don't (yet) strictly conform to the -above recommendation. +Some submodule examples of inheritance which don't (yet) strictly conform to the +above recommendation: + +- `tqdm/notebook.py `__ +- `tqdm/gui.py `__ +- `tqdm/contrib/telegram.py `__ +- `tqdm/contrib/discord.py `__ Dynamic Monitor/Meter ~~~~~~~~~~~~~~~~~~~~~ diff --git a/DEMO.ipynb b/DEMO.ipynb index 67f48299f..c80039031 100644 --- a/DEMO.ipynb +++ b/DEMO.ipynb @@ -703,7 +703,7 @@ " \"\"\"\n", " if tsize is not None:\n", " self.total = tsize\n", - " self.update(b * bsize - self.n) # will also set self.n = b * bsize\n", + " return self.update(b * bsize - self.n) # also sets self.n = b * bsize\n", "\n", "eg_link = \"https://caspersci.uk.to/matryoshka.zip\"\n", "with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=1,\n", diff --git a/Makefile b/Makefile index f0c5e5f8f..d9091cad0 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,7 @@ tqdm/tqdm.1: .meta/.tqdm.1.md tqdm/cli.py tqdm/std.py python -m tqdm --help | tail -n+5 |\ sed -r -e 's/\\/\\\\/g' \ -e 's/^ (--.*)=<(.*)> : (.*)$$/\n\\\1=*\2*\n: \3./' \ + -e 's/^ (--.*) : (.*)$$/\n\\\1\n: \2./' \ -e 's/ (-.*, )(--.*) /\n\1\\\2\n: /' |\ cat "$<" - |\ pandoc -o "$@" -s -t man diff --git a/README.rst b/README.rst index bd82ec910..ac43c547b 100644 --- a/README.rst +++ b/README.rst @@ -272,6 +272,16 @@ Or done on a file level using 7-zip: | grep -v Compressing 100%|██████████████████████████▉| 15327/15327 [01:00<00:00, 712.96files/s] +Pre-existing CLI programs already outputting basic progress information will +benefit from ``tqdm``'s ``--update`` and ``--update_to`` flags: + +.. code:: sh + + seq 3 0.1 5 | tqdm --total 5 --update_to --null + 100%|████████████████████████████████████| 5.0/5 [00:00<00:00, 9673.21it/s] + seq 10 | tqdm --update --null # 1 + 2 + ... + 10 = 55 iterations + 55it [00:00, 90006.52it/s] + FAQ and Known Issues -------------------- @@ -298,7 +308,7 @@ of a neat one-line progress bar. - Unicode: * Environments which report that they support unicode will have solid smooth - progressbars. The fallback is an ```ascii``-only bar. + progressbars. The fallback is an ``ascii``-only bar. * Windows consoles often only partially support unicode and thus `often require explicit ascii=True `__ (also `here `__). This is due to @@ -463,6 +473,16 @@ Extra CLI Options * bytes : bool, optional If true, will count bytes, ignore ``delim``, and default ``unit_scale`` to True, ``unit_divisor`` to 1024, and ``unit`` to 'B'. +* tee : bool, optional + If true, passes ``stdin`` to both ``stderr`` and ``stdout``. +* update : bool, optional + If true, will treat input as newly elapsed iterations, + i.e. numbers to pass to ``update()``. +* update_to : bool, optional + If true, will treat input as total elapsed iterations, + i.e. numbers to assign to ``self.n``. +* null : bool, optional + If true, will discard input (no stdout). * manpath : str, optional Directory in which to install tqdm man pages. * comppath : str, optional @@ -498,6 +518,11 @@ Returns Increment to add to the internal counter of iterations [default: 1]. If using float, consider specifying ``{n:.3f}`` or similar in ``bar_format``, or specifying ``unit_scale``. + + Returns + ------- + out : bool or None + True if a ``display()`` was triggered. """ def close(self): @@ -844,7 +869,7 @@ Here's an example with ``urllib``: """ if tsize is not None: self.total = tsize - self.update(b * bsize - self.n) # will also set self.n = b * bsize + return self.update(b * bsize - self.n) # also sets self.n = b * bsize eg_link = "https://caspersci.uk.to/matryoshka.zip" with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=1, @@ -911,6 +936,26 @@ The ``requests`` equivalent is nearly identical, albeit with a ``total``: for chunk in response.iter_content(chunk_size=4096): fout.write(chunk) +**Custom callback** + +``tqdm`` is known for intelligently skipping unnecessary displays. To make a +custom callback take advantage of this, simply use the return value of +``update()``. This is set to ``True`` if a ``display()`` was triggered. + +.. code:: python + + from tqdm.auto import tqdm as std_tqdm + + def external_callback(*args, **kwargs): + ... + + class TqdmExt(std_tqdm): + def update(self, n=1): + displayed = super(TqdmExt, self).update(n): + if displayed: + external_callback(**self.format_dict) + return displayed + Pandas Integration ~~~~~~~~~~~~~~~~~~ @@ -1040,10 +1085,13 @@ For further customisation, Consider overloading ``display()`` to use e.g. ``self.frontend(**self.format_dict)`` instead of ``self.sp(repr(self))``. -`tqdm/notebook.py `__ -and `tqdm/gui.py `__ -submodules are examples of inheritance which don't (yet) strictly conform to the -above recommendation. +Some submodule examples of inheritance which don't (yet) strictly conform to the +above recommendation: + +- `tqdm/notebook.py `__ +- `tqdm/gui.py `__ +- `tqdm/contrib/telegram.py `__ +- `tqdm/contrib/discord.py `__ Dynamic Monitor/Meter ~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/tqdm_wget.py b/examples/tqdm_wget.py index 0afdd5a0c..4a7ee946c 100644 --- a/examples/tqdm_wget.py +++ b/examples/tqdm_wget.py @@ -55,8 +55,9 @@ def update_to(b=1, bsize=1, tsize=None): """ if tsize not in (None, -1): t.total = tsize - t.update((b - last_b[0]) * bsize) + displayed = t.update((b - last_b[0]) * bsize) last_b[0] = b + return displayed return update_to @@ -81,7 +82,7 @@ def update_to(self, b=1, bsize=1, tsize=None): """ if tsize is not None: self.total = tsize - self.update(b * bsize - self.n) # will also set self.n = b * bsize + return self.update(b * bsize - self.n) # also sets self.n = b * bsize opts = docopt(__doc__) diff --git a/tqdm/_version.py b/tqdm/_version.py index 9d541d6f7..87ff14126 100644 --- a/tqdm/_version.py +++ b/tqdm/_version.py @@ -5,7 +5,7 @@ __all__ = ["__version__"] # major, minor, patch, -extra -version_info = 4, 48, 2 +version_info = 4, 49, 0 # Nice string for the version __version__ = '.'.join(map(str, version_info)) diff --git a/tqdm/auto.py b/tqdm/auto.py index f06830cd9..9dbf50d44 100644 --- a/tqdm/auto.py +++ b/tqdm/auto.py @@ -20,7 +20,7 @@ from .autonotebook import tqdm as notebook_tqdm from .autonotebook import trange as notebook_trange -if sys.version_info[:1] < (3, 4): +if sys.version_info[:2] < (3, 5): tqdm = notebook_tqdm trange = notebook_trange else: # Python3.5+ diff --git a/tqdm/cli.py b/tqdm/cli.py index 82160fb92..db90e7a00 100644 --- a/tqdm/cli.py +++ b/tqdm/cli.py @@ -3,14 +3,15 @@ """ from .std import tqdm, TqdmTypeError, TqdmKeyError from ._version import __version__ # NOQA -import sys -import re +from ast import literal_eval as numeric import logging +import re +import sys __all__ = ["main"] +log = logging.getLogger(__name__) def cast(val, typ): - log = logging.getLogger(__name__) log.debug((val, typ)) if " or " in typ: for t in typ.split(" or "): @@ -32,24 +33,35 @@ def cast(val, typ): return eval(typ + '("' + val + '")') except: if typ == 'chr': - return chr(ord(eval('"' + val + '"'))) + return chr(ord(eval('"' + val + '"'))).encode() else: raise TqdmTypeError(val + ' : ' + typ) -def posix_pipe(fin, fout, delim='\n', buf_size=256, - callback=lambda int: None # pragma: no cover - ): +def isBytes(val): + """Equivalent of `isinstance(val, six.binary_type)`.""" + try: + val.index(b'') + except TypeError: + return False + return True + + +def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, + callback=lambda float: None, # pragma: no cover + callback_len=True): """ Params ------ fin : file with `read(buf_size : int)` method fout : file with `write` (and optionally `flush`) methods. - callback : function(int), e.g.: `tqdm.update` + callback : function(float), e.g.: `tqdm.update` + callback_len : If (default: True) do `callback(len(buffer))`. + Otherwise, do `callback(data) for data in buffer.split(delim)`. """ fp_write = fout.write - # tmp = '' + # tmp = b'' if not delim: while True: tmp = fin.read(buf_size) @@ -63,16 +75,32 @@ def posix_pipe(fin, fout, delim='\n', buf_size=256, callback(len(tmp)) # return - buf = '' + buf = b'' + check_bytes = True + write_bytes = True # n = 0 while True: tmp = fin.read(buf_size) + if check_bytes: # first time; check encoding + check_bytes = False + if not isBytes(tmp): + # currently only triggered by `tests_main.py`. + # TODO: mock stdin/out better so that this isn't needed + write_bytes = False + delim = delim.decode() + buf = buf.decode() + # flush at EOF if not tmp: if buf: fp_write(buf) - callback(1 + buf.count(delim)) # n += 1 + buf.count(delim) + if callback_len: + # n += 1 + buf.count(delim) + callback(1 + buf.count(delim)) + else: + for i in buf.split(delim): + callback(i) getattr(fout, 'flush', lambda: None)() # pragma: no cover return # n @@ -84,8 +112,9 @@ def posix_pipe(fin, fout, delim='\n', buf_size=256, break else: fp_write(buf + tmp[:i + len(delim)]) - callback(1) # n += 1 - buf = '' + # n += 1 + callback(1 if callback_len else (buf + tmp[:i])) + buf = b'' if write_bytes else '' tmp = tmp[i + len(delim):] @@ -112,6 +141,16 @@ def posix_pipe(fin, fout, delim='\n', buf_size=256, bytes : bool, optional If true, will count bytes, ignore `delim`, and default `unit_scale` to True, `unit_divisor` to 1024, and `unit` to 'B'. + tee : bool, optional + If true, passes `stdin` to both `stderr` and `stdout`. + update : bool, optional + If true, will treat input as newly elapsed iterations, + i.e. numbers to pass to `update()`. + update_to : bool, optional + If true, will treat input as total elapsed iterations, + i.e. numbers to assign to `self.n`. + null : bool, optional + If true, will discard input (no stdout). manpath : str, optional Directory in which to install tqdm man pages. comppath : str, optional @@ -131,7 +170,7 @@ def main(fp=sys.stderr, argv=None): if argv is None: argv = sys.argv[1:] try: - log = argv.index('--log') + log_idx = argv.index('--log') except ValueError: for i in argv: if i.startswith('--log='): @@ -140,13 +179,12 @@ def main(fp=sys.stderr, argv=None): else: logLevel = 'INFO' else: - # argv.pop(log) - # logLevel = argv.pop(log) - logLevel = argv[log + 1] + # argv.pop(log_idx) + # logLevel = argv.pop(log_idx) + logLevel = argv[log_idx + 1] logging.basicConfig( level=getattr(logging, logLevel), format="%(levelname)s:%(module)s:%(lineno)d:%(message)s") - log = logging.getLogger(__name__) d = tqdm.__init__.__doc__ + CLI_EXTRA_DOC @@ -161,16 +199,17 @@ def main(fp=sys.stderr, argv=None): # d = RE_OPTS.sub(r' --\1=<\1> : \2', d) split = RE_OPTS.split(d) opt_types_desc = zip(split[1::3], split[2::3], split[3::3]) - d = ''.join('\n --{0}=<{0}> : {1}{2}'.format(*otd) + d = ''.join(('\n --{0} : {2}{3}' if otd[1] == 'bool' else + '\n --{0}=<{1}> : {2}{3}').format( + otd[0].replace('_', '-'), otd[0], *otd[1:]) for otd in opt_types_desc if otd[0] not in UNSUPPORTED_OPTS) d = """Usage: tqdm [--help | options] Options: - -h, --help Print this help and exit - -v, --version Print version and exit - + -h, --help Print this help and exit. + -v, --version Print version and exit. """ + d.strip('\n') + '\n' # opts = docopt(d, version=__version__) @@ -190,11 +229,19 @@ def main(fp=sys.stderr, argv=None): tqdm_args = {'file': fp} try: for (o, v) in opts.items(): + o = o.replace('-', '_') try: tqdm_args[o] = cast(v, opt_types[o]) except KeyError as e: raise TqdmKeyError(str(e)) log.debug('args:' + str(tqdm_args)) + + delim_per_char = tqdm_args.pop('bytes', False) + update = tqdm_args.pop('update', False) + update_to = tqdm_args.pop('update_to', False) + if sum((delim_per_char, update, update_to)) > 1: + raise TqdmKeyError( + "Can only have one of --bytes --update --update_to") except: fp.write('\nError:\nUsage:\n tqdm [--help | options]\n') for i in sys.stdin: @@ -202,12 +249,19 @@ def main(fp=sys.stderr, argv=None): raise else: buf_size = tqdm_args.pop('buf_size', 256) - delim = tqdm_args.pop('delim', '\n') - delim_per_char = tqdm_args.pop('bytes', False) + delim = tqdm_args.pop('delim', b'\\n') + tee = tqdm_args.pop('tee', False) manpath = tqdm_args.pop('manpath', None) comppath = tqdm_args.pop('comppath', None) + if tqdm_args.pop('null', False): + class stdout(object): + @staticmethod + def write(_): + pass + else: + stdout = sys.stdout + stdout = getattr(stdout, 'buffer', stdout) stdin = getattr(sys.stdin, 'buffer', sys.stdin) - stdout = getattr(sys.stdout, 'buffer', sys.stdout) if manpath or comppath: from os import path from shutil import copyfile @@ -225,6 +279,16 @@ def cp(src, dst): 'tqdm/completion.sh'), path.join(comppath, 'tqdm_completion.sh')) sys.exit(0) + if tee: + stdout_write = stdout.write + fp_write = getattr(fp, 'buffer', fp).write + + class stdout(object): + @staticmethod + def write(x): + with tqdm.external_write_mode(file=fp): + fp_write(x) + stdout_write(x) if delim_per_char: tqdm_args.setdefault('unit', 'B') tqdm_args.setdefault('unit_scale', True) @@ -232,11 +296,34 @@ def cp(src, dst): log.debug(tqdm_args) with tqdm(**tqdm_args) as t: posix_pipe(stdin, stdout, '', buf_size, t.update) - elif delim == '\n': + elif delim == b'\\n': log.debug(tqdm_args) - for i in tqdm(stdin, **tqdm_args): - stdout.write(i) + if update or update_to: + with tqdm(**tqdm_args) as t: + if update: + def callback(i): + t.update(numeric(i)) + else: # update_to + def callback(i): + t.update(numeric(i) - t.n) + for i in stdin: + stdout.write(i) + callback(i) + else: + for i in tqdm(stdin, **tqdm_args): + stdout.write(i) else: log.debug(tqdm_args) with tqdm(**tqdm_args) as t: - posix_pipe(stdin, stdout, delim, buf_size, t.update) + callback_len = False + if update: + def callback(i): + t.update(numeric(i)) + elif update_to: + def callback(i): + t.update(numeric(i) - t.n) + else: + callback = t.update + callback_len = True + posix_pipe(stdin, stdout, delim, buf_size, + callback, callback_len) diff --git a/tqdm/completion.sh b/tqdm/completion.sh index fabd3f2d3..42e84a8b5 100755 --- a/tqdm/completion.sh +++ b/tqdm/completion.sh @@ -12,7 +12,7 @@ _tqdm(){ COMPREPLY=($(compgen -W 'CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET' -- ${cur})) ;; *) - COMPREPLY=($(compgen -W '--ascii --bar_format --buf_size --bytes --comppath --delim --desc --disable --dynamic_ncols --help --initial --leave --lock_args --log --manpath --maxinterval --mininterval --miniters --ncols --nrows --position --postfix --smoothing --total --unit --unit_divisor --unit_scale --version --write_bytes -h -v' -- ${cur})) + COMPREPLY=($(compgen -W '--ascii --bar_format --buf_size --bytes --comppath --delim --desc --disable --dynamic_ncols --help --initial --leave --lock_args --log --manpath --maxinterval --mininterval --miniters --ncols --nrows --null --position --postfix --smoothing --tee --total --unit --unit_divisor --unit_scale --update --update_to --version --write_bytes -h -v' -- ${cur})) ;; esac } diff --git a/tqdm/gui.py b/tqdm/gui.py index 276780594..240aeb983 100644 --- a/tqdm/gui.py +++ b/tqdm/gui.py @@ -220,6 +220,7 @@ def update(self, n=1): # Store old values for next call self.last_print_n = self.n self.last_print_t = cur_t + return True def close(self): # if not self.gui: diff --git a/tqdm/notebook.py b/tqdm/notebook.py index 3abc561fe..39df5f250 100644 --- a/tqdm/notebook.py +++ b/tqdm/notebook.py @@ -237,7 +237,7 @@ def __iter__(self, *args, **kwargs): def update(self, *args, **kwargs): try: - super(tqdm_notebook, self).update(*args, **kwargs) + return super(tqdm_notebook, self).update(*args, **kwargs) # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt except: # NOQA # cannot catch KeyboardInterrupt when using manual tqdm diff --git a/tqdm/std.py b/tqdm/std.py index 593a9b3cd..05d7e5027 100644 --- a/tqdm/std.py +++ b/tqdm/std.py @@ -23,8 +23,7 @@ import threading as th from warnings import warn -__author__ = {"github.com/": ["noamraph", "obiwanus", "kmike", "hadim", - "casperdcl", "lrq3000"]} +__author__ = "https://github.com/tqdm/tqdm#contributions" __all__ = ['tqdm', 'trange', 'TqdmTypeError', 'TqdmKeyError', 'TqdmWarning', 'TqdmExperimentalWarning', 'TqdmDeprecationWarning', @@ -310,7 +309,8 @@ def print_status(s): @staticmethod def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, unit='it', unit_scale=False, rate=None, bar_format=None, - postfix=None, unit_divisor=1000, **extra_kwargs): + postfix=None, unit_divisor=1000, initial=0, + **extra_kwargs): """ Return a string-based progress bar given some parameters @@ -366,6 +366,8 @@ def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, However other types are supported (#382). unit_divisor : float, optional [default: 1000], ignored unless `unit_scale` is True. + initial : int or float, optional + The initial counter value [default: 0]. Returns ------- @@ -390,7 +392,7 @@ def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, # if unspecified, attempt to use rate = average speed # (we allow manual override since predicting time is an arcane art) if rate is None and elapsed: - rate = n / elapsed + rate = (n - initial) / elapsed inv_rate = 1 / rate if rate else None format_sizeof = tqdm.format_sizeof rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else @@ -1019,6 +1021,7 @@ def __init__(self, iterable=None, desc=None, total=None, leave=True, self.unit = unit self.unit_scale = unit_scale self.unit_divisor = unit_divisor + self.initial = initial self.lock_args = lock_args self.gui = gui self.dynamic_ncols = dynamic_ncols @@ -1202,6 +1205,11 @@ def update(self, n=1): Increment to add to the internal counter of iterations [default: 1]. If using float, consider specifying `{n:.3f}` or similar in `bar_format`, or specifying `unit_scale`. + + Returns + ------- + out : bool or None + True if a `display()` was triggered. """ # N.B.: see __iter__() for more comments. if self.disable: @@ -1257,6 +1265,7 @@ def update(self, n=1): # Store old values for next call self.last_print_n = self.n self.last_print_t = cur_t + return True def close(self): """Cleanup and (if leave=False) close the progressbar.""" @@ -1442,7 +1451,7 @@ def format_dict(self): unit_scale=self.unit_scale, rate=1 / self.avg_time if self.avg_time else None, bar_format=self.bar_format, postfix=self.postfix, - unit_divisor=self.unit_divisor) + unit_divisor=self.unit_divisor, initial=self.initial) def display(self, msg=None, pos=None): """ diff --git a/tqdm/tests/tests_main.py b/tqdm/tests/tests_main.py index 75727071e..08eccc05b 100644 --- a/tqdm/tests/tests_main.py +++ b/tqdm/tests/tests_main.py @@ -24,13 +24,11 @@ def __getattr__(self, _): return self -IN_DATA_LIST = map(str, _range(int(123))) NULL = Null() -# WARNING: this should be the last test as it messes with sys.stdin, argv @with_setup(pretest, posttest) -def test_main(): +def test_pipes(): """Test command line pipes""" ls_out = _sh('ls').replace('\r\n', '\n') ls = subprocess.Popen('ls', stdout=subprocess.PIPE, @@ -43,40 +41,143 @@ def test_main(): assert ls_out in res.replace('\r\n', '\n') - # semi-fake test which gets coverage: + +# WARNING: this should be the last test as it messes with sys.stdin, argv +@with_setup(pretest, posttest) +def test_main(): + """Test misc CLI options""" _SYS = sys.stdin, sys.argv + N = 123 + + # test direct import + sys.stdin = map(str, _range(N)) + sys.argv = ['', '--desc', 'Test CLI import', + '--ascii', 'True', '--unit_scale', 'True'] + import tqdm.__main__ # NOQA + sys.stderr.write("Test misc CLI options ... ") + # test --delim + IN_DATA = '\0'.join(map(str, _range(N))) with closing(StringIO()) as sys.stdin: - sys.argv = ['', '--desc', 'Test CLI --delim', + sys.argv = ['', '--desc', 'Test CLI delim', '--ascii', 'True', '--delim', r'\0', '--buf_size', '64'] - sys.stdin.write('\0'.join(map(str, _range(int(123))))) + sys.stdin.write(IN_DATA) # sys.stdin.write(b'\xff') # TODO sys.stdin.seek(0) - main() - sys.stdin = IN_DATA_LIST - - sys.argv = ['', '--desc', 'Test CLI pipes', - '--ascii', 'True', '--unit_scale', 'True'] - import tqdm.__main__ # NOQA + with closing(UnicodeIO()) as fp: + main(fp=fp) + assert str(N) + "it" in fp.getvalue() + # test --bytes + IN_DATA = IN_DATA.replace('\0', '\n') with closing(StringIO()) as sys.stdin: - IN_DATA = '\0'.join(IN_DATA_LIST) sys.stdin.write(IN_DATA) sys.stdin.seek(0) sys.argv = ['', '--ascii', '--bytes=True', '--unit_scale', 'False'] with closing(UnicodeIO()) as fp: main(fp=fp) assert str(len(IN_DATA)) in fp.getvalue() - sys.stdin = IN_DATA_LIST # test --log + sys.stdin = map(str, _range(N)) + # with closing(UnicodeIO()) as fp: + main(argv=['--log', 'DEBUG'], fp=NULL) + # assert "DEBUG:" in sys.stdout.getvalue() + + # test --tee + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--mininterval', '0', '--miniters', '1'], fp=fp) + res = len(fp.getvalue()) + # assert len(fp.getvalue()) < len(sys.stdout.getvalue()) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--tee', '--mininterval', '0', '--miniters', '1'], fp=fp) + # spaces to clear intermediate lines could increase length + assert len(fp.getvalue()) >= res + len(IN_DATA) + + # test --null + _STDOUT = sys.stdout + try: + with closing(StringIO()) as sys.stdout: + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--null'], fp=fp) + assert not sys.stdout.getvalue() + + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=[], fp=fp) + assert sys.stdout.getvalue() + except: + sys.stdout = _STDOUT + raise + else: + sys.stdout = _STDOUT + + # test integer --update with closing(StringIO()) as sys.stdin: - sys.stdin.write('\0'.join(map(str, _range(int(123))))) + sys.stdin.write(IN_DATA) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--update'], fp=fp) + res = fp.getvalue() + assert str(N // 2 * N) + "it" in res # arithmetic sum formula + + # test integer --update --delim + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA.replace('\n', 'D')) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--update', '--delim', 'D'], fp=fp) + res = fp.getvalue() + assert str(N // 2 * N) + "it" in res # arithmetic sum formula + + # test integer --update_to + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA) + sys.stdin.seek(0) - # with closing(UnicodeIO()) as fp: - main(argv=['--log', 'DEBUG'], fp=NULL) - # assert "DEBUG:" in sys.stdout.getvalue() - sys.stdin = IN_DATA_LIST + with closing(UnicodeIO()) as fp: + main(argv=['--update-to'], fp=fp) + res = fp.getvalue() + assert str(N - 1) + "it" in res + assert str(N) + "it" not in res + + # test integer --update_to --delim + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA.replace('\n', 'D')) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--update-to', '--delim', 'D'], fp=fp) + res = fp.getvalue() + assert str(N - 1) + "it" in res + assert str(N) + "it" not in res + + # test float --update_to + IN_DATA = '\n'.join((str(i / 2.0) for i in _range(N))) + with closing(StringIO()) as sys.stdin: + sys.stdin.write(IN_DATA) + + sys.stdin.seek(0) + with closing(UnicodeIO()) as fp: + main(argv=['--update-to'], fp=fp) + res = fp.getvalue() + assert str((N - 1) / 2.0) + "it" in res + assert str(N / 2.0) + "it" not in res # clean up sys.stdin, sys.argv = _SYS @@ -129,7 +230,7 @@ def test_comppath(): def test_exceptions(): """Test CLI Exceptions""" _SYS = sys.stdin, sys.argv - sys.stdin = IN_DATA_LIST + sys.stdin = map(str, _range(123)) sys.argv = ['', '-ascii', '-unit_scale', '--bad_arg_u_ment', 'foo'] try: @@ -158,6 +259,15 @@ def test_exceptions(): else: raise TqdmTypeError('invalid_int_value') + sys.argv = ['', '--update', '--update_to'] + try: + main(fp=NULL) + except TqdmKeyError as e: + if 'Can only have one of --' not in str(e): + raise + else: + raise TqdmKeyError('Cannot have both --update --update_to') + # test SystemExits for i in ('-h', '--help', '-v', '--version'): sys.argv = ['', i] diff --git a/tqdm/tests/tests_tqdm.py b/tqdm/tests/tests_tqdm.py index 8a6aee080..b3955d393 100644 --- a/tqdm/tests/tests_tqdm.py +++ b/tqdm/tests/tests_tqdm.py @@ -1975,3 +1975,15 @@ def test_screen_shape(): res = our_file.getvalue() assert "two" in res + + +@with_setup(pretest, posttest) +def test_initial(): + """Test `initial`""" + with closing(StringIO()) as our_file: + for _ in tqdm(_range(9), initial=10, total=19, file=our_file, + miniters=1, mininterval=0): + pass + out = our_file.getvalue() + assert '10/19' in out + assert '19/19' in out diff --git a/tqdm/tqdm.1 b/tqdm/tqdm.1 index f0c692452..e8dadade2 100644 --- a/tqdm/tqdm.1 +++ b/tqdm/tqdm.1 @@ -36,12 +36,12 @@ counting:\ 100%|█████████|\ 432/432\ [00:00<00:00,\ 794361.83f .SH OPTIONS .TP .B \-h, \-\-help -Print this help and exit +Print this help and exit. .RS .RE .TP .B \-v, \-\-version -Print version and exit +Print version and exit. .RS .RE .TP @@ -63,7 +63,7 @@ specify an initial arbitrary large positive number, e.g. .RS .RE .TP -.B \-\-leave=\f[I]leave\f[] +.B \-\-leave bool, optional. If [default: True], keeps all traces of the progressbar upon termination of iteration. @@ -117,7 +117,7 @@ The fallback is to use ASCII characters " 123456789#". .RS .RE .TP -.B \-\-disable=\f[I]disable\f[] +.B \-\-disable bool, optional. Whether to disable the entire progressbar wrapper [default: False]. If set to None, disable on non\-TTY. @@ -131,7 +131,7 @@ it]. .RS .RE .TP -.B \-\-unit_scale=\f[I]unit_scale\f[] +.B \-\-unit\-scale=\f[I]unit_scale\f[] bool or int or float, optional. If 1 or True, the number of iterations will be reduced/scaled automatically and a metric prefix following the International System of @@ -140,7 +140,7 @@ If any other non\-zero number, will scale \f[C]total\f[] and \f[C]n\f[]. .RS .RE .TP -.B \-\-dynamic_ncols=\f[I]dynamic_ncols\f[] +.B \-\-dynamic\-ncols bool, optional. If set, constantly alters \f[C]ncols\f[] and \f[C]nrows\f[] to the environment (allowing for window resizes) [default: False]. @@ -156,7 +156,7 @@ Ranges from 0 (average speed) to 1 (current/instantaneous speed) .RS .RE .TP -.B \-\-bar_format=\f[I]bar_format\f[] +.B \-\-bar\-format=\f[I]bar_format\f[] str, optional. Specify a custom bar string formatting. May impact performance. @@ -196,13 +196,13 @@ Calls \f[C]set_postfix(**postfix)\f[] if possible (dict). .RS .RE .TP -.B \-\-unit_divisor=\f[I]unit_divisor\f[] +.B \-\-unit\-divisor=\f[I]unit_divisor\f[] float, optional. [default: 1000], ignored unless \f[C]unit_scale\f[] is True. .RS .RE .TP -.B \-\-write_bytes=\f[I]write_bytes\f[] +.B \-\-write\-bytes bool, optional. If (default: None) and \f[C]file\f[] is unspecified, bytes will be written in Python 2. @@ -211,7 +211,7 @@ In all other cases will default to unicode. .RS .RE .TP -.B \-\-lock_args=\f[I]lock_args\f[] +.B \-\-lock\-args=\f[I]lock_args\f[] tuple, optional. Passed to \f[C]refresh\f[] for intermediate output (initialisation, iterating, and updating). @@ -236,14 +236,14 @@ N.B.: on Windows systems, Python converts \[aq]\\n\[aq] to .RS .RE .TP -.B \-\-buf_size=\f[I]buf_size\f[] +.B \-\-buf\-size=\f[I]buf_size\f[] int, optional. String buffer size in bytes [default: 256] used when \f[C]delim\f[] is specified. .RS .RE .TP -.B \-\-bytes=\f[I]bytes\f[] +.B \-\-bytes bool, optional. If true, will count bytes, ignore \f[C]delim\f[], and default \f[C]unit_scale\f[] to True, \f[C]unit_divisor\f[] to 1024, and @@ -251,6 +251,33 @@ If true, will count bytes, ignore \f[C]delim\f[], and default .RS .RE .TP +.B \-\-tee +bool, optional. +If true, passes \f[C]stdin\f[] to both \f[C]stderr\f[] and +\f[C]stdout\f[]. +.RS +.RE +.TP +.B \-\-update +bool, optional. +If true, will treat input as newly elapsed iterations, i.e. +numbers to pass to \f[C]update()\f[]. +.RS +.RE +.TP +.B \-\-update\-to +bool, optional. +If true, will treat input as total elapsed iterations, i.e. +numbers to assign to \f[C]self.n\f[]. +.RS +.RE +.TP +.B \-\-null +bool, optional. +If true, will discard input (no stdout). +.RS +.RE +.TP .B \-\-manpath=\f[I]manpath\f[] str, optional. Directory in which to install tqdm man pages.