diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 815b0bd34..2236d7703 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -16,22 +16,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - uses: actions/setup-python@v4 + with: + python-version: '3.x' - run: pip install -U tox - run: tox env: TOXENV: ${{ matrix.TOXENV }} asvfull: - if: (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) || github.event_name == 'schedule' + if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || github.event_name == 'schedule' name: Benchmark (Full) runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 + token: ${{ secrets.GH_TOKEN || github.token }} - uses: actions/setup-python@v4 + with: + python-version: '3.x' - name: Install run: | pip install -U wheel @@ -58,7 +61,7 @@ jobs: GIT_AUTHOR_NAME: ${{ github.actor }} GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com testasv: - if: github.event.ref != 'refs/heads/master' && ! startsWith(github.event.ref, 'refs/tags') + if: github.ref != 'refs/heads/master' && ! startsWith(github.ref, 'refs/tags') name: Benchmark (Branch) runs-on: ubuntu-latest steps: @@ -66,6 +69,8 @@ jobs: with: fetch-depth: 0 - uses: actions/setup-python@v4 + with: + python-version: '3.x' - name: Install run: | pip install -U wheel @@ -82,6 +87,6 @@ jobs: - name: Benchmark run: | asv continuous --interleave-processes --only-changed -f 1.25 master HEAD - CHANGES="$(asv compare --only-changed -f 1.25 master HEAD)" - echo "$CHANGES" - [ -z "$CHANGES" ] || exit 1 + CHANGES=$(asv compare --only-changed -f 1.25 master HEAD) + echo "$CHANGES" >> "$GITHUB_STEP_SUMMARY" + test -z "$CHANGES" || exit 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9aae1d14a..56b122907 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -153,7 +153,7 @@ jobs: tag="${GITHUB_REF#refs/tags/}" gh release create --title "tqdm $tag stable" --draft --notes "$changelog" "$tag" dist/${{ steps.dist.outputs.whl }} dist/${{ steps.dist.outputs.whl_asc }} env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} - uses: snapcore/action-build@v1 id: snap_build - if: github.event_name == 'push' && steps.collect_assets.outputs.snap_channel diff --git a/.meta/.readme.rst b/.meta/.readme.rst index 1dc2bbb92..4e325bcd2 100644 --- a/.meta/.readme.rst +++ b/.meta/.readme.rst @@ -255,7 +255,7 @@ This can be beautified further: .. code:: sh - $ BYTES="$(du -sb docs/ | cut -f1)" + $ BYTES=$(du -sb docs/ | cut -f1) $ tar -cf - docs/ \ | tqdm --bytes --total "$BYTES" --desc Processing | gzip \ | tqdm --bytes --total "$BYTES" --desc Compressed --position 1 \ @@ -332,6 +332,10 @@ of a neat one-line progress bar. buffering. - `No intermediate output in docker-compose `__: use ``docker-compose run`` instead of ``docker-compose up`` and ``tty: true``. +- Overriding defaults via environment variables: + e.g. in CI jobs, ``export TQDM_MININTERVAL=5`` to avoid log spam. + This override logic is handled by the ``tqdm.utils.envwrap`` decorator + (useful independent of ``tqdm``). If you come across any other difficulties, browse and file |GitHub-Issues|. @@ -345,12 +349,14 @@ Documentation class tqdm(): """{DOC_tqdm}""" + @envwrap("TQDM_", is_method=True) # override defaults via env vars def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, ncols=None, mininterval=0.1, maxinterval=10.0, miniters=None, ascii=None, disable=False, unit='it', unit_scale=False, dynamic_ncols=False, smoothing=0.3, bar_format=None, initial=0, position=None, - postfix=None, unit_divisor=1000): + postfix=None, unit_divisor=1000, write_bytes=False, + lock_args=None, nrows=None, colour=None, delay=0): Parameters ~~~~~~~~~~ @@ -1183,16 +1189,17 @@ are: ==================== ======================================================== ==== ================================ Name ID SLoC Notes ==================== ======================================================== ==== ================================ -Casper da Costa-Luis `casperdcl `__ ~78% primary maintainer |Gift-Casper| -Stephen Larroque `lrq3000 `__ ~10% team member -Martin Zugnoni `martinzugnoni `__ ~4% +Casper da Costa-Luis `casperdcl `__ ~80% primary maintainer |Gift-Casper| +Stephen Larroque `lrq3000 `__ ~9% team member +Martin Zugnoni `martinzugnoni `__ ~3% Daniel Ecer `de-code `__ ~2% Richard Sheridan `richardsheridan `__ ~1% Guangshuo Chen `chengs `__ ~1% +Helio Machado `0x2b3bfa0 `__ ~1% Kyle Altendorf `altendky `__ <1% +Noam Yorav-Raphael `noamraph `__ <1% original author Matthew Stevens `mjstevens777 `__ <1% Hadrien Mary `hadim `__ <1% team member -Noam Yorav-Raphael `noamraph `__ <1% original author Mikhail Korobov `kmike `__ <1% team member ==================== ======================================================== ==== ================================ @@ -1212,13 +1219,13 @@ Citation information: |DOI| |README-Hits| (Since 19 May 2016) -.. |Logo| image:: https://img.tqdm.ml/logo.gif -.. |Screenshot| image:: https://img.tqdm.ml/tqdm.gif -.. |Video| image:: https://img.tqdm.ml/video.jpg +.. |Logo| image:: https://tqdm.github.io/img/logo.gif +.. |Screenshot| image:: https://tqdm.github.io/img/tqdm.gif +.. |Video| image:: https://tqdm.github.io/img/video.jpg :target: https://tqdm.github.io/video -.. |Slides| image:: https://img.tqdm.ml/slides.jpg +.. |Slides| image:: https://tqdm.github.io/img/slides.jpg :target: https://tqdm.github.io/PyData2019/slides.html -.. |Merch| image:: https://img.tqdm.ml/merch.jpg +.. |Merch| image:: https://tqdm.github.io/img/merch.jpg :target: https://tqdm.github.io/merch .. |Build-Status| image:: https://img.shields.io/github/actions/workflow/status/tqdm/tqdm/test.yml?branch=master&label=tqdm&logo=GitHub :target: https://github.com/tqdm/tqdm/actions/workflows/test.yml @@ -1274,8 +1281,8 @@ Citation information: |DOI| :target: https://doi.org/10.5281/zenodo.595120 .. |binder-demo| image:: https://mybinder.org/badge_logo.svg :target: https://mybinder.org/v2/gh/tqdm/tqdm/master?filepath=DEMO.ipynb -.. |Screenshot-Jupyter1| image:: https://img.tqdm.ml/jupyter-1.gif -.. |Screenshot-Jupyter2| image:: https://img.tqdm.ml/jupyter-2.gif -.. |Screenshot-Jupyter3| image:: https://img.tqdm.ml/jupyter-3.gif -.. |README-Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif - :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif&style=social +.. |Screenshot-Jupyter1| image:: https://tqdm.github.io/img/jupyter-1.gif +.. |Screenshot-Jupyter2| image:: https://tqdm.github.io/img/jupyter-2.gif +.. |Screenshot-Jupyter3| image:: https://tqdm.github.io/img/jupyter-3.gif +.. |README-Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif + :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif&style=social diff --git a/.meta/mkcompletion.py b/.meta/mkcompletion.py index 387bd3075..130c2f803 100644 --- a/.meta/mkcompletion.py +++ b/.meta/mkcompletion.py @@ -3,10 +3,9 @@ """ import re import sys -from io import open as io_open -from os import path +from pathlib import Path -sys.path.insert(0, path.dirname(path.dirname(__file__))) +sys.path.insert(0, str(Path(__file__).parent.parent)) import tqdm # NOQA import tqdm.cli # NOQA @@ -27,13 +26,12 @@ def doc2opt(doc, user_input=True): # CLI options options = {'-h', '--help', '-v', '--version'} options_input = set() -for doc in (tqdm.tqdm.__init__.__doc__, tqdm.cli.CLI_EXTRA_DOC): +for doc in (tqdm.tqdm.__doc__, tqdm.cli.CLI_EXTRA_DOC): options.update(doc2opt(doc, user_input=False)) options_input.update(doc2opt(doc, user_input=True)) options.difference_update('--' + i for i in ('name',) + tqdm.cli.UNSUPPORTED_OPTS) options_input &= options options_input -= {"--log"} # manually dealt with -src_dir = path.abspath(path.dirname(__file__)) completion = u"""\ #!/usr/bin/env bash _tqdm(){{ @@ -58,6 +56,5 @@ def doc2opt(doc, user_input=True): """.format(opts=' '.join(sorted(options)), opts_manual='|'.join(sorted(options_input))) if __name__ == "__main__": - fncompletion = path.join(path.dirname(src_dir), 'tqdm', 'completion.sh') - with io_open(fncompletion, mode='w', encoding='utf-8') as fd: - fd.write(completion) + (Path(__file__).resolve().parent.parent / 'tqdm' / 'completion.sh').write_text( + completion, encoding='utf-8') diff --git a/.meta/mkdocs.py b/.meta/mkdocs.py index 6fdc0bad0..4c0d6ba20 100644 --- a/.meta/mkdocs.py +++ b/.meta/mkdocs.py @@ -2,11 +2,10 @@ Auto-generate README.rst from .meta/.readme.rst and docstrings. """ import sys -from io import open as io_open -from os import path +from pathlib import Path from textwrap import dedent -sys.path.insert(0, path.dirname(path.dirname(__file__))) +sys.path.insert(0, str(Path(__file__).parent.parent)) import tqdm # NOQA import tqdm.cli # NOQA @@ -42,13 +41,13 @@ def doc2rst(doc, arglist=True, raw=False): return doc -src_dir = path.abspath(path.dirname(__file__)) -README_rst = path.join(src_dir, '.readme.rst') -with io_open(README_rst, mode='r', encoding='utf-8') as fd: - README_rst = fd.read() -DOC_tqdm = doc2rst(tqdm.tqdm.__doc__, False).replace('\n', '\n ') -DOC_tqdm_init = doc2rst(tqdm.tqdm.__init__.__doc__) -DOC_tqdm_init_args = DOC_tqdm_init.partition(doc2rst(HEAD_ARGS))[-1].replace('\n ', '\n ') +src_dir = Path(__file__).parent.resolve() +README_rst = (src_dir / '.readme.rst').read_text("utf-8") +class_doc, init_doc = tqdm.tqdm.__doc__.split('\n\n', 1) +DOC_tqdm = doc2rst(class_doc + '\n', False).replace('\n', '\n ') +DOC_tqdm_init = doc2rst('\n' + init_doc) +DOC_tqdm_init_args = DOC_tqdm_init.partition(doc2rst(HEAD_ARGS))[-1].replace( + '\n ', '\n ').replace('\n ', '\n ') DOC_tqdm_init_args, _, DOC_tqdm_init_rets = DOC_tqdm_init_args.partition(doc2rst(HEAD_RETS)) DOC_cli = doc2rst(tqdm.cli.CLI_EXTRA_DOC).partition(doc2rst(HEAD_CLI))[-1] DOC_tqdm_tqdm = {} @@ -70,6 +69,4 @@ def doc2rst(doc, arglist=True, raw=False): README_rst = README_rst.replace('{DOC_tqdm.tqdm.%s}' % k, v) if __name__ == "__main__": - fndoc = path.join(path.dirname(src_dir), 'README.rst') - with io_open(fndoc, mode='w', encoding='utf-8') as fd: - fd.write(README_rst) + (src_dir.parent / 'README.rst').write_text(README_rst, encoding='utf-8') diff --git a/.meta/mksnap.py b/.meta/mksnap.py index 5a65d3b7a..29f15acd7 100644 --- a/.meta/mksnap.py +++ b/.meta/mksnap.py @@ -3,14 +3,12 @@ Auto-generate snapcraft.yaml. """ import sys -from io import open as io_open -from os import path +from pathlib import Path from subprocess import check_output # nosec -sys.path.insert(1, path.dirname(path.dirname(__file__))) +sys.path.insert(1, str(Path(__file__).parent.parent)) import tqdm # NOQA -src_dir = path.abspath(path.dirname(__file__)) snap_yml = r"""name: tqdm summary: A fast, extensible CLI progress bar description: | @@ -65,9 +63,8 @@ command: bin/tqdm completer: completion.sh """.format(version=tqdm.__version__, commit=check_output([ - 'git', 'describe', '--always']).decode('U8').strip()) # nosec -fname = path.join(path.dirname(src_dir), 'snapcraft.yaml') + 'git', 'describe', '--always']).decode('utf-8').strip()) # nosec if __name__ == "__main__": - with io_open(fname, mode='w', encoding='utf-8') as fd: - fd.write(snap_yml.decode('U8') if hasattr(snap_yml, 'decode') else snap_yml) + (Path(__file__).resolve().parent.parent / 'snapcraft.yaml').write_text( + snap_yml.decode('utf-8') if hasattr(snap_yml, 'decode') else snap_yml, encoding='utf-8') diff --git a/DEMO.ipynb b/DEMO.ipynb index 3dabe4507..8048cbc49 100644 --- a/DEMO.ipynb +++ b/DEMO.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "

