├── .github ├── FUNDING.yml ├── ci-reporter.yml └── workflows │ ├── codeql.yml │ ├── main.yml │ └── stale.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGES.rst ├── CONTRIBUTING.rst ├── ISSUE_TEMPLATE ├── LICENSE ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── circle.yml ├── docs ├── .gitignore ├── Makefile ├── _theme │ ├── LICENSE │ ├── flask_theme_support.py │ └── wolph │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ ├── flasky.css_t │ │ └── small_flask.css │ │ └── theme.conf ├── conf.py ├── contributing.rst ├── examples.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── progressbar.algorithms.rst ├── progressbar.bar.rst ├── progressbar.base.rst ├── progressbar.env.rst ├── progressbar.multi.rst ├── progressbar.rst ├── progressbar.shortcuts.rst ├── progressbar.terminal.base.rst ├── progressbar.terminal.colors.rst ├── progressbar.terminal.rst ├── progressbar.terminal.stream.rst ├── progressbar.utils.rst ├── progressbar.widgets.rst ├── requirements.txt └── usage.rst ├── examples.py ├── progressbar ├── __about__.py ├── __init__.py ├── __main__.py ├── algorithms.py ├── bar.py ├── base.py ├── env.py ├── multi.py ├── py.typed ├── shortcuts.py ├── terminal │ ├── __init__.py │ ├── base.py │ ├── colors.py │ ├── os_specific │ │ ├── __init__.py │ │ ├── posix.py │ │ └── windows.py │ └── stream.py ├── utils.py └── widgets.py ├── pyproject.toml ├── pytest.ini ├── ruff.toml ├── tests ├── conftest.py ├── original_examples.py ├── requirements.txt ├── test_algorithms.py ├── test_backwards_compatibility.py ├── test_color.py ├── test_custom_widgets.py ├── test_data.py ├── test_data_transfer_bar.py ├── test_dill_pickle.py ├── test_empty.py ├── test_end.py ├── test_failure.py ├── test_flush.py ├── test_iterators.py ├── test_job_status.py ├── test_large_values.py ├── test_misc.py ├── test_monitor_progress.py ├── test_multibar.py ├── test_progressbar.py ├── test_progressbar_command.py ├── test_samples.py ├── test_speed.py ├── test_stream.py ├── test_terminal.py ├── test_timed.py ├── test_timer.py ├── test_unicode.py ├── test_unknown_length.py ├── test_utils.py ├── test_widgets.py ├── test_windows.py ├── test_with.py └── test_wrappingio.py ├── tox.ini └── uv.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: WoLpH 2 | -------------------------------------------------------------------------------- /.github/ci-reporter.yml: -------------------------------------------------------------------------------- 1 | # Set to false to create a new comment instead of updating the app's first one 2 | updateComment: true 3 | 4 | # Use a custom string, or set to false to disable 5 | before: "✨ Good work on this PR so far! ✨ Unfortunately, the [ build]() is failing as of . Here's the output:" 6 | 7 | # Use a custom string, or set to false to disable 8 | after: "I'm sure you can fix it! If you need help, don't hesitate to ask a maintainer of the project!" 9 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "develop" ] 6 | pull_request: 7 | branches: [ "develop" ] 8 | schedule: 9 | - cron: "24 21 * * 1" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ python, javascript ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | if: ${{ matrix.language == 'python' || matrix.language == 'javascript' }} 38 | 39 | - name: Perform CodeQL Analysis 40 | uses: github/codeql-action/analyze@v2 41 | with: 42 | category: "/language:${{ matrix.language }}" 43 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: tox 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 10 12 | strategy: 13 | matrix: 14 | python-version: ['3.9', '3.10', '3.11', '3.12'] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip tox 25 | - name: Test with tox 26 | run: tox 27 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues and pull requests 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * *' # Run every day at midnight 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v8 13 | with: 14 | days-before-issue-stale: 30 15 | exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement 16 | exempt-all-assignees: true 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | 24 | # Installer logs 25 | pip-log.txt 26 | pip-delete-this-directory.txt 27 | 28 | # Unit test / coverage reports 29 | htmlcov/ 30 | .tox/ 31 | .coverage 32 | .cache 33 | nosetests.xml 34 | coverage.xml 35 | 36 | # Translations 37 | *.mo 38 | *.pot 39 | 40 | # Django stuff: 41 | *.log 42 | 43 | # Sphinx documentation 44 | docs/_build/ 45 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v2.4.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: check-yaml 9 | - id: check-added-large-files 10 | 11 | - repo: https://gitlab.com/pycqa/flake8 12 | rev: 3.7.9 13 | hooks: 14 | - id: flake8 15 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | formats: 27 | - pdf 28 | - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | python: 34 | install: 35 | - requirements: docs/requirements.txt 36 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | For the most recent changes to the Python Progressbar please look at the Git 6 | releases or the commit log: 7 | 8 | - https://github.com/WoLpH/python-progressbar/releases 9 | - https://github.com/WoLpH/python-progressbar/commits/develop 10 | 11 | Hint: click on the `...` button to see the change message. 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/WoLpH/python-progressbar/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | Python Progressbar could always use more documentation, whether as part of the 40 | official Python Progressbar docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/WoLpH/python-progressbar/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `python-progressbar` for local development. 59 | 60 | 1. Fork the `python-progressbar` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | $ git clone --branch develop git@github.com:your_name_here/python-progressbar.git 64 | 65 | 3. Install your local copy into a virtualenv. Assuming you have `uv` installed, this is how you set up your fork for local development:: 66 | 67 | $ cd progressbar/ 68 | $ uv sync 69 | 70 | 4. Create a branch for local development with `git-flow-avh`_:: 71 | 72 | $ git-flow feature start name-of-your-bugfix-or-feature 73 | 74 | Or without git-flow: 75 | 76 | $ git checkout -b feature/name-of-your-bugfix-or-feature 77 | 78 | Now you can make your changes locally. 79 | 80 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 progressbar tests 83 | $ py.test 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv using the requirements file. 87 | 88 | $ pip install -r tests/requirements.txt 89 | 90 | 6. Commit your changes and push your branch to GitHub with `git-flow-avh`_:: 91 | 92 | $ git add . 93 | $ git commit -m "Your detailed description of your changes." 94 | $ git-flow feature publish 95 | 96 | Or without git-flow: 97 | 98 | $ git add . 99 | $ git commit -m "Your detailed description of your changes." 100 | $ git push -u origin feature/name-of-your-bugfix-or-feature 101 | 102 | 7. Submit a pull request through the GitHub website. 103 | 104 | Pull Request Guidelines 105 | ----------------------- 106 | 107 | Before you submit a pull request, check that it meets these guidelines: 108 | 109 | 1. The pull request should include tests. 110 | 2. If the pull request adds functionality, the docs should be updated. Put 111 | your new functionality into a function with a docstring, and add the 112 | feature to the list in README.rst. 113 | 3. The pull request should work for Python 2.7, 3.3, and for PyPy. Check 114 | https://travis-ci.org/WoLpH/python-progressbar/pull_requests 115 | and make sure that the tests pass for all supported Python versions. 116 | 117 | Tips 118 | ---- 119 | 120 | To run a subset of tests:: 121 | 122 | $ py.test tests/some_test.py 123 | 124 | .. _git-flow-avh: https://github.com/petervanderdoes/gitflow 125 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | #### Description 2 | 3 | Description of the problem 4 | 5 | #### Code 6 | 7 | If applicable, code to reproduce the issue and/or the stacktrace of the issue 8 | 9 | #### Versions 10 | 11 | - Python version: `import sys; print(sys.version)` 12 | - Python distribution/environment: CPython/Anaconda/IPython/IDLE 13 | - Operating System: Windows 10, Ubuntu Linux, etc. 14 | - Package version: `import progressbar; print(progressbar.__version__)` 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Rick van Hattem (Wolph) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Python Progressbar nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 27 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-exclude *.pyc 2 | recursive-exclude *.pyo 3 | recursive-exclude *.html 4 | include AUTHORS.rst 5 | include CHANGES.rst 6 | include CONTRIBUTING.rst 7 | include LICENSE 8 | include README.rst 9 | include examples.py 10 | include requirements.txt 11 | include Makefile 12 | include pytest.ini 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # What Python version is installed where: 2 | # http://www.appveyor.com/docs/installed-software#python 3 | 4 | environment: 5 | matrix: 6 | - PYTHON: "C:\\Python27" 7 | TOX_ENV: "py27" 8 | 9 | - PYTHON: "C:\\Python34" 10 | TOX_ENV: "py34" 11 | 12 | - PYTHON: "C:\\Python35" 13 | TOX_ENV: "py35" 14 | 15 | - PYTHON: "C:\\Python36" 16 | TOX_ENV: "py36" 17 | 18 | 19 | init: 20 | - "%PYTHON%/python -V" 21 | - "%PYTHON%/python -c \"import struct;print( 8 * struct.calcsize(\'P\'))\"" 22 | 23 | install: 24 | - "%PYTHON%/Scripts/easy_install -U pip" 25 | - "%PYTHON%/Scripts/pip install tox" 26 | - "%PYTHON%/Scripts/pip install wheel" 27 | 28 | build: false # Not a C# project, build stuff at the test step instead. 29 | 30 | test_script: 31 | - "%PYTHON%/Scripts/tox -e %TOX_ENV%" 32 | 33 | after_test: 34 | - "%PYTHON%/python setup.py bdist_wheel" 35 | - ps: "ls dist" 36 | 37 | artifacts: 38 | - path: dist\* 39 | 40 | #on_success: 41 | # - TODO: upload the content of dist/*.whl to a public wheelhouse 42 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | pre: 3 | - pip install -r tests/requirements.txt 4 | 5 | test: 6 | override: 7 | - py.test 8 | 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ProgressBar.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ProgressBar.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ProgressBar" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ProgressBar" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_theme/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2012 Rick van Hattem. 4 | 5 | 6 | Original Projects: 7 | 8 | Copyright (c) 2010 Kenneth Reitz. 9 | Copyright (c) 2010 by Armin Ronacher. 10 | 11 | 12 | Some rights reserved. 13 | 14 | Redistribution and use in source and binary forms of the theme, with or 15 | without modification, are permitted provided that the following conditions 16 | are met: 17 | 18 | * Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | * Redistributions in binary form must reproduce the above 22 | copyright notice, this list of conditions and the following 23 | disclaimer in the documentation and/or other materials provided 24 | with the distribution. 25 | 26 | * The names of the contributors may not be used to endorse or 27 | promote products derived from this software without specific 28 | prior written permission. 29 | 30 | We kindly ask you to only use these themes in an unmodified manner just 31 | for Flask and Flask-related products, not for unrelated projects. If you 32 | like the visual style and want to use it for your own projects, please 33 | consider making some larger changes to the themes (such as changing 34 | font faces, sizes, colors or margins). 35 | 36 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 39 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 40 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 41 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 42 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 43 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 44 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 45 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 46 | POSSIBILITY OF SUCH DAMAGE. 47 | -------------------------------------------------------------------------------- /docs/_theme/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import ( 4 | Comment, 5 | Error, 6 | Generic, 7 | Keyword, 8 | Literal, 9 | Name, 10 | Number, 11 | Operator, 12 | Other, 13 | Punctuation, 14 | String, 15 | Whitespace, 16 | ) 17 | 18 | 19 | class FlaskyStyle(Style): 20 | background_color = '#f8f8f8' 21 | default_style = '' 22 | 23 | styles = { 24 | # No corresponding class for the following: 25 | # Text: '', # class: '' 26 | Whitespace: 'underline #f8f8f8', # class: 'w' 27 | Error: '#a40000 border:#ef2929', # class: 'err' 28 | Other: '#000000', # class 'x' 29 | Comment: 'italic #8f5902', # class: 'c' 30 | Comment.Preproc: 'noitalic', # class: 'cp' 31 | Keyword: 'bold #004461', # class: 'k' 32 | Keyword.Constant: 'bold #004461', # class: 'kc' 33 | Keyword.Declaration: 'bold #004461', # class: 'kd' 34 | Keyword.Namespace: 'bold #004461', # class: 'kn' 35 | Keyword.Pseudo: 'bold #004461', # class: 'kp' 36 | Keyword.Reserved: 'bold #004461', # class: 'kr' 37 | Keyword.Type: 'bold #004461', # class: 'kt' 38 | Operator: '#582800', # class: 'o' 39 | Operator.Word: 'bold #004461', # class: 'ow' - like keywords 40 | Punctuation: 'bold #000000', # class: 'p' 41 | # because special names such as Name.Class, Name.Function, etc. 42 | # are not recognized as such later in the parsing, we choose them 43 | # to look the same as ordinary variables. 44 | Name: '#000000', # class: 'n' 45 | Name.Attribute: '#c4a000', # class: 'na' - to be revised 46 | Name.Builtin: '#004461', # class: 'nb' 47 | Name.Builtin.Pseudo: '#3465a4', # class: 'bp' 48 | Name.Class: '#000000', # class: 'nc' - to be revised 49 | Name.Constant: '#000000', # class: 'no' - to be revised 50 | Name.Decorator: '#888', # class: 'nd' - to be revised 51 | Name.Entity: '#ce5c00', # class: 'ni' 52 | Name.Exception: 'bold #cc0000', # class: 'ne' 53 | Name.Function: '#000000', # class: 'nf' 54 | Name.Property: '#000000', # class: 'py' 55 | Name.Label: '#f57900', # class: 'nl' 56 | Name.Namespace: '#000000', # class: 'nn' - to be revised 57 | Name.Other: '#000000', # class: 'nx' 58 | Name.Tag: 'bold #004461', # class: 'nt' - like a keyword 59 | Name.Variable: '#000000', # class: 'nv' - to be revised 60 | Name.Variable.Class: '#000000', # class: 'vc' - to be revised 61 | Name.Variable.Global: '#000000', # class: 'vg' - to be revised 62 | Name.Variable.Instance: '#000000', # class: 'vi' - to be revised 63 | Number: '#990000', # class: 'm' 64 | Literal: '#000000', # class: 'l' 65 | Literal.Date: '#000000', # class: 'ld' 66 | String: '#4e9a06', # class: 's' 67 | String.Backtick: '#4e9a06', # class: 'sb' 68 | String.Char: '#4e9a06', # class: 'sc' 69 | String.Doc: 'italic #8f5902', # class: 'sd' - like a comment 70 | String.Double: '#4e9a06', # class: 's2' 71 | String.Escape: '#4e9a06', # class: 'se' 72 | String.Heredoc: '#4e9a06', # class: 'sh' 73 | String.Interpol: '#4e9a06', # class: 'si' 74 | String.Other: '#4e9a06', # class: 'sx' 75 | String.Regex: '#4e9a06', # class: 'sr' 76 | String.Single: '#4e9a06', # class: 's1' 77 | String.Symbol: '#4e9a06', # class: 'ss' 78 | Generic: '#000000', # class: 'g' 79 | Generic.Deleted: '#a40000', # class: 'gd' 80 | Generic.Emph: 'italic #000000', # class: 'ge' 81 | Generic.Error: '#ef2929', # class: 'gr' 82 | Generic.Heading: 'bold #000080', # class: 'gh' 83 | Generic.Inserted: '#00A000', # class: 'gi' 84 | Generic.Output: '#888', # class: 'go' 85 | Generic.Prompt: '#745334', # class: 'gp' 86 | Generic.Strong: 'bold #000000', # class: 'gs' 87 | Generic.Subheading: 'bold #800080', # class: 'gu' 88 | Generic.Traceback: 'bold #a40000', # class: 'gt' 89 | } 90 | -------------------------------------------------------------------------------- /docs/_theme/wolph/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {%- block footer %} 12 | 22 | 23 | 27 | {%- endblock %} 28 | -------------------------------------------------------------------------------- /docs/_theme/wolph/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_theme/wolph/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 0px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0 0 20px 0; 95 | margin: 0; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #555; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input[type="text"] { 136 | width: 160px!important; 137 | } 138 | div.sphinxsidebar input { 139 | border: 1px solid #ccc; 140 | font-family: 'Georgia', serif; 141 | font-size: 1em; 142 | } 143 | 144 | /* -- body styles ----------------------------------------------------------- */ 145 | 146 | a { 147 | color: #004B6B; 148 | text-decoration: underline; 149 | } 150 | 151 | a:hover { 152 | color: #6D4100; 153 | text-decoration: underline; 154 | } 155 | 156 | div.body h1, 157 | div.body h2, 158 | div.body h3, 159 | div.body h4, 160 | div.body h5, 161 | div.body h6 { 162 | font-family: 'Garamond', 'Georgia', serif; 163 | font-weight: normal; 164 | margin: 30px 0px 10px 0px; 165 | padding: 0; 166 | } 167 | 168 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 169 | div.body h2 { font-size: 180%; } 170 | div.body h3 { font-size: 150%; } 171 | div.body h4 { font-size: 130%; } 172 | div.body h5 { font-size: 100%; } 173 | div.body h6 { font-size: 100%; } 174 | 175 | a.headerlink { 176 | color: #ddd; 177 | padding: 0 4px; 178 | text-decoration: none; 179 | } 180 | 181 | a.headerlink:hover { 182 | color: #444; 183 | background: #eaeaea; 184 | } 185 | 186 | div.body p, div.body dd, div.body li { 187 | line-height: 1.4em; 188 | } 189 | 190 | div.admonition { 191 | background: #fafafa; 192 | margin: 20px -30px; 193 | padding: 10px 30px; 194 | border-top: 1px solid #ccc; 195 | border-bottom: 1px solid #ccc; 196 | } 197 | 198 | div.admonition tt.xref, div.admonition a tt { 199 | border-bottom: 1px solid #fafafa; 200 | } 201 | 202 | dd div.admonition { 203 | margin-left: -60px; 204 | padding-left: 60px; 205 | } 206 | 207 | div.admonition p.admonition-title { 208 | font-family: 'Garamond', 'Georgia', serif; 209 | font-weight: normal; 210 | font-size: 24px; 211 | margin: 0 0 10px 0; 212 | padding: 0; 213 | line-height: 1; 214 | } 215 | 216 | div.admonition p.last { 217 | margin-bottom: 0; 218 | } 219 | 220 | div.highlight { 221 | background-color: white; 222 | } 223 | 224 | dt:target, .highlight { 225 | background: #FAF3E8; 226 | } 227 | 228 | div.note { 229 | background-color: #eee; 230 | border: 1px solid #ccc; 231 | } 232 | 233 | div.seealso { 234 | background-color: #ffc; 235 | border: 1px solid #ff6; 236 | } 237 | 238 | div.topic { 239 | background-color: #eee; 240 | } 241 | 242 | p.admonition-title { 243 | display: inline; 244 | } 245 | 246 | p.admonition-title:after { 247 | content: ":"; 248 | } 249 | 250 | pre, tt { 251 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 252 | font-size: 0.9em; 253 | } 254 | 255 | img.screenshot { 256 | } 257 | 258 | tt.descname, tt.descclassname { 259 | font-size: 0.95em; 260 | } 261 | 262 | tt.descname { 263 | padding-right: 0.08em; 264 | } 265 | 266 | img.screenshot { 267 | -moz-box-shadow: 2px 2px 4px #eee; 268 | -webkit-box-shadow: 2px 2px 4px #eee; 269 | box-shadow: 2px 2px 4px #eee; 270 | } 271 | 272 | table.docutils { 273 | border: 1px solid #888; 274 | -moz-box-shadow: 2px 2px 4px #eee; 275 | -webkit-box-shadow: 2px 2px 4px #eee; 276 | box-shadow: 2px 2px 4px #eee; 277 | } 278 | 279 | table.docutils td, table.docutils th { 280 | border: 1px solid #888; 281 | padding: 0.25em 0.7em; 282 | } 283 | 284 | table.field-list, table.footnote { 285 | border: none; 286 | -moz-box-shadow: none; 287 | -webkit-box-shadow: none; 288 | box-shadow: none; 289 | } 290 | 291 | table.footnote { 292 | margin: 15px 0; 293 | width: 100%; 294 | border: 1px solid #eee; 295 | background: #fdfdfd; 296 | font-size: 0.9em; 297 | } 298 | 299 | table.footnote + table.footnote { 300 | margin-top: -15px; 301 | border-top: none; 302 | } 303 | 304 | table.field-list th { 305 | padding: 0 0.8em 0 0; 306 | } 307 | 308 | table.field-list td { 309 | padding: 0; 310 | } 311 | 312 | table.footnote td.label { 313 | width: 0px; 314 | padding: 0.3em 0 0.3em 0.5em; 315 | } 316 | 317 | table.footnote td { 318 | padding: 0.3em 0.5em; 319 | } 320 | 321 | dl { 322 | margin: 0; 323 | padding: 0; 324 | } 325 | 326 | dl dd { 327 | margin-left: 30px; 328 | } 329 | 330 | blockquote { 331 | margin: 0 0 0 30px; 332 | padding: 0; 333 | } 334 | 335 | ul, ol { 336 | margin: 10px 0 10px 30px; 337 | padding: 0; 338 | } 339 | 340 | pre { 341 | background: #eee; 342 | padding: 7px 30px; 343 | margin: 15px -30px; 344 | line-height: 1.3em; 345 | } 346 | 347 | dl pre, blockquote pre, li pre { 348 | margin-left: -60px; 349 | padding-left: 60px; 350 | } 351 | 352 | dl dl pre { 353 | margin-left: -90px; 354 | padding-left: 90px; 355 | } 356 | 357 | tt { 358 | background-color: #ecf0f3; 359 | color: #222; 360 | /* padding: 1px 2px; */ 361 | } 362 | 363 | tt.xref, a tt { 364 | background-color: #FBFBFB; 365 | border-bottom: 1px solid white; 366 | } 367 | 368 | a.reference { 369 | text-decoration: none; 370 | border-bottom: 1px dotted #004B6B; 371 | } 372 | 373 | a.reference:hover { 374 | border-bottom: 1px solid #6D4100; 375 | } 376 | 377 | a.footnote-reference { 378 | text-decoration: none; 379 | font-size: 0.7em; 380 | vertical-align: top; 381 | border-bottom: 1px dotted #004B6B; 382 | } 383 | 384 | a.footnote-reference:hover { 385 | border-bottom: 1px solid #6D4100; 386 | } 387 | 388 | a:hover tt { 389 | background: #EEE; 390 | } 391 | 392 | 393 | /* scrollbars */ 394 | 395 | ::-webkit-scrollbar { 396 | width: 6px; 397 | height: 6px; 398 | } 399 | 400 | ::-webkit-scrollbar-button:start:decrement, 401 | ::-webkit-scrollbar-button:end:increment { 402 | display: block; 403 | height: 10px; 404 | } 405 | 406 | ::-webkit-scrollbar-button:vertical:increment { 407 | background-color: #fff; 408 | } 409 | 410 | ::-webkit-scrollbar-track-piece { 411 | background-color: #eee; 412 | -webkit-border-radius: 3px; 413 | } 414 | 415 | ::-webkit-scrollbar-thumb:vertical { 416 | height: 50px; 417 | background-color: #ccc; 418 | -webkit-border-radius: 3px; 419 | } 420 | 421 | ::-webkit-scrollbar-thumb:horizontal { 422 | width: 50px; 423 | background-color: #ccc; 424 | -webkit-border-radius: 3px; 425 | } 426 | 427 | /* misc. */ 428 | 429 | .revsys-inline { 430 | display: none!important; 431 | } 432 | -------------------------------------------------------------------------------- /docs/_theme/wolph/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /docs/_theme/wolph/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # 4 | # Progress Bar documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Aug 20 11:47:33 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | import datetime 15 | import os 16 | import sys 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath('..')) 22 | 23 | from progressbar import __about__ as metadata 24 | 25 | # -- General configuration ----------------------------------------------- 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be extensions 31 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.doctest', 35 | 'sphinx.ext.intersphinx', 36 | 'sphinx.ext.todo', 37 | 'sphinx.ext.coverage', 38 | 'sphinx.ext.mathjax', 39 | 'sphinx.ext.ifconfig', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.napoleon', 42 | ] 43 | 44 | suppress_warnings = [ 45 | 'image.nonlocal_uri', 46 | ] 47 | 48 | needs_sphinx = '1.4' 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix of source filenames. 53 | source_suffix = '.rst' 54 | 55 | # The encoding of source files. 56 | # source_encoding = 'utf-8-sig' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # General information about the project. 62 | project = 'Progress Bar' 63 | project_slug: str = ''.join(project.capitalize().split()) 64 | copyright = f'{datetime.date.today().year}, {metadata.__author__}' 65 | 66 | # The version info for the project you're documenting, acts as replacement for 67 | # |version| and |release|, also used in various other places throughout the 68 | # built documents. 69 | # 70 | # The short X.Y version. 71 | version: str = metadata.__version__ 72 | # The full version, including alpha/beta/rc tags. 73 | release: str = metadata.__version__ 74 | 75 | # The language for content autogenerated by Sphinx. Refer to documentation 76 | # for a list of supported languages. 77 | # language = None 78 | 79 | # There are two options for replacing |today|: either, you set today to some 80 | # non-false value, then it is used: 81 | # today = '' 82 | # Else, today_fmt is used as the format for a strftime call. 83 | # today_fmt = '%B %d, %Y' 84 | 85 | # List of patterns, relative to source directory, that match files and 86 | # directories to ignore when looking for source files. 87 | exclude_patterns = ['_build'] 88 | 89 | # The reST default role (used for this markup: `text`) to use for all documents. 90 | # default_role = None 91 | 92 | # If true, '()' will be appended to :func: etc. cross-reference text. 93 | # add_function_parentheses = True 94 | 95 | # If true, the current module name will be prepended to all description 96 | # unit titles (such as .. function::). 97 | # add_module_names = True 98 | 99 | # If true, sectionauthor and moduleauthor directives will be shown in the 100 | # output. They are ignored by default. 101 | show_authors = True 102 | 103 | # The name of the Pygments (syntax highlighting) style to use. 104 | pygments_style = 'sphinx' 105 | 106 | # A list of ignored prefixes for module index sorting. 107 | # modindex_common_prefix = [] 108 | 109 | # If true, keep warnings as "system message" paragraphs in the built documents. 110 | # keep_warnings = False 111 | 112 | 113 | # -- Options for HTML output --------------------------------------------- 114 | 115 | # The theme to use for HTML and HTML Help pages. See the documentation for 116 | # a list of builtin themes. 117 | html_theme = 'wolph' 118 | 119 | # Theme options are theme-specific and customize the look and feel of a theme 120 | # further. For a list of options available for each theme, see the 121 | # documentation. 122 | # html_theme_options = {} 123 | 124 | # Add any paths that contain custom themes here, relative to this directory. 125 | html_theme_path = ['_theme'] 126 | 127 | # The name for this set of Sphinx documents. If None, it defaults to 128 | # " v documentation". 129 | # html_title = None 130 | 131 | # A shorter title for the navigation bar. Default is the same as html_title. 132 | # html_short_title = None 133 | 134 | # The name of an image file (relative to this directory) to place at the top 135 | # of the sidebar. 136 | # html_logo = None 137 | 138 | # The name of an image file (within the static path) to use as favicon of the 139 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 140 | # pixels large. 141 | # html_favicon = None 142 | 143 | # Add any paths that contain custom static files (such as style sheets) here, 144 | # relative to this directory. They are copied after the builtin static files, 145 | # so a file named "default.css" will overwrite the builtin "default.css". 146 | html_static_path = ['_static'] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 149 | # using the given strftime format. 150 | # html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | # html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | # html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names to 160 | # template names. 161 | # html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | # html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | # html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | # html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | # html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 176 | # html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 179 | # html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages will 182 | # contain a tag referring to it. The value of this option must be the 183 | # base URL from which the finished HTML is served. 184 | # html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | # html_file_suffix = None 188 | 189 | # Output file base name for HTML help builder. 190 | htmlhelp_basename = f'{project_slug}doc' 191 | 192 | 193 | # -- Options for LaTeX output -------------------------------------------- 194 | 195 | latex_elements = { 196 | # The paper size ('letterpaper' or 'a4paper'). 197 | #'papersize': 'letterpaper', 198 | # The font size ('10pt', '11pt' or '12pt'). 199 | #'pointsize': '10pt', 200 | # Additional stuff for the LaTeX preamble. 201 | #'preamble': '', 202 | } 203 | 204 | # Grouping the document tree into LaTeX files. List of tuples 205 | # (source start file, target name, title, author, documentclass [howto/manual]). 206 | latex_documents: list[tuple[str, ...]] = [ 207 | ( 208 | 'index', 209 | f'{project_slug}.tex', 210 | f'{project} Documentation', 211 | metadata.__author__, 212 | 'manual', 213 | ) 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at the top of 217 | # the title page. 218 | # latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings are parts, 221 | # not chapters. 222 | # latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | # latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | # latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | # latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | # latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output -------------------------------------- 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages: list[tuple[str, str, str, list[str], int]] = [ 242 | ( 243 | 'index', 244 | project_slug.lower(), 245 | f'{project} Documentation', 246 | [metadata.__author__], 247 | 1, 248 | ) 249 | ] 250 | 251 | # If true, show URL addresses after external links. 252 | # man_show_urls = False 253 | 254 | 255 | # -- Options for Texinfo output ------------------------------------------ 256 | 257 | # Grouping the document tree into Texinfo files. List of tuples 258 | # (source start file, target name, title, author, 259 | # dir menu entry, description, category) 260 | texinfo_documents: list[tuple[str, ...]] = [ 261 | ( 262 | 'index', 263 | project_slug, 264 | f'{project} Documentation', 265 | metadata.__author__, 266 | project_slug, 267 | 'One line description of project.', 268 | 'Miscellaneous', 269 | ) 270 | ] 271 | 272 | # Documents to append as an appendix to all manuals. 273 | # texinfo_appendices = [] 274 | 275 | # If false, no module index is generated. 276 | # texinfo_domain_indices = True 277 | 278 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 279 | # texinfo_show_urls = 'footnote' 280 | 281 | # If true, do not generate a @detailmenu in the "Top" node's menu. 282 | # texinfo_no_detailmenu = False 283 | 284 | 285 | # -- Options for Epub output --------------------------------------------- 286 | 287 | # Bibliographic Dublin Core info. 288 | epub_title: str = project 289 | epub_author: str = metadata.__author__ 290 | epub_publisher: str = metadata.__author__ 291 | epub_copyright: str = copyright 292 | 293 | # The language of the text. It defaults to the language option 294 | # or en if the language is not set. 295 | # epub_language = '' 296 | 297 | # The scheme of the identifier. Typical schemes are ISBN or URL. 298 | # epub_scheme = '' 299 | 300 | # The unique identifier of the text. This can be a ISBN number 301 | # or the project homepage. 302 | # epub_identifier = '' 303 | 304 | # A unique identification for the text. 305 | # epub_uid = '' 306 | 307 | # A tuple containing the cover image and cover page html template filenames. 308 | # epub_cover = () 309 | 310 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 311 | # epub_guide = () 312 | 313 | # HTML files that should be inserted before the pages created by sphinx. 314 | # The format is a list of tuples containing the path and title. 315 | # epub_pre_files = [] 316 | 317 | # HTML files that should be inserted after the pages created by sphinx. 318 | # The format is a list of tuples containing the path and title. 319 | # epub_post_files = [] 320 | 321 | # A list of files that should not be packed into the epub file. 322 | # epub_exclude_files = [] 323 | 324 | # The depth of the table of contents in toc.ncx. 325 | # epub_tocdepth = 3 326 | 327 | # Allow duplicate toc entries. 328 | # epub_tocdup = True 329 | 330 | # Fix unsupported image types using the PIL. 331 | # epub_fix_images = False 332 | 333 | # Scale large images. 334 | # epub_max_image_width = 0 335 | 336 | # If 'no', URL addresses will not be shown. 337 | # epub_show_urls = 'inline' 338 | 339 | # If false, no index is generated. 340 | # epub_use_index = True 341 | 342 | 343 | # Example configuration for intersphinx: refer to the Python standard library. 344 | intersphinx_mapping: dict[str, tuple[str, None]] = { 345 | 'python': ('https://docs.python.org/3', None) 346 | } 347 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | =================== 3 | 4 | .. literalinclude:: ../examples.py 5 | 6 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. _history: 2 | 3 | ======= 4 | History 5 | ======= 6 | 7 | .. include:: ../CHANGES.rst 8 | :start-line: 5 9 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ======================================== 2 | Welcome to Progress Bar's documentation! 3 | ======================================== 4 | 5 | .. toctree:: 6 | :maxdepth: 4 7 | 8 | usage 9 | examples 10 | contributing 11 | installation 12 | progressbar.shortcuts 13 | progressbar.bar 14 | progressbar.base 15 | progressbar.utils 16 | progressbar.widgets 17 | history 18 | 19 | .. include:: ../README.rst 20 | 21 | ****************** 22 | Indices and tables 23 | ****************** 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | 29 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ pip install progressbar2 8 | 9 | Or if you don't have pip:: 10 | 11 | $ easy_install progressbar2 12 | 13 | Or, if you have virtualenvwrapper installed:: 14 | 15 | $ mkvirtualenv progressbar2 16 | $ pip install progressbar2 17 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\progressbar.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\progressbar.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/progressbar.algorithms.rst: -------------------------------------------------------------------------------- 1 | progressbar.algorithms module 2 | ============================= 3 | 4 | .. automodule:: progressbar.algorithms 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.bar.rst: -------------------------------------------------------------------------------- 1 | progressbar.bar module 2 | ====================== 3 | 4 | .. automodule:: progressbar.bar 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :member-order: bysource 9 | -------------------------------------------------------------------------------- /docs/progressbar.base.rst: -------------------------------------------------------------------------------- 1 | progressbar.base module 2 | ======================= 3 | 4 | .. automodule:: progressbar.base 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.env.rst: -------------------------------------------------------------------------------- 1 | progressbar.env module 2 | ====================== 3 | 4 | .. automodule:: progressbar.env 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.multi.rst: -------------------------------------------------------------------------------- 1 | progressbar.multi module 2 | ======================== 3 | 4 | .. automodule:: progressbar.multi 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.rst: -------------------------------------------------------------------------------- 1 | progressbar package 2 | =================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | progressbar.terminal 11 | 12 | Submodules 13 | ---------- 14 | 15 | .. toctree:: 16 | :maxdepth: 4 17 | 18 | progressbar.bar 19 | progressbar.base 20 | progressbar.multi 21 | progressbar.shortcuts 22 | progressbar.utils 23 | progressbar.widgets 24 | 25 | Module contents 26 | --------------- 27 | 28 | .. automodule:: progressbar 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | -------------------------------------------------------------------------------- /docs/progressbar.shortcuts.rst: -------------------------------------------------------------------------------- 1 | progressbar\.shortcuts module 2 | ============================= 3 | 4 | .. automodule:: progressbar.shortcuts 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.terminal.base.rst: -------------------------------------------------------------------------------- 1 | progressbar.terminal.base module 2 | ================================ 3 | 4 | .. automodule:: progressbar.terminal.base 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.terminal.colors.rst: -------------------------------------------------------------------------------- 1 | progressbar.terminal.colors module 2 | ================================== 3 | 4 | .. automodule:: progressbar.terminal.colors 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.terminal.rst: -------------------------------------------------------------------------------- 1 | progressbar.terminal package 2 | ============================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | progressbar.terminal.os_specific 11 | 12 | Submodules 13 | ---------- 14 | 15 | .. toctree:: 16 | :maxdepth: 4 17 | 18 | progressbar.terminal.base 19 | progressbar.terminal.colors 20 | progressbar.terminal.stream 21 | 22 | Module contents 23 | --------------- 24 | 25 | .. automodule:: progressbar.terminal 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | -------------------------------------------------------------------------------- /docs/progressbar.terminal.stream.rst: -------------------------------------------------------------------------------- 1 | progressbar.terminal.stream module 2 | ================================== 3 | 4 | .. automodule:: progressbar.terminal.stream 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.utils.rst: -------------------------------------------------------------------------------- 1 | progressbar.utils module 2 | ======================== 3 | 4 | .. automodule:: progressbar.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/progressbar.widgets.rst: -------------------------------------------------------------------------------- 1 | progressbar.widgets module 2 | ========================== 3 | 4 | .. automodule:: progressbar.widgets 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | -e.[docs,tests] 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Usage 3 | ======== 4 | 5 | There are many ways to use Python Progressbar, you can see a few basic examples 6 | here but there are many more in the :doc:`examples` file. 7 | 8 | Wrapping an iterable 9 | ------------------------------------------------------------------------------ 10 | :: 11 | 12 | import time 13 | import progressbar 14 | 15 | bar = progressbar.ProgressBar() 16 | for i in bar(range(100)): 17 | time.sleep(0.02) 18 | 19 | Context wrapper 20 | ------------------------------------------------------------------------------ 21 | :: 22 | 23 | import time 24 | import progressbar 25 | 26 | with progressbar.ProgressBar(max_value=10) as bar: 27 | for i in range(10): 28 | time.sleep(0.1) 29 | bar.update(i) 30 | 31 | Combining progressbars with print output 32 | ------------------------------------------------------------------------------ 33 | :: 34 | 35 | import time 36 | import progressbar 37 | 38 | bar = progressbar.ProgressBar(redirect_stdout=True) 39 | for i in range(100): 40 | print 'Some text', i 41 | time.sleep(0.1) 42 | bar.update(i) 43 | 44 | Progressbar with unknown length 45 | ------------------------------------------------------------------------------ 46 | :: 47 | 48 | import time 49 | import progressbar 50 | 51 | bar = progressbar.ProgressBar(max_value=progressbar.UnknownLength) 52 | for i in range(20): 53 | time.sleep(0.1) 54 | bar.update(i) 55 | 56 | Bar with custom widgets 57 | ------------------------------------------------------------------------------ 58 | :: 59 | 60 | import time 61 | import progressbar 62 | 63 | bar = progressbar.ProgressBar(widgets=[ 64 | ' [', progressbar.Timer(), '] ', 65 | progressbar.Bar(), 66 | ' (', progressbar.ETA(), ') ', 67 | ]) 68 | for i in bar(range(20)): 69 | time.sleep(0.1) 70 | 71 | -------------------------------------------------------------------------------- /progressbar/__about__.py: -------------------------------------------------------------------------------- 1 | """Text progress bar library for Python. 2 | 3 | A text progress bar is typically used to display the progress of a long 4 | running operation, providing a visual cue that processing is underway. 5 | 6 | The ProgressBar class manages the current progress, and the format of the line 7 | is given by a number of widgets. A widget is an object that may display 8 | differently depending on the state of the progress bar. 9 | 10 | The progressbar module is very easy to use, yet very powerful. It will also 11 | automatically enable features like auto-resizing when the system supports it. 12 | """ 13 | 14 | __title__ = 'Python Progressbar' 15 | __package_name__ = 'progressbar2' 16 | __author__ = 'Rick van Hattem (Wolph)' 17 | __description__: str = ' '.join( 18 | """ 19 | A Python Progressbar library to provide visual (yet text based) progress to 20 | long running operations. 21 | """.strip().split(), 22 | ) 23 | __email__ = 'wolph@wol.ph' 24 | __version__ = '4.5.0' 25 | __license__ = 'BSD' 26 | __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' 27 | __url__ = 'https://github.com/WoLpH/python-progressbar' 28 | -------------------------------------------------------------------------------- /progressbar/__init__.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from .__about__ import __author__, __version__ 4 | from .algorithms import ( 5 | DoubleExponentialMovingAverage, 6 | ExponentialMovingAverage, 7 | SmoothingAlgorithm, 8 | ) 9 | from .bar import DataTransferBar, NullBar, ProgressBar 10 | from .base import UnknownLength 11 | from .multi import MultiBar, SortKey 12 | from .shortcuts import progressbar 13 | from .terminal.stream import LineOffsetStreamWrapper 14 | from .utils import len_color, streams 15 | from .widgets import ( 16 | ETA, 17 | AbsoluteETA, 18 | AdaptiveETA, 19 | AdaptiveTransferSpeed, 20 | AnimatedMarker, 21 | Bar, 22 | BouncingBar, 23 | Counter, 24 | CurrentTime, 25 | DataSize, 26 | DynamicMessage, 27 | FileTransferSpeed, 28 | FormatCustomText, 29 | FormatLabel, 30 | FormatLabelBar, 31 | GranularBar, 32 | JobStatusBar, 33 | MultiProgressBar, 34 | MultiRangeBar, 35 | Percentage, 36 | PercentageLabelBar, 37 | ReverseBar, 38 | RotatingMarker, 39 | SimpleProgress, 40 | SmoothingETA, 41 | Timer, 42 | Variable, 43 | VariableMixin, 44 | ) 45 | 46 | __date__ = str(date.today()) 47 | __all__ = [ 48 | 'ETA', 49 | 'AbsoluteETA', 50 | 'AdaptiveETA', 51 | 'AdaptiveTransferSpeed', 52 | 'AnimatedMarker', 53 | 'Bar', 54 | 'BouncingBar', 55 | 'Counter', 56 | 'CurrentTime', 57 | 'DataSize', 58 | 'DataTransferBar', 59 | 'DoubleExponentialMovingAverage', 60 | 'DynamicMessage', 61 | 'ExponentialMovingAverage', 62 | 'FileTransferSpeed', 63 | 'FormatCustomText', 64 | 'FormatLabel', 65 | 'FormatLabelBar', 66 | 'GranularBar', 67 | 'JobStatusBar', 68 | 'LineOffsetStreamWrapper', 69 | 'MultiBar', 70 | 'MultiProgressBar', 71 | 'MultiRangeBar', 72 | 'NullBar', 73 | 'Percentage', 74 | 'PercentageLabelBar', 75 | 'ProgressBar', 76 | 'ReverseBar', 77 | 'RotatingMarker', 78 | 'SimpleProgress', 79 | 'SmoothingAlgorithm', 80 | 'SmoothingETA', 81 | 'SortKey', 82 | 'Timer', 83 | 'UnknownLength', 84 | 'Variable', 85 | 'VariableMixin', 86 | '__author__', 87 | '__version__', 88 | 'len_color', 89 | 'progressbar', 90 | 'streams', 91 | ] 92 | -------------------------------------------------------------------------------- /progressbar/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import contextlib 5 | import pathlib 6 | import sys 7 | import typing 8 | from pathlib import Path 9 | from typing import IO, BinaryIO, TextIO 10 | 11 | import progressbar 12 | 13 | 14 | def size_to_bytes(size_str: str) -> int: 15 | """ 16 | Convert a size string with suffixes 'k', 'm', etc., to bytes. 17 | 18 | Note: This function also supports '@' as a prefix to a file path to get the 19 | file size. 20 | 21 | >>> size_to_bytes('1024k') 22 | 1048576 23 | >>> size_to_bytes('1024m') 24 | 1073741824 25 | >>> size_to_bytes('1024g') 26 | 1099511627776 27 | >>> size_to_bytes('1024') 28 | 1024 29 | >>> size_to_bytes('1024p') 30 | 1125899906842624 31 | """ 32 | 33 | # Define conversion rates 34 | suffix_exponent = { 35 | 'k': 1, 36 | 'm': 2, 37 | 'g': 3, 38 | 't': 4, 39 | 'p': 5, 40 | } 41 | 42 | # Initialize the default exponent to 0 (for bytes) 43 | exponent = 0 44 | 45 | # Check if the size starts with '@' (for file sizes, not handled here) 46 | if size_str.startswith('@'): 47 | return pathlib.Path(size_str[1:]).stat().st_size 48 | 49 | # Check if the last character is a known suffix and adjust the multiplier 50 | if size_str[-1].lower() in suffix_exponent: 51 | # Update exponent based on the suffix 52 | exponent = suffix_exponent[size_str[-1].lower()] 53 | # Remove the suffix from the size_str 54 | size_str = size_str[:-1] 55 | 56 | # Convert the size_str to an integer and apply the exponent 57 | return int(size_str) * (1024**exponent) 58 | 59 | 60 | def create_argument_parser() -> argparse.ArgumentParser: 61 | """ 62 | Create the argument parser for the `progressbar` command. 63 | """ 64 | 65 | parser = argparse.ArgumentParser( 66 | description=""" 67 | Monitor the progress of data through a pipe. 68 | 69 | Note that this is a Python implementation of the original `pv` command 70 | that is functional but not yet feature complete. 71 | """ 72 | ) 73 | 74 | # Display switches 75 | parser.add_argument( 76 | '-p', 77 | '--progress', 78 | action='store_true', 79 | help='Turn the progress bar on.', 80 | ) 81 | parser.add_argument( 82 | '-t', '--timer', action='store_true', help='Turn the timer on.' 83 | ) 84 | parser.add_argument( 85 | '-e', '--eta', action='store_true', help='Turn the ETA timer on.' 86 | ) 87 | parser.add_argument( 88 | '-I', 89 | '--fineta', 90 | action='store_true', 91 | help='Display the ETA as local time of arrival.', 92 | ) 93 | parser.add_argument( 94 | '-r', '--rate', action='store_true', help='Turn the rate counter on.' 95 | ) 96 | parser.add_argument( 97 | '-a', 98 | '--average-rate', 99 | action='store_true', 100 | help='Turn the average rate counter on.', 101 | ) 102 | parser.add_argument( 103 | '-b', 104 | '--bytes', 105 | action='store_true', 106 | help='Turn the total byte counter on.', 107 | ) 108 | parser.add_argument( 109 | '-8', 110 | '--bits', 111 | action='store_true', 112 | help='Display total bits instead of bytes.', 113 | ) 114 | parser.add_argument( 115 | '-T', 116 | '--buffer-percent', 117 | action='store_true', 118 | help='Turn on the transfer buffer percentage display.', 119 | ) 120 | parser.add_argument( 121 | '-A', 122 | '--last-written', 123 | type=int, 124 | help='Show the last NUM bytes written.', 125 | ) 126 | parser.add_argument( 127 | '-F', 128 | '--format', 129 | type=str, 130 | help='Use the format string FORMAT for output format.', 131 | ) 132 | parser.add_argument( 133 | '-n', '--numeric', action='store_true', help='Numeric output.' 134 | ) 135 | parser.add_argument( 136 | '-q', 137 | '--quiet', 138 | action='store_true', 139 | help='No output.', 140 | ) 141 | 142 | # Output modifiers 143 | parser.add_argument( 144 | '-W', 145 | '--wait', 146 | action='store_true', 147 | help='Wait until the first byte has been transferred.', 148 | ) 149 | parser.add_argument('-D', '--delay-start', type=float, help='Delay start.') 150 | parser.add_argument( 151 | '-s', '--size', type=str, help='Assume total data size is SIZE.' 152 | ) 153 | parser.add_argument( 154 | '-l', 155 | '--line-mode', 156 | action='store_true', 157 | help='Count lines instead of bytes.', 158 | ) 159 | parser.add_argument( 160 | '-0', 161 | '--null', 162 | action='store_true', 163 | help='Count lines terminated with a zero byte.', 164 | ) 165 | parser.add_argument( 166 | '-i', '--interval', type=float, help='Interval between updates.' 167 | ) 168 | parser.add_argument( 169 | '-m', 170 | '--average-rate-window', 171 | type=int, 172 | help='Window for average rate calculation.', 173 | ) 174 | parser.add_argument( 175 | '-w', 176 | '--width', 177 | type=int, 178 | help='Assume terminal is WIDTH characters wide.', 179 | ) 180 | parser.add_argument( 181 | '-H', '--height', type=int, help='Assume terminal is HEIGHT rows high.' 182 | ) 183 | parser.add_argument( 184 | '-N', '--name', type=str, help='Prefix output information with NAME.' 185 | ) 186 | parser.add_argument( 187 | '-f', '--force', action='store_true', help='Force output.' 188 | ) 189 | parser.add_argument( 190 | '-c', 191 | '--cursor', 192 | action='store_true', 193 | help='Use cursor positioning escape sequences.', 194 | ) 195 | 196 | # Data transfer modifiers 197 | parser.add_argument( 198 | '-L', 199 | '--rate-limit', 200 | type=str, 201 | help='Limit transfer to RATE bytes per second.', 202 | ) 203 | parser.add_argument( 204 | '-B', 205 | '--buffer-size', 206 | type=str, 207 | help='Use transfer buffer size of BYTES.', 208 | ) 209 | parser.add_argument( 210 | '-C', '--no-splice', action='store_true', help='Never use splice.' 211 | ) 212 | parser.add_argument( 213 | '-E', '--skip-errors', action='store_true', help='Ignore read errors.' 214 | ) 215 | parser.add_argument( 216 | '-Z', 217 | '--error-skip-block', 218 | type=str, 219 | help='Skip block size when ignoring errors.', 220 | ) 221 | parser.add_argument( 222 | '-S', 223 | '--stop-at-size', 224 | action='store_true', 225 | help='Stop transferring after SIZE bytes.', 226 | ) 227 | parser.add_argument( 228 | '-Y', 229 | '--sync', 230 | action='store_true', 231 | help='Synchronise buffer caches to disk after writes.', 232 | ) 233 | parser.add_argument( 234 | '-K', 235 | '--direct-io', 236 | action='store_true', 237 | help='Set O_DIRECT flag on all inputs/outputs.', 238 | ) 239 | parser.add_argument( 240 | '-X', 241 | '--discard', 242 | action='store_true', 243 | help='Discard input data instead of transferring it.', 244 | ) 245 | parser.add_argument( 246 | '-d', '--watchfd', type=str, help='Watch file descriptor of process.' 247 | ) 248 | parser.add_argument( 249 | '-R', 250 | '--remote', 251 | type=int, 252 | help='Remote control another running instance of pv.', 253 | ) 254 | 255 | # General options 256 | parser.add_argument( 257 | '-P', '--pidfile', type=pathlib.Path, help='Save process ID in FILE.' 258 | ) 259 | parser.add_argument( 260 | 'input', 261 | help='Input file path. Uses stdin if not specified.', 262 | default='-', 263 | nargs='*', 264 | ) 265 | parser.add_argument( 266 | '-o', 267 | '--output', 268 | default='-', 269 | help='Output file path. Uses stdout if not specified.', 270 | ) 271 | 272 | return parser 273 | 274 | 275 | def main(argv: list[str] | None = None) -> None: # noqa: C901 276 | """ 277 | Main function for the `progressbar` command. 278 | 279 | Args: 280 | argv (list[str] | None): Command-line arguments passed to the script. 281 | 282 | Returns: 283 | None 284 | """ 285 | parser: argparse.ArgumentParser = create_argument_parser() 286 | args: argparse.Namespace = parser.parse_args(argv) 287 | 288 | with contextlib.ExitStack() as stack: 289 | output_stream: typing.IO[typing.Any] = _get_output_stream( 290 | args.output, args.line_mode, stack 291 | ) 292 | 293 | input_paths: list[BinaryIO | TextIO | Path | IO[typing.Any]] = [] 294 | total_size: int = 0 295 | filesize_available: bool = True 296 | for filename in args.input: 297 | input_path: typing.IO[typing.Any] | pathlib.Path 298 | if filename == '-': 299 | if args.line_mode: 300 | input_path = sys.stdin 301 | else: 302 | input_path = sys.stdin.buffer 303 | 304 | filesize_available = False 305 | else: 306 | input_path = pathlib.Path(filename) 307 | if not input_path.exists(): 308 | parser.error(f'File not found: {filename}') 309 | 310 | if not args.size: 311 | total_size += input_path.stat().st_size 312 | 313 | input_paths.append(input_path) 314 | 315 | # Determine the size for the progress bar (if provided) 316 | if args.size: 317 | total_size = size_to_bytes(args.size) 318 | filesize_available = True 319 | 320 | if filesize_available: 321 | # Create the progress bar components 322 | widgets = [ 323 | progressbar.Percentage(), 324 | ' ', 325 | progressbar.Bar(), 326 | ' ', 327 | progressbar.Timer(), 328 | ' ', 329 | progressbar.FileTransferSpeed(), 330 | ] 331 | else: 332 | widgets = [ 333 | progressbar.SimpleProgress(), 334 | ' ', 335 | progressbar.DataSize(), 336 | ' ', 337 | progressbar.Timer(), 338 | ] 339 | 340 | if args.eta: 341 | widgets.append(' ') 342 | widgets.append(progressbar.AdaptiveETA()) 343 | 344 | # Initialize the progress bar 345 | bar = progressbar.ProgressBar( 346 | # widgets=widgets, 347 | max_value=total_size or None, 348 | max_error=False, 349 | ) 350 | 351 | # Data processing and updating the progress bar 352 | buffer_size = ( 353 | size_to_bytes(args.buffer_size) if args.buffer_size else 1024 354 | ) 355 | total_transferred = 0 356 | 357 | bar.start() 358 | with contextlib.suppress(KeyboardInterrupt): 359 | for input_path in input_paths: 360 | if isinstance(input_path, pathlib.Path): 361 | input_stream = stack.enter_context( 362 | input_path.open('r' if args.line_mode else 'rb') 363 | ) 364 | else: 365 | input_stream = input_path 366 | 367 | while True: 368 | data: str | bytes 369 | if args.line_mode: 370 | data = input_stream.readline(buffer_size) 371 | else: 372 | data = input_stream.read(buffer_size) 373 | 374 | if not data: 375 | break 376 | 377 | output_stream.write(data) 378 | total_transferred += len(data) 379 | bar.update(total_transferred) 380 | 381 | bar.finish(dirty=True) 382 | 383 | 384 | def _get_output_stream( 385 | output: str | None, 386 | line_mode: bool, 387 | stack: contextlib.ExitStack, 388 | ) -> typing.IO[typing.Any]: 389 | if output and output != '-': 390 | mode = 'w' if line_mode else 'wb' 391 | return stack.enter_context(open(output, mode)) # noqa: SIM115 392 | elif line_mode: 393 | return sys.stdout 394 | else: 395 | return sys.stdout.buffer 396 | 397 | 398 | if __name__ == '__main__': 399 | main() 400 | -------------------------------------------------------------------------------- /progressbar/algorithms.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import abc 4 | import typing 5 | from datetime import timedelta 6 | 7 | 8 | class SmoothingAlgorithm(abc.ABC): 9 | @abc.abstractmethod 10 | def __init__(self, **kwargs: typing.Any): 11 | raise NotImplementedError 12 | 13 | @abc.abstractmethod 14 | def update(self, new_value: float, elapsed: timedelta) -> float: 15 | """Updates the algorithm with a new value and returns the smoothed 16 | value. 17 | """ 18 | raise NotImplementedError 19 | 20 | 21 | class ExponentialMovingAverage(SmoothingAlgorithm): 22 | """ 23 | The Exponential Moving Average (EMA) is an exponentially weighted moving 24 | average that reduces the lag that's typically associated with a simple 25 | moving average. It's more responsive to recent changes in data. 26 | """ 27 | 28 | def __init__(self, alpha: float = 0.5) -> None: 29 | self.alpha = alpha 30 | self.value = 0 31 | 32 | def update(self, new_value: float, elapsed: timedelta) -> float: 33 | self.value = self.alpha * new_value + (1 - self.alpha) * self.value 34 | return self.value 35 | 36 | 37 | class DoubleExponentialMovingAverage(SmoothingAlgorithm): 38 | """ 39 | The Double Exponential Moving Average (DEMA) is essentially an EMA of an 40 | EMA, which reduces the lag that's typically associated with a simple EMA. 41 | It's more responsive to recent changes in data. 42 | """ 43 | 44 | def __init__(self, alpha: float = 0.5) -> None: 45 | self.alpha = alpha 46 | self.ema1 = 0 47 | self.ema2 = 0 48 | 49 | def update(self, new_value: float, elapsed: timedelta) -> float: 50 | self.ema1 = self.alpha * new_value + (1 - self.alpha) * self.ema1 51 | self.ema2 = self.alpha * self.ema1 + (1 - self.alpha) * self.ema2 52 | return 2 * self.ema1 - self.ema2 53 | -------------------------------------------------------------------------------- /progressbar/base.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | from typing import IO, TextIO 5 | 6 | 7 | class FalseMeta(type): 8 | @classmethod 9 | def __bool__(cls) -> bool: # pragma: no cover 10 | return False 11 | 12 | @classmethod 13 | def __cmp__(cls, other: typing.Any) -> int: # pragma: no cover 14 | return -1 15 | 16 | __nonzero__ = __bool__ 17 | 18 | 19 | class UnknownLength(metaclass=FalseMeta): 20 | pass 21 | 22 | 23 | class Undefined(metaclass=FalseMeta): 24 | pass 25 | 26 | 27 | assert IO is not None 28 | assert TextIO is not None 29 | 30 | __all__ = ( 31 | 'IO', 32 | 'FalseMeta', 33 | 'TextIO', 34 | 'Undefined', 35 | 'UnknownLength', 36 | ) 37 | -------------------------------------------------------------------------------- /progressbar/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import contextlib 4 | import enum 5 | import os 6 | import re 7 | import typing 8 | 9 | 10 | @typing.overload 11 | def env_flag(name: str, default: bool) -> bool: ... 12 | 13 | 14 | @typing.overload 15 | def env_flag(name: str, default: bool | None = None) -> bool | None: ... 16 | 17 | 18 | def env_flag(name: str, default: bool | None = None) -> bool | None: 19 | """ 20 | Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, 21 | on/off, and returns it as a boolean. 22 | 23 | If the environment variable is not defined, or has an unknown value, 24 | returns `default` 25 | """ 26 | v = os.getenv(name) 27 | if v and v.lower() in ('y', 'yes', 't', 'true', 'on', '1'): 28 | return True 29 | if v and v.lower() in ('n', 'no', 'f', 'false', 'off', '0'): 30 | return False 31 | return default 32 | 33 | 34 | class ColorSupport(enum.IntEnum): 35 | """Color support for the terminal.""" 36 | 37 | NONE = 0 38 | XTERM = 16 39 | XTERM_256 = 256 40 | XTERM_TRUECOLOR = 16777216 41 | WINDOWS = 8 42 | 43 | @classmethod 44 | def from_env(cls) -> ColorSupport: 45 | """Get the color support from the environment. 46 | 47 | If any of the environment variables contain `24bit` or `truecolor`, 48 | we will enable true color/24 bit support. If they contain `256`, we 49 | will enable 256 color/8 bit support. If they contain `xterm`, we will 50 | enable 16 color support. Otherwise, we will assume no color support. 51 | 52 | If `JUPYTER_COLUMNS` or `JUPYTER_LINES` or `JPY_PARENT_PID` is set, we 53 | will assume true color support. 54 | 55 | Note that the highest available value will be used! Having 56 | `COLORTERM=truecolor` will override `TERM=xterm-256color`. 57 | """ 58 | variables = ( 59 | 'FORCE_COLOR', 60 | 'PROGRESSBAR_ENABLE_COLORS', 61 | 'COLORTERM', 62 | 'TERM', 63 | ) 64 | 65 | if JUPYTER: 66 | # Jupyter notebook always supports true color. 67 | return cls.XTERM_TRUECOLOR 68 | elif os.name == 'nt': 69 | # We can't reliably detect true color support on Windows, so we 70 | # will assume it is supported if the console is configured to 71 | # support it. 72 | from .terminal.os_specific import windows 73 | 74 | if ( 75 | windows.get_console_mode() 76 | & windows.WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT 77 | ): 78 | return cls.XTERM_TRUECOLOR 79 | else: 80 | return cls.WINDOWS # pragma: no cover 81 | 82 | support = cls.NONE 83 | for variable in variables: 84 | value = os.environ.get(variable) 85 | if value is None: 86 | continue 87 | elif value in {'truecolor', '24bit'}: 88 | # Truecolor support, we don't need to check anything else. 89 | support = cls.XTERM_TRUECOLOR 90 | break 91 | elif '256' in value: 92 | support = max(cls.XTERM_256, support) 93 | elif value == 'xterm': 94 | support = max(cls.XTERM, support) 95 | 96 | return support 97 | 98 | 99 | def is_ansi_terminal( 100 | fd: typing.IO[typing.Any], 101 | is_terminal: bool | None = None, 102 | ) -> bool | None: # pragma: no cover 103 | if is_terminal is None: 104 | # Jupyter Notebooks support progress bars 105 | if JUPYTER: 106 | is_terminal = True 107 | # This works for newer versions of pycharm only. With older versions 108 | # there is no way to check. 109 | elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get( 110 | 'PYTEST_CURRENT_TEST' 111 | ): 112 | is_terminal = True 113 | 114 | if is_terminal is None: 115 | # check if we are writing to a terminal or not. typically a file object 116 | # is going to return False if the instance has been overridden and 117 | # isatty has not been defined we have no way of knowing so we will not 118 | # use ansi. ansi terminals will typically define one of the 2 119 | # environment variables. 120 | with contextlib.suppress(Exception): 121 | is_tty: bool = fd.isatty() 122 | # Try and match any of the huge amount of Linux/Unix ANSI consoles 123 | if is_tty and ANSI_TERM_RE.match(os.environ.get('TERM', '')): 124 | is_terminal = True 125 | # ANSICON is a Windows ANSI compatible console 126 | elif 'ANSICON' in os.environ: 127 | is_terminal = True 128 | elif os.name == 'nt': 129 | from .terminal.os_specific import windows 130 | 131 | return bool( 132 | windows.get_console_mode() 133 | & windows.WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT, 134 | ) 135 | else: 136 | is_terminal = None 137 | 138 | return is_terminal 139 | 140 | 141 | def is_terminal( 142 | fd: typing.IO[typing.Any], 143 | is_terminal: bool | None = None, 144 | ) -> bool | None: 145 | if is_terminal is None: 146 | # Full ansi support encompasses what we expect from a terminal 147 | is_terminal = is_ansi_terminal(fd) or None 148 | 149 | if is_terminal is None: 150 | # Allow a environment variable override 151 | is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None) 152 | 153 | if is_terminal is None: # pragma: no cover 154 | # Bare except because a lot can go wrong on different systems. If we do 155 | # get a TTY we know this is a valid terminal 156 | try: 157 | is_terminal = fd.isatty() 158 | except Exception: 159 | is_terminal = False 160 | 161 | return is_terminal 162 | 163 | 164 | # Enable Windows full color mode if possible 165 | if os.name == 'nt': 166 | pass 167 | 168 | # os_specific.set_console_mode() 169 | 170 | JUPYTER = bool( 171 | os.environ.get('JUPYTER_COLUMNS') 172 | or os.environ.get('JUPYTER_LINES') 173 | or os.environ.get('JPY_PARENT_PID') 174 | ) 175 | COLOR_SUPPORT = ColorSupport.from_env() 176 | ANSI_TERMS = ( 177 | '([xe]|bv)term', 178 | '(sco)?ansi', 179 | 'cygwin', 180 | 'konsole', 181 | 'linux', 182 | 'rxvt', 183 | 'screen', 184 | 'tmux', 185 | 'vt(10[02]|220|320)', 186 | ) 187 | ANSI_TERM_RE: re.Pattern[str] = re.compile( 188 | f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE 189 | ) 190 | -------------------------------------------------------------------------------- /progressbar/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/python-progressbar/ef3c0455107a0fb2bdbaaf8a1bbf8840488f00ec/progressbar/py.typed -------------------------------------------------------------------------------- /progressbar/shortcuts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | from . import ( 6 | bar, 7 | widgets as widgets_module, 8 | ) 9 | 10 | T = typing.TypeVar('T') 11 | 12 | 13 | def progressbar( 14 | iterator: typing.Iterator[T], 15 | min_value: bar.NumberT = 0, 16 | max_value: bar.ValueT = None, 17 | widgets: typing.Sequence[widgets_module.WidgetBase | str] | None = None, 18 | prefix: str | None = None, 19 | suffix: str | None = None, 20 | **kwargs: typing.Any, 21 | ) -> typing.Generator[T, None, None]: 22 | progressbar_ = bar.ProgressBar( 23 | min_value=min_value, 24 | max_value=max_value, 25 | widgets=widgets, 26 | prefix=prefix, 27 | suffix=suffix, 28 | **kwargs, 29 | ) 30 | yield from progressbar_(iterator) 31 | -------------------------------------------------------------------------------- /progressbar/terminal/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import * # noqa F403 4 | from .stream import * # noqa F403 5 | -------------------------------------------------------------------------------- /progressbar/terminal/os_specific/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | if os.name == 'nt': 4 | from .windows import ( 5 | get_console_mode as _get_console_mode, 6 | getch as _getch, 7 | reset_console_mode as _reset_console_mode, 8 | set_console_mode as _set_console_mode, 9 | ) 10 | 11 | else: 12 | from .posix import getch as _getch 13 | 14 | def _reset_console_mode() -> None: 15 | pass 16 | 17 | def _set_console_mode() -> bool: 18 | return False 19 | 20 | def _get_console_mode() -> int: 21 | return 0 22 | 23 | 24 | getch = _getch 25 | reset_console_mode = _reset_console_mode 26 | set_console_mode = _set_console_mode 27 | get_console_mode = _get_console_mode 28 | -------------------------------------------------------------------------------- /progressbar/terminal/os_specific/posix.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import termios 3 | import tty 4 | 5 | 6 | def getch() -> str: 7 | fd = sys.stdin.fileno() 8 | old_settings = termios.tcgetattr(fd) # type: ignore 9 | try: 10 | tty.setraw(sys.stdin.fileno()) # type: ignore 11 | ch = sys.stdin.read(1) 12 | finally: 13 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # type: ignore 14 | 15 | return ch 16 | -------------------------------------------------------------------------------- /progressbar/terminal/os_specific/windows.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: N801 2 | """ 3 | Windows specific code for the terminal. 4 | 5 | Note that the naming convention here is non-pythonic because we are 6 | matching the Windows API naming. 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | import ctypes 12 | import enum 13 | from ctypes.wintypes import ( 14 | BOOL as _BOOL, 15 | CHAR as _CHAR, 16 | DWORD as _DWORD, 17 | HANDLE as _HANDLE, 18 | SHORT as _SHORT, 19 | UINT as _UINT, 20 | WCHAR as _WCHAR, 21 | WORD as _WORD, 22 | ) 23 | 24 | _kernel32 = ctypes.windll.Kernel32 # type: ignore 25 | 26 | _STD_INPUT_HANDLE = _DWORD(-10) 27 | _STD_OUTPUT_HANDLE = _DWORD(-11) 28 | 29 | 30 | class WindowsConsoleModeFlags(enum.IntFlag): 31 | ENABLE_ECHO_INPUT = 0x0004 32 | ENABLE_EXTENDED_FLAGS = 0x0080 33 | ENABLE_INSERT_MODE = 0x0020 34 | ENABLE_LINE_INPUT = 0x0002 35 | ENABLE_MOUSE_INPUT = 0x0010 36 | ENABLE_PROCESSED_INPUT = 0x0001 37 | ENABLE_QUICK_EDIT_MODE = 0x0040 38 | ENABLE_WINDOW_INPUT = 0x0008 39 | ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 40 | 41 | ENABLE_PROCESSED_OUTPUT = 0x0001 42 | ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 43 | ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 44 | DISABLE_NEWLINE_AUTO_RETURN = 0x0008 45 | ENABLE_LVB_GRID_WORLDWIDE = 0x0010 46 | 47 | def __str__(self) -> str: 48 | return f'{self.name} (0x{self.value:04X})' 49 | 50 | 51 | _GetConsoleMode = _kernel32.GetConsoleMode 52 | _GetConsoleMode.restype = _BOOL 53 | 54 | _SetConsoleMode = _kernel32.SetConsoleMode 55 | _SetConsoleMode.restype = _BOOL 56 | 57 | _GetStdHandle = _kernel32.GetStdHandle 58 | _GetStdHandle.restype = _HANDLE 59 | 60 | _ReadConsoleInput = _kernel32.ReadConsoleInputA 61 | _ReadConsoleInput.restype = _BOOL 62 | 63 | _h_console_input = _GetStdHandle(_STD_INPUT_HANDLE) 64 | _input_mode = _DWORD() 65 | _GetConsoleMode(_HANDLE(_h_console_input), ctypes.byref(_input_mode)) 66 | 67 | _h_console_output = _GetStdHandle(_STD_OUTPUT_HANDLE) 68 | _output_mode = _DWORD() 69 | _GetConsoleMode(_HANDLE(_h_console_output), ctypes.byref(_output_mode)) 70 | 71 | 72 | class _COORD(ctypes.Structure): 73 | _fields_ = (('X', _SHORT), ('Y', _SHORT)) 74 | 75 | 76 | class _FOCUS_EVENT_RECORD(ctypes.Structure): 77 | _fields_ = (('bSetFocus', _BOOL),) 78 | 79 | 80 | class _KEY_EVENT_RECORD(ctypes.Structure): 81 | class _uchar(ctypes.Union): 82 | _fields_ = (('UnicodeChar', _WCHAR), ('AsciiChar', _CHAR)) 83 | 84 | _fields_ = ( 85 | ('bKeyDown', _BOOL), 86 | ('wRepeatCount', _WORD), 87 | ('wVirtualKeyCode', _WORD), 88 | ('wVirtualScanCode', _WORD), 89 | ('uChar', _uchar), 90 | ('dwControlKeyState', _DWORD), 91 | ) 92 | 93 | 94 | class _MENU_EVENT_RECORD(ctypes.Structure): 95 | _fields_ = (('dwCommandId', _UINT),) 96 | 97 | 98 | class _MOUSE_EVENT_RECORD(ctypes.Structure): 99 | _fields_ = ( 100 | ('dwMousePosition', _COORD), 101 | ('dwButtonState', _DWORD), 102 | ('dwControlKeyState', _DWORD), 103 | ('dwEventFlags', _DWORD), 104 | ) 105 | 106 | 107 | class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): 108 | _fields_ = (('dwSize', _COORD),) 109 | 110 | 111 | class _INPUT_RECORD(ctypes.Structure): 112 | class _Event(ctypes.Union): 113 | _fields_ = ( 114 | ('KeyEvent', _KEY_EVENT_RECORD), 115 | ('MouseEvent', _MOUSE_EVENT_RECORD), 116 | ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), 117 | ('MenuEvent', _MENU_EVENT_RECORD), 118 | ('FocusEvent', _FOCUS_EVENT_RECORD), 119 | ) 120 | 121 | _fields_ = (('EventType', _WORD), ('Event', _Event)) 122 | 123 | 124 | def reset_console_mode() -> None: 125 | _SetConsoleMode(_HANDLE(_h_console_input), _DWORD(_input_mode.value)) 126 | _SetConsoleMode(_HANDLE(_h_console_output), _DWORD(_output_mode.value)) 127 | 128 | 129 | def set_console_mode() -> bool: 130 | mode = ( 131 | _input_mode.value 132 | | WindowsConsoleModeFlags.ENABLE_VIRTUAL_TERMINAL_INPUT 133 | ) 134 | _SetConsoleMode(_HANDLE(_h_console_input), _DWORD(mode)) 135 | 136 | mode = ( 137 | _output_mode.value 138 | | WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT 139 | | WindowsConsoleModeFlags.ENABLE_VIRTUAL_TERMINAL_PROCESSING 140 | ) 141 | return bool(_SetConsoleMode(_HANDLE(_h_console_output), _DWORD(mode))) 142 | 143 | 144 | def get_console_mode() -> int: 145 | return _input_mode.value 146 | 147 | 148 | def set_text_color(color) -> None: 149 | _kernel32.SetConsoleTextAttribute(_h_console_output, color) 150 | 151 | 152 | def print_color(text, color) -> None: 153 | set_text_color(color) 154 | print(text) # noqa: T201 155 | set_text_color(7) # Reset to default color, grey 156 | 157 | 158 | def getch(): 159 | lp_buffer = (_INPUT_RECORD * 2)() 160 | n_length = _DWORD(2) 161 | lp_number_of_events_read = _DWORD() 162 | 163 | _ReadConsoleInput( 164 | _HANDLE(_h_console_input), 165 | lp_buffer, 166 | n_length, 167 | ctypes.byref(lp_number_of_events_read), 168 | ) 169 | 170 | char = lp_buffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') 171 | if char == '\x00': 172 | return None 173 | 174 | return char 175 | -------------------------------------------------------------------------------- /progressbar/terminal/stream.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | import typing 5 | from collections.abc import Iterable, Iterator 6 | from types import TracebackType 7 | 8 | from progressbar import base 9 | 10 | 11 | class TextIOOutputWrapper(base.TextIO): # pragma: no cover 12 | def __init__(self, stream: base.TextIO) -> None: 13 | self.stream = stream 14 | 15 | def close(self) -> None: 16 | self.stream.close() 17 | 18 | def fileno(self) -> int: 19 | return self.stream.fileno() 20 | 21 | def flush(self) -> None: 22 | pass 23 | 24 | def isatty(self) -> bool: 25 | return self.stream.isatty() 26 | 27 | def read(self, __n: int = -1) -> str: 28 | return self.stream.read(__n) 29 | 30 | def readable(self) -> bool: 31 | return self.stream.readable() 32 | 33 | def readline(self, __limit: int = -1) -> str: 34 | return self.stream.readline(__limit) 35 | 36 | def readlines(self, __hint: int = -1) -> list[str]: 37 | return self.stream.readlines(__hint) 38 | 39 | def seek(self, __offset: int, __whence: int = 0) -> int: 40 | return self.stream.seek(__offset, __whence) 41 | 42 | def seekable(self) -> bool: 43 | return self.stream.seekable() 44 | 45 | def tell(self) -> int: 46 | return self.stream.tell() 47 | 48 | def truncate(self, __size: int | None = None) -> int: 49 | return self.stream.truncate(__size) 50 | 51 | def writable(self) -> bool: 52 | return self.stream.writable() 53 | 54 | def writelines(self, __lines: Iterable[str]) -> None: 55 | return self.stream.writelines(__lines) 56 | 57 | def __next__(self) -> str: 58 | return self.stream.__next__() 59 | 60 | def __iter__(self) -> Iterator[str]: 61 | return self.stream.__iter__() 62 | 63 | def __exit__( 64 | self, 65 | __t: type[BaseException] | None, 66 | __value: BaseException | None, 67 | __traceback: TracebackType | None, 68 | ) -> None: 69 | return self.stream.__exit__(__t, __value, __traceback) 70 | 71 | def __enter__(self) -> base.TextIO: 72 | return self.stream.__enter__() 73 | 74 | 75 | class LineOffsetStreamWrapper(TextIOOutputWrapper): 76 | UP = '\033[F' 77 | DOWN = '\033[B' 78 | 79 | def __init__( 80 | self, lines: int = 0, stream: typing.TextIO = sys.stderr 81 | ) -> None: 82 | self.lines = lines 83 | super().__init__(stream) 84 | 85 | def write(self, data: str) -> int: 86 | data = data.rstrip('\n') 87 | # Move the cursor up 88 | self.stream.write(self.UP * self.lines) 89 | # Print a carriage return to reset the cursor position 90 | self.stream.write('\r') 91 | # Print the data without newlines so we don't change the position 92 | self.stream.write(data) 93 | # Move the cursor down 94 | self.stream.write(self.DOWN * self.lines) 95 | 96 | self.flush() 97 | return len(data) 98 | 99 | 100 | class LastLineStream(TextIOOutputWrapper): 101 | line: str = '' 102 | 103 | def seekable(self) -> bool: 104 | return False 105 | 106 | def readable(self) -> bool: 107 | return True 108 | 109 | def read(self, __n: int = -1) -> str: 110 | if __n < 0: 111 | return self.line 112 | else: 113 | return self.line[:__n] 114 | 115 | def readline(self, __limit: int = -1) -> str: 116 | if __limit < 0: 117 | return self.line 118 | else: 119 | return self.line[:__limit] 120 | 121 | def write(self, data: str) -> int: 122 | self.line = data 123 | return len(data) 124 | 125 | def truncate(self, __size: int | None = None) -> int: 126 | if __size is None: 127 | self.line = '' 128 | else: 129 | self.line = self.line[:__size] 130 | 131 | return len(self.line) 132 | 133 | def __iter__(self) -> typing.Generator[str, typing.Any, typing.Any]: 134 | yield self.line 135 | 136 | def writelines(self, __lines: Iterable[str]) -> None: 137 | line = '' 138 | # Walk through the lines and take the last one 139 | for line in __lines: # noqa: B007 140 | pass 141 | 142 | self.line = line 143 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = 'setuptools.build_meta' 3 | requires = ['setuptools', 'setuptools-scm'] 4 | 5 | [project] 6 | name = 'progressbar2' 7 | description = 'A Python Progressbar library to provide visual (yet text based) progress to long running operations.' 8 | dynamic = ['version'] 9 | authors = [{ name = 'Rick van Hattem (Wolph)', email = 'wolph@wol.ph' }] 10 | license = { text = 'BSD-3-Clause' } 11 | readme = 'README.rst' 12 | keywords = [ 13 | 'REPL', 14 | 'animated', 15 | 'bar', 16 | 'color', 17 | 'console', 18 | 'duration', 19 | 'efficient', 20 | 'elapsed', 21 | 'eta', 22 | 'feedback', 23 | 'live', 24 | 'meter', 25 | 'monitor', 26 | 'monitoring', 27 | 'multi-threaded', 28 | 'progress', 29 | 'progress-bar', 30 | 'progressbar', 31 | 'progressmeter', 32 | 'python', 33 | 'rate', 34 | 'simple', 35 | 'speed', 36 | 'spinner', 37 | 'stats', 38 | 'terminal', 39 | 'throughput', 40 | 'time', 41 | 'visual', 42 | ] 43 | classifiers = [ 44 | 'Development Status :: 5 - Production/Stable', 45 | 'Development Status :: 6 - Mature', 46 | 'Environment :: Console', 47 | 'Environment :: MacOS X', 48 | 'Environment :: Other Environment', 49 | 'Environment :: Win32 (MS Windows)', 50 | 'Environment :: X11 Applications', 51 | 'Framework :: IPython', 52 | 'Framework :: Jupyter', 53 | 'Intended Audience :: Developers', 54 | 'Intended Audience :: Education', 55 | 'Intended Audience :: End Users/Desktop', 56 | 'Intended Audience :: Other Audience', 57 | 'Intended Audience :: System Administrators', 58 | 'License :: OSI Approved :: BSD License', 59 | 'Natural Language :: English', 60 | 'Operating System :: MacOS :: MacOS X', 61 | 'Operating System :: MacOS', 62 | 'Operating System :: Microsoft :: MS-DOS', 63 | 'Operating System :: Microsoft :: Windows', 64 | 'Operating System :: Microsoft', 65 | 'Operating System :: POSIX :: BSD :: FreeBSD', 66 | 'Operating System :: POSIX :: BSD', 67 | 'Operating System :: POSIX :: Linux', 68 | 'Operating System :: POSIX :: SunOS/Solaris', 69 | 'Operating System :: POSIX', 70 | 'Operating System :: Unix', 71 | 'Programming Language :: Python :: 3 :: Only', 72 | 'Programming Language :: Python :: 3', 73 | 'Programming Language :: Python :: 3.8', 74 | 'Programming Language :: Python :: 3.9', 75 | 'Programming Language :: Python :: 3.10', 76 | 'Programming Language :: Python :: 3.11', 77 | 'Programming Language :: Python :: 3.12', 78 | 'Programming Language :: Python :: 3.13', 79 | 'Programming Language :: Python :: Implementation :: IronPython', 80 | 'Programming Language :: Python :: Implementation :: PyPy', 81 | 'Programming Language :: Python :: Implementation', 82 | 'Programming Language :: Python', 83 | 'Programming Language :: Unix Shell', 84 | 'Topic :: Desktop Environment', 85 | 'Topic :: Education :: Computer Aided Instruction (CAI)', 86 | 'Topic :: Education :: Testing', 87 | 'Topic :: Office/Business', 88 | 'Topic :: Other/Nonlisted Topic', 89 | 'Topic :: Software Development :: Build Tools', 90 | 'Topic :: Software Development :: Libraries', 91 | 'Topic :: Software Development :: Libraries :: Python Modules', 92 | 'Topic :: Software Development :: Pre-processors', 93 | 'Topic :: Software Development :: User Interfaces', 94 | 'Topic :: System :: Installation/Setup', 95 | 'Topic :: System :: Logging', 96 | 'Topic :: System :: Monitoring', 97 | 'Topic :: System :: Shells', 98 | 'Topic :: Terminals', 99 | 'Topic :: Utilities', 100 | 'Typing :: Typed', 101 | ] 102 | 103 | requires-python = '>3.8' 104 | dependencies = ['python-utils >= 3.8.1'] 105 | 106 | [project.urls] 107 | bugs = 'https://github.com/wolph/python-progressbar/issues' 108 | documentation = 'https://progressbar-2.readthedocs.io/en/latest/' 109 | repository = 'https://github.com/wolph/python-progressbar/' 110 | 111 | [project.scripts] 112 | progressbar = 'progressbar.__main__:main' 113 | 114 | [project.optional-dependencies] 115 | docs = [ 116 | 'sphinx>=1.8.5', 117 | 'sphinx-autodoc-typehints>=1.6.0', 118 | ] 119 | tests = [ 120 | 'dill>=0.3.6', 121 | 'flake8>=3.7.7', 122 | 'freezegun>=0.3.11', 123 | 'pytest-cov>=2.6.1', 124 | 'pytest-mypy', 125 | 'pytest>=4.6.9', 126 | 'sphinx>=1.8.5', 127 | 'pywin32; sys_platform == "win32"', 128 | ] 129 | 130 | [dependency-groups] 131 | dev = ['progressbar2[docs,tests]'] 132 | 133 | [tool.black] 134 | line-length = 79 135 | skip-string-normalization = true 136 | 137 | [tool.codespell] 138 | skip = '*/htmlcov,./docs/_build,*.asc' 139 | ignore-words-list = 'datas,numbert' 140 | 141 | [tool.coverage.run] 142 | branch = true 143 | source = ['progressbar', 'tests'] 144 | omit = [ 145 | '*/mock/*', 146 | '*/nose/*', 147 | '.tox/*', 148 | '*/os_specific/*', 149 | ] 150 | 151 | [tool.coverage.paths] 152 | source = ['progressbar'] 153 | 154 | [tool.coverage.report] 155 | fail_under = 100 156 | exclude_lines = [ 157 | 'pragma: no cover', 158 | '@abc.abstractmethod', 159 | 'def __repr__', 160 | 'if self.debug:', 161 | 'if settings.DEBUG', 162 | 'raise AssertionError', 163 | 'raise NotImplementedError', 164 | 'if 0:', 165 | 'if __name__ == .__main__.:', 166 | 'if types.TYPE_CHECKING:', 167 | '@typing.overload', 168 | 'if os.name == .nt.:', 169 | 'typing.Protocol', 170 | ] 171 | 172 | 173 | [tool.mypy] 174 | packages = ['progressbar', 'tests'] 175 | exclude = [ 176 | '^docs$', 177 | '^tests/original_examples.py$', 178 | '^examples.py$', 179 | ] 180 | 181 | 182 | [tool.pyright] 183 | include = ['progressbar'] 184 | exclude = ['examples', '.tox'] 185 | ignore = ['docs'] 186 | strict = [ 187 | 'progressbar/algorithms.py', 188 | 'progressbar/env.py', 189 | # 'progressbar/shortcuts.py', 190 | 'progressbar/multi.py', 191 | 'progressbar/__init__.py', 192 | 'progressbar/terminal/__init__.py', 193 | 'progressbar/terminal/stream.py', 194 | 'progressbar/terminal/os_specific/__init__.py', 195 | # 'progressbar/terminal/os_specific/posix.py', 196 | # 'progressbar/terminal/os_specific/windows.py', 197 | 'progressbar/terminal/base.py', 198 | 'progressbar/terminal/colors.py', 199 | # 'progressbar/widgets.py', 200 | # 'progressbar/utils.py', 201 | 'progressbar/__about__.py', 202 | # 'progressbar/bar.py', 203 | 'progressbar/__main__.py', 204 | 'progressbar/base.py', 205 | ] 206 | 207 | reportIncompatibleMethodOverride = false 208 | reportUnnecessaryIsInstance = false 209 | reportUnnecessaryCast = false 210 | reportUnnecessaryTypeAssertion = false 211 | reportUnnecessaryComparison = false 212 | reportUnnecessaryContains = false 213 | 214 | 215 | [tool.setuptools] 216 | include-package-data = true 217 | 218 | [tool.setuptools.dynamic] 219 | version = { attr = 'progressbar.__about__.__version__' } 220 | 221 | [tool.setuptools.packages.find] 222 | exclude = ['docs*', 'tests*'] 223 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = 3 | progressbar/*.py 4 | tests/*.py 5 | 6 | addopts = 7 | --cov progressbar 8 | --cov-report=html 9 | --cov-report=term-missing 10 | --cov-report=xml 11 | --cov-config=./pyproject.toml 12 | --no-cov-on-fail 13 | --doctest-modules 14 | 15 | norecursedirs = 16 | .* 17 | _* 18 | build 19 | dist 20 | docs 21 | progressbar/terminal/os_specific 22 | tmp* 23 | 24 | filterwarnings = 25 | ignore::DeprecationWarning 26 | 27 | markers = 28 | no_freezegun: Disable automatic freezegun wrapping 29 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | # We keep the ruff configuration separate so it can easily be shared across 2 | # all projects 3 | 4 | target-version = 'py39' 5 | 6 | #src = ['progressbar'] 7 | exclude = [ 8 | '.venv', 9 | '.tox', 10 | # Ignore local test files/directories/old-stuff 11 | 'test.py', 12 | '*_old.py', 13 | ] 14 | 15 | line-length = 79 16 | 17 | [lint] 18 | ignore = [ 19 | 'A001', # Variable {name} is shadowing a Python builtin 20 | 'A002', # Argument {name} is shadowing a Python builtin 21 | 'A003', # Class attribute {name} is shadowing a Python builtin 22 | 'B023', # function-uses-loop-variable 23 | 'B024', # `FormatWidgetMixin` is an abstract base class, but it has no abstract methods 24 | 'D205', # blank-line-after-summary 25 | 'D212', # multi-line-summary-first-line 26 | 'RET505', # Unnecessary `else` after `return` statement 27 | 'TRY003', # Avoid specifying long messages outside the exception class 28 | 'RET507', # Unnecessary `elif` after `continue` statement 29 | 'C405', # Unnecessary {obj_type} literal (rewrite as a set literal) 30 | 'C406', # Unnecessary {obj_type} literal (rewrite as a dict literal) 31 | 'C408', # Unnecessary {obj_type} call (rewrite as a literal) 32 | 'SIM114', # Combine `if` branches using logical `or` operator 33 | 'RET506', # Unnecessary `else` after `raise` statement 34 | 'Q001', # Remove bad quotes 35 | 'Q002', # Remove bad quotes 36 | 'FA100', # Missing `from __future__ import annotations`, but uses `typing.Optional` 37 | 'COM812', # Missing trailing comma in a list 38 | 'ISC001', # String concatenation with implicit str conversion 39 | 'SIM108', # Ternary operators are not always more readable 40 | 'RUF100', # Unused noqa directives. Due to multiple Python versions, we need to keep them 41 | ] 42 | 43 | select = [ 44 | 'A', # flake8-builtins 45 | 'ASYNC', # flake8 async checker 46 | 'B', # flake8-bugbear 47 | 'C4', # flake8-comprehensions 48 | 'C90', # mccabe 49 | 'COM', # flake8-commas 50 | 51 | ## Require docstrings for all public methods, would be good to enable at some point 52 | # 'D', # pydocstyle 53 | 54 | 'E', # pycodestyle error ('W' for warning) 55 | 'F', # pyflakes 56 | 'FA', # flake8-future-annotations 57 | 'I', # isort 58 | 'ICN', # flake8-import-conventions 59 | 'INP', # flake8-no-pep420 60 | 'ISC', # flake8-implicit-str-concat 61 | 'N', # pep8-naming 62 | 'NPY', # NumPy-specific rules 63 | 'PERF', # perflint, 64 | 'PIE', # flake8-pie 65 | 'Q', # flake8-quotes 66 | 67 | 'RET', # flake8-return 68 | 'RUF', # Ruff-specific rules 69 | 'SIM', # flake8-simplify 70 | 'T20', # flake8-print 71 | 'TD', # flake8-todos 72 | 'TRY', # tryceratops 73 | 'UP', # pyupgrade 74 | ] 75 | 76 | [lint.per-file-ignores] 77 | 'tests/*' = ['INP001', 'T201', 'T203'] 78 | 'examples.py' = ['T201', 'N806'] 79 | 'docs/conf.py' = ['E501', 'INP001'] 80 | 'docs/_theme/flask_theme_support.py' = ['RUF012', 'INP001'] 81 | 82 | [lint.pydocstyle] 83 | convention = 'google' 84 | ignore-decorators = [ 85 | 'typing.overload', 86 | 'typing.override', 87 | ] 88 | 89 | [lint.isort] 90 | case-sensitive = true 91 | combine-as-imports = true 92 | force-wrap-aliases = true 93 | 94 | [lint.flake8-quotes] 95 | docstring-quotes = 'single' 96 | inline-quotes = 'single' 97 | multiline-quotes = 'single' 98 | 99 | [format] 100 | line-ending = 'lf' 101 | indent-style = 'space' 102 | quote-style = 'single' 103 | docstring-code-format = true 104 | skip-magic-trailing-comma = false 105 | exclude = [ 106 | '__init__.py', 107 | ] 108 | 109 | [lint.pycodestyle] 110 | max-line-length = 79 111 | 112 | [lint.flake8-pytest-style] 113 | mark-parentheses = true 114 | 115 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import time 5 | import timeit 6 | from datetime import datetime 7 | 8 | import freezegun 9 | import pytest 10 | 11 | import progressbar 12 | 13 | LOG_LEVELS: dict[str, int] = { 14 | '0': logging.ERROR, 15 | '1': logging.WARNING, 16 | '2': logging.INFO, 17 | '3': logging.DEBUG, 18 | } 19 | 20 | 21 | def pytest_configure(config) -> None: 22 | logging.basicConfig( 23 | level=LOG_LEVELS.get(config.option.verbose, logging.DEBUG), 24 | ) 25 | 26 | 27 | @pytest.fixture(autouse=True) 28 | def small_interval(monkeypatch) -> None: 29 | # Remove the update limit for tests by default 30 | monkeypatch.setattr( 31 | progressbar.ProgressBar, 32 | '_MINIMUM_UPDATE_INTERVAL', 33 | 1e-6, 34 | ) 35 | monkeypatch.setattr(timeit, 'default_timer', time.time) 36 | 37 | 38 | @pytest.fixture(autouse=True) 39 | def sleep_faster(monkeypatch): 40 | # The timezone offset in seconds, add 10 seconds to make sure we don't 41 | # accidentally get the wrong hour 42 | offset_seconds = (datetime.now() - datetime.utcnow()).seconds + 10 43 | offset_hours = int(offset_seconds / 3600) 44 | 45 | freeze_time = freezegun.freeze_time(tz_offset=offset_hours) 46 | with freeze_time as fake_time: 47 | monkeypatch.setattr('time.sleep', fake_time.tick) 48 | monkeypatch.setattr('timeit.default_timer', time.time) 49 | yield freeze_time 50 | -------------------------------------------------------------------------------- /tests/original_examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import time 5 | 6 | from progressbar import ( 7 | ETA, 8 | AdaptiveETA, 9 | AnimatedMarker, 10 | Bar, 11 | BouncingBar, 12 | Counter, 13 | FileTransferSpeed, 14 | FormatLabel, 15 | Percentage, 16 | ProgressBar, 17 | ReverseBar, 18 | RotatingMarker, 19 | SimpleProgress, 20 | Timer, 21 | UnknownLength, 22 | ) 23 | 24 | examples = [] 25 | 26 | 27 | def example(fn): 28 | try: 29 | name = f'Example {int(fn.__name__[7:]):d}' 30 | except Exception: 31 | name = fn.__name__ 32 | 33 | def wrapped(): 34 | try: 35 | sys.stdout.write(f'Running: {name}\n') 36 | fn() 37 | sys.stdout.write('\n') 38 | except KeyboardInterrupt: 39 | sys.stdout.write('\nSkipping example.\n\n') 40 | 41 | examples.append(wrapped) 42 | return wrapped 43 | 44 | 45 | @example 46 | def example0() -> None: 47 | pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=300).start() 48 | for i in range(300): 49 | time.sleep(0.01) 50 | pbar.update(i + 1) 51 | pbar.finish() 52 | 53 | 54 | @example 55 | def example1() -> None: 56 | widgets = [ 57 | 'Test: ', 58 | Percentage(), 59 | ' ', 60 | Bar(marker=RotatingMarker()), 61 | ' ', 62 | ETA(), 63 | ' ', 64 | FileTransferSpeed(), 65 | ] 66 | pbar = ProgressBar(widgets=widgets, maxval=10000).start() 67 | for i in range(1000): 68 | # do something 69 | pbar.update(10 * i + 1) 70 | pbar.finish() 71 | 72 | 73 | @example 74 | def example2() -> None: 75 | class CrazyFileTransferSpeed(FileTransferSpeed): 76 | """It's bigger between 45 and 80 percent.""" 77 | 78 | def update(self, pbar): 79 | if 45 < pbar.percentage() < 80: 80 | return 'Bigger Now ' + FileTransferSpeed.update(self, pbar) 81 | else: 82 | return FileTransferSpeed.update(self, pbar) 83 | 84 | widgets = [ 85 | CrazyFileTransferSpeed(), 86 | ' <<<', 87 | Bar(), 88 | '>>> ', 89 | Percentage(), 90 | ' ', 91 | ETA(), 92 | ] 93 | pbar = ProgressBar(widgets=widgets, maxval=10000) 94 | # maybe do something 95 | pbar.start() 96 | for i in range(2000): 97 | # do something 98 | pbar.update(5 * i + 1) 99 | pbar.finish() 100 | 101 | 102 | @example 103 | def example3() -> None: 104 | widgets = [Bar('>'), ' ', ETA(), ' ', ReverseBar('<')] 105 | pbar = ProgressBar(widgets=widgets, maxval=10000).start() 106 | for i in range(1000): 107 | # do something 108 | pbar.update(10 * i + 1) 109 | pbar.finish() 110 | 111 | 112 | @example 113 | def example4() -> None: 114 | widgets = [ 115 | 'Test: ', 116 | Percentage(), 117 | ' ', 118 | Bar(marker='0', left='[', right=']'), 119 | ' ', 120 | ETA(), 121 | ' ', 122 | FileTransferSpeed(), 123 | ] 124 | pbar = ProgressBar(widgets=widgets, maxval=500) 125 | pbar.start() 126 | for i in range(100, 500 + 1, 50): 127 | time.sleep(0.2) 128 | pbar.update(i) 129 | pbar.finish() 130 | 131 | 132 | @example 133 | def example5() -> None: 134 | pbar = ProgressBar(widgets=[SimpleProgress()], maxval=17).start() 135 | for i in range(17): 136 | time.sleep(0.2) 137 | pbar.update(i + 1) 138 | pbar.finish() 139 | 140 | 141 | @example 142 | def example6() -> None: 143 | pbar = ProgressBar().start() 144 | for i in range(100): 145 | time.sleep(0.01) 146 | pbar.update(i + 1) 147 | pbar.finish() 148 | 149 | 150 | @example 151 | def example7() -> None: 152 | pbar = ProgressBar() # Progressbar can guess maxval automatically. 153 | for _i in pbar(range(80)): 154 | time.sleep(0.01) 155 | 156 | 157 | @example 158 | def example8() -> None: 159 | pbar = ProgressBar(maxval=80) # Progressbar can't guess maxval. 160 | for _i in pbar(i for i in range(80)): 161 | time.sleep(0.01) 162 | 163 | 164 | @example 165 | def example9() -> None: 166 | pbar = ProgressBar(widgets=['Working: ', AnimatedMarker()]) 167 | for _i in pbar(i for i in range(50)): 168 | time.sleep(0.08) 169 | 170 | 171 | @example 172 | def example10() -> None: 173 | widgets = ['Processed: ', Counter(), ' lines (', Timer(), ')'] 174 | pbar = ProgressBar(widgets=widgets) 175 | for _i in pbar(i for i in range(150)): 176 | time.sleep(0.1) 177 | 178 | 179 | @example 180 | def example11() -> None: 181 | widgets = [FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)')] 182 | pbar = ProgressBar(widgets=widgets) 183 | for _i in pbar(i for i in range(150)): 184 | time.sleep(0.1) 185 | 186 | 187 | @example 188 | def example12() -> None: 189 | widgets = ['Balloon: ', AnimatedMarker(markers='.oO@* ')] 190 | pbar = ProgressBar(widgets=widgets) 191 | for _i in pbar(i for i in range(24)): 192 | time.sleep(0.3) 193 | 194 | 195 | @example 196 | def example13() -> None: 197 | # You may need python 3.x to see this correctly 198 | try: 199 | widgets = ['Arrows: ', AnimatedMarker(markers='←↖↑↗→↘↓↙')] 200 | pbar = ProgressBar(widgets=widgets) 201 | for _i in pbar(i for i in range(24)): 202 | time.sleep(0.3) 203 | except UnicodeError: 204 | sys.stdout.write('Unicode error: skipping example') 205 | 206 | 207 | @example 208 | def example14() -> None: 209 | # You may need python 3.x to see this correctly 210 | try: 211 | widgets = ['Arrows: ', AnimatedMarker(markers='◢◣◤◥')] 212 | pbar = ProgressBar(widgets=widgets) 213 | for _i in pbar(i for i in range(24)): 214 | time.sleep(0.3) 215 | except UnicodeError: 216 | sys.stdout.write('Unicode error: skipping example') 217 | 218 | 219 | @example 220 | def example15() -> None: 221 | # You may need python 3.x to see this correctly 222 | try: 223 | widgets = ['Wheels: ', AnimatedMarker(markers='◐◓◑◒')] 224 | pbar = ProgressBar(widgets=widgets) 225 | for _i in pbar(i for i in range(24)): 226 | time.sleep(0.3) 227 | except UnicodeError: 228 | sys.stdout.write('Unicode error: skipping example') 229 | 230 | 231 | @example 232 | def example16() -> None: 233 | widgets = [FormatLabel('Bouncer: value %(value)d - '), BouncingBar()] 234 | pbar = ProgressBar(widgets=widgets) 235 | for _i in pbar(i for i in range(180)): 236 | time.sleep(0.05) 237 | 238 | 239 | @example 240 | def example17() -> None: 241 | widgets = [ 242 | FormatLabel('Animated Bouncer: value %(value)d - '), 243 | BouncingBar(marker=RotatingMarker()), 244 | ] 245 | 246 | pbar = ProgressBar(widgets=widgets) 247 | for _i in pbar(i for i in range(180)): 248 | time.sleep(0.05) 249 | 250 | 251 | @example 252 | def example18() -> None: 253 | widgets = [Percentage(), ' ', Bar(), ' ', ETA(), ' ', AdaptiveETA()] 254 | pbar = ProgressBar(widgets=widgets, maxval=500) 255 | pbar.start() 256 | for i in range(500): 257 | time.sleep(0.01 + (i < 100) * 0.01 + (i > 400) * 0.9) 258 | pbar.update(i + 1) 259 | pbar.finish() 260 | 261 | 262 | @example 263 | def example19() -> None: 264 | pbar = ProgressBar() 265 | for _i in pbar([]): 266 | pass 267 | pbar.finish() 268 | 269 | 270 | @example 271 | def example20() -> None: 272 | """Widgets that behave differently when length is unknown""" 273 | widgets = [ 274 | '[When length is unknown at first]', 275 | ' Progress: ', 276 | SimpleProgress(), 277 | ', Percent: ', 278 | Percentage(), 279 | ' ', 280 | ETA(), 281 | ' ', 282 | AdaptiveETA(), 283 | ] 284 | pbar = ProgressBar(widgets=widgets, maxval=UnknownLength) 285 | pbar.start() 286 | for i in range(20): 287 | time.sleep(0.5) 288 | if i == 10: 289 | pbar.maxval = 20 290 | pbar.update(i + 1) 291 | pbar.finish() 292 | 293 | 294 | if __name__ == '__main__': 295 | try: 296 | for example in examples: 297 | example() 298 | except KeyboardInterrupt: 299 | sys.stdout.write('\nQuitting examples.\n') 300 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | -e.[tests] 2 | -------------------------------------------------------------------------------- /tests/test_algorithms.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import pytest 4 | 5 | from progressbar import algorithms 6 | 7 | 8 | def test_ema_initialization() -> None: 9 | ema = algorithms.ExponentialMovingAverage() 10 | assert ema.alpha == 0.5 11 | assert ema.value == 0 12 | 13 | 14 | @pytest.mark.parametrize( 15 | 'alpha, new_value, expected', 16 | [ 17 | (0.5, 10, 5), 18 | (0.1, 20, 2), 19 | (0.9, 30, 27), 20 | (0.3, 15, 4.5), 21 | (0.7, 40, 28), 22 | (0.5, 0, 0), 23 | (0.2, 100, 20), 24 | (0.8, 50, 40), 25 | ], 26 | ) 27 | def test_ema_update(alpha, new_value: float, expected) -> None: 28 | ema = algorithms.ExponentialMovingAverage(alpha) 29 | result = ema.update(new_value, timedelta(seconds=1)) 30 | assert result == expected 31 | 32 | 33 | def test_dema_initialization() -> None: 34 | dema = algorithms.DoubleExponentialMovingAverage() 35 | assert dema.alpha == 0.5 36 | assert dema.ema1 == 0 37 | assert dema.ema2 == 0 38 | 39 | 40 | @pytest.mark.parametrize( 41 | 'alpha, new_value, expected', 42 | [ 43 | (0.5, 10, 7.5), 44 | (0.1, 20, 3.8), 45 | (0.9, 30, 29.7), 46 | (0.3, 15, 7.65), 47 | (0.5, 0, 0), 48 | (0.2, 100, 36.0), 49 | (0.8, 50, 48.0), 50 | ], 51 | ) 52 | def test_dema_update(alpha, new_value: float, expected) -> None: 53 | dema = algorithms.DoubleExponentialMovingAverage(alpha) 54 | result = dema.update(new_value, timedelta(seconds=1)) 55 | assert result == expected 56 | 57 | 58 | # Additional test functions can be added here as needed. 59 | -------------------------------------------------------------------------------- /tests/test_backwards_compatibility.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import progressbar 4 | 5 | 6 | def test_progressbar_1_widgets() -> None: 7 | widgets = [ 8 | progressbar.AdaptiveETA(format='Time left: %s'), 9 | progressbar.Timer(format='Time passed: %s'), 10 | progressbar.Bar(), 11 | ] 12 | 13 | bar = progressbar.ProgressBar(widgets=widgets, max_value=100).start() 14 | 15 | for i in range(1, 101): 16 | bar.update(i) 17 | time.sleep(0.1) 18 | -------------------------------------------------------------------------------- /tests/test_color.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import ClassVar 5 | 6 | import pytest 7 | 8 | import progressbar 9 | import progressbar.terminal 10 | from progressbar import env, terminal, widgets 11 | from progressbar.terminal import Color, Colors, apply_colors, colors 12 | 13 | ENVIRONMENT_VARIABLES = [ 14 | 'PROGRESSBAR_ENABLE_COLORS', 15 | 'FORCE_COLOR', 16 | 'COLORTERM', 17 | 'TERM', 18 | 'JUPYTER_COLUMNS', 19 | 'JUPYTER_LINES', 20 | 'JPY_PARENT_PID', 21 | ] 22 | 23 | 24 | @pytest.fixture(autouse=True) 25 | def clear_env(monkeypatch: pytest.MonkeyPatch) -> None: 26 | # Clear all environment variables that might affect the tests 27 | for variable in ENVIRONMENT_VARIABLES: 28 | monkeypatch.delenv(variable, raising=False) 29 | 30 | monkeypatch.setattr(env, 'JUPYTER', False) 31 | 32 | 33 | @pytest.mark.parametrize( 34 | 'variable', 35 | [ 36 | 'PROGRESSBAR_ENABLE_COLORS', 37 | 'FORCE_COLOR', 38 | ], 39 | ) 40 | def test_color_environment_variables( 41 | monkeypatch: pytest.MonkeyPatch, 42 | variable: str, 43 | ) -> None: 44 | if os.name == 'nt': 45 | # Windows has special handling so we need to disable that to make the 46 | # tests work properly 47 | monkeypatch.setattr(os, 'name', 'posix') 48 | 49 | monkeypatch.setattr( 50 | env, 51 | 'COLOR_SUPPORT', 52 | env.ColorSupport.XTERM_256, 53 | ) 54 | 55 | monkeypatch.setenv(variable, 'true') 56 | bar = progressbar.ProgressBar() 57 | assert not env.is_ansi_terminal(bar.fd) 58 | assert not bar.is_ansi_terminal 59 | assert bar.enable_colors 60 | 61 | monkeypatch.setenv(variable, 'false') 62 | bar = progressbar.ProgressBar() 63 | assert not bar.enable_colors 64 | 65 | monkeypatch.setenv(variable, '') 66 | bar = progressbar.ProgressBar() 67 | assert not bar.enable_colors 68 | 69 | 70 | @pytest.mark.parametrize( 71 | 'variable', 72 | [ 73 | 'FORCE_COLOR', 74 | 'PROGRESSBAR_ENABLE_COLORS', 75 | 'COLORTERM', 76 | 'TERM', 77 | ], 78 | ) 79 | @pytest.mark.parametrize( 80 | 'value', 81 | [ 82 | '', 83 | 'truecolor', 84 | '24bit', 85 | '256', 86 | 'xterm-256', 87 | 'xterm', 88 | ], 89 | ) 90 | def test_color_support_from_env(monkeypatch, variable, value) -> None: 91 | if os.name == 'nt': 92 | # Windows has special handling so we need to disable that to make the 93 | # tests work properly 94 | monkeypatch.setattr(os, 'name', 'posix') 95 | 96 | monkeypatch.setenv(variable, value) 97 | env.ColorSupport.from_env() 98 | 99 | 100 | @pytest.mark.parametrize( 101 | 'variable', 102 | [ 103 | 'JUPYTER_COLUMNS', 104 | 'JUPYTER_LINES', 105 | ], 106 | ) 107 | def test_color_support_from_env_jupyter(monkeypatch, variable) -> None: 108 | monkeypatch.setattr(env, 'JUPYTER', True) 109 | assert env.ColorSupport.from_env() == env.ColorSupport.XTERM_TRUECOLOR 110 | 111 | # Sanity check 112 | monkeypatch.setattr(env, 'JUPYTER', False) 113 | if os.name == 'nt': 114 | assert env.ColorSupport.from_env() == env.ColorSupport.WINDOWS 115 | else: 116 | assert env.ColorSupport.from_env() == env.ColorSupport.NONE 117 | 118 | 119 | def test_enable_colors_flags() -> None: 120 | bar = progressbar.ProgressBar(enable_colors=True) 121 | assert bar.enable_colors 122 | 123 | bar = progressbar.ProgressBar(enable_colors=False) 124 | assert not bar.enable_colors 125 | 126 | bar = progressbar.ProgressBar( 127 | enable_colors=env.ColorSupport.XTERM_TRUECOLOR, 128 | ) 129 | assert bar.enable_colors 130 | 131 | with pytest.raises(ValueError): 132 | progressbar.ProgressBar(enable_colors=12345) 133 | 134 | 135 | class _TestFixedColorSupport(progressbar.widgets.WidgetBase): 136 | _fixed_colors: ClassVar[widgets.TFixedColors] = widgets.TFixedColors( 137 | fg_none=progressbar.widgets.colors.yellow, 138 | bg_none=None, 139 | ) 140 | 141 | def __call__(self, *args, **kwargs) -> None: 142 | pass 143 | 144 | 145 | class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): 146 | _gradient_colors: ClassVar[widgets.TGradientColors] = ( 147 | widgets.TGradientColors( 148 | fg=progressbar.widgets.colors.gradient, 149 | bg=None, 150 | ) 151 | ) 152 | 153 | def __call__(self, *args, **kwargs) -> None: 154 | pass 155 | 156 | 157 | @pytest.mark.parametrize( 158 | 'widget', 159 | [ 160 | progressbar.Percentage, 161 | progressbar.SimpleProgress, 162 | _TestFixedColorSupport, 163 | _TestFixedGradientSupport, 164 | ], 165 | ) 166 | def test_color_widgets(widget) -> None: 167 | assert widget().uses_colors 168 | print(f'{widget} has colors? {widget.uses_colors}') 169 | 170 | 171 | def test_color_gradient() -> None: 172 | gradient = terminal.ColorGradient(colors.red) 173 | assert gradient.get_color(0) == gradient.get_color(-1) 174 | assert gradient.get_color(1) == gradient.get_color(2) 175 | 176 | assert gradient.get_color(0.5) == colors.red 177 | 178 | gradient = terminal.ColorGradient(colors.red, colors.yellow) 179 | assert gradient.get_color(0) == colors.red 180 | assert gradient.get_color(1) == colors.yellow 181 | assert gradient.get_color(0.5) != colors.red 182 | assert gradient.get_color(0.5) != colors.yellow 183 | 184 | gradient = terminal.ColorGradient( 185 | colors.red, 186 | colors.yellow, 187 | interpolate=False, 188 | ) 189 | assert gradient.get_color(0) == colors.red 190 | assert gradient.get_color(1) == colors.yellow 191 | assert gradient.get_color(0.5) == colors.red 192 | 193 | 194 | @pytest.mark.parametrize( 195 | 'widget', 196 | [ 197 | progressbar.Counter, 198 | ], 199 | ) 200 | def test_no_color_widgets(widget) -> None: 201 | assert not widget().uses_colors 202 | print(f'{widget} has colors? {widget.uses_colors}') 203 | 204 | assert widget( 205 | fixed_colors=_TestFixedColorSupport._fixed_colors, 206 | ).uses_colors 207 | assert widget( 208 | gradient_colors=_TestFixedGradientSupport._gradient_colors, 209 | ).uses_colors 210 | 211 | 212 | def test_colors(monkeypatch) -> None: 213 | for colors_ in Colors.by_rgb.values(): 214 | for color in colors_: 215 | rgb = color.rgb 216 | assert rgb.rgb 217 | assert rgb.hex 218 | assert rgb.to_ansi_16 is not None 219 | assert rgb.to_ansi_256 is not None 220 | assert rgb.to_windows is not None 221 | 222 | with monkeypatch.context() as context: 223 | context.setattr(env, 'COLOR_SUPPORT', env.ColorSupport.XTERM) 224 | assert color.underline 225 | context.setattr(env, 'COLOR_SUPPORT', env.ColorSupport.WINDOWS) 226 | assert color.underline 227 | 228 | assert color.fg 229 | assert color.bg 230 | assert str(rgb) 231 | assert color('test') 232 | 233 | color_no_name = Color( 234 | rgb=color.rgb, 235 | hls=color.hls, 236 | name=None, 237 | xterm=color.xterm, 238 | ) 239 | # Test without name 240 | assert str(color_no_name) != str(color) 241 | 242 | 243 | def test_color() -> None: 244 | color = colors.red 245 | if os.name != 'nt': 246 | assert color('x') == color.fg('x') != 'x' 247 | assert color.fg('x') != color.bg('x') != 'x' 248 | assert color.fg('x') != color.underline('x') != 'x' 249 | # Color hashes are based on the RGB value 250 | assert hash(color) == hash(terminal.Color(color.rgb, None, None, None)) 251 | Colors.register(color.rgb) 252 | 253 | 254 | @pytest.mark.parametrize( 255 | 'rgb,hls', 256 | [ 257 | (terminal.RGB(0, 0, 0), terminal.HSL(0, 0, 0)), 258 | (terminal.RGB(255, 255, 255), terminal.HSL(0, 0, 100)), 259 | (terminal.RGB(255, 0, 0), terminal.HSL(0, 100, 50)), 260 | (terminal.RGB(0, 255, 0), terminal.HSL(120, 100, 50)), 261 | (terminal.RGB(0, 0, 255), terminal.HSL(240, 100, 50)), 262 | (terminal.RGB(255, 255, 0), terminal.HSL(60, 100, 50)), 263 | (terminal.RGB(0, 255, 255), terminal.HSL(180, 100, 50)), 264 | (terminal.RGB(255, 0, 255), terminal.HSL(300, 100, 50)), 265 | (terminal.RGB(128, 128, 128), terminal.HSL(0, 0, 50)), 266 | (terminal.RGB(128, 0, 0), terminal.HSL(0, 100, 25)), 267 | (terminal.RGB(128, 128, 0), terminal.HSL(60, 100, 25)), 268 | (terminal.RGB(0, 128, 0), terminal.HSL(120, 100, 25)), 269 | (terminal.RGB(128, 0, 128), terminal.HSL(300, 100, 25)), 270 | (terminal.RGB(0, 128, 128), terminal.HSL(180, 100, 25)), 271 | (terminal.RGB(0, 0, 128), terminal.HSL(240, 100, 25)), 272 | (terminal.RGB(192, 192, 192), terminal.HSL(0, 0, 75)), 273 | ], 274 | ) 275 | def test_rgb_to_hls(rgb, hls) -> None: 276 | assert terminal.HSL.from_rgb(rgb) == hls 277 | 278 | 279 | @pytest.mark.parametrize( 280 | 'text, fg, bg, fg_none, bg_none, percentage, expected', 281 | [ 282 | ('test', None, None, None, None, None, 'test'), 283 | ('test', None, None, None, None, 1, 'test'), 284 | ( 285 | 'test', 286 | None, 287 | None, 288 | None, 289 | colors.red, 290 | None, 291 | '\x1b[48;5;9mtest\x1b[49m', 292 | ), 293 | ( 294 | 'test', 295 | None, 296 | colors.green, 297 | None, 298 | colors.red, 299 | None, 300 | '\x1b[48;5;9mtest\x1b[49m', 301 | ), 302 | ('test', None, colors.red, None, None, 1, '\x1b[48;5;9mtest\x1b[49m'), 303 | ('test', None, colors.red, None, None, None, 'test'), 304 | ( 305 | 'test', 306 | colors.green, 307 | None, 308 | colors.red, 309 | None, 310 | None, 311 | '\x1b[38;5;9mtest\x1b[39m', 312 | ), 313 | ( 314 | 'test', 315 | colors.green, 316 | colors.red, 317 | None, 318 | None, 319 | 1, 320 | '\x1b[48;5;9m\x1b[38;5;2mtest\x1b[39m\x1b[49m', 321 | ), 322 | ('test', colors.red, None, None, None, 1, '\x1b[38;5;9mtest\x1b[39m'), 323 | ('test', colors.red, None, None, None, None, 'test'), 324 | ('test', colors.red, colors.red, None, None, None, 'test'), 325 | ( 326 | 'test', 327 | colors.red, 328 | colors.yellow, 329 | None, 330 | None, 331 | 1, 332 | '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', 333 | ), 334 | ( 335 | 'test', 336 | colors.red, 337 | colors.yellow, 338 | None, 339 | None, 340 | 1, 341 | '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', 342 | ), 343 | ], 344 | ) 345 | def test_apply_colors( 346 | text: str, 347 | fg, 348 | bg, 349 | fg_none, 350 | bg_none, 351 | percentage: float | None, 352 | expected, 353 | monkeypatch, 354 | ) -> None: 355 | monkeypatch.setattr( 356 | env, 357 | 'COLOR_SUPPORT', 358 | env.ColorSupport.XTERM_256, 359 | ) 360 | assert ( 361 | apply_colors( 362 | text, 363 | fg=fg, 364 | bg=bg, 365 | fg_none=fg_none, 366 | bg_none=bg_none, 367 | percentage=percentage, 368 | ) 369 | == expected 370 | ) 371 | 372 | 373 | def test_windows_colors(monkeypatch) -> None: 374 | monkeypatch.setattr(env, 'COLOR_SUPPORT', env.ColorSupport.WINDOWS) 375 | assert ( 376 | apply_colors( 377 | 'test', 378 | fg=colors.red, 379 | bg=colors.red, 380 | fg_none=colors.red, 381 | bg_none=colors.red, 382 | percentage=1, 383 | ) 384 | == 'test' 385 | ) 386 | colors.red.underline('test') 387 | 388 | 389 | def test_ansi_color(monkeypatch) -> None: 390 | color = progressbar.terminal.Color( 391 | colors.red.rgb, 392 | colors.red.hls, 393 | 'red-ansi', 394 | None, 395 | ) 396 | 397 | for color_support in { 398 | env.ColorSupport.NONE, 399 | env.ColorSupport.XTERM, 400 | env.ColorSupport.XTERM_256, 401 | env.ColorSupport.XTERM_TRUECOLOR, 402 | }: 403 | monkeypatch.setattr( 404 | env, 405 | 'COLOR_SUPPORT', 406 | color_support, 407 | ) 408 | assert color.ansi is not None or color_support == env.ColorSupport.NONE 409 | 410 | 411 | def test_sgr_call() -> None: 412 | assert progressbar.terminal.encircled('test') == '\x1b[52mtest\x1b[54m' 413 | -------------------------------------------------------------------------------- /tests/test_custom_widgets.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | 5 | import progressbar 6 | 7 | 8 | class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): 9 | "It's bigger between 45 and 80 percent" 10 | 11 | def update(self, pbar): 12 | if 45 < pbar.percentage() < 80: 13 | value = progressbar.FileTransferSpeed.update(self, pbar) 14 | return f'Bigger Now {value}' 15 | else: 16 | return progressbar.FileTransferSpeed.update(self, pbar) 17 | 18 | 19 | def test_crazy_file_transfer_speed_widget() -> None: 20 | widgets = [ 21 | # CrazyFileTransferSpeed(), 22 | ' <<<', 23 | progressbar.Bar(), 24 | '>>> ', 25 | progressbar.Percentage(), 26 | ' ', 27 | progressbar.ETA(), 28 | ] 29 | 30 | p = progressbar.ProgressBar(widgets=widgets, max_value=1000) 31 | # maybe do something 32 | p.start() 33 | for i in range(0, 200, 5): 34 | # do something 35 | time.sleep(0.1) 36 | p.update(i + 1) 37 | p.finish() 38 | 39 | 40 | def test_variable_widget_widget() -> None: 41 | widgets = [ 42 | ' [', 43 | progressbar.Timer(), 44 | '] ', 45 | progressbar.Bar(), 46 | ' (', 47 | progressbar.ETA(), 48 | ') ', 49 | progressbar.Variable('loss'), 50 | progressbar.Variable('text'), 51 | progressbar.Variable('error', precision=None), 52 | progressbar.Variable('missing'), 53 | progressbar.Variable('predefined'), 54 | ] 55 | 56 | p = progressbar.ProgressBar( 57 | widgets=widgets, 58 | max_value=1000, 59 | variables=dict(predefined='predefined'), 60 | ) 61 | p.start() 62 | print('time', time, time.sleep) 63 | for i in range(0, 200, 5): 64 | time.sleep(0.1) 65 | p.update(i + 1, loss=0.5, text='spam', error=1) 66 | 67 | i += 1 68 | p.update(i, text=None) 69 | i += 1 70 | p.update(i, text=False) 71 | i += 1 72 | p.update(i, text=True, error='a') 73 | with pytest.raises(TypeError): 74 | p.update(i, non_existing_variable='error!') 75 | p.finish() 76 | 77 | 78 | def test_format_custom_text_widget() -> None: 79 | widget = progressbar.FormatCustomText( 80 | 'Spam: %(spam).1f kg, eggs: %(eggs)d', 81 | dict( 82 | spam=0.25, 83 | eggs=3, 84 | ), 85 | ) 86 | 87 | bar = progressbar.ProgressBar( 88 | widgets=[ 89 | widget, 90 | ], 91 | ) 92 | 93 | for i in bar(range(5)): 94 | widget.update_mapping(eggs=i * 2) 95 | assert widget.mapping['eggs'] == bar.widgets[0].mapping['eggs'] 96 | -------------------------------------------------------------------------------- /tests/test_data.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import progressbar 4 | 5 | 6 | @pytest.mark.parametrize( 7 | 'value,expected', 8 | [ 9 | (None, ' 0.0 B'), 10 | (1, ' 1.0 B'), 11 | (2**10 - 1, '1023.0 B'), 12 | (2**10 + 0, ' 1.0 KiB'), 13 | (2**20, ' 1.0 MiB'), 14 | (2**30, ' 1.0 GiB'), 15 | (2**40, ' 1.0 TiB'), 16 | (2**50, ' 1.0 PiB'), 17 | (2**60, ' 1.0 EiB'), 18 | (2**70, ' 1.0 ZiB'), 19 | (2**80, ' 1.0 YiB'), 20 | (2**90, '1024.0 YiB'), 21 | ], 22 | ) 23 | def test_data_size(value, expected) -> None: 24 | widget = progressbar.DataSize() 25 | assert widget(None, dict(value=value)) == expected 26 | -------------------------------------------------------------------------------- /tests/test_data_transfer_bar.py: -------------------------------------------------------------------------------- 1 | import progressbar 2 | from progressbar import DataTransferBar 3 | 4 | 5 | def test_known_length() -> None: 6 | dtb = DataTransferBar().start(max_value=50) 7 | for i in range(50): 8 | dtb.update(i) 9 | dtb.finish() 10 | 11 | 12 | def test_unknown_length() -> None: 13 | dtb = DataTransferBar().start(max_value=progressbar.UnknownLength) 14 | for i in range(50): 15 | dtb.update(i) 16 | dtb.finish() 17 | -------------------------------------------------------------------------------- /tests/test_dill_pickle.py: -------------------------------------------------------------------------------- 1 | import dill # type: ignore 2 | 3 | import progressbar 4 | 5 | 6 | def test_dill() -> None: 7 | bar = progressbar.ProgressBar() 8 | assert bar._started is False 9 | assert bar._finished is False 10 | 11 | assert dill.pickles(bar) is False 12 | 13 | assert bar._started is False 14 | # Should be false because it never should have started/initialized 15 | assert bar._finished is False 16 | -------------------------------------------------------------------------------- /tests/test_empty.py: -------------------------------------------------------------------------------- 1 | import progressbar 2 | 3 | 4 | def test_empty_list() -> None: 5 | for x in progressbar.ProgressBar()([]): 6 | print(x) 7 | 8 | 9 | def test_empty_iterator() -> None: 10 | for x in progressbar.ProgressBar(max_value=0)(iter([])): 11 | print(x) 12 | -------------------------------------------------------------------------------- /tests/test_end.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import progressbar 4 | 5 | 6 | @pytest.fixture(autouse=True) 7 | def large_interval(monkeypatch) -> None: 8 | # Remove the update limit for tests by default 9 | monkeypatch.setattr( 10 | progressbar.ProgressBar, 11 | '_MINIMUM_UPDATE_INTERVAL', 12 | 0.1, 13 | ) 14 | 15 | 16 | def test_end() -> None: 17 | m = 24514315 18 | p = progressbar.ProgressBar( 19 | widgets=[progressbar.Percentage(), progressbar.Bar()], 20 | max_value=m, 21 | ) 22 | 23 | for x in range(0, m, 8192): 24 | p.update(x) 25 | 26 | data = p.data() 27 | assert data['percentage'] < 100.0 28 | 29 | p.finish() 30 | 31 | data = p.data() 32 | assert data['percentage'] >= 100.0 33 | 34 | assert p.value == m 35 | 36 | 37 | def test_end_100(monkeypatch) -> None: 38 | assert progressbar.ProgressBar._MINIMUM_UPDATE_INTERVAL == 0.1 39 | p = progressbar.ProgressBar( 40 | widgets=[progressbar.Percentage(), progressbar.Bar()], 41 | max_value=103, 42 | ) 43 | 44 | for x in range(102): 45 | p.update(x) 46 | 47 | data = p.data() 48 | import pprint 49 | 50 | pprint.pprint(data) 51 | assert data['percentage'] < 100.0 52 | 53 | p.finish() 54 | 55 | data = p.data() 56 | assert data['percentage'] >= 100.0 57 | -------------------------------------------------------------------------------- /tests/test_failure.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import pytest 5 | 6 | import progressbar 7 | 8 | 9 | def test_missing_format_values(caplog) -> None: 10 | caplog.set_level(logging.CRITICAL, logger='progressbar.widgets') 11 | with pytest.raises(KeyError): 12 | p = progressbar.ProgressBar( 13 | widgets=[progressbar.widgets.FormatLabel('%(x)s')], 14 | ) 15 | p.update(5) 16 | 17 | 18 | def test_max_smaller_than_min() -> None: 19 | with pytest.raises(ValueError): 20 | progressbar.ProgressBar(min_value=10, max_value=5) 21 | 22 | 23 | def test_no_max_value() -> None: 24 | """Looping up to 5 without max_value? No problem""" 25 | p = progressbar.ProgressBar() 26 | p.start() 27 | for i in range(5): 28 | time.sleep(1) 29 | p.update(i) 30 | 31 | 32 | def test_correct_max_value() -> None: 33 | """Looping up to 5 when max_value is 10? No problem""" 34 | p = progressbar.ProgressBar(max_value=10) 35 | for i in range(5): 36 | time.sleep(1) 37 | p.update(i) 38 | 39 | 40 | def test_minus_max_value() -> None: 41 | """negative max_value, shouldn't work""" 42 | p = progressbar.ProgressBar(min_value=-2, max_value=-1) 43 | 44 | with pytest.raises(ValueError): 45 | p.update(-1) 46 | 47 | 48 | def test_zero_max_value() -> None: 49 | """max_value of zero, it could happen""" 50 | p = progressbar.ProgressBar(max_value=0) 51 | 52 | p.update(0) 53 | with pytest.raises(ValueError): 54 | p.update(1) 55 | 56 | 57 | def test_one_max_value() -> None: 58 | """max_value of one, another corner case""" 59 | p = progressbar.ProgressBar(max_value=1) 60 | 61 | p.update(0) 62 | p.update(0) 63 | p.update(1) 64 | with pytest.raises(ValueError): 65 | p.update(2) 66 | 67 | 68 | def test_changing_max_value() -> None: 69 | """Changing max_value? No problem""" 70 | p = progressbar.ProgressBar(max_value=10)(range(20), max_value=20) 71 | for _i in p: 72 | time.sleep(1) 73 | 74 | 75 | def test_backwards() -> None: 76 | """progressbar going backwards""" 77 | p = progressbar.ProgressBar(max_value=1) 78 | 79 | p.update(1) 80 | p.update(0) 81 | 82 | 83 | def test_incorrect_max_value() -> None: 84 | """Looping up to 10 when max_value is 5? This is madness!""" 85 | p = progressbar.ProgressBar(max_value=5) 86 | for i in range(5): 87 | time.sleep(1) 88 | p.update(i) 89 | 90 | with pytest.raises(ValueError): 91 | for i in range(5, 10): 92 | time.sleep(1) 93 | p.update(i) 94 | 95 | 96 | def test_deprecated_maxval() -> None: 97 | with pytest.warns(DeprecationWarning): 98 | progressbar.ProgressBar(maxval=5) 99 | 100 | 101 | def test_deprecated_poll() -> None: 102 | with pytest.warns(DeprecationWarning): 103 | progressbar.ProgressBar(poll=5) 104 | 105 | 106 | def test_deprecated_currval() -> None: 107 | with pytest.warns(DeprecationWarning): 108 | bar = progressbar.ProgressBar(max_value=5) 109 | bar.update(2) 110 | assert bar.currval == 2 111 | 112 | 113 | def test_unexpected_update_keyword_arg() -> None: 114 | p = progressbar.ProgressBar(max_value=10) 115 | with pytest.raises(TypeError): 116 | for i in range(10): 117 | time.sleep(1) 118 | p.update(i, foo=10) 119 | 120 | 121 | def test_variable_not_str() -> None: 122 | with pytest.raises(TypeError): 123 | progressbar.Variable(1) 124 | 125 | 126 | def test_variable_too_many_strs() -> None: 127 | with pytest.raises(ValueError): 128 | progressbar.Variable('too long') 129 | 130 | 131 | def test_negative_value() -> None: 132 | bar = progressbar.ProgressBar(max_value=10) 133 | with pytest.raises(ValueError): 134 | bar.update(value=-1) 135 | 136 | 137 | def test_increment() -> None: 138 | bar = progressbar.ProgressBar(max_value=10) 139 | bar.increment() 140 | del bar 141 | -------------------------------------------------------------------------------- /tests/test_flush.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import progressbar 4 | 5 | 6 | def test_flush() -> None: 7 | """Left justify using the terminal width""" 8 | p = progressbar.ProgressBar(poll_interval=0.001) 9 | p.print('hello') 10 | 11 | for i in range(10): 12 | print('pre-updates', p.updates) 13 | p.update(i) 14 | print('need update?', p._needs_update()) 15 | if i > 5: 16 | time.sleep(0.1) 17 | print('post-updates', p.updates) 18 | -------------------------------------------------------------------------------- /tests/test_iterators.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | 5 | import progressbar 6 | 7 | 8 | def test_list() -> None: 9 | """Progressbar can guess max_value automatically.""" 10 | p = progressbar.ProgressBar() 11 | for _i in p(range(10)): 12 | time.sleep(0.001) 13 | 14 | 15 | def test_iterator_with_max_value() -> None: 16 | """Progressbar can't guess max_value.""" 17 | p = progressbar.ProgressBar(max_value=10) 18 | for _i in p(iter(range(10))): 19 | time.sleep(0.001) 20 | 21 | 22 | def test_iterator_without_max_value_error() -> None: 23 | """Progressbar can't guess max_value.""" 24 | p = progressbar.ProgressBar() 25 | 26 | for _i in p(iter(range(10))): 27 | time.sleep(0.001) 28 | 29 | assert p.max_value is progressbar.UnknownLength 30 | 31 | 32 | def test_iterator_without_max_value() -> None: 33 | """Progressbar can't guess max_value.""" 34 | p = progressbar.ProgressBar( 35 | widgets=[ 36 | progressbar.AnimatedMarker(), 37 | progressbar.FormatLabel('%(value)d'), 38 | progressbar.BouncingBar(), 39 | progressbar.BouncingBar(marker=progressbar.RotatingMarker()), 40 | ], 41 | ) 42 | for _i in p(iter(range(10))): 43 | time.sleep(0.001) 44 | 45 | 46 | def test_iterator_with_incorrect_max_value() -> None: 47 | """Progressbar can't guess max_value.""" 48 | p = progressbar.ProgressBar(max_value=10) 49 | with pytest.raises(ValueError): 50 | for _i in p(iter(range(20))): 51 | time.sleep(0.001) 52 | 53 | 54 | def test_adding_value() -> None: 55 | p = progressbar.ProgressBar(max_value=10) 56 | p.start() 57 | p.update(5) 58 | p += 2 59 | p.increment(2) 60 | with pytest.raises(ValueError): 61 | p += 5 62 | -------------------------------------------------------------------------------- /tests/test_job_status.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | 5 | import progressbar 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'status', 10 | [ 11 | True, 12 | False, 13 | None, 14 | ], 15 | ) 16 | def test_status(status) -> None: 17 | with progressbar.ProgressBar( 18 | widgets=[progressbar.widgets.JobStatusBar('status')], 19 | ) as bar: 20 | for _ in range(5): 21 | bar.increment(status=status, force=True) 22 | time.sleep(0.1) 23 | -------------------------------------------------------------------------------- /tests/test_large_values.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import progressbar 4 | 5 | 6 | def test_large_max_value() -> None: 7 | with progressbar.ProgressBar(max_value=1e10) as bar: 8 | for i in range(10): 9 | bar.update(i) 10 | time.sleep(0.1) 11 | 12 | 13 | def test_value_beyond_max_value() -> None: 14 | with progressbar.ProgressBar(max_value=10, max_error=False) as bar: 15 | for i in range(20): 16 | bar.update(i) 17 | time.sleep(0.01) 18 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | from progressbar import __about__ 2 | 3 | 4 | def test_about() -> None: 5 | assert __about__.__title__ 6 | assert __about__.__package_name__ 7 | assert __about__.__author__ 8 | assert __about__.__description__ 9 | assert __about__.__email__ 10 | assert __about__.__version__ 11 | assert __about__.__license__ 12 | assert __about__.__copyright__ 13 | assert __about__.__url__ 14 | -------------------------------------------------------------------------------- /tests/test_monitor_progress.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # fmt: off 4 | import os 5 | import pprint 6 | 7 | import progressbar 8 | 9 | pytest_plugins = 'pytester' 10 | 11 | SCRIPT = """ 12 | import sys 13 | sys.path.append({progressbar_path!r}) 14 | import time 15 | import timeit 16 | import freezegun 17 | import progressbar 18 | 19 | 20 | with freezegun.freeze_time() as fake_time: 21 | timeit.default_timer = time.time 22 | with progressbar.ProgressBar(widgets={widgets}, **{kwargs!r}) as bar: 23 | bar._MINIMUM_UPDATE_INTERVAL = 1e-9 24 | for i in bar({items}): 25 | {loop_code} 26 | """ 27 | 28 | 29 | def _non_empty_lines(lines): 30 | return [line for line in lines if line.strip()] 31 | 32 | 33 | def _create_script( 34 | widgets=None, 35 | items: list[int] | None=None, 36 | loop_code: str='fake_time.tick(1)', 37 | term_width: int=60, 38 | **kwargs, 39 | ) -> str: 40 | if items is None: 41 | items = list(range(9)) 42 | kwargs['term_width'] = term_width 43 | 44 | # Reindent the loop code 45 | indent = '\n ' 46 | loop_code = loop_code.strip('\n').split('\n') 47 | dedent = len(loop_code[0]) - len(loop_code[0].lstrip()) 48 | for i, line in enumerate(loop_code): 49 | loop_code[i] = line[dedent:] 50 | 51 | script = SCRIPT.format( 52 | items=items, 53 | widgets=widgets, 54 | kwargs=kwargs, 55 | loop_code=indent.join(loop_code), 56 | progressbar_path=os.path.dirname( 57 | os.path.dirname(progressbar.__file__), 58 | ), 59 | ) 60 | print('# Script:') 61 | print('#' * 78) 62 | print(script) 63 | print('#' * 78) 64 | 65 | return script 66 | 67 | 68 | def test_list_example(testdir) -> None: 69 | """Run the simple example code in a python subprocess and then compare its 70 | stderr to what we expect to see from it. We run it in a subprocess to 71 | best capture its stderr. We expect to see match_lines in order in the 72 | output. This test is just a sanity check to ensure that the progress 73 | bar progresses from 1 to 10, it does not make sure that the""" 74 | 75 | result = testdir.runpython( 76 | testdir.makepyfile( 77 | _create_script( 78 | term_width=65, 79 | ), 80 | ), 81 | ) 82 | result.stderr.lines = [ 83 | line.rstrip() for line in _non_empty_lines(result.stderr.lines) 84 | ] 85 | pprint.pprint(result.stderr.lines, width=70) 86 | result.stderr.fnmatch_lines([ 87 | ' 0% (0 of 9) | | Elapsed Time: ?:00:00 ETA: --:--:--', 88 | ' 11% (1 of 9) |# | Elapsed Time: ?:00:01 ETA: ?:00:08', 89 | ' 22% (2 of 9) |## | Elapsed Time: ?:00:02 ETA: ?:00:07', 90 | ' 33% (3 of 9) |#### | Elapsed Time: ?:00:03 ETA: ?:00:06', 91 | ' 44% (4 of 9) |##### | Elapsed Time: ?:00:04 ETA: ?:00:05', 92 | ' 55% (5 of 9) |###### | Elapsed Time: ?:00:05 ETA: ?:00:04', 93 | ' 66% (6 of 9) |######## | Elapsed Time: ?:00:06 ETA: ?:00:03', 94 | ' 77% (7 of 9) |######### | Elapsed Time: ?:00:07 ETA: ?:00:02', 95 | ' 88% (8 of 9) |########## | Elapsed Time: ?:00:08 ETA: ?:00:01', 96 | '100% (9 of 9) |############| Elapsed Time: ?:00:09 Time: ?:00:09', 97 | ]) 98 | 99 | 100 | def test_generator_example(testdir) -> None: 101 | """Run the simple example code in a python subprocess and then compare its 102 | stderr to what we expect to see from it. We run it in a subprocess to 103 | best capture its stderr. We expect to see match_lines in order in the 104 | output. This test is just a sanity check to ensure that the progress 105 | bar progresses from 1 to 10, it does not make sure that the""" 106 | result = testdir.runpython( 107 | testdir.makepyfile( 108 | _create_script( 109 | items='iter(range(9))', 110 | ), 111 | ), 112 | ) 113 | result.stderr.lines = _non_empty_lines(result.stderr.lines) 114 | pprint.pprint(result.stderr.lines, width=70) 115 | 116 | lines = [ 117 | fr'[/\\|\-]\s+\|\s*#\s*\| {i:d} Elapsed Time: \d:00:{i:02d}' 118 | for i in range(9) 119 | ] 120 | result.stderr.re_match_lines(lines) 121 | 122 | 123 | def test_rapid_updates(testdir) -> None: 124 | """Run some example code that updates 10 times, then sleeps .1 seconds, 125 | this is meant to test that the progressbar progresses normally with 126 | this sample code, since there were issues with it in the past""" 127 | 128 | result = testdir.runpython( 129 | testdir.makepyfile( 130 | _create_script( 131 | term_width=60, 132 | items=list(range(10)), 133 | loop_code=""" 134 | if i < 5: 135 | fake_time.tick(1) 136 | else: 137 | fake_time.tick(2) 138 | """, 139 | ), 140 | ), 141 | ) 142 | result.stderr.lines = _non_empty_lines(result.stderr.lines) 143 | pprint.pprint(result.stderr.lines, width=70) 144 | result.stderr.fnmatch_lines( 145 | [ 146 | ' 0% (0 of 10) | | Elapsed Time: 0:00:00 ETA: --:--:--', 147 | ' 10% (1 of 10) | | Elapsed Time: 0:00:01 ETA: 0:00:09', 148 | ' 20% (2 of 10) |# | Elapsed Time: 0:00:02 ETA: 0:00:08', 149 | ' 30% (3 of 10) |# | Elapsed Time: 0:00:03 ETA: 0:00:07', 150 | ' 40% (4 of 10) |## | Elapsed Time: 0:00:04 ETA: 0:00:06', 151 | ' 50% (5 of 10) |### | Elapsed Time: 0:00:05 ETA: 0:00:05', 152 | ' 60% (6 of 10) |### | Elapsed Time: 0:00:07 ETA: 0:00:04', 153 | ' 70% (7 of 10) |#### | Elapsed Time: 0:00:09 ETA: 0:00:03', 154 | ' 80% (8 of 10) |#### | Elapsed Time: 0:00:11 ETA: 0:00:02', 155 | ' 90% (9 of 10) |##### | Elapsed Time: 0:00:13 ETA: 0:00:01', 156 | '100% (10 of 10) |#####| Elapsed Time: 0:00:15 Time: 0:00:15', 157 | ], 158 | ) 159 | 160 | 161 | def test_non_timed(testdir) -> None: 162 | result = testdir.runpython( 163 | testdir.makepyfile( 164 | _create_script( 165 | widgets='[progressbar.Percentage(), progressbar.Bar()]', 166 | items=list(range(5)), 167 | ), 168 | ), 169 | ) 170 | result.stderr.lines = _non_empty_lines(result.stderr.lines) 171 | pprint.pprint(result.stderr.lines, width=70) 172 | result.stderr.fnmatch_lines( 173 | [ 174 | ' 0%| |', 175 | ' 20%|########## |', 176 | ' 40%|##################### |', 177 | ' 60%|################################ |', 178 | ' 80%|########################################### |', 179 | '100%|######################################################|', 180 | ], 181 | ) 182 | 183 | 184 | def test_line_breaks(testdir) -> None: 185 | result = testdir.runpython( 186 | testdir.makepyfile( 187 | _create_script( 188 | widgets='[progressbar.Percentage(), progressbar.Bar()]', 189 | line_breaks=True, 190 | items=list(range(5)), 191 | ), 192 | ), 193 | ) 194 | pprint.pprint(result.stderr.str(), width=70) 195 | assert result.stderr.str() == '\n'.join( 196 | ( 197 | ' 0%| |', 198 | ' 20%|########## |', 199 | ' 40%|##################### |', 200 | ' 60%|################################ |', 201 | ' 80%|########################################### |', 202 | '100%|######################################################|', 203 | '100%|######################################################|', 204 | ), 205 | ) 206 | 207 | 208 | def test_no_line_breaks(testdir) -> None: 209 | result = testdir.runpython( 210 | testdir.makepyfile( 211 | _create_script( 212 | widgets='[progressbar.Percentage(), progressbar.Bar()]', 213 | line_breaks=False, 214 | items=list(range(5)), 215 | ), 216 | ), 217 | ) 218 | pprint.pprint(result.stderr.lines, width=70) 219 | assert result.stderr.lines == [ 220 | '', 221 | ' 0%| |', 222 | ' 20%|########## |', 223 | ' 40%|##################### |', 224 | ' 60%|################################ |', 225 | ' 80%|########################################### |', 226 | '100%|######################################################|', 227 | '', 228 | '100%|######################################################|', 229 | ] 230 | 231 | 232 | def test_percentage_label_bar(testdir) -> None: 233 | result = testdir.runpython( 234 | testdir.makepyfile( 235 | _create_script( 236 | widgets='[progressbar.PercentageLabelBar()]', 237 | line_breaks=False, 238 | items=list(range(5)), 239 | ), 240 | ), 241 | ) 242 | pprint.pprint(result.stderr.lines, width=70) 243 | assert result.stderr.lines == [ 244 | '', 245 | '| 0% |', 246 | '|########### 20% |', 247 | '|####################### 40% |', 248 | '|###########################60%#### |', 249 | '|###########################80%################ |', 250 | '|###########################100%###########################|', 251 | '', 252 | '|###########################100%###########################|', 253 | ] 254 | 255 | 256 | def test_granular_bar(testdir) -> None: 257 | result = testdir.runpython( 258 | testdir.makepyfile( 259 | _create_script( 260 | widgets='[progressbar.GranularBar(markers=" .oO")]', 261 | line_breaks=False, 262 | items=list(range(5)), 263 | ), 264 | ), 265 | ) 266 | pprint.pprint(result.stderr.lines, width=70) 267 | assert result.stderr.lines == [ 268 | '', 269 | '| |', 270 | '|OOOOOOOOOOO. |', 271 | '|OOOOOOOOOOOOOOOOOOOOOOO |', 272 | '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo |', 273 | '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO. |', 274 | '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', 275 | '', 276 | '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', 277 | ] 278 | 279 | 280 | def test_colors(testdir) -> None: 281 | kwargs = dict( 282 | items=range(1), 283 | widgets=['\033[92mgreen\033[0m'], 284 | ) 285 | 286 | result = testdir.runpython( 287 | testdir.makepyfile(_create_script(enable_colors=True, **kwargs)), 288 | ) 289 | pprint.pprint(result.stderr.lines, width=70) 290 | assert result.stderr.lines == ['\x1b[92mgreen\x1b[0m'] * 3 291 | 292 | result = testdir.runpython( 293 | testdir.makepyfile(_create_script(enable_colors=False, **kwargs)), 294 | ) 295 | pprint.pprint(result.stderr.lines, width=70) 296 | assert result.stderr.lines == ['green'] * 3 297 | -------------------------------------------------------------------------------- /tests/test_multibar.py: -------------------------------------------------------------------------------- 1 | import random 2 | import threading 3 | import time 4 | 5 | import pytest 6 | 7 | import progressbar 8 | 9 | N = 10 10 | BARS = 3 11 | SLEEP = 0.002 12 | 13 | 14 | def test_multi_progress_bar_out_of_range() -> None: 15 | widgets = [ 16 | progressbar.MultiProgressBar('multivalues'), 17 | ] 18 | 19 | bar = progressbar.ProgressBar(widgets=widgets, max_value=10) 20 | with pytest.raises(ValueError): 21 | bar.update(multivalues=[123]) 22 | 23 | with pytest.raises(ValueError): 24 | bar.update(multivalues=[-1]) 25 | 26 | 27 | def test_multibar() -> None: 28 | multibar = progressbar.MultiBar( 29 | sort_keyfunc=lambda bar: bar.label, 30 | remove_finished=0.005, 31 | ) 32 | multibar.show_initial = False 33 | multibar.render(force=True) 34 | multibar.show_initial = True 35 | multibar.render(force=True) 36 | multibar.start() 37 | 38 | multibar.append_label = False 39 | multibar.prepend_label = True 40 | 41 | # Test handling of progressbars that don't call the super constructors 42 | bar = progressbar.ProgressBar(max_value=N) 43 | bar.index = -1 44 | multibar['x'] = bar 45 | bar.start() 46 | # Test twice for other code paths 47 | multibar['x'] = bar 48 | multibar._label_bar(bar) 49 | multibar._label_bar(bar) 50 | bar.finish() 51 | del multibar['x'] 52 | 53 | multibar.prepend_label = False 54 | multibar.append_label = True 55 | 56 | append_bar = progressbar.ProgressBar(max_value=N) 57 | append_bar.start() 58 | multibar._label_bar(append_bar) 59 | multibar['append'] = append_bar 60 | multibar.render(force=True) 61 | 62 | def do_something(bar): 63 | for j in bar(range(N)): 64 | time.sleep(0.01) 65 | bar.update(j) 66 | 67 | for i in range(BARS): 68 | thread = threading.Thread( 69 | target=do_something, 70 | args=(multibar[f'bar {i}'],), 71 | ) 72 | thread.start() 73 | 74 | for bar in list(multibar.values()): 75 | for j in range(N): 76 | bar.update(j) 77 | time.sleep(SLEEP) 78 | 79 | multibar.render(force=True) 80 | 81 | multibar.remove_finished = False 82 | multibar.show_finished = False 83 | append_bar.finish() 84 | multibar.render(force=True) 85 | 86 | multibar.join(0.1) 87 | multibar.stop(0.1) 88 | 89 | 90 | @pytest.mark.parametrize( 91 | 'sort_key', 92 | [ 93 | None, 94 | 'index', 95 | 'label', 96 | 'value', 97 | 'percentage', 98 | progressbar.SortKey.CREATED, 99 | progressbar.SortKey.LABEL, 100 | progressbar.SortKey.VALUE, 101 | progressbar.SortKey.PERCENTAGE, 102 | ], 103 | ) 104 | def test_multibar_sorting(sort_key) -> None: 105 | with progressbar.MultiBar() as multibar: 106 | for i in range(BARS): 107 | label = f'bar {i}' 108 | multibar[label] = progressbar.ProgressBar(max_value=N) 109 | 110 | for bar in multibar.values(): 111 | for _j in bar(range(N)): 112 | assert bar.started() 113 | time.sleep(SLEEP) 114 | 115 | for bar in multibar.values(): 116 | assert bar.finished() 117 | 118 | 119 | def test_offset_bar() -> None: 120 | with progressbar.ProgressBar(line_offset=2) as bar: 121 | for i in range(N): 122 | bar.update(i) 123 | 124 | 125 | def test_multibar_show_finished() -> None: 126 | multibar = progressbar.MultiBar(show_finished=True) 127 | multibar['bar'] = progressbar.ProgressBar(max_value=N) 128 | multibar.render(force=True) 129 | with progressbar.MultiBar(show_finished=False) as multibar: 130 | multibar.finished_format = 'finished: {label}' 131 | 132 | for i in range(3): 133 | multibar[f'bar {i}'] = progressbar.ProgressBar(max_value=N) 134 | 135 | for bar in multibar.values(): 136 | for i in range(N): 137 | bar.update(i) 138 | time.sleep(SLEEP) 139 | 140 | multibar.render(force=True) 141 | 142 | 143 | def test_multibar_show_initial() -> None: 144 | multibar = progressbar.MultiBar(show_initial=False) 145 | multibar['bar'] = progressbar.ProgressBar(max_value=N) 146 | multibar.render(force=True) 147 | 148 | 149 | def test_multibar_empty_key() -> None: 150 | multibar = progressbar.MultiBar() 151 | multibar[''] = progressbar.ProgressBar(max_value=N) 152 | 153 | for name in multibar: 154 | assert name == '' 155 | bar = multibar[name] 156 | bar.update(1) 157 | 158 | multibar.render(force=True) 159 | 160 | 161 | def test_multibar_print() -> None: 162 | bars = 5 163 | n = 10 164 | 165 | def print_sometimes(bar, probability): 166 | for i in bar(range(n)): 167 | # Sleep up to 0.1 seconds 168 | time.sleep(random.random() * 0.1) 169 | 170 | # print messages at random intervals to show how extra output works 171 | if random.random() < probability: 172 | bar.print('random message for bar', bar, i) 173 | 174 | with progressbar.MultiBar() as multibar: 175 | for i in range(bars): 176 | # Get a progressbar 177 | bar = multibar[f'Thread label here {i}'] 178 | bar.max_error = False 179 | # Create a thread and pass the progressbar 180 | # Print never, sometimes and always 181 | threading.Thread(target=print_sometimes, args=(bar, 0)).start() 182 | threading.Thread(target=print_sometimes, args=(bar, 0.5)).start() 183 | threading.Thread(target=print_sometimes, args=(bar, 1)).start() 184 | 185 | for i in range(5): 186 | multibar.print(f'{i}', flush=False) 187 | 188 | multibar.update(force=True, flush=False) 189 | multibar.update(force=True, flush=True) 190 | 191 | 192 | def test_multibar_no_format() -> None: 193 | with progressbar.MultiBar( 194 | initial_format=None, finished_format=None 195 | ) as multibar: 196 | bar = multibar['a'] 197 | 198 | for i in bar(range(5)): 199 | bar.print(i) 200 | 201 | 202 | def test_multibar_finished() -> None: 203 | multibar = progressbar.MultiBar(initial_format=None, finished_format=None) 204 | bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) 205 | bar2 = multibar['bar2'] 206 | multibar.render(force=True) 207 | multibar.print('Hi') 208 | multibar.render(force=True, flush=False) 209 | 210 | for i in range(6): 211 | bar.update(i) 212 | bar2.update(i) 213 | 214 | multibar.render(force=True) 215 | 216 | 217 | def test_multibar_finished_format() -> None: 218 | multibar = progressbar.MultiBar( 219 | finished_format='Finished {label}', show_finished=True 220 | ) 221 | bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) 222 | bar2 = multibar['bar2'] 223 | multibar.render(force=True) 224 | multibar.print('Hi') 225 | multibar.render(force=True, flush=False) 226 | bar.start() 227 | bar2.start() 228 | multibar.render(force=True) 229 | multibar.print('Hi') 230 | multibar.render(force=True, flush=False) 231 | 232 | for i in range(6): 233 | bar.update(i) 234 | bar2.update(i) 235 | 236 | multibar.render(force=True) 237 | 238 | 239 | def test_multibar_threads() -> None: 240 | multibar = progressbar.MultiBar(finished_format=None, show_finished=True) 241 | bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) 242 | multibar.start() 243 | time.sleep(0.1) 244 | bar.update(3) 245 | time.sleep(0.1) 246 | multibar.join() 247 | bar.finish() 248 | multibar.join() 249 | multibar.render(force=True) 250 | -------------------------------------------------------------------------------- /tests/test_progressbar.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | import time 4 | 5 | import original_examples # type: ignore 6 | import pytest 7 | 8 | import progressbar 9 | 10 | # Import hack to allow for parallel Tox 11 | try: 12 | import examples 13 | except ImportError: 14 | import sys 15 | 16 | _project_dir: str = os.path.dirname(os.path.dirname(__file__)) 17 | sys.path.append(_project_dir) 18 | import examples 19 | 20 | sys.path.remove(_project_dir) 21 | 22 | 23 | def test_examples(monkeypatch) -> None: 24 | for example in examples.examples: 25 | with contextlib.suppress(ValueError): 26 | example() 27 | 28 | 29 | @pytest.mark.filterwarnings('ignore:.*maxval.*:DeprecationWarning') 30 | @pytest.mark.parametrize('example', original_examples.examples) 31 | def test_original_examples(example, monkeypatch) -> None: 32 | monkeypatch.setattr(progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1) 33 | monkeypatch.setattr(time, 'sleep', lambda t: None) 34 | example() 35 | 36 | 37 | @pytest.mark.parametrize('example', examples.examples) 38 | def test_examples_nullbar(monkeypatch, example) -> None: 39 | # Patch progressbar to use null bar instead of regular progress bar 40 | monkeypatch.setattr(progressbar, 'ProgressBar', progressbar.NullBar) 41 | assert progressbar.ProgressBar._MINIMUM_UPDATE_INTERVAL < 0.0001 42 | example() 43 | 44 | 45 | def test_reuse() -> None: 46 | bar = progressbar.ProgressBar() 47 | bar.start() 48 | for i in range(10): 49 | bar.update(i) 50 | bar.finish() 51 | 52 | bar.start(init=True) 53 | for i in range(10): 54 | bar.update(i) 55 | bar.finish() 56 | 57 | bar.start(init=False) 58 | for i in range(10): 59 | bar.update(i) 60 | bar.finish() 61 | 62 | 63 | def test_dirty() -> None: 64 | bar = progressbar.ProgressBar() 65 | bar.start() 66 | assert bar.started() 67 | for i in range(10): 68 | bar.update(i) 69 | bar.finish(dirty=True) 70 | assert bar.finished() 71 | assert bar.started() 72 | 73 | 74 | def test_negative_maximum() -> None: 75 | with ( 76 | pytest.raises(ValueError), 77 | progressbar.ProgressBar(max_value=-1) as progress, 78 | ): 79 | progress.start() 80 | -------------------------------------------------------------------------------- /tests/test_progressbar_command.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | import pytest 4 | 5 | import progressbar.__main__ as main 6 | 7 | 8 | def test_size_to_bytes() -> None: 9 | assert main.size_to_bytes('1') == 1 10 | assert main.size_to_bytes('1k') == 1024 11 | assert main.size_to_bytes('1m') == 1048576 12 | assert main.size_to_bytes('1g') == 1073741824 13 | assert main.size_to_bytes('1p') == 1125899906842624 14 | 15 | assert main.size_to_bytes('1024') == 1024 16 | assert main.size_to_bytes('1024k') == 1048576 17 | assert main.size_to_bytes('1024m') == 1073741824 18 | assert main.size_to_bytes('1024g') == 1099511627776 19 | assert main.size_to_bytes('1024p') == 1152921504606846976 20 | 21 | 22 | def test_filename_to_bytes(tmp_path) -> None: 23 | file = tmp_path / 'test' 24 | file.write_text('test') 25 | assert main.size_to_bytes(f'@{file}') == 4 26 | 27 | with pytest.raises(FileNotFoundError): 28 | main.size_to_bytes(f'@{tmp_path / "nonexistent"}') 29 | 30 | 31 | def test_create_argument_parser() -> None: 32 | parser = main.create_argument_parser() 33 | args = parser.parse_args( 34 | [ 35 | '-p', 36 | '-t', 37 | '-e', 38 | '-r', 39 | '-a', 40 | '-b', 41 | '-8', 42 | '-T', 43 | '-n', 44 | '-q', 45 | 'input', 46 | '-o', 47 | 'output', 48 | ] 49 | ) 50 | assert args.progress is True 51 | assert args.timer is True 52 | assert args.eta is True 53 | assert args.rate is True 54 | assert args.average_rate is True 55 | assert args.bytes is True 56 | assert args.bits is True 57 | assert args.buffer_percent is True 58 | assert args.last_written is None 59 | assert args.format is None 60 | assert args.numeric is True 61 | assert args.quiet is True 62 | assert args.input == ['input'] 63 | assert args.output == 'output' 64 | 65 | 66 | def test_main_binary(capsys) -> None: 67 | # Call the main function with different command line arguments 68 | main.main( 69 | [ 70 | '-p', 71 | '-t', 72 | '-e', 73 | '-r', 74 | '-a', 75 | '-b', 76 | '-8', 77 | '-T', 78 | '-n', 79 | '-q', 80 | __file__, 81 | ] 82 | ) 83 | 84 | captured = capsys.readouterr() 85 | assert 'test_main(capsys):' in captured.out 86 | 87 | 88 | def test_main_lines(capsys) -> None: 89 | # Call the main function with different command line arguments 90 | main.main( 91 | [ 92 | '-p', 93 | '-t', 94 | '-e', 95 | '-r', 96 | '-a', 97 | '-b', 98 | '-8', 99 | '-T', 100 | '-n', 101 | '-q', 102 | '-l', 103 | '-s', 104 | f'@{__file__}', 105 | __file__, 106 | ] 107 | ) 108 | 109 | captured = capsys.readouterr() 110 | assert 'test_main(capsys):' in captured.out 111 | 112 | 113 | class Input(io.StringIO): 114 | buffer: io.BytesIO 115 | 116 | @classmethod 117 | def create(cls, text: str) -> 'Input': 118 | instance = cls(text) 119 | instance.buffer = io.BytesIO(text.encode()) 120 | return instance 121 | 122 | 123 | def test_main_lines_output(monkeypatch, tmp_path) -> None: 124 | text = 'my input' 125 | monkeypatch.setattr('sys.stdin', Input.create(text)) 126 | output_filename = tmp_path / 'output' 127 | main.main(['-l', '-o', str(output_filename)]) 128 | 129 | assert output_filename.read_text() == text 130 | 131 | 132 | def test_main_bytes_output(monkeypatch, tmp_path) -> None: 133 | text = 'my input' 134 | 135 | monkeypatch.setattr('sys.stdin', Input.create(text)) 136 | output_filename = tmp_path / 'output' 137 | main.main(['-o', str(output_filename)]) 138 | 139 | assert output_filename.read_text() == f'{text}' 140 | 141 | 142 | def test_missing_input(tmp_path) -> None: 143 | with pytest.raises(SystemExit): 144 | main.main([str(tmp_path / 'output')]) 145 | -------------------------------------------------------------------------------- /tests/test_samples.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime, timedelta 3 | 4 | from python_utils.containers import SliceableDeque 5 | 6 | import progressbar 7 | from progressbar import widgets 8 | 9 | 10 | def test_numeric_samples() -> None: 11 | samples = 5 12 | samples_widget = widgets.SamplesMixin(samples=samples) 13 | bar = progressbar.ProgressBar(widgets=[samples_widget]) 14 | 15 | # Force updates in all cases 16 | samples_widget.INTERVAL = timedelta(0) 17 | 18 | start = datetime(2000, 1, 1) 19 | 20 | bar.value = 1 21 | bar.last_update_time = start + timedelta(seconds=bar.value) 22 | assert samples_widget(bar, None, True) == (None, None) 23 | 24 | for i in range(2, 6): 25 | bar.value = i 26 | bar.last_update_time = start + timedelta(seconds=i) 27 | assert samples_widget(bar, None, True) == (timedelta(0, i - 1), i - 1) 28 | 29 | bar.value = 8 30 | bar.last_update_time = start + timedelta(seconds=bar.value) 31 | assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) 32 | 33 | bar.value = 10 34 | bar.last_update_time = start + timedelta(seconds=bar.value) 35 | assert samples_widget(bar, None, True) == (timedelta(0, 7), 7) 36 | 37 | bar.value = 20 38 | bar.last_update_time = start + timedelta(seconds=bar.value) 39 | assert samples_widget(bar, None, True) == (timedelta(0, 16), 16) 40 | 41 | assert samples_widget(bar, None)[1] == SliceableDeque( 42 | [4, 5, 8, 10, 20], 43 | ) 44 | 45 | 46 | def test_timedelta_samples() -> None: 47 | samples = timedelta(seconds=5) 48 | samples_widget = widgets.SamplesMixin(samples=samples) 49 | bar = progressbar.ProgressBar(widgets=[samples_widget]) 50 | 51 | # Force updates in all cases 52 | samples_widget.INTERVAL = timedelta(0) 53 | 54 | start = datetime(2000, 1, 1) 55 | 56 | bar.value = 1 57 | bar.last_update_time = start + timedelta(seconds=bar.value) 58 | assert samples_widget(bar, None, True) == (None, None) 59 | 60 | for i in range(2, 6): 61 | time.sleep(1) 62 | bar.value = i 63 | bar.last_update_time = start + timedelta(seconds=i) 64 | assert samples_widget(bar, None, True) == (timedelta(0, i - 1), i - 1) 65 | 66 | bar.value = 8 67 | bar.last_update_time = start + timedelta(seconds=bar.value) 68 | assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) 69 | 70 | bar.last_update_time = start + timedelta(seconds=bar.value) 71 | bar.value = 8 72 | assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) 73 | 74 | bar.value = 10 75 | bar.last_update_time = start + timedelta(seconds=bar.value) 76 | assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) 77 | 78 | bar.value = 20 79 | bar.last_update_time = start + timedelta(seconds=bar.value) 80 | assert samples_widget(bar, None, True) == (timedelta(0, 10), 10) 81 | 82 | assert samples_widget(bar, None)[1] == [10, 20] 83 | 84 | 85 | def test_timedelta_no_update() -> None: 86 | samples = timedelta(seconds=0.1) 87 | samples_widget = widgets.SamplesMixin(samples=samples) 88 | bar = progressbar.ProgressBar(widgets=[samples_widget]) 89 | bar.update() 90 | 91 | assert samples_widget(bar, None, True) == (None, None) 92 | assert samples_widget(bar, None, False)[1] == [0] 93 | assert samples_widget(bar, None, True) == (None, None) 94 | assert samples_widget(bar, None, False)[1] == [0] 95 | 96 | time.sleep(1) 97 | assert samples_widget(bar, None, True) == (None, None) 98 | assert samples_widget(bar, None, False)[1] == [0] 99 | 100 | bar.update(1) 101 | assert samples_widget(bar, None, True) == (timedelta(0, 1), 1) 102 | assert samples_widget(bar, None, False)[1] == [0, 1] 103 | 104 | time.sleep(1) 105 | bar.update(2) 106 | assert samples_widget(bar, None, True) == (timedelta(0, 1), 1) 107 | assert samples_widget(bar, None, False)[1] == [1, 2] 108 | 109 | time.sleep(0.01) 110 | bar.update(3) 111 | assert samples_widget(bar, None, True) == (timedelta(0, 1), 1) 112 | assert samples_widget(bar, None, False)[1] == [1, 2] 113 | -------------------------------------------------------------------------------- /tests/test_speed.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import progressbar 4 | 5 | 6 | @pytest.mark.parametrize( 7 | 'total_seconds_elapsed,value,expected', 8 | [ 9 | (1, 0, ' 0.0 s/B'), 10 | (1, 0.01, '100.0 s/B'), 11 | (1, 0.1, ' 0.1 B/s'), 12 | (1, 1, ' 1.0 B/s'), 13 | (1, 2**10 - 1, '1023.0 B/s'), 14 | (1, 2**10 + 0, ' 1.0 KiB/s'), 15 | (1, 2**20, ' 1.0 MiB/s'), 16 | (1, 2**30, ' 1.0 GiB/s'), 17 | (1, 2**40, ' 1.0 TiB/s'), 18 | (1, 2**50, ' 1.0 PiB/s'), 19 | (1, 2**60, ' 1.0 EiB/s'), 20 | (1, 2**70, ' 1.0 ZiB/s'), 21 | (1, 2**80, ' 1.0 YiB/s'), 22 | (1, 2**90, '1024.0 YiB/s'), 23 | ], 24 | ) 25 | def test_file_transfer_speed(total_seconds_elapsed, value, expected) -> None: 26 | widget = progressbar.FileTransferSpeed() 27 | assert ( 28 | widget( 29 | None, 30 | dict( 31 | total_seconds_elapsed=total_seconds_elapsed, 32 | value=value, 33 | ), 34 | ) 35 | == expected 36 | ) 37 | -------------------------------------------------------------------------------- /tests/test_stream.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import sys 4 | 5 | import pytest 6 | 7 | import progressbar 8 | from progressbar import terminal 9 | 10 | 11 | def test_nowrap() -> None: 12 | # Make sure we definitely unwrap 13 | for _i in range(5): 14 | progressbar.streams.unwrap(stderr=True, stdout=True) 15 | 16 | stdout = sys.stdout 17 | stderr = sys.stderr 18 | 19 | progressbar.streams.wrap() 20 | 21 | assert stdout == sys.stdout 22 | assert stderr == sys.stderr 23 | 24 | progressbar.streams.unwrap() 25 | 26 | assert stdout == sys.stdout 27 | assert stderr == sys.stderr 28 | 29 | # Make sure we definitely unwrap 30 | for _i in range(5): 31 | progressbar.streams.unwrap(stderr=True, stdout=True) 32 | 33 | 34 | def test_wrap() -> None: 35 | # Make sure we definitely unwrap 36 | for _i in range(5): 37 | progressbar.streams.unwrap(stderr=True, stdout=True) 38 | 39 | stdout = sys.stdout 40 | stderr = sys.stderr 41 | 42 | progressbar.streams.wrap(stderr=True, stdout=True) 43 | 44 | assert stdout != sys.stdout 45 | assert stderr != sys.stderr 46 | 47 | # Wrap again 48 | stdout = sys.stdout 49 | stderr = sys.stderr 50 | 51 | progressbar.streams.wrap(stderr=True, stdout=True) 52 | 53 | assert stdout == sys.stdout 54 | assert stderr == sys.stderr 55 | 56 | # Make sure we definitely unwrap 57 | for _i in range(5): 58 | progressbar.streams.unwrap(stderr=True, stdout=True) 59 | 60 | 61 | def test_excepthook() -> None: 62 | progressbar.streams.wrap(stderr=True, stdout=True) 63 | 64 | try: 65 | raise RuntimeError() # noqa: TRY301 66 | except RuntimeError: 67 | progressbar.streams.excepthook(*sys.exc_info()) 68 | 69 | progressbar.streams.unwrap_excepthook() 70 | progressbar.streams.unwrap_excepthook() 71 | 72 | 73 | def test_fd_as_io_stream() -> None: 74 | stream = io.StringIO() 75 | with progressbar.ProgressBar(fd=stream) as pb: 76 | for i in range(101): 77 | pb.update(i) 78 | stream.close() 79 | 80 | 81 | def test_no_newlines() -> None: 82 | kwargs = dict( 83 | redirect_stderr=True, 84 | redirect_stdout=True, 85 | line_breaks=False, 86 | is_terminal=True, 87 | ) 88 | 89 | with progressbar.ProgressBar(**kwargs) as bar: 90 | for i in range(5): 91 | bar.update(i) 92 | 93 | for i in range(5, 10): 94 | try: 95 | print('\n\n', file=progressbar.streams.stdout) 96 | print('\n\n', file=progressbar.streams.stderr) 97 | except ValueError: 98 | pass 99 | bar.update(i) 100 | 101 | 102 | @pytest.mark.parametrize('stream', [sys.__stdout__, sys.__stderr__]) 103 | @pytest.mark.skipif(os.name == 'nt', reason='Windows does not support this') 104 | def test_fd_as_standard_streams(stream) -> None: 105 | with progressbar.ProgressBar(fd=stream) as pb: 106 | for i in range(101): 107 | pb.update(i) 108 | 109 | 110 | def test_line_offset_stream_wrapper() -> None: 111 | stream = terminal.LineOffsetStreamWrapper(5, io.StringIO()) 112 | stream.write('Hello World!') 113 | 114 | 115 | def test_last_line_stream_methods() -> None: 116 | stream = terminal.LastLineStream(io.StringIO()) 117 | 118 | # Test write method 119 | stream.write('Hello World!') 120 | assert stream.read() == 'Hello World!' 121 | assert stream.read(5) == 'Hello' 122 | 123 | # Test flush method 124 | stream.flush() 125 | assert stream.line == 'Hello World!' 126 | assert stream.readline() == 'Hello World!' 127 | assert stream.readline(5) == 'Hello' 128 | 129 | # Test truncate method 130 | stream.truncate(5) 131 | assert stream.line == 'Hello' 132 | stream.truncate() 133 | assert stream.line == '' 134 | 135 | # Test seekable/readable 136 | assert not stream.seekable() 137 | assert stream.readable() 138 | 139 | stream.writelines(['a', 'b', 'c']) 140 | assert stream.read() == 'c' 141 | 142 | assert list(stream) == ['c'] 143 | 144 | with stream: 145 | stream.write('Hello World!') 146 | assert stream.read() == 'Hello World!' 147 | assert stream.read(5) == 'Hello' 148 | 149 | # Test close method 150 | stream.close() 151 | -------------------------------------------------------------------------------- /tests/test_terminal.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | import time 4 | from datetime import timedelta 5 | 6 | import progressbar 7 | from progressbar import terminal 8 | 9 | 10 | def test_left_justify() -> None: 11 | """Left justify using the terminal width""" 12 | p = progressbar.ProgressBar( 13 | widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())], 14 | max_value=100, 15 | term_width=20, 16 | left_justify=True, 17 | ) 18 | 19 | assert p.term_width is not None 20 | for i in range(100): 21 | p.update(i) 22 | 23 | 24 | def test_right_justify() -> None: 25 | """Right justify using the terminal width""" 26 | p = progressbar.ProgressBar( 27 | widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())], 28 | max_value=100, 29 | term_width=20, 30 | left_justify=False, 31 | ) 32 | 33 | assert p.term_width is not None 34 | for i in range(100): 35 | p.update(i) 36 | 37 | 38 | def test_auto_width(monkeypatch) -> None: 39 | """Right justify using the terminal width""" 40 | 41 | def ioctl(*args): 42 | return '\xbf\x00\xeb\x00\x00\x00\x00\x00' 43 | 44 | def fake_signal(signal, func): 45 | pass 46 | 47 | try: 48 | import fcntl 49 | 50 | monkeypatch.setattr(fcntl, 'ioctl', ioctl) 51 | monkeypatch.setattr(signal, 'signal', fake_signal) 52 | p = progressbar.ProgressBar( 53 | widgets=[ 54 | progressbar.BouncingBar(marker=progressbar.RotatingMarker()), 55 | ], 56 | max_value=100, 57 | left_justify=True, 58 | term_width=None, 59 | ) 60 | 61 | assert p.term_width is not None 62 | for i in range(100): 63 | p.update(i) 64 | except ImportError: 65 | pass # Skip on Windows 66 | 67 | 68 | def test_fill_right() -> None: 69 | """Right justify using the terminal width""" 70 | p = progressbar.ProgressBar( 71 | widgets=[progressbar.BouncingBar(fill_left=False)], 72 | max_value=100, 73 | term_width=20, 74 | ) 75 | 76 | assert p.term_width is not None 77 | for i in range(100): 78 | p.update(i) 79 | 80 | 81 | def test_fill_left() -> None: 82 | """Right justify using the terminal width""" 83 | p = progressbar.ProgressBar( 84 | widgets=[progressbar.BouncingBar(fill_left=True)], 85 | max_value=100, 86 | term_width=20, 87 | ) 88 | 89 | assert p.term_width is not None 90 | for i in range(100): 91 | p.update(i) 92 | 93 | 94 | def test_no_fill(monkeypatch) -> None: 95 | """Simply bounce within the terminal width""" 96 | bar = progressbar.BouncingBar() 97 | bar.INTERVAL = timedelta(seconds=1) 98 | p = progressbar.ProgressBar( 99 | widgets=[bar], 100 | max_value=progressbar.UnknownLength, 101 | term_width=20, 102 | ) 103 | 104 | assert p.term_width is not None 105 | for i in range(30): 106 | p.update(i, force=True) 107 | # Fake the start time so we can actually emulate a moving progress bar 108 | p.start_time = p.start_time - timedelta(seconds=i) 109 | 110 | 111 | def test_stdout_redirection() -> None: 112 | p = progressbar.ProgressBar( 113 | fd=sys.stdout, 114 | max_value=10, 115 | redirect_stdout=True, 116 | ) 117 | 118 | for i in range(10): 119 | print('', file=sys.stdout) 120 | p.update(i) 121 | 122 | 123 | def test_double_stdout_redirection() -> None: 124 | p = progressbar.ProgressBar(max_value=10, redirect_stdout=True) 125 | p2 = progressbar.ProgressBar(max_value=10, redirect_stdout=True) 126 | 127 | for i in range(10): 128 | print('', file=sys.stdout) 129 | p.update(i) 130 | p2.update(i) 131 | 132 | 133 | def test_stderr_redirection() -> None: 134 | p = progressbar.ProgressBar(max_value=10, redirect_stderr=True) 135 | 136 | for i in range(10): 137 | print('', file=sys.stderr) 138 | p.update(i) 139 | 140 | 141 | def test_stdout_stderr_redirection() -> None: 142 | p = progressbar.ProgressBar( 143 | max_value=10, 144 | redirect_stdout=True, 145 | redirect_stderr=True, 146 | ) 147 | p.start() 148 | 149 | for i in range(10): 150 | time.sleep(0.01) 151 | print('', file=sys.stdout) 152 | print('', file=sys.stderr) 153 | p.update(i) 154 | 155 | p.finish() 156 | 157 | 158 | def test_resize(monkeypatch) -> None: 159 | def ioctl(*args): 160 | return '\xbf\x00\xeb\x00\x00\x00\x00\x00' 161 | 162 | def fake_signal(signal, func): 163 | pass 164 | 165 | try: 166 | import fcntl 167 | 168 | monkeypatch.setattr(fcntl, 'ioctl', ioctl) 169 | monkeypatch.setattr(signal, 'signal', fake_signal) 170 | 171 | p = progressbar.ProgressBar(max_value=10) 172 | p.start() 173 | 174 | for i in range(10): 175 | p.update(i) 176 | p._handle_resize() 177 | 178 | p.finish() 179 | except ImportError: 180 | pass # Skip on Windows 181 | 182 | 183 | def test_base() -> None: 184 | assert str(terminal.CUP) 185 | assert str(terminal.CLEAR_SCREEN_ALL_AND_HISTORY) 186 | 187 | terminal.clear_line(0) 188 | terminal.clear_line(1) 189 | -------------------------------------------------------------------------------- /tests/test_timed.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | import progressbar 5 | 6 | 7 | def test_timer() -> None: 8 | """Testing (Adaptive)ETA when the value doesn't actually change""" 9 | widgets = [ 10 | progressbar.Timer(), 11 | ] 12 | p = progressbar.ProgressBar( 13 | max_value=2, 14 | widgets=widgets, 15 | poll_interval=0.0001, 16 | ) 17 | 18 | p.start() 19 | p.update() 20 | p.update(1) 21 | p._needs_update() 22 | time.sleep(0.001) 23 | p.update(1) 24 | p.finish() 25 | 26 | 27 | def test_eta() -> None: 28 | """Testing (Adaptive)ETA when the value doesn't actually change""" 29 | widgets = [ 30 | progressbar.ETA(), 31 | ] 32 | p = progressbar.ProgressBar( 33 | min_value=0, 34 | max_value=2, 35 | widgets=widgets, 36 | poll_interval=0.0001, 37 | ) 38 | 39 | p.start() 40 | time.sleep(0.001) 41 | p.update(0) 42 | time.sleep(0.001) 43 | p.update(1) 44 | time.sleep(0.001) 45 | p.update(1) 46 | time.sleep(0.001) 47 | p.update(2) 48 | time.sleep(0.001) 49 | p.finish() 50 | time.sleep(0.001) 51 | p.update(2) 52 | 53 | 54 | def test_adaptive_eta() -> None: 55 | """Testing (Adaptive)ETA when the value doesn't actually change""" 56 | widgets = [ 57 | progressbar.AdaptiveETA(), 58 | ] 59 | widgets[0].INTERVAL = datetime.timedelta(microseconds=1) 60 | p = progressbar.ProgressBar( 61 | max_value=2, 62 | samples=2, 63 | widgets=widgets, 64 | poll_interval=0.0001, 65 | ) 66 | 67 | p.start() 68 | for _i in range(20): 69 | p.update(1) 70 | time.sleep(0.001) 71 | p.finish() 72 | 73 | 74 | def test_adaptive_transfer_speed() -> None: 75 | """Testing (Adaptive)ETA when the value doesn't actually change""" 76 | widgets = [ 77 | progressbar.AdaptiveTransferSpeed(), 78 | ] 79 | p = progressbar.ProgressBar( 80 | max_value=2, 81 | widgets=widgets, 82 | poll_interval=0.0001, 83 | ) 84 | 85 | p.start() 86 | p.update(1) 87 | time.sleep(0.001) 88 | p.update(1) 89 | p.finish() 90 | 91 | 92 | def test_etas(monkeypatch) -> None: 93 | """Compare file transfer speed to adaptive transfer speed""" 94 | n = 10 95 | interval = datetime.timedelta(seconds=1) 96 | widgets = [ 97 | progressbar.FileTransferSpeed(), 98 | progressbar.AdaptiveTransferSpeed(samples=n / 2), 99 | ] 100 | 101 | datas = [] 102 | 103 | # Capture the output sent towards the `_speed` method 104 | def calculate_eta(self, value, elapsed): 105 | """Capture the widget output""" 106 | data = dict( 107 | value=value, 108 | elapsed=int(elapsed), 109 | ) 110 | datas.append(data) 111 | return 0, 0 112 | 113 | monkeypatch.setattr(progressbar.FileTransferSpeed, '_speed', calculate_eta) 114 | monkeypatch.setattr( 115 | progressbar.AdaptiveTransferSpeed, 116 | '_speed', 117 | calculate_eta, 118 | ) 119 | 120 | for widget in widgets: 121 | widget.INTERVAL = interval 122 | 123 | p = progressbar.ProgressBar( 124 | max_value=n, 125 | widgets=widgets, 126 | poll_interval=interval, 127 | ) 128 | 129 | # Run the first few samples at a low speed and speed up later so we can 130 | # compare the results from both widgets 131 | for i in range(n): 132 | p.update(i) 133 | if i > n / 2: 134 | time.sleep(1) 135 | else: 136 | time.sleep(10) 137 | p.finish() 138 | 139 | # Due to weird travis issues, the actual testing is disabled for now 140 | # import pprint 141 | # pprint.pprint(datas[::2]) 142 | # pprint.pprint(datas[1::2]) 143 | 144 | # for i, (a, b) in enumerate(zip(datas[::2], datas[1::2])): 145 | # # Because the speed is identical initially, the results should be the 146 | # # same for adaptive and regular transfer speed. Only when the speed 147 | # # changes we should start see a lot of differences between the two 148 | # if i < (n / 2 - 1): 149 | # assert a['elapsed'] == b['elapsed'] 150 | # if i > (n / 2 + 1): 151 | # assert a['elapsed'] > b['elapsed'] 152 | 153 | 154 | def test_non_changing_eta() -> None: 155 | """Testing (Adaptive)ETA when the value doesn't actually change""" 156 | widgets = [ 157 | progressbar.AdaptiveETA(), 158 | progressbar.ETA(), 159 | progressbar.AdaptiveTransferSpeed(), 160 | ] 161 | p = progressbar.ProgressBar( 162 | max_value=2, 163 | widgets=widgets, 164 | poll_interval=0.0001, 165 | ) 166 | 167 | p.start() 168 | p.update(1) 169 | time.sleep(0.001) 170 | p.update(1) 171 | p.finish() 172 | 173 | 174 | def test_eta_not_available(): 175 | """ 176 | When ETA is not available (data coming from a generator), 177 | ETAs should not raise exceptions. 178 | """ 179 | 180 | def gen(): 181 | yield from range(200) 182 | 183 | widgets = [progressbar.AdaptiveETA(), progressbar.ETA()] 184 | 185 | bar = progressbar.ProgressBar(widgets=widgets) 186 | for _i in bar(gen()): 187 | pass 188 | -------------------------------------------------------------------------------- /tests/test_timer.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import pytest 4 | 5 | import progressbar 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'poll_interval,expected', 10 | [ 11 | (1, 1), 12 | (timedelta(seconds=1), 1), 13 | (0.001, 0.001), 14 | (timedelta(microseconds=1000), 0.001), 15 | ], 16 | ) 17 | @pytest.mark.parametrize( 18 | 'parameter', 19 | [ 20 | 'poll_interval', 21 | 'min_poll_interval', 22 | ], 23 | ) 24 | def test_poll_interval(parameter, poll_interval, expected) -> None: 25 | # Test int, float and timedelta intervals 26 | bar = progressbar.ProgressBar(**{parameter: poll_interval}) 27 | assert getattr(bar, parameter) == expected 28 | 29 | 30 | @pytest.mark.parametrize( 31 | 'interval', 32 | [ 33 | 1, 34 | timedelta(seconds=1), 35 | ], 36 | ) 37 | def test_intervals(monkeypatch, interval) -> None: 38 | monkeypatch.setattr( 39 | progressbar.ProgressBar, 40 | '_MINIMUM_UPDATE_INTERVAL', 41 | interval, 42 | ) 43 | bar = progressbar.ProgressBar(max_value=100) 44 | 45 | # Initially there should be no last_update_time 46 | assert bar.last_update_time is None 47 | 48 | # After updating there should be a last_update_time 49 | bar.update(1) 50 | assert bar.last_update_time 51 | 52 | # We should not need an update if the time is nearly the same as before 53 | last_update_time = bar.last_update_time 54 | bar.update(2) 55 | assert bar.last_update_time == last_update_time 56 | 57 | # We should need an update if we're beyond the poll_interval 58 | bar._last_update_time -= 2 59 | bar.update(3) 60 | assert bar.last_update_time != last_update_time 61 | -------------------------------------------------------------------------------- /tests/test_unicode.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import time 4 | 5 | import pytest 6 | from python_utils import converters 7 | 8 | import progressbar 9 | 10 | 11 | @pytest.mark.parametrize( 12 | 'name,markers', 13 | [ 14 | ('line arrows', '←↖↑↗→↘↓↙'), 15 | ('block arrows', '◢◣◤◥'), 16 | ('wheels', '◐◓◑◒'), 17 | ], 18 | ) 19 | @pytest.mark.parametrize('as_unicode', [True, False]) 20 | def test_markers(name, markers: bytes | str, as_unicode) -> None: 21 | if as_unicode: 22 | markers = converters.to_unicode(markers) 23 | else: 24 | markers = converters.to_str(markers) 25 | 26 | widgets = [ 27 | f'{name.capitalize()}: ', 28 | progressbar.AnimatedMarker(markers=markers), 29 | ] 30 | bar = progressbar.ProgressBar(widgets=widgets) 31 | bar._MINIMUM_UPDATE_INTERVAL = 1e-12 32 | for _i in bar(iter(range(24))): 33 | time.sleep(0.001) 34 | -------------------------------------------------------------------------------- /tests/test_unknown_length.py: -------------------------------------------------------------------------------- 1 | import progressbar 2 | 3 | 4 | def test_unknown_length() -> None: 5 | pb = progressbar.ProgressBar( 6 | widgets=[progressbar.AnimatedMarker()], 7 | max_value=progressbar.UnknownLength, 8 | ) 9 | assert pb.max_value is progressbar.UnknownLength 10 | 11 | 12 | def test_unknown_length_default_widgets() -> None: 13 | # The default widgets picked should work without a known max_value 14 | pb = progressbar.ProgressBar(max_value=progressbar.UnknownLength).start() 15 | for i in range(60): 16 | pb.update(i) 17 | pb.finish() 18 | 19 | 20 | def test_unknown_length_at_start() -> None: 21 | # The default widgets should be picked after we call .start() 22 | pb = progressbar.ProgressBar().start(max_value=progressbar.UnknownLength) 23 | for i in range(60): 24 | pb.update(i) 25 | pb.finish() 26 | 27 | pb2 = progressbar.ProgressBar().start(max_value=progressbar.UnknownLength) 28 | for w in pb2.widgets: 29 | print(type(w), repr(w)) 30 | assert any(isinstance(w, progressbar.Bar) for w in pb2.widgets) 31 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | import pytest 4 | 5 | import progressbar 6 | import progressbar.env 7 | 8 | 9 | @pytest.mark.parametrize( 10 | 'value,expected', 11 | [ 12 | (None, None), 13 | ('', None), 14 | ('1', True), 15 | ('y', True), 16 | ('t', True), 17 | ('yes', True), 18 | ('true', True), 19 | ('True', True), 20 | ('0', False), 21 | ('n', False), 22 | ('f', False), 23 | ('no', False), 24 | ('false', False), 25 | ('False', False), 26 | ], 27 | ) 28 | def test_env_flag(value, expected, monkeypatch) -> None: 29 | if value is not None: 30 | monkeypatch.setenv('TEST_ENV', value) 31 | assert progressbar.env.env_flag('TEST_ENV') == expected 32 | 33 | if value: 34 | monkeypatch.setenv('TEST_ENV', value.upper()) 35 | assert progressbar.env.env_flag('TEST_ENV') == expected 36 | 37 | monkeypatch.undo() 38 | 39 | 40 | def test_is_terminal(monkeypatch) -> None: 41 | fd = io.StringIO() 42 | 43 | monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) 44 | monkeypatch.setattr(progressbar.env, 'JUPYTER', False) 45 | 46 | assert progressbar.env.is_terminal(fd) is False 47 | assert progressbar.env.is_terminal(fd, True) is True 48 | assert progressbar.env.is_terminal(fd, False) is False 49 | 50 | monkeypatch.setattr(progressbar.env, 'JUPYTER', True) 51 | assert progressbar.env.is_terminal(fd) is True 52 | 53 | # Sanity check 54 | monkeypatch.setattr(progressbar.env, 'JUPYTER', False) 55 | assert progressbar.env.is_terminal(fd) is False 56 | 57 | monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') 58 | assert progressbar.env.is_terminal(fd) is True 59 | monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') 60 | assert progressbar.env.is_terminal(fd) is False 61 | monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') 62 | 63 | # Sanity check 64 | assert progressbar.env.is_terminal(fd) is False 65 | 66 | 67 | def test_is_ansi_terminal(monkeypatch) -> None: 68 | fd = io.StringIO() 69 | 70 | monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) 71 | monkeypatch.setattr(progressbar.env, 'JUPYTER', False) 72 | 73 | assert not progressbar.env.is_ansi_terminal(fd) 74 | assert progressbar.env.is_ansi_terminal(fd, True) is True 75 | assert progressbar.env.is_ansi_terminal(fd, False) is False 76 | 77 | monkeypatch.setattr(progressbar.env, 'JUPYTER', True) 78 | assert progressbar.env.is_ansi_terminal(fd) is True 79 | monkeypatch.setattr(progressbar.env, 'JUPYTER', False) 80 | 81 | # Sanity check 82 | assert not progressbar.env.is_ansi_terminal(fd) 83 | 84 | monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') 85 | assert not progressbar.env.is_ansi_terminal(fd) 86 | monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') 87 | assert not progressbar.env.is_ansi_terminal(fd) 88 | monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') 89 | 90 | # Sanity check 91 | assert not progressbar.env.is_ansi_terminal(fd) 92 | 93 | # Fake TTY mode for environment testing 94 | fd.isatty = lambda: True 95 | monkeypatch.setenv('TERM', 'xterm') 96 | assert progressbar.env.is_ansi_terminal(fd) is True 97 | monkeypatch.setenv('TERM', 'xterm-256') 98 | assert progressbar.env.is_ansi_terminal(fd) is True 99 | monkeypatch.setenv('TERM', 'xterm-256color') 100 | assert progressbar.env.is_ansi_terminal(fd) is True 101 | monkeypatch.setenv('TERM', 'xterm-24bit') 102 | assert progressbar.env.is_ansi_terminal(fd) is True 103 | monkeypatch.delenv('TERM') 104 | 105 | monkeypatch.setenv('ANSICON', 'true') 106 | assert progressbar.env.is_ansi_terminal(fd) is True 107 | monkeypatch.delenv('ANSICON') 108 | assert not progressbar.env.is_ansi_terminal(fd) 109 | 110 | def raise_error(): 111 | raise RuntimeError('test') 112 | 113 | fd.isatty = raise_error 114 | assert not progressbar.env.is_ansi_terminal(fd) 115 | -------------------------------------------------------------------------------- /tests/test_widgets.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import time 4 | 5 | import pytest 6 | 7 | import progressbar 8 | 9 | max_values: list[None | type[progressbar.base.UnknownLength] | int] = [ 10 | None, 11 | 10, 12 | progressbar.UnknownLength, 13 | ] 14 | 15 | 16 | def test_create_wrapper() -> None: 17 | with pytest.raises(AssertionError): 18 | progressbar.widgets.create_wrapper('ab') 19 | 20 | with pytest.raises(RuntimeError): 21 | progressbar.widgets.create_wrapper(123) 22 | 23 | 24 | def test_widgets_small_values() -> None: 25 | widgets = [ 26 | 'Test: ', 27 | progressbar.Percentage(), 28 | ' ', 29 | progressbar.Bar(marker=progressbar.RotatingMarker()), 30 | ' ', 31 | progressbar.ETA(), 32 | ' ', 33 | progressbar.AbsoluteETA(), 34 | ' ', 35 | progressbar.FileTransferSpeed(), 36 | ] 37 | p = progressbar.ProgressBar(widgets=widgets, max_value=10).start() 38 | p.update(0) 39 | for i in range(10): 40 | time.sleep(1) 41 | p.update(i + 1) 42 | p.finish() 43 | 44 | 45 | @pytest.mark.parametrize('max_value', [10**6, 10**8]) 46 | def test_widgets_large_values(max_value) -> None: 47 | widgets = [ 48 | 'Test: ', 49 | progressbar.Percentage(), 50 | ' ', 51 | progressbar.Bar(marker=progressbar.RotatingMarker()), 52 | ' ', 53 | progressbar.ETA(), 54 | ' ', 55 | progressbar.AbsoluteETA(), 56 | ' ', 57 | progressbar.FileTransferSpeed(), 58 | ] 59 | p = progressbar.ProgressBar(widgets=widgets, max_value=max_value).start() 60 | for i in range(0, 10**6, 10**4): 61 | time.sleep(1) 62 | p.update(i + 1) 63 | p.finish() 64 | 65 | 66 | def test_format_widget() -> None: 67 | widgets = [ 68 | progressbar.FormatLabel(f'%({mapping})r') 69 | for mapping in progressbar.FormatLabel.mapping 70 | ] 71 | p = progressbar.ProgressBar(widgets=widgets) 72 | for _ in p(range(10)): 73 | time.sleep(1) 74 | 75 | 76 | @pytest.mark.parametrize('max_value', [None, 10]) 77 | def test_all_widgets_small_values(max_value) -> None: 78 | widgets = [ 79 | progressbar.Timer(), 80 | progressbar.ETA(), 81 | progressbar.AdaptiveETA(), 82 | progressbar.AbsoluteETA(), 83 | progressbar.DataSize(), 84 | progressbar.FileTransferSpeed(), 85 | progressbar.AdaptiveTransferSpeed(), 86 | progressbar.AnimatedMarker(), 87 | progressbar.Counter(), 88 | progressbar.Percentage(), 89 | progressbar.FormatLabel('%(value)d'), 90 | progressbar.SimpleProgress(), 91 | progressbar.Bar(), 92 | progressbar.ReverseBar(), 93 | progressbar.BouncingBar(), 94 | progressbar.CurrentTime(), 95 | progressbar.CurrentTime(microseconds=False), 96 | progressbar.CurrentTime(microseconds=True), 97 | ] 98 | p = progressbar.ProgressBar(widgets=widgets, max_value=max_value) 99 | for i in range(10): 100 | time.sleep(1) 101 | p.update(i + 1) 102 | p.finish() 103 | 104 | 105 | @pytest.mark.parametrize('max_value', [10**6, 10**7]) 106 | def test_all_widgets_large_values(max_value) -> None: 107 | widgets = [ 108 | progressbar.Timer(), 109 | progressbar.ETA(), 110 | progressbar.AdaptiveETA(), 111 | progressbar.AbsoluteETA(), 112 | progressbar.DataSize(), 113 | progressbar.FileTransferSpeed(), 114 | progressbar.AdaptiveTransferSpeed(), 115 | progressbar.AnimatedMarker(), 116 | progressbar.Counter(), 117 | progressbar.Percentage(), 118 | progressbar.FormatLabel('%(value)d/%(max_value)d'), 119 | progressbar.SimpleProgress(), 120 | progressbar.Bar(fill=lambda progress, data, width: '#'), 121 | progressbar.ReverseBar(), 122 | progressbar.BouncingBar(), 123 | progressbar.FormatCustomText('Custom %(text)s', dict(text='text')), 124 | ] 125 | p = progressbar.ProgressBar(widgets=widgets, max_value=max_value) 126 | p.update() 127 | time.sleep(1) 128 | p.update() 129 | 130 | for i in range(0, 10**6, 10**4): 131 | time.sleep(1) 132 | p.update(i) 133 | 134 | 135 | @pytest.mark.parametrize('min_width', [None, 1, 2, 80, 120]) 136 | @pytest.mark.parametrize('term_width', [1, 2, 80, 120]) 137 | def test_all_widgets_min_width(min_width, term_width) -> None: 138 | widgets = [ 139 | progressbar.Timer(min_width=min_width), 140 | progressbar.ETA(min_width=min_width), 141 | progressbar.AdaptiveETA(min_width=min_width), 142 | progressbar.AbsoluteETA(min_width=min_width), 143 | progressbar.DataSize(min_width=min_width), 144 | progressbar.FileTransferSpeed(min_width=min_width), 145 | progressbar.AdaptiveTransferSpeed(min_width=min_width), 146 | progressbar.AnimatedMarker(min_width=min_width), 147 | progressbar.Counter(min_width=min_width), 148 | progressbar.Percentage(min_width=min_width), 149 | progressbar.FormatLabel('%(value)d', min_width=min_width), 150 | progressbar.SimpleProgress(min_width=min_width), 151 | progressbar.Bar(min_width=min_width), 152 | progressbar.ReverseBar(min_width=min_width), 153 | progressbar.BouncingBar(min_width=min_width), 154 | progressbar.FormatCustomText( 155 | 'Custom %(text)s', 156 | dict(text='text'), 157 | min_width=min_width, 158 | ), 159 | progressbar.DynamicMessage('custom', min_width=min_width), 160 | progressbar.CurrentTime(min_width=min_width), 161 | ] 162 | p = progressbar.ProgressBar(widgets=widgets, term_width=term_width) 163 | p.update(0) 164 | p.update() 165 | for widget in p._format_widgets(): 166 | if min_width and min_width > term_width: 167 | assert widget == '' 168 | else: 169 | assert widget != '' 170 | 171 | 172 | @pytest.mark.parametrize('max_width', [None, 1, 2, 80, 120]) 173 | @pytest.mark.parametrize('term_width', [1, 2, 80, 120]) 174 | def test_all_widgets_max_width(max_width, term_width) -> None: 175 | widgets = [ 176 | progressbar.Timer(max_width=max_width), 177 | progressbar.ETA(max_width=max_width), 178 | progressbar.AdaptiveETA(max_width=max_width), 179 | progressbar.AbsoluteETA(max_width=max_width), 180 | progressbar.DataSize(max_width=max_width), 181 | progressbar.FileTransferSpeed(max_width=max_width), 182 | progressbar.AdaptiveTransferSpeed(max_width=max_width), 183 | progressbar.AnimatedMarker(max_width=max_width), 184 | progressbar.Counter(max_width=max_width), 185 | progressbar.Percentage(max_width=max_width), 186 | progressbar.FormatLabel('%(value)d', max_width=max_width), 187 | progressbar.SimpleProgress(max_width=max_width), 188 | progressbar.Bar(max_width=max_width), 189 | progressbar.ReverseBar(max_width=max_width), 190 | progressbar.BouncingBar(max_width=max_width), 191 | progressbar.FormatCustomText( 192 | 'Custom %(text)s', 193 | dict(text='text'), 194 | max_width=max_width, 195 | ), 196 | progressbar.DynamicMessage('custom', max_width=max_width), 197 | progressbar.CurrentTime(max_width=max_width), 198 | ] 199 | p = progressbar.ProgressBar(widgets=widgets, term_width=term_width) 200 | p.update(0) 201 | p.update() 202 | for widget in p._format_widgets(): 203 | if max_width and max_width < term_width: 204 | assert widget == '' 205 | else: 206 | assert widget != '' 207 | -------------------------------------------------------------------------------- /tests/test_windows.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | import pytest 6 | 7 | if os.name == 'nt': 8 | import win32console # "pip install pypiwin32" to get this 9 | else: 10 | pytest.skip('skipping windows-only tests', allow_module_level=True) 11 | 12 | import progressbar 13 | 14 | pytest_plugins = 'pytester' 15 | _WIDGETS = [ 16 | progressbar.Percentage(), 17 | ' ', 18 | progressbar.Bar(), 19 | ' ', 20 | progressbar.FileTransferSpeed(), 21 | ' ', 22 | progressbar.ETA(), 23 | ] 24 | _MB: int = 1024 * 1024 25 | 26 | 27 | # --------------------------------------------------------------------------- 28 | def scrape_console(line_count): 29 | pcsb = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) 30 | csbi = pcsb.GetConsoleScreenBufferInfo() 31 | col_max = csbi['Size'].X 32 | row_max = csbi['CursorPosition'].Y 33 | 34 | line_count = min(line_count, row_max) 35 | lines = [] 36 | for row in range(line_count): 37 | pct = win32console.PyCOORDType(0, row + row_max - line_count) 38 | line = pcsb.ReadConsoleOutputCharacter(col_max, pct) 39 | lines.append(line.rstrip()) 40 | return lines 41 | 42 | 43 | # --------------------------------------------------------------------------- 44 | def runprogress() -> int: 45 | print('***BEGIN***') 46 | b = progressbar.ProgressBar( 47 | widgets=['example.m4v: ', *_WIDGETS], 48 | max_value=10 * _MB, 49 | ) 50 | for i in range(10): 51 | b.update((i + 1) * _MB) 52 | time.sleep(0.25) 53 | b.finish() 54 | print('***END***') 55 | return 0 56 | 57 | 58 | # --------------------------------------------------------------------------- 59 | def find(lines, x): 60 | try: 61 | return lines.index(x) 62 | except ValueError: 63 | return -sys.maxsize 64 | 65 | 66 | # --------------------------------------------------------------------------- 67 | def test_windows(testdir: pytest.Testdir) -> None: 68 | testdir.run( 69 | sys.executable, '-c', 'import progressbar; print(progressbar.__file__)' 70 | ) 71 | 72 | 73 | def main() -> int: 74 | runprogress() 75 | 76 | scraped_lines = scrape_console(100) 77 | # reverse lines so we find the LAST instances of output. 78 | scraped_lines.reverse() 79 | index_begin = find(scraped_lines, '***BEGIN***') 80 | index_end = find(scraped_lines, '***END***') 81 | 82 | if index_end + 2 != index_begin: 83 | print('ERROR: Unexpected multi-line output from progressbar') 84 | print(f'{index_begin=} {index_end=}') 85 | return 1 86 | return 0 87 | 88 | 89 | if __name__ == '__main__': 90 | main() 91 | -------------------------------------------------------------------------------- /tests/test_with.py: -------------------------------------------------------------------------------- 1 | import progressbar 2 | 3 | 4 | def test_with() -> None: 5 | with progressbar.ProgressBar(max_value=10) as p: 6 | for i in range(10): 7 | p.update(i) 8 | 9 | 10 | def test_with_stdout_redirection() -> None: 11 | with progressbar.ProgressBar(max_value=10, redirect_stdout=True) as p: 12 | for i in range(10): 13 | p.update(i) 14 | 15 | 16 | def test_with_extra_start() -> None: 17 | with progressbar.ProgressBar(max_value=10) as p: 18 | p.start() 19 | p.start() 20 | -------------------------------------------------------------------------------- /tests/test_wrappingio.py: -------------------------------------------------------------------------------- 1 | import io 2 | import sys 3 | 4 | import pytest 5 | 6 | from progressbar import utils 7 | 8 | 9 | def test_wrappingio() -> None: 10 | # Test the wrapping of our version of sys.stdout` ` q 11 | fd = utils.WrappingIO(sys.stdout) 12 | assert fd.fileno() 13 | assert not fd.isatty() 14 | 15 | assert not fd.read() 16 | assert not fd.readline() 17 | assert not fd.readlines() 18 | assert fd.readable() 19 | 20 | assert not fd.seek(0) 21 | assert fd.seekable() 22 | assert not fd.tell() 23 | 24 | assert not fd.truncate() 25 | assert fd.writable() 26 | assert fd.write('test') 27 | assert not fd.writelines(['test']) 28 | 29 | with pytest.raises(StopIteration): 30 | next(fd) 31 | with pytest.raises(StopIteration): 32 | next(iter(fd)) 33 | 34 | 35 | def test_wrapping_stringio() -> None: 36 | # Test the wrapping of our version of sys.stdout` ` q 37 | string_io = io.StringIO() 38 | fd = utils.WrappingIO(string_io) 39 | with fd: 40 | with pytest.raises(io.UnsupportedOperation): 41 | fd.fileno() 42 | 43 | assert not fd.isatty() 44 | 45 | assert not fd.read() 46 | assert not fd.readline() 47 | assert not fd.readlines() 48 | assert fd.readable() 49 | 50 | assert not fd.seek(0) 51 | assert fd.seekable() 52 | assert not fd.tell() 53 | 54 | assert not fd.truncate() 55 | assert fd.writable() 56 | assert fd.write('test') 57 | assert not fd.writelines(['test']) 58 | 59 | with pytest.raises(StopIteration): 60 | next(fd) 61 | with pytest.raises(StopIteration): 62 | next(iter(fd)) 63 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py38 4 | py39 5 | py310 6 | py311 7 | py312 8 | docs 9 | black 10 | ruff 11 | ; mypy 12 | ; codespell 13 | skip_missing_interpreters = True 14 | 15 | [testenv] 16 | deps = 17 | -r{toxinidir}/tests/requirements.txt 18 | pyright 19 | commands = 20 | pyright 21 | py.test --basetemp="{envtmpdir}" --confcutdir=.. {posargs} 22 | skip_install = true 23 | 24 | [testenv:mypy] 25 | changedir = 26 | basepython = python3 27 | deps = mypy 28 | commands = mypy {toxinidir}/progressbar 29 | 30 | [testenv:black] 31 | basepython = python3 32 | deps = black 33 | commands = black --skip-string-normalization --line-length 79 {toxinidir}/progressbar 34 | 35 | [testenv:docs] 36 | changedir = 37 | basepython = python3 38 | deps = -r{toxinidir}/docs/requirements.txt 39 | allowlist_externals = 40 | rm 41 | mkdir 42 | whitelist_externals = 43 | rm 44 | cd 45 | mkdir 46 | commands = 47 | rm -f docs/modules.rst 48 | mkdir -p docs/_static 49 | sphinx-apidoc -e -o docs/ progressbar */os_specific/* 50 | rm -f docs/modules.rst 51 | sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html {posargs} 52 | 53 | [testenv:ruff] 54 | commands = 55 | ruff check 56 | ruff format 57 | deps = ruff 58 | skip_install = true 59 | 60 | [testenv:codespell] 61 | changedir = {toxinidir} 62 | commands = codespell . 63 | deps = codespell 64 | skip_install = true 65 | command = codespell 66 | --------------------------------------------------------------------------------