diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01ea543..95b4a82 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: v5.0.0 + rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -37,9 +37,9 @@ repos: - numpy - pandas - pytest-timeout - - pytest-asyncio>=0.24 -- repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 + - pytest-asyncio +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 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.13.2 + rev: 5.10.1 hooks: - id: isort - repo: https://github.com/kynan/nbstripout - rev: 0.7.1 + rev: 0.5.0 hooks: - id: nbstripout args: [--keep-count, --keep-output] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f8f2a4..4cd86d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ Makefile: ``` make [] # on UNIX-like environments -python -m pymake [] # if make is unavailable +python setup.py make [] # 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 minor readability changes (such as converting `dict(a=1)` to `{'a': 1}`) is not a good enough 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 + 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 -m py]make test +[python setup.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 ensure -that each distribution has an alias to call the Python interpreter -(e.g. `python312` for Python 3.12's interpreter). +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. ### Alternative unit tests with pytest Alternatively, use `pytest` to run the tests just for the current Python version: -- install test requirements: `[python -m py]make install_test` +- install test requirements: `[python setup.py] make install_test` - run the following command: ``` -[python -m py]make alltests +[python setup.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 -m py]make []` will help. +`[python setup.py] make []` will help. Also consider `pip install`ing development utilities: -`[python -m py]make install_build` at a minimum, or a more thorough `conda env create`. +`[python setup.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 `pyproject.toml` +## Checking setup.py -To check that the `pyproject.toml` file is compliant with PyPI +To check that the `setup.py`/`setup.cfg`/`pyproject.toml` file is compliant with PyPI requirements (e.g. version number; reStructuredText in `README.rst`) use: ``` -[python -m py]make testsetup +[python setup.py] make testsetup ``` To upload just metadata (including overwriting mistakenly uploaded metadata) to PyPI, use: ``` -[python -m py]make pypimeta +[python setup.py] make pypimeta ``` @@ -199,7 +199,7 @@ git merge --no-ff pr-branch-name ### 4 Test ``` -[python -m py]make alltests +[python setup.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 -m py]make build +[python setup.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 -m py]make pypi +[python setup.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 -m py]make build`. +created by `[python setup.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 -m py]make pypimeta` +updating just the metadata is possible: `[python setup.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 -m py]make alltests` or rely on `pre-commit`) +1. test (`[python setup.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 -m py]make distclean` -7. **`[AUTO:GHA]`** `[python -m py]make build` +6. **`[AUTO:GHA]`** `[python setup.py] make distclean` +7. **`[AUTO:GHA]`** `[python setup.py] make build` 8. **`[AUTO:GHA]`** upload to PyPI. either: - a) `[python -m py]make pypi`, or + a) `[python setup.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 -m py]make testasvfull` + a) `[python setup.py] make testasvfull` b) `asv gh-pages` Key: diff --git a/DEMO.ipynb b/DEMO.ipynb index 73489bd..b892af4 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/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", + "[![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", "-|-|-|-|-|-\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(range(N))`." + "`tqdm(xrange(N))`." ] }, { @@ -58,7 +58,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "![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", + "![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", "-|-\n", "\n", "It can also be executed as a module with pipes:" @@ -413,7 +413,7 @@ " \"\"\"Provides a `total_time` format parameter\"\"\"\n", " @property\n", " def format_dict(self):\n", - " d = super().format_dict\n", + " d = super(TqdmExtraFormat, self).format_dict\n", " total_time = d[\"elapsed\"] * (d[\"total\"] or 0) / max(d[\"n\"], 1)\n", " d.update(total_time=self.format_interval(total_time) + \" in total\")\n", " return d\n", @@ -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://tqdm.github.io/img/jupyter-3.gif)\n", + "![Screenshot-Jupyter3](https://img.tqdm.ml/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://cgi.cdcl.ml/hits?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://cgi.cdcl.ml/hits?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", + "[![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", "-|-" ] }, diff --git a/LICENCE b/LICENCE index a8922b1..5b3cab7 100644 --- a/LICENCE +++ b/LICENCE @@ -7,11 +7,11 @@ Exceptions or notable authors are listed below in reverse chronological order: * files: * - MPL-2.0 2015-2024 (c) Casper da Costa-Luis + MPLv2.0 2015-2021 (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 README.rst .gitignore +* files: tqdm/_tqdm.py setup.py README.rst MANIFEST.in .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 e61571c..796f8b0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# IMPORTANT: for compatibility with `python -m pymake [alias]`, ensure: +# IMPORTANT: for compatibility with `python setup.py make [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 -m pymake -p + @python setup.py make -p alltests: @+make testcoverage @@ -58,14 +58,15 @@ testsetup: @make README.rst @make tqdm/tqdm.1 @make tqdm/completion.sh - @make help + python setup.py check --metadata --restructuredtext --strict + python setup.py make none testnb: - pytest tests_notebook.ipynb --cov=tqdm.notebook --cov-report=term -W=ignore --nbval --nbval-current-env --nbval-sanitize-with=.meta/nbval.ini + pytest tests_notebook.ipynb --nbval --nbval-current-env -W=ignore --nbval-sanitize-with=setup.cfg --cov=tqdm.notebook --cov-report=term testcoverage: @make coverclean - pytest tests_notebook.ipynb --cov=tqdm --cov-report= -W=ignore --nbval --nbval-current-env --nbval-sanitize-with=.meta/nbval.ini + pytest tests_notebook.ipynb --cov=tqdm --cov-report= --nbval --nbval-current-env --nbval-sanitize-with=setup.cfg -W=ignore pytest -k "not perf" --cov=tqdm --cov-report=xml --cov-report=term --cov-append --cov-fail-under=80 testperf: @@ -137,9 +138,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)" @@ -151,11 +152,12 @@ submodules: cd feedstock && git remote add autotick-bot git@github.com:regro-cf-autotick-bot/tqdm-feedstock install: - python -m pip install . + python setup.py install install_dev: - python -m pip install -e . + python setup.py develop --uninstall + python setup.py develop install_build: - python -m pip install -r .meta/requirements-build.txt + python -m pip install -r .meta/requirements-dev.txt install_test: python -m pip install -r .meta/requirements-test.txt pre-commit install @@ -163,11 +165,11 @@ install_test: build: @make prebuildclean @make testsetup - python -m build - python -m twine check dist/* + python setup.py sdist bdist_wheel + # python setup.py bdist_wininst pypi: - python -m twine upload dist/* + twine upload dist/* buildupload: @make build diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..d796709 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,1581 @@ +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 307dac4..a45f98d 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,12 +291,6 @@ 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 @@ -333,14 +327,12 @@ 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 @@ -357,14 +349,12 @@ 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, write_bytes=False, - lock_args=None, nrows=None, colour=None, delay=0): + postfix=None, unit_divisor=1000): Parameters ~~~~~~~~~~ @@ -460,7 +450,9 @@ Parameters * 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. + 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). @@ -639,7 +631,10 @@ Returns """Registers the current `tqdm` class with `pandas`.""" def trange(*args, **tqdm_kwargs): - """Shortcut for `tqdm(range(*args), **tqdm_kwargs)`.""" + """ + A shortcut for `tqdm(xrange(*args), **tqdm_kwargs)`. + On Python3+, `range` is used instead of `xrange`. + """ Convenience Functions ~~~~~~~~~~~~~~~~~~~~~ @@ -742,7 +737,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", {"value": 0}]) as t: + postfix=["Batch", dict(value=0)]) as t: for i in range(10): sleep(0.1) t.postfix[1]["value"] = i / 2 @@ -766,7 +761,7 @@ Additional ``bar_format`` parameters may also be defined by overriding """Provides a `total_time` format parameter""" @property def format_dict(self): - d = super().format_dict + 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 @@ -830,7 +825,7 @@ first. def progresser(n): interval = 0.001 / (n + 2) total = 5000 - text = f"#{n}, est. {interval * total:<04.2}s" + text = "#{}, est. {:<04.2}s".format(n, interval * total) for _ in trange(total, desc=text, position=n): sleep(interval) @@ -853,7 +848,7 @@ Note that in Python 3, ``tqdm.write`` is thread-safe: def progresser(n): interval = 0.001 / (n + 2) total = 5000 - text = f"#{n}, est. {interval * total:<04.2}s" + text = "#{}, est. {:<04.2}s".format(n, interval * total) for _ in trange(total, desc=text): sleep(interval) if n == 6: @@ -982,7 +977,7 @@ custom callback take advantage of this, simply use the return value of class TqdmExt(std_tqdm): def update(self, n=1): - displayed = super().update(n) + displayed = super(TqdmExt, self).update(n) if displayed: external_callback(**self.format_dict) return displayed @@ -1410,17 +1405,16 @@ are: ==================== ======================================================== ==== ================================ Name ID SLoC Notes ==================== ======================================================== ==== ================================ -Casper da Costa-Luis `casperdcl `__ ~80% primary maintainer |Gift-Casper| -Stephen Larroque `lrq3000 `__ ~9% team member -Martin Zugnoni `martinzugnoni `__ ~3% +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% -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 ==================== ======================================================== ==== ================================ @@ -1440,16 +1434,16 @@ Citation information: |DOI| |README-Hits| (Since 19 May 2016) -.. |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 +.. |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://tqdm.github.io/img/slides.jpg +.. |Slides| image:: https://img.tqdm.ml/slides.jpg :target: https://tqdm.github.io/PyData2019/slides.html -.. |Merch| image:: https://tqdm.github.io/img/merch.jpg +.. |Merch| image:: https://img.tqdm.ml/merch.jpg :target: https://tqdm.github.io/merch -.. |Build-Status| image:: https://img.shields.io/github/actions/workflow/status/tqdm/tqdm/test.yml?branch=master&label=tqdm&logo=GitHub - :target: https://github.com/tqdm/tqdm/actions/workflows/test.yml +.. |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 @@ -1502,8 +1496,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://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://cgi.cdcl.ml/hits?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://cgi.cdcl.ml/hits?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 +.. |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/environment.yml b/environment.yml index 89e0406..871e3e8 100644 --- a/environment.yml +++ b/environment.yml @@ -5,7 +5,7 @@ channels: - defaults dependencies: # base -- python >=3.7 +- python=3 - pip - ipykernel - ipywidgets @@ -20,28 +20,27 @@ dependencies: - pytest - pytest-cov - pytest-timeout -- pytest-asyncio>=0.24 +- pytest-asyncio # [py>=3.7] +- nbval - coverage # extras - dask # dask - 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 check pypi` -- python-build # `python -m build` +- twine # `pymake pypi` +- wheel # `setup.py bdist_wheel` # `cd docs && pymake` - mkdocs-material +- pydoc-markdown - pygments - pymdown-extensions - pip: - - git+https://github.com/casperdcl/nbval.git@master#egg=nbval # tests (native) - - py-make >=0.1.0 # `make/pymake` + - py-make >=0.1.0 # `setup.py make/pymake` - mkdocs-minify-plugin # `cd docs && pymake` - - git+https://github.com/tqdm/jsmin@fix-pip#egg=jsmin # `cd docs && pymake` - - pydoc-markdown >=4.6 # `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 18d7e33..3d15254 100644 --- a/examples/7zx.py +++ b/examples/7zx.py @@ -18,6 +18,8 @@ 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 3e31905..40f4f24 100644 --- a/examples/async_coroutines.py +++ b/examples/async_coroutines.py @@ -1,4 +1,6 @@ -"""Asynchronous examples using `asyncio`, `async` and `await`.""" +""" +Asynchronous examples using `asyncio`, `async` and `await` on `python>=3.7`. +""" import asyncio from tqdm.asyncio import tqdm, trange diff --git a/examples/paper.bib b/examples/paper.bib index a30d136..13901e5 100644 --- a/examples/paper.bib +++ b/examples/paper.bib @@ -126,7 +126,7 @@ @misc{hits, year="2019", title="{tqdm} hits", - url="https://cgi.cdcl.ml/hits?q=tqdm&a=plot", + url="https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot", author="Casper O. {da Costa-Luis}" } @book{miller, diff --git a/examples/parallel_bars.py b/examples/parallel_bars.py index b7e1494..498fd61 100644 --- a/examples/parallel_bars.py +++ b/examples/parallel_bars.py @@ -1,3 +1,6 @@ +from __future__ import print_function + +import sys from concurrent.futures import ThreadPoolExecutor from functools import partial from multiprocessing import Pool, RLock, freeze_support @@ -9,19 +12,21 @@ 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 = f"#{n}, est. {interval * total:<04.2g}s" + text = "#{0}, est. {1:<04.2}s".format(n, interval * total) 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 + if write_safe: + # we think we know about other bars (currently only py3 threading) if n == 6: tqdm.write("n == 6 completed") return n + 1 @@ -32,7 +37,7 @@ if __name__ == '__main__': L = list(range(NUM_SUBITERS))[::-1] print("Simple thread mapping") - thread_map(partial(progresser, write_safe=True), L, max_workers=4) + thread_map(partial(progresser, write_safe=not PY2), L, max_workers=4) print("Simple process mapping") process_map(partial(progresser), L, max_workers=4) @@ -49,5 +54,8 @@ if __name__ == '__main__': print("Multi-threading") tqdm.set_lock(TRLock()) - with ThreadPoolExecutor(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) as p: - p.map(partial(progresser, progress=True, write_safe=True, blocking=False), L) + 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) diff --git a/examples/redirect_print.py b/examples/redirect_print.py index 38e6b4f..0f9721e 100644 --- a/examples/redirect_print.py +++ b/examples/redirect_print.py @@ -10,6 +10,8 @@ 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 bff1c9e..f3401d3 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,4 +61,5 @@ 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=s, number=1), 'seconds') + print(timeit(stmt='try:\n\t_range = xrange' + '\nexcept:\n\t_range = range\n' + s, number=1), 'seconds') diff --git a/examples/tqdm_wget.py b/examples/tqdm_wget.py index ee8b9f3..8663e5a 100644 --- a/examples/tqdm_wget.py +++ b/examples/tqdm_wget.py @@ -20,8 +20,11 @@ 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 c31f86a..3eb7bbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,135 +5,3 @@ 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"} -# Trove classifiers (https://pypi.org/pypi?%3Aaction=list_classifiers) -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.12", - "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-asyncio>=0.24", "nbval"] -discord = ["requests"] -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" -asyncio_default_fixture_loop_scope = "function" - -[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 new file mode 100644 index 0000000..4fb34f3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,159 @@ +[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 new file mode 100755 index 0000000..89dadf5 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +#!/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 7960fa0..6717044 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,10 +18,24 @@ def pretest_posttest(): n = len(tqdm._instances) if n: tqdm._instances.clear() - raise EnvironmentError(f"{n} `tqdm` instances still in existence PRE-test") + raise EnvironmentError( + "{0} `tqdm` instances still in existence PRE-test".format(n)) yield if getattr(tqdm, "_instances", False): n = len(tqdm._instances) if n: tqdm._instances.clear() - raise EnvironmentError(f"{n} `tqdm` instances still in existence POST-test") + 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 diff --git a/tests/py37_asyncio.py b/tests/py37_asyncio.py new file mode 100644 index 0000000..8bf61e7 --- /dev/null +++ b/tests/py37_asyncio.py @@ -0,0 +1,128 @@ +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 250e658..6f08926 100644 --- a/tests/tests_asyncio.py +++ b/tests/tests_asyncio.py @@ -1,133 +1,11 @@ -"""Tests `tqdm.asyncio`.""" -import asyncio -from functools import partial -from sys import platform -from time import time +"""Tests `tqdm.asyncio` on `python>=3.7`.""" +import sys -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 - - acounter = acount() +if sys.version_info[:2] > (3, 6): + from .py37_asyncio import * # NOQA, pylint: disable=wildcard-import +else: + from .tests_tqdm import skip try: - with tqdm(acounter, desc="async_counter") as pbar: - async for i in pbar: - if i >= 8: - break - finally: - await acounter.aclose() - _, 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)) + skip("async not supported", allow_module_level=True) + except TypeError: + pass diff --git a/tests/tests_contrib.py b/tests/tests_contrib.py index 65c8cd5..69a1cad 100644 --- a/tests/tests_contrib.py +++ b/tests/tests_contrib.py @@ -1,6 +1,8 @@ """ Tests for `tqdm.contrib`. """ +import sys + import pytest from tqdm import tqdm @@ -45,9 +47,12 @@ def test_zip(tqdm_kwargs): with closing(StringIO()) as our_file: a = range(9) b = [i + 1 for i in a] - gen = tzip(a, b, file=our_file, **tqdm_kwargs) - assert gen != list(zip(a, b)) - assert list(gen) == list(zip(a, b)) + 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)) @pytest.mark.parametrize("tqdm_kwargs", [{}, {"tqdm_class": tqdm}]) @@ -56,6 +61,11 @@ def test_map(tqdm_kwargs): with closing(StringIO()) as our_file: a = range(9) b = [i + 1 for i in a] - gen = tmap(lambda x: x + 1, a, file=our_file, **tqdm_kwargs) - assert gen != b - assert list(gen) == b + 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 diff --git a/tests/tests_contrib_logging.py b/tests/tests_contrib_logging.py index b8d60b6..6f675dd 100644 --- a/tests/tests_contrib_logging.py +++ b/tests/tests_contrib_logging.py @@ -1,5 +1,7 @@ # 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 16992bf..8bf4b64 100644 --- a/tests/tests_dask.py +++ b/tests/tests_dask.py @@ -1,3 +1,5 @@ +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 5b7db28..220f946 100644 --- a/tests/tests_keras.py +++ b/tests/tests_keras.py @@ -1,3 +1,5 @@ +from __future__ import division + from .tests_tqdm import importorskip, mark pytestmark = mark.slow @@ -39,8 +41,8 @@ def test_keras(capsys): verbose=0)]) _, res = capsys.readouterr() assert "training: " in res - assert f"{epochs}/{epochs}" in res - assert f"{batches}/{batches}" not in res + assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert "{batches}/{batches}".format(batches=batches) not in res # full (epoch and batch) progress model.fit( @@ -58,8 +60,8 @@ def test_keras(capsys): verbose=2)]) _, res = capsys.readouterr() assert "training: " in res - assert f"{epochs}/{epochs}" in res - assert f"{batches}/{batches}" in res + assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert "{batches}/{batches}".format(batches=batches) in res # auto-detect epochs and batches model.fit( @@ -71,8 +73,8 @@ def test_keras(capsys): callbacks=[TqdmCallback(desc="training", verbose=2)]) _, res = capsys.readouterr() assert "training: " in res - assert f"{epochs}/{epochs}" in res - assert f"{batches}/{batches}" in res + assert "{epochs}/{epochs}".format(epochs=epochs) in res + assert "{batches}/{batches}".format(batches=batches) in res # continue training (start from epoch != 0) initial_epoch = 3 @@ -87,5 +89,5 @@ def test_keras(capsys): miniters=1, mininterval=0, maxinterval=0)]) _, res = capsys.readouterr() assert "training: " in res - assert f"{initial_epoch - 1}/{initial_epoch - 1}" not in res - assert f"{epochs}/{epochs}" in res + assert "{epochs}/{epochs}".format(epochs=initial_epoch - 1) not in res + assert "{epochs}/{epochs}".format(epochs=epochs) in res diff --git a/tests/tests_main.py b/tests/tests_main.py index 039ce33..0523cc7 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, closing, mark, raises +from .tests_tqdm import BytesIO, _range, closing, mark, raises def restore_sys(func): - """Decorates `func(capsysbinary)` to save & restore `sys.(stdin|argv)`.""" + """Decorates `func(capsysbin)` to save & restore `sys.(stdin|argv)`.""" @wraps(func) - def inner(capsysbinary): - """function requiring capsysbinary which may alter `sys.(stdin|argv)`""" + def inner(capsysbin): + """function requiring capsysbin which may alter `sys.(stdin|argv)`""" _SYS = sys.stdin, sys.argv try: - res = func(capsysbinary) + res = func(capsysbin) 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(capsysbinary): +def test_main_bytes(capsysbin): """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 = capsysbinary.readouterr() + out, err = capsysbin.readouterr() assert out == IN_DATA assert str(N) + "it" in err.decode("U8") @@ -90,26 +90,27 @@ def test_main_bytes(capsysbinary): sys.stdin.write(IN_DATA) sys.stdin.seek(0) main(sys.stderr, ['--ascii', '--bytes=True', '--unit_scale', 'False']) - out, err = capsysbinary.readouterr() + out, err = capsysbin.readouterr() assert out == IN_DATA assert str(len(IN_DATA)) + "B" in err.decode("U8") -def test_main_log(capsysbinary, caplog): +@mark.skipif(sys.version_info[0] == 2, reason="no caplog on py2") +def test_main_log(capsysbin, 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 = capsysbinary.readouterr() + out, err = capsysbin.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 = capsysbinary.readouterr() + out, err = capsysbin.readouterr() assert norm(out) == IN_DATA and b"123/123" in err assert caplog.record_tuples finally: @@ -117,39 +118,39 @@ def test_main_log(capsysbinary, caplog): @restore_sys -def test_main(capsysbinary): +def test_main(capsysbin): """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 = capsysbinary.readouterr() + out, err = capsysbin.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 = capsysbinary.readouterr() + out, err = capsysbin.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 = capsysbinary.readouterr() + out, err = capsysbin.readouterr() assert not out and b"123/123" in err # test integer --update main(sys.stderr, ['--update']) - out, err = capsysbinary.readouterr() + out, err = capsysbin.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 = capsysbinary.readouterr() + out, err = capsysbin.readouterr() assert norm(out) == IN_DATA assert (str(N - 1) + "it").encode() in err assert (str(N) + "it").encode() not in err @@ -160,23 +161,23 @@ def test_main(capsysbinary): # test integer --update --delim sys.stdin.seek(0) main(sys.stderr, ['--update', '--delim', 'D']) - out, err = capsysbinary.readouterr() + out, err = capsysbin.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 = capsysbinary.readouterr() + out, err = capsysbin.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 = capsysbinary.readouterr() + out, err = capsysbin.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 @@ -212,30 +213,30 @@ def test_comppath(tmp_path): @restore_sys -def test_exceptions(capsysbinary): +def test_exceptions(capsysbin): """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, _ = capsysbinary.readouterr() + out, _ = capsysbin.readouterr() assert norm(out) == IN_DATA with raises(TqdmTypeError, match="invalid_bool_value"): main(sys.stderr, argv=['-ascii', '-unit_scale', 'invalid_bool_value']) - out, _ = capsysbinary.readouterr() + out, _ = capsysbin.readouterr() assert norm(out) == IN_DATA with raises(TqdmTypeError, match="invalid_int_value"): main(sys.stderr, argv=['-ascii', '--total', 'invalid_int_value']) - out, _ = capsysbinary.readouterr() + out, _ = capsysbin.readouterr() assert norm(out) == IN_DATA with raises(TqdmKeyError, match="Can only have one of --"): main(sys.stderr, argv=['--update', '--update_to']) - out, _ = capsysbinary.readouterr() + out, _ = capsysbin.readouterr() assert norm(out) == IN_DATA # test SystemExits diff --git a/tests/tests_pandas.py b/tests/tests_pandas.py index 09dff1e..334a97c 100644 --- a/tests/tests_pandas.py +++ b/tests/tests_pandas.py @@ -4,7 +4,6 @@ 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 @@ -40,8 +39,8 @@ def test_pandas_rolling_expanding(): our_file.seek(0) if our_file.getvalue().count(exres) < 2: our_file.seek(0) - raise AssertionError( - f"\nExpected:\n{exres} at least twice.\nIn:\n{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( + exres + " at least twice.", our_file.read())) def test_pandas_series(): @@ -63,11 +62,10 @@ def test_pandas_series(): our_file.seek(0) if our_file.getvalue().count(exres) < 2: our_file.seek(0) - raise AssertionError( - f"\nExpected:\n{exres} at least twice.\nIn:\n{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( + exres + " at least twice.", our_file.read())) -@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: @@ -82,12 +80,6 @@ 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) @@ -102,8 +94,8 @@ def test_pandas_data_frame(): our_file.seek(0) if our_file.read().count('100%') < 3: our_file.seek(0) - raise AssertionError( - f"\nExpected:\n100% at least three times\nIn:\n{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( + '100% at least three times', our_file.read())) # apply_map, apply axis=0, apply axis=1 expects = ['20000/20000', '200/200', '100/100'] @@ -111,12 +103,10 @@ def test_pandas_data_frame(): our_file.seek(0) if our_file.getvalue().count(exres) < 1: our_file.seek(0) - raise AssertionError( - f"\nExpected:\n{exres} at least once.\nIn:\n{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:\n {1}\n".format( + exres + " at least once.", our_file.read())) -@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: @@ -129,8 +119,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(np.maximum.reduce) - res2 = df2.groupby("a").progress_apply(np.maximum.reduce) + res1 = df2.groupby("a").apply(max) + res2 = df2.groupby("a").progress_apply(max) assert res1.equals(res2) our_file.seek(0) @@ -140,7 +130,8 @@ def test_pandas_groupby_apply(): nexres = '100%|##########|' if nexres in our_file.read(): our_file.seek(0) - raise AssertionError(f"\nDid not expect:\n{nexres}\nIn:{our_file.read()}\n") + raise AssertionError("\nDid not expect:\n{0}\nIn:{1}\n".format( + nexres, our_file.read())) with closing(StringIO()) as our_file: tqdm.pandas(file=our_file, leave=True, ascii=True) @@ -149,28 +140,26 @@ def test_pandas_groupby_apply(): dfs.loc[0] = [2, 1, 1] dfs['d'] = 100 - expects = ['500/500', '1/1', '4/4', '4/4'] + expects = ['500/500', '1/1', '4/4', '2/2'] dfs.groupby(dfs.index).progress_apply(lambda x: None) dfs.groupby('d').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) + dfs.groupby(dfs.columns, axis=1).progress_apply(lambda x: None) + dfs.groupby([2, 2, 1, 1], axis=1).progress_apply(lambda x: None) our_file.seek(0) if our_file.read().count('100%') < 4: our_file.seek(0) - raise AssertionError( - f"\nExpected:\n100% at least four times\nIn:\n{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:\n{1}\n".format( + '100% at least four times', our_file.read())) for exres in expects: our_file.seek(0) if our_file.getvalue().count(exres) < 1: our_file.seek(0) - raise AssertionError( - f"\nExpected:\n{exres} at least once.\nIn:\n{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:\n {1}\n".format( + exres + " at least once.", our_file.read())) -@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: @@ -183,7 +172,8 @@ def test_pandas_leave(): exres = '100%|##########| 100/100' if exres not in our_file.read(): our_file.seek(0) - raise AssertionError(f"\nExpected:\n{exres}\nIn:{our_file.read()}\n") + raise AssertionError("\nExpected:\n{0}\nIn:{1}\n".format( + exres, our_file.read())) def test_pandas_apply_args_deprecation(): @@ -205,8 +195,6 @@ 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 a6c4823..552a169 100644 --- a/tests/tests_perf.py +++ b/tests/tests_perf.py @@ -1,3 +1,5 @@ +from __future__ import division, print_function + import sys from contextlib import contextmanager from functools import wraps @@ -12,7 +14,7 @@ except ImportError: from tqdm import tqdm, trange -from .tests_tqdm import importorskip, mark, patch_lock, skip +from .tests_tqdm import _range, importorskip, mark, patch_lock, skip pytestmark = mark.slow @@ -96,7 +98,10 @@ 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) - return f'{h:d}:{m:02d}:{s:02d}' if h else f'{m:02d}:{s:02d}' + if h: + return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) + else: + return '{0:02d}:{1:02d}'.format(m, s) def update_and_print(i=1): n[0] += i @@ -138,15 +143,20 @@ def simple_progress(iterable=None, total=None, file=sys.stdout, desc='', update_and_print(0) if iterable is not None: return update_and_yield() - return update_and_print + else: + 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( - f'{name_left}: {time_left:f}, {name_right}: {time_right:f}' - f', ratio {time_left / time_right:f} > {thresh:f}') + ('{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)) @retry_on_except() @@ -163,7 +173,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)) @@ -178,13 +188,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)) @@ -239,7 +249,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) @@ -255,13 +265,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) @@ -282,7 +292,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: @@ -300,7 +310,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) @@ -308,7 +318,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 2fff78c..c75e246 100644 --- a/tests/tests_rich.py +++ b/tests/tests_rich.py @@ -1,7 +1,10 @@ """Test `tqdm.rich`.""" -from .tests_tqdm import importorskip +import sys + +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 0cd9190..7ee55fb 100644 --- a/tests/tests_synchronisation.py +++ b/tests/tests_synchronisation.py @@ -1,9 +1,13 @@ +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 @@ -33,13 +37,18 @@ class Time(object): sleep(0.000001) # sleep to allow interrupt (instead of pass) -class FakeEvent(Event): +def FakeEvent(): """patched `threading.Event` where `wait()` uses `Time.fake_sleep()`""" - def wait(self, timeout=None): + event = Event() # not a class in py2 so can't inherit + + def wait(timeout=None): """uses Time.fake_sleep""" if timeout is not None: Time.fake_sleep(timeout) - return self.is_set() + return event.is_set() + + event.wait = wait + return event def patch_sleep(func): @@ -197,11 +206,19 @@ 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: - res = list(tqdm(pool.map(incr_bar, range(100)), disable=True)) + 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 assert sum(res) == sum(range(1, 101)) diff --git a/tests/tests_tqdm.py b/tests/tests_tqdm.py index 25644aa..bba457a 100644 --- a/tests/tests_tqdm.py +++ b/tests/tests_tqdm.py @@ -1,6 +1,8 @@ # -*- 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 @@ -35,6 +37,16 @@ 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: @@ -107,7 +119,7 @@ def cpu_timify(t, timer=None): class UnicodeIO(IOBase): """Unicode version of StringIO""" def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + super(UnicodeIO, self).__init__(*args, **kwargs) self.encoding = 'U8' # io.StringIO supports unicode, but no encoding self.text = '' self.cursor = 0 @@ -189,8 +201,6 @@ 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(): @@ -261,10 +271,11 @@ 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 - assert format_meter(0, 1000, 13, ncols=68, prefix='fullwidth: ') == ( - "fullwidth: 0%| | 0/1000 [00:13= (3,): + assert format_meter(0, 1000, 13, ncols=68, prefix='fullwidth: ') == ( + "fullwidth: 0%| | 0/1000 [00:13>> Got:\n{res}\n===") + raise AssertionError("\n<<< Expected:\n{0}\n>>> Got:\n{1}\n===".format( + exres + ', ...it/s]\n', our_file.getvalue())) # Closing after the output stream has closed with closing(StringIO()) as our_file: @@ -964,7 +976,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: @@ -975,11 +987,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) @@ -1005,11 +1017,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) @@ -1028,11 +1040,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) @@ -1086,7 +1098,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, str) + assert isinstance(t.bar_format, _unicode) def test_custom_format(): @@ -1095,7 +1107,7 @@ def test_custom_format(): """Provides a `total_time` format parameter""" @property def format_dict(self): - d = super().format_dict + 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 @@ -1115,7 +1127,7 @@ def test_eta(capsys): bar_format='{l_bar}{eta:%Y-%m-%d}'): pass _, err = capsys.readouterr() - assert f"\r100%|{dt.now():%Y-%m-%d}\n" in err + assert "\r100%|{eta:%Y-%m-%d}\n".format(eta=dt.now()) in err def test_unpause(): @@ -1245,7 +1257,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() @@ -1348,7 +1360,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 @@ -1723,7 +1735,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() @@ -1925,7 +1937,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() @@ -1936,7 +1948,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 @@ -1949,7 +1961,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 @@ -1965,7 +1977,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 @@ -1977,7 +1989,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 deleted file mode 100644 index 6cf1e6c..0000000 --- a/tests/tests_utils.py +++ /dev/null @@ -1,51 +0,0 @@ -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 35c3e6f..d77a5a8 100644 --- a/tox.ini +++ b/tox.ini @@ -4,17 +4,20 @@ # and then run "tox" from this directory. [tox] -envlist=py{37,38,39,310,311,312,py3}{,-tf}{,-keras}, perf, check +# 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 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 - 3.11: py311 - 3.12: py312 + pypy-2.7: pypy2 pypy-3.7: pypy3 [gh-actions:env] PLATFORM= @@ -23,11 +26,12 @@ PLATFORM= [core] deps= pytest + py3{4,5,6}: pytest<7 pytest-cov pytest-timeout - pytest-asyncio - ipywidgets - git+https://github.com/casperdcl/nbval.git@master#egg=nbval + 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 coverage coveralls codecov @@ -37,7 +41,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 @@ -45,17 +49,26 @@ deps= matplotlib numpy pandas - rich tf: tensorflow!=2.5.0 - keras: keras + !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 commands= - pytest --cov=tqdm --cov-report= -W=ignore tests_notebook.ipynb --nbval --nbval-current-env --nbval-sanitize-with=.meta/nbval.ini - pytest --cov=tqdm --cov-report=xml --cov-report=term --cov-append -k "not perf" + 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" {[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:pypy3] +[testenv:py{34,py2,py3}] deps={[core]deps} [testenv:perf] @@ -65,12 +78,11 @@ deps= pytest-asyncio commands=pytest -k perf -[testenv:check] +[testenv:setup.py] deps= - build - twine + docutils + pygments py-make>=0.1.0 commands= - {envpython} -m build - {envpython} -m twine check dist/* - {envpython} -m pymake -h + {envpython} setup.py check --restructuredtext --metadata --strict + {envpython} setup.py make none diff --git a/tqdm.egg-info/PKG-INFO b/tqdm.egg-info/PKG-INFO new file mode 100644 index 0000000..d796709 --- /dev/null +++ b/tqdm.egg-info/PKG-INFO @@ -0,0 +1,1581 @@ +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 new file mode 100644 index 0000000..41d7b89 --- /dev/null +++ b/tqdm.egg-info/SOURCES.txt @@ -0,0 +1,88 @@ +.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 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tqdm.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/tqdm.egg-info/entry_points.txt b/tqdm.egg-info/entry_points.txt new file mode 100644 index 0000000..540e60f --- /dev/null +++ b/tqdm.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +tqdm = tqdm.cli:main diff --git a/tqdm.egg-info/requires.txt b/tqdm.egg-info/requires.txt new file mode 100644 index 0000000..352f7a3 --- /dev/null +++ b/tqdm.egg-info/requires.txt @@ -0,0 +1,20 @@ + +[: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 new file mode 100644 index 0000000..78620c4 --- /dev/null +++ b/tqdm.egg-info/top_level.txt @@ -0,0 +1 @@ +tqdm diff --git a/tqdm/__init__.py b/tqdm/__init__.py index 8081f77..a021d16 100644 --- a/tqdm/__init__.py +++ b/tqdm/__init__.py @@ -29,7 +29,10 @@ def tqdm_notebook(*args, **kwargs): # pragma: no cover def tnrange(*args, **kwargs): # pragma: no cover - """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + """ + A shortcut for `tqdm.notebook.tqdm(xrange(*args), **kwargs)`. + On Python3+, `range` is used instead of `xrange`. + """ from warnings import warn from .notebook import trange as _tnrange diff --git a/tqdm/_dist_ver.py b/tqdm/_dist_ver.py new file mode 100644 index 0000000..1c2d004 --- /dev/null +++ b/tqdm/_dist_ver.py @@ -0,0 +1 @@ +__version__ = '4.64.1' diff --git a/tqdm/_utils.py b/tqdm/_utils.py index 385e849..2cf1090 100644 --- a/tqdm/_utils.py +++ b/tqdm/_utils.py @@ -2,9 +2,10 @@ 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, - _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) + 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) 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 2d00a0a..97c5f88 100644 --- a/tqdm/asyncio.py +++ b/tqdm/asyncio.py @@ -18,10 +18,10 @@ __all__ = ['tqdm_asyncio', 'tarange', 'tqdm', 'trange'] class tqdm_asyncio(std_tqdm): """ - Asynchronous-friendly version of tqdm. + Asynchronous-friendly version of tqdm (Python 3.6+). """ def __init__(self, iterable=None, *args, **kwargs): - super().__init__(iterable, *args, **kwargs) + super(tqdm_asyncio, self).__init__(iterable, *args, **kwargs) self.iterable_awaitable = False if iterable is not None: if hasattr(iterable, "__anext__"): diff --git a/tqdm/auto.py b/tqdm/auto.py index 206c440..cffca20 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` +- `tqdm.asyncio` on Python3.6+ - `tqdm.std` base class Usage: @@ -12,6 +12,7 @@ Usage: >>> for i in trange(10): ... ... """ +import sys import warnings from .std import TqdmExperimentalWarning @@ -19,22 +20,25 @@ 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 -from .asyncio import tqdm as asyncio_tqdm -from .std import tqdm as std_tqdm +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) + 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 e54a7fc..3ed25fb 100644 --- a/tqdm/cli.py +++ b/tqdm/cli.py @@ -5,7 +5,6 @@ import logging import re import sys from ast import literal_eval as numeric -from textwrap import indent from .std import TqdmKeyError, TqdmTypeError, tqdm from .version import __version__ @@ -22,34 +21,23 @@ def cast(val, typ): return cast(val, t) except TqdmTypeError: pass - raise TqdmTypeError(f"{val} : {typ}") + raise TqdmTypeError(val + ' : ' + typ) # sys.stderr.write('\ndebug | `val:type`: `' + val + ':' + typ + '`.\n') if typ == 'bool': if (val == 'True') or (val == ''): return True - if val == 'False': + elif val == 'False': return False - raise TqdmTypeError(val + ' : ' + typ) - if typ == 'chr': - if len(val) == 1: - return val.encode() - if re.match(r"^\\\w+$", val): - return eval(f'"{val}"').encode() - raise TqdmTypeError(f"{val} : {typ}") - if typ == 'str': - return val - if typ == 'int': - try: - return int(val) - except ValueError as exc: - raise TqdmTypeError(f"{val} : {typ}") from exc - if typ == 'float': - try: - return float(val) - except ValueError as exc: - raise TqdmTypeError(f"{val} : {typ}") from exc - raise TqdmTypeError(f"{val} : {typ}") + else: + raise TqdmTypeError(val + ' : ' + typ) + try: + return eval(typ + '("' + val + '")') + except Exception: + if typ == 'chr': + return chr(ord(eval('"' + val + '"'))).encode() + else: + raise TqdmTypeError(val + ' : ' + typ) def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, @@ -110,7 +98,7 @@ def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, # ((opt, type), ... ) -RE_OPTS = re.compile(r'\n {4}(\S+)\s{2,}:\s*([^,]+)') +RE_OPTS = re.compile(r'\n {8}(\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)) def thread_map(fn, *iterables, **tqdm_kwargs): diff --git a/tqdm/contrib/discord.py b/tqdm/contrib/discord.py index 574baa8..0edd35c 100644 --- a/tqdm/contrib/discord.py +++ b/tqdm/contrib/discord.py @@ -6,85 +6,54 @@ Usage: >>> for i in trange(10, token='{token}', channel_id='{channel_id}'): ... ... -![screenshot](https://tqdm.github.io/img/screenshot-discord.png) +![screenshot](https://img.tqdm.ml/screenshot-discord.png) """ -from os import getenv -from warnings import warn +from __future__ import absolute_import -from requests import Session -from requests.utils import default_user_agent +import logging +from os import getenv + +try: + from disco.client import Client, ClientConfig +except ImportError: + raise ImportError("Please `pip install disco-py`") from ..auto import tqdm as tqdm_auto -from ..std import TqdmWarning -from ..version import __version__ +from ..utils import _range from .utils_worker import MonoWorker -__author__ = {"github.com/": ["casperdcl", "guigoruiz1"]} +__author__ = {"github.com/": ["casperdcl"]} __all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange'] class DiscordIO(MonoWorker): """Non-blocking file-like IO using a Discord Bot.""" - API = "https://discord.com/api/v10" - UA = f"tqdm (https://tqdm.github.io, {__version__}) {default_user_agent()}" - def __init__(self, token, channel_id): """Creates a new message in the given `channel_id`.""" - super().__init__() - self.token = token - self.channel_id = channel_id - self.session = Session() + super(DiscordIO, self).__init__() + config = ClientConfig() + config.token = token + client = Client(config) self.text = self.__class__.__name__ - self.message_id - - @property - def message_id(self): - if hasattr(self, '_message_id'): - return self._message_id try: - res = self.session.post( - f'{self.API}/channels/{self.channel_id}/messages', - headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}, - json={'content': f"`{self.text}`"}).json() + self.message = client.api.channels_messages_create(channel_id, self.text) except Exception as e: tqdm_auto.write(str(e)) - else: - if res.get('error_code') == 429: - warn("Creation rate limit: try increasing `mininterval`.", - TqdmWarning, stacklevel=2) - else: - self._message_id = res['id'] - return self._message_id + self.message = None def write(self, s): - """Replaces internal `message_id`'s text with `s`.""" + """Replaces internal `message`'s text with `s`.""" if not s: s = "..." s = s.replace('\r', '').strip() if s == self.text: - return # avoid duplicate message Bot error - message_id = self.message_id - if message_id is None: + return # skip duplicate message + message = self.message + if message is None: return self.text = s try: - future = self.submit( - self.session.patch, - f'{self.API}/channels/{self.channel_id}/messages/{message_id}', - headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}, - json={'content': f"`{self.text}`"}) - except Exception as e: - tqdm_auto.write(str(e)) - else: - return future - - def delete(self): - """Deletes internal `message_id`.""" - try: - future = self.submit( - self.session.delete, - f'{self.API}/channels/{self.channel_id}/messages/{self.message_id}', - headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}) + future = self.submit(message.edit, '`' + s + '`') except Exception as e: tqdm_auto.write(str(e)) else: @@ -109,22 +78,26 @@ class tqdm_discord(tqdm_auto): """ Parameters ---------- - token : str, required. Discord bot token + token : str, required. Discord token [default: ${TQDM_DISCORD_TOKEN}]. channel_id : int, required. Discord channel ID [default: ${TQDM_DISCORD_CHANNEL_ID}]. + mininterval : float, optional. + Minimum of [default: 1.5] to avoid rate limit. See `tqdm.auto.tqdm.__init__` for other parameters. """ if not kwargs.get('disable'): kwargs = kwargs.copy() + logging.getLogger("HTTPClient").setLevel(logging.WARNING) self.dio = DiscordIO( - kwargs.pop('token', getenv('TQDM_DISCORD_TOKEN')), - kwargs.pop('channel_id', getenv('TQDM_DISCORD_CHANNEL_ID'))) - super().__init__(*args, **kwargs) + kwargs.pop('token', getenv("TQDM_DISCORD_TOKEN")), + kwargs.pop('channel_id', getenv("TQDM_DISCORD_CHANNEL_ID"))) + kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5)) + super(tqdm_discord, self).__init__(*args, **kwargs) def display(self, **kwargs): - super().display(**kwargs) + super(tqdm_discord, self).display(**kwargs) fmt = self.format_dict if fmt.get('bar_format', None): fmt['bar_format'] = fmt['bar_format'].replace( @@ -134,21 +107,17 @@ class tqdm_discord(tqdm_auto): self.dio.write(self.format_meter(**fmt)) def clear(self, *args, **kwargs): - super().clear(*args, **kwargs) + super(tqdm_discord, self).clear(*args, **kwargs) if not self.disable: self.dio.write("") - def close(self): - if self.disable: - return - super().close() - if not (self.leave or (self.leave is None and self.pos == 0)): - self.dio.delete() - def tdrange(*args, **kwargs): - """Shortcut for `tqdm.contrib.discord.tqdm(range(*args), **kwargs)`.""" - return tqdm_discord(range(*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) # Aliases diff --git a/tqdm/contrib/itertools.py b/tqdm/contrib/itertools.py index e67651a..5f22505 100644 --- a/tqdm/contrib/itertools.py +++ b/tqdm/contrib/itertools.py @@ -1,6 +1,8 @@ """ 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 e06febe..cd9925e 100644 --- a/tqdm/contrib/logging.py +++ b/tqdm/contrib/logging.py @@ -1,12 +1,14 @@ """ 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 # noqa: F401 + from typing import Iterator, List, Optional, Type # pylint: disable=unused-import except ImportError: pass @@ -18,7 +20,7 @@ class _TqdmLoggingHandler(logging.StreamHandler): self, tqdm_class=std_tqdm # type: Type[std_tqdm] ): - super().__init__() + super(_TqdmLoggingHandler, self).__init__() self.tqdm_class = tqdm_class def emit(self, record): diff --git a/tqdm/contrib/slack.py b/tqdm/contrib/slack.py index 9bca8ee..b478d92 100644 --- a/tqdm/contrib/slack.py +++ b/tqdm/contrib/slack.py @@ -6,8 +6,10 @@ Usage: >>> for i in trange(10, token='{token}', channel='{channel}'): ... ... -![screenshot](https://tqdm.github.io/img/screenshot-slack.png) +![screenshot](https://img.tqdm.ml/screenshot-slack.png) """ +from __future__ import absolute_import + import logging from os import getenv @@ -17,6 +19,7 @@ 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"]} @@ -27,7 +30,7 @@ class SlackIO(MonoWorker): """Non-blocking file-like IO using a Slack app.""" def __init__(self, token, channel): """Creates a new message in the given `channel`.""" - super().__init__() + super(SlackIO, self).__init__() self.client = WebClient(token=token) self.text = self.__class__.__name__ try: @@ -88,10 +91,10 @@ class tqdm_slack(tqdm_auto): kwargs.pop('token', getenv("TQDM_SLACK_TOKEN")), kwargs.pop('channel', getenv("TQDM_SLACK_CHANNEL"))) kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5)) - super().__init__(*args, **kwargs) + super(tqdm_slack, self).__init__(*args, **kwargs) def display(self, **kwargs): - super().display(**kwargs) + super(tqdm_slack, self).display(**kwargs) fmt = self.format_dict if fmt.get('bar_format', None): fmt['bar_format'] = fmt['bar_format'].replace( @@ -105,14 +108,17 @@ class tqdm_slack(tqdm_auto): self.sio.write(self.format_meter(**fmt)) def clear(self, *args, **kwargs): - super().clear(*args, **kwargs) + super(tqdm_slack, self).clear(*args, **kwargs) if not self.disable: self.sio.write("") def tsrange(*args, **kwargs): - """Shortcut for `tqdm.contrib.slack.tqdm(range(*args), **kwargs)`.""" - return tqdm_slack(range(*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) # Aliases diff --git a/tqdm/contrib/telegram.py b/tqdm/contrib/telegram.py index 0191518..99cbe8c 100644 --- a/tqdm/contrib/telegram.py +++ b/tqdm/contrib/telegram.py @@ -6,8 +6,10 @@ Usage: >>> for i in trange(10, token='{token}', chat_id='{chat_id}'): ... ... -![screenshot](https://tqdm.github.io/img/screenshot-telegram.gif) +![screenshot](https://img.tqdm.ml/screenshot-telegram.gif) """ +from __future__ import absolute_import + from os import getenv from warnings import warn @@ -15,6 +17,7 @@ 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"]} @@ -27,7 +30,7 @@ class TelegramIO(MonoWorker): def __init__(self, token, chat_id): """Creates a new message in the given `chat_id`.""" - super().__init__() + super(TelegramIO, self).__init__() self.token = token self.chat_id = chat_id self.session = Session() @@ -118,10 +121,10 @@ class tqdm_telegram(tqdm_auto): self.tgio = TelegramIO( kwargs.pop('token', getenv('TQDM_TELEGRAM_TOKEN')), kwargs.pop('chat_id', getenv('TQDM_TELEGRAM_CHAT_ID'))) - super().__init__(*args, **kwargs) + super(tqdm_telegram, self).__init__(*args, **kwargs) def display(self, **kwargs): - super().display(**kwargs) + super(tqdm_telegram, self).display(**kwargs) fmt = self.format_dict if fmt.get('bar_format', None): fmt['bar_format'] = fmt['bar_format'].replace( @@ -131,21 +134,24 @@ class tqdm_telegram(tqdm_auto): self.tgio.write(self.format_meter(**fmt)) def clear(self, *args, **kwargs): - super().clear(*args, **kwargs) + super(tqdm_telegram, self).clear(*args, **kwargs) if not self.disable: self.tgio.write("") def close(self): if self.disable: return - super().close() + super(tqdm_telegram, self).close() if not (self.leave or (self.leave is None and self.pos == 0)): self.tgio.delete() def ttgrange(*args, **kwargs): - """Shortcut for `tqdm.contrib.telegram.tqdm(range(*args), **kwargs)`.""" - return tqdm_telegram(range(*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) # Aliases diff --git a/tqdm/contrib/utils_worker.py b/tqdm/contrib/utils_worker.py index 2a03a2a..17adda6 100644 --- a/tqdm/contrib/utils_worker.py +++ b/tqdm/contrib/utils_worker.py @@ -1,6 +1,8 @@ """ 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 57f1b66..6fc7504 100644 --- a/tqdm/dask.py +++ b/tqdm/dask.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + from functools import partial from dask.callbacks import Callback @@ -20,7 +22,7 @@ class TqdmCallback(Callback): tqdm_kwargs : optional Any other arguments used for all bars. """ - super().__init__(start=start, pretask=pretask) + super(TqdmCallback, self).__init__(start=start, pretask=pretask) if tqdm_kwargs: tqdm_class = partial(tqdm_class, **tqdm_kwargs) self.tqdm_class = tqdm_class diff --git a/tqdm/gui.py b/tqdm/gui.py index cb52fb9..4612701 100644 --- a/tqdm/gui.py +++ b/tqdm/gui.py @@ -8,14 +8,16 @@ 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'] @@ -32,7 +34,7 @@ class tqdm_gui(std_tqdm): # pragma: no cover kwargs = kwargs.copy() kwargs['gui'] = True colour = kwargs.pop('colour', 'g') - super().__init__(*args, **kwargs) + super(tqdm_gui, self).__init__(*args, **kwargs) if self.disable: return @@ -122,7 +124,6 @@ class tqdm_gui(std_tqdm): # pragma: no cover ax = self.ax line1 = self.line1 line2 = self.line2 - hspan = getattr(self, 'hspan', None) # instantaneous rate y = delta_it / delta_t # overall rate @@ -149,10 +150,18 @@ class tqdm_gui(std_tqdm): # pragma: no cover if total: line1.set_data(xdata, ydata) line2.set_data(xdata, zdata) - if hspan: - hspan.set_xy((0, ymin)) - hspan.set_height(ymax - ymin) - hspan.set_width(n / total) + try: + poly_lims = self.hspan.get_xy() + except AttributeError: + self.hspan = self.plt.axhspan(0, 0.001, xmin=0, xmax=0, color='g') + poly_lims = self.hspan.get_xy() + poly_lims[0, 1] = ymin + poly_lims[1, 1] = ymax + poly_lims[2] = [n / total, ymax] + poly_lims[3] = [poly_lims[2, 0], ymin] + if len(poly_lims) > 4: + poly_lims[4, 1] = ymin + self.hspan.set_xy(poly_lims) else: t_ago = [cur_t - i for i in xdata] line1.set_data(t_ago, ydata) @@ -164,14 +173,17 @@ class tqdm_gui(std_tqdm): # pragma: no cover "{bar}", "") msg = self.format_meter(**d) if '' in msg: - msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) + msg = "".join(re.split(r'\|?\|?', msg, 1)) ax.set_title(msg, fontname="DejaVu Sans Mono", fontsize=11) self.plt.pause(1e-9) def tgrange(*args, **kwargs): - """Shortcut for `tqdm.gui.tqdm(range(*args), **kwargs)`.""" - return tqdm_gui(range(*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) # Aliases diff --git a/tqdm/keras.py b/tqdm/keras.py index cce9467..523e62e 100644 --- a/tqdm/keras.py +++ b/tqdm/keras.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, division + from copy import copy from functools import partial @@ -94,7 +96,7 @@ class TqdmCallback(keras.callbacks.Callback): raise KeyError('Unknown verbosity') def on_train_end(self, *_, **__): - if hasattr(self, 'batch_bar'): + if self.verbose: self.batch_bar.close() self.epoch_bar.close() diff --git a/tqdm/notebook.py b/tqdm/notebook.py index 77b91bd..ffd0947 100644 --- a/tqdm/notebook.py +++ b/tqdm/notebook.py @@ -7,14 +7,18 @@ 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 @@ -59,6 +63,12 @@ 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." @@ -80,7 +90,7 @@ class TqdmHBox(HBox): def __repr__(self, pretty=False): pbar = getattr(self, 'pbar', None) if pbar is None: - return super().__repr__() + return super(TqdmHBox, self).__repr__() return pbar.format_meter(**self._json_(pretty)) def _repr_pretty_(self, pp, *_, **__): @@ -157,10 +167,9 @@ 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, maxsplit=1)) + left, right = map(escape, re.split(r'\|?\|?', msg, 1)) else: left, right = '', escape(msg) @@ -220,7 +229,7 @@ class tqdm_notebook(std_tqdm): kwargs['disable'] = bool(kwargs.get('disable', False)) colour = kwargs.pop('colour', None) display_here = kwargs.pop('display', True) - super().__init__(*args, **kwargs) + super(tqdm_notebook, self).__init__(*args, **kwargs) if self.disable or not kwargs['gui']: self.disp = lambda *_, **__: None return @@ -246,7 +255,7 @@ class tqdm_notebook(std_tqdm): def __iter__(self): try: - it = super().__iter__() + it = super(tqdm_notebook, self).__iter__() for obj in it: # return super(tqdm...) will not catch exception yield obj @@ -259,7 +268,7 @@ class tqdm_notebook(std_tqdm): def update(self, n=1): try: - return super().update(n=n) + return super(tqdm_notebook, self).update(n=n) # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt except: # NOQA # cannot catch KeyboardInterrupt when using manual tqdm @@ -272,7 +281,7 @@ class tqdm_notebook(std_tqdm): def close(self): if self.disable: return - super().close() + super(tqdm_notebook, self).close() # Try to detect if there was an error or KeyboardInterrupt # in manual mode: if n < total, things probably got wrong if self.total and self.n < self.total: @@ -297,19 +306,22 @@ class tqdm_notebook(std_tqdm): total : int or float, optional. Total to use for the new bar. """ if self.disable: - return super().reset(total=total) + return super(tqdm_notebook, self).reset(total=total) _, pbar, _ = self.container.children pbar.bar_style = '' if total is not None: pbar.max = total if not self.total and self.ncols is None: # no longer unknown total pbar.layout.width = None # reset width - return super().reset(total=total) + return super(tqdm_notebook, self).reset(total=total) def tnrange(*args, **kwargs): - """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" - return tqdm_notebook(range(*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) # Aliases diff --git a/tqdm/rich.py b/tqdm/rich.py index 3d392ed..69893ff 100644 --- a/tqdm/rich.py +++ b/tqdm/rich.py @@ -6,6 +6,8 @@ Usage: >>> for i in trange(10): ... ... """ +from __future__ import absolute_import + from warnings import warn from rich.progress import ( @@ -13,6 +15,7 @@ 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'] @@ -90,7 +93,7 @@ class tqdm_rich(std_tqdm): # pragma: no cover kwargs['disable'] = bool(kwargs.get('disable', False)) progress = kwargs.pop('progress', None) options = kwargs.pop('options', {}).copy() - super().__init__(*args, **kwargs) + super(tqdm_rich, self).__init__(*args, **kwargs) if self.disable: return @@ -116,8 +119,7 @@ class tqdm_rich(std_tqdm): # pragma: no cover def close(self): if self.disable: return - self.display() # print 100%, vis #1306 - super().close() + super(tqdm_rich, self).close() self._prog.__exit__(None, None, None) def clear(self, *_, **__): @@ -138,12 +140,15 @@ class tqdm_rich(std_tqdm): # pragma: no cover """ if hasattr(self, '_prog'): self._prog.reset(total=total) - super().reset(total=total) + super(tqdm_rich, self).reset(total=total) def trrange(*args, **kwargs): - """Shortcut for `tqdm.rich.tqdm(range(*args), **kwargs)`.""" - return tqdm_rich(range(*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) # Aliases diff --git a/tqdm/std.py b/tqdm/std.py index e91ad30..5f9dcca 100644 --- a/tqdm/std.py +++ b/tqdm/std.py @@ -7,10 +7,12 @@ 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, timezone +from datetime import datetime, timedelta from numbers import Number from time import time from warnings import warn @@ -19,8 +21,8 @@ from weakref import WeakSet from ._monitor import TMonitor from .utils import ( CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace, SimpleTextIOWrapper, - _is_ascii, _screen_shape_wrapper, _supports_unicode, _term_move_up, disp_len, disp_trim, - envwrap) + _basestring, _is_ascii, _range, _screen_shape_wrapper, _supports_unicode, _term_move_up, + _unich, _unicode, disp_len, disp_trim) __author__ = "https://github.com/tqdm/tqdm#contributions" __all__ = ['tqdm', 'trange', @@ -46,7 +48,7 @@ class TqdmWarning(Warning): if fp_write is not None: fp_write("\n" + self.__class__.__name__ + ": " + str(msg).rstrip() + '\n') else: - super().__init__(msg, *a, **k) + super(TqdmWarning, self).__init__(msg, *a, **k) class TqdmExperimentalWarning(TqdmWarning, FutureWarning): @@ -142,7 +144,7 @@ class Bar(object): + `b`: blank (`charset=" "` override) """ ASCII = " 123456789#" - UTF = u" " + u''.join(map(chr, range(0x258F, 0x2587, -1))) + UTF = u" " + u''.join(map(_unich, range(0x258F, 0x2587, -1))) BLANK = " " COLOUR_RESET = '\x1b[0m' COLOUR_RGB = '\x1b[38;2;%d;%d;%dm' @@ -247,120 +249,6 @@ 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 @@ -391,11 +279,11 @@ class tqdm(Comparable): if abs(num) < 999.5: if abs(num) < 99.95: if abs(num) < 9.995: - return f'{num:1.2f}{unit}{suffix}' - return f'{num:2.1f}{unit}{suffix}' - return f'{num:3.0f}{unit}{suffix}' + return '{0:1.2f}'.format(num) + unit + suffix + return '{0:2.1f}'.format(num) + unit + suffix + return '{0:3.0f}'.format(num) + unit + suffix num /= divisor - return f'{num:3.1f}Y{suffix}' + return '{0:3.1f}Y'.format(num) + suffix @staticmethod def format_interval(t): @@ -414,7 +302,10 @@ class tqdm(Comparable): """ mins, s = divmod(int(t), 60) h, m = divmod(mins, 60) - return f'{h:d}:{m:02d}:{s:02d}' if h else f'{m:02d}:{s:02d}' + if h: + return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) + else: + return '{0:02d}:{1:02d}'.format(m, s) @staticmethod def format_num(n): @@ -431,7 +322,7 @@ class tqdm(Comparable): out : str Formatted number. """ - f = f'{n:.3g}'.replace('e+0', 'e+').replace('e-0', 'e-') + f = '{0:.3g}'.format(n).replace('+0', '+').replace('-0', '-') n = str(n) return f if len(f) < len(n) else n @@ -449,7 +340,7 @@ class tqdm(Comparable): getattr(sys.stdout, 'flush', lambda: None)() def fp_write(s): - fp.write(str(s)) + fp.write(_unicode(s)) fp_flush() last_len = [0] @@ -551,10 +442,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 f'{rate:5.2f}') - if rate else '?') + unit + '/s' + rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else + '{0:5.2f}'.format(rate)) if rate else '?') + unit + '/s' rate_inv_fmt = ( - (format_sizeof(inv_rate) if unit_scale else f'{inv_rate:5.2f}') + (format_sizeof(inv_rate) if unit_scale else '{0:5.2f}'.format(inv_rate)) if inv_rate else '?') + 's/' + unit rate_fmt = rate_inv_fmt if inv_rate and inv_rate > 1 else rate_noinv_fmt @@ -574,7 +465,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.fromtimestamp(0, timezone.utc)) + if rate and total else datetime.utcfromtimestamp(0)) except OverflowError: eta_dt = datetime.max @@ -586,25 +477,26 @@ class tqdm(Comparable): else: l_bar = '' - r_bar = f'| {n_fmt}/{total_fmt} [{elapsed_str}<{remaining_str}, {rate_fmt}{postfix}]' + r_bar = '| {0}/{1} [{2}<{3}, {4}{5}]'.format( + n_fmt, total_fmt, elapsed_str, remaining_str, rate_fmt, postfix) # Custom bar formatting # Populate a dict with all available progress indicators - format_dict = { + format_dict = 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: @@ -612,7 +504,7 @@ class tqdm(Comparable): frac = n / total percentage = frac * 100 - l_bar += f'{percentage:3.0f}%|' + l_bar += '{0:3.0f}%|'.format(percentage) if ncols == 0: return l_bar[:-1] + r_bar[1:] @@ -621,16 +513,21 @@ 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() - nobar = bar_format.format(bar=full_bar, **format_dict) + 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) if not full_bar.format_called: - return nobar # no `{bar}`; nothing else to do + # no {bar}, we can just format and return + return nobar # Formatting progress bar space available for bar's display full_bar = Bar(frac, @@ -638,7 +535,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 = str(bar_format) + bar_format = _unicode(bar_format) res = bar_format.format(bar=full_bar, **format_dict) return disp_trim(res, ncols) if ncols else res @@ -657,8 +554,8 @@ class tqdm(Comparable): return disp_trim(res, ncols) if ncols else res else: # no total: no progressbar, ETA, just progress stats - return (f'{(prefix + ": ") if prefix else ""}' - f'{n_fmt}{unit} [{elapsed_str}, {rate_fmt}{postfix}]') + return '{0}{1}{2} [{3}, {4}{5}]'.format( + (prefix + ": ") if prefix else '', n_fmt, unit, elapsed_str, rate_fmt, postfix) def __new__(cls, *_, **__): instance = object.__new__(cls) @@ -930,8 +827,6 @@ 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() @@ -948,17 +843,133 @@ 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=False, - lock_args=None, nrows=None, colour=None, delay=0.0, gui=False, + position=None, postfix=None, unit_divisor=1000, write_bytes=None, + lock_args=None, nrows=None, colour=None, delay=0, gui=False, **kwargs): - """see tqdm.tqdm for arguments""" + """ + 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,) + if file is None: file = sys.stderr @@ -1040,7 +1051,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 = str(bar_format) + bar_format = _unicode(bar_format) if smoothing is None: smoothing = 0 @@ -1109,6 +1120,9 @@ 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 @@ -1284,7 +1298,7 @@ class tqdm(Comparable): # annoyingly, _supports_unicode isn't good enough def fp_write(s): - self.fp.write(str(s)) + self.fp.write(_unicode(s)) try: fp_write('') @@ -1421,7 +1435,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], str): + elif not isinstance(postfix[key], _basestring): postfix[key] = str(postfix[key]) # Else if it's a string, don't need to preprocess anything # Stitch together to get the final postfix @@ -1440,7 +1454,7 @@ class tqdm(Comparable): def moveto(self, n): # TODO: private method - self.fp.write('\n' * n + _term_move_up() * -n) + self.fp.write(_unicode('\n' * n + _term_move_up() * -n)) getattr(self.fp, 'flush', lambda: None)() @property @@ -1520,5 +1534,8 @@ class tqdm(Comparable): def trange(*args, **kwargs): - """Shortcut for tqdm(range(*args), **kwargs).""" - return tqdm(range(*args), **kwargs) + """ + A shortcut for tqdm(xrange(*args), **kwargs). + On Python3+ range is used instead of xrange. + """ + return tqdm(_range(*args), **kwargs) diff --git a/tqdm/tk.py b/tqdm/tk.py index 788303c..92adb51 100644 --- a/tqdm/tk.py +++ b/tqdm/tk.py @@ -6,14 +6,22 @@ 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'] @@ -53,7 +61,7 @@ class tqdm_tk(std_tqdm): # pragma: no cover grab = kwargs.pop('grab', False) tk_parent = kwargs.pop('tk_parent', None) self._cancel_callback = kwargs.pop('cancel_callback', None) - super().__init__(*args, **kwargs) + super(tqdm_tk, self).__init__(*args, **kwargs) if self.disable: return @@ -135,7 +143,7 @@ class tqdm_tk(std_tqdm): # pragma: no cover "{bar}", "") msg = self.format_meter(**d) if '' in msg: - msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) + msg = "".join(re.split(r'\|?\|?', msg, 1)) self._tk_text_var.set(msg) if not self._tk_dispatching: self._tk_window.update() @@ -172,7 +180,7 @@ class tqdm_tk(std_tqdm): # pragma: no cover self._tk_pbar.configure(maximum=100, mode="indeterminate") else: self._tk_pbar.configure(maximum=total, mode="determinate") - super().reset(total=total) + super(tqdm_tk, self).reset(total=total) @staticmethod def _tk_dispatching_helper(): @@ -187,8 +195,11 @@ class tqdm_tk(std_tqdm): # pragma: no cover def ttkrange(*args, **kwargs): - """Shortcut for `tqdm.tk.tqdm(range(*args), **kwargs)`.""" - return tqdm_tk(range(*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) # Aliases diff --git a/tqdm/tqdm.1 b/tqdm/tqdm.1 index b90ab4b..0533198 100644 --- a/tqdm/tqdm.1 +++ b/tqdm/tqdm.1 @@ -204,8 +204,10 @@ float, optional. .TP .B \-\-write\-bytes bool, optional. -Whether to write bytes. -If (default: False) will write unicode. +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. .RS .RE .TP diff --git a/tqdm/utils.py b/tqdm/utils.py index af3ec7d..0632b8d 100644 --- a/tqdm/utils.py +++ b/tqdm/utils.py @@ -4,17 +4,34 @@ General helpers required for `tqdm.std`. import os import re import sys -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 functools import wraps from warnings import warn from weakref import proxy -_range, _unich, _unicode, _basestring = range, chr, str, str +# 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 + 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', 'freebsd']) +IS_NIX = any(CUR_OS.startswith(i) for i in ['aix', 'linux', 'darwin']) RE_ANSI = re.compile(r"\x1b\[[;\d]*[A-Za-z]") try: @@ -31,78 +48,10 @@ 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') - >>> f"{a:5d}" + >>> "{:5d}".format(a) 'something' """ # NOQA: P102 def __init__(self, replace=''): @@ -167,7 +116,7 @@ class SimpleTextIOWrapper(ObjectWrapper): """ # pylint: disable=too-few-public-methods def __init__(self, wrapped, encoding): - super().__init__(wrapped) + super(SimpleTextIOWrapper, self).__init__(wrapped) self.wrapper_setattr('encoding', encoding) def write(self, s): @@ -211,7 +160,7 @@ class DisableOnWriteError(ObjectWrapper): return inner def __init__(self, wrapped, tqdm_instance): - super().__init__(wrapped) + super(DisableOnWriteError, self).__init__(wrapped) if hasattr(wrapped, 'write'): self.wrapper_setattr( 'write', self.disable_on_exception(tqdm_instance, wrapped.write)) @@ -229,7 +178,7 @@ class CallbackIOWrapper(ObjectWrapper): Wrap a given `file`-like object's `read()` or `write()` to report lengths to the given `callback` """ - super().__init__(stream) + super(CallbackIOWrapper, self).__init__(stream) func = getattr(stream, method) if method == "write": @wraps(func) @@ -371,8 +320,14 @@ def _term_move_up(): # pragma: no cover return '' if (os.name == 'nt') and (colorama is None) else '\x1b[A' -def _text_width(s): - return sum(2 if east_asian_width(ch) in 'FW' else 1 for ch in str(s)) +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 disp_len(data):