tqdm

\n", - "\n", + "\n", "\n", "[![Py-Versions](https://img.shields.io/pypi/pyversions/tqdm.svg?logo=python&logoColor=white)](https://pypi.org/project/tqdm)|[![Versions](https://img.shields.io/pypi/v/tqdm.svg)](https://tqdm.github.io/releases)|[![Conda-Forge-Status](https://img.shields.io/conda/v/conda-forge/tqdm.svg?label=conda-forge&logo=conda-forge)](https://anaconda.org/conda-forge/tqdm)|[![Docker](https://img.shields.io/badge/docker-pull-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/tqdm/tqdm)|[![Snapcraft](https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft)](https://snapcraft.io/tqdm)\n", "-|-|-|-|-\n", @@ -58,7 +58,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "![Screenshot](https://img.tqdm.ml/tqdm.gif)|[![Video](https://img.tqdm.ml/video.jpg)](https://tqdm.github.io/video) [![Slides](https://img.tqdm.ml/slides.jpg)](https://tqdm.github.io/PyData2019/slides.html) [![Merch](https://img.tqdm.ml/merch.jpg)](https://tqdm.github.io/merch)\n", + "![Screenshot](https://tqdm.github.io/img/tqdm.gif)|[![Video](https://tqdm.github.io/img/video.jpg)](https://tqdm.github.io/video) [![Slides](https://tqdm.github.io/img/slides.jpg)](https://tqdm.github.io/PyData2019/slides.html) [![Merch](https://tqdm.github.io/img/merch.jpg)](https://tqdm.github.io/merch)\n", "-|-\n", "\n", "It can also be executed as a module with pipes:" @@ -737,7 +737,7 @@ "bars and colour hints (blue: normal, green: completed, red:\n", "error/interrupt, light blue: no ETA); as demonstrated below.\n", "\n", - "![Screenshot-Jupyter3](https://img.tqdm.ml/jupyter-3.gif)\n", + "![Screenshot-Jupyter3](https://tqdm.github.io/img/jupyter-3.gif)\n", "\n", "The `notebook` version supports percentage or pixels for overall width\n", "(e.g.: `ncols='100%'` or `ncols='480px'`).\n", @@ -843,7 +843,7 @@ "specify any file-like object using the `file` argument. For example,\n", "this can be used to redirect the messages writing to a log file or class.\n", "\n", - "[![README-Hits](https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif)](https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif&style=social)|(Since 19 May 2016)\n", + "[![README-Hits](https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif)](https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif&style=social)|(Since 19 May 2016)\n", "-|-" ] }, diff --git a/README.rst b/README.rst index d5588e917..defa9ad35 100644 --- a/README.rst +++ b/README.rst @@ -255,7 +255,7 @@ This can be beautified further: .. code:: sh - $ BYTES="$(du -sb docs/ | cut -f1)" + $ BYTES=$(du -sb docs/ | cut -f1) $ tar -cf - docs/ \ | tqdm --bytes --total "$BYTES" --desc Processing | gzip \ | tqdm --bytes --total "$BYTES" --desc Compressed --position 1 \ @@ -332,6 +332,10 @@ of a neat one-line progress bar. buffering. - `No intermediate output in docker-compose `__: use ``docker-compose run`` instead of ``docker-compose up`` and ``tty: true``. +- Overriding defaults via environment variables: + e.g. in CI jobs, ``export TQDM_MININTERVAL=5`` to avoid log spam. + This override logic is handled by the ``tqdm.utils.envwrap`` decorator + (useful independent of ``tqdm``). If you come across any other difficulties, browse and file |GitHub-Issues|. @@ -349,12 +353,14 @@ Documentation progressbar every time a value is requested. """ + @envwrap("TQDM_", is_method=True) # override defaults via env vars def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, ncols=None, mininterval=0.1, maxinterval=10.0, miniters=None, ascii=None, disable=False, unit='it', unit_scale=False, dynamic_ncols=False, smoothing=0.3, bar_format=None, initial=0, position=None, - postfix=None, unit_divisor=1000): + postfix=None, unit_divisor=1000, write_bytes=False, + lock_args=None, nrows=None, colour=None, delay=0): Parameters ~~~~~~~~~~ @@ -1400,16 +1406,17 @@ are: ==================== ======================================================== ==== ================================ Name ID SLoC Notes ==================== ======================================================== ==== ================================ -Casper da Costa-Luis `casperdcl `__ ~78% primary maintainer |Gift-Casper| -Stephen Larroque `lrq3000 `__ ~10% team member -Martin Zugnoni `martinzugnoni `__ ~4% +Casper da Costa-Luis `casperdcl `__ ~80% primary maintainer |Gift-Casper| +Stephen Larroque `lrq3000 `__ ~9% team member +Martin Zugnoni `martinzugnoni `__ ~3% Daniel Ecer `de-code `__ ~2% Richard Sheridan `richardsheridan `__ ~1% Guangshuo Chen `chengs `__ ~1% +Helio Machado `0x2b3bfa0 `__ ~1% Kyle Altendorf `altendky `__ <1% +Noam Yorav-Raphael `noamraph `__ <1% original author Matthew Stevens `mjstevens777 `__ <1% Hadrien Mary `hadim `__ <1% team member -Noam Yorav-Raphael `noamraph `__ <1% original author Mikhail Korobov `kmike `__ <1% team member ==================== ======================================================== ==== ================================ @@ -1429,13 +1436,13 @@ Citation information: |DOI| |README-Hits| (Since 19 May 2016) -.. |Logo| image:: https://img.tqdm.ml/logo.gif -.. |Screenshot| image:: https://img.tqdm.ml/tqdm.gif -.. |Video| image:: https://img.tqdm.ml/video.jpg +.. |Logo| image:: https://tqdm.github.io/img/logo.gif +.. |Screenshot| image:: https://tqdm.github.io/img/tqdm.gif +.. |Video| image:: https://tqdm.github.io/img/video.jpg :target: https://tqdm.github.io/video -.. |Slides| image:: https://img.tqdm.ml/slides.jpg +.. |Slides| image:: https://tqdm.github.io/img/slides.jpg :target: https://tqdm.github.io/PyData2019/slides.html -.. |Merch| image:: https://img.tqdm.ml/merch.jpg +.. |Merch| image:: https://tqdm.github.io/img/merch.jpg :target: https://tqdm.github.io/merch .. |Build-Status| image:: https://img.shields.io/github/actions/workflow/status/tqdm/tqdm/test.yml?branch=master&label=tqdm&logo=GitHub :target: https://github.com/tqdm/tqdm/actions/workflows/test.yml @@ -1491,8 +1498,8 @@ Citation information: |DOI| :target: https://doi.org/10.5281/zenodo.595120 .. |binder-demo| image:: https://mybinder.org/badge_logo.svg :target: https://mybinder.org/v2/gh/tqdm/tqdm/master?filepath=DEMO.ipynb -.. |Screenshot-Jupyter1| image:: https://img.tqdm.ml/jupyter-1.gif -.. |Screenshot-Jupyter2| image:: https://img.tqdm.ml/jupyter-2.gif -.. |Screenshot-Jupyter3| image:: https://img.tqdm.ml/jupyter-3.gif -.. |README-Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif - :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif&style=social +.. |Screenshot-Jupyter1| image:: https://tqdm.github.io/img/jupyter-1.gif +.. |Screenshot-Jupyter2| image:: https://tqdm.github.io/img/jupyter-2.gif +.. |Screenshot-Jupyter3| image:: https://tqdm.github.io/img/jupyter-3.gif +.. |README-Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif + :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://tqdm.github.io/img/favicon.png&f=https://tqdm.github.io/img/logo.gif&style=social diff --git a/tests/tests_utils.py b/tests/tests_utils.py new file mode 100644 index 000000000..5fb20dcea --- /dev/null +++ b/tests/tests_utils.py @@ -0,0 +1,41 @@ +from pytest import mark + +from tqdm.utils import IS_WIN, envwrap + + +def test_envwrap(monkeypatch): + """Test envwrap overrides""" + env_a = 42 + env_c = 1337 + monkeypatch.setenv('FUNC_A', str(env_a)) + monkeypatch.setenv('FUNC_TyPe_HiNt', str(env_c)) + monkeypatch.setenv('FUNC_Unused', "x") + + @envwrap("FUNC_") + def func(a=1, b=2, type_hint: int = None): + return a, b, type_hint + + assert (env_a, 2, 1337) == func(), "expected env override" + assert (99, 2, 1337) == func(a=99), "expected manual override" + + env_literal = 3.14159 + monkeypatch.setenv('FUNC_literal', str(env_literal)) + + @envwrap("FUNC_", literal_eval=True) + def another_func(literal="some_string"): + return literal + + assert env_literal == another_func() + + +@mark.skipif(IS_WIN, reason="no lowercase environ on Windows") +def test_envwrap_case(monkeypatch): + """Test envwrap case-sensitive overrides""" + env_liTeRaL = 3.14159 + monkeypatch.setenv('FUNC_liTeRaL', str(env_liTeRaL)) + + @envwrap("FUNC_", literal_eval=True, case_sensitive=True) + def func(liTeRaL="some_string"): + return liTeRaL + + assert env_liTeRaL == func() diff --git a/tqdm/cli.py b/tqdm/cli.py index 444262831..1223d4977 100644 --- a/tqdm/cli.py +++ b/tqdm/cli.py @@ -98,7 +98,7 @@ def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, # ((opt, type), ... ) -RE_OPTS = re.compile(r'\n {8}(\S+)\s{2,}:\s*([^,]+)') +RE_OPTS = re.compile(r'\n {4}(\S+)\s{2,}:\s*([^,]+)') # better split method assuming no positional args RE_SHLEX = re.compile(r'\s*(?>> for i in trange(10, token='{token}', channel_id='{channel_id}'): ... ... -![screenshot](https://img.tqdm.ml/screenshot-discord.png) +![screenshot](https://tqdm.github.io/img/screenshot-discord.png) """ import logging from os import getenv diff --git a/tqdm/contrib/slack.py b/tqdm/contrib/slack.py index c8fe713a7..d4c850ca4 100644 --- a/tqdm/contrib/slack.py +++ b/tqdm/contrib/slack.py @@ -6,7 +6,7 @@ >>> for i in trange(10, token='{token}', channel='{channel}'): ... ... -![screenshot](https://img.tqdm.ml/screenshot-slack.png) +![screenshot](https://tqdm.github.io/img/screenshot-slack.png) """ import logging from os import getenv diff --git a/tqdm/contrib/telegram.py b/tqdm/contrib/telegram.py index b9a589b59..cbeadf20f 100644 --- a/tqdm/contrib/telegram.py +++ b/tqdm/contrib/telegram.py @@ -6,7 +6,7 @@ >>> for i in trange(10, token='{token}', chat_id='{chat_id}'): ... ... -![screenshot](https://img.tqdm.ml/screenshot-telegram.gif) +![screenshot](https://tqdm.github.io/img/screenshot-telegram.gif) """ from os import getenv from warnings import warn diff --git a/tqdm/std.py b/tqdm/std.py index 1163137e9..218a561f6 100644 --- a/tqdm/std.py +++ b/tqdm/std.py @@ -19,7 +19,8 @@ from ._monitor import TMonitor from .utils import ( CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace, SimpleTextIOWrapper, - _is_ascii, _screen_shape_wrapper, _supports_unicode, _term_move_up, disp_len, disp_trim) + _is_ascii, _screen_shape_wrapper, _supports_unicode, _term_move_up, disp_len, disp_trim, + envwrap) __author__ = "https://github.com/tqdm/tqdm#contributions" __all__ = ['tqdm', 'trange', @@ -246,6 +247,120 @@ class tqdm(Comparable): Decorate an iterable object, returning an iterator which acts exactly like the original iterable, but prints a dynamically updating progressbar every time a value is requested. + + Parameters + ---------- + iterable : iterable, optional + Iterable to decorate with a progressbar. + Leave blank to manually manage the updates. + desc : str, optional + Prefix for the progressbar. + total : int or float, optional + The number of expected iterations. If unspecified, + len(iterable) is used if possible. If float("inf") or as a last + resort, only basic progress statistics are displayed + (no ETA, no progressbar). + If `gui` is True and this parameter needs subsequent updating, + specify an initial arbitrary large positive number, + e.g. 9e9. + leave : bool, optional + If [default: True], keeps all traces of the progressbar + upon termination of iteration. + If `None`, will leave only if `position` is `0`. + file : `io.TextIOWrapper` or `io.StringIO`, optional + Specifies where to output the progress messages + (default: sys.stderr). Uses `file.write(str)` and `file.flush()` + methods. For encoding, see `write_bytes`. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes the progressbar to stay within this bound. + If unspecified, attempts to use environment width. The + fallback is a meter width of 10 and no limit for the counter and + statistics. If 0, will not print any meter (only stats). + mininterval : float, optional + Minimum progress display update interval [default: 0.1] seconds. + maxinterval : float, optional + Maximum progress display update interval [default: 10] seconds. + Automatically adjusts `miniters` to correspond to `mininterval` + after long display update lag. Only works if `dynamic_miniters` + or monitor thread is enabled. + miniters : int or float, optional + Minimum progress display update interval, in iterations. + If 0 and `dynamic_miniters`, will automatically adjust to equal + `mininterval` (more CPU efficient, good for tight loops). + If > 0, will skip display of specified number of iterations. + Tweak this and `mininterval` to get very efficient loops. + If your progress is erratic with both fast and slow iterations + (network, skipping items, etc) you should set miniters=1. + ascii : bool or str, optional + If unspecified or False, use unicode (smooth blocks) to fill + the meter. The fallback is to use ASCII characters " 123456789#". + disable : bool, optional + Whether to disable the entire progressbar wrapper + [default: False]. If set to None, disable on non-TTY. + unit : str, optional + String that will be used to define the unit of each iteration + [default: it]. + unit_scale : 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 Units standard will be added + (kilo, mega, etc.) [default: False]. If any other non-zero + number, will scale `total` and `n`. + dynamic_ncols : bool, optional + If set, constantly alters `ncols` and `nrows` to the + environment (allowing for window resizes) [default: False]. + smoothing : float, optional + Exponential moving average smoothing factor for speed estimates + (ignored in GUI mode). Ranges from 0 (average speed) to 1 + (current/instantaneous speed) [default: 0.3]. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + initial : int or float, optional + The initial counter value. Useful when restarting a progress + bar [default: 0]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + position : int, optional + Specify the line offset to print this bar (starting from 0) + Automatic if unspecified. + Useful to manage multiple bars at once (eg, from threads). + postfix : dict or *, optional + Specify additional stats to display at the end of the bar. + Calls `set_postfix(**postfix)` if possible (dict). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + write_bytes : bool, optional + Whether to write bytes. If (default: False) will write unicode. + lock_args : tuple, optional + Passed to `refresh` for intermediate output + (initialisation, iterating, and updating). + nrows : int, optional + The screen height. If specified, hides nested bars outside this + bound. If unspecified, attempts to use environment height. + The fallback is 20. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + delay : float, optional + Don't display until [default: 0] seconds have elapsed. + gui : bool, optional + WARNING: internal parameter - do not use. + Use tqdm.gui.tqdm(...) instead. If set, will attempt to use + matplotlib animations for a graphical output [default: False]. + + Returns + ------- + out : decorated iterator. """ monitor_interval = 10 # set to 0 to disable the thread @@ -834,6 +949,7 @@ def wrapper(*args, **kwargs): elif _Rolling_and_Expanding is not None: _Rolling_and_Expanding.progress_apply = inner_generator() + @envwrap("TQDM_", is_method=True) # override defaults via env vars def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, ncols=None, mininterval=0.1, maxinterval=10.0, miniters=None, ascii=None, disable=False, unit='it', unit_scale=False, @@ -841,121 +957,7 @@ def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, position=None, postfix=None, unit_divisor=1000, write_bytes=False, lock_args=None, nrows=None, colour=None, delay=0, gui=False, **kwargs): - """ - Parameters - ---------- - iterable : iterable, optional - Iterable to decorate with a progressbar. - Leave blank to manually manage the updates. - desc : str, optional - Prefix for the progressbar. - total : int or float, optional - The number of expected iterations. If unspecified, - len(iterable) is used if possible. If float("inf") or as a last - resort, only basic progress statistics are displayed - (no ETA, no progressbar). - If `gui` is True and this parameter needs subsequent updating, - specify an initial arbitrary large positive number, - e.g. 9e9. - leave : bool, optional - If [default: True], keeps all traces of the progressbar - upon termination of iteration. - If `None`, will leave only if `position` is `0`. - file : `io.TextIOWrapper` or `io.StringIO`, optional - Specifies where to output the progress messages - (default: sys.stderr). Uses `file.write(str)` and `file.flush()` - methods. For encoding, see `write_bytes`. - ncols : int, optional - The width of the entire output message. If specified, - dynamically resizes the progressbar to stay within this bound. - If unspecified, attempts to use environment width. The - fallback is a meter width of 10 and no limit for the counter and - statistics. If 0, will not print any meter (only stats). - mininterval : float, optional - Minimum progress display update interval [default: 0.1] seconds. - maxinterval : float, optional - Maximum progress display update interval [default: 10] seconds. - Automatically adjusts `miniters` to correspond to `mininterval` - after long display update lag. Only works if `dynamic_miniters` - or monitor thread is enabled. - miniters : int or float, optional - Minimum progress display update interval, in iterations. - If 0 and `dynamic_miniters`, will automatically adjust to equal - `mininterval` (more CPU efficient, good for tight loops). - If > 0, will skip display of specified number of iterations. - Tweak this and `mininterval` to get very efficient loops. - If your progress is erratic with both fast and slow iterations - (network, skipping items, etc) you should set miniters=1. - ascii : bool or str, optional - If unspecified or False, use unicode (smooth blocks) to fill - the meter. The fallback is to use ASCII characters " 123456789#". - disable : bool, optional - Whether to disable the entire progressbar wrapper - [default: False]. If set to None, disable on non-TTY. - unit : str, optional - String that will be used to define the unit of each iteration - [default: it]. - unit_scale : 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 Units standard will be added - (kilo, mega, etc.) [default: False]. If any other non-zero - number, will scale `total` and `n`. - dynamic_ncols : bool, optional - If set, constantly alters `ncols` and `nrows` to the - environment (allowing for window resizes) [default: False]. - smoothing : float, optional - Exponential moving average smoothing factor for speed estimates - (ignored in GUI mode). Ranges from 0 (average speed) to 1 - (current/instantaneous speed) [default: 0.3]. - bar_format : str, optional - Specify a custom bar string formatting. May impact performance. - [default: '{l_bar}{bar}{r_bar}'], where - l_bar='{desc}: {percentage:3.0f}%|' and - r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' - '{rate_fmt}{postfix}]' - Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, - percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, - rate, rate_fmt, rate_noinv, rate_noinv_fmt, - rate_inv, rate_inv_fmt, postfix, unit_divisor, - remaining, remaining_s, eta. - Note that a trailing ": " is automatically removed after {desc} - if the latter is empty. - initial : int or float, optional - The initial counter value. Useful when restarting a progress - bar [default: 0]. If using float, consider specifying `{n:.3f}` - or similar in `bar_format`, or specifying `unit_scale`. - position : int, optional - Specify the line offset to print this bar (starting from 0) - Automatic if unspecified. - Useful to manage multiple bars at once (eg, from threads). - postfix : dict or *, optional - Specify additional stats to display at the end of the bar. - Calls `set_postfix(**postfix)` if possible (dict). - unit_divisor : float, optional - [default: 1000], ignored unless `unit_scale` is True. - write_bytes : bool, optional - Whether to write bytes. If (default: False) will write unicode. - lock_args : tuple, optional - Passed to `refresh` for intermediate output - (initialisation, iterating, and updating). - nrows : int, optional - The screen height. If specified, hides nested bars outside this - bound. If unspecified, attempts to use environment height. - The fallback is 20. - colour : str, optional - Bar colour (e.g. 'green', '#00ff00'). - delay : float, optional - Don't display until [default: 0] seconds have elapsed. - gui : bool, optional - WARNING: internal parameter - do not use. - Use tqdm.gui.tqdm(...) instead. If set, will attempt to use - matplotlib animations for a graphical output [default: False]. - - Returns - ------- - out : decorated iterator. - """ + """see tqdm.tqdm for arguments""" if file is None: file = sys.stderr diff --git a/tqdm/tqdm.1 b/tqdm/tqdm.1 index 0533198ca..b90ab4b9e 100644 --- a/tqdm/tqdm.1 +++ b/tqdm/tqdm.1 @@ -204,10 +204,8 @@ float, optional. .TP .B \-\-write\-bytes bool, optional. -If (default: None) and \f[C]file\f[] is unspecified, bytes will be -written in Python 2. -If \f[C]True\f[] will also write bytes. -In all other cases will default to unicode. +Whether to write bytes. +If (default: False) will write unicode. .RS .RE .TP diff --git a/tqdm/utils.py b/tqdm/utils.py index c5d3a6e32..249a73960 100644 --- a/tqdm/utils.py +++ b/tqdm/utils.py @@ -4,7 +4,9 @@ import os import re import sys -from functools import wraps +from ast import literal_eval as safe_eval +from functools import partial, partialmethod, wraps +from inspect import signature # TODO consider using wcswidth third-party package for 0-width characters from unicodedata import east_asian_width from warnings import warn @@ -30,6 +32,62 @@ colorama.init() +def envwrap(prefix, case_sensitive=False, literal_eval=False, is_method=False): + """ + Override parameter defaults via `os.environ[prefix + param_name]`. + Precedence (highest first): + - call (`foo(a=3)`) + - environ (`FOO_A=2`) + - signature (`def foo(a=1)`) + + Parameters + ---------- + prefix : str + Env var prefix, e.g. "FOO_" + case_sensitive : bool, optional + If (default: False), treat env var "FOO_Some_ARG" as "FOO_some_arg". + literal_eval : bool, optional + Whether to `ast.literal_eval` the detected env var overrides. + Otherwise if (default: False), infer types from function signature. + is_method : bool, optional + Whether to use `functools.partialmethod`. If (default: False) use `functools.partial`. + + Examples + -------- + ``` + $ cat foo.py + from tqdm.utils import envwrap + @envwrap("FOO_") + def test(a=1, b=2, c=3): + print(f"received: a={a}, b={b}, c={c}") + + $ FOO_A=42 FOO_C=1337 python -c 'import foo; foo.test(c=99)' + received: a=42, b=2, c=99 + ``` + """ + i = len(prefix) + env_overrides = {k[i:] if case_sensitive else k[i:].lower(): v + for k, v in os.environ.items() if k.startswith(prefix)} + part = partialmethod if is_method else partial + + def wrap(func): + params = signature(func).parameters + overrides = {k: v for k, v in env_overrides.items() if k in params} + if literal_eval: + return part(func, **{k: safe_eval(v) for k, v in overrides.items()}) + # use `func` signature to infer env override `type` (fallback to `str`) + for k in overrides: + param = params[k] + if param.annotation is not param.empty: + typ = param.annotation + # TODO: parse type in {Union, Any, Optional, ...} + else: + typ = str if param.default is None else type(param.default) + overrides[k] = typ(overrides[k]) + return part(func, **overrides) + return wrap + + class FormatReplace(object): """ >>> a = FormatReplace('something')