From d7f67a741417e84c1106ff7f7d35d0968f56aba4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 2 Mar 2024 09:25:08 +0100 Subject: Merging upstream version 4.66.2. Signed-off-by: Daniel Baumann --- .pre-commit-config.yaml | 12 +- CONTRIBUTING.md | 48 +- DEMO.ipynb | 12 +- LICENCE | 4 +- Makefile | 26 +- PKG-INFO | 1581 ------------------------------------ README.rst | 68 +- environment.yml | 11 +- examples/7zx.py | 2 - examples/async_coroutines.py | 4 +- examples/parallel_bars.py | 18 +- examples/redirect_print.py | 2 - examples/simple_examples.py | 7 +- examples/tqdm_wget.py | 5 +- pyproject.toml | 128 +++ setup.cfg | 159 ---- setup.py | 16 - tests/conftest.py | 18 +- tests/py37_asyncio.py | 128 --- tests/tests_asyncio.py | 140 +++- tests/tests_contrib.py | 22 +- tests/tests_contrib_logging.py | 2 - tests/tests_dask.py | 2 - tests/tests_keras.py | 18 +- tests/tests_main.py | 63 +- tests/tests_pandas.py | 54 +- tests/tests_perf.py | 38 +- tests/tests_rich.py | 5 +- tests/tests_synchronisation.py | 25 +- tests/tests_tqdm.py | 124 ++- tests/tests_utils.py | 51 ++ tox.ini | 47 +- tqdm.egg-info/PKG-INFO | 1581 ------------------------------------ tqdm.egg-info/SOURCES.txt | 88 -- tqdm.egg-info/dependency_links.txt | 1 - tqdm.egg-info/entry_points.txt | 2 - tqdm.egg-info/requires.txt | 20 - tqdm.egg-info/top_level.txt | 1 - tqdm/__init__.py | 5 +- tqdm/_dist_ver.py | 1 - tqdm/_utils.py | 7 +- tqdm/asyncio.py | 2 +- tqdm/auto.py | 40 +- tqdm/cli.py | 74 +- tqdm/contrib/__init__.py | 16 +- tqdm/contrib/concurrent.py | 37 +- tqdm/contrib/discord.py | 12 +- tqdm/contrib/itertools.py | 2 - tqdm/contrib/logging.py | 4 +- tqdm/contrib/slack.py | 12 +- tqdm/contrib/telegram.py | 12 +- tqdm/contrib/utils_worker.py | 2 - tqdm/dask.py | 2 - tqdm/gui.py | 13 +- tqdm/keras.py | 4 +- tqdm/notebook.py | 22 +- tqdm/rich.py | 10 +- tqdm/std.py | 345 ++++---- tqdm/tk.py | 21 +- tqdm/tqdm.1 | 6 +- tqdm/utils.py | 107 ++- 61 files changed, 921 insertions(+), 4368 deletions(-) delete mode 100644 PKG-INFO delete mode 100644 setup.cfg delete mode 100755 setup.py delete mode 100644 tests/py37_asyncio.py create mode 100644 tests/tests_utils.py delete mode 100644 tqdm.egg-info/PKG-INFO delete mode 100644 tqdm.egg-info/SOURCES.txt delete mode 100644 tqdm.egg-info/dependency_links.txt delete mode 100644 tqdm.egg-info/entry_points.txt delete mode 100644 tqdm.egg-info/requires.txt delete mode 100644 tqdm.egg-info/top_level.txt delete mode 100644 tqdm/_dist_ver.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95b4a82..4de1025 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -38,8 +38,8 @@ repos: - pandas - pytest-timeout - pytest-asyncio -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 +- repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 hooks: - id: flake8 args: [-j8] @@ -49,14 +49,14 @@ repos: - flake8-comprehensions - flake8-debugger - flake8-isort + - flake8-pyproject - flake8-string-format - - flake8-type-annotations - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/kynan/nbstripout - rev: 0.5.0 + rev: 0.7.1 hooks: - id: nbstripout args: [--keep-count, --keep-output] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cd86d0..533a8ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ Makefile: ``` make [] # on UNIX-like environments -python setup.py make [] # if make is unavailable +python -m pymake [] # if make is unavailable ``` The latter depends on [`py-make>=0.1.0`](https://github.com/tqdm/py-make). @@ -51,7 +51,7 @@ However it would be helpful to bear in mind: * use two spaces between variable name and colon, specify a type, and most likely state that it's optional: `VAR:TYPE[, optional]` * use [default: ...] for default values of keyword arguments + will not break backward compatibility unless there is a very good reason - * e.g. breaking py26 compatibility purely in favour of readability (such as converting `dict(a=1)` to `{'a': 1}`) is not a good enough reason + * e.g. breaking py26 compatibility purely in favour of minor readability changes (such as converting `dict(a=1)` to `{'a': 1}`) is not a good enough reason + API changes should be discussed carefully + remember, with millions of downloads per month, `tqdm` must be extremely fast and reliable - Any other kind of change may be included in a (possibly new) submodule @@ -85,7 +85,7 @@ The standard way to run the tests: - run the following command: ``` -[python setup.py] make test +[python -m py]make test # or: tox --skip-missing-interpreters ``` @@ -97,19 +97,19 @@ versions of Python.) Note: to install all versions of the Python interpreter that are specified in [tox.ini](https://github.com/tqdm/tqdm/blob/master/tox.ini), -you can use `MiniConda` to install a minimal setup. You must also make sure -that each distribution has an alias to call the Python interpreter: -`python27` for Python 2.7's interpreter, `python32` for Python 3.2's, etc. +you can use `MiniConda` to install a minimal setup. You must also ensure +that each distribution has an alias to call the Python interpreter +(e.g. `python311` for Python 3.11's interpreter). ### Alternative unit tests with pytest Alternatively, use `pytest` to run the tests just for the current Python version: -- install test requirements: `[python setup.py] make install_test` +- install test requirements: `[python -m py]make install_test` - run the following command: ``` -[python setup.py] make alltests +[python -m py]make alltests ``` @@ -118,9 +118,9 @@ Alternatively, use `pytest` to run the tests just for the current Python version This section is intended for the project's maintainers and describes how to build and upload a new release. Once again, -`[python setup.py] make []` will help. +`[python -m py]make []` will help. Also consider `pip install`ing development utilities: -`[python setup.py] make install_build` at a minimum, or a more thorough `conda env create`. +`[python -m py]make install_build` at a minimum, or a more thorough `conda env create`. ## Pre-commit Hook @@ -137,20 +137,20 @@ The `tqdm` repository managers should: - follow the [Semantic Versioning](https://semver.org) convention for tagging -## Checking setup.py +## Checking `pyproject.toml` -To check that the `setup.py`/`setup.cfg`/`pyproject.toml` file is compliant with PyPI +To check that the `pyproject.toml` file is compliant with PyPI requirements (e.g. version number; reStructuredText in `README.rst`) use: ``` -[python setup.py] make testsetup +[python -m py]make testsetup ``` To upload just metadata (including overwriting mistakenly uploaded metadata) to PyPI, use: ``` -[python setup.py] make pypimeta +[python -m py]make pypimeta ``` @@ -199,7 +199,7 @@ git merge --no-ff pr-branch-name ### 4 Test ``` -[python setup.py] make alltests +[python -m py]make alltests ``` ### 5 Push to master @@ -233,7 +233,7 @@ Manual instructions are given below in case of failure. Build `tqdm` into a distributable python package: ``` -[python setup.py] make build +[python -m py]make build ``` This will generate several builds in the `dist/` folder. On non-windows @@ -243,13 +243,13 @@ Finally, upload everything to PyPI. This can be done easily using the [twine](https://github.com/pypa/twine) module: ``` -[python setup.py] make pypi +[python -m py]make pypi ``` Also, the new release can (should) be added to GitHub by creating a new release from the [web interface](https://github.com/tqdm/tqdm/releases); uploading packages from the `dist/` folder -created by `[python setup.py] make build`. +created by `[python -m py]make build`. The [wiki] can be automatically updated with GitHub release notes by running `make` within the wiki repository. @@ -282,7 +282,7 @@ before the real deployment - in case of a mistake, you can delete an uploaded release on PyPI, but you cannot re-upload another with the same version number - in case of a mistake in the metadata on PyPI (e.g. bad README), -updating just the metadata is possible: `[python setup.py] make pypimeta` +updating just the metadata is possible: `[python -m py]make pypimeta` ## Updating Websites @@ -333,16 +333,16 @@ to assist with maintenance. For experienced devs, once happy with local master, follow the steps below. Much is automated so really it's steps 1-5, then 11(a). -1. test (`[python setup.py] make alltests` or rely on `pre-commit`) +1. test (`[python -m py]make alltests` or rely on `pre-commit`) 2. `git commit [--amend] # -m "bump version"` 3. `git push` 4. wait for tests to pass a) in case of failure, fix and go back to (1) 5. `git tag vM.m.p && git push --tags` or comment `/tag vM.m.p commit_hash` -6. **`[AUTO:GHA]`** `[python setup.py] make distclean` -7. **`[AUTO:GHA]`** `[python setup.py] make build` +6. **`[AUTO:GHA]`** `[python -m py]make distclean` +7. **`[AUTO:GHA]`** `[python -m py]make build` 8. **`[AUTO:GHA]`** upload to PyPI. either: - a) `[python setup.py] make pypi`, or + a) `[python -m py]make pypi`, or b) `twine upload -s -i $(git config user.signingkey) dist/tqdm-*` 9. **`[AUTO:GHA]`** upload to docker hub: a) `make -B docker` @@ -359,7 +359,7 @@ Much is automated so really it's steps 1-5, then 11(a). 13. **`[SUB][AUTO:GHA-rel]`** run `make deploy` in the `docs` submodule to update website 14. **`[SUB][AUTO:GHA-rel]`** accept the automated PR in the `feedstock` submodule to update conda 15. **`[AUTO:GHA-rel]`** update the [gh-pages project] benchmarks - a) `[python setup.py] make testasvfull` + a) `[python -m py]make testasvfull` b) `asv gh-pages` Key: diff --git a/DEMO.ipynb b/DEMO.ipynb index b892af4..8048cbc 100644 --- a/DEMO.ipynb +++ b/DEMO.ipynb @@ -5,12 +5,12 @@ "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", "\n", - "[![Build-Status](https://img.shields.io/github/workflow/status/tqdm/tqdm/Test/master?logo=GitHub)](https://github.com/tqdm/tqdm/actions?query=workflow%3ATest)|[![Coverage-Status](https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls)](https://coveralls.io/github/tqdm/tqdm)|[![Branch-Coverage-Status](https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg)](https://codecov.io/gh/tqdm/tqdm)|[![Codacy-Grade](https://app.codacy.com/project/badge/Grade/3f965571598f44549c7818f29cdcf177)](https://www.codacy.com/gh/tqdm/tqdm/dashboard)|[![Libraries-Rank](https://img.shields.io/librariesio/sourcerank/pypi/tqdm.svg?logo=koding&logoColor=white)](https://libraries.io/pypi/tqdm)|[![PyPI-Downloads](https://img.shields.io/pypi/dm/tqdm.svg?label=pypi%20downloads&logo=PyPI&logoColor=white)](https://pepy.tech/project/tqdm)\n", + "[![Build-Status](https://img.shields.io/github/actions/workflow/status/tqdm/tqdm/test.yml?branch=master&label=tqdm&logo=GitHub)](https://github.com/tqdm/tqdm/actions/workflows/test.yml)|[![Coverage-Status](https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls)](https://coveralls.io/github/tqdm/tqdm)|[![Branch-Coverage-Status](https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg)](https://codecov.io/gh/tqdm/tqdm)|[![Codacy-Grade](https://app.codacy.com/project/badge/Grade/3f965571598f44549c7818f29cdcf177)](https://www.codacy.com/gh/tqdm/tqdm/dashboard)|[![Libraries-Rank](https://img.shields.io/librariesio/sourcerank/pypi/tqdm.svg?logo=koding&logoColor=white)](https://libraries.io/pypi/tqdm)|[![PyPI-Downloads](https://img.shields.io/pypi/dm/tqdm.svg?label=pypi%20downloads&logo=PyPI&logoColor=white)](https://pepy.tech/project/tqdm)\n", "-|-|-|-|-|-\n", "\n", "[![DOI](https://img.shields.io/badge/DOI-10.5281/zenodo.595120-blue.svg)](https://doi.org/10.5281/zenodo.595120)|[![LICENCE](https://img.shields.io/pypi/l/tqdm.svg)](https://raw.githubusercontent.com/tqdm/tqdm/master/LICENCE)|[![OpenHub-Status](https://www.openhub.net/p/tqdm/widgets/project_thin_badge?format=gif)](https://www.openhub.net/p/tqdm?ref=Thin+badge)|[![binder-demo](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/tqdm/tqdm/master?filepath=DEMO.ipynb)|[![awesome-python](https://awesome.re/mentioned-badge.svg)](https://github.com/vinta/awesome-python)\n", @@ -40,7 +40,7 @@ "metadata": {}, "source": [ "`trange(N)` can be also used as a convenient shortcut for\n", - "`tqdm(xrange(N))`." + "`tqdm(range(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/LICENCE b/LICENCE index 5b3cab7..a8922b1 100644 --- a/LICENCE +++ b/LICENCE @@ -7,11 +7,11 @@ Exceptions or notable authors are listed below in reverse chronological order: * files: * - MPLv2.0 2015-2021 (c) Casper da Costa-Luis + MPL-2.0 2015-2024 (c) Casper da Costa-Luis [casperdcl](https://github.com/casperdcl). * files: tqdm/_tqdm.py MIT 2016 (c) [PR #96] on behalf of Google Inc. -* files: tqdm/_tqdm.py setup.py README.rst MANIFEST.in .gitignore +* files: tqdm/_tqdm.py README.rst .gitignore MIT 2013 (c) Noam Yorav-Raphael, original author. [PR #96]: https://github.com/tqdm/tqdm/pull/96 diff --git a/Makefile b/Makefile index 796f8b0..ab2863c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# IMPORTANT: for compatibility with `python setup.py make [alias]`, ensure: +# IMPORTANT: for compatibility with `python -m pymake [alias]`, ensure: # 1. Every alias is preceded by @[+]make (eg: @make alias) # 2. A maximum of one @make alias or command per line # see: https://github.com/tqdm/py-make/issues/1 @@ -31,7 +31,7 @@ run help: - @python setup.py make -p + @python -m pymake -p alltests: @+make testcoverage @@ -58,15 +58,14 @@ testsetup: @make README.rst @make tqdm/tqdm.1 @make tqdm/completion.sh - python setup.py check --metadata --restructuredtext --strict - python setup.py make none + @make help testnb: - pytest tests_notebook.ipynb --nbval --nbval-current-env -W=ignore --nbval-sanitize-with=setup.cfg --cov=tqdm.notebook --cov-report=term + pytest tests_notebook.ipynb --cov=tqdm.notebook --cov-report=term -W=ignore --nbval --current-env --sanitize-with=.meta/nbval.ini testcoverage: @make coverclean - pytest tests_notebook.ipynb --cov=tqdm --cov-report= --nbval --nbval-current-env --nbval-sanitize-with=setup.cfg -W=ignore + pytest tests_notebook.ipynb --cov=tqdm --cov-report= -W=ignore --nbval --current-env --sanitize-with=.meta/nbval.ini pytest -k "not perf" --cov=tqdm --cov-report=xml --cov-report=term --cov-append --cov-fail-under=80 testperf: @@ -138,9 +137,9 @@ clean: @+python -c "import os, glob; [os.remove(i) for i in glob.glob('*.py[co]')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('tests/*.py[co]')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('benchmarks/*.py[co]')]" + @+python -c "import os, glob; [os.remove(i) for i in glob.glob('examples/*.py[co]')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('tqdm/*.py[co]')]" @+python -c "import os, glob; [os.remove(i) for i in glob.glob('tqdm/contrib/*.py[co]')]" - @+python -c "import os, glob; [os.remove(i) for i in glob.glob('tqdm/examples/*.py[co]')]" toxclean: @+python -c "import shutil; shutil.rmtree('.tox', True)" @@ -152,12 +151,11 @@ submodules: cd feedstock && git remote add autotick-bot git@github.com:regro-cf-autotick-bot/tqdm-feedstock install: - python setup.py install + python -m pip install . install_dev: - python setup.py develop --uninstall - python setup.py develop + python -m pip install -e . install_build: - python -m pip install -r .meta/requirements-dev.txt + python -m pip install -r .meta/requirements-build.txt install_test: python -m pip install -r .meta/requirements-test.txt pre-commit install @@ -165,11 +163,11 @@ install_test: build: @make prebuildclean @make testsetup - python setup.py sdist bdist_wheel - # python setup.py bdist_wininst + python -m build + python -m twine check dist/* pypi: - twine upload dist/* + python -m twine upload dist/* buildupload: @make build diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index d796709..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,1581 +0,0 @@ -Metadata-Version: 2.1 -Name: tqdm -Version: 4.64.1 -Summary: Fast, Extensible Progress Meter -Home-page: https://tqdm.github.io -Maintainer: tqdm developers -Maintainer-email: python.tqdm@gmail.com -License: MPLv2.0, MIT Licences -Project-URL: Changelog, https://tqdm.github.io/releases -Project-URL: Source, https://github.com/tqdm/tqdm -Project-URL: Wiki, https://github.com/tqdm/tqdm/wiki -Keywords: progressbar,progressmeter,progress,bar,meter,rate,eta,console,terminal,time -Platform: any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Console -Classifier: Environment :: MacOS X -Classifier: Environment :: Other Environment -Classifier: Environment :: Win32 (MS Windows) -Classifier: Environment :: X11 Applications -Classifier: Framework :: IPython -Classifier: Framework :: Jupyter -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Education -Classifier: Intended Audience :: End Users/Desktop -Classifier: Intended Audience :: Other Audience -Classifier: Intended Audience :: System Administrators -Classifier: License :: OSI Approved :: MIT License -Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) -Classifier: Operating System :: MacOS -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Operating System :: Microsoft -Classifier: Operating System :: Microsoft :: MS-DOS -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: POSIX -Classifier: Operating System :: POSIX :: BSD -Classifier: Operating System :: POSIX :: BSD :: FreeBSD -Classifier: Operating System :: POSIX :: Linux -Classifier: Operating System :: POSIX :: SunOS/Solaris -Classifier: Operating System :: Unix -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: Implementation -Classifier: Programming Language :: Python :: Implementation :: IronPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Programming Language :: Unix Shell -Classifier: Topic :: Desktop Environment -Classifier: Topic :: Education :: Computer Aided Instruction (CAI) -Classifier: Topic :: Education :: Testing -Classifier: Topic :: Office/Business -Classifier: Topic :: Other/Nonlisted Topic -Classifier: Topic :: Software Development :: Build Tools -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Software Development :: Pre-processors -Classifier: Topic :: Software Development :: User Interfaces -Classifier: Topic :: System :: Installation/Setup -Classifier: Topic :: System :: Logging -Classifier: Topic :: System :: Monitoring -Classifier: Topic :: System :: Shells -Classifier: Topic :: Terminals -Classifier: Topic :: Utilities -Provides: tqdm -Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7 -Description-Content-Type: text/x-rst -Provides-Extra: dev -Provides-Extra: slack -Provides-Extra: telegram -Provides-Extra: notebook -License-File: LICENCE - -|Logo| - -tqdm -==== - -|Py-Versions| |Versions| |Conda-Forge-Status| |Docker| |Snapcraft| - -|Build-Status| |Coverage-Status| |Branch-Coverage-Status| |Codacy-Grade| |Libraries-Rank| |PyPI-Downloads| - -|LICENCE| |OpenHub-Status| |binder-demo| |awesome-python| - -``tqdm`` derives from the Arabic word *taqaddum* (تقدّم) which can mean "progress," -and is an abbreviation for "I love you so much" in Spanish (*te quiero demasiado*). - -Instantly make your loops show a smart progress meter - just wrap any -iterable with ``tqdm(iterable)``, and you're done! - -.. code:: python - - from tqdm import tqdm - for i in tqdm(range(10000)): - ... - -``76%|████████████████████████        | 7568/10000 [00:33<00:10, 229.00it/s]`` - -``trange(N)`` can be also used as a convenient shortcut for -``tqdm(range(N))``. - -|Screenshot| - |Video| |Slides| |Merch| - -It can also be executed as a module with pipes: - -.. code:: sh - - $ seq 9999999 | tqdm --bytes | wc -l - 75.2MB [00:00, 217MB/s] - 9999999 - - $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \ - > backup.tgz - 32%|██████████▍ | 8.89G/27.9G [00:42<01:31, 223MB/s] - -Overhead is low -- about 60ns per iteration (80ns with ``tqdm.gui``), and is -unit tested against performance regression. -By comparison, the well-established -`ProgressBar `__ has -an 800ns/iter overhead. - -In addition to its low overhead, ``tqdm`` uses smart algorithms to predict -the remaining time and to skip unnecessary iteration displays, which allows -for a negligible overhead in most cases. - -``tqdm`` works on any platform -(Linux, Windows, Mac, FreeBSD, NetBSD, Solaris/SunOS), -in any console or in a GUI, and is also friendly with IPython/Jupyter notebooks. - -``tqdm`` does not require any dependencies (not even ``curses``!), just -Python and an environment supporting ``carriage return \r`` and -``line feed \n`` control characters. - ------------------------------------------- - -.. contents:: Table of contents - :backlinks: top - :local: - - -Installation ------------- - -Latest PyPI stable release -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -|Versions| |PyPI-Downloads| |Libraries-Dependents| - -.. code:: sh - - pip install tqdm - -Latest development release on GitHub -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -|GitHub-Status| |GitHub-Stars| |GitHub-Commits| |GitHub-Forks| |GitHub-Updated| - -Pull and install pre-release ``devel`` branch: - -.. code:: sh - - pip install "git+https://github.com/tqdm/tqdm.git@devel#egg=tqdm" - -Latest Conda release -~~~~~~~~~~~~~~~~~~~~ - -|Conda-Forge-Status| - -.. code:: sh - - conda install -c conda-forge tqdm - -Latest Snapcraft release -~~~~~~~~~~~~~~~~~~~~~~~~ - -|Snapcraft| - -There are 3 channels to choose from: - -.. code:: sh - - snap install tqdm # implies --stable, i.e. latest tagged release - snap install tqdm --candidate # master branch - snap install tqdm --edge # devel branch - -Note that ``snap`` binaries are purely for CLI use (not ``import``-able), and -automatically set up ``bash`` tab-completion. - -Latest Docker release -~~~~~~~~~~~~~~~~~~~~~ - -|Docker| - -.. code:: sh - - docker pull tqdm/tqdm - docker run -i --rm tqdm/tqdm --help - -Other -~~~~~ - -There are other (unofficial) places where ``tqdm`` may be downloaded, particularly for CLI use: - -|Repology| - -.. |Repology| image:: https://repology.org/badge/tiny-repos/python:tqdm.svg - :target: https://repology.org/project/python:tqdm/versions - -Changelog ---------- - -The list of all changes is available either on GitHub's Releases: -|GitHub-Status|, on the -`wiki `__, or on the -`website `__. - - -Usage ------ - -``tqdm`` is very versatile and can be used in a number of ways. -The three main ones are given below. - -Iterable-based -~~~~~~~~~~~~~~ - -Wrap ``tqdm()`` around any iterable: - -.. code:: python - - from tqdm import tqdm - from time import sleep - - text = "" - for char in tqdm(["a", "b", "c", "d"]): - sleep(0.25) - text = text + char - -``trange(i)`` is a special optimised instance of ``tqdm(range(i))``: - -.. code:: python - - from tqdm import trange - - for i in trange(100): - sleep(0.01) - -Instantiation outside of the loop allows for manual control over ``tqdm()``: - -.. code:: python - - pbar = tqdm(["a", "b", "c", "d"]) - for char in pbar: - sleep(0.25) - pbar.set_description("Processing %s" % char) - -Manual -~~~~~~ - -Manual control of ``tqdm()`` updates using a ``with`` statement: - -.. code:: python - - with tqdm(total=100) as pbar: - for i in range(10): - sleep(0.1) - pbar.update(10) - -If the optional variable ``total`` (or an iterable with ``len()``) is -provided, predictive stats are displayed. - -``with`` is also optional (you can just assign ``tqdm()`` to a variable, -but in this case don't forget to ``del`` or ``close()`` at the end: - -.. code:: python - - pbar = tqdm(total=100) - for i in range(10): - sleep(0.1) - pbar.update(10) - pbar.close() - -Module -~~~~~~ - -Perhaps the most wonderful use of ``tqdm`` is in a script or on the command -line. Simply inserting ``tqdm`` (or ``python -m tqdm``) between pipes will pass -through all ``stdin`` to ``stdout`` while printing progress to ``stderr``. - -The example below demonstrate counting the number of lines in all Python files -in the current directory, with timing information included. - -.. code:: sh - - $ time find . -name '*.py' -type f -exec cat \{} \; | wc -l - 857365 - - real 0m3.458s - user 0m0.274s - sys 0m3.325s - - $ time find . -name '*.py' -type f -exec cat \{} \; | tqdm | wc -l - 857366it [00:03, 246471.31it/s] - 857365 - - real 0m3.585s - user 0m0.862s - sys 0m3.358s - -Note that the usual arguments for ``tqdm`` can also be specified. - -.. code:: sh - - $ find . -name '*.py' -type f -exec cat \{} \; | - tqdm --unit loc --unit_scale --total 857366 >> /dev/null - 100%|█████████████████████████████████| 857K/857K [00:04<00:00, 246Kloc/s] - -Backing up a large directory? - -.. code:: sh - - $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \ - > backup.tgz - 44%|██████████████▊ | 153M/352M [00:14<00:18, 11.0MB/s] - -This can be beautified further: - -.. code:: sh - - $ BYTES="$(du -sb docs/ | cut -f1)" - $ tar -cf - docs/ \ - | tqdm --bytes --total "$BYTES" --desc Processing | gzip \ - | tqdm --bytes --total "$BYTES" --desc Compressed --position 1 \ - > ~/backup.tgz - Processing: 100%|██████████████████████| 352M/352M [00:14<00:00, 30.2MB/s] - Compressed: 42%|█████████▎ | 148M/352M [00:14<00:19, 10.9MB/s] - -Or done on a file level using 7-zip: - -.. code:: sh - - $ 7z a -bd -r backup.7z docs/ | grep Compressing \ - | tqdm --total $(find docs/ -type f | wc -l) --unit files \ - | 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 --------------------- - -|GitHub-Issues| - -The most common issues relate to excessive output on multiple lines, instead -of a neat one-line progress bar. - -- Consoles in general: require support for carriage return (``CR``, ``\r``). -- Nested progress bars: - - * Consoles in general: require support for moving cursors up to the - previous line. For example, - `IDLE `__, - `ConEmu `__ and - `PyCharm `__ (also - `here `__, - `here `__, and - `here `__) - lack full support. - * Windows: additionally may require the Python module ``colorama`` - to ensure nested bars stay within their respective lines. - -- Unicode: - - * Environments which report that they support unicode will have solid smooth - 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 - either normal-width unicode characters being incorrectly displayed as - "wide", or some unicode characters not rendering. - -- Wrapping generators: - - * Generator wrapper functions tend to hide the length of iterables. - ``tqdm`` does not. - * Replace ``tqdm(enumerate(...))`` with ``enumerate(tqdm(...))`` or - ``tqdm(enumerate(x), total=len(x), ...)``. - The same applies to ``numpy.ndenumerate``. - * Replace ``tqdm(zip(a, b))`` with ``zip(tqdm(a), b)`` or even - ``zip(tqdm(a), tqdm(b))``. - * The same applies to ``itertools``. - * Some useful convenience functions can be found under ``tqdm.contrib``. - -- `Hanging pipes in python2 `__: - when using ``tqdm`` on the CLI, you may need to use Python 3.5+ for correct - buffering. -- `No intermediate output in docker-compose `__: - use ``docker-compose run`` instead of ``docker-compose up`` and ``tty: true``. - -If you come across any other difficulties, browse and file |GitHub-Issues|. - -Documentation -------------- - -|Py-Versions| |README-Hits| (Since 19 May 2016) - -.. code:: python - - class tqdm(): - """ - 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. - """ - - 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): - -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 - If (default: None) and ``file`` is unspecified, - bytes will be written in Python 2. If ``True`` will also write - bytes. In all other cases will default to 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. - -Extra CLI Options -~~~~~~~~~~~~~~~~~ - -* delim : chr, optional - Delimiting character [default: '\n']. Use '\0' for null. - N.B.: on Windows systems, Python converts '\n' to '\r\n'. -* buf_size : int, optional - String buffer size in bytes [default: 256] - used when ``delim`` is specified. -* 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()``. Note that this is slow - (~2e5 it/s) since every input must be decoded as a number. -* update_to : bool, optional - If true, will treat input as total elapsed iterations, - i.e. numbers to assign to ``self.n``. Note that this is slow - (~2e5 it/s) since every input must be decoded as a number. -* null : bool, optional - If true, will discard input (no stdout). -* manpath : str, optional - Directory in which to install tqdm man pages. -* comppath : str, optional - Directory in which to place tqdm completion. -* log : str, optional - CRITICAL|FATAL|ERROR|WARN(ING)|[default: 'INFO']|DEBUG|NOTSET. - -Returns -~~~~~~~ - -* out : decorated iterator. - -.. code:: python - - class tqdm(): - def update(self, n=1): - """ - Manually update the progress bar, useful for streams - such as reading files. - E.g.: - >>> t = tqdm(total=filesize) # Initialise - >>> for current_buffer in stream: - ... ... - ... t.update(len(current_buffer)) - >>> t.close() - The last line is highly recommended, but possibly not necessary if - ``t.update()`` will be called in such a way that ``filesize`` will be - exactly reached and printed. - - Parameters - ---------- - n : int or float, optional - 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): - """Cleanup and (if leave=False) close the progressbar.""" - - def clear(self, nomove=False): - """Clear current bar display.""" - - def refresh(self): - """ - Force refresh the display of this bar. - - Parameters - ---------- - nolock : bool, optional - If ``True``, does not lock. - If [default: ``False``]: calls ``acquire()`` on internal lock. - lock_args : tuple, optional - Passed to internal lock's ``acquire()``. - If specified, will only ``display()`` if ``acquire()`` returns ``True``. - """ - - def unpause(self): - """Restart tqdm timer from last print time.""" - - def reset(self, total=None): - """ - Resets to 0 iterations for repeated use. - - Consider combining with ``leave=True``. - - Parameters - ---------- - total : int or float, optional. Total to use for the new bar. - """ - - def set_description(self, desc=None, refresh=True): - """ - Set/modify description of the progress bar. - - Parameters - ---------- - desc : str, optional - refresh : bool, optional - Forces refresh [default: True]. - """ - - def set_postfix(self, ordered_dict=None, refresh=True, **tqdm_kwargs): - """ - Set/modify postfix (additional stats) - with automatic formatting based on datatype. - - Parameters - ---------- - ordered_dict : dict or OrderedDict, optional - refresh : bool, optional - Forces refresh [default: True]. - kwargs : dict, optional - """ - - @classmethod - def write(cls, s, file=sys.stdout, end="\n"): - """Print a message via tqdm (without overlap with bars).""" - - @property - def format_dict(self): - """Public API for read-only member access.""" - - def display(self, msg=None, pos=None): - """ - Use ``self.sp`` to display ``msg`` in the specified ``pos``. - - Consider overloading this function when inheriting to use e.g.: - ``self.some_frontend(**self.format_dict)`` instead of ``self.sp``. - - Parameters - ---------- - msg : str, optional. What to display (default: ``repr(self)``). - pos : int, optional. Position to ``moveto`` - (default: ``abs(self.pos)``). - """ - - @classmethod - @contextmanager - def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs): - """ - stream : file-like object. - method : str, "read" or "write". The result of ``read()`` and - the first argument of ``write()`` should have a ``len()``. - - >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: - ... while True: - ... chunk = fobj.read(chunk_size) - ... if not chunk: - ... break - """ - - @classmethod - def pandas(cls, *targs, **tqdm_kwargs): - """Registers the current `tqdm` class with `pandas`.""" - - def trange(*args, **tqdm_kwargs): - """ - A shortcut for `tqdm(xrange(*args), **tqdm_kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - -Convenience Functions -~~~~~~~~~~~~~~~~~~~~~ - -.. code:: python - - def tqdm.contrib.tenumerate(iterable, start=0, total=None, - tqdm_class=tqdm.auto.tqdm, **tqdm_kwargs): - """Equivalent of `numpy.ndenumerate` or builtin `enumerate`.""" - - def tqdm.contrib.tzip(iter1, *iter2plus, **tqdm_kwargs): - """Equivalent of builtin `zip`.""" - - def tqdm.contrib.tmap(function, *sequences, **tqdm_kwargs): - """Equivalent of builtin `map`.""" - -Submodules -~~~~~~~~~~ - -.. code:: python - - class tqdm.notebook.tqdm(tqdm.tqdm): - """IPython/Jupyter Notebook widget.""" - - class tqdm.auto.tqdm(tqdm.tqdm): - """Automatically chooses beween `tqdm.notebook` and `tqdm.tqdm`.""" - - class tqdm.asyncio.tqdm(tqdm.tqdm): - """Asynchronous version.""" - @classmethod - def as_completed(cls, fs, *, loop=None, timeout=None, total=None, - **tqdm_kwargs): - """Wrapper for `asyncio.as_completed`.""" - - class tqdm.gui.tqdm(tqdm.tqdm): - """Matplotlib GUI version.""" - - class tqdm.tk.tqdm(tqdm.tqdm): - """Tkinter GUI version.""" - - class tqdm.rich.tqdm(tqdm.tqdm): - """`rich.progress` version.""" - - class tqdm.keras.TqdmCallback(keras.callbacks.Callback): - """Keras callback for epoch and batch progress.""" - - class tqdm.dask.TqdmCallback(dask.callbacks.Callback): - """Dask callback for task progress.""" - - -``contrib`` -+++++++++++ - -The ``tqdm.contrib`` package also contains experimental modules: - -- ``tqdm.contrib.itertools``: Thin wrappers around ``itertools`` -- ``tqdm.contrib.concurrent``: Thin wrappers around ``concurrent.futures`` -- ``tqdm.contrib.slack``: Posts to `Slack `__ bots -- ``tqdm.contrib.discord``: Posts to `Discord `__ bots -- ``tqdm.contrib.telegram``: Posts to `Telegram `__ bots -- ``tqdm.contrib.bells``: Automagically enables all optional features - - * ``auto``, ``pandas``, ``slack``, ``discord``, ``telegram`` - -Examples and Advanced Usage ---------------------------- - -- See the `examples `__ - folder; -- import the module and run ``help()``; -- consult the `wiki `__; - - * this has an - `excellent article `__ - on how to make a **great** progressbar; - -- check out the `slides from PyData London `__, or -- run the |binder-demo|. - -Description and additional stats -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Custom information can be displayed and updated dynamically on ``tqdm`` bars -with the ``desc`` and ``postfix`` arguments: - -.. code:: python - - from tqdm import tqdm, trange - from random import random, randint - from time import sleep - - with trange(10) as t: - for i in t: - # Description will be displayed on the left - t.set_description('GEN %i' % i) - # Postfix will be displayed on the right, - # formatted automatically based on argument's datatype - t.set_postfix(loss=random(), gen=randint(1,999), str='h', - lst=[1, 2]) - sleep(0.1) - - with tqdm(total=10, bar_format="{postfix[0]} {postfix[1][value]:>8.2g}", - postfix=["Batch", dict(value=0)]) as t: - for i in range(10): - sleep(0.1) - t.postfix[1]["value"] = i / 2 - t.update() - -Points to remember when using ``{postfix[...]}`` in the ``bar_format`` string: - -- ``postfix`` also needs to be passed as an initial argument in a compatible - format, and -- ``postfix`` will be auto-converted to a string if it is a ``dict``-like - object. To prevent this behaviour, insert an extra item into the dictionary - where the key is not a string. - -Additional ``bar_format`` parameters may also be defined by overriding -``format_dict``, and the bar itself may be modified using ``ascii``: - -.. code:: python - - from tqdm import tqdm - class TqdmExtraFormat(tqdm): - """Provides a `total_time` format parameter""" - @property - def format_dict(self): - d = super(TqdmExtraFormat, self).format_dict - total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1) - d.update(total_time=self.format_interval(total_time) + " in total") - return d - - for i in TqdmExtraFormat( - range(9), ascii=" .oO0", - bar_format="{total_time}: {percentage:.0f}%|{bar}{r_bar}"): - if i == 4: - break - -.. code:: - - 00:00 in total: 44%|0000. | 4/9 [00:00<00:00, 962.93it/s] - -Note that ``{bar}`` also supports a format specifier ``[width][type]``. - -- ``width`` - - * unspecified (default): automatic to fill ``ncols`` - * ``int >= 0``: fixed width overriding ``ncols`` logic - * ``int < 0``: subtract from the automatic default - -- ``type`` - - * ``a``: ascii (``ascii=True`` override) - * ``u``: unicode (``ascii=False`` override) - * ``b``: blank (``ascii=" "`` override) - -This means a fixed bar with right-justified text may be created by using: -``bar_format="{l_bar}{bar:10}|{bar:-10b}right-justified"`` - -Nested progress bars -~~~~~~~~~~~~~~~~~~~~ - -``tqdm`` supports nested progress bars. Here's an example: - -.. code:: python - - from tqdm.auto import trange - from time import sleep - - for i in trange(4, desc='1st loop'): - for j in trange(5, desc='2nd loop'): - for k in trange(50, desc='3rd loop', leave=False): - sleep(0.01) - -For manual control over positioning (e.g. for multi-processing use), -you may specify ``position=n`` where ``n=0`` for the outermost bar, -``n=1`` for the next, and so on. -However, it's best to check if ``tqdm`` can work without manual ``position`` -first. - -.. code:: python - - from time import sleep - from tqdm import trange, tqdm - from multiprocessing import Pool, RLock, freeze_support - - L = list(range(9)) - - def progresser(n): - interval = 0.001 / (n + 2) - total = 5000 - text = "#{}, est. {:<04.2}s".format(n, interval * total) - for _ in trange(total, desc=text, position=n): - sleep(interval) - - if __name__ == '__main__': - freeze_support() # for Windows support - tqdm.set_lock(RLock()) # for managing output contention - p = Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) - p.map(progresser, L) - -Note that in Python 3, ``tqdm.write`` is thread-safe: - -.. code:: python - - from time import sleep - from tqdm import tqdm, trange - from concurrent.futures import ThreadPoolExecutor - - L = list(range(9)) - - def progresser(n): - interval = 0.001 / (n + 2) - total = 5000 - text = "#{}, est. {:<04.2}s".format(n, interval * total) - for _ in trange(total, desc=text): - sleep(interval) - if n == 6: - tqdm.write("n == 6 completed.") - tqdm.write("`tqdm.write()` is thread-safe in py3!") - - if __name__ == '__main__': - with ThreadPoolExecutor() as p: - p.map(progresser, L) - -Hooks and callbacks -~~~~~~~~~~~~~~~~~~~ - -``tqdm`` can easily support callbacks/hooks and manual updates. -Here's an example with ``urllib``: - -**``urllib.urlretrieve`` documentation** - - | [...] - | If present, the hook function will be called once - | on establishment of the network connection and once after each block read - | thereafter. The hook will be passed three arguments; a count of blocks - | transferred so far, a block size in bytes, and the total size of the file. - | [...] - -.. code:: python - - import urllib, os - from tqdm import tqdm - urllib = getattr(urllib, 'request', urllib) - - class TqdmUpTo(tqdm): - """Provides `update_to(n)` which uses `tqdm.update(delta_n)`.""" - def update_to(self, b=1, bsize=1, tsize=None): - """ - b : int, optional - Number of blocks transferred so far [default: 1]. - bsize : int, optional - Size of each block (in tqdm units) [default: 1]. - tsize : int, optional - Total size (in tqdm units). If [default: None] remains unchanged. - """ - if tsize is not None: - self.total = tsize - 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, - desc=eg_link.split('/')[-1]) as t: # all optional kwargs - urllib.urlretrieve(eg_link, filename=os.devnull, - reporthook=t.update_to, data=None) - t.total = t.n - -Inspired by `twine#242 `__. -Functional alternative in -`examples/tqdm_wget.py `__. - -It is recommend to use ``miniters=1`` whenever there is potentially -large differences in iteration speed (e.g. downloading a file over -a patchy connection). - -**Wrapping read/write methods** - -To measure throughput through a file-like object's ``read`` or ``write`` -methods, use ``CallbackIOWrapper``: - -.. code:: python - - from tqdm.auto import tqdm - from tqdm.utils import CallbackIOWrapper - - with tqdm(total=file_obj.size, - unit='B', unit_scale=True, unit_divisor=1024) as t: - fobj = CallbackIOWrapper(t.update, file_obj, "read") - while True: - chunk = fobj.read(chunk_size) - if not chunk: - break - t.reset() - # ... continue to use `t` for something else - -Alternatively, use the even simpler ``wrapattr`` convenience function, -which would condense both the ``urllib`` and ``CallbackIOWrapper`` examples -down to: - -.. code:: python - - import urllib, os - from tqdm import tqdm - - eg_link = "https://caspersci.uk.to/matryoshka.zip" - response = getattr(urllib, 'request', urllib).urlopen(eg_link) - with tqdm.wrapattr(open(os.devnull, "wb"), "write", - miniters=1, desc=eg_link.split('/')[-1], - total=getattr(response, 'length', None)) as fout: - for chunk in response: - fout.write(chunk) - -The ``requests`` equivalent is nearly identical: - -.. code:: python - - import requests, os - from tqdm import tqdm - - eg_link = "https://caspersci.uk.to/matryoshka.zip" - response = requests.get(eg_link, stream=True) - with tqdm.wrapattr(open(os.devnull, "wb"), "write", - miniters=1, desc=eg_link.split('/')[-1], - total=int(response.headers.get('content-length', 0))) as fout: - 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 - -``asyncio`` -~~~~~~~~~~~ - -Note that ``break`` isn't currently caught by asynchronous iterators. -This means that ``tqdm`` cannot clean up after itself in this case: - -.. code:: python - - from tqdm.asyncio import tqdm - - async for i in tqdm(range(9)): - if i == 2: - break - -Instead, either call ``pbar.close()`` manually or use the context manager syntax: - -.. code:: python - - from tqdm.asyncio import tqdm - - with tqdm(range(9)) as pbar: - async for i in pbar: - if i == 2: - break - -Pandas Integration -~~~~~~~~~~~~~~~~~~ - -Due to popular demand we've added support for ``pandas`` -- here's an example -for ``DataFrame.progress_apply`` and ``DataFrameGroupBy.progress_apply``: - -.. code:: python - - import pandas as pd - import numpy as np - from tqdm import tqdm - - df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) - - # Register `pandas.progress_apply` and `pandas.Series.map_apply` with `tqdm` - # (can use `tqdm.gui.tqdm`, `tqdm.notebook.tqdm`, optional kwargs, etc.) - tqdm.pandas(desc="my bar!") - - # Now you can use `progress_apply` instead of `apply` - # and `progress_map` instead of `map` - df.progress_apply(lambda x: x**2) - # can also groupby: - # df.groupby(0).progress_apply(lambda x: x**2) - -In case you're interested in how this works (and how to modify it for your -own callbacks), see the -`examples `__ -folder or import the module and run ``help()``. - -Keras Integration -~~~~~~~~~~~~~~~~~ - -A ``keras`` callback is also available: - -.. code:: python - - from tqdm.keras import TqdmCallback - - ... - - model.fit(..., verbose=0, callbacks=[TqdmCallback()]) - -Dask Integration -~~~~~~~~~~~~~~~~ - -A ``dask`` callback is also available: - -.. code:: python - - from tqdm.dask import TqdmCallback - - with TqdmCallback(desc="compute"): - ... - arr.compute() - - # or use callback globally - cb = TqdmCallback(desc="global") - cb.register() - arr.compute() - -IPython/Jupyter Integration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -IPython/Jupyter is supported via the ``tqdm.notebook`` submodule: - -.. code:: python - - from tqdm.notebook import trange, tqdm - from time import sleep - - for i in trange(3, desc='1st loop'): - for j in tqdm(range(100), desc='2nd loop'): - sleep(0.01) - -In addition to ``tqdm`` features, the submodule provides a native Jupyter -widget (compatible with IPython v1-v4 and Jupyter), fully working nested bars -and colour hints (blue: normal, green: completed, red: error/interrupt, -light blue: no ETA); as demonstrated below. - -|Screenshot-Jupyter1| -|Screenshot-Jupyter2| -|Screenshot-Jupyter3| - -The ``notebook`` version supports percentage or pixels for overall width -(e.g.: ``ncols='100%'`` or ``ncols='480px'``). - -It is also possible to let ``tqdm`` automatically choose between -console or notebook versions by using the ``autonotebook`` submodule: - -.. code:: python - - from tqdm.autonotebook import tqdm - tqdm.pandas() - -Note that this will issue a ``TqdmExperimentalWarning`` if run in a notebook -since it is not meant to be possible to distinguish between ``jupyter notebook`` -and ``jupyter console``. Use ``auto`` instead of ``autonotebook`` to suppress -this warning. - -Note that notebooks will display the bar in the cell where it was created. -This may be a different cell from the one where it is used. -If this is not desired, either - -- delay the creation of the bar to the cell where it must be displayed, or -- create the bar with ``display=False``, and in a later cell call - ``display(bar.container)``: - -.. code:: python - - from tqdm.notebook import tqdm - pbar = tqdm(..., display=False) - -.. code:: python - - # different cell - display(pbar.container) - -The ``keras`` callback has a ``display()`` method which can be used likewise: - -.. code:: python - - from tqdm.keras import TqdmCallback - cbk = TqdmCallback(display=False) - -.. code:: python - - # different cell - cbk.display() - model.fit(..., verbose=0, callbacks=[cbk]) - -Another possibility is to have a single bar (near the top of the notebook) -which is constantly re-used (using ``reset()`` rather than ``close()``). -For this reason, the notebook version (unlike the CLI version) does not -automatically call ``close()`` upon ``Exception``. - -.. code:: python - - from tqdm.notebook import tqdm - pbar = tqdm() - -.. code:: python - - # different cell - iterable = range(100) - pbar.reset(total=len(iterable)) # initialise with new `total` - for i in iterable: - pbar.update() - pbar.refresh() # force print final status but don't `close()` - -Custom Integration -~~~~~~~~~~~~~~~~~~ - -To change the default arguments (such as making ``dynamic_ncols=True``), -simply use built-in Python magic: - -.. code:: python - - from functools import partial - from tqdm import tqdm as std_tqdm - tqdm = partial(std_tqdm, dynamic_ncols=True) - -For further customisation, -``tqdm`` may be inherited from to create custom callbacks (as with the -``TqdmUpTo`` example `above <#hooks-and-callbacks>`__) or for custom frontends -(e.g. GUIs such as notebook or plotting packages). In the latter case: - -1. ``def __init__()`` to call ``super().__init__(..., gui=True)`` to disable - terminal ``status_printer`` creation. -2. Redefine: ``close()``, ``clear()``, ``display()``. - -Consider overloading ``display()`` to use e.g. -``self.frontend(**self.format_dict)`` instead of ``self.sp(repr(self))``. - -Some submodule examples of inheritance: - -- `tqdm/notebook.py `__ -- `tqdm/gui.py `__ -- `tqdm/tk.py `__ -- `tqdm/contrib/slack.py `__ -- `tqdm/contrib/discord.py `__ -- `tqdm/contrib/telegram.py `__ - -Dynamic Monitor/Meter -~~~~~~~~~~~~~~~~~~~~~ - -You can use a ``tqdm`` as a meter which is not monotonically increasing. -This could be because ``n`` decreases (e.g. a CPU usage monitor) or ``total`` -changes. - -One example would be recursively searching for files. The ``total`` is the -number of objects found so far, while ``n`` is the number of those objects which -are files (rather than folders): - -.. code:: python - - from tqdm import tqdm - import os.path - - def find_files_recursively(path, show_progress=True): - files = [] - # total=1 assumes `path` is a file - t = tqdm(total=1, unit="file", disable=not show_progress) - if not os.path.exists(path): - raise IOError("Cannot find:" + path) - - def append_found_file(f): - files.append(f) - t.update() - - def list_found_dir(path): - """returns os.listdir(path) assuming os.path.isdir(path)""" - listing = os.listdir(path) - # subtract 1 since a "file" we found was actually this directory - t.total += len(listing) - 1 - # fancy way to give info without forcing a refresh - t.set_postfix(dir=path[-10:], refresh=False) - t.update(0) # may trigger a refresh - return listing - - def recursively_search(path): - if os.path.isdir(path): - for f in list_found_dir(path): - recursively_search(os.path.join(path, f)) - else: - append_found_file(path) - - recursively_search(path) - t.set_postfix(dir=path) - t.close() - return files - -Using ``update(0)`` is a handy way to let ``tqdm`` decide when to trigger a -display refresh to avoid console spamming. - -Writing messages -~~~~~~~~~~~~~~~~ - -This is a work in progress (see -`#737 `__). - -Since ``tqdm`` uses a simple printing mechanism to display progress bars, -you should not write any message in the terminal using ``print()`` while -a progressbar is open. - -To write messages in the terminal without any collision with ``tqdm`` bar -display, a ``.write()`` method is provided: - -.. code:: python - - from tqdm.auto import tqdm, trange - from time import sleep - - bar = trange(10) - for i in bar: - # Print using tqdm class method .write() - sleep(0.1) - if not (i % 3): - tqdm.write("Done task %i" % i) - # Can also use bar.write() - -By default, this will print to standard output ``sys.stdout``. but you can -specify any file-like object using the ``file`` argument. For example, this -can be used to redirect the messages writing to a log file or class. - -Redirecting writing -~~~~~~~~~~~~~~~~~~~ - -If using a library that can print messages to the console, editing the library -by replacing ``print()`` with ``tqdm.write()`` may not be desirable. -In that case, redirecting ``sys.stdout`` to ``tqdm.write()`` is an option. - -To redirect ``sys.stdout``, create a file-like class that will write -any input string to ``tqdm.write()``, and supply the arguments -``file=sys.stdout, dynamic_ncols=True``. - -A reusable canonical example is given below: - -.. code:: python - - from time import sleep - import contextlib - import sys - from tqdm import tqdm - from tqdm.contrib import DummyTqdmFile - - - @contextlib.contextmanager - def std_out_err_redirect_tqdm(): - orig_out_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err) - yield orig_out_err[0] - # Relay exceptions - except Exception as exc: - raise exc - # Always restore sys.stdout/err if necessary - finally: - sys.stdout, sys.stderr = orig_out_err - - def some_fun(i): - print("Fee, fi, fo,".split()[i]) - - # Redirect stdout to tqdm.write() (don't forget the `as save_stdout`) - with std_out_err_redirect_tqdm() as orig_stdout: - # tqdm needs the original stdout - # and dynamic_ncols=True to autodetect console width - for i in tqdm(range(3), file=orig_stdout, dynamic_ncols=True): - sleep(.5) - some_fun(i) - - # After the `with`, printing is restored - print("Done!") - -Redirecting ``logging`` -~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to ``sys.stdout``/``sys.stderr`` as detailed above, console ``logging`` -may also be redirected to ``tqdm.write()``. - -Warning: if also redirecting ``sys.stdout``/``sys.stderr``, make sure to -redirect ``logging`` first if needed. - -Helper methods are available in ``tqdm.contrib.logging``. For example: - -.. code:: python - - import logging - from tqdm import trange - from tqdm.contrib.logging import logging_redirect_tqdm - - LOG = logging.getLogger(__name__) - - if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - with logging_redirect_tqdm(): - for i in trange(9): - if i == 4: - LOG.info("console logging redirected to `tqdm.write()`") - # logging restored - -Monitoring thread, intervals and miniters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``tqdm`` implements a few tricks to increase efficiency and reduce overhead. - -- Avoid unnecessary frequent bar refreshing: ``mininterval`` defines how long - to wait between each refresh. ``tqdm`` always gets updated in the background, - but it will display only every ``mininterval``. -- Reduce number of calls to check system clock/time. -- ``mininterval`` is more intuitive to configure than ``miniters``. - A clever adjustment system ``dynamic_miniters`` will automatically adjust - ``miniters`` to the amount of iterations that fit into time ``mininterval``. - Essentially, ``tqdm`` will check if it's time to print without actually - checking time. This behaviour can be still be bypassed by manually setting - ``miniters``. - -However, consider a case with a combination of fast and slow iterations. -After a few fast iterations, ``dynamic_miniters`` will set ``miniters`` to a -large number. When iteration rate subsequently slows, ``miniters`` will -remain large and thus reduce display update frequency. To address this: - -- ``maxinterval`` defines the maximum time between display refreshes. - A concurrent monitoring thread checks for overdue updates and forces one - where necessary. - -The monitoring thread should not have a noticeable overhead, and guarantees -updates at least every 10 seconds by default. -This value can be directly changed by setting the ``monitor_interval`` of -any ``tqdm`` instance (i.e. ``t = tqdm.tqdm(...); t.monitor_interval = 2``). -The monitor thread may be disabled application-wide by setting -``tqdm.tqdm.monitor_interval = 0`` before instantiation of any ``tqdm`` bar. - - -Merch ------ - -You can buy `tqdm branded merch `__ now! - -Contributions -------------- - -|GitHub-Commits| |GitHub-Issues| |GitHub-PRs| |OpenHub-Status| |GitHub-Contributions| |CII Best Practices| - -All source code is hosted on `GitHub `__. -Contributions are welcome. - -See the -`CONTRIBUTING `__ -file for more information. - -Developers who have made significant contributions, ranked by *SLoC* -(surviving lines of code, -`git fame `__ ``-wMC --excl '\.(png|gif|jpg)$'``), -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% -Daniel Ecer `de-code `__ ~2% -Richard Sheridan `richardsheridan `__ ~1% -Guangshuo Chen `chengs `__ ~1% -Kyle Altendorf `altendky `__ <1% -Matthew Stevens `mjstevens777 `__ <1% -Hadrien Mary `hadim `__ <1% team member -Noam Yorav-Raphael `noamraph `__ <1% original author -Mikhail Korobov `kmike `__ <1% team member -==================== ======================================================== ==== ================================ - -Ports to Other Languages -~~~~~~~~~~~~~~~~~~~~~~~~ - -A list is available on -`this wiki page `__. - - -LICENCE -------- - -Open Source (OSI approved): |LICENCE| - -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 - :target: https://tqdm.github.io/video -.. |Slides| image:: https://img.tqdm.ml/slides.jpg - :target: https://tqdm.github.io/PyData2019/slides.html -.. |Merch| image:: https://img.tqdm.ml/merch.jpg - :target: https://tqdm.github.io/merch -.. |Build-Status| image:: https://img.shields.io/github/workflow/status/tqdm/tqdm/Test/master?logo=GitHub - :target: https://github.com/tqdm/tqdm/actions?query=workflow%3ATest -.. |Coverage-Status| image:: https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls - :target: https://coveralls.io/github/tqdm/tqdm -.. |Branch-Coverage-Status| image:: https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg - :target: https://codecov.io/gh/tqdm/tqdm -.. |Codacy-Grade| image:: https://app.codacy.com/project/badge/Grade/3f965571598f44549c7818f29cdcf177 - :target: https://www.codacy.com/gh/tqdm/tqdm/dashboard -.. |CII Best Practices| image:: https://bestpractices.coreinfrastructure.org/projects/3264/badge - :target: https://bestpractices.coreinfrastructure.org/projects/3264 -.. |GitHub-Status| image:: https://img.shields.io/github/tag/tqdm/tqdm.svg?maxAge=86400&logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/releases -.. |GitHub-Forks| image:: https://img.shields.io/github/forks/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/network -.. |GitHub-Stars| image:: https://img.shields.io/github/stars/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/stargazers -.. |GitHub-Commits| image:: https://img.shields.io/github/commit-activity/y/tqdm/tqdm.svg?logo=git&logoColor=white - :target: https://github.com/tqdm/tqdm/graphs/commit-activity -.. |GitHub-Issues| image:: https://img.shields.io/github/issues-closed/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/issues?q= -.. |GitHub-PRs| image:: https://img.shields.io/github/issues-pr-closed/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/pulls -.. |GitHub-Contributions| image:: https://img.shields.io/github/contributors/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/graphs/contributors -.. |GitHub-Updated| image:: https://img.shields.io/github/last-commit/tqdm/tqdm/master.svg?logo=github&logoColor=white&label=pushed - :target: https://github.com/tqdm/tqdm/pulse -.. |Gift-Casper| image:: https://img.shields.io/badge/dynamic/json.svg?color=ff69b4&label=gifts%20received&prefix=%C2%A3&query=%24..sum&url=https%3A%2F%2Fcaspersci.uk.to%2Fgifts.json - :target: https://cdcl.ml/sponsor -.. |Versions| image:: https://img.shields.io/pypi/v/tqdm.svg - :target: https://tqdm.github.io/releases -.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/tqdm.svg?label=pypi%20downloads&logo=PyPI&logoColor=white - :target: https://pepy.tech/project/tqdm -.. |Py-Versions| image:: https://img.shields.io/pypi/pyversions/tqdm.svg?logo=python&logoColor=white - :target: https://pypi.org/project/tqdm -.. |Conda-Forge-Status| image:: https://img.shields.io/conda/v/conda-forge/tqdm.svg?label=conda-forge&logo=conda-forge - :target: https://anaconda.org/conda-forge/tqdm -.. |Snapcraft| image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft - :target: https://snapcraft.io/tqdm -.. |Docker| image:: https://img.shields.io/badge/docker-pull-blue.svg?logo=docker&logoColor=white - :target: https://hub.docker.com/r/tqdm/tqdm -.. |Libraries-Rank| image:: https://img.shields.io/librariesio/sourcerank/pypi/tqdm.svg?logo=koding&logoColor=white - :target: https://libraries.io/pypi/tqdm -.. |Libraries-Dependents| image:: https://img.shields.io/librariesio/dependent-repos/pypi/tqdm.svg?logo=koding&logoColor=white - :target: https://github.com/tqdm/tqdm/network/dependents -.. |OpenHub-Status| image:: https://www.openhub.net/p/tqdm/widgets/project_thin_badge?format=gif - :target: https://www.openhub.net/p/tqdm?ref=Thin+badge -.. |awesome-python| image:: https://awesome.re/mentioned-badge.svg - :target: https://github.com/vinta/awesome-python -.. |LICENCE| image:: https://img.shields.io/pypi/l/tqdm.svg - :target: https://raw.githubusercontent.com/tqdm/tqdm/master/LICENCE -.. |DOI| image:: https://img.shields.io/badge/DOI-10.5281/zenodo.595120-blue.svg - :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 diff --git a/README.rst b/README.rst index a45f98d..a148cb1 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 \ @@ -291,6 +291,12 @@ The most common issues relate to excessive output on multiple lines, instead of a neat one-line progress bar. - Consoles in general: require support for carriage return (``CR``, ``\r``). + + * Some cloud logging consoles which don't support ``\r`` properly + (`cloudwatch `__, + `K8s `__) may benefit from + ``export TQDM_POSITION=-1``. + - Nested progress bars: * Consoles in general: require support for moving cursors up to the @@ -327,12 +333,14 @@ of a neat one-line progress bar. * The same applies to ``itertools``. * Some useful convenience functions can be found under ``tqdm.contrib``. -- `Hanging pipes in python2 `__: - when using ``tqdm`` on the CLI, you may need to use Python 3.5+ for correct - 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/cloud 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|. Documentation @@ -349,12 +357,14 @@ Documentation progressbar every time a value is requested. """ + @envwrap("TQDM_") # 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 ~~~~~~~~~~ @@ -450,9 +460,7 @@ Parameters * unit_divisor : float, optional [default: 1000], ignored unless ``unit_scale`` is True. * write_bytes : bool, optional - If (default: None) and ``file`` is unspecified, - bytes will be written in Python 2. If ``True`` will also write - bytes. In all other cases will default to unicode. + Whether to write bytes. If (default: False) will write unicode. * lock_args : tuple, optional Passed to ``refresh`` for intermediate output (initialisation, iterating, and updating). @@ -631,10 +639,7 @@ Returns """Registers the current `tqdm` class with `pandas`.""" def trange(*args, **tqdm_kwargs): - """ - A shortcut for `tqdm(xrange(*args), **tqdm_kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ + """Shortcut for `tqdm(range(*args), **tqdm_kwargs)`.""" Convenience Functions ~~~~~~~~~~~~~~~~~~~~~ @@ -737,7 +742,7 @@ with the ``desc`` and ``postfix`` arguments: sleep(0.1) with tqdm(total=10, bar_format="{postfix[0]} {postfix[1][value]:>8.2g}", - postfix=["Batch", dict(value=0)]) as t: + postfix=["Batch", {"value": 0}]) as t: for i in range(10): sleep(0.1) t.postfix[1]["value"] = i / 2 @@ -825,7 +830,7 @@ first. def progresser(n): interval = 0.001 / (n + 2) total = 5000 - text = "#{}, est. {:<04.2}s".format(n, interval * total) + text = f"#{n}, est. {interval * total:<04.2}s" for _ in trange(total, desc=text, position=n): sleep(interval) @@ -848,7 +853,7 @@ Note that in Python 3, ``tqdm.write`` is thread-safe: def progresser(n): interval = 0.001 / (n + 2) total = 5000 - text = "#{}, est. {:<04.2}s".format(n, interval * total) + text = f"#{n}, est. {interval * total:<04.2}s" for _ in trange(total, desc=text): sleep(interval) if n == 6: @@ -1405,16 +1410,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 ==================== ======================================================== ==== ================================ @@ -1434,16 +1440,16 @@ 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/workflow/status/tqdm/tqdm/Test/master?logo=GitHub - :target: https://github.com/tqdm/tqdm/actions?query=workflow%3ATest +.. |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 .. |Coverage-Status| image:: https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls :target: https://coveralls.io/github/tqdm/tqdm .. |Branch-Coverage-Status| image:: https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg @@ -1496,8 +1502,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/environment.yml b/environment.yml index 871e3e8..e2e6b95 100644 --- a/environment.yml +++ b/environment.yml @@ -5,7 +5,7 @@ channels: - defaults dependencies: # base -- python=3 +- python >=3.7 - pip - ipykernel - ipywidgets @@ -20,7 +20,7 @@ dependencies: - pytest - pytest-cov - pytest-timeout -- pytest-asyncio # [py>=3.7] +- pytest-asyncio - nbval - coverage # extras @@ -28,19 +28,20 @@ dependencies: - matplotlib # gui - numpy # pandas, keras, contrib.tenumerate - pandas +- pyarrow # pandas - tensorflow # keras - slack-sdk # contrib.slack - requests # contrib.telegram - rich # rich - argopt # `cd wiki && pymake` -- twine # `pymake pypi` -- wheel # `setup.py bdist_wheel` +- twine # `pymake check pypi` +- build # `python -m build` # `cd docs && pymake` - mkdocs-material - pydoc-markdown - pygments - pymdown-extensions - pip: - - py-make >=0.1.0 # `setup.py make/pymake` + - py-make >=0.1.0 # `make/pymake` - mkdocs-minify-plugin # `cd docs && pymake` - git+https://github.com/tqdm/jsmin@python3-only#egg=jsmin # `cd docs && pymake` diff --git a/examples/7zx.py b/examples/7zx.py index 3d15254..18d7e33 100644 --- a/examples/7zx.py +++ b/examples/7zx.py @@ -18,8 +18,6 @@ Options: NOTSET -d, --debug-trace Print lots of debugging information (-D NOTSET) """ -from __future__ import print_function - import io import logging import os diff --git a/examples/async_coroutines.py b/examples/async_coroutines.py index 40f4f24..3e31905 100644 --- a/examples/async_coroutines.py +++ b/examples/async_coroutines.py @@ -1,6 +1,4 @@ -""" -Asynchronous examples using `asyncio`, `async` and `await` on `python>=3.7`. -""" +"""Asynchronous examples using `asyncio`, `async` and `await`.""" import asyncio from tqdm.asyncio import tqdm, trange diff --git a/examples/parallel_bars.py b/examples/parallel_bars.py index 498fd61..b7e1494 100644 --- a/examples/parallel_bars.py +++ b/examples/parallel_bars.py @@ -1,6 +1,3 @@ -from __future__ import print_function - -import sys from concurrent.futures import ThreadPoolExecutor from functools import partial from multiprocessing import Pool, RLock, freeze_support @@ -12,21 +9,19 @@ from tqdm.auto import tqdm, trange from tqdm.contrib.concurrent import process_map, thread_map NUM_SUBITERS = 9 -PY2 = sys.version_info[:1] <= (2,) def progresser(n, auto_position=True, write_safe=False, blocking=True, progress=False): interval = random() * 0.002 / (NUM_SUBITERS - n + 2) # nosec total = 5000 - text = "#{0}, est. {1:<04.2}s".format(n, interval * total) + text = f"#{n}, est. {interval * total:<04.2g}s" for _ in trange(total, desc=text, disable=not progress, lock_args=None if blocking else (False,), position=None if auto_position else n): sleep(interval) # NB: may not clear instances with higher `position` upon completion # since this worker may not know about other bars #796 - if write_safe: - # we think we know about other bars (currently only py3 threading) + if write_safe: # we think we know about other bars if n == 6: tqdm.write("n == 6 completed") return n + 1 @@ -37,7 +32,7 @@ if __name__ == '__main__': L = list(range(NUM_SUBITERS))[::-1] print("Simple thread mapping") - thread_map(partial(progresser, write_safe=not PY2), L, max_workers=4) + thread_map(partial(progresser, write_safe=True), L, max_workers=4) print("Simple process mapping") process_map(partial(progresser), L, max_workers=4) @@ -54,8 +49,5 @@ if __name__ == '__main__': print("Multi-threading") tqdm.set_lock(TRLock()) - pool_args = {} - if not PY2: - pool_args.update(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) - with ThreadPoolExecutor(**pool_args) as p: - p.map(partial(progresser, progress=True, write_safe=not PY2, blocking=False), L) + with ThreadPoolExecutor(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) as p: + p.map(partial(progresser, progress=True, write_safe=True, blocking=False), L) diff --git a/examples/redirect_print.py b/examples/redirect_print.py index 0f9721e..38e6b4f 100644 --- a/examples/redirect_print.py +++ b/examples/redirect_print.py @@ -10,8 +10,6 @@ any input string to `tqdm.write()`, and supply the arguments A reusable canonical example is given below: """ -from __future__ import print_function - import contextlib import sys from time import sleep diff --git a/examples/simple_examples.py b/examples/simple_examples.py index f3401d3..bff1c9e 100644 --- a/examples/simple_examples.py +++ b/examples/simple_examples.py @@ -2,7 +2,7 @@ # Simple tqdm examples and profiling # Benchmark -for i in _range(int(1e8)): +for i in range(int(1e8)): pass # Basic demo @@ -33,7 +33,7 @@ try: except ImportError: pass else: - for i in ProgressBar()(_range(int(1e8))): + for i in ProgressBar()(range(int(1e8))): pass # Dynamic miniters benchmark @@ -61,5 +61,4 @@ for _ in trange(16, leave=True): stmts = filter(None, re.split(r'\n\s*#.*?\n', __doc__)) for s in stmts: print(s.replace('import tqdm\n', '')) - print(timeit(stmt='try:\n\t_range = xrange' - '\nexcept:\n\t_range = range\n' + s, number=1), 'seconds') + print(timeit(stmt=s, number=1), 'seconds') diff --git a/examples/tqdm_wget.py b/examples/tqdm_wget.py index 8663e5a..ee8b9f3 100644 --- a/examples/tqdm_wget.py +++ b/examples/tqdm_wget.py @@ -20,11 +20,8 @@ Options: The local file path in which to save the url [default: /dev/null]. """ -try: - from urllib import request as urllib -except ImportError: # py2 - import urllib from os import devnull +from urllib import request as urllib from docopt import docopt diff --git a/pyproject.toml b/pyproject.toml index 3eb7bbc..6ee6d5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,131 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "tqdm/_dist_ver.py" write_to_template = "__version__ = '{version}'\n" + +[tool.setuptools.packages.find] +exclude = ["benchmarks", "examples", "tests", "wiki", "docs", "feedstock"] + +[project.urls] +homepage = "https://tqdm.github.io" +repository = "https://github.com/tqdm/tqdm" +changelog = "https://tqdm.github.io/releases" +wiki = "https://github.com/tqdm/tqdm/wiki" + +[project] +name = "tqdm" +dynamic = ["version"] +maintainers = [{name = "tqdm developers", email = "devs@tqdm.ml"}] +description = "Fast, Extensible Progress Meter" +readme = "README.rst" +requires-python = ">=3.7" +keywords = ["progressbar", "progressmeter", "progress", "bar", "meter", "rate", "eta", "console", "terminal", "time"] +license = {text = "MPL-2.0 AND MIT"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Environment :: MacOS X", + "Environment :: Other Environment", + "Environment :: Win32 (MS Windows)", + "Environment :: X11 Applications", + "Framework :: IPython", + "Framework :: Jupyter", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Other Audience", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Operating System :: MacOS", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft", + "Operating System :: Microsoft :: MS-DOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: POSIX :: BSD", + "Operating System :: POSIX :: BSD :: FreeBSD", + "Operating System :: POSIX :: Linux", + "Operating System :: POSIX :: SunOS/Solaris", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: Implementation", + "Programming Language :: Python :: Implementation :: IronPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Unix Shell", + "Topic :: Desktop Environment", + "Topic :: Education :: Computer Aided Instruction (CAI)", + "Topic :: Education :: Testing", + "Topic :: Office/Business", + "Topic :: Other/Nonlisted Topic", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Pre-processors", + "Topic :: Software Development :: User Interfaces", + "Topic :: System :: Installation/Setup", + "Topic :: System :: Logging", + "Topic :: System :: Monitoring", + "Topic :: System :: Shells", + "Topic :: Terminals", + "Topic :: Utilities"] +dependencies = ['colorama; platform_system == "Windows"'] + +[project.optional-dependencies] +dev = ["pytest>=6", "pytest-cov", "pytest-timeout", "pytest-xdist"] +slack = ["slack-sdk"] +telegram = ["requests"] +notebook = ["ipywidgets>=6"] + +[project.scripts] +tqdm = "tqdm.cli:main" + +[tool.flake8] +max_line_length = 99 +exclude = [".git", "__pycache__", "build", "dist", ".eggs", ".asv", ".tox", ".ipynb_checkpoints"] + +[tool.yapf] +spaces_before_comment = [15, 20] +arithmetic_precedence_indication = true +allow_split_before_dict_value = false +coalesce_brackets = true +column_limit = 99 +each_dict_entry_on_separate_line = false +space_between_ending_comma_and_closing_bracket = false +split_before_named_assigns = false +split_before_closing_bracket = false +blank_line_before_nested_class_or_def = false + +[tool.isort] +line_length = 99 +multi_line_output = 4 +known_first_party = ["tqdm", "tests"] + +[tool.pytest.ini_options] +minversion = "6.0" +timeout = 30 +log_level = "INFO" +markers = ["asyncio", "slow"] +python_files = ["tests_*.py", "tests_*.ipynb"] +testpaths = ["tests"] +addopts = "-v --tb=short -rxs -W=error --durations=0 --durations-min=0.1 --asyncio-mode=strict" + +[tool.coverage.run] +branch = true +include = ["tqdm/*"] +omit = [ + "tqdm/contrib/bells.py", + "tqdm/contrib/slack.py", + "tqdm/contrib/discord.py", + "tqdm/contrib/telegram.py", + "tqdm/contrib/utils_worker.py"] +relative_files = true +disable_warnings = ["include-ignored"] +[tool.coverage.report] +show_missing = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4fb34f3..0000000 --- a/setup.cfg +++ /dev/null @@ -1,159 +0,0 @@ -[metadata] -name = tqdm -url = https://tqdm.github.io -project_urls = - Changelog=https://tqdm.github.io/releases - Source=https://github.com/tqdm/tqdm - Wiki=https://github.com/tqdm/tqdm/wiki -maintainer = tqdm developers -maintainer_email = python.tqdm@gmail.com -license = MPLv2.0, MIT Licences -license_file = LICENCE -description = Fast, Extensible Progress Meter -long_description = file: README.rst -long_description_content_type = text/x-rst -keywords = progressbar, progressmeter, progress, bar, meter, rate, eta, console, terminal, time -platforms = any -provides = tqdm -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Console - Environment :: MacOS X - Environment :: Other Environment - Environment :: Win32 (MS Windows) - Environment :: X11 Applications - Framework :: IPython - Framework :: Jupyter - Intended Audience :: Developers - Intended Audience :: Education - Intended Audience :: End Users/Desktop - Intended Audience :: Other Audience - Intended Audience :: System Administrators - License :: OSI Approved :: MIT License - License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) - Operating System :: MacOS - Operating System :: MacOS :: MacOS X - Operating System :: Microsoft - Operating System :: Microsoft :: MS-DOS - Operating System :: Microsoft :: Windows - Operating System :: POSIX - Operating System :: POSIX :: BSD - Operating System :: POSIX :: BSD :: FreeBSD - Operating System :: POSIX :: Linux - Operating System :: POSIX :: SunOS/Solaris - Operating System :: Unix - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: Implementation - Programming Language :: Python :: Implementation :: IronPython - Programming Language :: Python :: Implementation :: PyPy - Programming Language :: Unix Shell - Topic :: Desktop Environment - Topic :: Education :: Computer Aided Instruction (CAI) - Topic :: Education :: Testing - Topic :: Office/Business - Topic :: Other/Nonlisted Topic - Topic :: Software Development :: Build Tools - Topic :: Software Development :: Libraries - Topic :: Software Development :: Libraries :: Python Modules - Topic :: Software Development :: Pre-processors - Topic :: Software Development :: User Interfaces - Topic :: System :: Installation/Setup - Topic :: System :: Logging - Topic :: System :: Monitoring - Topic :: System :: Shells - Topic :: Terminals - Topic :: Utilities - -[options] -setup_requires = setuptools>=42; setuptools_scm[toml]>=3.4 -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* -install_requires = - colorama; platform_system == 'Windows' - importlib_resources; python_version < "3.7" -tests_require = tox -include_package_data = True -packages = find: - -[options.extras_require] -dev = py-make>=0.1.0; twine; wheel -slack = slack-sdk -telegram = requests -notebook = ipywidgets>=6 - -[options.entry_points] -console_scripts = - tqdm=tqdm.cli:main - -[options.packages.find] -exclude = benchmarks, tests - -[bdist_wheel] -universal = 1 - -[flake8] -max_line_length = 99 -exclude = .asv,.eggs,.tox,.ipynb_checkpoints,build,dist,.git,__pycache__ - -[pydocstyle] -add_ignore = D400,D415 - -[yapf] -coalesce_brackets = True -column_limit = 99 -each_dict_entry_on_separate_line = False -i18n_comment = NOQA -space_between_ending_comma_and_closing_bracket = False -split_before_named_assigns = False -split_before_closing_bracket = False - -[isort] -line_length = 99 -multi_line_output = 4 -known_first_party = tqdm,tests - -[tool:pytest] -timeout = 30 -log_level = INFO -markers = - asyncio - slow -python_files = tests_*.py tests_*.ipynb -testpaths = tests -addopts = -v --tb=short -rxs -W=error --durations=0 --durations-min=0.1 --asyncio-mode=strict - -[regex1] -regex = (?<= )[\s\d.]+(it/s|s/it) -replace = ??.??it/s - -[regex2] -regex = 00:0[01]<00:0[01] -replace = 00:00<00:00 - -[coverage:run] -branch = True -include = tqdm/* -omit = - tqdm/contrib/bells.py - tqdm/contrib/slack.py - tqdm/contrib/discord.py - tqdm/contrib/telegram.py - tqdm/contrib/utils_worker.py -relative_files = True -disable_warnings = include-ignored - -[coverage:report] -show_missing = True - -[egg_info] -tag_build = -tag_date = 0 - diff --git a/setup.py b/setup.py deleted file mode 100755 index 89dadf5..0000000 --- a/setup.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import sys -from os import path - -from setuptools import setup - -src_dir = path.abspath(path.dirname(__file__)) -if sys.argv[1].lower().strip() == 'make': # exec Makefile commands - import pymake - fpath = path.join(src_dir, 'Makefile') - pymake.main(['-f', fpath] + sys.argv[2:]) - # Stop to avoid setup.py raising non-standard command error - sys.exit(0) - -setup(use_scm_version=True) diff --git a/tests/conftest.py b/tests/conftest.py index 6717044..7960fa0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,24 +18,10 @@ def pretest_posttest(): n = len(tqdm._instances) if n: tqdm._instances.clear() - raise EnvironmentError( - "{0} `tqdm` instances still in existence PRE-test".format(n)) + raise EnvironmentError(f"{n} `tqdm` instances still in existence PRE-test") yield if getattr(tqdm, "_instances", False): n = len(tqdm._instances) if n: tqdm._instances.clear() - raise EnvironmentError( - "{0} `tqdm` instances still in existence POST-test".format(n)) - - -if sys.version_info[0] > 2: - @fixture - def capsysbin(capsysbinary): - """alias for capsysbinary (py3)""" - return capsysbinary -else: - @fixture - def capsysbin(capsys): - """alias for capsys (py2)""" - return capsys + raise EnvironmentError(f"{n} `tqdm` instances still in existence POST-test") diff --git a/tests/py37_asyncio.py b/tests/py37_asyncio.py deleted file mode 100644 index 8bf61e7..0000000 --- a/tests/py37_asyncio.py +++ /dev/null @@ -1,128 +0,0 @@ -import asyncio -from functools import partial -from sys import platform -from time import time - -from tqdm.asyncio import tarange, tqdm_asyncio - -from .tests_tqdm import StringIO, closing, mark - -tqdm = partial(tqdm_asyncio, miniters=0, mininterval=0) -trange = partial(tarange, miniters=0, mininterval=0) -as_completed = partial(tqdm_asyncio.as_completed, miniters=0, mininterval=0) -gather = partial(tqdm_asyncio.gather, miniters=0, mininterval=0) - - -def count(start=0, step=1): - i = start - while True: - new_start = yield i - if new_start is None: - i += step - else: - i = new_start - - -async def acount(*args, **kwargs): - for i in count(*args, **kwargs): - yield i - - -@mark.asyncio -async def test_break(): - """Test asyncio break""" - pbar = tqdm(count()) - async for _ in pbar: - break - pbar.close() - - -@mark.asyncio -async def test_generators(capsys): - """Test asyncio generators""" - with tqdm(count(), desc="counter") as pbar: - async for i in pbar: - if i >= 8: - break - _, err = capsys.readouterr() - assert '9it' in err - - with tqdm(acount(), desc="async_counter") as pbar: - async for i in pbar: - if i >= 8: - break - _, err = capsys.readouterr() - assert '9it' in err - - -@mark.asyncio -async def test_range(): - """Test asyncio range""" - with closing(StringIO()) as our_file: - async for _ in tqdm(range(9), desc="range", file=our_file): - pass - assert '9/9' in our_file.getvalue() - our_file.seek(0) - our_file.truncate() - - async for _ in trange(9, desc="trange", file=our_file): - pass - assert '9/9' in our_file.getvalue() - - -@mark.asyncio -async def test_nested(): - """Test asyncio nested""" - with closing(StringIO()) as our_file: - async for _ in tqdm(trange(9, desc="inner", file=our_file), - desc="outer", file=our_file): - pass - assert 'inner: 100%' in our_file.getvalue() - assert 'outer: 100%' in our_file.getvalue() - - -@mark.asyncio -async def test_coroutines(): - """Test asyncio coroutine.send""" - with closing(StringIO()) as our_file: - with tqdm(count(), file=our_file) as pbar: - async for i in pbar: - if i == 9: - pbar.send(-10) - elif i < 0: - assert i == -9 - break - assert '10it' in our_file.getvalue() - - -@mark.slow -@mark.asyncio -@mark.parametrize("tol", [0.2 if platform.startswith("darwin") else 0.1]) -async def test_as_completed(capsys, tol): - """Test asyncio as_completed""" - for retry in range(3): - t = time() - skew = time() - t - for i in as_completed([asyncio.sleep(0.01 * i) for i in range(30, 0, -1)]): - await i - t = time() - t - 2 * skew - try: - assert 0.3 * (1 - tol) < t < 0.3 * (1 + tol), t - _, err = capsys.readouterr() - assert '30/30' in err - except AssertionError: - if retry == 2: - raise - - -async def double(i): - return i * 2 - - -@mark.asyncio -async def test_gather(capsys): - """Test asyncio gather""" - res = await gather(*map(double, range(30))) - _, err = capsys.readouterr() - assert '30/30' in err - assert res == list(range(0, 30 * 2, 2)) diff --git a/tests/tests_asyncio.py b/tests/tests_asyncio.py index 6f08926..bdef569 100644 --- a/tests/tests_asyncio.py +++ b/tests/tests_asyncio.py @@ -1,11 +1,129 @@ -"""Tests `tqdm.asyncio` on `python>=3.7`.""" -import sys - -if sys.version_info[:2] > (3, 6): - from .py37_asyncio import * # NOQA, pylint: disable=wildcard-import -else: - from .tests_tqdm import skip - try: - skip("async not supported", allow_module_level=True) - except TypeError: - pass +"""Tests `tqdm.asyncio`.""" +import asyncio +from functools import partial +from sys import platform +from time import time + +from tqdm.asyncio import tarange, tqdm_asyncio + +from .tests_tqdm import StringIO, closing, mark + +tqdm = partial(tqdm_asyncio, miniters=0, mininterval=0) +trange = partial(tarange, miniters=0, mininterval=0) +as_completed = partial(tqdm_asyncio.as_completed, miniters=0, mininterval=0) +gather = partial(tqdm_asyncio.gather, miniters=0, mininterval=0) + + +def count(start=0, step=1): + i = start + while True: + new_start = yield i + if new_start is None: + i += step + else: + i = new_start + + +async def acount(*args, **kwargs): + for i in count(*args, **kwargs): + yield i + + +@mark.asyncio +async def test_break(): + """Test asyncio break""" + pbar = tqdm(count()) + async for _ in pbar: + break + pbar.close() + + +@mark.asyncio +async def test_generators(capsys): + """Test asyncio generators""" + with tqdm(count(), desc="counter") as pbar: + async for i in pbar: + if i >= 8: + break + _, err = capsys.readouterr() + assert '9it' in err + + with tqdm(acount(), desc="async_counter") as pbar: + async for i in pbar: + if i >= 8: + break + _, err = capsys.readouterr() + assert '9it' in err + + +@mark.asyncio +async def test_range(): + """Test asyncio range""" + with closing(StringIO()) as our_file: + async for _ in tqdm(range(9), desc="range", file=our_file): + pass + assert '9/9' in our_file.getvalue() + our_file.seek(0) + our_file.truncate() + + async for _ in trange(9, desc="trange", file=our_file): + pass + assert '9/9' in our_file.getvalue() + + +@mark.asyncio +async def test_nested(): + """Test asyncio nested""" + with closing(StringIO()) as our_file: + async for _ in tqdm(trange(9, desc="inner", file=our_file), + desc="outer", file=our_file): + pass + assert 'inner: 100%' in our_file.getvalue() + assert 'outer: 100%' in our_file.getvalue() + + +@mark.asyncio +async def test_coroutines(): + """Test asyncio coroutine.send""" + with closing(StringIO()) as our_file: + with tqdm(count(), file=our_file) as pbar: + async for i in pbar: + if i == 9: + pbar.send(-10) + elif i < 0: + assert i == -9 + break + assert '10it' in our_file.getvalue() + + +@mark.slow +@mark.asyncio +@mark.parametrize("tol", [0.2 if platform.startswith("darwin") else 0.1]) +async def test_as_completed(capsys, tol): + """Test asyncio as_completed""" + for retry in range(3): + t = time() + skew = time() - t + for i in as_completed([asyncio.sleep(0.01 * i) for i in range(30, 0, -1)]): + await i + t = time() - t - 2 * skew + try: + assert 0.3 * (1 - tol) < t < 0.3 * (1 + tol), t + _, err = capsys.readouterr() + assert '30/30' in err + except AssertionError: + if retry == 2: + raise + + +async def double(i): + return i * 2 + + +@mark.asyncio +async def test_gather(capsys): + """Test asyncio gather""" + res = await gather(*map(double, range(30))) + _, err = capsys.readouterr() + assert '30/30' in err + assert res == list(range(0, 30 * 2, 2)) diff --git a/tests/tests_contrib.py b/tests/tests_contrib.py index 69a1cad..65c8cd5 100644 --- a/tests/tests_contrib.py +++ b/tests/tests_contrib.py @@ -1,8 +1,6 @@ """ Tests for `tqdm.contrib`. """ -import sys - import pytest from tqdm import tqdm @@ -47,12 +45,9 @@ def test_zip(tqdm_kwargs): with closing(StringIO()) as our_file: a = range(9) b = [i + 1 for i in a] - if sys.version_info[:1] < (3,): - assert tzip(a, b, file=our_file, **tqdm_kwargs) == zip(a, b) - else: - gen = tzip(a, b, file=our_file, **tqdm_kwargs) - assert gen != list(zip(a, b)) - assert list(gen) == list(zip(a, b)) + gen = tzip(a, b, file=our_file, **tqdm_kwargs) + assert gen != list(zip(a, b)) + assert list(gen) == list(zip(a, b)) @pytest.mark.parametrize("tqdm_kwargs", [{}, {"tqdm_class": tqdm}]) @@ -61,11 +56,6 @@ def test_map(tqdm_kwargs): with closing(StringIO()) as our_file: a = range(9) b = [i + 1 for i in a] - if sys.version_info[:1] < (3,): - assert tmap(lambda x: x + 1, a, file=our_file, **tqdm_kwargs) == map( - incr, a - ) - else: - gen = tmap(lambda x: x + 1, a, file=our_file, **tqdm_kwargs) - assert gen != b - assert list(gen) == b + gen = tmap(lambda x: x + 1, a, file=our_file, **tqdm_kwargs) + assert gen != b + assert list(gen) == b diff --git a/tests/tests_contrib_logging.py b/tests/tests_contrib_logging.py index 6f675dd..b8d60b6 100644 --- a/tests/tests_contrib_logging.py +++ b/tests/tests_contrib_logging.py @@ -1,7 +1,5 @@ # pylint: disable=missing-module-docstring, missing-class-docstring # pylint: disable=missing-function-docstring, no-self-use -from __future__ import absolute_import - import logging import logging.handlers import sys diff --git a/tests/tests_dask.py b/tests/tests_dask.py index 8bf4b64..16992bf 100644 --- a/tests/tests_dask.py +++ b/tests/tests_dask.py @@ -1,5 +1,3 @@ -from __future__ import division - from time import sleep from .tests_tqdm import importorskip, mark diff --git a/tests/tests_keras.py b/tests/tests_keras.py index 220f946..5b7db28 100644 --- a/tests/tests_keras.py +++ b/tests/tests_keras.py @@ -1,5 +1,3 @@ -from __future__ import division - from .tests_tqdm import importorskip, mark pytestmark = mark.slow @@ -41,8 +39,8 @@ def test_keras(capsys): verbose=0)]) _, res = capsys.readouterr() assert "training: " in res - assert "{epochs}/{epochs}".format(epochs=epochs) in res - assert "{batches}/{batches}".format(batches=batches) not in res + assert f"{epochs}/{epochs}" in res + assert f"{batches}/{batches}" not in res # full (epoch and batch) progress model.fit( @@ -60,8 +58,8 @@ def test_keras(capsys): verbose=2)]) _, res = capsys.readouterr() assert "training: " in res - assert "{epochs}/{epochs}".format(epochs=epochs) in res - assert "{batches}/{batches}".format(batches=batches) in res + assert f"{epochs}/{epochs}" in res + assert f"{batches}/{batches}" in res # auto-detect epochs and batches model.fit( @@ -73,8 +71,8 @@ def test_keras(capsys): callbacks=[TqdmCallback(desc="training", verbose=2)]) _, res = capsys.readouterr() assert "training: " in res - assert "{epochs}/{epochs}".format(epochs=epochs) in res - assert "{batches}/{batches}".format(batches=batches) in res + assert f"{epochs}/{epochs}" in res + assert f"{batches}/{batches}" in res # continue training (start from epoch != 0) initial_epoch = 3 @@ -89,5 +87,5 @@ def test_keras(capsys): miniters=1, mininterval=0, maxinterval=0)]) _, res = capsys.readouterr() assert "training: " in res - assert "{epochs}/{epochs}".format(epochs=initial_epoch - 1) not in res - assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert f"{initial_epoch - 1}/{initial_epoch - 1}" not in res + assert f"{epochs}/{epochs}" in res diff --git a/tests/tests_main.py b/tests/tests_main.py index 0523cc7..039ce33 100644 --- a/tests/tests_main.py +++ b/tests/tests_main.py @@ -8,17 +8,17 @@ from os import linesep from tqdm.cli import TqdmKeyError, TqdmTypeError, main from tqdm.utils import IS_WIN -from .tests_tqdm import BytesIO, _range, closing, mark, raises +from .tests_tqdm import BytesIO, closing, mark, raises def restore_sys(func): - """Decorates `func(capsysbin)` to save & restore `sys.(stdin|argv)`.""" + """Decorates `func(capsysbinary)` to save & restore `sys.(stdin|argv)`.""" @wraps(func) - def inner(capsysbin): - """function requiring capsysbin which may alter `sys.(stdin|argv)`""" + def inner(capsysbinary): + """function requiring capsysbinary which may alter `sys.(stdin|argv)`""" _SYS = sys.stdin, sys.argv try: - res = func(capsysbin) + res = func(capsysbinary) finally: sys.stdin, sys.argv = _SYS return res @@ -58,7 +58,7 @@ def test_main_import(): N = 123 _SYS = sys.stdin, sys.argv # test direct import - sys.stdin = [str(i).encode() for i in _range(N)] + sys.stdin = [str(i).encode() for i in range(N)] sys.argv = ['', '--desc', 'Test CLI import', '--ascii', 'True', '--unit_scale', 'True'] try: @@ -68,19 +68,19 @@ def test_main_import(): @restore_sys -def test_main_bytes(capsysbin): +def test_main_bytes(capsysbinary): """Test CLI --bytes""" N = 123 # test --delim - IN_DATA = '\0'.join(map(str, _range(N))).encode() + IN_DATA = '\0'.join(map(str, range(N))).encode() with closing(BytesIO()) as sys.stdin: sys.stdin.write(IN_DATA) # sys.stdin.write(b'\xff') # TODO sys.stdin.seek(0) main(sys.stderr, ['--desc', 'Test CLI delim', '--ascii', 'True', '--delim', r'\0', '--buf_size', '64']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert out == IN_DATA assert str(N) + "it" in err.decode("U8") @@ -90,27 +90,26 @@ def test_main_bytes(capsysbin): sys.stdin.write(IN_DATA) sys.stdin.seek(0) main(sys.stderr, ['--ascii', '--bytes=True', '--unit_scale', 'False']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert out == IN_DATA assert str(len(IN_DATA)) + "B" in err.decode("U8") -@mark.skipif(sys.version_info[0] == 2, reason="no caplog on py2") -def test_main_log(capsysbin, caplog): +def test_main_log(capsysbinary, caplog): """Test CLI --log""" _SYS = sys.stdin, sys.argv N = 123 - sys.stdin = [(str(i) + '\n').encode() for i in _range(N)] + sys.stdin = [(str(i) + '\n').encode() for i in range(N)] IN_DATA = b''.join(sys.stdin) try: with caplog.at_level(logging.INFO): main(sys.stderr, ['--log', 'INFO']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA and b"123/123" in err assert not caplog.record_tuples with caplog.at_level(logging.DEBUG): main(sys.stderr, ['--log', 'DEBUG']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA and b"123/123" in err assert caplog.record_tuples finally: @@ -118,39 +117,39 @@ def test_main_log(capsysbin, caplog): @restore_sys -def test_main(capsysbin): +def test_main(capsysbinary): """Test misc CLI options""" N = 123 - sys.stdin = [(str(i) + '\n').encode() for i in _range(N)] + sys.stdin = [(str(i) + '\n').encode() for i in range(N)] IN_DATA = b''.join(sys.stdin) # test --tee main(sys.stderr, ['--mininterval', '0', '--miniters', '1']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA and b"123/123" in err assert N <= len(err.split(b"\r")) < N + 5 len_err = len(err) main(sys.stderr, ['--tee', '--mininterval', '0', '--miniters', '1']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA and b"123/123" in err # spaces to clear intermediate lines could increase length assert len_err + len(norm(out)) <= len(err) # test --null main(sys.stderr, ['--null']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert not out and b"123/123" in err # test integer --update main(sys.stderr, ['--update']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA assert (str(N // 2 * N) + "it").encode() in err, "expected arithmetic sum formula" # test integer --update_to main(sys.stderr, ['--update-to']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA assert (str(N - 1) + "it").encode() in err assert (str(N) + "it").encode() not in err @@ -161,23 +160,23 @@ def test_main(capsysbin): # test integer --update --delim sys.stdin.seek(0) main(sys.stderr, ['--update', '--delim', 'D']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert out == IN_DATA.replace(b'\n', b'D') assert (str(N // 2 * N) + "it").encode() in err, "expected arithmetic sum" # test integer --update_to --delim sys.stdin.seek(0) main(sys.stderr, ['--update-to', '--delim', 'D']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert out == IN_DATA.replace(b'\n', b'D') assert (str(N - 1) + "it").encode() in err assert (str(N) + "it").encode() not in err # test float --update_to - sys.stdin = [(str(i / 2.0) + '\n').encode() for i in _range(N)] + sys.stdin = [(str(i / 2.0) + '\n').encode() for i in range(N)] IN_DATA = b''.join(sys.stdin) main(sys.stderr, ['--update-to']) - out, err = capsysbin.readouterr() + out, err = capsysbinary.readouterr() assert norm(out) == IN_DATA assert (str((N - 1) / 2.0) + "it").encode() in err assert (str(N / 2.0) + "it").encode() not in err @@ -213,30 +212,30 @@ def test_comppath(tmp_path): @restore_sys -def test_exceptions(capsysbin): +def test_exceptions(capsysbinary): """Test CLI Exceptions""" N = 123 - sys.stdin = [str(i) + '\n' for i in _range(N)] + sys.stdin = [str(i) + '\n' for i in range(N)] IN_DATA = ''.join(sys.stdin).encode() with raises(TqdmKeyError, match="bad_arg_u_ment"): main(sys.stderr, argv=['-ascii', '-unit_scale', '--bad_arg_u_ment', 'foo']) - out, _ = capsysbin.readouterr() + out, _ = capsysbinary.readouterr() assert norm(out) == IN_DATA with raises(TqdmTypeError, match="invalid_bool_value"): main(sys.stderr, argv=['-ascii', '-unit_scale', 'invalid_bool_value']) - out, _ = capsysbin.readouterr() + out, _ = capsysbinary.readouterr() assert norm(out) == IN_DATA with raises(TqdmTypeError, match="invalid_int_value"): main(sys.stderr, argv=['-ascii', '--total', 'invalid_int_value']) - out, _ = capsysbin.readouterr() + out, _ = capsysbinary.readouterr() assert norm(out) == IN_DATA with raises(TqdmKeyError, match="Can only have one of --"): main(sys.stderr, argv=['--update', '--update_to']) - out, _ = capsysbin.readouterr() + out, _ = capsysbinary.readouterr() assert norm(out) == IN_DATA # test SystemExits diff --git a/tests/tests_pandas.py b/tests/tests_pandas.py index 334a97c..09dff1e 100644 --- a/tests/tests_pandas.py +++ b/tests/tests_pandas.py @@ -4,6 +4,7 @@ from .tests_tqdm import StringIO, closing, importorskip, mark, skip pytestmark = mark.slow +np = importorskip('numpy') random = importorskip('numpy.random') rand = random.rand randint = random.randint @@ -39,8 +40,8 @@ def test_pandas_rolling_expanding(): our_file.seek(0) if our_file.getvalue().count(exres) < 2: our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( - exres + " at least twice.", our_file.read())) + raise AssertionError( + f"\nExpected:\n{exres} at least twice.\nIn:\n{our_file.read()}\n") def test_pandas_series(): @@ -62,10 +63,11 @@ def test_pandas_series(): our_file.seek(0) if our_file.getvalue().count(exres) < 2: our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( - exres + " at least twice.", our_file.read())) + raise AssertionError( + f"\nExpected:\n{exres} at least twice.\nIn:\n{our_file.read()}\n") +@mark.filterwarnings("ignore:DataFrame.applymap has been deprecated:FutureWarning") def test_pandas_data_frame(): """Test pandas.DataFrame.progress_apply and .progress_applymap""" with closing(StringIO()) as our_file: @@ -80,6 +82,12 @@ def test_pandas_data_frame(): res2 = df.applymap(task_func) assert res1.equals(res2) + # map + if hasattr(df, 'map'): # pandas>=2.1.0 + res1 = df.progress_map(task_func) + res2 = df.map(task_func) + assert res1.equals(res2) + # apply unhashable res1 = [] df.progress_apply(res1.extend) @@ -94,8 +102,8 @@ def test_pandas_data_frame(): our_file.seek(0) if our_file.read().count('100%') < 3: our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( - '100% at least three times', our_file.read())) + raise AssertionError( + f"\nExpected:\n100% at least three times\nIn:\n{our_file.read()}\n") # apply_map, apply axis=0, apply axis=1 expects = ['20000/20000', '200/200', '100/100'] @@ -103,10 +111,12 @@ def test_pandas_data_frame(): our_file.seek(0) if our_file.getvalue().count(exres) < 1: our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:\n {1}\n".format( - exres + " at least once.", our_file.read())) + raise AssertionError( + f"\nExpected:\n{exres} at least once.\nIn:\n{our_file.read()}\n") +@mark.filterwarnings( + "ignore:DataFrameGroupBy.apply operated on the grouping columns:DeprecationWarning") def test_pandas_groupby_apply(): """Test pandas.DataFrame.groupby(...).progress_apply""" with closing(StringIO()) as our_file: @@ -119,8 +129,8 @@ def test_pandas_groupby_apply(): dfs.groupby(['a']).progress_apply(lambda x: None) df2 = df = pd.DataFrame({'a': randint(1, 8, 10000), 'b': rand(10000)}) - res1 = df2.groupby("a").apply(max) - res2 = df2.groupby("a").progress_apply(max) + res1 = df2.groupby("a").apply(np.maximum.reduce) + res2 = df2.groupby("a").progress_apply(np.maximum.reduce) assert res1.equals(res2) our_file.seek(0) @@ -130,8 +140,7 @@ def test_pandas_groupby_apply(): nexres = '100%|##########|' if nexres in our_file.read(): our_file.seek(0) - raise AssertionError("\nDid not expect:\n{0}\nIn:{1}\n".format( - nexres, our_file.read())) + raise AssertionError(f"\nDid not expect:\n{nexres}\nIn:{our_file.read()}\n") with closing(StringIO()) as our_file: tqdm.pandas(file=our_file, leave=True, ascii=True) @@ -140,26 +149,28 @@ def test_pandas_groupby_apply(): dfs.loc[0] = [2, 1, 1] dfs['d'] = 100 - expects = ['500/500', '1/1', '4/4', '2/2'] + expects = ['500/500', '1/1', '4/4', '4/4'] dfs.groupby(dfs.index).progress_apply(lambda x: None) dfs.groupby('d').progress_apply(lambda x: None) - dfs.groupby(dfs.columns, axis=1).progress_apply(lambda x: None) - dfs.groupby([2, 2, 1, 1], axis=1).progress_apply(lambda x: None) + dfs.T.groupby(dfs.columns).progress_apply(lambda x: None) + dfs.T.groupby([2, 2, 1, 1]).progress_apply(lambda x: None) our_file.seek(0) if our_file.read().count('100%') < 4: our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( - '100% at least four times', our_file.read())) + raise AssertionError( + f"\nExpected:\n100% at least four times\nIn:\n{our_file.read()}\n") for exres in expects: our_file.seek(0) if our_file.getvalue().count(exres) < 1: our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:\n {1}\n".format( - exres + " at least once.", our_file.read())) + raise AssertionError( + f"\nExpected:\n{exres} at least once.\nIn:\n{our_file.read()}\n") +@mark.filterwarnings( + "ignore:DataFrameGroupBy.apply operated on the grouping columns:DeprecationWarning") def test_pandas_leave(): """Test pandas with `leave=True`""" with closing(StringIO()) as our_file: @@ -172,8 +183,7 @@ def test_pandas_leave(): exres = '100%|##########| 100/100' if exres not in our_file.read(): our_file.seek(0) - raise AssertionError("\nExpected:\n{0}\nIn:{1}\n".format( - exres, our_file.read())) + raise AssertionError(f"\nExpected:\n{exres}\nIn:{our_file.read()}\n") def test_pandas_apply_args_deprecation(): @@ -195,6 +205,8 @@ def test_pandas_apply_args_deprecation(): "keyword arguments instead")) +@mark.filterwarnings( + "ignore:DataFrameGroupBy.apply operated on the grouping columns:DeprecationWarning") def test_pandas_deprecation(): """Test bar object instance as argument deprecation""" try: diff --git a/tests/tests_perf.py b/tests/tests_perf.py index 552a169..a6c4823 100644 --- a/tests/tests_perf.py +++ b/tests/tests_perf.py @@ -1,5 +1,3 @@ -from __future__ import division, print_function - import sys from contextlib import contextmanager from functools import wraps @@ -14,7 +12,7 @@ except ImportError: from tqdm import tqdm, trange -from .tests_tqdm import _range, importorskip, mark, patch_lock, skip +from .tests_tqdm import importorskip, mark, patch_lock, skip pytestmark = mark.slow @@ -98,10 +96,7 @@ def simple_progress(iterable=None, total=None, file=sys.stdout, desc='', def format_interval(t): mins, s = divmod(int(t), 60) h, m = divmod(mins, 60) - if h: - return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) - else: - return '{0:02d}:{1:02d}'.format(m, s) + return f'{h:d}:{m:02d}:{s:02d}' if h else f'{m:02d}:{s:02d}' def update_and_print(i=1): n[0] += i @@ -143,20 +138,15 @@ def simple_progress(iterable=None, total=None, file=sys.stdout, desc='', update_and_print(0) if iterable is not None: return update_and_yield() - else: - return update_and_print + return update_and_print def assert_performance(thresh, name_left, time_left, name_right, time_right): """raises if time_left > thresh * time_right""" if time_left > thresh * time_right: raise ValueError( - ('{name[0]}: {time[0]:f}, ' - '{name[1]}: {time[1]:f}, ' - 'ratio {ratio:f} > {thresh:f}').format( - name=(name_left, name_right), - time=(time_left, time_right), - ratio=time_left / time_right, thresh=thresh)) + f'{name_left}: {time_left:f}, {name_right}: {time_right:f}' + f', ratio {time_left / time_right:f} > {thresh:f}') @retry_on_except() @@ -173,7 +163,7 @@ def test_iter_basic_overhead(): a = 0 with relative_timer() as time_bench: - for i in _range(total): + for i in range(total): a += i sys.stdout.write(str(a)) @@ -188,13 +178,13 @@ def test_manual_basic_overhead(): with tqdm(total=total * 10, leave=True) as t: a = 0 with relative_timer() as time_tqdm: - for i in _range(total): + for i in range(total): a += i t.update(10) a = 0 with relative_timer() as time_bench: - for i in _range(total): + for i in range(total): a += i sys.stdout.write(str(a)) @@ -249,7 +239,7 @@ def test_iter_overhead_hard(): a = 0 with relative_timer() as time_bench: - for i in _range(total): + for i in range(total): a += i sys.stdout.write(("%i" % a) * 40) @@ -265,13 +255,13 @@ def test_manual_overhead_hard(): mininterval=0, maxinterval=0) as t: a = 0 with relative_timer() as time_tqdm: - for i in _range(total): + for i in range(total): a += i t.update(10) a = 0 with relative_timer() as time_bench: - for i in _range(total): + for i in range(total): a += i sys.stdout.write(("%i" % a) * 40) @@ -292,7 +282,7 @@ def test_iter_overhead_simplebar_hard(): assert a == (total ** 2 - total) / 2.0 a = 0 - s = simple_progress(_range(total), leave=True, + s = simple_progress(range(total), leave=True, miniters=1, mininterval=0) with relative_timer() as time_bench: for i in s: @@ -310,7 +300,7 @@ def test_manual_overhead_simplebar_hard(): mininterval=0, maxinterval=0) as t: a = 0 with relative_timer() as time_tqdm: - for i in _range(total): + for i in range(total): a += i t.update(10) @@ -318,7 +308,7 @@ def test_manual_overhead_simplebar_hard(): miniters=1, mininterval=0) a = 0 with relative_timer() as time_bench: - for i in _range(total): + for i in range(total): a += i simplebar_update(10) diff --git a/tests/tests_rich.py b/tests/tests_rich.py index c75e246..2fff78c 100644 --- a/tests/tests_rich.py +++ b/tests/tests_rich.py @@ -1,10 +1,7 @@ """Test `tqdm.rich`.""" -import sys +from .tests_tqdm import importorskip -from .tests_tqdm import importorskip, mark - -@mark.skipif(sys.version_info[:3] < (3, 6, 1), reason="`rich` needs py>=3.6.1") def test_rich_import(): """Test `tqdm.rich` import""" importorskip('tqdm.rich') diff --git a/tests/tests_synchronisation.py b/tests/tests_synchronisation.py index 7ee55fb..0cd9190 100644 --- a/tests/tests_synchronisation.py +++ b/tests/tests_synchronisation.py @@ -1,13 +1,9 @@ -from __future__ import division - -import sys from functools import wraps from threading import Event from time import sleep, time from tqdm import TMonitor, tqdm, trange -from .tests_perf import retry_on_except from .tests_tqdm import StringIO, closing, importorskip, patch_lock, skip @@ -37,18 +33,13 @@ class Time(object): sleep(0.000001) # sleep to allow interrupt (instead of pass) -def FakeEvent(): +class FakeEvent(Event): """patched `threading.Event` where `wait()` uses `Time.fake_sleep()`""" - event = Event() # not a class in py2 so can't inherit - - def wait(timeout=None): + def wait(self, timeout=None): """uses Time.fake_sleep""" if timeout is not None: Time.fake_sleep(timeout) - return event.is_set() - - event.wait = wait - return event + return self.is_set() def patch_sleep(func): @@ -206,19 +197,11 @@ def test_imap(): assert res[-1] == 100 -# py2: locks won't propagate to incr_bar so may cause `AttributeError` -@retry_on_except(n=3 if sys.version_info < (3,) else 1, check_cpu_time=False) @patch_lock(thread=True) def test_threadpool(): """Test concurrent.futures.ThreadPoolExecutor""" ThreadPoolExecutor = importorskip('concurrent.futures').ThreadPoolExecutor with ThreadPoolExecutor(8) as pool: - try: - res = list(tqdm(pool.map(incr_bar, range(100)), disable=True)) - except AttributeError: - if sys.version_info < (3,): - skip("not supported on py2") - else: - raise + res = list(tqdm(pool.map(incr_bar, range(100)), disable=True)) assert sum(res) == sum(range(1, 101)) diff --git a/tests/tests_tqdm.py b/tests/tests_tqdm.py index bba457a..d0ba14f 100644 --- a/tests/tests_tqdm.py +++ b/tests/tests_tqdm.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Advice: use repr(our_file.read()) to print the full output of tqdm # (else '\r' will replace the previous lines and you'll see only the latest. -from __future__ import print_function - import csv import os import re @@ -37,16 +35,6 @@ if getattr(StringIO, '__exit__', False) and getattr(StringIO, '__enter__', False else: from contextlib import closing -try: - _range = xrange -except NameError: - _range = range - -try: - _unicode = unicode -except NameError: - _unicode = str - nt_and_no_colorama = False if os.name == 'nt': try: @@ -201,6 +189,8 @@ def test_format_num(): assert float(format_num(1337)) == 1337 assert format_num(int(1e6)) == '1e+6' assert format_num(1239876) == '1' '239' '876' + assert format_num(0.00001234) == '1.23e-5' + assert format_num(-0.1234) == '-0.123' def test_format_meter(): @@ -271,11 +261,10 @@ def test_format_meter(): 20, 100, 12, ncols=14, rate=8.1, bar_format=r'{l_bar}{bar}|{n_fmt}/{total_fmt}') == " 20%|" + unich(0x258d) + " |20/100" # Check wide characters - if sys.version_info >= (3,): - assert format_meter(0, 1000, 13, ncols=68, prefix='fullwidth: ') == ( - "fullwidth: 0%| | 0/1000 [00:13>> Got:\n{1}\n===".format( - exres + ', ...it/s]\n', our_file.getvalue())) + raise AssertionError(f"\n<<< Expected:\n{exres}, ...it/s]\n>>> Got:\n{res}\n===") # Closing after the output stream has closed with closing(StringIO()) as our_file: @@ -976,7 +964,7 @@ def test_smoothing(): # -- Test disabling smoothing with closing(StringIO()) as our_file: - with tqdm(_range(3), file=our_file, smoothing=None, leave=True) as t: + with tqdm(range(3), file=our_file, smoothing=None, leave=True) as t: cpu_timify(t, timer) for _ in t: @@ -987,11 +975,11 @@ def test_smoothing(): # 1st case: no smoothing (only use average) with closing(StringIO()) as our_file2: with closing(StringIO()) as our_file: - t = tqdm(_range(3), file=our_file2, smoothing=None, leave=True, + t = tqdm(range(3), file=our_file2, smoothing=None, leave=True, miniters=1, mininterval=0) cpu_timify(t, timer) - with tqdm(_range(3), file=our_file, smoothing=None, leave=True, + with tqdm(range(3), file=our_file, smoothing=None, leave=True, miniters=1, mininterval=0) as t2: cpu_timify(t2, timer) @@ -1017,11 +1005,11 @@ def test_smoothing(): # 2nd case: use max smoothing (= instant rate) with closing(StringIO()) as our_file2: with closing(StringIO()) as our_file: - t = tqdm(_range(3), file=our_file2, smoothing=1, leave=True, + t = tqdm(range(3), file=our_file2, smoothing=1, leave=True, miniters=1, mininterval=0) cpu_timify(t, timer) - with tqdm(_range(3), file=our_file, smoothing=1, leave=True, + with tqdm(range(3), file=our_file, smoothing=1, leave=True, miniters=1, mininterval=0) as t2: cpu_timify(t2, timer) @@ -1040,11 +1028,11 @@ def test_smoothing(): # 3rd case: use medium smoothing with closing(StringIO()) as our_file2: with closing(StringIO()) as our_file: - t = tqdm(_range(3), file=our_file2, smoothing=0.5, leave=True, + t = tqdm(range(3), file=our_file2, smoothing=0.5, leave=True, miniters=1, mininterval=0) cpu_timify(t, timer) - t2 = tqdm(_range(3), file=our_file, smoothing=0.5, leave=True, + t2 = tqdm(range(3), file=our_file, smoothing=0.5, leave=True, miniters=1, mininterval=0) cpu_timify(t2, timer) @@ -1098,7 +1086,7 @@ def test_bar_format(): with closing(StringIO()) as our_file: bar_format = r'hello world' with tqdm(ascii=False, bar_format=bar_format, file=our_file) as t: - assert isinstance(t.bar_format, _unicode) + assert isinstance(t.bar_format, str) def test_custom_format(): @@ -1127,7 +1115,7 @@ def test_eta(capsys): bar_format='{l_bar}{eta:%Y-%m-%d}'): pass _, err = capsys.readouterr() - assert "\r100%|{eta:%Y-%m-%d}\n".format(eta=dt.now()) in err + assert f"\r100%|{dt.now():%Y-%m-%d}\n" in err def test_unpause(): @@ -1257,7 +1245,7 @@ def test_position(): t1 = tqdm(desc='pos0 bar', position=0, **kwargs) t2 = tqdm(desc='pos1 bar', position=1, **kwargs) t3 = tqdm(desc='pos2 bar', position=2, **kwargs) - for _ in _range(2): + for _ in range(2): t1.update() t3.update() t2.update() @@ -1360,7 +1348,7 @@ def test_deprecated_gui(): # t.close() # len(tqdm._instances) += 1 # undo the close() decrement - t = tqdm(_range(3), gui=True, file=our_file, miniters=1, mininterval=0) + t = tqdm(range(3), gui=True, file=our_file, miniters=1, mininterval=0) try: for _ in t: pass @@ -1735,7 +1723,7 @@ def test_external_write(): def test_unit_scale(): """Test numeric `unit_scale`""" with closing(StringIO()) as our_file: - for _ in tqdm(_range(9), unit_scale=9, file=our_file, + for _ in tqdm(range(9), unit_scale=9, file=our_file, miniters=1, mininterval=0): pass out = our_file.getvalue() @@ -1937,7 +1925,7 @@ def test_screen_shape(): def test_initial(): """Test `initial`""" with closing(StringIO()) as our_file: - for _ in tqdm(_range(9), initial=10, total=19, file=our_file, + for _ in tqdm(range(9), initial=10, total=19, file=our_file, miniters=1, mininterval=0): pass out = our_file.getvalue() @@ -1948,7 +1936,7 @@ def test_initial(): def test_colour(): """Test `colour`""" with closing(StringIO()) as our_file: - for _ in tqdm(_range(9), file=our_file, colour="#beefed"): + for _ in tqdm(range(9), file=our_file, colour="#beefed"): pass out = our_file.getvalue() assert '\x1b[38;2;%d;%d;%dm' % (0xbe, 0xef, 0xed) in out @@ -1961,7 +1949,7 @@ def test_colour(): assert "Unknown colour" in str(w[-1].message) with closing(StringIO()) as our_file2: - for _ in tqdm(_range(9), file=our_file2, colour="blue"): + for _ in tqdm(range(9), file=our_file2, colour="blue"): pass out = our_file2.getvalue() assert '\x1b[34m' in out @@ -1977,7 +1965,7 @@ def test_closed(): def test_reversed(capsys): """Test reversed()""" - for _ in reversed(tqdm(_range(9))): + for _ in reversed(tqdm(range(9))): pass out, err = capsys.readouterr() assert not out @@ -1989,7 +1977,7 @@ def test_contains(capsys): """Test __contains__ doesn't iterate""" with tqdm(list(range(9))) as t: assert 9 not in t - assert all(i in t for i in _range(9)) + assert all(i in t for i in range(9)) out, err = capsys.readouterr() assert not out assert ' 0%' in err diff --git a/tests/tests_utils.py b/tests/tests_utils.py new file mode 100644 index 0000000..6cf1e6c --- /dev/null +++ b/tests/tests_utils.py @@ -0,0 +1,51 @@ +from ast import literal_eval +from collections import defaultdict +from typing import Union # py<3.10 + +from tqdm.utils import envwrap + + +def test_envwrap(monkeypatch): + """Test @envwrap (basic)""" + monkeypatch.setenv('FUNC_A', "42") + monkeypatch.setenv('FUNC_TyPe_HiNt', "1337") + monkeypatch.setenv('FUNC_Unused', "x") + + @envwrap("FUNC_") + def func(a=1, b=2, type_hint: int = None): + return a, b, type_hint + + assert (42, 2, 1337) == func() + assert (99, 2, 1337) == func(a=99) + + +def test_envwrap_types(monkeypatch): + """Test @envwrap(types)""" + monkeypatch.setenv('FUNC_notype', "3.14159") + + @envwrap("FUNC_", types=defaultdict(lambda: literal_eval)) + def func(notype=None): + return notype + + assert 3.14159 == func() + + monkeypatch.setenv('FUNC_number', "1") + monkeypatch.setenv('FUNC_string', "1") + + @envwrap("FUNC_", types={'number': int}) + def nofallback(number=None, string=None): + return number, string + + assert 1, "1" == nofallback() + + +def test_envwrap_annotations(monkeypatch): + """Test @envwrap with typehints""" + monkeypatch.setenv('FUNC_number', "1.1") + monkeypatch.setenv('FUNC_string', "1.1") + + @envwrap("FUNC_") + def annotated(number: Union[int, float] = None, string: int = None): + return number, string + + assert 1.1, "1.1" == annotated() diff --git a/tox.ini b/tox.ini index d77a5a8..cd5dc1b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,20 +4,16 @@ # and then run "tox" from this directory. [tox] -# deprecation warning: py{27,py2,34,35,36} -envlist=py{27,34,35,36,37,38,39,310,py2,py3}{,-tf}{,-keras}, perf, setup.py +envlist=py{37,38,39,310,311,py3}{,-tf}{,-keras}, perf, check isolated_build=True [gh-actions] python= - 2.7: py27 - 3.5: py35 - 3.6: py36 3.7: py37 3.8: py38 3.9: py39 3.10: py310 - pypy-2.7: pypy2 + 3.11: py311 pypy-3.7: pypy3 [gh-actions:env] PLATFORM= @@ -26,12 +22,11 @@ PLATFORM= [core] deps= pytest - py3{4,5,6}: pytest<7 pytest-cov pytest-timeout - py3{7,8,9,10}: pytest-asyncio - py3{6,7,8,9,10}: ipywidgets - py3{7,8,9,10}: git+https://github.com/casperdcl/nbval.git@master#egg=nbval + pytest-asyncio + ipywidgets + git+https://github.com/casperdcl/nbval.git@master#egg=nbval coverage coveralls codecov @@ -41,7 +36,7 @@ commands= - codacy report -l Python -r coverage.xml --partial [testenv] -passenv=TOXENV CI GITHUB_* CODECOV_* COVERALLS_* CODACY_* HOME +passenv=TOXENV,CI,GITHUB_*,CODECOV_*,COVERALLS_*,CODACY_*,HOME deps= {[core]deps} cython @@ -49,26 +44,17 @@ deps= matplotlib numpy pandas + rich tf: tensorflow!=2.5.0 - !py27-keras: keras - py27-keras: keras<2.5 - py35-keras: keras<2.7 - py27-tf: protobuf<3.18 - py3{6,7,8,9,10}: rich + keras: keras commands= - py3{4,5,6}: pytest --cov=tqdm --cov-report=xml --cov-report=term -k "not perf" -o addopts= -v --tb=short -rxs -W=error --durations=0 --durations-min=0.1 - py3{7,8,9,10}: pytest --cov=tqdm --cov-report= tests_notebook.ipynb --nbval --nbval-current-env -W=ignore --nbval-sanitize-with=setup.cfg - py3{7,8,9,10}: pytest --cov=tqdm --cov-report=xml --cov-report=term --cov-append -k "not perf" + pytest --cov=tqdm --cov-report= -W=ignore tests_notebook.ipynb --nbval --current-env --sanitize-with=.meta/nbval.ini + pytest --cov=tqdm --cov-report=xml --cov-report=term --cov-append -k "not perf" {[core]commands} allowlist_externals=codacy -[testenv:py{27,py2}{,-tf}{,-keras}] -commands= - pytest --cov=tqdm --cov-report=xml --cov-report=term -k "not perf" -o addopts= -v --tb=short -rxs -W=error --durations=10 - {[core]commands} - # no cython/numpy/pandas -[testenv:py{34,py2,py3}] +[testenv:pypy3] deps={[core]deps} [testenv:perf] @@ -78,11 +64,12 @@ deps= pytest-asyncio commands=pytest -k perf -[testenv:setup.py] +[testenv:check] deps= - docutils - pygments + build + twine py-make>=0.1.0 commands= - {envpython} setup.py check --restructuredtext --metadata --strict - {envpython} setup.py make none + {envpython} -m build + {envpython} -m twine check dist/* + {envpython} -m pymake -h diff --git a/tqdm.egg-info/PKG-INFO b/tqdm.egg-info/PKG-INFO deleted file mode 100644 index d796709..0000000 --- a/tqdm.egg-info/PKG-INFO +++ /dev/null @@ -1,1581 +0,0 @@ -Metadata-Version: 2.1 -Name: tqdm -Version: 4.64.1 -Summary: Fast, Extensible Progress Meter -Home-page: https://tqdm.github.io -Maintainer: tqdm developers -Maintainer-email: python.tqdm@gmail.com -License: MPLv2.0, MIT Licences -Project-URL: Changelog, https://tqdm.github.io/releases -Project-URL: Source, https://github.com/tqdm/tqdm -Project-URL: Wiki, https://github.com/tqdm/tqdm/wiki -Keywords: progressbar,progressmeter,progress,bar,meter,rate,eta,console,terminal,time -Platform: any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Console -Classifier: Environment :: MacOS X -Classifier: Environment :: Other Environment -Classifier: Environment :: Win32 (MS Windows) -Classifier: Environment :: X11 Applications -Classifier: Framework :: IPython -Classifier: Framework :: Jupyter -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Education -Classifier: Intended Audience :: End Users/Desktop -Classifier: Intended Audience :: Other Audience -Classifier: Intended Audience :: System Administrators -Classifier: License :: OSI Approved :: MIT License -Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) -Classifier: Operating System :: MacOS -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Operating System :: Microsoft -Classifier: Operating System :: Microsoft :: MS-DOS -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: POSIX -Classifier: Operating System :: POSIX :: BSD -Classifier: Operating System :: POSIX :: BSD :: FreeBSD -Classifier: Operating System :: POSIX :: Linux -Classifier: Operating System :: POSIX :: SunOS/Solaris -Classifier: Operating System :: Unix -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: Implementation -Classifier: Programming Language :: Python :: Implementation :: IronPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Programming Language :: Unix Shell -Classifier: Topic :: Desktop Environment -Classifier: Topic :: Education :: Computer Aided Instruction (CAI) -Classifier: Topic :: Education :: Testing -Classifier: Topic :: Office/Business -Classifier: Topic :: Other/Nonlisted Topic -Classifier: Topic :: Software Development :: Build Tools -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Software Development :: Pre-processors -Classifier: Topic :: Software Development :: User Interfaces -Classifier: Topic :: System :: Installation/Setup -Classifier: Topic :: System :: Logging -Classifier: Topic :: System :: Monitoring -Classifier: Topic :: System :: Shells -Classifier: Topic :: Terminals -Classifier: Topic :: Utilities -Provides: tqdm -Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7 -Description-Content-Type: text/x-rst -Provides-Extra: dev -Provides-Extra: slack -Provides-Extra: telegram -Provides-Extra: notebook -License-File: LICENCE - -|Logo| - -tqdm -==== - -|Py-Versions| |Versions| |Conda-Forge-Status| |Docker| |Snapcraft| - -|Build-Status| |Coverage-Status| |Branch-Coverage-Status| |Codacy-Grade| |Libraries-Rank| |PyPI-Downloads| - -|LICENCE| |OpenHub-Status| |binder-demo| |awesome-python| - -``tqdm`` derives from the Arabic word *taqaddum* (تقدّم) which can mean "progress," -and is an abbreviation for "I love you so much" in Spanish (*te quiero demasiado*). - -Instantly make your loops show a smart progress meter - just wrap any -iterable with ``tqdm(iterable)``, and you're done! - -.. code:: python - - from tqdm import tqdm - for i in tqdm(range(10000)): - ... - -``76%|████████████████████████        | 7568/10000 [00:33<00:10, 229.00it/s]`` - -``trange(N)`` can be also used as a convenient shortcut for -``tqdm(range(N))``. - -|Screenshot| - |Video| |Slides| |Merch| - -It can also be executed as a module with pipes: - -.. code:: sh - - $ seq 9999999 | tqdm --bytes | wc -l - 75.2MB [00:00, 217MB/s] - 9999999 - - $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \ - > backup.tgz - 32%|██████████▍ | 8.89G/27.9G [00:42<01:31, 223MB/s] - -Overhead is low -- about 60ns per iteration (80ns with ``tqdm.gui``), and is -unit tested against performance regression. -By comparison, the well-established -`ProgressBar `__ has -an 800ns/iter overhead. - -In addition to its low overhead, ``tqdm`` uses smart algorithms to predict -the remaining time and to skip unnecessary iteration displays, which allows -for a negligible overhead in most cases. - -``tqdm`` works on any platform -(Linux, Windows, Mac, FreeBSD, NetBSD, Solaris/SunOS), -in any console or in a GUI, and is also friendly with IPython/Jupyter notebooks. - -``tqdm`` does not require any dependencies (not even ``curses``!), just -Python and an environment supporting ``carriage return \r`` and -``line feed \n`` control characters. - ------------------------------------------- - -.. contents:: Table of contents - :backlinks: top - :local: - - -Installation ------------- - -Latest PyPI stable release -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -|Versions| |PyPI-Downloads| |Libraries-Dependents| - -.. code:: sh - - pip install tqdm - -Latest development release on GitHub -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -|GitHub-Status| |GitHub-Stars| |GitHub-Commits| |GitHub-Forks| |GitHub-Updated| - -Pull and install pre-release ``devel`` branch: - -.. code:: sh - - pip install "git+https://github.com/tqdm/tqdm.git@devel#egg=tqdm" - -Latest Conda release -~~~~~~~~~~~~~~~~~~~~ - -|Conda-Forge-Status| - -.. code:: sh - - conda install -c conda-forge tqdm - -Latest Snapcraft release -~~~~~~~~~~~~~~~~~~~~~~~~ - -|Snapcraft| - -There are 3 channels to choose from: - -.. code:: sh - - snap install tqdm # implies --stable, i.e. latest tagged release - snap install tqdm --candidate # master branch - snap install tqdm --edge # devel branch - -Note that ``snap`` binaries are purely for CLI use (not ``import``-able), and -automatically set up ``bash`` tab-completion. - -Latest Docker release -~~~~~~~~~~~~~~~~~~~~~ - -|Docker| - -.. code:: sh - - docker pull tqdm/tqdm - docker run -i --rm tqdm/tqdm --help - -Other -~~~~~ - -There are other (unofficial) places where ``tqdm`` may be downloaded, particularly for CLI use: - -|Repology| - -.. |Repology| image:: https://repology.org/badge/tiny-repos/python:tqdm.svg - :target: https://repology.org/project/python:tqdm/versions - -Changelog ---------- - -The list of all changes is available either on GitHub's Releases: -|GitHub-Status|, on the -`wiki `__, or on the -`website `__. - - -Usage ------ - -``tqdm`` is very versatile and can be used in a number of ways. -The three main ones are given below. - -Iterable-based -~~~~~~~~~~~~~~ - -Wrap ``tqdm()`` around any iterable: - -.. code:: python - - from tqdm import tqdm - from time import sleep - - text = "" - for char in tqdm(["a", "b", "c", "d"]): - sleep(0.25) - text = text + char - -``trange(i)`` is a special optimised instance of ``tqdm(range(i))``: - -.. code:: python - - from tqdm import trange - - for i in trange(100): - sleep(0.01) - -Instantiation outside of the loop allows for manual control over ``tqdm()``: - -.. code:: python - - pbar = tqdm(["a", "b", "c", "d"]) - for char in pbar: - sleep(0.25) - pbar.set_description("Processing %s" % char) - -Manual -~~~~~~ - -Manual control of ``tqdm()`` updates using a ``with`` statement: - -.. code:: python - - with tqdm(total=100) as pbar: - for i in range(10): - sleep(0.1) - pbar.update(10) - -If the optional variable ``total`` (or an iterable with ``len()``) is -provided, predictive stats are displayed. - -``with`` is also optional (you can just assign ``tqdm()`` to a variable, -but in this case don't forget to ``del`` or ``close()`` at the end: - -.. code:: python - - pbar = tqdm(total=100) - for i in range(10): - sleep(0.1) - pbar.update(10) - pbar.close() - -Module -~~~~~~ - -Perhaps the most wonderful use of ``tqdm`` is in a script or on the command -line. Simply inserting ``tqdm`` (or ``python -m tqdm``) between pipes will pass -through all ``stdin`` to ``stdout`` while printing progress to ``stderr``. - -The example below demonstrate counting the number of lines in all Python files -in the current directory, with timing information included. - -.. code:: sh - - $ time find . -name '*.py' -type f -exec cat \{} \; | wc -l - 857365 - - real 0m3.458s - user 0m0.274s - sys 0m3.325s - - $ time find . -name '*.py' -type f -exec cat \{} \; | tqdm | wc -l - 857366it [00:03, 246471.31it/s] - 857365 - - real 0m3.585s - user 0m0.862s - sys 0m3.358s - -Note that the usual arguments for ``tqdm`` can also be specified. - -.. code:: sh - - $ find . -name '*.py' -type f -exec cat \{} \; | - tqdm --unit loc --unit_scale --total 857366 >> /dev/null - 100%|█████████████████████████████████| 857K/857K [00:04<00:00, 246Kloc/s] - -Backing up a large directory? - -.. code:: sh - - $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \ - > backup.tgz - 44%|██████████████▊ | 153M/352M [00:14<00:18, 11.0MB/s] - -This can be beautified further: - -.. code:: sh - - $ BYTES="$(du -sb docs/ | cut -f1)" - $ tar -cf - docs/ \ - | tqdm --bytes --total "$BYTES" --desc Processing | gzip \ - | tqdm --bytes --total "$BYTES" --desc Compressed --position 1 \ - > ~/backup.tgz - Processing: 100%|██████████████████████| 352M/352M [00:14<00:00, 30.2MB/s] - Compressed: 42%|█████████▎ | 148M/352M [00:14<00:19, 10.9MB/s] - -Or done on a file level using 7-zip: - -.. code:: sh - - $ 7z a -bd -r backup.7z docs/ | grep Compressing \ - | tqdm --total $(find docs/ -type f | wc -l) --unit files \ - | 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 --------------------- - -|GitHub-Issues| - -The most common issues relate to excessive output on multiple lines, instead -of a neat one-line progress bar. - -- Consoles in general: require support for carriage return (``CR``, ``\r``). -- Nested progress bars: - - * Consoles in general: require support for moving cursors up to the - previous line. For example, - `IDLE `__, - `ConEmu `__ and - `PyCharm `__ (also - `here `__, - `here `__, and - `here `__) - lack full support. - * Windows: additionally may require the Python module ``colorama`` - to ensure nested bars stay within their respective lines. - -- Unicode: - - * Environments which report that they support unicode will have solid smooth - 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 - either normal-width unicode characters being incorrectly displayed as - "wide", or some unicode characters not rendering. - -- Wrapping generators: - - * Generator wrapper functions tend to hide the length of iterables. - ``tqdm`` does not. - * Replace ``tqdm(enumerate(...))`` with ``enumerate(tqdm(...))`` or - ``tqdm(enumerate(x), total=len(x), ...)``. - The same applies to ``numpy.ndenumerate``. - * Replace ``tqdm(zip(a, b))`` with ``zip(tqdm(a), b)`` or even - ``zip(tqdm(a), tqdm(b))``. - * The same applies to ``itertools``. - * Some useful convenience functions can be found under ``tqdm.contrib``. - -- `Hanging pipes in python2 `__: - when using ``tqdm`` on the CLI, you may need to use Python 3.5+ for correct - buffering. -- `No intermediate output in docker-compose `__: - use ``docker-compose run`` instead of ``docker-compose up`` and ``tty: true``. - -If you come across any other difficulties, browse and file |GitHub-Issues|. - -Documentation -------------- - -|Py-Versions| |README-Hits| (Since 19 May 2016) - -.. code:: python - - class tqdm(): - """ - 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. - """ - - 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): - -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 - If (default: None) and ``file`` is unspecified, - bytes will be written in Python 2. If ``True`` will also write - bytes. In all other cases will default to 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. - -Extra CLI Options -~~~~~~~~~~~~~~~~~ - -* delim : chr, optional - Delimiting character [default: '\n']. Use '\0' for null. - N.B.: on Windows systems, Python converts '\n' to '\r\n'. -* buf_size : int, optional - String buffer size in bytes [default: 256] - used when ``delim`` is specified. -* 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()``. Note that this is slow - (~2e5 it/s) since every input must be decoded as a number. -* update_to : bool, optional - If true, will treat input as total elapsed iterations, - i.e. numbers to assign to ``self.n``. Note that this is slow - (~2e5 it/s) since every input must be decoded as a number. -* null : bool, optional - If true, will discard input (no stdout). -* manpath : str, optional - Directory in which to install tqdm man pages. -* comppath : str, optional - Directory in which to place tqdm completion. -* log : str, optional - CRITICAL|FATAL|ERROR|WARN(ING)|[default: 'INFO']|DEBUG|NOTSET. - -Returns -~~~~~~~ - -* out : decorated iterator. - -.. code:: python - - class tqdm(): - def update(self, n=1): - """ - Manually update the progress bar, useful for streams - such as reading files. - E.g.: - >>> t = tqdm(total=filesize) # Initialise - >>> for current_buffer in stream: - ... ... - ... t.update(len(current_buffer)) - >>> t.close() - The last line is highly recommended, but possibly not necessary if - ``t.update()`` will be called in such a way that ``filesize`` will be - exactly reached and printed. - - Parameters - ---------- - n : int or float, optional - 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): - """Cleanup and (if leave=False) close the progressbar.""" - - def clear(self, nomove=False): - """Clear current bar display.""" - - def refresh(self): - """ - Force refresh the display of this bar. - - Parameters - ---------- - nolock : bool, optional - If ``True``, does not lock. - If [default: ``False``]: calls ``acquire()`` on internal lock. - lock_args : tuple, optional - Passed to internal lock's ``acquire()``. - If specified, will only ``display()`` if ``acquire()`` returns ``True``. - """ - - def unpause(self): - """Restart tqdm timer from last print time.""" - - def reset(self, total=None): - """ - Resets to 0 iterations for repeated use. - - Consider combining with ``leave=True``. - - Parameters - ---------- - total : int or float, optional. Total to use for the new bar. - """ - - def set_description(self, desc=None, refresh=True): - """ - Set/modify description of the progress bar. - - Parameters - ---------- - desc : str, optional - refresh : bool, optional - Forces refresh [default: True]. - """ - - def set_postfix(self, ordered_dict=None, refresh=True, **tqdm_kwargs): - """ - Set/modify postfix (additional stats) - with automatic formatting based on datatype. - - Parameters - ---------- - ordered_dict : dict or OrderedDict, optional - refresh : bool, optional - Forces refresh [default: True]. - kwargs : dict, optional - """ - - @classmethod - def write(cls, s, file=sys.stdout, end="\n"): - """Print a message via tqdm (without overlap with bars).""" - - @property - def format_dict(self): - """Public API for read-only member access.""" - - def display(self, msg=None, pos=None): - """ - Use ``self.sp`` to display ``msg`` in the specified ``pos``. - - Consider overloading this function when inheriting to use e.g.: - ``self.some_frontend(**self.format_dict)`` instead of ``self.sp``. - - Parameters - ---------- - msg : str, optional. What to display (default: ``repr(self)``). - pos : int, optional. Position to ``moveto`` - (default: ``abs(self.pos)``). - """ - - @classmethod - @contextmanager - def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs): - """ - stream : file-like object. - method : str, "read" or "write". The result of ``read()`` and - the first argument of ``write()`` should have a ``len()``. - - >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: - ... while True: - ... chunk = fobj.read(chunk_size) - ... if not chunk: - ... break - """ - - @classmethod - def pandas(cls, *targs, **tqdm_kwargs): - """Registers the current `tqdm` class with `pandas`.""" - - def trange(*args, **tqdm_kwargs): - """ - A shortcut for `tqdm(xrange(*args), **tqdm_kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - -Convenience Functions -~~~~~~~~~~~~~~~~~~~~~ - -.. code:: python - - def tqdm.contrib.tenumerate(iterable, start=0, total=None, - tqdm_class=tqdm.auto.tqdm, **tqdm_kwargs): - """Equivalent of `numpy.ndenumerate` or builtin `enumerate`.""" - - def tqdm.contrib.tzip(iter1, *iter2plus, **tqdm_kwargs): - """Equivalent of builtin `zip`.""" - - def tqdm.contrib.tmap(function, *sequences, **tqdm_kwargs): - """Equivalent of builtin `map`.""" - -Submodules -~~~~~~~~~~ - -.. code:: python - - class tqdm.notebook.tqdm(tqdm.tqdm): - """IPython/Jupyter Notebook widget.""" - - class tqdm.auto.tqdm(tqdm.tqdm): - """Automatically chooses beween `tqdm.notebook` and `tqdm.tqdm`.""" - - class tqdm.asyncio.tqdm(tqdm.tqdm): - """Asynchronous version.""" - @classmethod - def as_completed(cls, fs, *, loop=None, timeout=None, total=None, - **tqdm_kwargs): - """Wrapper for `asyncio.as_completed`.""" - - class tqdm.gui.tqdm(tqdm.tqdm): - """Matplotlib GUI version.""" - - class tqdm.tk.tqdm(tqdm.tqdm): - """Tkinter GUI version.""" - - class tqdm.rich.tqdm(tqdm.tqdm): - """`rich.progress` version.""" - - class tqdm.keras.TqdmCallback(keras.callbacks.Callback): - """Keras callback for epoch and batch progress.""" - - class tqdm.dask.TqdmCallback(dask.callbacks.Callback): - """Dask callback for task progress.""" - - -``contrib`` -+++++++++++ - -The ``tqdm.contrib`` package also contains experimental modules: - -- ``tqdm.contrib.itertools``: Thin wrappers around ``itertools`` -- ``tqdm.contrib.concurrent``: Thin wrappers around ``concurrent.futures`` -- ``tqdm.contrib.slack``: Posts to `Slack `__ bots -- ``tqdm.contrib.discord``: Posts to `Discord `__ bots -- ``tqdm.contrib.telegram``: Posts to `Telegram `__ bots -- ``tqdm.contrib.bells``: Automagically enables all optional features - - * ``auto``, ``pandas``, ``slack``, ``discord``, ``telegram`` - -Examples and Advanced Usage ---------------------------- - -- See the `examples `__ - folder; -- import the module and run ``help()``; -- consult the `wiki `__; - - * this has an - `excellent article `__ - on how to make a **great** progressbar; - -- check out the `slides from PyData London `__, or -- run the |binder-demo|. - -Description and additional stats -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Custom information can be displayed and updated dynamically on ``tqdm`` bars -with the ``desc`` and ``postfix`` arguments: - -.. code:: python - - from tqdm import tqdm, trange - from random import random, randint - from time import sleep - - with trange(10) as t: - for i in t: - # Description will be displayed on the left - t.set_description('GEN %i' % i) - # Postfix will be displayed on the right, - # formatted automatically based on argument's datatype - t.set_postfix(loss=random(), gen=randint(1,999), str='h', - lst=[1, 2]) - sleep(0.1) - - with tqdm(total=10, bar_format="{postfix[0]} {postfix[1][value]:>8.2g}", - postfix=["Batch", dict(value=0)]) as t: - for i in range(10): - sleep(0.1) - t.postfix[1]["value"] = i / 2 - t.update() - -Points to remember when using ``{postfix[...]}`` in the ``bar_format`` string: - -- ``postfix`` also needs to be passed as an initial argument in a compatible - format, and -- ``postfix`` will be auto-converted to a string if it is a ``dict``-like - object. To prevent this behaviour, insert an extra item into the dictionary - where the key is not a string. - -Additional ``bar_format`` parameters may also be defined by overriding -``format_dict``, and the bar itself may be modified using ``ascii``: - -.. code:: python - - from tqdm import tqdm - class TqdmExtraFormat(tqdm): - """Provides a `total_time` format parameter""" - @property - def format_dict(self): - d = super(TqdmExtraFormat, self).format_dict - total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1) - d.update(total_time=self.format_interval(total_time) + " in total") - return d - - for i in TqdmExtraFormat( - range(9), ascii=" .oO0", - bar_format="{total_time}: {percentage:.0f}%|{bar}{r_bar}"): - if i == 4: - break - -.. code:: - - 00:00 in total: 44%|0000. | 4/9 [00:00<00:00, 962.93it/s] - -Note that ``{bar}`` also supports a format specifier ``[width][type]``. - -- ``width`` - - * unspecified (default): automatic to fill ``ncols`` - * ``int >= 0``: fixed width overriding ``ncols`` logic - * ``int < 0``: subtract from the automatic default - -- ``type`` - - * ``a``: ascii (``ascii=True`` override) - * ``u``: unicode (``ascii=False`` override) - * ``b``: blank (``ascii=" "`` override) - -This means a fixed bar with right-justified text may be created by using: -``bar_format="{l_bar}{bar:10}|{bar:-10b}right-justified"`` - -Nested progress bars -~~~~~~~~~~~~~~~~~~~~ - -``tqdm`` supports nested progress bars. Here's an example: - -.. code:: python - - from tqdm.auto import trange - from time import sleep - - for i in trange(4, desc='1st loop'): - for j in trange(5, desc='2nd loop'): - for k in trange(50, desc='3rd loop', leave=False): - sleep(0.01) - -For manual control over positioning (e.g. for multi-processing use), -you may specify ``position=n`` where ``n=0`` for the outermost bar, -``n=1`` for the next, and so on. -However, it's best to check if ``tqdm`` can work without manual ``position`` -first. - -.. code:: python - - from time import sleep - from tqdm import trange, tqdm - from multiprocessing import Pool, RLock, freeze_support - - L = list(range(9)) - - def progresser(n): - interval = 0.001 / (n + 2) - total = 5000 - text = "#{}, est. {:<04.2}s".format(n, interval * total) - for _ in trange(total, desc=text, position=n): - sleep(interval) - - if __name__ == '__main__': - freeze_support() # for Windows support - tqdm.set_lock(RLock()) # for managing output contention - p = Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) - p.map(progresser, L) - -Note that in Python 3, ``tqdm.write`` is thread-safe: - -.. code:: python - - from time import sleep - from tqdm import tqdm, trange - from concurrent.futures import ThreadPoolExecutor - - L = list(range(9)) - - def progresser(n): - interval = 0.001 / (n + 2) - total = 5000 - text = "#{}, est. {:<04.2}s".format(n, interval * total) - for _ in trange(total, desc=text): - sleep(interval) - if n == 6: - tqdm.write("n == 6 completed.") - tqdm.write("`tqdm.write()` is thread-safe in py3!") - - if __name__ == '__main__': - with ThreadPoolExecutor() as p: - p.map(progresser, L) - -Hooks and callbacks -~~~~~~~~~~~~~~~~~~~ - -``tqdm`` can easily support callbacks/hooks and manual updates. -Here's an example with ``urllib``: - -**``urllib.urlretrieve`` documentation** - - | [...] - | If present, the hook function will be called once - | on establishment of the network connection and once after each block read - | thereafter. The hook will be passed three arguments; a count of blocks - | transferred so far, a block size in bytes, and the total size of the file. - | [...] - -.. code:: python - - import urllib, os - from tqdm import tqdm - urllib = getattr(urllib, 'request', urllib) - - class TqdmUpTo(tqdm): - """Provides `update_to(n)` which uses `tqdm.update(delta_n)`.""" - def update_to(self, b=1, bsize=1, tsize=None): - """ - b : int, optional - Number of blocks transferred so far [default: 1]. - bsize : int, optional - Size of each block (in tqdm units) [default: 1]. - tsize : int, optional - Total size (in tqdm units). If [default: None] remains unchanged. - """ - if tsize is not None: - self.total = tsize - 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, - desc=eg_link.split('/')[-1]) as t: # all optional kwargs - urllib.urlretrieve(eg_link, filename=os.devnull, - reporthook=t.update_to, data=None) - t.total = t.n - -Inspired by `twine#242 `__. -Functional alternative in -`examples/tqdm_wget.py `__. - -It is recommend to use ``miniters=1`` whenever there is potentially -large differences in iteration speed (e.g. downloading a file over -a patchy connection). - -**Wrapping read/write methods** - -To measure throughput through a file-like object's ``read`` or ``write`` -methods, use ``CallbackIOWrapper``: - -.. code:: python - - from tqdm.auto import tqdm - from tqdm.utils import CallbackIOWrapper - - with tqdm(total=file_obj.size, - unit='B', unit_scale=True, unit_divisor=1024) as t: - fobj = CallbackIOWrapper(t.update, file_obj, "read") - while True: - chunk = fobj.read(chunk_size) - if not chunk: - break - t.reset() - # ... continue to use `t` for something else - -Alternatively, use the even simpler ``wrapattr`` convenience function, -which would condense both the ``urllib`` and ``CallbackIOWrapper`` examples -down to: - -.. code:: python - - import urllib, os - from tqdm import tqdm - - eg_link = "https://caspersci.uk.to/matryoshka.zip" - response = getattr(urllib, 'request', urllib).urlopen(eg_link) - with tqdm.wrapattr(open(os.devnull, "wb"), "write", - miniters=1, desc=eg_link.split('/')[-1], - total=getattr(response, 'length', None)) as fout: - for chunk in response: - fout.write(chunk) - -The ``requests`` equivalent is nearly identical: - -.. code:: python - - import requests, os - from tqdm import tqdm - - eg_link = "https://caspersci.uk.to/matryoshka.zip" - response = requests.get(eg_link, stream=True) - with tqdm.wrapattr(open(os.devnull, "wb"), "write", - miniters=1, desc=eg_link.split('/')[-1], - total=int(response.headers.get('content-length', 0))) as fout: - 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 - -``asyncio`` -~~~~~~~~~~~ - -Note that ``break`` isn't currently caught by asynchronous iterators. -This means that ``tqdm`` cannot clean up after itself in this case: - -.. code:: python - - from tqdm.asyncio import tqdm - - async for i in tqdm(range(9)): - if i == 2: - break - -Instead, either call ``pbar.close()`` manually or use the context manager syntax: - -.. code:: python - - from tqdm.asyncio import tqdm - - with tqdm(range(9)) as pbar: - async for i in pbar: - if i == 2: - break - -Pandas Integration -~~~~~~~~~~~~~~~~~~ - -Due to popular demand we've added support for ``pandas`` -- here's an example -for ``DataFrame.progress_apply`` and ``DataFrameGroupBy.progress_apply``: - -.. code:: python - - import pandas as pd - import numpy as np - from tqdm import tqdm - - df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) - - # Register `pandas.progress_apply` and `pandas.Series.map_apply` with `tqdm` - # (can use `tqdm.gui.tqdm`, `tqdm.notebook.tqdm`, optional kwargs, etc.) - tqdm.pandas(desc="my bar!") - - # Now you can use `progress_apply` instead of `apply` - # and `progress_map` instead of `map` - df.progress_apply(lambda x: x**2) - # can also groupby: - # df.groupby(0).progress_apply(lambda x: x**2) - -In case you're interested in how this works (and how to modify it for your -own callbacks), see the -`examples `__ -folder or import the module and run ``help()``. - -Keras Integration -~~~~~~~~~~~~~~~~~ - -A ``keras`` callback is also available: - -.. code:: python - - from tqdm.keras import TqdmCallback - - ... - - model.fit(..., verbose=0, callbacks=[TqdmCallback()]) - -Dask Integration -~~~~~~~~~~~~~~~~ - -A ``dask`` callback is also available: - -.. code:: python - - from tqdm.dask import TqdmCallback - - with TqdmCallback(desc="compute"): - ... - arr.compute() - - # or use callback globally - cb = TqdmCallback(desc="global") - cb.register() - arr.compute() - -IPython/Jupyter Integration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -IPython/Jupyter is supported via the ``tqdm.notebook`` submodule: - -.. code:: python - - from tqdm.notebook import trange, tqdm - from time import sleep - - for i in trange(3, desc='1st loop'): - for j in tqdm(range(100), desc='2nd loop'): - sleep(0.01) - -In addition to ``tqdm`` features, the submodule provides a native Jupyter -widget (compatible with IPython v1-v4 and Jupyter), fully working nested bars -and colour hints (blue: normal, green: completed, red: error/interrupt, -light blue: no ETA); as demonstrated below. - -|Screenshot-Jupyter1| -|Screenshot-Jupyter2| -|Screenshot-Jupyter3| - -The ``notebook`` version supports percentage or pixels for overall width -(e.g.: ``ncols='100%'`` or ``ncols='480px'``). - -It is also possible to let ``tqdm`` automatically choose between -console or notebook versions by using the ``autonotebook`` submodule: - -.. code:: python - - from tqdm.autonotebook import tqdm - tqdm.pandas() - -Note that this will issue a ``TqdmExperimentalWarning`` if run in a notebook -since it is not meant to be possible to distinguish between ``jupyter notebook`` -and ``jupyter console``. Use ``auto`` instead of ``autonotebook`` to suppress -this warning. - -Note that notebooks will display the bar in the cell where it was created. -This may be a different cell from the one where it is used. -If this is not desired, either - -- delay the creation of the bar to the cell where it must be displayed, or -- create the bar with ``display=False``, and in a later cell call - ``display(bar.container)``: - -.. code:: python - - from tqdm.notebook import tqdm - pbar = tqdm(..., display=False) - -.. code:: python - - # different cell - display(pbar.container) - -The ``keras`` callback has a ``display()`` method which can be used likewise: - -.. code:: python - - from tqdm.keras import TqdmCallback - cbk = TqdmCallback(display=False) - -.. code:: python - - # different cell - cbk.display() - model.fit(..., verbose=0, callbacks=[cbk]) - -Another possibility is to have a single bar (near the top of the notebook) -which is constantly re-used (using ``reset()`` rather than ``close()``). -For this reason, the notebook version (unlike the CLI version) does not -automatically call ``close()`` upon ``Exception``. - -.. code:: python - - from tqdm.notebook import tqdm - pbar = tqdm() - -.. code:: python - - # different cell - iterable = range(100) - pbar.reset(total=len(iterable)) # initialise with new `total` - for i in iterable: - pbar.update() - pbar.refresh() # force print final status but don't `close()` - -Custom Integration -~~~~~~~~~~~~~~~~~~ - -To change the default arguments (such as making ``dynamic_ncols=True``), -simply use built-in Python magic: - -.. code:: python - - from functools import partial - from tqdm import tqdm as std_tqdm - tqdm = partial(std_tqdm, dynamic_ncols=True) - -For further customisation, -``tqdm`` may be inherited from to create custom callbacks (as with the -``TqdmUpTo`` example `above <#hooks-and-callbacks>`__) or for custom frontends -(e.g. GUIs such as notebook or plotting packages). In the latter case: - -1. ``def __init__()`` to call ``super().__init__(..., gui=True)`` to disable - terminal ``status_printer`` creation. -2. Redefine: ``close()``, ``clear()``, ``display()``. - -Consider overloading ``display()`` to use e.g. -``self.frontend(**self.format_dict)`` instead of ``self.sp(repr(self))``. - -Some submodule examples of inheritance: - -- `tqdm/notebook.py `__ -- `tqdm/gui.py `__ -- `tqdm/tk.py `__ -- `tqdm/contrib/slack.py `__ -- `tqdm/contrib/discord.py `__ -- `tqdm/contrib/telegram.py `__ - -Dynamic Monitor/Meter -~~~~~~~~~~~~~~~~~~~~~ - -You can use a ``tqdm`` as a meter which is not monotonically increasing. -This could be because ``n`` decreases (e.g. a CPU usage monitor) or ``total`` -changes. - -One example would be recursively searching for files. The ``total`` is the -number of objects found so far, while ``n`` is the number of those objects which -are files (rather than folders): - -.. code:: python - - from tqdm import tqdm - import os.path - - def find_files_recursively(path, show_progress=True): - files = [] - # total=1 assumes `path` is a file - t = tqdm(total=1, unit="file", disable=not show_progress) - if not os.path.exists(path): - raise IOError("Cannot find:" + path) - - def append_found_file(f): - files.append(f) - t.update() - - def list_found_dir(path): - """returns os.listdir(path) assuming os.path.isdir(path)""" - listing = os.listdir(path) - # subtract 1 since a "file" we found was actually this directory - t.total += len(listing) - 1 - # fancy way to give info without forcing a refresh - t.set_postfix(dir=path[-10:], refresh=False) - t.update(0) # may trigger a refresh - return listing - - def recursively_search(path): - if os.path.isdir(path): - for f in list_found_dir(path): - recursively_search(os.path.join(path, f)) - else: - append_found_file(path) - - recursively_search(path) - t.set_postfix(dir=path) - t.close() - return files - -Using ``update(0)`` is a handy way to let ``tqdm`` decide when to trigger a -display refresh to avoid console spamming. - -Writing messages -~~~~~~~~~~~~~~~~ - -This is a work in progress (see -`#737 `__). - -Since ``tqdm`` uses a simple printing mechanism to display progress bars, -you should not write any message in the terminal using ``print()`` while -a progressbar is open. - -To write messages in the terminal without any collision with ``tqdm`` bar -display, a ``.write()`` method is provided: - -.. code:: python - - from tqdm.auto import tqdm, trange - from time import sleep - - bar = trange(10) - for i in bar: - # Print using tqdm class method .write() - sleep(0.1) - if not (i % 3): - tqdm.write("Done task %i" % i) - # Can also use bar.write() - -By default, this will print to standard output ``sys.stdout``. but you can -specify any file-like object using the ``file`` argument. For example, this -can be used to redirect the messages writing to a log file or class. - -Redirecting writing -~~~~~~~~~~~~~~~~~~~ - -If using a library that can print messages to the console, editing the library -by replacing ``print()`` with ``tqdm.write()`` may not be desirable. -In that case, redirecting ``sys.stdout`` to ``tqdm.write()`` is an option. - -To redirect ``sys.stdout``, create a file-like class that will write -any input string to ``tqdm.write()``, and supply the arguments -``file=sys.stdout, dynamic_ncols=True``. - -A reusable canonical example is given below: - -.. code:: python - - from time import sleep - import contextlib - import sys - from tqdm import tqdm - from tqdm.contrib import DummyTqdmFile - - - @contextlib.contextmanager - def std_out_err_redirect_tqdm(): - orig_out_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err) - yield orig_out_err[0] - # Relay exceptions - except Exception as exc: - raise exc - # Always restore sys.stdout/err if necessary - finally: - sys.stdout, sys.stderr = orig_out_err - - def some_fun(i): - print("Fee, fi, fo,".split()[i]) - - # Redirect stdout to tqdm.write() (don't forget the `as save_stdout`) - with std_out_err_redirect_tqdm() as orig_stdout: - # tqdm needs the original stdout - # and dynamic_ncols=True to autodetect console width - for i in tqdm(range(3), file=orig_stdout, dynamic_ncols=True): - sleep(.5) - some_fun(i) - - # After the `with`, printing is restored - print("Done!") - -Redirecting ``logging`` -~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to ``sys.stdout``/``sys.stderr`` as detailed above, console ``logging`` -may also be redirected to ``tqdm.write()``. - -Warning: if also redirecting ``sys.stdout``/``sys.stderr``, make sure to -redirect ``logging`` first if needed. - -Helper methods are available in ``tqdm.contrib.logging``. For example: - -.. code:: python - - import logging - from tqdm import trange - from tqdm.contrib.logging import logging_redirect_tqdm - - LOG = logging.getLogger(__name__) - - if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - with logging_redirect_tqdm(): - for i in trange(9): - if i == 4: - LOG.info("console logging redirected to `tqdm.write()`") - # logging restored - -Monitoring thread, intervals and miniters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``tqdm`` implements a few tricks to increase efficiency and reduce overhead. - -- Avoid unnecessary frequent bar refreshing: ``mininterval`` defines how long - to wait between each refresh. ``tqdm`` always gets updated in the background, - but it will display only every ``mininterval``. -- Reduce number of calls to check system clock/time. -- ``mininterval`` is more intuitive to configure than ``miniters``. - A clever adjustment system ``dynamic_miniters`` will automatically adjust - ``miniters`` to the amount of iterations that fit into time ``mininterval``. - Essentially, ``tqdm`` will check if it's time to print without actually - checking time. This behaviour can be still be bypassed by manually setting - ``miniters``. - -However, consider a case with a combination of fast and slow iterations. -After a few fast iterations, ``dynamic_miniters`` will set ``miniters`` to a -large number. When iteration rate subsequently slows, ``miniters`` will -remain large and thus reduce display update frequency. To address this: - -- ``maxinterval`` defines the maximum time between display refreshes. - A concurrent monitoring thread checks for overdue updates and forces one - where necessary. - -The monitoring thread should not have a noticeable overhead, and guarantees -updates at least every 10 seconds by default. -This value can be directly changed by setting the ``monitor_interval`` of -any ``tqdm`` instance (i.e. ``t = tqdm.tqdm(...); t.monitor_interval = 2``). -The monitor thread may be disabled application-wide by setting -``tqdm.tqdm.monitor_interval = 0`` before instantiation of any ``tqdm`` bar. - - -Merch ------ - -You can buy `tqdm branded merch `__ now! - -Contributions -------------- - -|GitHub-Commits| |GitHub-Issues| |GitHub-PRs| |OpenHub-Status| |GitHub-Contributions| |CII Best Practices| - -All source code is hosted on `GitHub `__. -Contributions are welcome. - -See the -`CONTRIBUTING `__ -file for more information. - -Developers who have made significant contributions, ranked by *SLoC* -(surviving lines of code, -`git fame `__ ``-wMC --excl '\.(png|gif|jpg)$'``), -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% -Daniel Ecer `de-code `__ ~2% -Richard Sheridan `richardsheridan `__ ~1% -Guangshuo Chen `chengs `__ ~1% -Kyle Altendorf `altendky `__ <1% -Matthew Stevens `mjstevens777 `__ <1% -Hadrien Mary `hadim `__ <1% team member -Noam Yorav-Raphael `noamraph `__ <1% original author -Mikhail Korobov `kmike `__ <1% team member -==================== ======================================================== ==== ================================ - -Ports to Other Languages -~~~~~~~~~~~~~~~~~~~~~~~~ - -A list is available on -`this wiki page `__. - - -LICENCE -------- - -Open Source (OSI approved): |LICENCE| - -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 - :target: https://tqdm.github.io/video -.. |Slides| image:: https://img.tqdm.ml/slides.jpg - :target: https://tqdm.github.io/PyData2019/slides.html -.. |Merch| image:: https://img.tqdm.ml/merch.jpg - :target: https://tqdm.github.io/merch -.. |Build-Status| image:: https://img.shields.io/github/workflow/status/tqdm/tqdm/Test/master?logo=GitHub - :target: https://github.com/tqdm/tqdm/actions?query=workflow%3ATest -.. |Coverage-Status| image:: https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls - :target: https://coveralls.io/github/tqdm/tqdm -.. |Branch-Coverage-Status| image:: https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg - :target: https://codecov.io/gh/tqdm/tqdm -.. |Codacy-Grade| image:: https://app.codacy.com/project/badge/Grade/3f965571598f44549c7818f29cdcf177 - :target: https://www.codacy.com/gh/tqdm/tqdm/dashboard -.. |CII Best Practices| image:: https://bestpractices.coreinfrastructure.org/projects/3264/badge - :target: https://bestpractices.coreinfrastructure.org/projects/3264 -.. |GitHub-Status| image:: https://img.shields.io/github/tag/tqdm/tqdm.svg?maxAge=86400&logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/releases -.. |GitHub-Forks| image:: https://img.shields.io/github/forks/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/network -.. |GitHub-Stars| image:: https://img.shields.io/github/stars/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/stargazers -.. |GitHub-Commits| image:: https://img.shields.io/github/commit-activity/y/tqdm/tqdm.svg?logo=git&logoColor=white - :target: https://github.com/tqdm/tqdm/graphs/commit-activity -.. |GitHub-Issues| image:: https://img.shields.io/github/issues-closed/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/issues?q= -.. |GitHub-PRs| image:: https://img.shields.io/github/issues-pr-closed/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/pulls -.. |GitHub-Contributions| image:: https://img.shields.io/github/contributors/tqdm/tqdm.svg?logo=github&logoColor=white - :target: https://github.com/tqdm/tqdm/graphs/contributors -.. |GitHub-Updated| image:: https://img.shields.io/github/last-commit/tqdm/tqdm/master.svg?logo=github&logoColor=white&label=pushed - :target: https://github.com/tqdm/tqdm/pulse -.. |Gift-Casper| image:: https://img.shields.io/badge/dynamic/json.svg?color=ff69b4&label=gifts%20received&prefix=%C2%A3&query=%24..sum&url=https%3A%2F%2Fcaspersci.uk.to%2Fgifts.json - :target: https://cdcl.ml/sponsor -.. |Versions| image:: https://img.shields.io/pypi/v/tqdm.svg - :target: https://tqdm.github.io/releases -.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/tqdm.svg?label=pypi%20downloads&logo=PyPI&logoColor=white - :target: https://pepy.tech/project/tqdm -.. |Py-Versions| image:: https://img.shields.io/pypi/pyversions/tqdm.svg?logo=python&logoColor=white - :target: https://pypi.org/project/tqdm -.. |Conda-Forge-Status| image:: https://img.shields.io/conda/v/conda-forge/tqdm.svg?label=conda-forge&logo=conda-forge - :target: https://anaconda.org/conda-forge/tqdm -.. |Snapcraft| image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft - :target: https://snapcraft.io/tqdm -.. |Docker| image:: https://img.shields.io/badge/docker-pull-blue.svg?logo=docker&logoColor=white - :target: https://hub.docker.com/r/tqdm/tqdm -.. |Libraries-Rank| image:: https://img.shields.io/librariesio/sourcerank/pypi/tqdm.svg?logo=koding&logoColor=white - :target: https://libraries.io/pypi/tqdm -.. |Libraries-Dependents| image:: https://img.shields.io/librariesio/dependent-repos/pypi/tqdm.svg?logo=koding&logoColor=white - :target: https://github.com/tqdm/tqdm/network/dependents -.. |OpenHub-Status| image:: https://www.openhub.net/p/tqdm/widgets/project_thin_badge?format=gif - :target: https://www.openhub.net/p/tqdm?ref=Thin+badge -.. |awesome-python| image:: https://awesome.re/mentioned-badge.svg - :target: https://github.com/vinta/awesome-python -.. |LICENCE| image:: https://img.shields.io/pypi/l/tqdm.svg - :target: https://raw.githubusercontent.com/tqdm/tqdm/master/LICENCE -.. |DOI| image:: https://img.shields.io/badge/DOI-10.5281/zenodo.595120-blue.svg - :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 diff --git a/tqdm.egg-info/SOURCES.txt b/tqdm.egg-info/SOURCES.txt deleted file mode 100644 index 41d7b89..0000000 --- a/tqdm.egg-info/SOURCES.txt +++ /dev/null @@ -1,88 +0,0 @@ -.pre-commit-config.yaml -.zenodo.json -CODE_OF_CONDUCT.md -CONTRIBUTING.md -DEMO.ipynb -LICENCE -Makefile -README.rst -environment.yml -logo.png -pyproject.toml -setup.cfg -setup.py -tests_notebook.ipynb -tox.ini -examples/7zx.py -examples/async_coroutines.py -examples/coroutine_pipe.py -examples/include_no_requirements.py -examples/pandas_progress_apply.py -examples/paper.bib -examples/paper.md -examples/parallel_bars.py -examples/redirect_print.py -examples/simple_examples.py -examples/tqdm_requests.py -examples/tqdm_wget.py -examples/wrapping_generators.py -tests/__init__.py -tests/conftest.py -tests/py37_asyncio.py -tests/tests_asyncio.py -tests/tests_concurrent.py -tests/tests_contrib.py -tests/tests_contrib_logging.py -tests/tests_dask.py -tests/tests_gui.py -tests/tests_itertools.py -tests/tests_keras.py -tests/tests_main.py -tests/tests_notebook.py -tests/tests_pandas.py -tests/tests_perf.py -tests/tests_rich.py -tests/tests_synchronisation.py -tests/tests_tk.py -tests/tests_tqdm.py -tests/tests_version.py -tqdm/__init__.py -tqdm/__main__.py -tqdm/_dist_ver.py -tqdm/_main.py -tqdm/_monitor.py -tqdm/_tqdm.py -tqdm/_tqdm_gui.py -tqdm/_tqdm_notebook.py -tqdm/_tqdm_pandas.py -tqdm/_utils.py -tqdm/asyncio.py -tqdm/auto.py -tqdm/autonotebook.py -tqdm/cli.py -tqdm/completion.sh -tqdm/dask.py -tqdm/gui.py -tqdm/keras.py -tqdm/notebook.py -tqdm/rich.py -tqdm/std.py -tqdm/tk.py -tqdm/tqdm.1 -tqdm/utils.py -tqdm/version.py -tqdm.egg-info/PKG-INFO -tqdm.egg-info/SOURCES.txt -tqdm.egg-info/dependency_links.txt -tqdm.egg-info/entry_points.txt -tqdm.egg-info/requires.txt -tqdm.egg-info/top_level.txt -tqdm/contrib/__init__.py -tqdm/contrib/bells.py -tqdm/contrib/concurrent.py -tqdm/contrib/discord.py -tqdm/contrib/itertools.py -tqdm/contrib/logging.py -tqdm/contrib/slack.py -tqdm/contrib/telegram.py -tqdm/contrib/utils_worker.py \ No newline at end of file diff --git a/tqdm.egg-info/dependency_links.txt b/tqdm.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/tqdm.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tqdm.egg-info/entry_points.txt b/tqdm.egg-info/entry_points.txt deleted file mode 100644 index 540e60f..0000000 --- a/tqdm.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -tqdm = tqdm.cli:main diff --git a/tqdm.egg-info/requires.txt b/tqdm.egg-info/requires.txt deleted file mode 100644 index 352f7a3..0000000 --- a/tqdm.egg-info/requires.txt +++ /dev/null @@ -1,20 +0,0 @@ - -[:platform_system == "Windows"] -colorama - -[:python_version < "3.7"] -importlib_resources - -[dev] -py-make>=0.1.0 -twine -wheel - -[notebook] -ipywidgets>=6 - -[slack] -slack-sdk - -[telegram] -requests diff --git a/tqdm.egg-info/top_level.txt b/tqdm.egg-info/top_level.txt deleted file mode 100644 index 78620c4..0000000 --- a/tqdm.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -tqdm diff --git a/tqdm/__init__.py b/tqdm/__init__.py index a021d16..8081f77 100644 --- a/tqdm/__init__.py +++ b/tqdm/__init__.py @@ -29,10 +29,7 @@ def tqdm_notebook(*args, **kwargs): # pragma: no cover def tnrange(*args, **kwargs): # pragma: no cover - """ - A shortcut for `tqdm.notebook.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" from warnings import warn from .notebook import trange as _tnrange diff --git a/tqdm/_dist_ver.py b/tqdm/_dist_ver.py deleted file mode 100644 index 1c2d004..0000000 --- a/tqdm/_dist_ver.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '4.64.1' diff --git a/tqdm/_utils.py b/tqdm/_utils.py index 2cf1090..385e849 100644 --- a/tqdm/_utils.py +++ b/tqdm/_utils.py @@ -2,10 +2,9 @@ from warnings import warn from .std import TqdmDeprecationWarning from .utils import ( # NOQA, pylint: disable=unused-import - CUR_OS, IS_NIX, IS_WIN, RE_ANSI, Comparable, FormatReplace, SimpleTextIOWrapper, _basestring, - _environ_cols_wrapper, _is_ascii, _is_utf, _range, _screen_shape_linux, _screen_shape_tput, - _screen_shape_windows, _screen_shape_wrapper, _supports_unicode, _term_move_up, _unich, - _unicode, colorama) + CUR_OS, IS_NIX, IS_WIN, RE_ANSI, Comparable, FormatReplace, SimpleTextIOWrapper, + _environ_cols_wrapper, _is_ascii, _is_utf, _screen_shape_linux, _screen_shape_tput, + _screen_shape_windows, _screen_shape_wrapper, _supports_unicode, _term_move_up, colorama) warn("This function will be removed in tqdm==5.0.0\n" "Please use `tqdm.utils.*` instead of `tqdm._utils.*`", diff --git a/tqdm/asyncio.py b/tqdm/asyncio.py index 97c5f88..ddc89b8 100644 --- a/tqdm/asyncio.py +++ b/tqdm/asyncio.py @@ -18,7 +18,7 @@ __all__ = ['tqdm_asyncio', 'tarange', 'tqdm', 'trange'] class tqdm_asyncio(std_tqdm): """ - Asynchronous-friendly version of tqdm (Python 3.6+). + Asynchronous-friendly version of tqdm. """ def __init__(self, iterable=None, *args, **kwargs): super(tqdm_asyncio, self).__init__(iterable, *args, **kwargs) diff --git a/tqdm/auto.py b/tqdm/auto.py index cffca20..206c440 100644 --- a/tqdm/auto.py +++ b/tqdm/auto.py @@ -4,7 +4,7 @@ Enables multiple commonly used features. Method resolution order: - `tqdm.autonotebook` without import warnings -- `tqdm.asyncio` on Python3.6+ +- `tqdm.asyncio` - `tqdm.std` base class Usage: @@ -12,7 +12,6 @@ Usage: >>> for i in trange(10): ... ... """ -import sys import warnings from .std import TqdmExperimentalWarning @@ -20,25 +19,22 @@ from .std import TqdmExperimentalWarning with warnings.catch_warnings(): warnings.simplefilter("ignore", category=TqdmExperimentalWarning) from .autonotebook import tqdm as notebook_tqdm - from .autonotebook import trange as notebook_trange - -if sys.version_info[:2] < (3, 6): - tqdm = notebook_tqdm - trange = notebook_trange -else: # Python3.6+ - from .asyncio import tqdm as asyncio_tqdm - from .std import tqdm as std_tqdm - - if notebook_tqdm != std_tqdm: - class tqdm(notebook_tqdm, asyncio_tqdm): # pylint: disable=inconsistent-mro - pass - else: - tqdm = asyncio_tqdm - - def trange(*args, **kwargs): - """ - A shortcut for `tqdm.auto.tqdm(range(*args), **kwargs)`. - """ - return tqdm(range(*args), **kwargs) + +from .asyncio import tqdm as asyncio_tqdm +from .std import tqdm as std_tqdm + +if notebook_tqdm != std_tqdm: + class tqdm(notebook_tqdm, asyncio_tqdm): # pylint: disable=inconsistent-mro + pass +else: + tqdm = asyncio_tqdm + + +def trange(*args, **kwargs): + """ + A shortcut for `tqdm.auto.tqdm(range(*args), **kwargs)`. + """ + return tqdm(range(*args), **kwargs) + __all__ = ["tqdm", "trange"] diff --git a/tqdm/cli.py b/tqdm/cli.py index 3ed25fb..1223d49 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*(?= (3, 7): - # share lock in case workers are already using `tqdm` - pool_kwargs.update(initializer=tqdm_class.set_lock, initargs=(lk,)) - map_args = {} - if not (3, 0) < sys_version < (3, 5): - map_args.update(chunksize=chunksize) - with PoolExecutor(**pool_kwargs) as ex: - return list(tqdm_class(ex.map(fn, *iterables, **map_args), **kwargs)) + # share lock in case workers are already using `tqdm` + with PoolExecutor(max_workers=max_workers, initializer=tqdm_class.set_lock, + initargs=(lk,)) as ex: + return list(tqdm_class(ex.map(fn, *iterables, chunksize=chunksize), **kwargs)) def thread_map(fn, *iterables, **tqdm_kwargs): diff --git a/tqdm/contrib/discord.py b/tqdm/contrib/discord.py index 0edd35c..1e41308 100644 --- a/tqdm/contrib/discord.py +++ b/tqdm/contrib/discord.py @@ -6,10 +6,8 @@ Usage: >>> 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) """ -from __future__ import absolute_import - import logging from os import getenv @@ -19,7 +17,6 @@ except ImportError: raise ImportError("Please `pip install disco-py`") from ..auto import tqdm as tqdm_auto -from ..utils import _range from .utils_worker import MonoWorker __author__ = {"github.com/": ["casperdcl"]} @@ -113,11 +110,8 @@ class tqdm_discord(tqdm_auto): def tdrange(*args, **kwargs): - """ - A shortcut for `tqdm.contrib.discord.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_discord(_range(*args), **kwargs) + """Shortcut for `tqdm.contrib.discord.tqdm(range(*args), **kwargs)`.""" + return tqdm_discord(range(*args), **kwargs) # Aliases diff --git a/tqdm/contrib/itertools.py b/tqdm/contrib/itertools.py index 5f22505..e67651a 100644 --- a/tqdm/contrib/itertools.py +++ b/tqdm/contrib/itertools.py @@ -1,8 +1,6 @@ """ Thin wrappers around `itertools`. """ -from __future__ import absolute_import - import itertools from ..auto import tqdm as tqdm_auto diff --git a/tqdm/contrib/logging.py b/tqdm/contrib/logging.py index cd9925e..b8eaec5 100644 --- a/tqdm/contrib/logging.py +++ b/tqdm/contrib/logging.py @@ -1,14 +1,12 @@ """ Helper functionality for interoperability with stdlib `logging`. """ -from __future__ import absolute_import - import logging import sys from contextlib import contextmanager try: - from typing import Iterator, List, Optional, Type # pylint: disable=unused-import + from typing import Iterator, List, Optional, Type # noqa: F401 except ImportError: pass diff --git a/tqdm/contrib/slack.py b/tqdm/contrib/slack.py index b478d92..d4c850c 100644 --- a/tqdm/contrib/slack.py +++ b/tqdm/contrib/slack.py @@ -6,10 +6,8 @@ Usage: >>> 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) """ -from __future__ import absolute_import - import logging from os import getenv @@ -19,7 +17,6 @@ except ImportError: raise ImportError("Please `pip install slack-sdk`") from ..auto import tqdm as tqdm_auto -from ..utils import _range from .utils_worker import MonoWorker __author__ = {"github.com/": ["0x2b3bfa0", "casperdcl"]} @@ -114,11 +111,8 @@ class tqdm_slack(tqdm_auto): def tsrange(*args, **kwargs): - """ - A shortcut for `tqdm.contrib.slack.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_slack(_range(*args), **kwargs) + """Shortcut for `tqdm.contrib.slack.tqdm(range(*args), **kwargs)`.""" + return tqdm_slack(range(*args), **kwargs) # Aliases diff --git a/tqdm/contrib/telegram.py b/tqdm/contrib/telegram.py index 99cbe8c..cbeadf2 100644 --- a/tqdm/contrib/telegram.py +++ b/tqdm/contrib/telegram.py @@ -6,10 +6,8 @@ Usage: >>> 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 __future__ import absolute_import - from os import getenv from warnings import warn @@ -17,7 +15,6 @@ from requests import Session from ..auto import tqdm as tqdm_auto from ..std import TqdmWarning -from ..utils import _range from .utils_worker import MonoWorker __author__ = {"github.com/": ["casperdcl"]} @@ -147,11 +144,8 @@ class tqdm_telegram(tqdm_auto): def ttgrange(*args, **kwargs): - """ - A shortcut for `tqdm.contrib.telegram.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_telegram(_range(*args), **kwargs) + """Shortcut for `tqdm.contrib.telegram.tqdm(range(*args), **kwargs)`.""" + return tqdm_telegram(range(*args), **kwargs) # Aliases diff --git a/tqdm/contrib/utils_worker.py b/tqdm/contrib/utils_worker.py index 17adda6..2a03a2a 100644 --- a/tqdm/contrib/utils_worker.py +++ b/tqdm/contrib/utils_worker.py @@ -1,8 +1,6 @@ """ IO/concurrency helpers for `tqdm.contrib`. """ -from __future__ import absolute_import - from collections import deque from concurrent.futures import ThreadPoolExecutor diff --git a/tqdm/dask.py b/tqdm/dask.py index 6fc7504..af9926a 100644 --- a/tqdm/dask.py +++ b/tqdm/dask.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from functools import partial from dask.callbacks import Callback diff --git a/tqdm/gui.py b/tqdm/gui.py index 4612701..8bab6ac 100644 --- a/tqdm/gui.py +++ b/tqdm/gui.py @@ -8,16 +8,14 @@ Usage: """ # future division is important to divide integers and get as # a result precise floating numbers (instead of truncated int) -from __future__ import absolute_import, division - import re from warnings import warn # to inherit from the tqdm class from .std import TqdmExperimentalWarning from .std import tqdm as std_tqdm + # import compatibility functions and utilities -from .utils import _range __author__ = {"github.com/": ["casperdcl", "lrq3000"]} __all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange'] @@ -173,17 +171,14 @@ class tqdm_gui(std_tqdm): # pragma: no cover "{bar}", "") msg = self.format_meter(**d) if '' in msg: - msg = "".join(re.split(r'\|?\|?', msg, 1)) + msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) ax.set_title(msg, fontname="DejaVu Sans Mono", fontsize=11) self.plt.pause(1e-9) def tgrange(*args, **kwargs): - """ - A shortcut for `tqdm.gui.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_gui(_range(*args), **kwargs) + """Shortcut for `tqdm.gui.tqdm(range(*args), **kwargs)`.""" + return tqdm_gui(range(*args), **kwargs) # Aliases diff --git a/tqdm/keras.py b/tqdm/keras.py index 523e62e..cce9467 100644 --- a/tqdm/keras.py +++ b/tqdm/keras.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, division - from copy import copy from functools import partial @@ -96,7 +94,7 @@ class TqdmCallback(keras.callbacks.Callback): raise KeyError('Unknown verbosity') def on_train_end(self, *_, **__): - if self.verbose: + if hasattr(self, 'batch_bar'): self.batch_bar.close() self.epoch_bar.close() diff --git a/tqdm/notebook.py b/tqdm/notebook.py index ffd0947..6ee43a6 100644 --- a/tqdm/notebook.py +++ b/tqdm/notebook.py @@ -7,18 +7,14 @@ Usage: >>> for i in trange(10): ... ... """ -# future division is important to divide integers and get as -# a result precise floating numbers (instead of truncated int) -from __future__ import absolute_import, division - # import compatibility functions and utilities import re import sys +from html import escape from weakref import proxy # to inherit from the tqdm class from .std import tqdm as std_tqdm -from .utils import _range if True: # pragma: no cover # import IPython/Jupyter base widget and display utilities @@ -63,12 +59,6 @@ if True: # pragma: no cover except ImportError: pass - # HTML encoding - try: # Py3 - from html import escape - except ImportError: # Py2 - from cgi import escape - __author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]} __all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange'] WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets." @@ -167,9 +157,10 @@ class tqdm_notebook(std_tqdm): pbar.value = self.n if msg: + msg = msg.replace(' ', u'\u2007') # fix html space padding # html escape special characters (like '&') if '' in msg: - left, right = map(escape, re.split(r'\|?\|?', msg, 1)) + left, right = map(escape, re.split(r'\|?\|?', msg, maxsplit=1)) else: left, right = '', escape(msg) @@ -317,11 +308,8 @@ class tqdm_notebook(std_tqdm): def tnrange(*args, **kwargs): - """ - A shortcut for `tqdm.notebook.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_notebook(_range(*args), **kwargs) + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + return tqdm_notebook(range(*args), **kwargs) # Aliases diff --git a/tqdm/rich.py b/tqdm/rich.py index 69893ff..00e1ddf 100644 --- a/tqdm/rich.py +++ b/tqdm/rich.py @@ -6,8 +6,6 @@ Usage: >>> for i in trange(10): ... ... """ -from __future__ import absolute_import - from warnings import warn from rich.progress import ( @@ -15,7 +13,6 @@ from rich.progress import ( from .std import TqdmExperimentalWarning from .std import tqdm as std_tqdm -from .utils import _range __author__ = {"github.com/": ["casperdcl"]} __all__ = ['tqdm_rich', 'trrange', 'tqdm', 'trange'] @@ -144,11 +141,8 @@ class tqdm_rich(std_tqdm): # pragma: no cover def trrange(*args, **kwargs): - """ - A shortcut for `tqdm.rich.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_rich(_range(*args), **kwargs) + """Shortcut for `tqdm.rich.tqdm(range(*args), **kwargs)`.""" + return tqdm_rich(range(*args), **kwargs) # Aliases diff --git a/tqdm/std.py b/tqdm/std.py index 5f9dcca..e58fdca 100644 --- a/tqdm/std.py +++ b/tqdm/std.py @@ -7,12 +7,10 @@ Usage: >>> for i in trange(10): ... ... """ -from __future__ import absolute_import, division - import sys from collections import OrderedDict, defaultdict from contextlib import contextmanager -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from numbers import Number from time import time from warnings import warn @@ -21,8 +19,8 @@ from weakref import WeakSet from ._monitor import TMonitor from .utils import ( CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace, SimpleTextIOWrapper, - _basestring, _is_ascii, _range, _screen_shape_wrapper, _supports_unicode, _term_move_up, - _unich, _unicode, 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', @@ -144,7 +142,7 @@ class Bar(object): + `b`: blank (`charset=" "` override) """ ASCII = " 123456789#" - UTF = u" " + u''.join(map(_unich, range(0x258F, 0x2587, -1))) + UTF = u" " + u''.join(map(chr, range(0x258F, 0x2587, -1))) BLANK = " " COLOUR_RESET = '\x1b[0m' COLOUR_RGB = '\x1b[38;2;%d;%d;%dm' @@ -249,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 @@ -279,11 +391,11 @@ class tqdm(Comparable): if abs(num) < 999.5: if abs(num) < 99.95: if abs(num) < 9.995: - return '{0:1.2f}'.format(num) + unit + suffix - return '{0:2.1f}'.format(num) + unit + suffix - return '{0:3.0f}'.format(num) + unit + suffix + return f'{num:1.2f}{unit}{suffix}' + return f'{num:2.1f}{unit}{suffix}' + return f'{num:3.0f}{unit}{suffix}' num /= divisor - return '{0:3.1f}Y'.format(num) + suffix + return f'{num:3.1f}Y{suffix}' @staticmethod def format_interval(t): @@ -302,10 +414,7 @@ class tqdm(Comparable): """ mins, s = divmod(int(t), 60) h, m = divmod(mins, 60) - if h: - return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) - else: - return '{0:02d}:{1:02d}'.format(m, s) + return f'{h:d}:{m:02d}:{s:02d}' if h else f'{m:02d}:{s:02d}' @staticmethod def format_num(n): @@ -322,7 +431,7 @@ class tqdm(Comparable): out : str Formatted number. """ - f = '{0:.3g}'.format(n).replace('+0', '+').replace('-0', '-') + f = f'{n:.3g}'.replace('e+0', 'e+').replace('e-0', 'e-') n = str(n) return f if len(f) < len(n) else n @@ -340,7 +449,7 @@ class tqdm(Comparable): getattr(sys.stdout, 'flush', lambda: None)() def fp_write(s): - fp.write(_unicode(s)) + fp.write(str(s)) fp_flush() last_len = [0] @@ -442,10 +551,10 @@ class tqdm(Comparable): 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 - '{0:5.2f}'.format(rate)) if rate else '?') + unit + '/s' + rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else f'{rate:5.2f}') + if rate else '?') + unit + '/s' rate_inv_fmt = ( - (format_sizeof(inv_rate) if unit_scale else '{0:5.2f}'.format(inv_rate)) + (format_sizeof(inv_rate) if unit_scale else f'{inv_rate:5.2f}') if inv_rate else '?') + 's/' + unit rate_fmt = rate_inv_fmt if inv_rate and inv_rate > 1 else rate_noinv_fmt @@ -465,7 +574,7 @@ class tqdm(Comparable): remaining_str = tqdm.format_interval(remaining) if rate else '?' try: eta_dt = (datetime.now() + timedelta(seconds=remaining) - if rate and total else datetime.utcfromtimestamp(0)) + if rate and total else datetime.fromtimestamp(0, timezone.utc)) except OverflowError: eta_dt = datetime.max @@ -477,26 +586,25 @@ class tqdm(Comparable): else: l_bar = '' - r_bar = '| {0}/{1} [{2}<{3}, {4}{5}]'.format( - n_fmt, total_fmt, elapsed_str, remaining_str, rate_fmt, postfix) + r_bar = f'| {n_fmt}/{total_fmt} [{elapsed_str}<{remaining_str}, {rate_fmt}{postfix}]' # Custom bar formatting # Populate a dict with all available progress indicators - format_dict = dict( + format_dict = { # slight extension of self.format_dict - n=n, n_fmt=n_fmt, total=total, total_fmt=total_fmt, - elapsed=elapsed_str, elapsed_s=elapsed, - ncols=ncols, desc=prefix or '', unit=unit, - rate=inv_rate if inv_rate and inv_rate > 1 else rate, - rate_fmt=rate_fmt, rate_noinv=rate, - rate_noinv_fmt=rate_noinv_fmt, rate_inv=inv_rate, - rate_inv_fmt=rate_inv_fmt, - postfix=postfix, unit_divisor=unit_divisor, - colour=colour, + 'n': n, 'n_fmt': n_fmt, 'total': total, 'total_fmt': total_fmt, + 'elapsed': elapsed_str, 'elapsed_s': elapsed, + 'ncols': ncols, 'desc': prefix or '', 'unit': unit, + 'rate': inv_rate if inv_rate and inv_rate > 1 else rate, + 'rate_fmt': rate_fmt, 'rate_noinv': rate, + 'rate_noinv_fmt': rate_noinv_fmt, 'rate_inv': inv_rate, + 'rate_inv_fmt': rate_inv_fmt, + 'postfix': postfix, 'unit_divisor': unit_divisor, + 'colour': colour, # plus more useful definitions - remaining=remaining_str, remaining_s=remaining, - l_bar=l_bar, r_bar=r_bar, eta=eta_dt, - **extra_kwargs) + 'remaining': remaining_str, 'remaining_s': remaining, + 'l_bar': l_bar, 'r_bar': r_bar, 'eta': eta_dt, + **extra_kwargs} # total is known: we can predict some stats if total: @@ -504,7 +612,7 @@ class tqdm(Comparable): frac = n / total percentage = frac * 100 - l_bar += '{0:3.0f}%|'.format(percentage) + l_bar += f'{percentage:3.0f}%|' if ncols == 0: return l_bar[:-1] + r_bar[1:] @@ -513,21 +621,16 @@ class tqdm(Comparable): if bar_format: format_dict.update(percentage=percentage) - # auto-remove colon for empty `desc` + # auto-remove colon for empty `{desc}` if not prefix: bar_format = bar_format.replace("{desc}: ", '') else: bar_format = "{l_bar}{bar}{r_bar}" full_bar = FormatReplace() - try: - nobar = bar_format.format(bar=full_bar, **format_dict) - except UnicodeEncodeError: - bar_format = _unicode(bar_format) - nobar = bar_format.format(bar=full_bar, **format_dict) + nobar = bar_format.format(bar=full_bar, **format_dict) if not full_bar.format_called: - # no {bar}, we can just format and return - return nobar + return nobar # no `{bar}`; nothing else to do # Formatting progress bar space available for bar's display full_bar = Bar(frac, @@ -535,7 +638,7 @@ class tqdm(Comparable): charset=Bar.ASCII if ascii is True else ascii or Bar.UTF, colour=colour) if not _is_ascii(full_bar.charset) and _is_ascii(bar_format): - bar_format = _unicode(bar_format) + bar_format = str(bar_format) res = bar_format.format(bar=full_bar, **format_dict) return disp_trim(res, ncols) if ncols else res @@ -554,8 +657,8 @@ class tqdm(Comparable): return disp_trim(res, ncols) if ncols else res else: # no total: no progressbar, ETA, just progress stats - return '{0}{1}{2} [{3}, {4}{5}]'.format( - (prefix + ": ") if prefix else '', n_fmt, unit, elapsed_str, rate_fmt, postfix) + return (f'{(prefix + ": ") if prefix else ""}' + f'{n_fmt}{unit} [{elapsed_str}, {rate_fmt}{postfix}]') def __new__(cls, *_, **__): instance = object.__new__(cls) @@ -827,6 +930,8 @@ class tqdm(Comparable): DataFrame.progress_apply = inner_generator() DataFrameGroupBy.progress_apply = inner_generator() DataFrame.progress_applymap = inner_generator('applymap') + DataFrame.progress_map = inner_generator('map') + DataFrameGroupBy.progress_map = inner_generator('map') if Panel is not None: Panel.progress_apply = inner_generator() @@ -843,133 +948,17 @@ class tqdm(Comparable): elif _Rolling_and_Expanding is not None: _Rolling_and_Expanding.progress_apply = inner_generator() + # override defaults via env vars + @envwrap("TQDM_", is_method=True, types={'total': float, 'ncols': int, 'miniters': float, + 'position': int, 'nrows': int}) 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, write_bytes=None, - lock_args=None, nrows=None, colour=None, delay=0, gui=False, + position=None, postfix=None, unit_divisor=1000, write_bytes=False, + lock_args=None, nrows=None, colour=None, delay=0.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 - If (default: None) and `file` is unspecified, - bytes will be written in Python 2. If `True` will also write - bytes. In all other cases will default to 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. - """ - if write_bytes is None: - write_bytes = file is None and sys.version_info < (3,) - + """see tqdm.tqdm for arguments""" if file is None: file = sys.stderr @@ -1051,7 +1040,7 @@ class tqdm(Comparable): if bar_format and ascii is not True and not _is_ascii(ascii): # Convert bar format into unicode since terminal uses unicode - bar_format = _unicode(bar_format) + bar_format = str(bar_format) if smoothing is None: smoothing = 0 @@ -1120,9 +1109,6 @@ class tqdm(Comparable): raise TypeError('bool() undefined when iterable == total == None') return bool(self.iterable) - def __nonzero__(self): - return self.__bool__() - def __len__(self): return ( self.total if self.iterable is None @@ -1298,7 +1284,7 @@ class tqdm(Comparable): # annoyingly, _supports_unicode isn't good enough def fp_write(s): - self.fp.write(_unicode(s)) + self.fp.write(str(s)) try: fp_write('') @@ -1435,7 +1421,7 @@ class tqdm(Comparable): if isinstance(postfix[key], Number): postfix[key] = self.format_num(postfix[key]) # Else for any other type, try to get the string conversion - elif not isinstance(postfix[key], _basestring): + elif not isinstance(postfix[key], str): postfix[key] = str(postfix[key]) # Else if it's a string, don't need to preprocess anything # Stitch together to get the final postfix @@ -1454,7 +1440,7 @@ class tqdm(Comparable): def moveto(self, n): # TODO: private method - self.fp.write(_unicode('\n' * n + _term_move_up() * -n)) + self.fp.write('\n' * n + _term_move_up() * -n) getattr(self.fp, 'flush', lambda: None)() @property @@ -1534,8 +1520,5 @@ class tqdm(Comparable): def trange(*args, **kwargs): - """ - A shortcut for tqdm(xrange(*args), **kwargs). - On Python3+ range is used instead of xrange. - """ - return tqdm(_range(*args), **kwargs) + """Shortcut for tqdm(range(*args), **kwargs).""" + return tqdm(range(*args), **kwargs) diff --git a/tqdm/tk.py b/tqdm/tk.py index 92adb51..dfebf5c 100644 --- a/tqdm/tk.py +++ b/tqdm/tk.py @@ -6,22 +6,14 @@ Usage: >>> for i in trange(10): ... ... """ -from __future__ import absolute_import, division - import re import sys +import tkinter +import tkinter.ttk as ttk from warnings import warn -try: - import tkinter - import tkinter.ttk as ttk -except ImportError: - import Tkinter as tkinter - import ttk as ttk - from .std import TqdmExperimentalWarning, TqdmWarning from .std import tqdm as std_tqdm -from .utils import _range __author__ = {"github.com/": ["richardsheridan", "casperdcl"]} __all__ = ['tqdm_tk', 'ttkrange', 'tqdm', 'trange'] @@ -143,7 +135,7 @@ class tqdm_tk(std_tqdm): # pragma: no cover "{bar}", "") msg = self.format_meter(**d) if '' in msg: - msg = "".join(re.split(r'\|?\|?', msg, 1)) + msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) self._tk_text_var.set(msg) if not self._tk_dispatching: self._tk_window.update() @@ -195,11 +187,8 @@ class tqdm_tk(std_tqdm): # pragma: no cover def ttkrange(*args, **kwargs): - """ - A shortcut for `tqdm.tk.tqdm(xrange(*args), **kwargs)`. - On Python3+, `range` is used instead of `xrange`. - """ - return tqdm_tk(_range(*args), **kwargs) + """Shortcut for `tqdm.tk.tqdm(range(*args), **kwargs)`.""" + return tqdm_tk(range(*args), **kwargs) # Aliases diff --git a/tqdm/tqdm.1 b/tqdm/tqdm.1 index 0533198..b90ab4b 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 0632b8d..9883fda 100644 --- a/tqdm/utils.py +++ b/tqdm/utils.py @@ -4,31 +4,14 @@ General helpers required for `tqdm.std`. import os import re import sys -from functools import wraps +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 from weakref import proxy -# py2/3 compat -try: - _range = xrange -except NameError: - _range = range - -try: - _unich = unichr -except NameError: - _unich = chr - -try: - _unicode = unicode -except NameError: - _unicode = str - -try: - _basestring = basestring -except NameError: - _basestring = str - +_range, _unich, _unicode, _basestring = range, chr, str, str CUR_OS = sys.platform IS_WIN = any(CUR_OS.startswith(i) for i in ['win32', 'cygwin']) IS_NIX = any(CUR_OS.startswith(i) for i in ['aix', 'linux', 'darwin']) @@ -48,10 +31,78 @@ else: colorama.init() +def envwrap(prefix, types=None, is_method=False): + """ + Override parameter defaults via `os.environ[prefix + param_name]`. + Maps UPPER_CASE env vars map to lower_case param names. + camelCase isn't supported (because Windows ignores case). + + 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_" + types : dict, optional + Fallback mappings `{'param_name': type, ...}` if types cannot be + inferred from function signature. + Consider using `types=collections.defaultdict(lambda: ast.literal_eval)`. + 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 + ``` + """ + if types is None: + types = {} + i = len(prefix) + env_overrides = {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 + # ignore unknown env vars + overrides = {k: v for k, v in env_overrides.items() if k in params} + # infer overrides' `type`s + for k in overrides: + param = params[k] + if param.annotation is not param.empty: # typehints + for typ in getattr(param.annotation, '__args__', (param.annotation,)): + try: + overrides[k] = typ(overrides[k]) + except Exception: + pass + else: + break + elif param.default is not None: # type of default value + overrides[k] = type(param.default)(overrides[k]) + else: + try: # `types` fallback + overrides[k] = types[k](overrides[k]) + except KeyError: # keep unconverted (`str`) + pass + return part(func, **overrides) + return wrap + + class FormatReplace(object): """ >>> a = FormatReplace('something') - >>> "{:5d}".format(a) + >>> f"{a:5d}" 'something' """ # NOQA: P102 def __init__(self, replace=''): @@ -320,14 +371,8 @@ def _term_move_up(): # pragma: no cover return '' if (os.name == 'nt') and (colorama is None) else '\x1b[A' -try: - # TODO consider using wcswidth third-party package for 0-width characters - from unicodedata import east_asian_width -except ImportError: - _text_width = len -else: - def _text_width(s): - return sum(2 if east_asian_width(ch) in 'FW' else 1 for ch in _unicode(s)) +def _text_width(s): + return sum(2 if east_asian_width(ch) in 'FW' else 1 for ch in str(s)) def disp_len(data): -- cgit v1.2.3