From 418bef55a7f7bebffa889a06230a8df936620200 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 21 Aug 2023 03:09:00 +0200 Subject: [PATCH 001/118] Added stale action --- .github/workflows/stale.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..3169ca3b --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,20 @@ +name: Close stale issues and pull requests + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' # Run every day at midnight + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + days-before-stale: 30 + exempt-issue-labels: | + in-progress + help-wanted + pinned + security + enhancement \ No newline at end of file From ae146fd15f9286a6edb6d3fe661f92aacd32e6c6 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 9 Sep 2020 15:25:18 +0200 Subject: [PATCH 002/118] Incrementing version to v3.53.1 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 2dd04577..339835e2 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '3.53.0' +__version__ = '3.53.1' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From c60f241dfa98898d4a66cbfd1e25c2986ad5f293 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 15 Mar 2021 01:40:40 +0100 Subject: [PATCH 003/118] Update readme to fix #245 --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index cdda4301..eb155c54 100644 --- a/README.rst +++ b/README.rst @@ -37,6 +37,8 @@ Introduction A text progress bar is typically used to display the progress of a long running operation, providing a visual cue that processing is underway. +The progressbar is based on the old Python progressbar package that was published on the now defunct Google Code. Since that project was completely abandoned by its developer and the developer did not respond to email, I decided to fork the package. This package is still backwards compatible with the original progressbar package so you can safely use it as a drop-in replacement for existing project. + The ProgressBar class manages the current progress, and the format of the line is given by a number of widgets. A widget is an object that may display differently depending on the state of the progress bar. There are many types From 76492c7e0cb42ce74787c98d8fca9c46ac28f571 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 1 Aug 2021 18:40:24 +0200 Subject: [PATCH 004/118] switch to flake8 only --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index b23190a7..d5462630 100644 --- a/setup.py +++ b/setup.py @@ -45,8 +45,6 @@ def run_tests(self): 'flake8>=3.7.7', 'pytest>=4.6.9', 'pytest-cov>=2.6.1', - 'pytest-flakes>=4.0.0', - 'pytest-pep8>=1.0.6', 'freezegun>=0.3.11', 'sphinx>=1.8.5', ] From ca93ec72e8bf0cd5ba1505329151959bade0c3d6 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 9 Sep 2021 03:11:58 +0200 Subject: [PATCH 005/118] applied isatty fix thanks to @piotrbartman to fix #254 --- progressbar/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/progressbar/utils.py b/progressbar/utils.py index 7ef1790a..f1e2dd42 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -191,6 +191,9 @@ def __init__(self, target, capturing=False, listeners=set()): self.listeners = listeners self.needs_clear = False + def isatty(self): + return self.target.isatty() + def write(self, value): if self.capturing: self.buffer.write(value) From 39cb9e42e795234b57739893d4008af88e39ae0f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 9 Sep 2021 03:12:09 +0200 Subject: [PATCH 006/118] Incrementing version to v3.53.2 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 339835e2..4294f43e 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '3.53.1' +__version__ = '3.53.2' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 5abbe4971d9154ad9849d34ae5814e7ddefc69c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 13 Sep 2021 21:20:49 +0200 Subject: [PATCH 007/118] Stop using deprecated distutils.util Using distutils emits a DeprecationWarning with python 3.10 at runtime. (And the module is slated for removal in python 3.12.) The list of accepted strings is taken from https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool --- progressbar/utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index f1e2dd42..a9dc6f54 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -1,5 +1,4 @@ from __future__ import absolute_import -import distutils.util import atexit import io import os @@ -173,13 +172,15 @@ def env_flag(name, default=None): Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, on/off, and returns it as a boolean - If the environt variable is not defined, or has an unknown value, returns - `default` + If the environment variable is not defined, or has an unknown value, + returns `default` ''' - try: - return bool(distutils.util.strtobool(os.environ.get(name, ''))) - except ValueError: - return default + v = os.getenv(name) + if v and v.lower() in ('y', 'yes', 't', 'true', 'on', '1'): + return True + if v and v.lower() in ('n', 'no', 'f', 'false', 'off', '0'): + return False + return default class WrappingIO: From 110b5fb63ec19bf22f26cce62e2023ab54cf3094 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 15 Sep 2021 00:42:42 +0200 Subject: [PATCH 008/118] Incrementing version to v3.53.3 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 4294f43e..6832fe2a 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '3.53.2' +__version__ = '3.53.3' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From f6c14050ef6446e2cb1b376ccd293be36520476b Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 4 Oct 2021 13:02:02 -0700 Subject: [PATCH 009/118] Allow customizing the N/A% string in Percentage. Move the selection of whether to use N/A% to a separate method. --- progressbar/widgets.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index f514f7dd..02ef4824 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -628,17 +628,20 @@ def __call__(self, progress, data, format=None): class Percentage(FormatWidgetMixin, WidgetBase): '''Displays the current percentage as a number with a percent sign.''' - def __init__(self, format='%(percentage)3d%%', **kwargs): + def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): + self.na = na FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) def __call__(self, progress, data, format=None): + return FormatWidgetMixin.__call__(self, progress, data, self.get_format(progress, data)) + + def get_format(self, progress, data): # If percentage is not available, display N/A% if 'percentage' in data and not data['percentage']: - return FormatWidgetMixin.__call__(self, progress, data, - format='N/A%%') + return self.na - return FormatWidgetMixin.__call__(self, progress, data) + return self.format class SimpleProgress(FormatWidgetMixin, WidgetBase): From 033e8c17b94b19e059bd0543ce93126379148eca Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 4 Oct 2021 13:03:46 -0700 Subject: [PATCH 010/118] Show 0% instead of N/A% when percentage is 0. --- progressbar/widgets.py | 10 ++++++---- tests/test_monitor_progress.py | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 02ef4824..33e81272 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -634,14 +634,16 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def __call__(self, progress, data, format=None): - return FormatWidgetMixin.__call__(self, progress, data, self.get_format(progress, data)) + return FormatWidgetMixin.__call__(self, progress, data, + self.get_format(progress, data)) - def get_format(self, progress, data): + def get_format(self, progress, data, format=None): # If percentage is not available, display N/A% - if 'percentage' in data and not data['percentage']: + if ('percentage' in data and not data['percentage'] and + data['percentage'] != 0): return self.na - return self.format + return format or self.format class SimpleProgress(FormatWidgetMixin, WidgetBase): diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index d55c86ab..097261c6 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -65,7 +65,7 @@ def test_list_example(testdir): if l.strip()] pprint.pprint(result.stderr.lines, width=70) result.stderr.fnmatch_lines([ - 'N/A% (0 of 9) | | Elapsed Time: ?:00:00 ETA: --:--:--', + ' 0% (0 of 9) | | Elapsed Time: ?:00:00 ETA: --:--:--', ' 11% (1 of 9) |# | Elapsed Time: ?:00:01 ETA: ?:00:08', ' 22% (2 of 9) |## | Elapsed Time: ?:00:02 ETA: ?:00:07', ' 33% (3 of 9) |#### | Elapsed Time: ?:00:03 ETA: ?:00:06', @@ -117,7 +117,7 @@ def test_rapid_updates(testdir): result.stderr.lines = [l for l in result.stderr.lines if l.strip()] pprint.pprint(result.stderr.lines, width=70) result.stderr.fnmatch_lines([ - 'N/A% (0 of 10) | | Elapsed Time: ?:00:00 ETA: --:--:--', + ' 0% (0 of 10) | | Elapsed Time: ?:00:00 ETA: --:--:--', ' 10% (1 of 10) | | Elapsed Time: ?:00:01 ETA: ?:00:09', ' 20% (2 of 10) |# | Elapsed Time: ?:00:02 ETA: ?:00:08', ' 30% (3 of 10) |# | Elapsed Time: ?:00:03 ETA: ?:00:07', @@ -139,7 +139,7 @@ def test_non_timed(testdir): result.stderr.lines = [l for l in result.stderr.lines if l.strip()] pprint.pprint(result.stderr.lines, width=70) result.stderr.fnmatch_lines([ - 'N/A%| |', + ' 0%| |', ' 20%|########## |', ' 40%|##################### |', ' 60%|################################ |', @@ -156,7 +156,7 @@ def test_line_breaks(testdir): ))) pprint.pprint(result.stderr.str(), width=70) assert result.stderr.str() == u'\n'.join(( - u'N/A%| |', + u' 0%| |', u' 20%|########## |', u' 40%|##################### |', u' 60%|################################ |', @@ -175,7 +175,7 @@ def test_no_line_breaks(testdir): pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ u'', - u'N/A%| |', + u' 0%| |', u' 20%|########## |', u' 40%|##################### |', u' 60%|################################ |', From f9e515233850cc686eff93a63b008ea425b41a38 Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 4 Oct 2021 14:19:11 -0700 Subject: [PATCH 011/118] Add a bar that shows a label in the center. Include a specialization with a percentage in the center. --- progressbar/__init__.py | 4 ++++ progressbar/widgets.py | 31 +++++++++++++++++++++++++++++++ tests/test_monitor_progress.py | 20 ++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/progressbar/__init__.py b/progressbar/__init__.py index b108c419..ef504514 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -26,6 +26,8 @@ VariableMixin, MultiRangeBar, MultiProgressBar, + FormatLabelBar, + PercentageLabelBar, Variable, DynamicMessage, FormatCustomText, @@ -72,6 +74,8 @@ 'VariableMixin', 'MultiRangeBar', 'MultiProgressBar', + 'FormatLabelBar', + 'PercentageLabelBar', 'Variable', 'DynamicMessage', 'FormatCustomText', diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 33e81272..65d1c99b 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -909,6 +909,37 @@ def get_values(self, progress, data): return ranges +class FormatLabelBar(FormatLabel, Bar): + '''A bar which has a formatted label in the center.''' + def __init__(self, format, **kwargs): + FormatLabel.__init__(self, format, **kwargs) + Bar.__init__(self, **kwargs) + + def __call__(self, progress, data, width, format=None): + center = FormatLabel.__call__(self, progress, data, format=format) + bar = Bar.__call__(self, progress, data, width) + + # Aligns the center of the label to the center of the bar + center_len = progress.custom_len(center) + center_left = int((width - center_len) / 2) + center_right = center_left + center_len + return bar[:center_left] + center + bar[center_right:] + + +class PercentageLabelBar(Percentage, FormatLabelBar): + '''A bar which displays the current percentage in the center.''' + # %3d adds an extra space that makes it look off-center + # %2d keeps the label somewhat consistently in-place + def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): + Percentage.__init__(self, format, na=na, **kwargs) + FormatLabelBar.__init__(self, format, **kwargs) + + def __call__(self, progress, data, width, format=None): + return FormatLabelBar.__call__( + self, progress, data, width, + format=Percentage.get_format(self, progress, data, format=None)) + + class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index 097261c6..36ce89c6 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -186,6 +186,26 @@ def test_no_line_breaks(testdir): ] +def test_percentage_label_bar(testdir): + result = testdir.runpython(testdir.makepyfile(_create_script( + widgets='[progressbar.PercentageLabelBar()]', + line_breaks=False, + items=list(range(5)), + ))) + pprint.pprint(result.stderr.lines, width=70) + assert result.stderr.lines == [ + u'', + u'| 0% |', + u'|########### 20% |', + u'|####################### 40% |', + u'|###########################60%#### |', + u'|###########################80%################ |', + u'|###########################100%###########################|', + u'', + u'|###########################100%###########################|' + ] + + def test_colors(testdir): kwargs = dict( items=range(1), From 470483bafb938429f602d71425913b71f9f9daee Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 4 Oct 2021 14:34:54 -0700 Subject: [PATCH 012/118] Add new widgets to README and examples. --- README.rst | 2 ++ examples.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/README.rst b/README.rst index eb155c54..f91d97d5 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,9 @@ of widgets: - `FileTransferSpeed `_ - `FormatCustomText `_ - `FormatLabel `_ + - `FormatLabelBar `_ - `Percentage `_ + - `PercentageLabelBar `_ - `ReverseBar `_ - `RotatingMarker `_ - `SimpleProgress `_ diff --git a/examples.py b/examples.py index 379cb11c..a3300440 100644 --- a/examples.py +++ b/examples.py @@ -177,6 +177,17 @@ def multi_progress_bar_example(left=True): time.sleep(0.02) +@example +def percentage_label_bar_example(): + widgets = [progressbar.PercentageLabelBar()] + bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start() + for i in range(10): + # do something + time.sleep(0.1) + bar.update(i + 1) + bar.finish() + + @example def file_transfer_example(): widgets = [ From 0dbdb8581c76d2c9004a09fbbfe925c9ec9a7fec Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 11 Oct 2021 00:02:34 +0200 Subject: [PATCH 013/118] added get_format method to FormatWidgetMixin --- progressbar/base.py | 4 ++++ progressbar/widgets.py | 25 ++++++++++--------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/progressbar/base.py b/progressbar/base.py index 6383cfcf..a8c8e714 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -15,3 +15,7 @@ def __cmp__(self, other): # pragma: no cover class UnknownLength(six.with_metaclass(FalseMeta, object)): pass + + +class Undefined(six.with_metaclass(FalseMeta, object)): + pass diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 65d1c99b..5e24c3de 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -114,15 +114,19 @@ def __init__(self, format, new_style=False, **kwargs): self.new_style = new_style self.format = format + def get_format(self, progress, data, format=None): + return format or self.format + def __call__(self, progress, data, format=None): '''Formats the widget into a string''' + format = self.get_format(progress, data, format) try: if self.new_style: - return (format or self.format).format(**data) + return format.format(**data) else: - return (format or self.format) % data + return format % data except (TypeError, KeyError): - print('Error while formatting %r' % self.format, file=sys.stderr) + print('Error while formatting %r' % format, file=sys.stderr) pprint.pprint(data, stream=sys.stderr) raise @@ -633,17 +637,13 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - def __call__(self, progress, data, format=None): - return FormatWidgetMixin.__call__(self, progress, data, - self.get_format(progress, data)) - def get_format(self, progress, data, format=None): # If percentage is not available, display N/A% - if ('percentage' in data and not data['percentage'] and - data['percentage'] != 0): + percentage = data.get('percentage', base.Undefined) + if not percentage and percentage != 0: return self.na - return format or self.format + return FormatWidgetMixin.get_format(self, progress, data, format) class SimpleProgress(FormatWidgetMixin, WidgetBase): @@ -934,11 +934,6 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): Percentage.__init__(self, format, na=na, **kwargs) FormatLabelBar.__init__(self, format, **kwargs) - def __call__(self, progress, data, width, format=None): - return FormatLabelBar.__call__( - self, progress, data, width, - format=Percentage.get_format(self, progress, data, format=None)) - class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' From b64a78d63e3c7484df1e7704d4d2f3b955bcb776 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 12 Oct 2021 23:36:29 +0200 Subject: [PATCH 014/118] added github workflow --- .github/workflows/main.yml | 27 +++++++++++++++++++++++++++ README.rst | 7 ++++--- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..02709dc7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,27 @@ +name: tox + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox tox-gh-actions + - name: Test with tox + run: tox diff --git a/README.rst b/README.rst index f91d97d5..18c2bec9 100644 --- a/README.rst +++ b/README.rst @@ -2,10 +2,11 @@ Text progress bar library for Python. ############################################################################## -Travis status: +Build status: -.. image:: https://travis-ci.org/WoLpH/python-progressbar.svg?branch=master - :target: https://travis-ci.org/WoLpH/python-progressbar +.. image:: https://github.com/WoLpH/python-progressbar/actions/workflows/main.yml/badge.svg + :alt: python-progressbar test status + :target: https://github.com/WoLpH/python-progressbar/actions Coverage: From e7f4d1e41ece173f73b24265083d91c5797959d0 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 12 Oct 2021 23:42:38 +0200 Subject: [PATCH 015/118] Incrementing version to v3.54.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 6832fe2a..880be3eb 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '3.53.3' +__version__ = '3.54.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 0ad8251f0bb6ea41cfe6839bc78f62c8e097914c Mon Sep 17 00:00:00 2001 From: Carey Metcalfe Date: Wed, 13 Oct 2021 22:51:04 -0400 Subject: [PATCH 016/118] Add GranularBar widget `GranularBar` is a widget that displays progress at a sub-character granularity by using multiple marker characters. Using the `GranularBar` in its default configuration will show a smooth progress bar using unicode block characters. More examples are provided in `examples.py` --- README.rst | 1 + examples.py | 13 ++++++++ progressbar/__init__.py | 2 ++ progressbar/widgets.py | 56 ++++++++++++++++++++++++++++++++++ tests/test_monitor_progress.py | 19 ++++++++++++ 5 files changed, 91 insertions(+) diff --git a/README.rst b/README.rst index 18c2bec9..b485fb99 100644 --- a/README.rst +++ b/README.rst @@ -60,6 +60,7 @@ of widgets: - `FormatCustomText `_ - `FormatLabel `_ - `FormatLabelBar `_ + - `GranularBar `_ - `Percentage `_ - `PercentageLabelBar `_ - `ReverseBar `_ diff --git a/examples.py b/examples.py index a3300440..605ef5d9 100644 --- a/examples.py +++ b/examples.py @@ -176,6 +176,19 @@ def multi_progress_bar_example(left=True): bar.update(progress, jobs=jobs, force=True) time.sleep(0.02) +@example +def granular_progress_example(): + widgets=[ + progressbar.GranularBar(markers=" ▏▎▍▌▋▊▉█", left='', right='|'), + progressbar.GranularBar(markers=" ▁▂▃▄▅▆▇█", left='', right='|'), + progressbar.GranularBar(markers=" ▖▌▛█", left='', right='|'), + progressbar.GranularBar(markers=" ░▒▓█", left='', right='|'), + progressbar.GranularBar(markers=" ⡀⡄⡆⡇⣇⣧⣷⣿", left='', right='|'), + progressbar.GranularBar(markers=" .oO", left='', right=''), + ] + for i in progressbar.progressbar(range(100), widgets=widgets): + time.sleep(0.03) + @example def percentage_label_bar_example(): diff --git a/progressbar/__init__.py b/progressbar/__init__.py index ef504514..f93ab86d 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -26,6 +26,7 @@ VariableMixin, MultiRangeBar, MultiProgressBar, + GranularBar, FormatLabelBar, PercentageLabelBar, Variable, @@ -74,6 +75,7 @@ 'VariableMixin', 'MultiRangeBar', 'MultiProgressBar', + 'GranularBar', 'FormatLabelBar', 'PercentageLabelBar', 'Variable', diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 5e24c3de..449a09d6 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -909,6 +909,62 @@ def get_values(self, progress, data): return ranges +class GranularBar(AutoWidthWidgetBase): + '''A progressbar that can display progress at a sub-character granularity + by using multiple marker characters. + + Examples of markers: + - Smooth: ` ▏▎▍▌▋▊▉█` (default) + - Bar: ` ▁▂▃▄▅▆▇█` + - Snake: ` ▖▌▛█` + - Fade in: ` ░▒▓█` + - Dots: ` ⡀⡄⡆⡇⣇⣧⣷⣿` + - Growing circles: ` .oO` + ''' + + def __init__(self, markers=' ▏▎▍▌▋▊▉█', left='|', right='|', **kwargs): + '''Creates a customizable progress bar. + + markers - string of characters to use as granular progress markers. The + first character should represent 0% and the last 100%. + Ex: ` .oO` + left - string or callable object to use as a left border + right - string or callable object to use as a right border + ''' + self.markers = markers + self.left = string_or_lambda(left) + self.right = string_or_lambda(right) + + AutoWidthWidgetBase.__init__(self, **kwargs) + + def __call__(self, progress, data, width): + left = converters.to_unicode(self.left(progress, data, width)) + right = converters.to_unicode(self.right(progress, data, width)) + width -= progress.custom_len(left) + progress.custom_len(right) + + if progress.max_value is not base.UnknownLength \ + and progress.max_value > 0: + percent = progress.value / progress.max_value + else: + percent = 0 + + num_chars = percent * width + + marker = self.markers[-1] * int(num_chars) + + marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) + if marker_idx: + marker += self.markers[marker_idx] + + marker = converters.to_unicode(marker) + + # Make sure we ignore invisible characters when filling + width += len(marker) - progress.custom_len(marker) + marker = marker.ljust(width, self.markers[0]) + + return left + marker + right + + class FormatLabelBar(FormatLabel, Bar): '''A bar which has a formatted label in the center.''' def __init__(self, format, **kwargs): diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index 36ce89c6..943af85a 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -206,6 +206,25 @@ def test_percentage_label_bar(testdir): ] +def test_granular_bar(testdir): + result = testdir.runpython(testdir.makepyfile(_create_script( + widgets='[progressbar.GranularBar(markers=" .oO")]', + line_breaks=False, + items=list(range(5)), + ))) + pprint.pprint(result.stderr.lines, width=70) + assert result.stderr.lines == [u'', + u'| |', + u'|OOOOOOOOOOO. |', + u'|OOOOOOOOOOOOOOOOOOOOOOO |', + u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo |', + u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO. |', + u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', + u'', + u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|' + ] + + def test_colors(testdir): kwargs = dict( items=range(1), From c1fcabf83d0539a57fac7223cf21945a4cfda7fa Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Fri, 15 Oct 2021 11:44:17 +0200 Subject: [PATCH 017/118] Added class for easy granular marker configuration --- examples.py | 3 ++- progressbar/utils.py | 2 +- progressbar/widgets.py | 17 +++++++++++++++-- tests/test_monitor_progress.py | 3 ++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/examples.py b/examples.py index 605ef5d9..0c6948da 100644 --- a/examples.py +++ b/examples.py @@ -176,9 +176,10 @@ def multi_progress_bar_example(left=True): bar.update(progress, jobs=jobs, force=True) time.sleep(0.02) + @example def granular_progress_example(): - widgets=[ + widgets = [ progressbar.GranularBar(markers=" ▏▎▍▌▋▊▉█", left='', right='|'), progressbar.GranularBar(markers=" ▁▂▃▄▅▆▇█", left='', right='|'), progressbar.GranularBar(markers=" ▖▌▛█", left='', right='|'), diff --git a/progressbar/utils.py b/progressbar/utils.py index a9dc6f54..258249ff 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -192,7 +192,7 @@ def __init__(self, target, capturing=False, listeners=set()): self.listeners = listeners self.needs_clear = False - def isatty(self): + def isatty(self): # pragma: no cover return self.target.isatty() def write(self, value): diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 449a09d6..e9c03cab 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -909,6 +909,15 @@ def get_values(self, progress, data): return ranges +class GranularMarkers: + smooth = ' ▏▎▍▌▋▊▉█' + bar = ' ▁▂▃▄▅▆▇█' + snake = ' ▖▌▛█' + fade_in = ' ░▒▓█' + dots = ' ⡀⡄⡆⡇⣇⣧⣷⣿' + growing_circles = ' .oO' + + class GranularBar(AutoWidthWidgetBase): '''A progressbar that can display progress at a sub-character granularity by using multiple marker characters. @@ -920,14 +929,18 @@ class GranularBar(AutoWidthWidgetBase): - Fade in: ` ░▒▓█` - Dots: ` ⡀⡄⡆⡇⣇⣧⣷⣿` - Growing circles: ` .oO` + + The markers can be accessed through GranularMarkers. GranularMarkers.dots + for example ''' - def __init__(self, markers=' ▏▎▍▌▋▊▉█', left='|', right='|', **kwargs): + def __init__(self, markers=GranularMarkers.smooth, left='|', right='|', + **kwargs): '''Creates a customizable progress bar. markers - string of characters to use as granular progress markers. The first character should represent 0% and the last 100%. - Ex: ` .oO` + Ex: ` .oO`. left - string or callable object to use as a left border right - string or callable object to use as a right border ''' diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index 943af85a..5dd6f5ee 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -213,7 +213,8 @@ def test_granular_bar(testdir): items=list(range(5)), ))) pprint.pprint(result.stderr.lines, width=70) - assert result.stderr.lines == [u'', + assert result.stderr.lines == [ + u'', u'| |', u'|OOOOOOOOOOO. |', u'|OOOOOOOOOOOOOOOOOOOOOOO |', From ca6d555dded686b074a6d09d01d841c776bf0a9f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Fri, 15 Oct 2021 14:20:11 +0200 Subject: [PATCH 018/118] Incrementing version to v3.55.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 880be3eb..35b1c9de 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '3.54.0' +__version__ = '3.55.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 82886d521d3fb112dbc746625c24903246a2c0c9 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 03:41:31 +0100 Subject: [PATCH 019/118] updated to python 3 only support --- .github/workflows/main.yml | 2 +- examples.py | 11 ++---- progressbar/__init__.py | 80 ++++++++++++++++---------------------- progressbar/bar.py | 24 ++++-------- progressbar/base.py | 7 +--- progressbar/utils.py | 17 ++++---- progressbar/widgets.py | 23 ++++------- setup.py | 68 +++++++------------------------- tests/test_flush.py | 2 - tests/test_stream.py | 2 - tests/test_terminal.py | 2 - tox.ini | 11 +++--- 12 files changed, 84 insertions(+), 165 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 02709dc7..7b19e5d8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 diff --git a/examples.py b/examples.py index 0c6948da..1c033839 100644 --- a/examples.py +++ b/examples.py @@ -1,12 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import with_statement - import functools import random import sys @@ -187,7 +181,10 @@ def granular_progress_example(): progressbar.GranularBar(markers=" ⡀⡄⡆⡇⣇⣧⣷⣿", left='', right='|'), progressbar.GranularBar(markers=" .oO", left='', right=''), ] - for i in progressbar.progressbar(range(100), widgets=widgets): + for i in progressbar.progressbar(list(range(100)), widgets=widgets): + time.sleep(0.03) + + for i in progressbar.progressbar(iter(range(100)), widgets=widgets): time.sleep(0.03) diff --git a/progressbar/__init__.py b/progressbar/__init__.py index f93ab86d..33d7c719 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -1,52 +1,40 @@ from datetime import date -from .utils import ( - len_color, - streams -) -from .shortcuts import progressbar - -from .widgets import ( - Timer, - ETA, - AdaptiveETA, - AbsoluteETA, - DataSize, - FileTransferSpeed, - AdaptiveTransferSpeed, - AnimatedMarker, - Counter, - Percentage, - FormatLabel, - SimpleProgress, - Bar, - ReverseBar, - BouncingBar, - RotatingMarker, - VariableMixin, - MultiRangeBar, - MultiProgressBar, - GranularBar, - FormatLabelBar, - PercentageLabelBar, - Variable, - DynamicMessage, - FormatCustomText, - CurrentTime -) - -from .bar import ( - ProgressBar, - DataTransferBar, - NullBar, -) +from .__about__ import __author__ +from .__about__ import __version__ +from .bar import DataTransferBar +from .bar import NullBar +from .bar import ProgressBar from .base import UnknownLength - - -from .__about__ import ( - __author__, - __version__, -) +from .shortcuts import progressbar +from .utils import len_color +from .utils import streams +from .widgets import AbsoluteETA +from .widgets import AdaptiveETA +from .widgets import AdaptiveTransferSpeed +from .widgets import AnimatedMarker +from .widgets import Bar +from .widgets import BouncingBar +from .widgets import Counter +from .widgets import CurrentTime +from .widgets import DataSize +from .widgets import DynamicMessage +from .widgets import ETA +from .widgets import FileTransferSpeed +from .widgets import FormatCustomText +from .widgets import FormatLabel +from .widgets import FormatLabelBar +from .widgets import GranularBar +from .widgets import MultiProgressBar +from .widgets import MultiRangeBar +from .widgets import Percentage +from .widgets import PercentageLabelBar +from .widgets import ReverseBar +from .widgets import RotatingMarker +from .widgets import SimpleProgress +from .widgets import Timer +from .widgets import Variable +from .widgets import VariableMixin __date__ = str(date.today()) __all__ = [ diff --git a/progressbar/bar.py b/progressbar/bar.py index b5980e23..7848b776 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,17 +1,13 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import unicode_literals -from __future__ import with_statement - -import sys +import logging import math import os +import sys import time import timeit -import logging import warnings -from datetime import datetime from copy import deepcopy +from datetime import datetime + try: # pragma: no cover from collections import abc except ImportError: # pragma: no cover @@ -19,14 +15,11 @@ from python_utils import converters -import six - from . import widgets from . import widgets as widgets_module # Avoid name collision from . import base from . import utils - logger = logging.getLogger(__name__) @@ -77,7 +70,7 @@ def __init__(self, fd=sys.stderr, is_terminal=None, line_breaks=None, # iteractive terminals) or write line breaks (suitable for log files) if line_breaks is None: line_breaks = utils.env_flag('PROGRESSBAR_LINE_BREAKS', not - self.is_terminal) + self.is_terminal) self.line_breaks = line_breaks # Check if ANSI escape characters are enabled (suitable for iteractive @@ -197,7 +190,6 @@ def finish(self, end='\n'): class ProgressBar(StdRedirectMixin, ResizableMixin, ProgressBarBase): - '''The ProgressBar class which updates and prints the bar. Args: @@ -489,7 +481,7 @@ def data(self): total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) + - (elapsed.microseconds / 1000000.), + (elapsed.microseconds / 1000000.), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -585,7 +577,7 @@ def _format_widgets(self): elif isinstance(widget, widgets.AutoWidthWidgetBase): result.append(widget) expanding.insert(0, index) - elif isinstance(widget, six.string_types): + elif isinstance(widget, str): result.append(widget) width -= self.custom_len(widget) else: @@ -795,6 +787,7 @@ class DataTransferBar(ProgressBar): This assumes that the values its given are numbers of bytes. ''' + def default_widgets(self): if self.max_value: return [ @@ -813,7 +806,6 @@ def default_widgets(self): class NullBar(ProgressBar): - ''' Progress bar that does absolutely nothing. Useful for single verbosity flags diff --git a/progressbar/base.py b/progressbar/base.py index a8c8e714..df278e59 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -1,7 +1,4 @@ # -*- mode: python; coding: utf-8 -*- -from __future__ import absolute_import -import six - class FalseMeta(type): def __bool__(self): # pragma: no cover @@ -13,9 +10,9 @@ def __cmp__(self, other): # pragma: no cover __nonzero__ = __bool__ -class UnknownLength(six.with_metaclass(FalseMeta, object)): +class UnknownLength(metaclass=FalseMeta): pass -class Undefined(six.with_metaclass(FalseMeta, object)): +class Undefined(metaclass=FalseMeta): pass diff --git a/progressbar/utils.py b/progressbar/utils.py index 258249ff..e589105f 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -1,17 +1,16 @@ -from __future__ import absolute_import import atexit +import datetime import io +import logging import os import re import sys -import logging -import datetime -from python_utils.time import timedelta_to_seconds, epoch, format_time + from python_utils.converters import scale_1024 from python_utils.terminal import get_terminal_size - -import six - +from python_utils.time import epoch +from python_utils.time import format_time +from python_utils.time import timedelta_to_seconds assert timedelta_to_seconds assert get_terminal_size @@ -19,7 +18,6 @@ assert scale_1024 assert epoch - ANSI_TERMS = ( '([xe]|bv)term', '(sco)?ansi', @@ -186,7 +184,7 @@ def env_flag(name, default=None): class WrappingIO: def __init__(self, target, capturing=False, listeners=set()): - self.buffer = six.StringIO() + self.buffer = io.StringIO() self.target = target self.capturing = capturing self.listeners = listeners @@ -402,6 +400,7 @@ class AttributeDict(dict): ... AttributeError: No such attribute: spam ''' + def __getattr__(self, name): if name in self: return self[name] diff --git a/progressbar/widgets.py b/progressbar/widgets.py index e9c03cab..1eaa0c09 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -1,20 +1,12 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import with_statement - import abc -import sys -import pprint import datetime import functools +import pprint +import sys from python_utils import converters -import six - from . import base from . import utils @@ -24,7 +16,7 @@ def string_or_lambda(input_): - if isinstance(input_, six.string_types): + if isinstance(input_, str): def render_input(progress, data, width): return input_ % data @@ -50,7 +42,7 @@ def create_wrapper(wrapper): elif not wrapper: return - if isinstance(wrapper, six.string_types): + if isinstance(wrapper, str): assert '{}' in wrapper, 'Expected string with {} for formatting' else: raise RuntimeError('Pass either a begin/end string as a tuple or a' @@ -84,7 +76,7 @@ def _marker(progress, data, width): else: return marker - if isinstance(marker, six.string_types): + if isinstance(marker, str): marker = converters.to_unicode(marker) assert utils.len_color(marker) == 1, \ 'Markers are required to be 1 char' @@ -811,7 +803,7 @@ class VariableMixin(object): '''Mixin to display a custom user variable ''' def __init__(self, name, **kwargs): - if not isinstance(name, six.string_types): + if not isinstance(name, str): raise TypeError('Variable(): argument must be a string') if len(name.split()) > 1: raise ValueError('Variable(): argument must be single word') @@ -980,6 +972,7 @@ def __call__(self, progress, data, width): class FormatLabelBar(FormatLabel, Bar): '''A bar which has a formatted label in the center.''' + def __init__(self, format, **kwargs): FormatLabel.__init__(self, format, **kwargs) Bar.__init__(self, **kwargs) @@ -997,6 +990,7 @@ def __call__(self, progress, data, width, format=None): class PercentageLabelBar(Percentage, FormatLabelBar): '''A bar which displays the current percentage in the center.''' + # %3d adds an extra space that makes it look off-center # %2d keeps the label somewhat consistently in-place def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): @@ -1070,4 +1064,3 @@ def current_datetime(self): def current_time(self): return self.current_datetime().time() - diff --git a/setup.py b/setup.py index d5462630..3f4f7bdf 100644 --- a/setup.py +++ b/setup.py @@ -4,55 +4,16 @@ import os import sys -from setuptools.command.test import test as TestCommand - -try: - from setuptools import setup, find_packages -except ImportError: - from distutils.core import setup, find_packages - - -# Not all systems use utf8 encoding by default, this works around that issue -if sys.version_info > (3,): - from functools import partial - open = partial(open, encoding='utf8') - +from setuptools import setup, find_packages # To prevent importing about and thereby breaking the coverage info we use this # exec hack about = {} -with open("progressbar/__about__.py") as fp: +with open('progressbar/__about__.py', encoding='utf8') as fp: exec(fp.read(), about) -class PyTest(TestCommand): - user_options = [('pytest-args=', 'a', 'Arguments to pass to pytest')] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.pytest_args = '' - - def run_tests(self): - import shlex - # import here, cause outside the eggs aren't loaded - import pytest - errno = pytest.main(shlex.split(self.pytest_args)) - sys.exit(errno) - - install_reqs = [] -tests_require = [ - 'flake8>=3.7.7', - 'pytest>=4.6.9', - 'pytest-cov>=2.6.1', - 'freezegun>=0.3.11', - 'sphinx>=1.8.5', -] - -if sys.version_info < (2, 7): - tests_require += ['unittest2'] - - if sys.argv[-1] == 'info': for k, v in about.items(): print('%s: %s' % (k, v)) @@ -65,7 +26,6 @@ def run_tests(self): readme = \ 'See http://pypi.python.org/pypi/%(__package_name__)s/' % about - if __name__ == '__main__': setup( name='progressbar2', @@ -80,32 +40,32 @@ def run_tests(self): long_description=readme, include_package_data=True, install_requires=[ - 'python-utils>=2.3.0', - 'six', + 'python-utils>=3.0.0', ], - tests_require=tests_require, setup_requires=['setuptools'], zip_safe=False, - cmdclass={'test': PyTest}, extras_require={ 'docs': [ - 'sphinx>=1.7.4', + 'sphinx>=1.8.5', + ], + 'tests': [ + 'flake8>=3.7.7', + 'pytest>=4.6.9', + 'pytest-cov>=2.6.1', + 'freezegun>=0.3.11', + 'sphinx>=1.8.5', ], - 'tests': tests_require, }, + python_requires='>=3.7.0', classifiers=[ 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', - "Programming Language :: Python :: 2", - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: PyPy', ], ) diff --git a/tests/test_flush.py b/tests/test_flush.py index 7f4317eb..69dc4e30 100644 --- a/tests/test_flush.py +++ b/tests/test_flush.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import time import progressbar diff --git a/tests/test_stream.py b/tests/test_stream.py index 235f174a..6dcfcf7c 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import io import sys import pytest diff --git a/tests/test_terminal.py b/tests/test_terminal.py index f55f3df9..997bb0d6 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys import time import signal diff --git a/tox.ini b/tox.ini index 9af9ca8b..a36a8ddd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,15 @@ [tox] -envlist = py27, py33, py34, py35, py36, py37, py38, pypy, flake8, docs +envlist = py37, py38, py39, py310, pypy3, flake8, docs skip_missing_interpreters = True [testenv] basepython = - py27: python2.7 - py34: python3.4 - py35: python3.5 py36: python3.6 py37: python3.7 py38: python3.8 - pypy: pypy + py39: python3.9 + py310: python3.10 + pypy3: pypy3 deps = -r{toxinidir}/tests/requirements.txt commands = py.test --basetemp="{envtmpdir}" --confcutdir=.. {posargs} @@ -18,7 +17,7 @@ changedir = tests [testenv:flake8] changedir = -basepython = python2.7 +basepython = python3 deps = flake8 commands = flake8 {toxinidir}/progressbar {toxinidir}/tests {toxinidir}/examples.py From d8f684ca7b0bb322056f627dd02950f09ed8c567 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 04:26:45 +0100 Subject: [PATCH 020/118] added some type hinting --- progressbar/bar.py | 15 ++++++--- progressbar/utils.py | 71 +++++++++++++++++++++++++----------------- progressbar/widgets.py | 4 +-- setup.py | 1 + 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 7848b776..a884dcab 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -8,6 +8,8 @@ from copy import deepcopy from datetime import datetime +from python_utils import types + try: # pragma: no cover from collections import abc except ImportError: # pragma: no cover @@ -51,8 +53,10 @@ class ProgressBarBase(abc.Iterable, ProgressBarMixinBase): class DefaultFdMixin(ProgressBarMixinBase): - def __init__(self, fd=sys.stderr, is_terminal=None, line_breaks=None, - enable_colors=None, **kwargs): + def __init__(self, fd: types.IO = sys.stderr, + is_terminal: bool | None = None, + line_breaks: bool | None = None, + enable_colors: bool | None = None, **kwargs): if fd is sys.stdout: fd = utils.streams.original_stdout @@ -115,7 +119,7 @@ def finish(self, *args, **kwargs): # pragma: no cover class ResizableMixin(ProgressBarMixinBase): - def __init__(self, term_width=None, **kwargs): + def __init__(self, term_width: int | None = None, **kwargs): ProgressBarMixinBase.__init__(self, **kwargs) self.signal_set = False @@ -149,7 +153,8 @@ def finish(self): # pragma: no cover class StdRedirectMixin(DefaultFdMixin): - def __init__(self, redirect_stderr=False, redirect_stdout=False, **kwargs): + def __init__(self, redirect_stderr: bool=False, redirect_stdout: + bool=False, **kwargs): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr self.redirect_stdout = redirect_stdout @@ -172,7 +177,7 @@ def start(self, *args, **kwargs): utils.streams.start_capturing(self) DefaultFdMixin.start(self, *args, **kwargs) - def update(self, value=None): + def update(self, value: float=None): if not self.line_breaks and utils.streams.needs_clear(): self.fd.write('\r' + ' ' * self.term_width + '\r') diff --git a/progressbar/utils.py b/progressbar/utils.py index e589105f..101fac7e 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import atexit import datetime import io @@ -5,7 +7,11 @@ import os import re import sys +from typing import Optional +from typing import Set +from typing import Union +from python_utils import types from python_utils.converters import scale_1024 from python_utils.terminal import get_terminal_size from python_utils.time import epoch @@ -32,7 +38,8 @@ ANSI_TERM_RE = re.compile('^({})'.format('|'.join(ANSI_TERMS)), re.IGNORECASE) -def is_ansi_terminal(fd, is_terminal=None): # pragma: no cover +def is_ansi_terminal(fd: types.IO, is_terminal: bool | None = None) \ + -> bool: # pragma: no cover if is_terminal is None: # Jupyter Notebooks define this variable and support progress bars if 'JPY_PARENT_PID' in os.environ: @@ -64,7 +71,7 @@ def is_ansi_terminal(fd, is_terminal=None): # pragma: no cover return is_terminal -def is_terminal(fd, is_terminal=None): +def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: if is_terminal is None: # Full ansi support encompasses what we expect from a terminal is_terminal = is_ansi_terminal(True) or None @@ -84,7 +91,8 @@ def is_terminal(fd, is_terminal=None): return is_terminal -def deltas_to_seconds(*deltas, **kwargs): # default=ValueError): +def deltas_to_seconds(*deltas, **kwargs) -> Optional[ + Union[float, int]]: # default=ValueError): ''' Convert timedeltas and seconds as int to seconds as float while coalescing @@ -128,7 +136,7 @@ def deltas_to_seconds(*deltas, **kwargs): # default=ValueError): return default -def no_color(value): +def no_color(value: Union[str, bytes]) -> Union[str, bytes]: ''' Return the `value` without ANSI escape codes @@ -151,7 +159,7 @@ def no_color(value): return re.sub(pattern, replace, value) -def len_color(value): +def len_color(value: Union[str, bytes]) -> int: ''' Return the length of `value` without ANSI escape codes @@ -165,7 +173,7 @@ def len_color(value): return len(no_color(value)) -def env_flag(name, default=None): +def env_flag(name: str, default: Optional[bool] = None) -> Optional[bool]: ''' Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, on/off, and returns it as a boolean @@ -183,7 +191,8 @@ def env_flag(name, default=None): class WrappingIO: - def __init__(self, target, capturing=False, listeners=set()): + def __init__(self, target: types.IO, capturing: bool = False, listeners: + Set['progressbar.ProgressBar'] = set()) -> None: self.buffer = io.StringIO() self.target = target self.capturing = capturing @@ -193,7 +202,7 @@ def __init__(self, target, capturing=False, listeners=set()): def isatty(self): # pragma: no cover return self.target.isatty() - def write(self, value): + def write(self, value: str) -> None: if self.capturing: self.buffer.write(value) if '\n' in value: # pragma: no branch @@ -205,10 +214,10 @@ def write(self, value): if '\n' in value: # pragma: no branch self.flush_target() - def flush(self): + def flush(self) -> None: self.buffer.flush() - def _flush(self): + def _flush(self) -> None: value = self.buffer.getvalue() if value: self.flush() @@ -220,12 +229,12 @@ def _flush(self): # when explicitly flushing, always flush the target as well self.flush_target() - def flush_target(self): # pragma: no cover + def flush_target(self) -> None: # pragma: no cover if not self.target.closed and getattr(self.target, 'flush'): self.target.flush() -class StreamWrapper(object): +class StreamWrapper: '''Wrap stdout and stderr globally''' def __init__(self): @@ -244,14 +253,20 @@ def __init__(self): if env_flag('WRAP_STDERR', default=False): # pragma: no cover self.wrap_stderr() - def start_capturing(self, bar=None): + def start_capturing( + self, + bar: types.U['progressbar.ProgressBar', + 'progressbar.DataTransferBar', None] = None, + ) -> None: if bar: # pragma: no branch self.listeners.add(bar) self.capturing += 1 self.update_capturing() - def stop_capturing(self, bar=None): + def stop_capturing(self, bar: Optional[ + Union[ + 'progressbar.ProgressBar', 'progressbar.DataTransferBar']] = None) -> None: if bar: # pragma: no branch try: self.listeners.remove(bar) @@ -261,7 +276,7 @@ def stop_capturing(self, bar=None): self.capturing -= 1 self.update_capturing() - def update_capturing(self): # pragma: no cover + def update_capturing(self) -> None: # pragma: no cover if isinstance(self.stdout, WrappingIO): self.stdout.capturing = self.capturing > 0 @@ -271,14 +286,14 @@ def update_capturing(self): # pragma: no cover if self.capturing <= 0: self.flush() - def wrap(self, stdout=False, stderr=False): + def wrap(self, stdout: bool = False, stderr: bool = False) -> None: if stdout: self.wrap_stdout() if stderr: self.wrap_stderr() - def wrap_stdout(self): + def wrap_stdout(self) -> types.IO: self.wrap_excepthook() if not self.wrapped_stdout: @@ -288,7 +303,7 @@ def wrap_stdout(self): return sys.stdout - def wrap_stderr(self): + def wrap_stderr(self) -> types.IO: self.wrap_excepthook() if not self.wrapped_stderr: @@ -298,44 +313,44 @@ def wrap_stderr(self): return sys.stderr - def unwrap_excepthook(self): + def unwrap_excepthook(self) -> None: if self.wrapped_excepthook: self.wrapped_excepthook -= 1 sys.excepthook = self.original_excepthook - def wrap_excepthook(self): + def wrap_excepthook(self) -> None: if not self.wrapped_excepthook: logger.debug('wrapping excepthook') self.wrapped_excepthook += 1 sys.excepthook = self.excepthook - def unwrap(self, stdout=False, stderr=False): + def unwrap(self, stdout: bool = False, stderr: bool = False) -> None: if stdout: self.unwrap_stdout() if stderr: self.unwrap_stderr() - def unwrap_stdout(self): + def unwrap_stdout(self) -> None: if self.wrapped_stdout > 1: self.wrapped_stdout -= 1 else: sys.stdout = self.original_stdout self.wrapped_stdout = 0 - def unwrap_stderr(self): + def unwrap_stderr(self) -> None: if self.wrapped_stderr > 1: self.wrapped_stderr -= 1 else: sys.stderr = self.original_stderr self.wrapped_stderr = 0 - def needs_clear(self): # pragma: no cover + def needs_clear(self) -> bool: # pragma: no cover stdout_needs_clear = getattr(self.stdout, 'needs_clear', False) stderr_needs_clear = getattr(self.stderr, 'needs_clear', False) return stderr_needs_clear or stdout_needs_clear - def flush(self): + def flush(self) -> None: if self.wrapped_stdout: # pragma: no branch try: self.stdout._flush() @@ -401,16 +416,16 @@ class AttributeDict(dict): AttributeError: No such attribute: spam ''' - def __getattr__(self, name): + def __getattr__(self, name: str) -> int: if name in self: return self[name] else: raise AttributeError("No such attribute: " + name) - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: int) -> None: self[name] = value - def __delattr__(self, name): + def __delattr__(self, name: str) -> None: if name in self: del self[name] else: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 1eaa0c09..285c51e9 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -152,7 +152,7 @@ def __init__(self, min_width=None, max_width=None, **kwargs): self.min_width = min_width self.max_width = max_width - def check_size(self, progress): + def check_size(self, progress: 'progressbar.ProgressBar'): if self.min_width and self.min_width > progress.term_width: return False elif self.max_width and self.max_width < progress.term_width: @@ -247,7 +247,7 @@ class FormatLabel(FormatWidgetMixin, WidgetBase): 'value': ('value', None), } - def __init__(self, format, **kwargs): + def __init__(self, format: str, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) diff --git a/setup.py b/setup.py index 3f4f7bdf..f72abd08 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ 'flake8>=3.7.7', 'pytest>=4.6.9', 'pytest-cov>=2.6.1', + 'pytest-mypy', 'freezegun>=0.3.11', 'sphinx>=1.8.5', ], From bec6a33a8524b6906dda6ede665e3cb3c5b40978 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:13:41 +0100 Subject: [PATCH 021/118] simplified types --- progressbar/utils.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index 101fac7e..d8e9f2a3 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -7,9 +7,7 @@ import os import re import sys -from typing import Optional from typing import Set -from typing import Union from python_utils import types from python_utils.converters import scale_1024 @@ -91,8 +89,8 @@ def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: return is_terminal -def deltas_to_seconds(*deltas, **kwargs) -> Optional[ - Union[float, int]]: # default=ValueError): +def deltas_to_seconds(*deltas, + **kwargs) -> int | float | None: # default=ValueError): ''' Convert timedeltas and seconds as int to seconds as float while coalescing @@ -136,7 +134,7 @@ def deltas_to_seconds(*deltas, **kwargs) -> Optional[ return default -def no_color(value: Union[str, bytes]) -> Union[str, bytes]: +def no_color(value: types.StringTypes) -> types.StringTypes: ''' Return the `value` without ANSI escape codes @@ -159,7 +157,7 @@ def no_color(value: Union[str, bytes]) -> Union[str, bytes]: return re.sub(pattern, replace, value) -def len_color(value: Union[str, bytes]) -> int: +def len_color(value: types.StringTypes) -> int: ''' Return the length of `value` without ANSI escape codes @@ -173,7 +171,7 @@ def len_color(value: Union[str, bytes]) -> int: return len(no_color(value)) -def env_flag(name: str, default: Optional[bool] = None) -> Optional[bool]: +def env_flag(name: str, default: bool | None = None) -> bool | None: ''' Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, on/off, and returns it as a boolean @@ -264,9 +262,9 @@ def start_capturing( self.capturing += 1 self.update_capturing() - def stop_capturing(self, bar: Optional[ - Union[ - 'progressbar.ProgressBar', 'progressbar.DataTransferBar']] = None) -> None: + def stop_capturing( + self, bar: 'progressbar.ProgressBar' + | 'progressbar.DataTransferBar' | None = None) -> None: if bar: # pragma: no branch try: self.listeners.remove(bar) From 16234f2b0d9ca170e71312eaa1c22b4fda8183d2 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:16:54 +0100 Subject: [PATCH 022/118] fixed python 3.7 support --- progressbar/bar.py | 8 +++++--- tox.ini | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index a884dcab..fd538dcd 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import math import os @@ -153,8 +155,8 @@ def finish(self): # pragma: no cover class StdRedirectMixin(DefaultFdMixin): - def __init__(self, redirect_stderr: bool=False, redirect_stdout: - bool=False, **kwargs): + def __init__(self, redirect_stderr: bool = False, redirect_stdout: + bool = False, **kwargs): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr self.redirect_stdout = redirect_stdout @@ -177,7 +179,7 @@ def start(self, *args, **kwargs): utils.streams.start_capturing(self) DefaultFdMixin.start(self, *args, **kwargs) - def update(self, value: float=None): + def update(self, value: float = None): if not self.line_breaks and utils.streams.needs_clear(): self.fd.write('\r' + ' ' * self.term_width + '\r') diff --git a/tox.ini b/tox.ini index a36a8ddd..1ed7e3c6 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ commands = flake8 {toxinidir}/progressbar {toxinidir}/tests {toxinidir}/examples [testenv:docs] changedir = -basepython = python3 +basepython = python3.7 deps = -r{toxinidir}/docs/requirements.txt whitelist_externals = rm From 37831993c5d3a3980919ec8cefdba795c2a6c51d Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:17:32 +0100 Subject: [PATCH 023/118] any modern python installation should be able to build the docs --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1ed7e3c6..a36a8ddd 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ commands = flake8 {toxinidir}/progressbar {toxinidir}/tests {toxinidir}/examples [testenv:docs] changedir = -basepython = python3.7 +basepython = python3 deps = -r{toxinidir}/docs/requirements.txt whitelist_externals = rm From 5e1087a11d76b77a0972a4720166e3f434f9c0d4 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:27:29 +0100 Subject: [PATCH 024/118] simplified types --- progressbar/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index d8e9f2a3..da7d3955 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -7,7 +7,6 @@ import os import re import sys -from typing import Set from python_utils import types from python_utils.converters import scale_1024 @@ -190,7 +189,7 @@ def env_flag(name: str, default: bool | None = None) -> bool | None: class WrappingIO: def __init__(self, target: types.IO, capturing: bool = False, listeners: - Set['progressbar.ProgressBar'] = set()) -> None: + types.Set['progressbar.ProgressBar'] = set()) -> None: self.buffer = io.StringIO() self.target = target self.capturing = capturing @@ -253,8 +252,8 @@ def __init__(self): def start_capturing( self, - bar: types.U['progressbar.ProgressBar', - 'progressbar.DataTransferBar', None] = None, + bar: 'progressbar.ProgressBar' | 'progressbar.DataTransferBar' + | None = None, ) -> None: if bar: # pragma: no branch self.listeners.add(bar) From b1c19c713f8a75070097b3a65a526c496b5bcd6b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:33:02 +0100 Subject: [PATCH 025/118] fixed documentation styling issues --- progressbar/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/progressbar/utils.py b/progressbar/utils.py index da7d3955..7fed5f50 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -378,12 +378,14 @@ class AttributeDict(dict): >>> attrs = AttributeDict(spam=123) # Reading + >>> attrs['spam'] 123 >>> attrs.spam 123 # Read after update using attribute + >>> attrs.spam = 456 >>> attrs['spam'] 456 @@ -391,6 +393,7 @@ class AttributeDict(dict): 456 # Read after update using dict access + >>> attrs['spam'] = 123 >>> attrs['spam'] 123 @@ -398,6 +401,7 @@ class AttributeDict(dict): 123 # Read after update using dict access + >>> del attrs.spam >>> attrs['spam'] Traceback (most recent call last): From 251c7a4f616f9f9b44d49e9adf2984c29c510a8c Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:41:05 +0100 Subject: [PATCH 026/118] small type hinting improvements --- progressbar/utils.py | 19 ++++++++----------- progressbar/widgets.py | 6 +++++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index 7fed5f50..b1be944f 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -15,6 +15,9 @@ from python_utils.time import format_time from python_utils.time import timedelta_to_seconds +if types.TYPE_CHECKING: + from .bar import ProgressBar + assert timedelta_to_seconds assert get_terminal_size assert format_time @@ -188,12 +191,12 @@ def env_flag(name: str, default: bool | None = None) -> bool | None: class WrappingIO: - def __init__(self, target: types.IO, capturing: bool = False, listeners: - types.Set['progressbar.ProgressBar'] = set()) -> None: + def __init__(self, target: types.IO, capturing: bool = False, + listeners: types.Set[ProgressBar] = None) -> None: self.buffer = io.StringIO() self.target = target self.capturing = capturing - self.listeners = listeners + self.listeners = listeners or set() self.needs_clear = False def isatty(self): # pragma: no cover @@ -250,20 +253,14 @@ def __init__(self): if env_flag('WRAP_STDERR', default=False): # pragma: no cover self.wrap_stderr() - def start_capturing( - self, - bar: 'progressbar.ProgressBar' | 'progressbar.DataTransferBar' - | None = None, - ) -> None: + def start_capturing(self, bar: ProgressBar | None = None) -> None: if bar: # pragma: no branch self.listeners.add(bar) self.capturing += 1 self.update_capturing() - def stop_capturing( - self, bar: 'progressbar.ProgressBar' - | 'progressbar.DataTransferBar' | None = None) -> None: + def stop_capturing(self, bar: ProgressBar | None = None) -> None: if bar: # pragma: no branch try: self.listeners.remove(bar) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 285c51e9..9ffb68af 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -6,10 +6,14 @@ import sys from python_utils import converters +from python_utils import types from . import base from . import utils +if types.TYPE_CHECKING: + from .bar import ProgressBar + MAX_DATE = datetime.date.max MAX_TIME = datetime.time.max MAX_DATETIME = datetime.datetime.max @@ -152,7 +156,7 @@ def __init__(self, min_width=None, max_width=None, **kwargs): self.min_width = min_width self.max_width = max_width - def check_size(self, progress: 'progressbar.ProgressBar'): + def check_size(self, progress: 'ProgressBar'): if self.min_width and self.min_width > progress.term_width: return False elif self.max_width and self.max_width < progress.term_width: From 69eb5d4f0b27a056e63c92be50de82943402a9b4 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 12:45:03 +0100 Subject: [PATCH 027/118] small type hinting improvements --- progressbar/bar.py | 12 ++++++------ tox.ini | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index fd538dcd..025471e9 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -75,8 +75,8 @@ def __init__(self, fd: types.IO = sys.stderr, # Check if it should overwrite the current line (suitable for # iteractive terminals) or write line breaks (suitable for log files) if line_breaks is None: - line_breaks = utils.env_flag('PROGRESSBAR_LINE_BREAKS', not - self.is_terminal) + line_breaks = utils.env_flag('PROGRESSBAR_LINE_BREAKS', + not self.is_terminal) self.line_breaks = line_breaks # Check if ANSI escape characters are enabled (suitable for iteractive @@ -155,8 +155,8 @@ def finish(self): # pragma: no cover class StdRedirectMixin(DefaultFdMixin): - def __init__(self, redirect_stderr: bool = False, redirect_stdout: - bool = False, **kwargs): + def __init__(self, redirect_stderr: bool = False, + redirect_stdout: bool = False, **kwargs): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr self.redirect_stdout = redirect_stdout @@ -487,8 +487,8 @@ def data(self): # The seconds since the bar started total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 - seconds_elapsed=(elapsed.seconds % 60) + - (elapsed.microseconds / 1000000.), + seconds_elapsed=(elapsed.seconds % 60) + + (elapsed.microseconds / 1000000.), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 diff --git a/tox.ini b/tox.ini index a36a8ddd..99d982dd 100644 --- a/tox.ini +++ b/tox.ini @@ -38,7 +38,7 @@ commands = sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html {posargs} [flake8] -ignore = W391, W504, E741 +ignore = W391, W504, E741, W503, E131 exclude = docs, progressbar/six.py From 9081722d83dcc049b8e8287f24c0a077ea1408e0 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 5 Jan 2022 14:41:27 +0100 Subject: [PATCH 028/118] Incrementing version to v4.0.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 35b1c9de..98474085 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '3.55.0' +__version__ = '4.0.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 63cd190818e03edf783583955dd2baf082645808 Mon Sep 17 00:00:00 2001 From: William Andre Date: Wed, 8 Jun 2022 17:04:32 +0200 Subject: [PATCH 029/118] Delegate unknown attrs to target in WrappingIO Same idea as fbb2f4d18703f387349e77c126214d8eb9bd89c1 but more generic. Only the flushing should be wrapped, everything else should behave like it would on the target. The issue arises while trying to access attributes like `encoding` or `fileno`. --- progressbar/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index b1be944f..6a322db4 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -199,8 +199,8 @@ def __init__(self, target: types.IO, capturing: bool = False, self.listeners = listeners or set() self.needs_clear = False - def isatty(self): # pragma: no cover - return self.target.isatty() + def __getattr__(self, name): # pragma: no cover + return getattr(self.target, name) def write(self, value: str) -> None: if self.capturing: From 48d5c77f12cc0ba18d2ebfbd7c4d85dabd017657 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 12:47:23 +0200 Subject: [PATCH 030/118] added basic (still broken) typing tests --- .coveragerc | 1 + .github/workflows/main.yml | 1 - progressbar/py.typed | 0 tox.ini | 6 ++++++ 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 progressbar/py.typed diff --git a/.coveragerc b/.coveragerc index 995b08fe..e5ec1f57 100644 --- a/.coveragerc +++ b/.coveragerc @@ -22,3 +22,4 @@ exclude_lines = raise NotImplementedError if 0: if __name__ == .__main__.: + if types.TYPE_CHECKING: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b19e5d8..4c86a063 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,6 +22,5 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox tox-gh-actions - name: Test with tox run: tox diff --git a/progressbar/py.typed b/progressbar/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/tox.ini b/tox.ini index 99d982dd..afba92f9 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,12 @@ basepython = python3 deps = flake8 commands = flake8 {toxinidir}/progressbar {toxinidir}/tests {toxinidir}/examples.py +[testenv:pyright] +changedir = +basepython = python3 +deps = pyright +commands = pyright {toxinidir}/progressbar + [testenv:docs] changedir = basepython = python3 From 94ca752ef39ac61f89945bfaf897e778b77000db Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 13:04:18 +0200 Subject: [PATCH 031/118] added tox to requirements for github --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c86a063..e534cab5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,6 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip tox - name: Test with tox run: tox From 8d81738ddf3818ccdf6457422360e8167279d049 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 13:41:26 +0200 Subject: [PATCH 032/118] fixing (some) github actions warnings --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e534cab5..84ccac34 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,9 +14,9 @@ jobs: python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 86dc62ad6cb781ce42f820515be47b5ba716f87e Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 13:51:52 +0200 Subject: [PATCH 033/118] added multiple threaded progress bars example --- README.rst | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/README.rst b/README.rst index b485fb99..94b33333 100644 --- a/README.rst +++ b/README.rst @@ -237,3 +237,68 @@ Bar with wide Chinese (or other multibyte) characters ) for i in bar(range(10)): time.sleep(0.1) + +Showing multiple (threaded) independent progress bars in parallel +============================================================================== + +While this method works fine and will continue to work fine, a smarter and +fully automatic version of this is currently being made: +https://github.com/WoLpH/python-progressbar/issues/176 + +.. code:: python + + import random + import sys + import threading + import time + + import progressbar + + output_lock = threading.Lock() + + + class LineOffsetStreamWrapper: + UP = '\033[F' + DOWN = '\033[B' + + def __init__(self, lines=0, stream=sys.stderr): + self.stream = stream + self.lines = lines + + def write(self, data): + with output_lock: + self.stream.write(self.UP * self.lines) + self.stream.write(data) + self.stream.write(self.DOWN * self.lines) + self.stream.flush() + + def __getattr__(self, name): + return getattr(self.stream, name) + + + bars = [] + for i in range(5): + bars.append( + progressbar.ProgressBar( + fd=LineOffsetStreamWrapper(i), + max_value=1000, + ) + ) + + if i: + print('Reserve a line for the progressbar') + + + class Worker(threading.Thread): + def __init__(self, bar): + super().__init__() + self.bar = bar + + def run(self): + for i in range(1000): + time.sleep(random.random() / 100) + self.bar.update(i) + + + for bar in bars: + Worker(bar).start() From 32c41ed1d81b39be8afa3f79f50eba6cf549b66a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 13:54:28 +0200 Subject: [PATCH 034/118] Incrementing version to v4.1.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 98474085..fdbcba78 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '4.0.0' +__version__ = '4.1.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From a19340ef77b4d95631af6a2eb644d18de071634f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 14:44:57 +0200 Subject: [PATCH 035/118] Fixed backwards compatibility with original progressbar library --- progressbar/widgets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 9ffb68af..8d81bb6d 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -272,6 +272,9 @@ class Timer(FormatLabel, TimeSensitiveWidgetBase): '''WidgetBase which displays the elapsed seconds.''' def __init__(self, format='Elapsed Time: %(elapsed)s', **kwargs): + if '%s' in format and '%(elapsed)s' not in format: + format = format.replace('%s', '%(elapsed)s') + FormatLabel.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) @@ -373,6 +376,9 @@ def __init__( format_NA='ETA: N/A', **kwargs): + if '%s' in format and '%(eta)s' not in format: + format = format.replace('%s', '%(eta)s') + Timer.__init__(self, **kwargs) self.format_not_started = format_not_started self.format_finished = format_finished From 1022974f44f9b853e07fe6f0065ebd7aea3672c3 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 18 Oct 2022 14:45:27 +0200 Subject: [PATCH 036/118] Incrementing version to v4.1.1 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index fdbcba78..f964fbda 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '4.1.0' +__version__ = '4.1.1' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 2191f63aa72ab5ea20a8269ba09c8ea7efdbe2a4 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 20 Oct 2022 01:20:41 +0200 Subject: [PATCH 037/118] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f3aaf432..38887b7c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015, Rick van Hattem (Wolph) +Copyright (c) 2022, Rick van Hattem (Wolph) All rights reserved. Redistribution and use in source and binary forms, with or without From 7374b1928475addc2f4e2db563881054ceec3326 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 13:54:42 +0200 Subject: [PATCH 038/118] Small documentation tweaks --- docs/index.rst | 2 ++ docs/progressbar.bar.rst | 1 + progressbar/bar.py | 24 ++++++++++++++++-------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index f505cbaa..58b16d58 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,6 +5,7 @@ Welcome to Progress Bar's documentation! .. toctree:: :maxdepth: 4 + usage examples contributing installation @@ -13,6 +14,7 @@ Welcome to Progress Bar's documentation! progressbar.base progressbar.utils progressbar.widgets + history .. include:: ../README.rst diff --git a/docs/progressbar.bar.rst b/docs/progressbar.bar.rst index 971fa84e..7b7a0a39 100644 --- a/docs/progressbar.bar.rst +++ b/docs/progressbar.bar.rst @@ -5,3 +5,4 @@ progressbar.bar module :members: :undoc-members: :show-inheritance: + :member-order: bysource diff --git a/progressbar/bar.py b/progressbar/bar.py index 025471e9..cd094a0a 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -27,6 +27,9 @@ logger = logging.getLogger(__name__) +T = types.TypeVar('T') + + class ProgressBarMixinBase(object): def __init__(self, **kwargs): @@ -265,16 +268,21 @@ class ProgressBar(StdRedirectMixin, ResizableMixin, ProgressBarBase): the current progress bar. As a result, you have access to the ProgressBar's methods and attributes. Although there is nothing preventing you from changing the ProgressBar you should treat it as read only. - - Useful methods and attributes include (Public API): - - value: current progress (min_value <= value <= max_value) - - max_value: maximum (and final) value - - end_time: not None if the bar has finished (reached 100%) - - start_time: the time when start() method of ProgressBar was called - - seconds_elapsed: seconds elapsed since start_time and last call to - update ''' + #: Current progress (min_value <= value <= max_value) + value: T + #: Maximum (and final) value. Beyond this value an error will be raised + #: unless the `max_error` parameter is `False`. + max_value: T + #: The time the progressbar reached `max_value` or when `finish()` was + #: called. + end_time: datetime + #: The time `start()` was called or iteration started. + start_time: datetime + #: Seconds between `start_time` and last call to `update()` + seconds_elapsed: float + _DEFAULT_MAXVAL = base.UnknownLength # update every 50 milliseconds (up to a 20 times per second) _MINIMUM_UPDATE_INTERVAL = 0.050 From 9e7d716eb6c6816ad6b8f01ce5c47ed9404eca5b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 13:54:59 +0200 Subject: [PATCH 039/118] added currval support for legacy progressbar users --- progressbar/bar.py | 10 ++++++++++ tests/test_failure.py | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/progressbar/bar.py b/progressbar/bar.py index cd094a0a..36043303 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -796,6 +796,16 @@ def finish(self, end='\n', dirty=False): ResizableMixin.finish(self) ProgressBarBase.finish(self) + @property + def currval(self): + ''' + Legacy method to make progressbar-2 compatible with the original + progressbar package + ''' + warnings.warn('The usage of `currval` is deprecated, please use ' + '`value` instead', DeprecationWarning) + return self.value + class DataTransferBar(ProgressBar): '''A progress bar with sensible defaults for downloads etc. diff --git a/tests/test_failure.py b/tests/test_failure.py index 40fee23c..030ab292 100644 --- a/tests/test_failure.py +++ b/tests/test_failure.py @@ -99,6 +99,13 @@ def test_deprecated_poll(): progressbar.ProgressBar(poll=5) +def test_deprecated_currval(): + with pytest.warns(DeprecationWarning): + bar = progressbar.ProgressBar(max_value=5) + bar.update(2) + assert bar.currval == 2 + + def test_unexpected_update_keyword_arg(): p = progressbar.ProgressBar(max_value=10) with pytest.raises(TypeError): From 3fa2c2de41e280c156549dc1f3a9781aecdeddaa Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 14:14:41 +0200 Subject: [PATCH 040/118] added method for easy incrementing --- progressbar/bar.py | 5 ++++- tests/test_iterators.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 36043303..bb3db497 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -576,7 +576,10 @@ def __enter__(self): def __iadd__(self, value): 'Updates the ProgressBar by adding a new value.' - self.update(self.value + value) + return self.increment(value) + + def increment(self, value, *args, **kwargs): + self.update(self.value + value, *args, **kwargs) return self def _format_widgets(self): diff --git a/tests/test_iterators.py b/tests/test_iterators.py index 188809a3..b32c529e 100644 --- a/tests/test_iterators.py +++ b/tests/test_iterators.py @@ -51,7 +51,8 @@ def test_adding_value(): p = progressbar.ProgressBar(max_value=10) p.start() p.update(5) - p += 5 + p += 2 + p.increment(2) with pytest.raises(ValueError): p += 5 From cad33aaed2acbec526b3e6717448e18378493dcc Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 15:39:36 +0200 Subject: [PATCH 041/118] added usage doc --- docs/usage.rst | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/usage.rst diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 00000000..6aa03303 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,70 @@ +======== +Usage +======== + +There are many ways to use Python Progressbar, you can see a few basic examples +here but there are many more in the :doc:`examples` file. + +Wrapping an iterable +------------------------------------------------------------------------------ +:: + + import time + import progressbar + + bar = progressbar.ProgressBar() + for i in bar(range(100)): + time.sleep(0.02) + +Context wrapper +------------------------------------------------------------------------------ +:: + + import time + import progressbar + + with progressbar.ProgressBar(max_value=10) as bar: + for i in range(10): + time.sleep(0.1) + bar.update(i) + +Combining progressbars with print output +------------------------------------------------------------------------------ +:: + + import time + import progressbar + + bar = progressbar.ProgressBar(redirect_stdout=True) + for i in range(100): + print 'Some text', i + time.sleep(0.1) + bar.update(i) + +Progressbar with unknown length +------------------------------------------------------------------------------ +:: + + import time + import progressbar + + bar = progressbar.ProgressBar(max_value=progressbar.UnknownLength) + for i in range(20): + time.sleep(0.1) + bar.update(i) + +Bar with custom widgets +------------------------------------------------------------------------------ +:: + + import time + import progressbar + + bar = progressbar.ProgressBar(widgets=[ + ' [', progressbar.Timer(), '] ', + progressbar.Bar(), + ' (', progressbar.ETA(), ') ', + ]) + for i in bar(range(20)): + time.sleep(0.1) + From aea3ec338a4b76a71be75e17e09d1ff80044d416 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 15:42:38 +0200 Subject: [PATCH 042/118] added history doc --- docs/history.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docs/history.rst diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 00000000..91f04cb8 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,8 @@ +.. _history: + +======= +History +======= + +.. include:: ../CHANGES.rst + :start-line: 5 From ec8e228e0d03e43eb917e790e833bfefe6dbd899 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 15:45:18 +0200 Subject: [PATCH 043/118] added default value to increment method --- progressbar/bar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index bb3db497..691a61ea 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -578,7 +578,7 @@ def __iadd__(self, value): 'Updates the ProgressBar by adding a new value.' return self.increment(value) - def increment(self, value, *args, **kwargs): + def increment(self, value=1, *args, **kwargs): self.update(self.value + value, *args, **kwargs) return self From 0a099b01b206cd3415b94688e72f4c4c72797dec Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 26 Oct 2022 15:47:23 +0200 Subject: [PATCH 044/118] Incrementing version to v4.2.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index f964fbda..bc898708 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -19,7 +19,7 @@ long running operations. '''.strip().split()) __email__ = 'wolph@wol.ph' -__version__ = '4.1.1' +__version__ = '4.2.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 2330478c0e7b284dc6b443f9390e7075ea494353 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 27 Oct 2022 12:23:03 +0200 Subject: [PATCH 045/118] reformatted and added more type hinting --- progressbar/bar.py | 189 ++++++++++++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 69 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 691a61ea..4cb79f5d 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,42 +1,40 @@ from __future__ import annotations import logging -import math import os import sys import time import timeit import warnings +from abc import ABC from copy import deepcopy from datetime import datetime -from python_utils import types - -try: # pragma: no cover - from collections import abc -except ImportError: # pragma: no cover - import collections as abc - -from python_utils import converters +import math +from python_utils import converters, types -from . import widgets -from . import widgets as widgets_module # Avoid name collision -from . import base -from . import utils +from . import ( + base, + utils, + widgets, + widgets as widgets_module, # Avoid name collision +) logger = logging.getLogger(__name__) - T = types.TypeVar('T') class ProgressBarMixinBase(object): + _started = False + _finished = False + term_width: int = 80 def __init__(self, **kwargs): - self._finished = False + pass def start(self, **kwargs): - pass + self._started = True def update(self, value=None): pass @@ -45,23 +43,36 @@ def finish(self): # pragma: no cover self._finished = True def __del__(self): - if not self._finished: # pragma: no cover + if not self._finished and self._started: # pragma: no cover try: self.finish() except Exception: - pass + # Never raise during cleanup. We're too late now + logging.debug( + 'Exception raised during ProgressBar cleanup', + exc_info=True + ) + def __getstate__(self): + return self.__dict__ -class ProgressBarBase(abc.Iterable, ProgressBarMixinBase): + +class ProgressBarBase(types.Iterable, ProgressBarMixinBase, ABC): pass class DefaultFdMixin(ProgressBarMixinBase): - - def __init__(self, fd: types.IO = sys.stderr, - is_terminal: bool | None = None, - line_breaks: bool | None = None, - enable_colors: bool | None = None, **kwargs): + fd: types.IO = sys.stderr + is_ansi_terminal: bool = False + line_breaks: bool = True + enable_colors: bool = False + + def __init__( + self, fd: types.IO = sys.stderr, + is_terminal: bool | None = None, + line_breaks: bool | None = None, + enable_colors: bool | None = None, **kwargs + ): if fd is sys.stdout: fd = utils.streams.original_stdout @@ -73,22 +84,27 @@ def __init__(self, fd: types.IO = sys.stderr, # Check if this is an interactive terminal self.is_terminal = utils.is_terminal( - fd, is_terminal or self.is_ansi_terminal) + fd, is_terminal or self.is_ansi_terminal + ) # Check if it should overwrite the current line (suitable for # iteractive terminals) or write line breaks (suitable for log files) if line_breaks is None: - line_breaks = utils.env_flag('PROGRESSBAR_LINE_BREAKS', - not self.is_terminal) - self.line_breaks = line_breaks + line_breaks = utils.env_flag( + 'PROGRESSBAR_LINE_BREAKS', + not self.is_terminal + ) + self.line_breaks = bool(line_breaks) # Check if ANSI escape characters are enabled (suitable for iteractive # terminals), or should be stripped off (suitable for log files) if enable_colors is None: - enable_colors = utils.env_flag('PROGRESSBAR_ENABLE_COLORS', - self.is_ansi_terminal) + enable_colors = utils.env_flag( + 'PROGRESSBAR_ENABLE_COLORS', + self.is_ansi_terminal + ) - self.enable_colors = enable_colors + self.enable_colors = bool(enable_colors) ProgressBarMixinBase.__init__(self, **kwargs) @@ -121,6 +137,16 @@ def finish(self, *args, **kwargs): # pragma: no cover self.fd.flush() + def _format_line(self): + 'Joins the widgets and justifies the line' + + widgets = ''.join(self._to_unicode(self._format_widgets())) + + if self.left_justify: + return widgets.ljust(self.term_width) + else: + return widgets.rjust(self.term_width) + class ResizableMixin(ProgressBarMixinBase): @@ -157,9 +183,17 @@ def finish(self): # pragma: no cover class StdRedirectMixin(DefaultFdMixin): - - def __init__(self, redirect_stderr: bool = False, - redirect_stdout: bool = False, **kwargs): + redirect_stderr: bool = False + redirect_stdout: bool = False + stdout: types.IO + stderr: types.IO + _stdout: types.IO + _stderr: types.IO + + def __init__( + self, redirect_stderr: bool = False, + redirect_stdout: bool = False, **kwargs + ): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr self.redirect_stdout = redirect_stdout @@ -199,7 +233,12 @@ def finish(self, end='\n'): utils.streams.unwrap_stderr() -class ProgressBar(StdRedirectMixin, ResizableMixin, ProgressBarBase): +class ProgressBar( + StdRedirectMixin, + ResizableMixin, + ProgressBarBase, + types.Generic[T], +): '''The ProgressBar class which updates and prints the bar. Args: @@ -287,11 +326,13 @@ class ProgressBar(StdRedirectMixin, ResizableMixin, ProgressBarBase): # update every 50 milliseconds (up to a 20 times per second) _MINIMUM_UPDATE_INTERVAL = 0.050 - def __init__(self, min_value=0, max_value=None, widgets=None, - left_justify=True, initial_value=0, poll_interval=None, - widget_kwargs=None, custom_len=utils.len_color, - max_error=True, prefix=None, suffix=None, variables=None, - min_poll_interval=None, **kwargs): + def __init__( + self, min_value=0, max_value=None, widgets=None, + left_justify=True, initial_value=0, poll_interval=None, + widget_kwargs=None, custom_len=utils.len_color, + max_error=True, prefix=None, suffix=None, variables=None, + min_poll_interval=None, **kwargs + ): ''' Initializes a progress bar with sane defaults ''' @@ -299,19 +340,25 @@ def __init__(self, min_value=0, max_value=None, widgets=None, ResizableMixin.__init__(self, **kwargs) ProgressBarBase.__init__(self, **kwargs) if not max_value and kwargs.get('maxval') is not None: - warnings.warn('The usage of `maxval` is deprecated, please use ' - '`max_value` instead', DeprecationWarning) + warnings.warn( + 'The usage of `maxval` is deprecated, please use ' + '`max_value` instead', DeprecationWarning + ) max_value = kwargs.get('maxval') if not poll_interval and kwargs.get('poll'): - warnings.warn('The usage of `poll` is deprecated, please use ' - '`poll_interval` instead', DeprecationWarning) + warnings.warn( + 'The usage of `poll` is deprecated, please use ' + '`poll_interval` instead', DeprecationWarning + ) poll_interval = kwargs.get('poll') if max_value: if min_value > max_value: - raise ValueError('Max value needs to be bigger than the min ' - 'value') + raise ValueError( + 'Max value needs to be bigger than the min ' + 'value' + ) self.min_value = min_value self.max_value = max_value self.max_error = max_error @@ -346,10 +393,13 @@ def __init__(self, min_value=0, max_value=None, widgets=None, # comparison is run for _every_ update. With billions of updates # (downloading a 1GiB file for example) this adds up. poll_interval = utils.deltas_to_seconds(poll_interval, default=None) - min_poll_interval = utils.deltas_to_seconds(min_poll_interval, - default=None) + min_poll_interval = utils.deltas_to_seconds( + min_poll_interval, + default=None + ) self._MINIMUM_UPDATE_INTERVAL = utils.deltas_to_seconds( - self._MINIMUM_UPDATE_INTERVAL) + self._MINIMUM_UPDATE_INTERVAL + ) # Note that the _MINIMUM_UPDATE_INTERVAL sets the minimum in case of # low values. @@ -520,7 +570,8 @@ def default_widgets(self): widgets.Percentage(**self.widget_kwargs), ' ', widgets.SimpleProgress( format='(%s)' % widgets.SimpleProgress.DEFAULT_FORMAT, - **self.widget_kwargs), + **self.widget_kwargs + ), ' ', widgets.Bar(**self.widget_kwargs), ' ', widgets.Timer(**self.widget_kwargs), ' ', widgets.AdaptiveETA(**self.widget_kwargs), @@ -590,7 +641,7 @@ def _format_widgets(self): for index, widget in enumerate(self.widgets): if isinstance(widget, widgets.WidgetBase) \ - and not widget.check_size(self): + and not widget.check_size(self): continue elif isinstance(widget, widgets.AutoWidthWidgetBase): result.append(widget) @@ -621,16 +672,6 @@ def _to_unicode(cls, args): for arg in args: yield converters.to_unicode(arg) - def _format_line(self): - 'Joins the widgets and justifies the line' - - widgets = ''.join(self._to_unicode(self._format_widgets())) - - if self.left_justify: - return widgets.ljust(self.term_width) - else: - return widgets.rjust(self.term_width) - def _needs_update(self): 'Returns whether the ProgressBar should redraw the line.' delta = timeit.default_timer() - self._last_update_timer @@ -671,7 +712,8 @@ def update(self, value=None, force=False, **kwargs): elif self.max_error: raise ValueError( 'Value %s is out of range, should be between %s and %s' - % (value, self.min_value, self.max_value)) + % (value, self.min_value, self.max_value) + ) else: self.max_value = value @@ -684,7 +726,8 @@ def update(self, value=None, force=False, **kwargs): if key not in self.variables: raise TypeError( 'update() got an unexpected keyword ' + - 'argument {0!r}'.format(key)) + 'argument {0!r}'.format(key) + ) elif self.variables[key] != kwargs[key]: self.variables[key] = kwargs[key] variables_changed = True @@ -738,15 +781,21 @@ def start(self, max_value=None, init=True): self.widgets = self.default_widgets() if self.prefix: - self.widgets.insert(0, widgets.FormatLabel( - self.prefix, new_style=True)) + self.widgets.insert( + 0, widgets.FormatLabel( + self.prefix, new_style=True + ) + ) # Unset the prefix variable after applying so an extra start() # won't keep copying it self.prefix = None if self.suffix: - self.widgets.append(widgets.FormatLabel( - self.suffix, new_style=True)) + self.widgets.append( + widgets.FormatLabel( + self.suffix, new_style=True + ) + ) # Unset the suffix variable after applying so an extra start() # won't keep copying it self.suffix = None @@ -805,8 +854,10 @@ def currval(self): Legacy method to make progressbar-2 compatible with the original progressbar package ''' - warnings.warn('The usage of `currval` is deprecated, please use ' - '`value` instead', DeprecationWarning) + warnings.warn( + 'The usage of `currval` is deprecated, please use ' + '`value` instead', DeprecationWarning + ) return self.value From 633585091b53ae08c2c1f174e6eacf0f1880bafa Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Fri, 28 Oct 2022 20:03:43 +0200 Subject: [PATCH 046/118] Much more type hinting --- progressbar/widgets.py | 426 ++++++++++++++++++++++++++++------------- 1 file changed, 291 insertions(+), 135 deletions(-) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 8d81bb6d..30ca01a5 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- +from __future__ import annotations import abc import datetime import functools import pprint import sys +import typing -from python_utils import converters -from python_utils import types +from python_utils import converters, types -from . import base -from . import utils +from . import base, utils if types.TYPE_CHECKING: from .bar import ProgressBar @@ -18,6 +18,9 @@ MAX_TIME = datetime.time.max MAX_DATETIME = datetime.datetime.max +Data = types.Dict[str, types.Any] +FormatString = typing.Optional[str] + def string_or_lambda(input_): if isinstance(input_, str): @@ -49,24 +52,26 @@ def create_wrapper(wrapper): if isinstance(wrapper, str): assert '{}' in wrapper, 'Expected string with {} for formatting' else: - raise RuntimeError('Pass either a begin/end string as a tuple or a' - ' template string with {}') + raise RuntimeError( + 'Pass either a begin/end string as a tuple or a' + ' template string with {}' + ) return wrapper -def wrapper(function, wrapper): +def wrapper(function, wrapper_): '''Wrap the output of a function in a template string or a tuple with begin/end strings ''' - wrapper = create_wrapper(wrapper) - if not wrapper: + wrapper_ = create_wrapper(wrapper_) + if not wrapper_: return function @functools.wraps(function) def wrap(*args, **kwargs): - return wrapper.format(function(*args, **kwargs)) + return wrapper_.format(function(*args, **kwargs)) return wrap @@ -74,7 +79,7 @@ def wrap(*args, **kwargs): def create_marker(marker, wrap=None): def _marker(progress, data, width): if progress.max_value is not base.UnknownLength \ - and progress.max_value > 0: + and progress.max_value > 0: length = int(progress.value / progress.max_value * width) return (marker * length) else: @@ -89,7 +94,7 @@ def _marker(progress, data, width): return wrapper(marker, wrap) -class FormatWidgetMixin(object): +class FormatWidgetMixin(abc.ABC): '''Mixin to format widgets using a formatstring Variables available: @@ -104,16 +109,25 @@ class FormatWidgetMixin(object): days - percentage: Percentage as a float ''' - required_values = [] - def __init__(self, format, new_style=False, **kwargs): + def __init__(self, format: str, new_style: bool = False, **kwargs): self.new_style = new_style self.format = format - def get_format(self, progress, data, format=None): + def get_format( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None + ) -> str: return format or self.format - def __call__(self, progress, data, format=None): + def __call__( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None, + ): '''Formats the widget into a string''' format = self.get_format(progress, data, format) try: @@ -127,7 +141,7 @@ def __call__(self, progress, data, format=None): raise -class WidthWidgetMixin(object): +class WidthWidgetMixin(abc.ABC): '''Mixing to make sure widgets are only visible if the screen is within a specified size range so the progressbar fits on both large and small screens.. @@ -136,7 +150,7 @@ class WidthWidgetMixin(object): - min_width: Only display the widget if at least `min_width` is left - max_width: Only display the widget if at most `max_width` is left - >>> class Progress(object): + >>> class Progress: ... term_width = 0 >>> WidthWidgetMixin(5, 10).check_size(Progress) @@ -165,8 +179,7 @@ def check_size(self, progress: 'ProgressBar'): return True -class WidgetBase(WidthWidgetMixin): - __metaclass__ = abc.ABCMeta +class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): '''The base class for all widgets The ProgressBar will call the widget's update value when the widget should @@ -196,14 +209,14 @@ class WidgetBase(WidthWidgetMixin): copy = True @abc.abstractmethod - def __call__(self, progress, data): + def __call__(self, progress: ProgressBar, data: Data): '''Updates the widget. progress - a reference to the calling ProgressBar ''' -class AutoWidthWidgetBase(WidgetBase): +class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): '''The base class for all variable width widgets. This widget is much like the \\hfill command in TeX, it will expand to @@ -212,7 +225,12 @@ class AutoWidthWidgetBase(WidgetBase): ''' @abc.abstractmethod - def __call__(self, progress, data, width): + def __call__( + self, + progress: ProgressBar, + data: Data, + width: int = 0, + ): '''Updates the widget providing the total width the widget must fill. progress - a reference to the calling ProgressBar @@ -220,7 +238,7 @@ def __call__(self, progress, data, width): ''' -class TimeSensitiveWidgetBase(WidgetBase): +class TimeSensitiveWidgetBase(WidgetBase, metaclass=abc.ABCMeta): '''The base class for all time sensitive widgets. Some widgets like timers would become out of date unless updated at least @@ -233,7 +251,7 @@ class FormatLabel(FormatWidgetMixin, WidgetBase): '''Displays a formatted label >>> label = FormatLabel('%(value)s', min_width=5, max_width=10) - >>> class Progress(object): + >>> class Progress: ... pass >>> label = FormatLabel('{value} :: {value:^6}', new_style=True) >>> str(label(Progress, dict(value='test'))) @@ -255,7 +273,12 @@ def __init__(self, format: str, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data, **kwargs): + def __call__( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None, + ): for name, (key, transform) in self.mapping.items(): try: if transform is None: @@ -265,7 +288,7 @@ def __call__(self, progress, data, **kwargs): except (KeyError, ValueError, IndexError): # pragma: no cover pass - return FormatWidgetMixin.__call__(self, progress, data, **kwargs) + return FormatWidgetMixin.__call__(self, progress, data, format) class Timer(FormatLabel, TimeSensitiveWidgetBase): @@ -282,7 +305,7 @@ def __init__(self, format='Elapsed Time: %(elapsed)s', **kwargs): format_time = staticmethod(utils.format_time) -class SamplesMixin(TimeSensitiveWidgetBase): +class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): ''' Mixing for widgets that average multiple measurements @@ -314,19 +337,21 @@ class SamplesMixin(TimeSensitiveWidgetBase): True ''' - def __init__(self, samples=datetime.timedelta(seconds=2), key_prefix=None, - **kwargs): + def __init__( + self, samples=datetime.timedelta(seconds=2), key_prefix=None, + **kwargs, + ): self.samples = samples self.key_prefix = (self.__class__.__name__ or key_prefix) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) - def get_sample_times(self, progress, data): + def get_sample_times(self, progress: ProgressBar, data: Data): return progress.extra.setdefault(self.key_prefix + 'sample_times', []) - def get_sample_values(self, progress, data): + def get_sample_values(self, progress: ProgressBar, data: Data): return progress.extra.setdefault(self.key_prefix + 'sample_values', []) - def __call__(self, progress, data, delta=False): + def __call__(self, progress: ProgressBar, data: Data, delta: bool = False): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -368,13 +393,14 @@ class ETA(Timer): '''WidgetBase which attempts to estimate the time of arrival.''' def __init__( - self, - format_not_started='ETA: --:--:--', - format_finished='Time: %(elapsed)8s', - format='ETA: %(eta)8s', - format_zero='ETA: 00:00:00', - format_NA='ETA: N/A', - **kwargs): + self, + format_not_started='ETA: --:--:--', + format_finished='Time: %(elapsed)8s', + format='ETA: %(eta)8s', + format_zero='ETA: 00:00:00', + format_NA='ETA: N/A', + **kwargs, + ): if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -386,7 +412,7 @@ def __init__( self.format_zero = format_zero self.format_NA = format_NA - def _calculate_eta(self, progress, data, value, elapsed): + def _calculate_eta(self, progress: ProgressBar, data: Data, value, elapsed): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: # The max() prevents zero division errors @@ -398,7 +424,13 @@ def _calculate_eta(self, progress, data, value, elapsed): return eta_seconds - def __call__(self, progress, data, value=None, elapsed=None): + def __call__( + self, + progress: ProgressBar, + data: Data, + value=None, + elapsed=None, + ): '''Updates the widget to show the ETA or total time when finished.''' if value is None: value = data['value'] @@ -409,7 +441,8 @@ def __call__(self, progress, data, value=None, elapsed=None): ETA_NA = False try: data['eta_seconds'] = self._calculate_eta( - progress, data, value=value, elapsed=elapsed) + progress, data, value=value, elapsed=elapsed + ) except TypeError: data['eta_seconds'] = None ETA_NA = True @@ -438,7 +471,7 @@ def __call__(self, progress, data, value=None, elapsed=None): class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' - def _calculate_eta(self, progress, data, value, elapsed): + def _calculate_eta(self, progress: ProgressBar, data: Data, value, elapsed): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() try: @@ -447,13 +480,16 @@ def _calculate_eta(self, progress, data, value, elapsed): return datetime.datetime.max def __init__( - self, - format_not_started='Estimated finish time: ----/--/-- --:--:--', - format_finished='Finished at: %(elapsed)s', - format='Estimated finish time: %(eta)s', - **kwargs): - ETA.__init__(self, format_not_started=format_not_started, - format_finished=format_finished, format=format, **kwargs) + self, + format_not_started='Estimated finish time: ----/--/-- --:--:--', + format_finished='Finished at: %(elapsed)s', + format='Estimated finish time: %(eta)s', + **kwargs, + ): + ETA.__init__( + self, format_not_started=format_not_started, + format_finished=format_finished, format=format, **kwargs, + ) class AdaptiveETA(ETA, SamplesMixin): @@ -467,9 +503,17 @@ def __init__(self, **kwargs): ETA.__init__(self, **kwargs) SamplesMixin.__init__(self, **kwargs) - def __call__(self, progress, data): - elapsed, value = SamplesMixin.__call__(self, progress, data, - delta=True) + def __call__( + self, + progress: ProgressBar, + data: Data, + value=None, + elapsed=None, + ): + elapsed, value = SamplesMixin.__call__( + self, progress, data, + delta=True + ) if not elapsed: value = None elapsed = 0 @@ -486,17 +530,23 @@ class DataSize(FormatWidgetMixin, WidgetBase): ''' def __init__( - self, variable='value', - format='%(scaled)5.1f %(prefix)s%(unit)s', unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs): + self, variable='value', + format='%(scaled)5.1f %(prefix)s%(unit)s', unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, + ): self.variable = variable self.unit = unit self.prefixes = prefixes FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data): + def __call__( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None, + ): value = data[self.variable] if value is not None: scaled, power = utils.scale_1024(value, len(self.prefixes)) @@ -507,7 +557,7 @@ def __call__(self, progress, data): data['prefix'] = self.prefixes[power] data['unit'] = self.unit - return FormatWidgetMixin.__call__(self, progress, data) + return FormatWidgetMixin.__call__(self, progress, data, format) class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): @@ -516,10 +566,11 @@ class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' def __init__( - self, format='%(scaled)5.1f %(prefix)s%(unit)-s/s', - inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs): + self, format='%(scaled)5.1f %(prefix)s%(unit)-s/s', + inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, + ): self.unit = unit self.prefixes = prefixes self.inverse_format = inverse_format @@ -530,17 +581,24 @@ def _speed(self, value, elapsed): speed = float(value) / elapsed return utils.scale_1024(speed, len(self.prefixes)) - def __call__(self, progress, data, value=None, total_seconds_elapsed=None): + def __call__( + self, + progress: ProgressBar, + data, + value=None, + total_seconds_elapsed=None + ): '''Updates the widget with the current SI prefixed speed.''' if value is None: value = data['value'] elapsed = utils.deltas_to_seconds( total_seconds_elapsed, - data['total_seconds_elapsed']) + data['total_seconds_elapsed'] + ) if value is not None and elapsed is not None \ - and elapsed > 2e-6 and value > 2e-6: # =~ 0 + and elapsed > 2e-6 and value > 2e-6: # =~ 0 scaled, power = self._speed(value, elapsed) else: scaled = power = 0 @@ -551,8 +609,10 @@ def __call__(self, progress, data, value=None, total_seconds_elapsed=None): scaled = 1 / scaled data['scaled'] = scaled data['prefix'] = self.prefixes[0] - return FormatWidgetMixin.__call__(self, progress, data, - self.inverse_format) + return FormatWidgetMixin.__call__( + self, progress, data, + self.inverse_format + ) else: data['scaled'] = scaled data['prefix'] = self.prefixes[power] @@ -567,9 +627,17 @@ def __init__(self, **kwargs): FileTransferSpeed.__init__(self, **kwargs) SamplesMixin.__init__(self, **kwargs) - def __call__(self, progress, data): - elapsed, value = SamplesMixin.__call__(self, progress, data, - delta=True) + def __call__( + self, + progress: ProgressBar, + data, + value=None, + total_seconds_elapsed=None + ): + elapsed, value = SamplesMixin.__call__( + self, progress, data, + delta=True + ) return FileTransferSpeed.__call__(self, progress, data, value, elapsed) @@ -578,8 +646,10 @@ class AnimatedMarker(TimeSensitiveWidgetBase): it were rotating. ''' - def __init__(self, markers='|/-\\', default=None, fill='', - marker_wrap=None, fill_wrap=None, **kwargs): + def __init__( + self, markers='|/-\\', default=None, fill='', + marker_wrap=None, fill_wrap=None, **kwargs, + ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) self.default = default or markers[0] @@ -587,7 +657,7 @@ def __init__(self, markers='|/-\\', default=None, fill='', self.fill = create_marker(fill, self.fill_wrap) if fill else None WidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data, width=None): + def __call__(self, progress: ProgressBar, data: Data, width=None): '''Updates the widget to show the next marker or the first marker when finished''' @@ -600,8 +670,11 @@ def __call__(self, progress, data, width=None): if self.fill: # Cut the last character so we can replace it with our marker - fill = self.fill(progress, data, width - progress.custom_len( - marker)) + fill = self.fill( + progress, data, width - progress.custom_len( + marker + ) + ) else: fill = '' @@ -627,7 +700,7 @@ def __init__(self, format='%(value)d', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - def __call__(self, progress, data, format=None): + def __call__(self, progress: ProgressBar, data: Data, format=None): return FormatWidgetMixin.__call__(self, progress, data, format) @@ -639,7 +712,7 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - def get_format(self, progress, data, format=None): + def get_format(self, progress: ProgressBar, data: Data, format=None): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: @@ -658,7 +731,7 @@ def __init__(self, format=DEFAULT_FORMAT, **kwargs): WidgetBase.__init__(self, format=format, **kwargs) self.max_width_cache = dict(default=self.max_width) - def __call__(self, progress, data, format=None): + def __call__(self, progress: ProgressBar, data: Data, format=None): # If max_value is not available, display N/A if data.get('max_value'): data['max_value_s'] = data.get('max_value') @@ -671,8 +744,10 @@ def __call__(self, progress, data, format=None): else: data['value_s'] = 0 - formatted = FormatWidgetMixin.__call__(self, progress, data, - format=format) + formatted = FormatWidgetMixin.__call__( + self, progress, data, + format=format + ) # Guess the maximum width from the min and max value key = progress.min_value, progress.max_value @@ -684,8 +759,11 @@ def __call__(self, progress, data, format=None): continue temporary_data['value'] = value - width = progress.custom_len(FormatWidgetMixin.__call__( - self, progress, temporary_data, format=format)) + width = progress.custom_len( + FormatWidgetMixin.__call__( + self, progress, temporary_data, format=format + ) + ) if width: # pragma: no branch max_width = max(max_width or 0, width) @@ -701,8 +779,10 @@ def __call__(self, progress, data, format=None): class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' - def __init__(self, marker='#', left='|', right='|', fill=' ', - fill_left=True, marker_wrap=None, **kwargs): + def __init__( + self, marker='#', left='|', right='|', fill=' ', + fill_left=True, marker_wrap=None, **kwargs, + ): '''Creates a customizable progress bar. The callable takes the same parameters as the `__call__` method @@ -722,7 +802,12 @@ def __init__(self, marker='#', left='|', right='|', fill=' ', AutoWidthWidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data, width): + def __call__( + self, + progress: ProgressBar, + data: Data, + width: int = 0, + ): '''Updates the progress bar and its subcomponents''' left = converters.to_unicode(self.left(progress, data, width)) @@ -745,8 +830,10 @@ def __call__(self, progress, data, width): class ReverseBar(Bar): '''A bar which has a marker that goes from right to left''' - def __init__(self, marker='#', left='|', right='|', fill=' ', - fill_left=False, **kwargs): + def __init__( + self, marker='#', left='|', right='|', fill=' ', + fill_left=False, **kwargs, + ): '''Creates a customizable progress bar. marker - string or updatable object to use as a marker @@ -755,8 +842,10 @@ def __init__(self, marker='#', left='|', right='|', fill=' ', fill - character to use for the empty part of the progress bar fill_left - whether to fill from the left or the right ''' - Bar.__init__(self, marker=marker, left=left, right=right, fill=fill, - fill_left=fill_left, **kwargs) + Bar.__init__( + self, marker=marker, left=left, right=right, fill=fill, + fill_left=fill_left, **kwargs, + ) class BouncingBar(Bar, TimeSensitiveWidgetBase): @@ -764,7 +853,12 @@ class BouncingBar(Bar, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(milliseconds=100) - def __call__(self, progress, data, width): + def __call__( + self, + progress: ProgressBar, + data: Data, + width: int = 0, + ): '''Updates the progress bar and its subcomponents''' left = converters.to_unicode(self.left(progress, data, width)) @@ -776,7 +870,8 @@ def __call__(self, progress, data, width): if width: # pragma: no branch value = int( - data['total_seconds_elapsed'] / self.INTERVAL.total_seconds()) + data['total_seconds_elapsed'] / self.INTERVAL.total_seconds() + ) a = value % width b = width - a - 1 @@ -792,24 +887,35 @@ def __call__(self, progress, data, width): class FormatCustomText(FormatWidgetMixin, WidgetBase): - mapping = {} + mapping: types.Dict[str, types.Any] = {} copy = False - def __init__(self, format, mapping=mapping, **kwargs): + def __init__( + self, + format: str, + mapping: types.Dict[str, types.Any] = None, + **kwargs, + ): self.format = format - self.mapping = mapping + self.mapping = mapping or self.mapping FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) - def update_mapping(self, **mapping): + def update_mapping(self, **mapping: types.Dict[str, types.Any]): self.mapping.update(mapping) - def __call__(self, progress, data): + def __call__( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None, + ): return FormatWidgetMixin.__call__( - self, progress, self.mapping, self.format) + self, progress, self.mapping, format or self.format + ) -class VariableMixin(object): +class VariableMixin: '''Mixin to display a custom user variable ''' def __init__(self, name, **kwargs): @@ -843,10 +949,15 @@ def __init__(self, name, markers, **kwargs): for marker in markers ] - def get_values(self, progress, data): + def get_values(self, progress: ProgressBar, data: Data): return data['variables'][self.name] or [] - def __call__(self, progress, data, width): + def __call__( + self, + progress: ProgressBar, + data: Data, + width: int = 0, + ): '''Updates the progress bar and its subcomponents''' left = converters.to_unicode(self.left(progress, data, width)) @@ -877,37 +988,43 @@ def __call__(self, progress, data, width): class MultiProgressBar(MultiRangeBar): - def __init__(self, - name, - # NOTE: the markers are not whitespace even though some - # terminals don't show the characters correctly! - markers=' ▁▂▃▄▅▆▇█', - **kwargs): - MultiRangeBar.__init__(self, name=name, - markers=list(reversed(markers)), **kwargs) - - def get_values(self, progress, data): + def __init__( + self, + name, + # NOTE: the markers are not whitespace even though some + # terminals don't show the characters correctly! + markers=' ▁▂▃▄▅▆▇█', + **kwargs, + ): + MultiRangeBar.__init__( + self, name=name, + markers=list(reversed(markers)), **kwargs, + ) + + def get_values(self, progress: ProgressBar, data: Data): ranges = [0] * len(self.markers) - for progress in data['variables'][self.name] or []: - if not isinstance(progress, (int, float)): + for value in data['variables'][self.name] or []: + if not isinstance(value, (int, float)): # Progress is (value, max) - progress_value, progress_max = progress - progress = float(progress_value) / float(progress_max) + progress_value, progress_max = value + value = float(progress_value) / float(progress_max) - if progress < 0 or progress > 1: + if not 0 <= value <= 1: raise ValueError( 'Range value needs to be in the range [0..1], got %s' % - progress) + value + ) - range_ = progress * (len(ranges) - 1) + range_ = value * (len(ranges) - 1) pos = int(range_) frac = range_ % 1 - ranges[pos] += (1 - frac) - if (frac): - ranges[pos + 1] += (frac) + ranges[pos] += 1 - frac + if frac: + ranges[pos + 1] += frac if self.fill_left: ranges = list(reversed(ranges)) + return ranges @@ -936,8 +1053,10 @@ class GranularBar(AutoWidthWidgetBase): for example ''' - def __init__(self, markers=GranularMarkers.smooth, left='|', right='|', - **kwargs): + def __init__( + self, markers=GranularMarkers.smooth, left='|', right='|', + **kwargs, + ): '''Creates a customizable progress bar. markers - string of characters to use as granular progress markers. The @@ -952,13 +1071,18 @@ def __init__(self, markers=GranularMarkers.smooth, left='|', right='|', AutoWidthWidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data, width): + def __call__( + self, + progress: ProgressBar, + data: Data, + width: int = 0, + ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) if progress.max_value is not base.UnknownLength \ - and progress.max_value > 0: + and progress.max_value > 0: percent = progress.value / progress.max_value else: percent = 0 @@ -987,7 +1111,13 @@ def __init__(self, format, **kwargs): FormatLabel.__init__(self, format, **kwargs) Bar.__init__(self, **kwargs) - def __call__(self, progress, data, width, format=None): + def __call__( # type: ignore + self, + progress: ProgressBar, + data: Data, + width: int = 0, + format: FormatString = None, + ): center = FormatLabel.__call__(self, progress, data, format=format) bar = Bar.__call__(self, progress, data, width) @@ -1007,12 +1137,25 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): Percentage.__init__(self, format, na=na, **kwargs) FormatLabelBar.__init__(self, format, **kwargs) + def __call__( # type: ignore + self, + progress: ProgressBar, + data: Data, + width: int = 0, + format: FormatString = None, + ): + return super().__call__(progress, data, width, format=format) + + + class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' - def __init__(self, name, format='{name}: {formatted_value}', - width=6, precision=3, **kwargs): + def __init__( + self, name, format='{name}: {formatted_value}', + width=6, precision=3, **kwargs, + ): '''Creates a Variable associated with the given name.''' self.format = format self.width = width @@ -1020,7 +1163,12 @@ def __init__(self, name, format='{name}: {formatted_value}', VariableMixin.__init__(self, name=name) WidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data): + def __call__( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None + ): value = data['variables'][self.name] context = data.copy() context['value'] = value @@ -1037,7 +1185,8 @@ def __call__(self, progress, data): except (TypeError, ValueError): if value: context['formatted_value'] = '{value:{width}}'.format( - **context) + **context + ) else: context['formatted_value'] = '-' * self.width @@ -1053,17 +1202,24 @@ class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): '''Widget which displays the current (date)time with seconds resolution.''' INTERVAL = datetime.timedelta(seconds=1) - def __init__(self, format='Current Time: %(current_time)s', - microseconds=False, **kwargs): + def __init__( + self, format='Current Time: %(current_time)s', + microseconds=False, **kwargs, + ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) - def __call__(self, progress, data): + def __call__( + self, + progress: ProgressBar, + data: Data, + format: types.Optional[str] = None, + ): data['current_time'] = self.current_time() data['current_datetime'] = self.current_datetime() - return FormatWidgetMixin.__call__(self, progress, data) + return FormatWidgetMixin.__call__(self, progress, data, format=format) def current_datetime(self): now = datetime.datetime.now() From 5b74652fe17b572aed3223b64368b2d0e1ddffac Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 29 Oct 2022 14:09:30 +0200 Subject: [PATCH 047/118] fixed type issues --- progressbar/utils.py | 170 ++++++++++++++++++++++++++++++++----------- 1 file changed, 126 insertions(+), 44 deletions(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index 6a322db4..65b9747d 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -7,13 +7,13 @@ import os import re import sys +from types import TracebackType +from typing import Iterable, Iterator, Type from python_utils import types from python_utils.converters import scale_1024 from python_utils.terminal import get_terminal_size -from python_utils.time import epoch -from python_utils.time import format_time -from python_utils.time import timedelta_to_seconds +from python_utils.time import epoch, format_time, timedelta_to_seconds if types.TYPE_CHECKING: from .bar import ProgressBar @@ -39,7 +39,7 @@ def is_ansi_terminal(fd: types.IO, is_terminal: bool | None = None) \ - -> bool: # pragma: no cover + -> bool: # pragma: no cover if is_terminal is None: # Jupyter Notebooks define this variable and support progress bars if 'JPY_PARENT_PID' in os.environ: @@ -68,13 +68,13 @@ def is_ansi_terminal(fd: types.IO, is_terminal: bool | None = None) \ except Exception: is_terminal = False - return is_terminal + return bool(is_terminal) def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: if is_terminal is None: # Full ansi support encompasses what we expect from a terminal - is_terminal = is_ansi_terminal(True) or None + is_terminal = is_ansi_terminal(fd) or None if is_terminal is None: # Allow a environment variable override @@ -88,11 +88,13 @@ def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: except Exception: is_terminal = False - return is_terminal + return bool(is_terminal) -def deltas_to_seconds(*deltas, - **kwargs) -> int | float | None: # default=ValueError): +def deltas_to_seconds( + *deltas, + **kwargs +) -> int | float | None: # default=ValueError): ''' Convert timedeltas and seconds as int to seconds as float while coalescing @@ -148,15 +150,9 @@ def no_color(value: types.StringTypes) -> types.StringTypes: 'abc' ''' if isinstance(value, bytes): - pattern = '\\\u001b\\[.*?[@-~]' - pattern = pattern.encode() - replace = b'' - assert isinstance(pattern, bytes) + return re.sub('\\\u001b\\[.*?[@-~]'.encode(), b'', value) else: - pattern = u'\x1b\\[.*?[@-~]' - replace = '' - - return re.sub(pattern, replace, value) + return re.sub(u'\x1b\\[.*?[@-~]', '', value) def len_color(value: types.StringTypes) -> int: @@ -190,30 +186,37 @@ def env_flag(name: str, default: bool | None = None) -> bool | None: class WrappingIO: - - def __init__(self, target: types.IO, capturing: bool = False, - listeners: types.Set[ProgressBar] = None) -> None: + buffer: io.StringIO + target: types.IO + capturing: bool + listeners: set + needs_clear: bool = False + + def __init__( + self, target: types.IO, capturing: bool = False, + listeners: types.Set[ProgressBar] = None + ) -> None: self.buffer = io.StringIO() self.target = target self.capturing = capturing self.listeners = listeners or set() self.needs_clear = False - def __getattr__(self, name): # pragma: no cover - return getattr(self.target, name) - - def write(self, value: str) -> None: + def write(self, value: str) -> int: + ret = 0 if self.capturing: - self.buffer.write(value) + ret += self.buffer.write(value) if '\n' in value: # pragma: no branch self.needs_clear = True for listener in self.listeners: # pragma: no branch listener.update() else: - self.target.write(value) + ret += self.target.write(value) if '\n' in value: # pragma: no branch self.flush_target() + return ret + def flush(self) -> None: self.buffer.flush() @@ -233,9 +236,80 @@ def flush_target(self) -> None: # pragma: no cover if not self.target.closed and getattr(self.target, 'flush'): self.target.flush() + def __enter__(self) -> WrappingIO: + return self + + def fileno(self) -> int: + return self.target.fileno() + + def isatty(self) -> bool: + return self.target.isatty() + + def read(self, n: int = -1) -> str: + return self.target.read(n) + + def readable(self) -> bool: + return self.target.readable() + + def readline(self, limit: int = -1) -> str: + return self.target.readline(limit) + + def readlines(self, hint: int = -1) -> list[str]: + return self.target.readlines(hint) + + def seek(self, offset: int, whence: int = os.SEEK_SET) -> int: + return self.target.seek(offset, whence) + + def seekable(self) -> bool: + return self.target.seekable() + + def tell(self) -> int: + return self.target.tell() + + def truncate(self, size: types.Optional[int] = None) -> int: + return self.target.truncate(size) + + def writable(self) -> bool: + return self.target.writable() + + def writelines(self, lines: Iterable[str]) -> None: + return self.target.writelines(lines) + + def close(self) -> None: + self.flush() + self.target.close() + + def __next__(self) -> str: + return self.target.__next__() + + def __iter__(self) -> Iterator[str]: + return self.target.__iter__() + + def __exit__( + self, + __t: Type[BaseException] | None, + __value: BaseException | None, + __traceback: TracebackType | None + ) -> None: + self.close() + class StreamWrapper: '''Wrap stdout and stderr globally''' + stdout: types.Union[types.TextIO, WrappingIO] + stderr: types.Union[types.TextIO, WrappingIO] + original_excepthook: types.Callable[ + [ + types.Optional[ + types.Type[BaseException]], + types.Optional[BaseException], + types.Optional[TracebackType], + ], None] + wrapped_stdout: int = 0 + wrapped_stderr: int = 0 + wrapped_excepthook: int = 0 + capturing: int = 0 + listeners: set def __init__(self): self.stdout = self.original_stdout = sys.stdout @@ -291,8 +365,10 @@ def wrap_stdout(self) -> types.IO: self.wrap_excepthook() if not self.wrapped_stdout: - self.stdout = sys.stdout = WrappingIO(self.original_stdout, - listeners=self.listeners) + self.stdout = sys.stdout = WrappingIO( # type: ignore + self.original_stdout, + listeners=self.listeners + ) self.wrapped_stdout += 1 return sys.stdout @@ -301,8 +377,10 @@ def wrap_stderr(self) -> types.IO: self.wrap_excepthook() if not self.wrapped_stderr: - self.stderr = sys.stderr = WrappingIO(self.original_stderr, - listeners=self.listeners) + self.stderr = sys.stderr = WrappingIO( # type: ignore + self.original_stderr, + listeners=self.listeners + ) self.wrapped_stderr += 1 return sys.stderr @@ -346,22 +424,26 @@ def needs_clear(self) -> bool: # pragma: no cover def flush(self) -> None: if self.wrapped_stdout: # pragma: no branch - try: - self.stdout._flush() - except (io.UnsupportedOperation, - AttributeError): # pragma: no cover - self.wrapped_stdout = False - logger.warn('Disabling stdout redirection, %r is not seekable', - sys.stdout) + if isinstance(self.stdout, WrappingIO): # pragma: no branch + try: + self.stdout._flush() + except io.UnsupportedOperation: # pragma: no cover + self.wrapped_stdout = False + logger.warning( + 'Disabling stdout redirection, %r is not seekable', + sys.stdout + ) if self.wrapped_stderr: # pragma: no branch - try: - self.stderr._flush() - except (io.UnsupportedOperation, - AttributeError): # pragma: no cover - self.wrapped_stderr = False - logger.warn('Disabling stderr redirection, %r is not seekable', - sys.stderr) + if isinstance(self.stderr, WrappingIO): # pragma: no branch + try: + self.stderr._flush() + except io.UnsupportedOperation: # pragma: no cover + self.wrapped_stderr = False + logger.warning( + 'Disabling stderr redirection, %r is not seekable', + sys.stderr + ) def excepthook(self, exc_type, exc_value, exc_traceback): self.original_excepthook(exc_type, exc_value, exc_traceback) From 82e08695035975237475bfb5c77b0d43b6eca23c Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 30 Oct 2022 13:14:35 +0200 Subject: [PATCH 048/118] fixed more type issues --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f72abd08..850df2de 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ long_description=readme, include_package_data=True, install_requires=[ - 'python-utils>=3.0.0', + 'python-utils>=3.4.5', ], setup_requires=['setuptools'], zip_safe=False, @@ -55,6 +55,7 @@ 'pytest-mypy', 'freezegun>=0.3.11', 'sphinx>=1.8.5', + 'dill>=0.3.6', ], }, python_requires='>=3.7.0', From 0253fc0b76e480ed2c48f8e2691af5d3ff5504f8 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 31 Oct 2022 11:46:12 +0100 Subject: [PATCH 049/118] added backwards compatibility tests --- tests/test_backwards_compatibility.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/test_backwards_compatibility.py diff --git a/tests/test_backwards_compatibility.py b/tests/test_backwards_compatibility.py new file mode 100644 index 00000000..027c3f9e --- /dev/null +++ b/tests/test_backwards_compatibility.py @@ -0,0 +1,16 @@ +import time +import progressbar + + +def test_progressbar_1_widgets(): + widgets = [ + progressbar.AdaptiveETA(format="Time left: %s"), + progressbar.Timer(format="Time passed: %s"), + progressbar.Bar() + ] + + bar = progressbar.ProgressBar(widgets=widgets, max_value=100).start() + + for i in range(1, 101): + bar.update(i) + time.sleep(0.1) From f10fa70bb37bd2887794dd37741517d0e4fc65cc Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 31 Oct 2022 11:48:27 +0100 Subject: [PATCH 050/118] fixed issue with random prints when dill pickling progressbar. Fixes: #263 --- tests/test_dill_pickle.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_dill_pickle.py diff --git a/tests/test_dill_pickle.py b/tests/test_dill_pickle.py new file mode 100644 index 00000000..ce1ee43d --- /dev/null +++ b/tests/test_dill_pickle.py @@ -0,0 +1,17 @@ +import pickle + +import dill + +import progressbar + + +def test_dill(): + bar = progressbar.ProgressBar() + assert bar._started == False + assert bar._finished == False + + assert not dill.pickles(bar) + + assert bar._started == False + # Should be false because it never should have started/initialized + assert bar._finished == False From e2129d65217c3a09589a2322c58e9ee069167770 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 31 Oct 2022 11:48:38 +0100 Subject: [PATCH 051/118] more type tests --- tests/test_wrappingio.py | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/test_wrappingio.py diff --git a/tests/test_wrappingio.py b/tests/test_wrappingio.py new file mode 100644 index 00000000..6a9f105a --- /dev/null +++ b/tests/test_wrappingio.py @@ -0,0 +1,63 @@ +import io +import os +import sys + +import pytest + +from progressbar import utils + + +def test_wrappingio(): + # Test the wrapping of our version of sys.stdout` ` q + fd = utils.WrappingIO(sys.stdout) + assert fd.fileno() + assert not fd.isatty() + + assert not fd.read() + assert not fd.readline() + assert not fd.readlines() + assert fd.readable() + + assert not fd.seek(0) + assert fd.seekable() + assert not fd.tell() + + assert not fd.truncate() + assert fd.writable() + assert fd.write('test') + assert not fd.writelines(['test']) + + with pytest.raises(StopIteration): + next(fd) + with pytest.raises(StopIteration): + next(iter(fd)) + + +def test_wrapping_stringio(): + # Test the wrapping of our version of sys.stdout` ` q + string_io = io.StringIO() + fd = utils.WrappingIO(string_io) + with fd: + with pytest.raises(io.UnsupportedOperation): + fd.fileno() + + assert not fd.isatty() + + assert not fd.read() + assert not fd.readline() + assert not fd.readlines() + assert fd.readable() + + assert not fd.seek(0) + assert fd.seekable() + assert not fd.tell() + + assert not fd.truncate() + assert fd.writable() + assert fd.write('test') + assert not fd.writelines(['test']) + + with pytest.raises(StopIteration): + next(fd) + with pytest.raises(StopIteration): + next(iter(fd)) From 5e749c57074544cdcdd4d552d5930cd680de01a5 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 31 Oct 2022 12:51:26 +0100 Subject: [PATCH 052/118] small formatting changes --- progressbar/__about__.py | 6 +- progressbar/bar.py | 122 ++++++++++++++++----------- progressbar/base.py | 1 + progressbar/shortcuts.py | 20 ++++- progressbar/utils.py | 34 ++++---- progressbar/widgets.py | 176 +++++++++++++++++++++++++-------------- 6 files changed, 224 insertions(+), 135 deletions(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index bc898708..64d0af06 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -14,10 +14,12 @@ __title__ = 'Python Progressbar' __package_name__ = 'progressbar2' __author__ = 'Rick van Hattem (Wolph)' -__description__ = ' '.join(''' +__description__ = ' '.join( + ''' A Python Progressbar library to provide visual (yet text based) progress to long running operations. -'''.strip().split()) +'''.strip().split() +) __email__ = 'wolph@wol.ph' __version__ = '4.2.0' __license__ = 'BSD' diff --git a/progressbar/bar.py b/progressbar/bar.py index 4cb79f5d..2ec687ee 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -50,7 +50,7 @@ def __del__(self): # Never raise during cleanup. We're too late now logging.debug( 'Exception raised during ProgressBar cleanup', - exc_info=True + exc_info=True, ) def __getstate__(self): @@ -68,10 +68,12 @@ class DefaultFdMixin(ProgressBarMixinBase): enable_colors: bool = False def __init__( - self, fd: types.IO = sys.stderr, + self, + fd: types.IO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, - enable_colors: bool | None = None, **kwargs + enable_colors: bool | None = None, + **kwargs, ): if fd is sys.stdout: fd = utils.streams.original_stdout @@ -91,8 +93,7 @@ def __init__( # iteractive terminals) or write line breaks (suitable for log files) if line_breaks is None: line_breaks = utils.env_flag( - 'PROGRESSBAR_LINE_BREAKS', - not self.is_terminal + 'PROGRESSBAR_LINE_BREAKS', not self.is_terminal ) self.line_breaks = bool(line_breaks) @@ -100,8 +101,7 @@ def __init__( # terminals), or should be stripped off (suitable for log files) if enable_colors is None: enable_colors = utils.env_flag( - 'PROGRESSBAR_ENABLE_COLORS', - self.is_ansi_terminal + 'PROGRESSBAR_ENABLE_COLORS', self.is_ansi_terminal ) self.enable_colors = bool(enable_colors) @@ -149,7 +149,6 @@ def _format_line(self): class ResizableMixin(ProgressBarMixinBase): - def __init__(self, term_width: int | None = None, **kwargs): ProgressBarMixinBase.__init__(self, **kwargs) @@ -160,6 +159,7 @@ def __init__(self, term_width: int | None = None, **kwargs): try: self._handle_resize() import signal + self._prev_handle = signal.getsignal(signal.SIGWINCH) signal.signal(signal.SIGWINCH, self._handle_resize) self.signal_set = True @@ -177,6 +177,7 @@ def finish(self): # pragma: no cover if self.signal_set: try: import signal + signal.signal(signal.SIGWINCH, self._prev_handle) except Exception: # pragma no cover pass @@ -191,8 +192,10 @@ class StdRedirectMixin(DefaultFdMixin): _stderr: types.IO def __init__( - self, redirect_stderr: bool = False, - redirect_stdout: bool = False, **kwargs + self, + redirect_stderr: bool = False, + redirect_stdout: bool = False, + **kwargs, ): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr @@ -327,11 +330,21 @@ class ProgressBar( _MINIMUM_UPDATE_INTERVAL = 0.050 def __init__( - self, min_value=0, max_value=None, widgets=None, - left_justify=True, initial_value=0, poll_interval=None, - widget_kwargs=None, custom_len=utils.len_color, - max_error=True, prefix=None, suffix=None, variables=None, - min_poll_interval=None, **kwargs + self, + min_value=0, + max_value=None, + widgets=None, + left_justify=True, + initial_value=0, + poll_interval=None, + widget_kwargs=None, + custom_len=utils.len_color, + max_error=True, + prefix=None, + suffix=None, + variables=None, + min_poll_interval=None, + **kwargs, ): ''' Initializes a progress bar with sane defaults @@ -342,22 +355,23 @@ def __init__( if not max_value and kwargs.get('maxval') is not None: warnings.warn( 'The usage of `maxval` is deprecated, please use ' - '`max_value` instead', DeprecationWarning + '`max_value` instead', + DeprecationWarning, ) max_value = kwargs.get('maxval') if not poll_interval and kwargs.get('poll'): warnings.warn( 'The usage of `poll` is deprecated, please use ' - '`poll_interval` instead', DeprecationWarning + '`poll_interval` instead', + DeprecationWarning, ) poll_interval = kwargs.get('poll') if max_value: if min_value > max_value: raise ValueError( - 'Max value needs to be bigger than the min ' - 'value' + 'Max value needs to be bigger than the min ' 'value' ) self.min_value = min_value self.max_value = max_value @@ -394,8 +408,7 @@ def __init__( # (downloading a 1GiB file for example) this adds up. poll_interval = utils.deltas_to_seconds(poll_interval, default=None) min_poll_interval = utils.deltas_to_seconds( - min_poll_interval, - default=None + min_poll_interval, default=None ) self._MINIMUM_UPDATE_INTERVAL = utils.deltas_to_seconds( self._MINIMUM_UPDATE_INTERVAL @@ -412,7 +425,7 @@ def __init__( # A dictionary of names that can be used by Variable and FormatWidget self.variables = utils.AttributeDict(variables or {}) - for widget in (self.widgets or []): + for widget in self.widgets or []: if isinstance(widget, widgets_module.VariableMixin): if widget.name not in self.variables: self.variables[widget.name] = None @@ -546,7 +559,7 @@ def data(self): total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -568,20 +581,27 @@ def default_widgets(self): if self.max_value: return [ widgets.Percentage(**self.widget_kwargs), - ' ', widgets.SimpleProgress( + ' ', + widgets.SimpleProgress( format='(%s)' % widgets.SimpleProgress.DEFAULT_FORMAT, - **self.widget_kwargs + **self.widget_kwargs, ), - ' ', widgets.Bar(**self.widget_kwargs), - ' ', widgets.Timer(**self.widget_kwargs), - ' ', widgets.AdaptiveETA(**self.widget_kwargs), + ' ', + widgets.Bar(**self.widget_kwargs), + ' ', + widgets.Timer(**self.widget_kwargs), + ' ', + widgets.AdaptiveETA(**self.widget_kwargs), ] else: return [ widgets.AnimatedMarker(**self.widget_kwargs), - ' ', widgets.BouncingBar(**self.widget_kwargs), - ' ', widgets.Counter(**self.widget_kwargs), - ' ', widgets.Timer(**self.widget_kwargs), + ' ', + widgets.BouncingBar(**self.widget_kwargs), + ' ', + widgets.Counter(**self.widget_kwargs), + ' ', + widgets.Timer(**self.widget_kwargs), ] def __call__(self, iterable, max_value=None): @@ -640,8 +660,9 @@ def _format_widgets(self): data = self.data() for index, widget in enumerate(self.widgets): - if isinstance(widget, widgets.WidgetBase) \ - and not widget.check_size(self): + if isinstance( + widget, widgets.WidgetBase + ) and not widget.check_size(self): continue elif isinstance(widget, widgets.AutoWidthWidgetBase): result.append(widget) @@ -656,7 +677,7 @@ def _format_widgets(self): count = len(expanding) while expanding: - portion = max(int(math.ceil(width * 1. / count)), 0) + portion = max(int(math.ceil(width * 1.0 / count)), 0) index = expanding.pop() widget = result[index] count -= 1 @@ -725,8 +746,8 @@ def update(self, value=None, force=False, **kwargs): for key in kwargs: if key not in self.variables: raise TypeError( - 'update() got an unexpected keyword ' + - 'argument {0!r}'.format(key) + 'update() got an unexpected keyword ' + + 'argument {0!r}'.format(key) ) elif self.variables[key] != kwargs[key]: self.variables[key] = kwargs[key] @@ -782,9 +803,7 @@ def start(self, max_value=None, init=True): if self.prefix: self.widgets.insert( - 0, widgets.FormatLabel( - self.prefix, new_style=True - ) + 0, widgets.FormatLabel(self.prefix, new_style=True) ) # Unset the prefix variable after applying so an extra start() # won't keep copying it @@ -792,9 +811,7 @@ def start(self, max_value=None, init=True): if self.suffix: self.widgets.append( - widgets.FormatLabel( - self.suffix, new_style=True - ) + widgets.FormatLabel(self.suffix, new_style=True) ) # Unset the suffix variable after applying so an extra start() # won't keep copying it @@ -856,7 +873,8 @@ def currval(self): ''' warnings.warn( 'The usage of `currval` is deprecated, please use ' - '`value` instead', DeprecationWarning + '`value` instead', + DeprecationWarning, ) return self.value @@ -871,16 +889,22 @@ def default_widgets(self): if self.max_value: return [ widgets.Percentage(), - ' of ', widgets.DataSize('max_value'), - ' ', widgets.Bar(), - ' ', widgets.Timer(), - ' ', widgets.AdaptiveETA(), + ' of ', + widgets.DataSize('max_value'), + ' ', + widgets.Bar(), + ' ', + widgets.Timer(), + ' ', + widgets.AdaptiveETA(), ] else: return [ widgets.AnimatedMarker(), - ' ', widgets.DataSize(), - ' ', widgets.Timer(), + ' ', + widgets.DataSize(), + ' ', + widgets.Timer(), ] diff --git a/progressbar/base.py b/progressbar/base.py index df278e59..72f93846 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -1,5 +1,6 @@ # -*- mode: python; coding: utf-8 -*- + class FalseMeta(type): def __bool__(self): # pragma: no cover return False diff --git a/progressbar/shortcuts.py b/progressbar/shortcuts.py index f882a5a2..9e1502dd 100644 --- a/progressbar/shortcuts.py +++ b/progressbar/shortcuts.py @@ -1,11 +1,23 @@ from . import bar -def progressbar(iterator, min_value=0, max_value=None, - widgets=None, prefix=None, suffix=None, **kwargs): +def progressbar( + iterator, + min_value=0, + max_value=None, + widgets=None, + prefix=None, + suffix=None, + **kwargs +): progressbar = bar.ProgressBar( - min_value=min_value, max_value=max_value, - widgets=widgets, prefix=prefix, suffix=suffix, **kwargs) + min_value=min_value, + max_value=max_value, + widgets=widgets, + prefix=prefix, + suffix=suffix, + **kwargs + ) for result in progressbar(iterator): yield result diff --git a/progressbar/utils.py b/progressbar/utils.py index 65b9747d..721f4e6d 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -38,8 +38,9 @@ ANSI_TERM_RE = re.compile('^({})'.format('|'.join(ANSI_TERMS)), re.IGNORECASE) -def is_ansi_terminal(fd: types.IO, is_terminal: bool | None = None) \ - -> bool: # pragma: no cover +def is_ansi_terminal( + fd: types.IO, is_terminal: bool | None = None +) -> bool: # pragma: no cover if is_terminal is None: # Jupyter Notebooks define this variable and support progress bars if 'JPY_PARENT_PID' in os.environ: @@ -92,8 +93,7 @@ def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: def deltas_to_seconds( - *deltas, - **kwargs + *deltas, **kwargs ) -> int | float | None: # default=ValueError): ''' Convert timedeltas and seconds as int to seconds as float while coalescing @@ -193,8 +193,10 @@ class WrappingIO: needs_clear: bool = False def __init__( - self, target: types.IO, capturing: bool = False, - listeners: types.Set[ProgressBar] = None + self, + target: types.IO, + capturing: bool = False, + listeners: types.Set[ProgressBar] = None, ) -> None: self.buffer = io.StringIO() self.target = target @@ -289,22 +291,24 @@ def __exit__( self, __t: Type[BaseException] | None, __value: BaseException | None, - __traceback: TracebackType | None + __traceback: TracebackType | None, ) -> None: self.close() class StreamWrapper: '''Wrap stdout and stderr globally''' + stdout: types.Union[types.TextIO, WrappingIO] stderr: types.Union[types.TextIO, WrappingIO] original_excepthook: types.Callable[ [ - types.Optional[ - types.Type[BaseException]], + types.Optional[types.Type[BaseException]], types.Optional[BaseException], types.Optional[TracebackType], - ], None] + ], + None, + ] wrapped_stdout: int = 0 wrapped_stderr: int = 0 wrapped_excepthook: int = 0 @@ -366,8 +370,7 @@ def wrap_stdout(self) -> types.IO: if not self.wrapped_stdout: self.stdout = sys.stdout = WrappingIO( # type: ignore - self.original_stdout, - listeners=self.listeners + self.original_stdout, listeners=self.listeners ) self.wrapped_stdout += 1 @@ -378,8 +381,7 @@ def wrap_stderr(self) -> types.IO: if not self.wrapped_stderr: self.stderr = sys.stderr = WrappingIO( # type: ignore - self.original_stderr, - listeners=self.listeners + self.original_stderr, listeners=self.listeners ) self.wrapped_stderr += 1 @@ -431,7 +433,7 @@ def flush(self) -> None: self.wrapped_stdout = False logger.warning( 'Disabling stdout redirection, %r is not seekable', - sys.stdout + sys.stdout, ) if self.wrapped_stderr: # pragma: no branch @@ -442,7 +444,7 @@ def flush(self) -> None: self.wrapped_stderr = False logger.warning( 'Disabling stderr redirection, %r is not seekable', - sys.stderr + sys.stderr, ) def excepthook(self, exc_type, exc_value, exc_traceback): diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 30ca01a5..ae8e2723 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -24,6 +24,7 @@ def string_or_lambda(input_): if isinstance(input_, str): + def render_input(progress, data, width): return input_ % data @@ -78,17 +79,20 @@ def wrap(*args, **kwargs): def create_marker(marker, wrap=None): def _marker(progress, data, width): - if progress.max_value is not base.UnknownLength \ - and progress.max_value > 0: + if ( + progress.max_value is not base.UnknownLength + and progress.max_value > 0 + ): length = int(progress.value / progress.max_value * width) - return (marker * length) + return marker * length else: return marker if isinstance(marker, str): marker = converters.to_unicode(marker) - assert utils.len_color(marker) == 1, \ - 'Markers are required to be 1 char' + assert ( + utils.len_color(marker) == 1 + ), 'Markers are required to be 1 char' return wrapper(_marker, wrap) else: return wrapper(marker, wrap) @@ -118,7 +122,7 @@ def get_format( self, progress: ProgressBar, data: Data, - format: types.Optional[str] = None + format: types.Optional[str] = None, ) -> str: return format or self.format @@ -206,6 +210,7 @@ class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): progressbar can be reused. Some widgets such as the FormatCustomText require the shared state so this needs to be optional ''' + copy = True @abc.abstractmethod @@ -244,6 +249,7 @@ class TimeSensitiveWidgetBase(WidgetBase, metaclass=abc.ABCMeta): Some widgets like timers would become out of date unless updated at least every `INTERVAL` ''' + INTERVAL = datetime.timedelta(milliseconds=100) @@ -338,7 +344,9 @@ class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): ''' def __init__( - self, samples=datetime.timedelta(seconds=2), key_prefix=None, + self, + samples=datetime.timedelta(seconds=2), + key_prefix=None, **kwargs, ): self.samples = samples @@ -368,9 +376,11 @@ def __call__(self, progress: ProgressBar, data: Data, delta: bool = False): if isinstance(self.samples, datetime.timedelta): minimum_time = progress.last_update_time - self.samples minimum_value = sample_values[-1] - while (sample_times[2:] and - minimum_time > sample_times[1] and - minimum_value > sample_values[1]): + while ( + sample_times[2:] + and minimum_time > sample_times[1] + and minimum_value > sample_values[1] + ): sample_times.pop(0) sample_values.pop(0) else: @@ -412,7 +422,9 @@ def __init__( self.format_zero = format_zero self.format_NA = format_NA - def _calculate_eta(self, progress: ProgressBar, data: Data, value, elapsed): + def _calculate_eta( + self, progress: ProgressBar, data: Data, value, elapsed + ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: # The max() prevents zero division errors @@ -471,7 +483,9 @@ def __call__( class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' - def _calculate_eta(self, progress: ProgressBar, data: Data, value, elapsed): + def _calculate_eta( + self, progress: ProgressBar, data: Data, value, elapsed + ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() try: @@ -487,8 +501,11 @@ def __init__( **kwargs, ): ETA.__init__( - self, format_not_started=format_not_started, - format_finished=format_finished, format=format, **kwargs, + self, + format_not_started=format_not_started, + format_finished=format_finished, + format=format, + **kwargs, ) @@ -511,8 +528,7 @@ def __call__( elapsed=None, ): elapsed, value = SamplesMixin.__call__( - self, progress, data, - delta=True + self, progress, data, delta=True ) if not elapsed: value = None @@ -530,8 +546,10 @@ class DataSize(FormatWidgetMixin, WidgetBase): ''' def __init__( - self, variable='value', - format='%(scaled)5.1f %(prefix)s%(unit)s', unit='B', + self, + variable='value', + format='%(scaled)5.1f %(prefix)s%(unit)s', + unit='B', prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), **kwargs, ): @@ -566,8 +584,10 @@ class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' def __init__( - self, format='%(scaled)5.1f %(prefix)s%(unit)-s/s', - inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', unit='B', + self, + format='%(scaled)5.1f %(prefix)s%(unit)-s/s', + inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', + unit='B', prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), **kwargs, ): @@ -586,19 +606,22 @@ def __call__( progress: ProgressBar, data, value=None, - total_seconds_elapsed=None + total_seconds_elapsed=None, ): '''Updates the widget with the current SI prefixed speed.''' if value is None: value = data['value'] elapsed = utils.deltas_to_seconds( - total_seconds_elapsed, - data['total_seconds_elapsed'] + total_seconds_elapsed, data['total_seconds_elapsed'] ) - if value is not None and elapsed is not None \ - and elapsed > 2e-6 and value > 2e-6: # =~ 0 + if ( + value is not None + and elapsed is not None + and elapsed > 2e-6 + and value > 2e-6 + ): # =~ 0 scaled, power = self._speed(value, elapsed) else: scaled = power = 0 @@ -610,8 +633,7 @@ def __call__( data['scaled'] = scaled data['prefix'] = self.prefixes[0] return FormatWidgetMixin.__call__( - self, progress, data, - self.inverse_format + self, progress, data, self.inverse_format ) else: data['scaled'] = scaled @@ -620,8 +642,7 @@ def __call__( class AdaptiveTransferSpeed(FileTransferSpeed, SamplesMixin): - '''WidgetBase for showing the transfer speed, based on the last X samples - ''' + '''WidgetBase for showing the transfer speed, based on the last X samples''' def __init__(self, **kwargs): FileTransferSpeed.__init__(self, **kwargs) @@ -632,11 +653,10 @@ def __call__( progress: ProgressBar, data, value=None, - total_seconds_elapsed=None + total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( - self, progress, data, - delta=True + self, progress, data, delta=True ) return FileTransferSpeed.__call__(self, progress, data, value, elapsed) @@ -647,8 +667,13 @@ class AnimatedMarker(TimeSensitiveWidgetBase): ''' def __init__( - self, markers='|/-\\', default=None, fill='', - marker_wrap=None, fill_wrap=None, **kwargs, + self, + markers='|/-\\', + default=None, + fill='', + marker_wrap=None, + fill_wrap=None, + **kwargs, ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) @@ -671,9 +696,7 @@ def __call__(self, progress: ProgressBar, data: Data, width=None): if self.fill: # Cut the last character so we can replace it with our marker fill = self.fill( - progress, data, width - progress.custom_len( - marker - ) + progress, data, width - progress.custom_len(marker) ) else: fill = '' @@ -745,8 +768,7 @@ def __call__(self, progress: ProgressBar, data: Data, format=None): data['value_s'] = 0 formatted = FormatWidgetMixin.__call__( - self, progress, data, - format=format + self, progress, data, format=format ) # Guess the maximum width from the min and max value @@ -780,8 +802,14 @@ class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' def __init__( - self, marker='#', left='|', right='|', fill=' ', - fill_left=True, marker_wrap=None, **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=True, + marker_wrap=None, + **kwargs, ): '''Creates a customizable progress bar. @@ -831,8 +859,13 @@ class ReverseBar(Bar): '''A bar which has a marker that goes from right to left''' def __init__( - self, marker='#', left='|', right='|', fill=' ', - fill_left=False, **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=False, + **kwargs, ): '''Creates a customizable progress bar. @@ -843,8 +876,13 @@ def __init__( fill_left - whether to fill from the left or the right ''' Bar.__init__( - self, marker=marker, left=left, right=right, fill=fill, - fill_left=fill_left, **kwargs, + self, + marker=marker, + left=left, + right=right, + fill=fill, + fill_left=fill_left, + **kwargs, ) @@ -916,7 +954,7 @@ def __call__( class VariableMixin: - '''Mixin to display a custom user variable ''' + '''Mixin to display a custom user variable''' def __init__(self, name, **kwargs): if not isinstance(name, str): @@ -944,10 +982,7 @@ class MultiRangeBar(Bar, VariableMixin): def __init__(self, name, markers, **kwargs): VariableMixin.__init__(self, name) Bar.__init__(self, **kwargs) - self.markers = [ - string_or_lambda(marker) - for marker in markers - ] + self.markers = [string_or_lambda(marker) for marker in markers] def get_values(self, progress: ProgressBar, data: Data): return data['variables'][self.name] or [] @@ -997,8 +1032,10 @@ def __init__( **kwargs, ): MultiRangeBar.__init__( - self, name=name, - markers=list(reversed(markers)), **kwargs, + self, + name=name, + markers=list(reversed(markers)), + **kwargs, ) def get_values(self, progress: ProgressBar, data: Data): @@ -1011,8 +1048,8 @@ def get_values(self, progress: ProgressBar, data: Data): if not 0 <= value <= 1: raise ValueError( - 'Range value needs to be in the range [0..1], got %s' % - value + 'Range value needs to be in the range [0..1], got %s' + % value ) range_ = value * (len(ranges) - 1) @@ -1054,7 +1091,10 @@ class GranularBar(AutoWidthWidgetBase): ''' def __init__( - self, markers=GranularMarkers.smooth, left='|', right='|', + self, + markers=GranularMarkers.smooth, + left='|', + right='|', **kwargs, ): '''Creates a customizable progress bar. @@ -1081,8 +1121,10 @@ def __call__( right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) - if progress.max_value is not base.UnknownLength \ - and progress.max_value > 0: + if ( + progress.max_value is not base.UnknownLength + and progress.max_value > 0 + ): percent = progress.value / progress.max_value else: percent = 0 @@ -1147,14 +1189,16 @@ def __call__( # type: ignore return super().__call__(progress, data, width, format=format) - - class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' def __init__( - self, name, format='{name}: {formatted_value}', - width=6, precision=3, **kwargs, + self, + name, + format='{name}: {formatted_value}', + width=6, + precision=3, + **kwargs, ): '''Creates a Variable associated with the given name.''' self.format = format @@ -1167,7 +1211,7 @@ def __call__( self, progress: ProgressBar, data: Data, - format: types.Optional[str] = None + format: types.Optional[str] = None, ): value = data['variables'][self.name] context = data.copy() @@ -1195,16 +1239,20 @@ def __call__( class DynamicMessage(Variable): '''Kept for backwards compatibility, please use `Variable` instead.''' + pass class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): '''Widget which displays the current (date)time with seconds resolution.''' + INTERVAL = datetime.timedelta(seconds=1) def __init__( - self, format='Current Time: %(current_time)s', - microseconds=False, **kwargs, + self, + format='Current Time: %(current_time)s', + microseconds=False, + **kwargs, ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) From b84bdf4ca1b575b7913ce4c315c340f5eab70b64 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 00:29:53 +0100 Subject: [PATCH 053/118] Fixed tests running from pycharm --- progressbar/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/progressbar/utils.py b/progressbar/utils.py index 721f4e6d..c1400ec6 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -47,7 +47,9 @@ def is_ansi_terminal( is_terminal = True # This works for newer versions of pycharm only. older versions there # is no way to check. - elif os.environ.get('PYCHARM_HOSTED') == '1': + elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get( + 'PYTEST_CURRENT_TEST' + ): is_terminal = True if is_terminal is None: From 33c27f0a6758d80bb87eeb5c0b2ab6d11a03ed0c Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 11:42:00 +0100 Subject: [PATCH 054/118] Added more terminal tests --- tests/test_utils.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index 3f292bfd..0ff4a7a1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -54,3 +54,30 @@ def test_is_terminal(monkeypatch): # Sanity check assert progressbar.utils.is_terminal(fd) is False + + +def test_is_ansi_terminal(monkeypatch): + fd = io.StringIO() + + monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) + monkeypatch.delenv('JPY_PARENT_PID', raising=False) + + assert progressbar.utils.is_ansi_terminal(fd) is False + assert progressbar.utils.is_ansi_terminal(fd, True) is True + assert progressbar.utils.is_ansi_terminal(fd, False) is False + + monkeypatch.setenv('JPY_PARENT_PID', '123') + assert progressbar.utils.is_ansi_terminal(fd) is True + monkeypatch.delenv('JPY_PARENT_PID') + + # Sanity check + assert progressbar.utils.is_ansi_terminal(fd) is False + + monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') + assert progressbar.utils.is_ansi_terminal(fd) is False + monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') + assert progressbar.utils.is_ansi_terminal(fd) is False + monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') + + # Sanity check + assert progressbar.utils.is_ansi_terminal(fd) is False From 56989899a690936df22f572a371226e48da2f28f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 11:42:37 +0100 Subject: [PATCH 055/118] Added black, mypy and pyright to tox list --- tox.ini | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index afba92f9..df0a7d69 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37, py38, py39, py310, pypy3, flake8, docs +envlist = py37, py38, py39, py310, pypy3, flake8, docs, black, mypy, pyright skip_missing_interpreters = True [testenv] @@ -21,12 +21,23 @@ basepython = python3 deps = flake8 commands = flake8 {toxinidir}/progressbar {toxinidir}/tests {toxinidir}/examples.py +[testenv:mypy] +changedir = +basepython = python3 +deps = mypy +commands = mypy {toxinidir}/progressbar + [testenv:pyright] changedir = basepython = python3 deps = pyright commands = pyright {toxinidir}/progressbar +[testenv:black] +basepython = python3 +deps = black +commands = black --skip-string-normalization --line-length 79 {toxinidir}/progressbar + [testenv:docs] changedir = basepython = python3 From 9b5b0414e1a57ecf626ac553b6485ab328e0231d Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 11:43:17 +0100 Subject: [PATCH 056/118] Added type hints to widgets --- progressbar/widgets.py | 103 +++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index ae8e2723..336dfb79 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import annotations + import abc import datetime import functools @@ -12,7 +13,7 @@ from . import base, utils if types.TYPE_CHECKING: - from .bar import ProgressBar + from .bar import ProgressBarMixinBase MAX_DATE = datetime.date.max MAX_TIME = datetime.time.max @@ -120,7 +121,7 @@ def __init__(self, format: str, new_style: bool = False, **kwargs): def get_format( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ) -> str: @@ -128,7 +129,7 @@ def get_format( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): @@ -148,7 +149,7 @@ def __call__( class WidthWidgetMixin(abc.ABC): '''Mixing to make sure widgets are only visible if the screen is within a specified size range so the progressbar fits on both large and small - screens.. + screens. Variables available: - min_width: Only display the widget if at least `min_width` is left @@ -174,7 +175,7 @@ def __init__(self, min_width=None, max_width=None, **kwargs): self.min_width = min_width self.max_width = max_width - def check_size(self, progress: 'ProgressBar'): + def check_size(self, progress: ProgressBarMixinBase): if self.min_width and self.min_width > progress.term_width: return False elif self.max_width and self.max_width < progress.term_width: @@ -190,7 +191,7 @@ class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): be updated. The widget's size may change between calls, but the widget may display incorrectly if the size changes drastically and repeatedly. - The boolean INTERVAL informs the ProgressBar that it should be + The INTERVAL timedelta informs the ProgressBar that it should be updated more often because it is time sensitive. The widgets are only visible if the screen is within a @@ -214,7 +215,7 @@ class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): copy = True @abc.abstractmethod - def __call__(self, progress: ProgressBar, data: Data): + def __call__(self, progress: ProgressBarMixinBase, data: Data): '''Updates the widget. progress - a reference to the calling ProgressBar @@ -232,7 +233,7 @@ class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): @abc.abstractmethod def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, ): @@ -281,7 +282,7 @@ def __init__(self, format: str, **kwargs): def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): @@ -350,16 +351,20 @@ def __init__( **kwargs, ): self.samples = samples - self.key_prefix = (self.__class__.__name__ or key_prefix) + '_' + self.key_prefix = ( + key_prefix if key_prefix else self.__class__.__name__ + ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) - def get_sample_times(self, progress: ProgressBar, data: Data): + def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault(self.key_prefix + 'sample_times', []) - def get_sample_values(self, progress: ProgressBar, data: Data): + def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault(self.key_prefix + 'sample_values', []) - def __call__(self, progress: ProgressBar, data: Data, delta: bool = False): + def __call__( + self, progress: ProgressBarMixinBase, data: Data, delta: bool = False + ): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -423,7 +428,7 @@ def __init__( self.format_NA = format_NA def _calculate_eta( - self, progress: ProgressBar, data: Data, value, elapsed + self, progress: ProgressBarMixinBase, data: Data, value, elapsed ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: @@ -438,7 +443,7 @@ def _calculate_eta( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, value=None, elapsed=None, @@ -484,7 +489,7 @@ class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' def _calculate_eta( - self, progress: ProgressBar, data: Data, value, elapsed + self, progress: ProgressBarMixinBase, data: Data, value, elapsed ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() @@ -522,7 +527,7 @@ def __init__(self, **kwargs): def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, value=None, elapsed=None, @@ -561,7 +566,7 @@ def __init__( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): @@ -603,7 +608,7 @@ def _speed(self, value, elapsed): def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data, value=None, total_seconds_elapsed=None, @@ -650,7 +655,7 @@ def __init__(self, **kwargs): def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data, value=None, total_seconds_elapsed=None, @@ -682,7 +687,7 @@ def __init__( self.fill = create_marker(fill, self.fill_wrap) if fill else None WidgetBase.__init__(self, **kwargs) - def __call__(self, progress: ProgressBar, data: Data, width=None): + def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): '''Updates the widget to show the next marker or the first marker when finished''' @@ -709,7 +714,7 @@ def __call__(self, progress: ProgressBar, data: Data, width=None): # cast fill to the same type as marker fill = type(marker)(fill) - return fill + marker + return fill + marker # type: ignore # Alias for backwards compatibility @@ -723,7 +728,9 @@ def __init__(self, format='%(value)d', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - def __call__(self, progress: ProgressBar, data: Data, format=None): + def __call__( + self, progress: ProgressBarMixinBase, data: Data, format=None + ): return FormatWidgetMixin.__call__(self, progress, data, format) @@ -735,7 +742,9 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - def get_format(self, progress: ProgressBar, data: Data, format=None): + def get_format( + self, progress: ProgressBarMixinBase, data: Data, format=None + ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: @@ -747,6 +756,11 @@ def get_format(self, progress: ProgressBar, data: Data, format=None): class SimpleProgress(FormatWidgetMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' + max_width_cache: dict[ + types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], + types.Optional[int], + ] + DEFAULT_FORMAT = '%(value_s)s of %(max_value_s)s' def __init__(self, format=DEFAULT_FORMAT, **kwargs): @@ -754,7 +768,9 @@ def __init__(self, format=DEFAULT_FORMAT, **kwargs): WidgetBase.__init__(self, format=format, **kwargs) self.max_width_cache = dict(default=self.max_width) - def __call__(self, progress: ProgressBar, data: Data, format=None): + def __call__( + self, progress: ProgressBarMixinBase, data: Data, format=None + ): # If max_value is not available, display N/A if data.get('max_value'): data['max_value_s'] = data.get('max_value') @@ -773,7 +789,9 @@ def __call__(self, progress: ProgressBar, data: Data, format=None): # Guess the maximum width from the min and max value key = progress.min_value, progress.max_value - max_width = self.max_width_cache.get(key, self.max_width) + max_width: types.Optional[int] = self.max_width_cache.get( + key, self.max_width + ) if not max_width: temporary_data = data.copy() for value in key: @@ -832,7 +850,7 @@ def __init__( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, ): @@ -893,7 +911,7 @@ class BouncingBar(Bar, TimeSensitiveWidgetBase): def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, ): @@ -944,7 +962,7 @@ def update_mapping(self, **mapping: types.Dict[str, types.Any]): def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): @@ -984,12 +1002,12 @@ def __init__(self, name, markers, **kwargs): Bar.__init__(self, **kwargs) self.markers = [string_or_lambda(marker) for marker in markers] - def get_values(self, progress: ProgressBar, data: Data): + def get_values(self, progress: ProgressBarMixinBase, data: Data): return data['variables'][self.name] or [] def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, ): @@ -1038,8 +1056,8 @@ def __init__( **kwargs, ) - def get_values(self, progress: ProgressBar, data: Data): - ranges = [0] * len(self.markers) + def get_values(self, progress: ProgressBarMixinBase, data: Data): + ranges = [0.0] * len(self.markers) for value in data['variables'][self.name] or []: if not isinstance(value, (int, float)): # Progress is (value, max) @@ -1113,7 +1131,7 @@ def __init__( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, ): @@ -1121,11 +1139,14 @@ def __call__( right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) + max_value = progress.max_value + # mypy doesn't get that the first part of the if statement makes sure + # we get the correct type if ( - progress.max_value is not base.UnknownLength - and progress.max_value > 0 + max_value is not base.UnknownLength + and max_value > 0 # type: ignore ): - percent = progress.value / progress.max_value + percent = progress.value / max_value # type: ignore else: percent = 0 @@ -1155,7 +1176,7 @@ def __init__(self, format, **kwargs): def __call__( # type: ignore self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, format: FormatString = None, @@ -1181,7 +1202,7 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): def __call__( # type: ignore self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, width: int = 0, format: FormatString = None, @@ -1209,7 +1230,7 @@ def __init__( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): @@ -1260,7 +1281,7 @@ def __init__( def __call__( self, - progress: ProgressBar, + progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): From 9619bdc2181122408e45451e9671e1407631cdc7 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 11:45:45 +0100 Subject: [PATCH 057/118] Little flake8 cleanup --- tests/test_dill_pickle.py | 12 +++++------- tests/test_wrappingio.py | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/test_dill_pickle.py b/tests/test_dill_pickle.py index ce1ee43d..bfa1da4b 100644 --- a/tests/test_dill_pickle.py +++ b/tests/test_dill_pickle.py @@ -1,5 +1,3 @@ -import pickle - import dill import progressbar @@ -7,11 +5,11 @@ def test_dill(): bar = progressbar.ProgressBar() - assert bar._started == False - assert bar._finished == False + assert bar._started is False + assert bar._finished is False - assert not dill.pickles(bar) + assert dill.pickles(bar) is False - assert bar._started == False + assert bar._started is False # Should be false because it never should have started/initialized - assert bar._finished == False + assert bar._finished is False diff --git a/tests/test_wrappingio.py b/tests/test_wrappingio.py index 6a9f105a..8a352872 100644 --- a/tests/test_wrappingio.py +++ b/tests/test_wrappingio.py @@ -1,5 +1,4 @@ import io -import os import sys import pytest From 3ab12743fce6073c7f8a34e37095dd625dad16e6 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 11:46:18 +0100 Subject: [PATCH 058/118] Full pyright and mypy compliant type hinting --- progressbar/bar.py | 270 +++++++++++++++++++++++++---------------- progressbar/utils.py | 21 ++-- progressbar/widgets.py | 4 +- 3 files changed, 179 insertions(+), 116 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 2ec687ee..eaa27a5f 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,14 +1,15 @@ from __future__ import annotations +import abc import logging import os import sys import time import timeit import warnings -from abc import ABC from copy import deepcopy from datetime import datetime +from typing import Type import math from python_utils import converters, types @@ -22,13 +23,79 @@ logger = logging.getLogger(__name__) -T = types.TypeVar('T') +# float also accepts integers and longs but we don't want an explicit union +# due to type checking complexity +T = float -class ProgressBarMixinBase(object): +class ProgressBarMixinBase(abc.ABC): _started = False _finished = False + _last_update_time: types.Optional[float] = None + + #: The terminal width. This should be automatically detected but will + #: fall back to 80 if auto detection is not possible. term_width: int = 80 + #: The widgets to render, defaults to the result of `default_widget()` + widgets: types.List[widgets_module.WidgetBase] + #: When going beyond the max_value, raise an error if True or silently + #: ignore otherwise + max_error: bool + #: Prefix the progressbar with the given string + prefix: types.Optional[str] + #: Suffix the progressbar with the given string + suffix: types.Optional[str] + #: Justify to the left if `True` or the right if `False` + left_justify: bool + #: The default keyword arguments for the `default_widgets` if no widgets + #: are configured + widget_kwargs: types.Dict[str, types.Any] + #: Custom length function for multibyte characters such as CJK + # custom_len: types.Callable[[str], int] + custom_len: types.ClassVar[ + types.Callable[['ProgressBarMixinBase', str], int] + ] + #: The time the progress bar was started + initial_start_time: types.Optional[datetime] + #: The interval to poll for updates in seconds if there are updates + poll_interval: types.Optional[float] + #: The minimum interval to poll for updates in seconds even if there are + #: no updates + min_poll_interval: float + + #: Current progress (min_value <= value <= max_value) + value: T + #: The minimum/start value for the progress bar + min_value: T + #: Maximum (and final) value. Beyond this value an error will be raised + #: unless the `max_error` parameter is `False`. + max_value: T | types.Type[base.UnknownLength] + #: The time the progressbar reached `max_value` or when `finish()` was + #: called. + end_time: types.Optional[datetime] + #: The time `start()` was called or iteration started. + start_time: types.Optional[datetime] + #: Seconds between `start_time` and last call to `update()` + seconds_elapsed: float + + #: Extra data for widgets with persistent state. This is used by + #: sampling widgets for example. Since widgets can be shared between + #: multiple progressbars we need to store the state with the progressbar. + extra: types.Dict[str, types.Any] + + def get_last_update_time(self) -> types.Optional[datetime]: + if self._last_update_time: + return datetime.fromtimestamp(self._last_update_time) + else: + return None + + def set_last_update_time(self, value: types.Optional[datetime]): + if value: + self._last_update_time = time.mktime(value.timetuple()) + else: + self._last_update_time = None + + last_update_time = property(get_last_update_time, set_last_update_time) def __init__(self, **kwargs): pass @@ -56,15 +123,25 @@ def __del__(self): def __getstate__(self): return self.__dict__ + def data(self) -> types.Dict[str, types.Any]: + raise NotImplementedError() + -class ProgressBarBase(types.Iterable, ProgressBarMixinBase, ABC): +class ProgressBarBase(types.Iterable, ProgressBarMixinBase): pass class DefaultFdMixin(ProgressBarMixinBase): + # The file descriptor to write to. Defaults to `sys.stderr` fd: types.IO = sys.stderr + #: Set the terminal to be ANSI compatible. If a terminal is ANSI + #: compatible we will automatically enable `colors` and disable + #: `line_breaks`. is_ansi_terminal: bool = False + #: Whether to print line breaks. This is useful for logging the + #: progressbar. When disabled the current line is overwritten. line_breaks: bool = True + #: Enable or disable colors. Defaults to auto detection enable_colors: bool = False def __init__( @@ -147,6 +224,46 @@ def _format_line(self): else: return widgets.rjust(self.term_width) + def _format_widgets(self): + result = [] + expanding = [] + width = self.term_width + data = self.data() + + for index, widget in enumerate(self.widgets): + if isinstance( + widget, widgets.WidgetBase + ) and not widget.check_size(self): + continue + elif isinstance(widget, widgets.AutoWidthWidgetBase): + result.append(widget) + expanding.insert(0, index) + elif isinstance(widget, str): + result.append(widget) + width -= self.custom_len(widget) + else: + widget_output = converters.to_unicode(widget(self, data)) + result.append(widget_output) + width -= self.custom_len(widget_output) + + count = len(expanding) + while expanding: + portion = max(int(math.ceil(width * 1.0 / count)), 0) + index = expanding.pop() + widget = result[index] + count -= 1 + + widget_output = widget(self, data, portion) + width -= self.custom_len(widget_output) + result[index] = widget_output + + return result + + @classmethod + def _to_unicode(cls, args): + for arg in args: + yield converters.to_unicode(arg) + class ResizableMixin(ProgressBarMixinBase): def __init__(self, term_width: int | None = None, **kwargs): @@ -219,7 +336,7 @@ def start(self, *args, **kwargs): utils.streams.start_capturing(self) DefaultFdMixin.start(self, *args, **kwargs) - def update(self, value: float = None): + def update(self, value: types.Optional[float] = None): if not self.line_breaks and utils.streams.needs_clear(): self.fd.write('\r' + ' ' * self.term_width + '\r') @@ -240,7 +357,6 @@ class ProgressBar( StdRedirectMixin, ResizableMixin, ProgressBarBase, - types.Generic[T], ): '''The ProgressBar class which updates and prints the bar. @@ -312,33 +428,23 @@ class ProgressBar( you from changing the ProgressBar you should treat it as read only. ''' - #: Current progress (min_value <= value <= max_value) - value: T - #: Maximum (and final) value. Beyond this value an error will be raised - #: unless the `max_error` parameter is `False`. - max_value: T - #: The time the progressbar reached `max_value` or when `finish()` was - #: called. - end_time: datetime - #: The time `start()` was called or iteration started. - start_time: datetime - #: Seconds between `start_time` and last call to `update()` - seconds_elapsed: float + _iterable: types.Optional[types.Iterable] - _DEFAULT_MAXVAL = base.UnknownLength + _DEFAULT_MAXVAL: Type[base.UnknownLength] = base.UnknownLength # update every 50 milliseconds (up to a 20 times per second) - _MINIMUM_UPDATE_INTERVAL = 0.050 + _MINIMUM_UPDATE_INTERVAL: float = 0.050 + _last_update_time: types.Optional[float] = None def __init__( self, - min_value=0, - max_value=None, - widgets=None, - left_justify=True, - initial_value=0, - poll_interval=None, - widget_kwargs=None, - custom_len=utils.len_color, + min_value: T = 0, + max_value: T | types.Type[base.UnknownLength] | None = None, + widgets: types.List[widgets_module.WidgetBase] = None, + left_justify: bool = True, + initial_value: T = 0, + poll_interval: types.Optional[float] = None, + widget_kwargs: types.Dict[str, types.Any] = None, + custom_len: types.Callable[[str], int] = utils.len_color, max_error=True, prefix=None, suffix=None, @@ -369,33 +475,33 @@ def __init__( poll_interval = kwargs.get('poll') if max_value: - if min_value > max_value: + # mypy doesn't understand that a boolean check excludes + # `UnknownLength` + if min_value > max_value: # type: ignore raise ValueError( 'Max value needs to be bigger than the min ' 'value' ) self.min_value = min_value - self.max_value = max_value + # Legacy issue, `max_value` can be `None` before execution. After + # that it either has a value or is `UnknownLength` + self.max_value = max_value # type: ignore self.max_error = max_error # Only copy the widget if it's safe to copy. Most widgets are so we # assume this to be true - if widgets is None: - self.widgets = widgets - else: - self.widgets = [] - for widget in widgets: - if getattr(widget, 'copy', True): - widget = deepcopy(widget) - self.widgets.append(widget) + self.widgets = [] + for widget in widgets or []: + if getattr(widget, 'copy', True): + widget = deepcopy(widget) + self.widgets.append(widget) - self.widgets = widgets self.prefix = prefix self.suffix = suffix self.widget_kwargs = widget_kwargs or {} self.left_justify = left_justify self.value = initial_value self._iterable = None - self.custom_len = custom_len + self.custom_len = custom_len # type: ignore self.initial_start_time = kwargs.get('start_time') self.init() @@ -410,8 +516,9 @@ def __init__( min_poll_interval = utils.deltas_to_seconds( min_poll_interval, default=None ) - self._MINIMUM_UPDATE_INTERVAL = utils.deltas_to_seconds( - self._MINIMUM_UPDATE_INTERVAL + self._MINIMUM_UPDATE_INTERVAL = ( + utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) + or self._MINIMUM_UPDATE_INTERVAL ) # Note that the _MINIMUM_UPDATE_INTERVAL sets the minimum in case of @@ -421,11 +528,11 @@ def __init__( min_poll_interval or self._MINIMUM_UPDATE_INTERVAL, self._MINIMUM_UPDATE_INTERVAL, float(os.environ.get('PROGRESSBAR_MINIMUM_UPDATE_INTERVAL', 0)), - ) + ) # type: ignore # A dictionary of names that can be used by Variable and FormatWidget self.variables = utils.AttributeDict(variables or {}) - for widget in self.widgets or []: + for widget in self.widgets: if isinstance(widget, widgets_module.VariableMixin): if widget.name not in self.variables: self.variables[widget.name] = None @@ -494,19 +601,7 @@ def percentage(self): return percentage - def get_last_update_time(self): - if self._last_update_time: - return datetime.fromtimestamp(self._last_update_time) - - def set_last_update_time(self, value): - if value: - self._last_update_time = time.mktime(value.timetuple()) - else: - self._last_update_time = None - - last_update_time = property(get_last_update_time, set_last_update_time) - - def data(self): + def data(self) -> types.Dict[str, types.Any]: ''' Returns: @@ -653,46 +748,6 @@ def increment(self, value=1, *args, **kwargs): self.update(self.value + value, *args, **kwargs) return self - def _format_widgets(self): - result = [] - expanding = [] - width = self.term_width - data = self.data() - - for index, widget in enumerate(self.widgets): - if isinstance( - widget, widgets.WidgetBase - ) and not widget.check_size(self): - continue - elif isinstance(widget, widgets.AutoWidthWidgetBase): - result.append(widget) - expanding.insert(0, index) - elif isinstance(widget, str): - result.append(widget) - width -= self.custom_len(widget) - else: - widget_output = converters.to_unicode(widget(self, data)) - result.append(widget_output) - width -= self.custom_len(widget_output) - - count = len(expanding) - while expanding: - portion = max(int(math.ceil(width * 1.0 / count)), 0) - index = expanding.pop() - widget = result[index] - count -= 1 - - widget_output = widget(self, data, portion) - width -= self.custom_len(widget_output) - result[index] = widget_output - - return result - - @classmethod - def _to_unicode(cls, args): - for arg in args: - yield converters.to_unicode(arg) - def _needs_update(self): 'Returns whether the ProgressBar should redraw the line.' delta = timeit.default_timer() - self._last_update_timer @@ -707,8 +762,10 @@ def _needs_update(self): # add more bars to progressbar (according to current # terminal width) try: - divisor = self.max_value / self.term_width # float division - if self.value // divisor != self.previous_value // divisor: + divisor: float = self.max_value / self.term_width # type: ignore + value_divisor = self.value // divisor # type: ignore + pvalue_divisor = self.previous_value // divisor # type: ignore + if value_divisor != pvalue_divisor: return True except Exception: # ignore any division errors @@ -798,7 +855,7 @@ def start(self, max_value=None, init=True): ProgressBarBase.start(self, max_value=max_value) # Constructing the default widgets is only done when we know max_value - if self.widgets is None: + if not self.widgets: self.widgets = self.default_widgets() if self.prefix: @@ -818,10 +875,11 @@ def start(self, max_value=None, init=True): self.suffix = None for widget in self.widgets: - interval = getattr(widget, 'INTERVAL', None) + interval: int | float | None = utils.deltas_to_seconds( + getattr(widget, 'INTERVAL', None), + default=None, + ) if interval is not None: - interval = utils.deltas_to_seconds(interval) - self.poll_interval = min( self.poll_interval or interval, interval, @@ -832,7 +890,11 @@ def start(self, max_value=None, init=True): # https://github.com/WoLpH/python-progressbar/issues/207 self.next_update = 0 - if self.max_value is not base.UnknownLength and self.max_value < 0: + if ( + self.max_value is not base.UnknownLength + and self.max_value is not None + and self.max_value < 0 # type: ignore + ): raise ValueError('max_value out of range, got %r' % self.max_value) now = datetime.now() diff --git a/progressbar/utils.py b/progressbar/utils.py index c1400ec6..d65eeb10 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -16,7 +16,7 @@ from python_utils.time import epoch, format_time, timedelta_to_seconds if types.TYPE_CHECKING: - from .bar import ProgressBar + from .bar import ProgressBar, ProgressBarMixinBase assert timedelta_to_seconds assert get_terminal_size @@ -95,8 +95,9 @@ def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: def deltas_to_seconds( - *deltas, **kwargs -) -> int | float | None: # default=ValueError): + *deltas, + default: types.Optional[types.Type[ValueError]] = ValueError, +) -> int | float | None: ''' Convert timedeltas and seconds as int to seconds as float while coalescing @@ -121,9 +122,6 @@ def deltas_to_seconds( >>> deltas_to_seconds(default=0.0) 0.0 ''' - default = kwargs.pop('default', ValueError) - assert not kwargs, 'Only the `default` keyword argument is supported' - for delta in deltas: if delta is None: continue @@ -137,7 +135,8 @@ def deltas_to_seconds( if default is ValueError: raise ValueError('No valid deltas passed to `deltas_to_seconds`') else: - return default + # mypy doesn't understand the `default is ValueError` check + return default # type: ignore def no_color(value: types.StringTypes) -> types.StringTypes: @@ -301,8 +300,8 @@ def __exit__( class StreamWrapper: '''Wrap stdout and stderr globally''' - stdout: types.Union[types.TextIO, WrappingIO] - stderr: types.Union[types.TextIO, WrappingIO] + stdout: types.TextIO | WrappingIO + stderr: types.TextIO | WrappingIO original_excepthook: types.Callable[ [ types.Optional[types.Type[BaseException]], @@ -333,14 +332,14 @@ def __init__(self): if env_flag('WRAP_STDERR', default=False): # pragma: no cover self.wrap_stderr() - def start_capturing(self, bar: ProgressBar | None = None) -> None: + def start_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: if bar: # pragma: no branch self.listeners.add(bar) self.capturing += 1 self.update_capturing() - def stop_capturing(self, bar: ProgressBar | None = None) -> None: + def stop_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: if bar: # pragma: no branch try: self.listeners.remove(bar) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 336dfb79..fdc074de 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -647,7 +647,9 @@ def __call__( class AdaptiveTransferSpeed(FileTransferSpeed, SamplesMixin): - '''WidgetBase for showing the transfer speed, based on the last X samples''' + ''' + WidgetBase for showing the transfer speed, based on the last X samples + ''' def __init__(self, **kwargs): FileTransferSpeed.__init__(self, **kwargs) From 62459cc5eebbe8f79c7f279b2a1bfb1567ad692a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 12:23:38 +0100 Subject: [PATCH 059/118] pypy3 builds are broken again, disabling for now --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index df0a7d69..99be8934 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37, py38, py39, py310, pypy3, flake8, docs, black, mypy, pyright +envlist = py37, py38, py39, py310, flake8, docs, black, mypy, pyright skip_missing_interpreters = True [testenv] From f48bfee31c46bc6f89860edc3c53d32b3e03ae1b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 12:37:58 +0100 Subject: [PATCH 060/118] docs clarification --- progressbar/widgets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index fdc074de..7e5b78e9 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -585,7 +585,7 @@ def __call__( class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' - WidgetBase for showing the transfer speed (useful for file transfers). + Widget for showing the current transfer speed (useful for file transfers). ''' def __init__( @@ -647,9 +647,7 @@ def __call__( class AdaptiveTransferSpeed(FileTransferSpeed, SamplesMixin): - ''' - WidgetBase for showing the transfer speed, based on the last X samples - ''' + '''Widget for showing the transfer speed based on the last X samples''' def __init__(self, **kwargs): FileTransferSpeed.__init__(self, **kwargs) From 02021b95311d21056d5cebc6a28152681b956010 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 13:50:14 +0100 Subject: [PATCH 061/118] Added python 3.7 type hinting compatibility --- progressbar/bar.py | 12 ++++++------ progressbar/base.py | 8 ++++++++ progressbar/utils.py | 18 ++++++++++-------- progressbar/widgets.py | 7 ++++--- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index eaa27a5f..ab067e6c 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -133,7 +133,7 @@ class ProgressBarBase(types.Iterable, ProgressBarMixinBase): class DefaultFdMixin(ProgressBarMixinBase): # The file descriptor to write to. Defaults to `sys.stderr` - fd: types.IO = sys.stderr + fd: base.IO = sys.stderr #: Set the terminal to be ANSI compatible. If a terminal is ANSI #: compatible we will automatically enable `colors` and disable #: `line_breaks`. @@ -146,7 +146,7 @@ class DefaultFdMixin(ProgressBarMixinBase): def __init__( self, - fd: types.IO = sys.stderr, + fd: base.IO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, enable_colors: bool | None = None, @@ -303,10 +303,10 @@ def finish(self): # pragma: no cover class StdRedirectMixin(DefaultFdMixin): redirect_stderr: bool = False redirect_stdout: bool = False - stdout: types.IO - stderr: types.IO - _stdout: types.IO - _stderr: types.IO + stdout: base.IO + stderr: base.IO + _stdout: base.IO + _stderr: base.IO def __init__( self, diff --git a/progressbar/base.py b/progressbar/base.py index 72f93846..d3f12d52 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -1,4 +1,5 @@ # -*- mode: python; coding: utf-8 -*- +from python_utils import types class FalseMeta(type): @@ -17,3 +18,10 @@ class UnknownLength(metaclass=FalseMeta): class Undefined(metaclass=FalseMeta): pass + + +try: + IO = types.IO # type: ignore + TextIO = types.TextIO # type: ignore +except AttributeError: + from typing.io import IO # type: ignore diff --git a/progressbar/utils.py b/progressbar/utils.py index d65eeb10..76590096 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -15,6 +15,8 @@ from python_utils.terminal import get_terminal_size from python_utils.time import epoch, format_time, timedelta_to_seconds +from progressbar import base + if types.TYPE_CHECKING: from .bar import ProgressBar, ProgressBarMixinBase @@ -39,7 +41,7 @@ def is_ansi_terminal( - fd: types.IO, is_terminal: bool | None = None + fd: base.IO, is_terminal: bool | None = None ) -> bool: # pragma: no cover if is_terminal is None: # Jupyter Notebooks define this variable and support progress bars @@ -74,7 +76,7 @@ def is_ansi_terminal( return bool(is_terminal) -def is_terminal(fd: types.IO, is_terminal: bool | None = None) -> bool: +def is_terminal(fd: base.IO, is_terminal: bool | None = None) -> bool: if is_terminal is None: # Full ansi support encompasses what we expect from a terminal is_terminal = is_ansi_terminal(fd) or None @@ -188,14 +190,14 @@ def env_flag(name: str, default: bool | None = None) -> bool | None: class WrappingIO: buffer: io.StringIO - target: types.IO + target: base.IO capturing: bool listeners: set needs_clear: bool = False def __init__( self, - target: types.IO, + target: base.IO, capturing: bool = False, listeners: types.Set[ProgressBar] = None, ) -> None: @@ -300,8 +302,8 @@ def __exit__( class StreamWrapper: '''Wrap stdout and stderr globally''' - stdout: types.TextIO | WrappingIO - stderr: types.TextIO | WrappingIO + stdout: base.TextIO | WrappingIO + stderr: base.TextIO | WrappingIO original_excepthook: types.Callable[ [ types.Optional[types.Type[BaseException]], @@ -366,7 +368,7 @@ def wrap(self, stdout: bool = False, stderr: bool = False) -> None: if stderr: self.wrap_stderr() - def wrap_stdout(self) -> types.IO: + def wrap_stdout(self) -> base.IO: self.wrap_excepthook() if not self.wrapped_stdout: @@ -377,7 +379,7 @@ def wrap_stdout(self) -> types.IO: return sys.stdout - def wrap_stderr(self) -> types.IO: + def wrap_stderr(self) -> base.IO: self.wrap_excepthook() if not self.wrapped_stderr: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 7e5b78e9..56d6b417 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -207,9 +207,10 @@ class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): - max_width: Only display the widget if at most `max_width` is left - weight: Widgets with a higher `weigth` will be calculated before widgets with a lower one - - copy: Copy this widget when initializing the progress bar so the - progressbar can be reused. Some widgets such as the FormatCustomText - require the shared state so this needs to be optional + - copy: Copy this widget when initializing the progress bar so the + progressbar can be reused. Some widgets such as the FormatCustomText + require the shared state so this needs to be optional + ''' copy = True From 23affbaf7871dc654996414666be34e88ace2087 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 13:53:05 +0100 Subject: [PATCH 062/118] Added python 3.7 type hinting compatibility --- progressbar/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/progressbar/base.py b/progressbar/base.py index d3f12d52..9bf4e568 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -24,4 +24,7 @@ class Undefined(metaclass=FalseMeta): IO = types.IO # type: ignore TextIO = types.TextIO # type: ignore except AttributeError: - from typing.io import IO # type: ignore + from typing.io import IO, TextIO # type: ignore + +assert IO +assert TextIO From 98a46c6d3bc0bf46a9a46a4ffd2d70d81f5963f8 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 16:23:10 +0100 Subject: [PATCH 063/118] All type issues finally fixed? Maybe? --- progressbar/bar.py | 37 +++++++++++++++++++++---------------- progressbar/base.py | 2 +- progressbar/utils.py | 30 +++++++++++++++++++----------- progressbar/widgets.py | 15 +++++++++------ 4 files changed, 50 insertions(+), 34 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index ab067e6c..806a98a8 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -51,10 +51,10 @@ class ProgressBarMixinBase(abc.ABC): #: are configured widget_kwargs: types.Dict[str, types.Any] #: Custom length function for multibyte characters such as CJK - # custom_len: types.Callable[[str], int] - custom_len: types.ClassVar[ - types.Callable[['ProgressBarMixinBase', str], int] - ] + # mypy and pyright can't agree on what the correct one is... so we'll + # need to use a helper function :( + # custom_len: types.Callable[['ProgressBarMixinBase', str], int] + custom_len: types.Callable[[str], int] #: The time the progress bar was started initial_start_time: types.Optional[datetime] #: The interval to poll for updates in seconds if there are updates @@ -188,7 +188,7 @@ def __init__( def update(self, *args, **kwargs): ProgressBarMixinBase.update(self, *args, **kwargs) - line = converters.to_unicode(self._format_line()) + line: str = converters.to_unicode(self._format_line()) if not self.enable_colors: line = utils.no_color(line) @@ -240,11 +240,11 @@ def _format_widgets(self): expanding.insert(0, index) elif isinstance(widget, str): result.append(widget) - width -= self.custom_len(widget) + width -= self.custom_len(widget) # type: ignore else: widget_output = converters.to_unicode(widget(self, data)) result.append(widget_output) - width -= self.custom_len(widget_output) + width -= self.custom_len(widget_output) # type: ignore count = len(expanding) while expanding: @@ -254,7 +254,7 @@ def _format_widgets(self): count -= 1 widget_output = widget(self, data, portion) - width -= self.custom_len(widget_output) + width -= self.custom_len(widget_output) # type: ignore result[index] = widget_output return result @@ -303,8 +303,8 @@ def finish(self): # pragma: no cover class StdRedirectMixin(DefaultFdMixin): redirect_stderr: bool = False redirect_stdout: bool = False - stdout: base.IO - stderr: base.IO + stdout: utils.WrappingIO | base.IO + stderr: utils.WrappingIO | base.IO _stdout: base.IO _stderr: base.IO @@ -428,7 +428,7 @@ class ProgressBar( you from changing the ProgressBar you should treat it as read only. ''' - _iterable: types.Optional[types.Iterable] + _iterable: types.Optional[types.Iterator] _DEFAULT_MAXVAL: Type[base.UnknownLength] = base.UnknownLength # update every 50 milliseconds (up to a 20 times per second) @@ -439,11 +439,11 @@ def __init__( self, min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, - widgets: types.List[widgets_module.WidgetBase] = None, + widgets: types.Optional[types.List[widgets_module.WidgetBase]] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, - widget_kwargs: types.Dict[str, types.Any] = None, + widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None, custom_len: types.Callable[[str], int] = utils.len_color, max_error=True, prefix=None, @@ -594,7 +594,7 @@ def percentage(self): return None elif self.max_value: todo = self.value - self.min_value - total = self.max_value - self.min_value + total = self.max_value - self.min_value # type: ignore percentage = 100.0 * todo / total else: percentage = 100.0 @@ -631,7 +631,7 @@ def data(self) -> types.Dict[str, types.Any]: ''' self._last_update_time = time.time() self._last_update_timer = timeit.default_timer() - elapsed = self.last_update_time - self.start_time + elapsed = self.last_update_time - self.start_time # type: ignore # For Python 2.7 and higher we have _`timedelta.total_seconds`, but we # want to support older versions as well total_seconds_elapsed = utils.deltas_to_seconds(elapsed) @@ -717,11 +717,16 @@ def __iter__(self): def __next__(self): try: - value = next(self._iterable) + if self._iterable is None: # pragma: no cover + value = self.value + else: + value = next(self._iterable) + if self.start_time is None: self.start() else: self.update(self.value + 1) + return value except StopIteration: self.finish() diff --git a/progressbar/base.py b/progressbar/base.py index 9bf4e568..8639f557 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -20,7 +20,7 @@ class Undefined(metaclass=FalseMeta): pass -try: +try: # pragma: no cover IO = types.IO # type: ignore TextIO = types.TextIO # type: ignore except AttributeError: diff --git a/progressbar/utils.py b/progressbar/utils.py index 76590096..7f84a91d 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -26,6 +26,8 @@ assert scale_1024 assert epoch +StringT = types.TypeVar('StringT', bound=types.StringTypes) + ANSI_TERMS = ( '([xe]|bv)term', '(sco)?ansi', @@ -141,7 +143,7 @@ def deltas_to_seconds( return default # type: ignore -def no_color(value: types.StringTypes) -> types.StringTypes: +def no_color(value: StringT) -> StringT: ''' Return the `value` without ANSI escape codes @@ -153,9 +155,10 @@ def no_color(value: types.StringTypes) -> types.StringTypes: 'abc' ''' if isinstance(value, bytes): - return re.sub('\\\u001b\\[.*?[@-~]'.encode(), b'', value) + pattern: bytes = '\\\u001b\\[.*?[@-~]'.encode() + return re.sub(pattern, b'', value) # type: ignore else: - return re.sub(u'\x1b\\[.*?[@-~]', '', value) + return re.sub(u'\x1b\\[.*?[@-~]', '', value) # type: ignore def len_color(value: types.StringTypes) -> int: @@ -199,7 +202,7 @@ def __init__( self, target: base.IO, capturing: bool = False, - listeners: types.Set[ProgressBar] = None, + listeners: types.Optional[types.Set[ProgressBar]] = None, ) -> None: self.buffer = io.StringIO() self.target = target @@ -306,12 +309,17 @@ class StreamWrapper: stderr: base.TextIO | WrappingIO original_excepthook: types.Callable[ [ - types.Optional[types.Type[BaseException]], - types.Optional[BaseException], - types.Optional[TracebackType], + types.Type[BaseException], + BaseException, + TracebackType | None, ], None, ] + # original_excepthook: types.Callable[ + # [ + # types.Type[BaseException], + # BaseException, TracebackType | None, + # ], None] | None wrapped_stdout: int = 0 wrapped_stderr: int = 0 wrapped_excepthook: int = 0 @@ -368,7 +376,7 @@ def wrap(self, stdout: bool = False, stderr: bool = False) -> None: if stderr: self.wrap_stderr() - def wrap_stdout(self) -> base.IO: + def wrap_stdout(self) -> WrappingIO: self.wrap_excepthook() if not self.wrapped_stdout: @@ -377,9 +385,9 @@ def wrap_stdout(self) -> base.IO: ) self.wrapped_stdout += 1 - return sys.stdout + return sys.stdout # type: ignore - def wrap_stderr(self) -> base.IO: + def wrap_stderr(self) -> WrappingIO: self.wrap_excepthook() if not self.wrapped_stderr: @@ -388,7 +396,7 @@ def wrap_stderr(self) -> base.IO: ) self.wrapped_stderr += 1 - return sys.stderr + return sys.stderr # type: ignore def unwrap_excepthook(self) -> None: if self.wrapped_excepthook: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 56d6b417..b13d9f33 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -132,7 +132,7 @@ def __call__( progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, - ): + ) -> str: '''Formats the widget into a string''' format = self.get_format(progress, data, format) try: @@ -216,7 +216,7 @@ class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): copy = True @abc.abstractmethod - def __call__(self, progress: ProgressBarMixinBase, data: Data): + def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: '''Updates the widget. progress - a reference to the calling ProgressBar @@ -237,7 +237,7 @@ def __call__( progress: ProgressBarMixinBase, data: Data, width: int = 0, - ): + ) -> str: '''Updates the widget providing the total width the widget must fill. progress - a reference to the calling ProgressBar @@ -702,7 +702,9 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): if self.fill: # Cut the last character so we can replace it with our marker fill = self.fill( - progress, data, width - progress.custom_len(marker) + progress, + data, + width - progress.custom_len(marker), # type: ignore ) else: fill = '' @@ -767,7 +769,8 @@ class SimpleProgress(FormatWidgetMixin, WidgetBase): def __init__(self, format=DEFAULT_FORMAT, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - self.max_width_cache = dict(default=self.max_width) + self.max_width_cache = dict() + self.max_width_cache['default'] = self.max_width or 0 def __call__( self, progress: ProgressBarMixinBase, data: Data, format=None @@ -950,7 +953,7 @@ class FormatCustomText(FormatWidgetMixin, WidgetBase): def __init__( self, format: str, - mapping: types.Dict[str, types.Any] = None, + mapping: types.Optional[types.Dict[str, types.Any]] = None, **kwargs, ): self.format = format From a0259d1fcdcff118ba288198f612cb7f4a717e01 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 16:36:36 +0100 Subject: [PATCH 064/118] added pyright config --- pyrightconfig.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 pyrightconfig.json diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..58d8fa22 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,11 @@ +{ + "include": [ + "progressbar" + ], + "exclude": [ + "examples" + ], + "ignore": [ + "docs" + ], +} From 8f01c632b36657494807c311698c5be2194e9139 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 16:38:47 +0100 Subject: [PATCH 065/118] Incrementing version to v4.3b.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index 64d0af06..a5d57b2e 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -21,7 +21,7 @@ '''.strip().split() ) __email__ = 'wolph@wol.ph' -__version__ = '4.2.0' +__version__ = '4.3b.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' From 10b8eaf26f760255737329775590466725f0f590 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 16:54:36 +0100 Subject: [PATCH 066/118] strings are also supported as "widgets" --- progressbar/bar.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 806a98a8..6be96e07 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -37,7 +37,7 @@ class ProgressBarMixinBase(abc.ABC): #: fall back to 80 if auto detection is not possible. term_width: int = 80 #: The widgets to render, defaults to the result of `default_widget()` - widgets: types.List[widgets_module.WidgetBase] + widgets: types.List[widgets_module.WidgetBase | str] #: When going beyond the max_value, raise an error if True or silently #: ignore otherwise max_error: bool @@ -439,7 +439,9 @@ def __init__( self, min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, - widgets: types.Optional[types.List[widgets_module.WidgetBase]] = None, + widgets: types.Optional[ + types.List[widgets_module.WidgetBase | str] + ] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, From 78c95b649984b1610d1d4420143baeaa31252d00 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 22:38:21 +0100 Subject: [PATCH 067/118] Added more tests and clarified more errors --- progressbar/bar.py | 34 +++++++++++++++------------------- tests/test_custom_widgets.py | 5 +++++ tests/test_failure.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 6be96e07..9a0afd2d 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -111,14 +111,7 @@ def finish(self): # pragma: no cover def __del__(self): if not self._finished and self._started: # pragma: no cover - try: - self.finish() - except Exception: - # Never raise during cleanup. We're too late now - logging.debug( - 'Exception raised during ProgressBar cleanup', - exc_info=True, - ) + self.finish() def __getstate__(self): return self.__dict__ @@ -656,7 +649,7 @@ def data(self) -> types.Dict[str, types.Any]: total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.0), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -787,20 +780,23 @@ def update(self, value=None, force=False, **kwargs): self.start() return self.update(value, force=force, **kwargs) - if value is not None and value is not base.UnknownLength: + if value is not None and value is not base.UnknownLength and isinstance(value, int): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update pass - elif self.min_value <= value <= self.max_value: # pragma: no cover - # Correct value, let's accept - pass - elif self.max_error: + elif self.min_value > value: raise ValueError( - 'Value %s is out of range, should be between %s and %s' + 'Value %s is too small. Should be between %s and %s' % (value, self.min_value, self.max_value) ) - else: - self.max_value = value + elif self.max_value < value: + if self.max_error: + raise ValueError( + 'Value %s is too large. Should be between %s and %s' + % (value, self.min_value, self.max_value) + ) + else: + value = self.max_value self.previous_value = self.value self.value = value @@ -810,8 +806,8 @@ def update(self, value=None, force=False, **kwargs): for key in kwargs: if key not in self.variables: raise TypeError( - 'update() got an unexpected keyword ' - + 'argument {0!r}'.format(key) + 'update() got an unexpected variable name as argument ' + '{0!r}'.format(key) ) elif self.variables[key] != kwargs[key]: self.variables[key] = kwargs[key] diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index 95252818..e757ded5 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -1,4 +1,7 @@ import time + +import pytest + import progressbar @@ -60,6 +63,8 @@ def test_variable_widget_widget(): p.update(i, text=False) i += 1 p.update(i, text=True, error='a') + with pytest.raises(TypeError): + p.update(i, non_existing_variable='error!') p.finish() diff --git a/tests/test_failure.py b/tests/test_failure.py index 030ab292..a389da4b 100644 --- a/tests/test_failure.py +++ b/tests/test_failure.py @@ -122,3 +122,15 @@ def test_variable_not_str(): def test_variable_too_many_strs(): with pytest.raises(ValueError): progressbar.Variable('too long') + + +def test_negative_value(): + bar = progressbar.ProgressBar(max_value=10) + with pytest.raises(ValueError): + bar.update(value=-1) + + +def test_increment(): + bar = progressbar.ProgressBar(max_value=10) + bar.increment() + del bar From b23c93e7d5a8abe6af9e13b7bdb62a6c3e884f12 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 2 Nov 2022 02:47:58 +0100 Subject: [PATCH 068/118] Small type improvements --- progressbar/bar.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 9a0afd2d..b1a5c96b 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -2,6 +2,7 @@ import abc import logging +import math import os import sys import time @@ -11,7 +12,6 @@ from datetime import datetime from typing import Type -import math from python_utils import converters, types from . import ( @@ -37,7 +37,7 @@ class ProgressBarMixinBase(abc.ABC): #: fall back to 80 if auto detection is not possible. term_width: int = 80 #: The widgets to render, defaults to the result of `default_widget()` - widgets: types.List[widgets_module.WidgetBase | str] + widgets: types.MutableSequence[widgets_module.WidgetBase | str] #: When going beyond the max_value, raise an error if True or silently #: ignore otherwise max_error: bool @@ -433,8 +433,7 @@ def __init__( min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, widgets: types.Optional[ - types.List[widgets_module.WidgetBase | str] - ] = None, + types.Sequence[widgets_module.WidgetBase | str]] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, @@ -780,16 +779,19 @@ def update(self, value=None, force=False, **kwargs): self.start() return self.update(value, force=force, **kwargs) - if value is not None and value is not base.UnknownLength and isinstance(value, int): + if value is not None and value is not base.UnknownLength and isinstance( + value, + int + ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update pass - elif self.min_value > value: + elif self.min_value > value: # type: ignore raise ValueError( 'Value %s is too small. Should be between %s and %s' % (value, self.min_value, self.max_value) ) - elif self.max_value < value: + elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( 'Value %s is too large. Should be between %s and %s' @@ -799,7 +801,7 @@ def update(self, value=None, force=False, **kwargs): value = self.max_value self.previous_value = self.value - self.value = value + self.value = value # type: ignore # Save the updated values for dynamic messages variables_changed = False @@ -817,7 +819,7 @@ def update(self, value=None, force=False, **kwargs): self.updates += 1 ResizableMixin.update(self, value=value) ProgressBarBase.update(self, value=value) - StdRedirectMixin.update(self, value=value) + StdRedirectMixin.update(self, value=value) # type: ignore # Only flush if something was actually written self.fd.flush() From 8a86cb4fedb0a9f31c11b21e88d8b9c01d66e2d6 Mon Sep 17 00:00:00 2001 From: LGTM Migrator Date: Wed, 9 Nov 2022 08:20:47 +0000 Subject: [PATCH 069/118] Add CodeQL workflow for GitHub code scanning --- .github/workflows/codeql.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..44ccfb21 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: "CodeQL" + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + schedule: + - cron: "24 21 * * 1" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python, javascript ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + if: ${{ matrix.language == 'python' || matrix.language == 'javascript' }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From aa06fd39205c78d7597cf114874e26966751c703 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 13 Nov 2022 22:22:52 +0100 Subject: [PATCH 070/118] added ansi code from @kdschlosser --- progressbar/ansi.py | 1042 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1042 insertions(+) create mode 100644 progressbar/ansi.py diff --git a/progressbar/ansi.py b/progressbar/ansi.py new file mode 100644 index 00000000..9b9f2dbc --- /dev/null +++ b/progressbar/ansi.py @@ -0,0 +1,1042 @@ +import threading + +from . import utils +from .os_functions import getch + +ESC = '\x1B' +CSI = ESC + '[' + + +CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) + + +# Report Cursor Position (CPR), response = [row;column] as row;columnR +class _CPR(str): + _response_lock = threading.Lock() + + def __call__(self, stream): + res = '' + + with self._response_lock: + stream.write(str(self)) + stream.flush() + + while not res.endswith('R'): + char = getch() + + if char is not None: + res += char + + res = res[2:-1].split(';') + + res = tuple(int(item) if item.isdigit() else item for item in res) + + if len(res) == 1: + return res[0] + + return res + + def row(self, stream): + row, _ = self(stream) + return row + + def column(self, stream): + _, column = self(stream) + return column + + +DSR = CSI + '{n}n' # Device Status Report (DSR) +CPR = _CPR(DSR.format(n=6)) + +IL = CSI + '{n}L' # Insert n Line(s) (default = 1) + +DECRST = CSI + '?{n}l' # DEC Private Mode Reset +DECRTCEM = DECRST.format(n=25) # Hide Cursor + +DECSET = CSI + '?{n}h' # DEC Private Mode Set +DECTCEM = DECSET.format(n=25) # Show Cursor + + +# possible values: +# 0 = Normal (default) +# 1 = Bold +# 2 = Faint +# 3 = Italic +# 4 = Underlined +# 5 = Slow blink (appears as Bold) +# 6 = Rapid Blink +# 7 = Inverse +# 8 = Invisible, i.e., hidden (VT300) +# 9 = Strike through +# 10 = Primary (default) font +# 20 = Gothic Font +# 21 = Double underline +# 22 = Normal intensity (neither bold nor faint) +# 23 = Not italic +# 24 = Not underlined +# 25 = Steady (not blinking) +# 26 = Proportional spacing +# 27 = Not inverse +# 28 = Visible, i.e., not hidden (VT300) +# 29 = No strike through +# 30 = Set foreground color to Black +# 31 = Set foreground color to Red +# 32 = Set foreground color to Green +# 33 = Set foreground color to Yellow +# 34 = Set foreground color to Blue +# 35 = Set foreground color to Magenta +# 36 = Set foreground color to Cyan +# 37 = Set foreground color to White +# 39 = Set foreground color to default (original) +# 40 = Set background color to Black +# 41 = Set background color to Red +# 42 = Set background color to Green +# 43 = Set background color to Yellow +# 44 = Set background color to Blue +# 45 = Set background color to Magenta +# 46 = Set background color to Cyan +# 47 = Set background color to White +# 49 = Set background color to default (original). +# 50 = Disable proportional spacing +# 51 = Framed +# 52 = Encircled +# 53 = Overlined +# 54 = Neither framed nor encircled +# 55 = Not overlined +# 58 = Set underine color (2;r;g;b) +# 59 = Default underline color +# If 16-color support is compiled, the following apply. +# Assume that xterm’s resources are set so that the ISO color codes are the +# first 8 of a set of 16. Then the aixterm colors are the bright versions of +# the ISO colors: +# 90 = Set foreground color to Black +# 91 = Set foreground color to Red +# 92 = Set foreground color to Green +# 93 = Set foreground color to Yellow +# 94 = Set foreground color to Blue +# 95 = Set foreground color to Magenta +# 96 = Set foreground color to Cyan +# 97 = Set foreground color to White +# 100 = Set background color to Black +# 101 = Set background color to Red +# 102 = Set background color to Green +# 103 = Set background color to Yellow +# 104 = Set background color to Blue +# 105 = Set background color to Magenta +# 106 = Set background color to Cyan +# 107 = Set background color to White +# +# If xterm is compiled with the 16-color support disabled, it supports the +# following, from rxvt: +# 100 = Set foreground and background color to default + +# If 88- or 256-color support is compiled, the following apply. +# 38;5;x = Set foreground color to x +# 48;5;x = Set background color to x +SGR = CSI + '{n}m' # Character Attributes + + +class ENCIRCLED(str): + """ + Your guess is as good as mine. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=52), args[1], SGR.format(n=54)]) + return super(ENCIRCLED, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes encircled? + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=52), SGR.format(n=54)) + + +class FRAMED(str): + """ + Your guess is as good as mine. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=51), args[1], SGR.format(n=54)]) + return super(FRAMED, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes Frame? + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=51), SGR.format(n=54)) + + +class GOTHIC(str): + """ + Changes text font to Gothic + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=20), args[1], SGR.format(n=10)]) + return super(GOTHIC, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Makes text font normal + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=20), SGR.format(n=10)) + + +class ITALIC(str): + """ + Makes the text italic + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=3), args[1], SGR.format(n=23)]) + return super(ITALIC, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the italic. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=3), SGR.format(n=23)) + + +class STRIKE_THROUGH(str): + """ + Strikes through the text. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=9), args[1], SGR.format(n=29)]) + return super(STRIKE_THROUGH, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the strike through + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=9), SGR.format(n=29)) + + +class FAST_BLINK(str): + """ + Makes the text blink fast + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=6), args[1], SGR.format(n=25)]) + return super(FAST_BLINK, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Makes the text steady + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=6), SGR.format(n=25)) + + +class SLOW_BLINK(str): + """ + Makes the text blonk slowely. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=5), args[1], SGR.format(n=25)]) + return super(SLOW_BLINK, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Makes the text steady + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=5), SGR.format(n=25)) + + +class OVERLINE(str): + """ + Overlines the text provided. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=53), args[1], SGR.format(n=55)]) + return super(OVERLINE, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the overline from the text + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=53), SGR.format(n=55)) + + +class UNDERLINE(str): + """ + Underlines the text provided. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=4), args[1], SGR.format(n=24)]) + return super(UNDERLINE, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the underline from the text + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=4), SGR.format(n=24)) + + +class DOUBLE_UNDERLINE(str): + """ + Double underlines the text provided. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=21), args[1], SGR.format(n=24)]) + return super(DOUBLE_UNDERLINE, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the double underline from the text + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=21), SGR.format(n=24)) + + +class BOLD(str): + """ + Makes the supplied text BOLD + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=1), args[1], SGR.format(n=22)]) + return super(BOLD, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the BOLD from the text. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=1), SGR.format(n=22)) + + +class FAINT(str): + """ + Makes the supplied text FAINT + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=2), args[1], SGR.format(n=22)]) + return super(FAINT, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the FAINT from the text. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=2), SGR.format(n=22)) + + +class INVERT_COLORS(str): + """ + Switches the background and forground colors. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=7), args[1], SGR.format(n=27)]) + return super(INVERT_COLORS, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the color inversion and returns the original text provided. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=7), SGR.format(n=27)) + + +class Color(str): + """ + Color base class + + This class is a wrapper for the `str` class that adds a couple of + class methods. It makes it easier to add and remove an ansi color escape + sequence from a string of text. + + There are 141 HTML colors that have already been provided however you can + make a custom color if you would like. + + To make a custom color simply subclass this class and override the `_rgb` + class attribute supplying your own RGB value as a tuple (R, G, B) + """ + _rgb = (0, 0, 0) + + @classmethod + def fg(cls, text): + """ + Adds the ansi escape codes to set the foreground color to this color. + """ + return cls(''.join([ + CSI, + '38;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=39) + ])) + + @classmethod + def bg(cls, text): + """ + Adds the ansi escape codes to set the background color to this color. + """ + return cls(''.join([ + CSI, + '48;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=49) + ])) + + @classmethod + def ul(cls, text): + """ + Adds the ansi escape codes to set the underline color to this color. + """ + return cls(''.join([ + CSI, + '58;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=59) + ])) + + @property + def raw(self): + """ + Removes this color from the text provided + """ + text = self.__str__() + + if text.startswith(CSI + '48;2'): + text = utils.remove_ansi( + text, + CSI + '48;2;{0};{1};{2}m'.format(*self._rgb), + SGR.format(n=49) + ) + elif text.startswith(CSI + '38;2'): + text = utils.remove_ansi( + text, + CSI + '38;2;{0};{1};{2}m'.format(*self._rgb), + SGR.format(n=39) + ) + + else: + text = utils.remove_ansi( + text, + CSI + '58;2;{0};{1};{2}m'.format(*self._rgb), + SGR.format(n=59) + ) + + return text + + +class INDIAN_RED(Color): + _rgb = (205, 92, 92) + + +class LIGHT_CORAL(Color): + _rgb = (240, 128, 128) + + +class SALMON(Color): + _rgb = (250, 128, 114) + + +class DARK_SALMON(Color): + _rgb = (233, 150, 122) + + +class LIGHT_SALMON(Color): + _rgb = (255, 160, 122) + + +class CRIMSON(Color): + _rgb = (220, 20, 60) + + +class RED(Color): + _rgb = (255, 0, 0) + + +class FIRE_BRICK(Color): + _rgb = (178, 34, 34) + + +class DARK_RED(Color): + _rgb = (139, 0, 0) + + +class PINK(Color): + _rgb = (255, 192, 203) + + +class LIGHT_PINK(Color): + _rgb = (255, 182, 193) + + +class HOT_PINK(Color): + _rgb = (255, 105, 180) + + +class DEEP_PINK(Color): + _rgb = (255, 20, 147) + + +class MEDIUM_VIOLET_RED(Color): + _rgb = (199, 21, 133) + + +class PALE_VIOLET_RED(Color): + _rgb = (219, 112, 147) + + +class CORAL(Color): + _rgb = (255, 127, 80) + + +class TOMATO(Color): + _rgb = (255, 99, 71) + + +class ORANGE_RED(Color): + _rgb = (255, 69, 0) + + +class DARK_ORANGE(Color): + _rgb = (255, 140, 0) + + +class ORANGE(Color): + _rgb = (255, 165, 0) + + +class GOLD(Color): + _rgb = (255, 215, 0) + + +class YELLOW(Color): + _rgb = (255, 255, 0) + + +class LIGHT_YELLOW(Color): + _rgb = (255, 255, 224) + + +class LEMON_CHIFFON(Color): + _rgb = (255, 250, 205) + + +class LIGHT_GOLDENROD_YELLOW(Color): + _rgb = (250, 250, 210) + + +class PAPAYA_WHIP(Color): + _rgb = (255, 239, 213) + + +class MOCCASIN(Color): + _rgb = (255, 228, 181) + + +class PEACH_PUFF(Color): + _rgb = (255, 218, 185) + + +class PALE_GOLDENROD(Color): + _rgb = (238, 232, 170) + + +class KHAKI(Color): + _rgb = (240, 230, 140) + + +class DARK_KHAKI(Color): + _rgb = (189, 183, 107) + + +class LAVENDER(Color): + _rgb = (230, 230, 250) + + +class THISTLE(Color): + _rgb = (216, 191, 216) + + +class PLUM(Color): + _rgb = (221, 160, 221) + + +class VIOLET(Color): + _rgb = (238, 130, 238) + + +class ORCHID(Color): + _rgb = (218, 112, 214) + + +class FUCHSIA(Color): + _rgb = (255, 0, 255) + + +class MAGENTA(Color): + _rgb = (255, 0, 255) + + +class MEDIUM_ORCHID(Color): + _rgb = (186, 85, 211) + + +class MEDIUM_PURPLE(Color): + _rgb = (147, 112, 219) + + +class REBECCA_PURPLE(Color): + _rgb = (102, 51, 153) + + +class BLUE_VIOLET(Color): + _rgb = (138, 43, 226) + + +class DARK_VIOLET(Color): + _rgb = (148, 0, 211) + + +class DARK_ORCHID(Color): + _rgb = (153, 50, 204) + + +class DARK_MAGENTA(Color): + _rgb = (139, 0, 139) + + +class PURPLE(Color): + _rgb = (128, 0, 128) + + +class INDIGO(Color): + _rgb = (75, 0, 130) + + +class SLATE_BLUE(Color): + _rgb = (106, 90, 205) + + +class DARK_SLATE_BLUE(Color): + _rgb = (72, 61, 139) + + +class MEDIUM_SLATE_BLUE(Color): + _rgb = (123, 104, 238) + + +class GREEN_YELLOW(Color): + _rgb = (173, 255, 47) + + +class CHARTREUSE(Color): + _rgb = (127, 255, 0) + + +class LAWN_GREEN(Color): + _rgb = (124, 252, 0) + + +class LIME(Color): + _rgb = (0, 255, 0) + + +class LIME_GREEN(Color): + _rgb = (50, 205, 50) + + +class PALE_GREEN(Color): + _rgb = (152, 251, 152) + + +class LIGHT_GREEN(Color): + _rgb = (144, 238, 144) + + +class MEDIUM_SPRING_GREEN(Color): + _rgb = (0, 250, 154) + + +class SPRING_GREEN(Color): + _rgb = (0, 255, 127) + + +class MEDIUM_SEA_GREEN(Color): + _rgb = (60, 179, 113) + + +class SEA_GREEN(Color): + _rgb = (46, 139, 87) + + +class FOREST_GREEN(Color): + _rgb = (34, 139, 34) + + +class GREEN(Color): + _rgb = (0, 128, 0) + + +class DARK_GREEN(Color): + _rgb = (0, 100, 0) + + +class YELLOW_GREEN(Color): + _rgb = (154, 205, 50) + + +class OLIVE_DRAB(Color): + _rgb = (107, 142, 35) + + +class OLIVE(Color): + _rgb = (128, 128, 0) + + +class DARK_OLIVE_GREEN(Color): + _rgb = (85, 107, 47) + + +class MEDIUM_AQUAMARINE(Color): + _rgb = (102, 205, 170) + + +class DARK_SEA_GREEN(Color): + _rgb = (143, 188, 139) + + +class LIGHT_SEA_GREEN(Color): + _rgb = (32, 178, 170) + + +class DARK_CYAN(Color): + _rgb = (0, 139, 139) + + +class TEAL(Color): + _rgb = (0, 128, 128) + + +class AQUA(Color): + _rgb = (0, 255, 255) + + +class CYAN(Color): + _rgb = (0, 255, 255) + + +class LIGHT_CYAN(Color): + _rgb = (224, 255, 255) + + +class PALE_TURQUOISE(Color): + _rgb = (175, 238, 238) + + +class AQUAMARINE(Color): + _rgb = (127, 255, 212) + + +class TURQUOISE(Color): + _rgb = (64, 224, 208) + + +class MEDIUM_TURQUOISE(Color): + _rgb = (72, 209, 204) + + +class DARK_TURQUOISE(Color): + _rgb = (0, 206, 209) + + +class CADET_BLUE(Color): + _rgb = (95, 158, 160) + + +class STEEL_BLUE(Color): + _rgb = (70, 130, 180) + + +class LIGHT_STEEL_BLUE(Color): + _rgb = (176, 196, 222) + + +class POWDER_BLUE(Color): + _rgb = (176, 224, 230) + + +class LIGHT_BLUE(Color): + _rgb = (173, 216, 230) + + +class SKY_BLUE(Color): + _rgb = (135, 206, 235) + + +class LIGHT_SKY_BLUE(Color): + _rgb = (135, 206, 250) + + +class DEEP_SKY_BLUE(Color): + _rgb = (0, 191, 255) + + +class DODGER_BLUE(Color): + _rgb = (30, 144, 255) + + +class CORNFLOWER_BLUE(Color): + _rgb = (100, 149, 237) + + +class ROYAL_BLUE(Color): + _rgb = (65, 105, 225) + + +class BLUE(Color): + _rgb = (0, 0, 255) + + +class MEDIUM_BLUE(Color): + _rgb = (0, 0, 205) + + +class DARK_BLUE(Color): + _rgb = (0, 0, 139) + + +class NAVY(Color): + _rgb = (0, 0, 128) + + +class MIDNIGHT_BLUE(Color): + _rgb = (25, 25, 112) + + +class CORNSILK(Color): + _rgb = (255, 248, 220) + + +class BLANCHED_ALMOND(Color): + _rgb = (255, 235, 205) + + +class BISQUE(Color): + _rgb = (255, 228, 196) + + +class NAVAJO_WHITE(Color): + _rgb = (255, 222, 173) + + +class WHEAT(Color): + _rgb = (245, 222, 179) + + +class BURLY_WOOD(Color): + _rgb = (222, 184, 135) + + +class TAN(Color): + _rgb = (210, 180, 140) + + +class ROSY_BROWN(Color): + _rgb = (188, 143, 143) + + +class SANDY_BROWN(Color): + _rgb = (244, 164, 96) + + +class GOLDENROD(Color): + _rgb = (218, 165, 32) + + +class DARK_GOLDENROD(Color): + _rgb = (184, 134, 11) + + +class PERU(Color): + _rgb = (205, 133, 63) + + +class CHOCOLATE(Color): + _rgb = (210, 105, 30) + + +class SADDLE_BROWN(Color): + _rgb = (139, 69, 19) + + +class SIENNA(Color): + _rgb = (160, 82, 45) + + +class BROWN(Color): + _rgb = (165, 42, 42) + + +class MAROON(Color): + _rgb = (128, 0, 0) + + +class WHITE(Color): + _rgb = (255, 255, 255) + + +class SNOW(Color): + _rgb = (255, 250, 250) + + +class HONEY_DEW(Color): + _rgb = (240, 255, 240) + + +class MINT_CREAM(Color): + _rgb = (245, 255, 250) + + +class AZURE(Color): + _rgb = (240, 255, 255) + + +class ALICE_BLUE(Color): + _rgb = (240, 248, 255) + + +class GHOST_WHITE(Color): + _rgb = (248, 248, 255) + + +class WHITE_SMOKE(Color): + _rgb = (245, 245, 245) + + +class SEA_SHELL(Color): + _rgb = (255, 245, 238) + + +class BEIGE(Color): + _rgb = (245, 245, 220) + + +class OLD_LACE(Color): + _rgb = (253, 245, 230) + + +class FLORAL_WHITE(Color): + _rgb = (255, 250, 240) + + +class IVORY(Color): + _rgb = (255, 255, 240) + + +class ANTIQUE_WHITE(Color): + _rgb = (250, 235, 215) + + +class LINEN(Color): + _rgb = (250, 240, 230) + + +class LAVENDER_BLUSH(Color): + _rgb = (255, 240, 245) + + +class MISTY_ROSE(Color): + _rgb = (255, 228, 225) + + +class GAINSBORO(Color): + _rgb = (220, 220, 220) + + +class LIGHT_GRAY(Color): + _rgb = (211, 211, 211) + + +class SILVER(Color): + _rgb = (192, 192, 192) + + +class DARK_GRAY(Color): + _rgb = (169, 169, 169) + + +class GRAY(Color): + _rgb = (128, 128, 128) + + +class DIM_GRAY(Color): + _rgb = (105, 105, 105) + + +class LIGHT_SLATE_GRAY(Color): + _rgb = (119, 136, 153) + + +class SLATE_GRAY(Color): + _rgb = (112, 128, 144) + + +class DARK_SLATE_GRAY(Color): + _rgb = (47, 79, 79) + + +class BLACK(Color): + _rgb = (0, 0, 0) From 982aa343f33c59fb097d47358a3cee4ecf0ad72e Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 13 Nov 2022 02:24:34 +0100 Subject: [PATCH 071/118] added linux code from @kdschlosser --- progressbar/os_functions/__init__.py | 24 ++++++++++++++++++++++++ progressbar/os_functions/nix.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 progressbar/os_functions/__init__.py create mode 100644 progressbar/os_functions/nix.py diff --git a/progressbar/os_functions/__init__.py b/progressbar/os_functions/__init__.py new file mode 100644 index 00000000..8b442283 --- /dev/null +++ b/progressbar/os_functions/__init__.py @@ -0,0 +1,24 @@ +import sys + +if sys.platform.startswith('win'): + from .windows import ( + getch as _getch, + set_console_mode as _set_console_mode, + reset_console_mode as _reset_console_mode + ) + +else: + from .nix import getch as _getch + + def _reset_console_mode(): + pass + + + def _set_console_mode(): + pass + + +getch = _getch +reset_console_mode = _reset_console_mode +set_console_mode = _set_console_mode + diff --git a/progressbar/os_functions/nix.py b/progressbar/os_functions/nix.py new file mode 100644 index 00000000..e46fbdf0 --- /dev/null +++ b/progressbar/os_functions/nix.py @@ -0,0 +1,15 @@ +import sys +import tty +import termios + + +def getch(): + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + return ch From 566ba280010953ede9511c397f8227a8b83e779b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 20 Nov 2022 04:01:43 +0100 Subject: [PATCH 072/118] added windows code from @kdschlosser --- progressbar/os_functions/windows.py | 144 ++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 progressbar/os_functions/windows.py diff --git a/progressbar/os_functions/windows.py b/progressbar/os_functions/windows.py new file mode 100644 index 00000000..edac0696 --- /dev/null +++ b/progressbar/os_functions/windows.py @@ -0,0 +1,144 @@ +import ctypes +from ctypes.wintypes import ( + DWORD as _DWORD, + HANDLE as _HANDLE, + BOOL as _BOOL, + WORD as _WORD, + UINT as _UINT, + WCHAR as _WCHAR, + CHAR as _CHAR, + SHORT as _SHORT +) + +_kernel32 = ctypes.windll.Kernel32 + +_ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 +_ENABLE_PROCESSED_OUTPUT = 0x0001 +_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + +_STD_INPUT_HANDLE = _DWORD(-10) +_STD_OUTPUT_HANDLE = _DWORD(-11) + + +_GetConsoleMode = _kernel32.GetConsoleMode +_GetConsoleMode.restype = _BOOL + +_SetConsoleMode = _kernel32.SetConsoleMode +_SetConsoleMode.restype = _BOOL + +_GetStdHandle = _kernel32.GetStdHandle +_GetStdHandle.restype = _HANDLE + +_ReadConsoleInput = _kernel32.ReadConsoleInputA +_ReadConsoleInput.restype = _BOOL + + +_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) +_input_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) + +_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) +_output_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) + + +class _COORD(ctypes.Structure): + _fields_ = [ + ('X', _SHORT), + ('Y', _SHORT) + ] + + +class _FOCUS_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('bSetFocus', _BOOL) + ] + + +class _KEY_EVENT_RECORD(ctypes.Structure): + class _uchar(ctypes.Union): + _fields_ = [ + ('UnicodeChar', _WCHAR), + ('AsciiChar', _CHAR) + ] + + _fields_ = [ + ('bKeyDown', _BOOL), + ('wRepeatCount', _WORD), + ('wVirtualKeyCode', _WORD), + ('wVirtualScanCode', _WORD), + ('uChar', _uchar), + ('dwControlKeyState', _DWORD) + ] + + +class _MENU_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwCommandId', _UINT) + ] + + +class _MOUSE_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwMousePosition', _COORD), + ('dwButtonState', _DWORD), + ('dwControlKeyState', _DWORD), + ('dwEventFlags', _DWORD) + ] + + +class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): + _fields_ = [ + ('dwSize', _COORD) + ] + + +class _INPUT_RECORD(ctypes.Structure): + class _Event(ctypes.Union): + _fields_ = [ + ('KeyEvent', _KEY_EVENT_RECORD), + ('MouseEvent', _MOUSE_EVENT_RECORD), + ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), + ('MenuEvent', _MENU_EVENT_RECORD), + ('FocusEvent', _FOCUS_EVENT_RECORD) + ] + + _fields_ = [ + ('EventType', _WORD), + ('Event', _Event) + ] + + +def reset_console_mode(): + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) + + +def set_console_mode(): + mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) + + mode = ( + _output_mode.value | + _ENABLE_PROCESSED_OUTPUT | + _ENABLE_VIRTUAL_TERMINAL_PROCESSING + ) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) + + +def getch(): + lpBuffer = (_INPUT_RECORD * 2)() + nLength = _DWORD(2) + lpNumberOfEventsRead = _DWORD() + + _ReadConsoleInput( + _HANDLE(_hConsoleInput), + lpBuffer, nLength, + ctypes.byref(lpNumberOfEventsRead) + ) + + char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') + if char == '\x00': + return None + + return char From db63a3cfa605b0608a0f5139f8052d87831f76f5 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Nov 2022 17:37:55 +0100 Subject: [PATCH 073/118] consistent code styling and converted color to namedtuple --- progressbar/ansi.py | 466 +++++++++++++++++++++++--------------------- 1 file changed, 240 insertions(+), 226 deletions(-) diff --git a/progressbar/ansi.py b/progressbar/ansi.py index 9b9f2dbc..2ace68d6 100644 --- a/progressbar/ansi.py +++ b/progressbar/ansi.py @@ -1,3 +1,4 @@ +import collections import threading from . import utils @@ -6,7 +7,6 @@ ESC = '\x1B' CSI = ESC + '[' - CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) @@ -56,7 +56,6 @@ def column(self, stream): DECSET = CSI + '?{n}h' # DEC Private Mode Set DECTCEM = DECSET.format(n=25) # Show Cursor - # possible values: # 0 = Normal (default) # 1 = Bold @@ -137,9 +136,9 @@ def column(self, stream): class ENCIRCLED(str): - """ + ''' Your guess is as good as mine. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -149,17 +148,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes encircled? - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=52), SGR.format(n=54)) class FRAMED(str): - """ + ''' Your guess is as good as mine. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -169,17 +168,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes Frame? - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=51), SGR.format(n=54)) class GOTHIC(str): - """ + ''' Changes text font to Gothic - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -189,17 +188,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Makes text font normal - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=20), SGR.format(n=10)) class ITALIC(str): - """ + ''' Makes the text italic - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -209,17 +208,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the italic. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=3), SGR.format(n=23)) class STRIKE_THROUGH(str): - """ + ''' Strikes through the text. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -229,17 +228,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the strike through - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=9), SGR.format(n=29)) class FAST_BLINK(str): - """ + ''' Makes the text blink fast - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -249,17 +248,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Makes the text steady - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=6), SGR.format(n=25)) class SLOW_BLINK(str): - """ + ''' Makes the text blonk slowely. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -269,17 +268,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Makes the text steady - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=5), SGR.format(n=25)) class OVERLINE(str): - """ + ''' Overlines the text provided. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -289,17 +288,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the overline from the text - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=53), SGR.format(n=55)) class UNDERLINE(str): - """ + ''' Underlines the text provided. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -309,17 +308,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the underline from the text - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=4), SGR.format(n=24)) class DOUBLE_UNDERLINE(str): - """ + ''' Double underlines the text provided. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -329,17 +328,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the double underline from the text - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=21), SGR.format(n=24)) - + class BOLD(str): - """ + ''' Makes the supplied text BOLD - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -349,17 +348,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the BOLD from the text. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=1), SGR.format(n=22)) class FAINT(str): - """ + ''' Makes the supplied text FAINT - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -369,18 +368,18 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the FAINT from the text. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=2), SGR.format(n=22)) class INVERT_COLORS(str): - """ + ''' Switches the background and forground colors. - """ - + ''' + @classmethod def __new__(cls, *args, **kwargs): args = list(args) @@ -389,15 +388,18 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the color inversion and returns the original text provided. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=7), SGR.format(n=27)) +RGB = collections.namedtuple('RGB', ['r', 'g', 'b']) + + class Color(str): - """ + ''' Color base class This class is a wrapper for the `str` class that adds a couple of @@ -409,50 +411,62 @@ class methods. It makes it easier to add and remove an ansi color escape To make a custom color simply subclass this class and override the `_rgb` class attribute supplying your own RGB value as a tuple (R, G, B) - """ - _rgb = (0, 0, 0) + ''' + _rgb: RGB = RGB(0, 0, 0) @classmethod def fg(cls, text): - """ + ''' Adds the ansi escape codes to set the foreground color to this color. - """ - return cls(''.join([ - CSI, - '38;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=39) - ])) + ''' + return cls( + ''.join( + [ + CSI, + '38;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=39) + ] + ) + ) @classmethod def bg(cls, text): - """ + ''' Adds the ansi escape codes to set the background color to this color. - """ - return cls(''.join([ - CSI, - '48;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=49) - ])) + ''' + return cls( + ''.join( + [ + CSI, + '48;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=49) + ] + ) + ) @classmethod def ul(cls, text): - """ + ''' Adds the ansi escape codes to set the underline color to this color. - """ - return cls(''.join([ - CSI, - '58;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=59) - ])) + ''' + return cls( + ''.join( + [ + CSI, + '58;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=59) + ] + ) + ) @property def raw(self): - """ + ''' Removes this color from the text provided - """ + ''' text = self.__str__() if text.startswith(CSI + '48;2'): @@ -479,564 +493,564 @@ def raw(self): class INDIAN_RED(Color): - _rgb = (205, 92, 92) + _rgb = RGB(205, 92, 92) class LIGHT_CORAL(Color): - _rgb = (240, 128, 128) + _rgb = RGB(240, 128, 128) class SALMON(Color): - _rgb = (250, 128, 114) + _rgb = RGB(250, 128, 114) class DARK_SALMON(Color): - _rgb = (233, 150, 122) + _rgb = RGB(233, 150, 122) class LIGHT_SALMON(Color): - _rgb = (255, 160, 122) + _rgb = RGB(255, 160, 122) class CRIMSON(Color): - _rgb = (220, 20, 60) + _rgb = RGB(220, 20, 60) class RED(Color): - _rgb = (255, 0, 0) + _rgb = RGB(255, 0, 0) class FIRE_BRICK(Color): - _rgb = (178, 34, 34) + _rgb = RGB(178, 34, 34) class DARK_RED(Color): - _rgb = (139, 0, 0) + _rgb = RGB(139, 0, 0) class PINK(Color): - _rgb = (255, 192, 203) + _rgb = RGB(255, 192, 203) class LIGHT_PINK(Color): - _rgb = (255, 182, 193) + _rgb = RGB(255, 182, 193) class HOT_PINK(Color): - _rgb = (255, 105, 180) + _rgb = RGB(255, 105, 180) class DEEP_PINK(Color): - _rgb = (255, 20, 147) + _rgb = RGB(255, 20, 147) class MEDIUM_VIOLET_RED(Color): - _rgb = (199, 21, 133) + _rgb = RGB(199, 21, 133) class PALE_VIOLET_RED(Color): - _rgb = (219, 112, 147) + _rgb = RGB(219, 112, 147) class CORAL(Color): - _rgb = (255, 127, 80) + _rgb = RGB(255, 127, 80) class TOMATO(Color): - _rgb = (255, 99, 71) + _rgb = RGB(255, 99, 71) class ORANGE_RED(Color): - _rgb = (255, 69, 0) + _rgb = RGB(255, 69, 0) class DARK_ORANGE(Color): - _rgb = (255, 140, 0) + _rgb = RGB(255, 140, 0) class ORANGE(Color): - _rgb = (255, 165, 0) + _rgb = RGB(255, 165, 0) class GOLD(Color): - _rgb = (255, 215, 0) + _rgb = RGB(255, 215, 0) class YELLOW(Color): - _rgb = (255, 255, 0) + _rgb = RGB(255, 255, 0) class LIGHT_YELLOW(Color): - _rgb = (255, 255, 224) + _rgb = RGB(255, 255, 224) class LEMON_CHIFFON(Color): - _rgb = (255, 250, 205) + _rgb = RGB(255, 250, 205) class LIGHT_GOLDENROD_YELLOW(Color): - _rgb = (250, 250, 210) + _rgb = RGB(250, 250, 210) class PAPAYA_WHIP(Color): - _rgb = (255, 239, 213) + _rgb = RGB(255, 239, 213) class MOCCASIN(Color): - _rgb = (255, 228, 181) + _rgb = RGB(255, 228, 181) class PEACH_PUFF(Color): - _rgb = (255, 218, 185) + _rgb = RGB(255, 218, 185) class PALE_GOLDENROD(Color): - _rgb = (238, 232, 170) + _rgb = RGB(238, 232, 170) class KHAKI(Color): - _rgb = (240, 230, 140) + _rgb = RGB(240, 230, 140) class DARK_KHAKI(Color): - _rgb = (189, 183, 107) + _rgb = RGB(189, 183, 107) class LAVENDER(Color): - _rgb = (230, 230, 250) + _rgb = RGB(230, 230, 250) class THISTLE(Color): - _rgb = (216, 191, 216) + _rgb = RGB(216, 191, 216) class PLUM(Color): - _rgb = (221, 160, 221) + _rgb = RGB(221, 160, 221) class VIOLET(Color): - _rgb = (238, 130, 238) + _rgb = RGB(238, 130, 238) class ORCHID(Color): - _rgb = (218, 112, 214) + _rgb = RGB(218, 112, 214) class FUCHSIA(Color): - _rgb = (255, 0, 255) + _rgb = RGB(255, 0, 255) class MAGENTA(Color): - _rgb = (255, 0, 255) + _rgb = RGB(255, 0, 255) class MEDIUM_ORCHID(Color): - _rgb = (186, 85, 211) + _rgb = RGB(186, 85, 211) class MEDIUM_PURPLE(Color): - _rgb = (147, 112, 219) + _rgb = RGB(147, 112, 219) class REBECCA_PURPLE(Color): - _rgb = (102, 51, 153) + _rgb = RGB(102, 51, 153) class BLUE_VIOLET(Color): - _rgb = (138, 43, 226) + _rgb = RGB(138, 43, 226) class DARK_VIOLET(Color): - _rgb = (148, 0, 211) + _rgb = RGB(148, 0, 211) class DARK_ORCHID(Color): - _rgb = (153, 50, 204) + _rgb = RGB(153, 50, 204) class DARK_MAGENTA(Color): - _rgb = (139, 0, 139) + _rgb = RGB(139, 0, 139) class PURPLE(Color): - _rgb = (128, 0, 128) + _rgb = RGB(128, 0, 128) class INDIGO(Color): - _rgb = (75, 0, 130) + _rgb = RGB(75, 0, 130) class SLATE_BLUE(Color): - _rgb = (106, 90, 205) + _rgb = RGB(106, 90, 205) class DARK_SLATE_BLUE(Color): - _rgb = (72, 61, 139) + _rgb = RGB(72, 61, 139) class MEDIUM_SLATE_BLUE(Color): - _rgb = (123, 104, 238) + _rgb = RGB(123, 104, 238) class GREEN_YELLOW(Color): - _rgb = (173, 255, 47) + _rgb = RGB(173, 255, 47) class CHARTREUSE(Color): - _rgb = (127, 255, 0) + _rgb = RGB(127, 255, 0) class LAWN_GREEN(Color): - _rgb = (124, 252, 0) + _rgb = RGB(124, 252, 0) class LIME(Color): - _rgb = (0, 255, 0) + _rgb = RGB(0, 255, 0) class LIME_GREEN(Color): - _rgb = (50, 205, 50) + _rgb = RGB(50, 205, 50) class PALE_GREEN(Color): - _rgb = (152, 251, 152) + _rgb = RGB(152, 251, 152) class LIGHT_GREEN(Color): - _rgb = (144, 238, 144) + _rgb = RGB(144, 238, 144) class MEDIUM_SPRING_GREEN(Color): - _rgb = (0, 250, 154) + _rgb = RGB(0, 250, 154) class SPRING_GREEN(Color): - _rgb = (0, 255, 127) + _rgb = RGB(0, 255, 127) class MEDIUM_SEA_GREEN(Color): - _rgb = (60, 179, 113) + _rgb = RGB(60, 179, 113) class SEA_GREEN(Color): - _rgb = (46, 139, 87) + _rgb = RGB(46, 139, 87) class FOREST_GREEN(Color): - _rgb = (34, 139, 34) + _rgb = RGB(34, 139, 34) class GREEN(Color): - _rgb = (0, 128, 0) + _rgb = RGB(0, 128, 0) class DARK_GREEN(Color): - _rgb = (0, 100, 0) + _rgb = RGB(0, 100, 0) class YELLOW_GREEN(Color): - _rgb = (154, 205, 50) + _rgb = RGB(154, 205, 50) class OLIVE_DRAB(Color): - _rgb = (107, 142, 35) + _rgb = RGB(107, 142, 35) class OLIVE(Color): - _rgb = (128, 128, 0) + _rgb = RGB(128, 128, 0) class DARK_OLIVE_GREEN(Color): - _rgb = (85, 107, 47) + _rgb = RGB(85, 107, 47) class MEDIUM_AQUAMARINE(Color): - _rgb = (102, 205, 170) + _rgb = RGB(102, 205, 170) class DARK_SEA_GREEN(Color): - _rgb = (143, 188, 139) + _rgb = RGB(143, 188, 139) class LIGHT_SEA_GREEN(Color): - _rgb = (32, 178, 170) + _rgb = RGB(32, 178, 170) class DARK_CYAN(Color): - _rgb = (0, 139, 139) + _rgb = RGB(0, 139, 139) class TEAL(Color): - _rgb = (0, 128, 128) + _rgb = RGB(0, 128, 128) class AQUA(Color): - _rgb = (0, 255, 255) + _rgb = RGB(0, 255, 255) class CYAN(Color): - _rgb = (0, 255, 255) + _rgb = RGB(0, 255, 255) class LIGHT_CYAN(Color): - _rgb = (224, 255, 255) + _rgb = RGB(224, 255, 255) class PALE_TURQUOISE(Color): - _rgb = (175, 238, 238) + _rgb = RGB(175, 238, 238) class AQUAMARINE(Color): - _rgb = (127, 255, 212) + _rgb = RGB(127, 255, 212) class TURQUOISE(Color): - _rgb = (64, 224, 208) + _rgb = RGB(64, 224, 208) class MEDIUM_TURQUOISE(Color): - _rgb = (72, 209, 204) + _rgb = RGB(72, 209, 204) class DARK_TURQUOISE(Color): - _rgb = (0, 206, 209) + _rgb = RGB(0, 206, 209) class CADET_BLUE(Color): - _rgb = (95, 158, 160) + _rgb = RGB(95, 158, 160) class STEEL_BLUE(Color): - _rgb = (70, 130, 180) + _rgb = RGB(70, 130, 180) class LIGHT_STEEL_BLUE(Color): - _rgb = (176, 196, 222) + _rgb = RGB(176, 196, 222) class POWDER_BLUE(Color): - _rgb = (176, 224, 230) + _rgb = RGB(176, 224, 230) class LIGHT_BLUE(Color): - _rgb = (173, 216, 230) + _rgb = RGB(173, 216, 230) class SKY_BLUE(Color): - _rgb = (135, 206, 235) + _rgb = RGB(135, 206, 235) class LIGHT_SKY_BLUE(Color): - _rgb = (135, 206, 250) + _rgb = RGB(135, 206, 250) class DEEP_SKY_BLUE(Color): - _rgb = (0, 191, 255) + _rgb = RGB(0, 191, 255) class DODGER_BLUE(Color): - _rgb = (30, 144, 255) + _rgb = RGB(30, 144, 255) class CORNFLOWER_BLUE(Color): - _rgb = (100, 149, 237) + _rgb = RGB(100, 149, 237) class ROYAL_BLUE(Color): - _rgb = (65, 105, 225) + _rgb = RGB(65, 105, 225) class BLUE(Color): - _rgb = (0, 0, 255) + _rgb = RGB(0, 0, 255) class MEDIUM_BLUE(Color): - _rgb = (0, 0, 205) + _rgb = RGB(0, 0, 205) class DARK_BLUE(Color): - _rgb = (0, 0, 139) + _rgb = RGB(0, 0, 139) class NAVY(Color): - _rgb = (0, 0, 128) + _rgb = RGB(0, 0, 128) class MIDNIGHT_BLUE(Color): - _rgb = (25, 25, 112) + _rgb = RGB(25, 25, 112) class CORNSILK(Color): - _rgb = (255, 248, 220) + _rgb = RGB(255, 248, 220) class BLANCHED_ALMOND(Color): - _rgb = (255, 235, 205) + _rgb = RGB(255, 235, 205) class BISQUE(Color): - _rgb = (255, 228, 196) + _rgb = RGB(255, 228, 196) class NAVAJO_WHITE(Color): - _rgb = (255, 222, 173) + _rgb = RGB(255, 222, 173) class WHEAT(Color): - _rgb = (245, 222, 179) + _rgb = RGB(245, 222, 179) class BURLY_WOOD(Color): - _rgb = (222, 184, 135) + _rgb = RGB(222, 184, 135) class TAN(Color): - _rgb = (210, 180, 140) + _rgb = RGB(210, 180, 140) class ROSY_BROWN(Color): - _rgb = (188, 143, 143) + _rgb = RGB(188, 143, 143) class SANDY_BROWN(Color): - _rgb = (244, 164, 96) + _rgb = RGB(244, 164, 96) class GOLDENROD(Color): - _rgb = (218, 165, 32) + _rgb = RGB(218, 165, 32) class DARK_GOLDENROD(Color): - _rgb = (184, 134, 11) + _rgb = RGB(184, 134, 11) class PERU(Color): - _rgb = (205, 133, 63) + _rgb = RGB(205, 133, 63) class CHOCOLATE(Color): - _rgb = (210, 105, 30) + _rgb = RGB(210, 105, 30) class SADDLE_BROWN(Color): - _rgb = (139, 69, 19) + _rgb = RGB(139, 69, 19) class SIENNA(Color): - _rgb = (160, 82, 45) + _rgb = RGB(160, 82, 45) class BROWN(Color): - _rgb = (165, 42, 42) + _rgb = RGB(165, 42, 42) class MAROON(Color): - _rgb = (128, 0, 0) + _rgb = RGB(128, 0, 0) class WHITE(Color): - _rgb = (255, 255, 255) + _rgb = RGB(255, 255, 255) class SNOW(Color): - _rgb = (255, 250, 250) + _rgb = RGB(255, 250, 250) class HONEY_DEW(Color): - _rgb = (240, 255, 240) + _rgb = RGB(240, 255, 240) class MINT_CREAM(Color): - _rgb = (245, 255, 250) + _rgb = RGB(245, 255, 250) class AZURE(Color): - _rgb = (240, 255, 255) + _rgb = RGB(240, 255, 255) class ALICE_BLUE(Color): - _rgb = (240, 248, 255) + _rgb = RGB(240, 248, 255) class GHOST_WHITE(Color): - _rgb = (248, 248, 255) + _rgb = RGB(248, 248, 255) class WHITE_SMOKE(Color): - _rgb = (245, 245, 245) + _rgb = RGB(245, 245, 245) class SEA_SHELL(Color): - _rgb = (255, 245, 238) + _rgb = RGB(255, 245, 238) class BEIGE(Color): - _rgb = (245, 245, 220) + _rgb = RGB(245, 245, 220) class OLD_LACE(Color): - _rgb = (253, 245, 230) + _rgb = RGB(253, 245, 230) class FLORAL_WHITE(Color): - _rgb = (255, 250, 240) + _rgb = RGB(255, 250, 240) class IVORY(Color): - _rgb = (255, 255, 240) + _rgb = RGB(255, 255, 240) class ANTIQUE_WHITE(Color): - _rgb = (250, 235, 215) + _rgb = RGB(250, 235, 215) class LINEN(Color): - _rgb = (250, 240, 230) + _rgb = RGB(250, 240, 230) class LAVENDER_BLUSH(Color): - _rgb = (255, 240, 245) + _rgb = RGB(255, 240, 245) class MISTY_ROSE(Color): - _rgb = (255, 228, 225) + _rgb = RGB(255, 228, 225) class GAINSBORO(Color): - _rgb = (220, 220, 220) + _rgb = RGB(220, 220, 220) class LIGHT_GRAY(Color): - _rgb = (211, 211, 211) + _rgb = RGB(211, 211, 211) class SILVER(Color): - _rgb = (192, 192, 192) + _rgb = RGB(192, 192, 192) class DARK_GRAY(Color): - _rgb = (169, 169, 169) + _rgb = RGB(169, 169, 169) class GRAY(Color): - _rgb = (128, 128, 128) + _rgb = RGB(128, 128, 128) class DIM_GRAY(Color): - _rgb = (105, 105, 105) + _rgb = RGB(105, 105, 105) class LIGHT_SLATE_GRAY(Color): - _rgb = (119, 136, 153) + _rgb = RGB(119, 136, 153) class SLATE_GRAY(Color): - _rgb = (112, 128, 144) + _rgb = RGB(112, 128, 144) class DARK_SLATE_GRAY(Color): - _rgb = (47, 79, 79) + _rgb = RGB(47, 79, 79) class BLACK(Color): - _rgb = (0, 0, 0) + _rgb = RGB(0, 0, 0) From f7e0e5109dc49d99b532d88d773c4c30cfe60756 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Nov 2022 17:38:20 +0100 Subject: [PATCH 074/118] removed unneeded os functions for now --- progressbar/os_functions/__init__.py | 24 ----- progressbar/os_functions/nix.py | 15 --- progressbar/os_functions/windows.py | 144 --------------------------- 3 files changed, 183 deletions(-) delete mode 100644 progressbar/os_functions/__init__.py delete mode 100644 progressbar/os_functions/nix.py delete mode 100644 progressbar/os_functions/windows.py diff --git a/progressbar/os_functions/__init__.py b/progressbar/os_functions/__init__.py deleted file mode 100644 index 8b442283..00000000 --- a/progressbar/os_functions/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -import sys - -if sys.platform.startswith('win'): - from .windows import ( - getch as _getch, - set_console_mode as _set_console_mode, - reset_console_mode as _reset_console_mode - ) - -else: - from .nix import getch as _getch - - def _reset_console_mode(): - pass - - - def _set_console_mode(): - pass - - -getch = _getch -reset_console_mode = _reset_console_mode -set_console_mode = _set_console_mode - diff --git a/progressbar/os_functions/nix.py b/progressbar/os_functions/nix.py deleted file mode 100644 index e46fbdf0..00000000 --- a/progressbar/os_functions/nix.py +++ /dev/null @@ -1,15 +0,0 @@ -import sys -import tty -import termios - - -def getch(): - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - ch = sys.stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - - return ch diff --git a/progressbar/os_functions/windows.py b/progressbar/os_functions/windows.py deleted file mode 100644 index edac0696..00000000 --- a/progressbar/os_functions/windows.py +++ /dev/null @@ -1,144 +0,0 @@ -import ctypes -from ctypes.wintypes import ( - DWORD as _DWORD, - HANDLE as _HANDLE, - BOOL as _BOOL, - WORD as _WORD, - UINT as _UINT, - WCHAR as _WCHAR, - CHAR as _CHAR, - SHORT as _SHORT -) - -_kernel32 = ctypes.windll.Kernel32 - -_ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 -_ENABLE_PROCESSED_OUTPUT = 0x0001 -_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - -_STD_INPUT_HANDLE = _DWORD(-10) -_STD_OUTPUT_HANDLE = _DWORD(-11) - - -_GetConsoleMode = _kernel32.GetConsoleMode -_GetConsoleMode.restype = _BOOL - -_SetConsoleMode = _kernel32.SetConsoleMode -_SetConsoleMode.restype = _BOOL - -_GetStdHandle = _kernel32.GetStdHandle -_GetStdHandle.restype = _HANDLE - -_ReadConsoleInput = _kernel32.ReadConsoleInputA -_ReadConsoleInput.restype = _BOOL - - -_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) -_input_mode = _DWORD() -_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) - -_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) -_output_mode = _DWORD() -_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) - - -class _COORD(ctypes.Structure): - _fields_ = [ - ('X', _SHORT), - ('Y', _SHORT) - ] - - -class _FOCUS_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('bSetFocus', _BOOL) - ] - - -class _KEY_EVENT_RECORD(ctypes.Structure): - class _uchar(ctypes.Union): - _fields_ = [ - ('UnicodeChar', _WCHAR), - ('AsciiChar', _CHAR) - ] - - _fields_ = [ - ('bKeyDown', _BOOL), - ('wRepeatCount', _WORD), - ('wVirtualKeyCode', _WORD), - ('wVirtualScanCode', _WORD), - ('uChar', _uchar), - ('dwControlKeyState', _DWORD) - ] - - -class _MENU_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('dwCommandId', _UINT) - ] - - -class _MOUSE_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('dwMousePosition', _COORD), - ('dwButtonState', _DWORD), - ('dwControlKeyState', _DWORD), - ('dwEventFlags', _DWORD) - ] - - -class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): - _fields_ = [ - ('dwSize', _COORD) - ] - - -class _INPUT_RECORD(ctypes.Structure): - class _Event(ctypes.Union): - _fields_ = [ - ('KeyEvent', _KEY_EVENT_RECORD), - ('MouseEvent', _MOUSE_EVENT_RECORD), - ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), - ('MenuEvent', _MENU_EVENT_RECORD), - ('FocusEvent', _FOCUS_EVENT_RECORD) - ] - - _fields_ = [ - ('EventType', _WORD), - ('Event', _Event) - ] - - -def reset_console_mode(): - _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) - _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) - - -def set_console_mode(): - mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT - _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) - - mode = ( - _output_mode.value | - _ENABLE_PROCESSED_OUTPUT | - _ENABLE_VIRTUAL_TERMINAL_PROCESSING - ) - _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) - - -def getch(): - lpBuffer = (_INPUT_RECORD * 2)() - nLength = _DWORD(2) - lpNumberOfEventsRead = _DWORD() - - _ReadConsoleInput( - _HANDLE(_hConsoleInput), - lpBuffer, nLength, - ctypes.byref(lpNumberOfEventsRead) - ) - - char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') - if char == '\x00': - return None - - return char From af1c00b73e244c8960779bababb7d61782a06e0d Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 6 Dec 2022 02:52:54 +0100 Subject: [PATCH 075/118] Added ANSI terminal support for colors, bold, italic, underline, and many more --- progressbar/ansi.py | 1056 ------------------------------ progressbar/terminal/__init__.py | 0 progressbar/terminal/base.py | 353 ++++++++++ progressbar/terminal/colors.py | 947 +++++++++++++++++++++++++++ 4 files changed, 1300 insertions(+), 1056 deletions(-) delete mode 100644 progressbar/ansi.py create mode 100644 progressbar/terminal/__init__.py create mode 100644 progressbar/terminal/base.py create mode 100644 progressbar/terminal/colors.py diff --git a/progressbar/ansi.py b/progressbar/ansi.py deleted file mode 100644 index 2ace68d6..00000000 --- a/progressbar/ansi.py +++ /dev/null @@ -1,1056 +0,0 @@ -import collections -import threading - -from . import utils -from .os_functions import getch - -ESC = '\x1B' -CSI = ESC + '[' - -CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) - - -# Report Cursor Position (CPR), response = [row;column] as row;columnR -class _CPR(str): - _response_lock = threading.Lock() - - def __call__(self, stream): - res = '' - - with self._response_lock: - stream.write(str(self)) - stream.flush() - - while not res.endswith('R'): - char = getch() - - if char is not None: - res += char - - res = res[2:-1].split(';') - - res = tuple(int(item) if item.isdigit() else item for item in res) - - if len(res) == 1: - return res[0] - - return res - - def row(self, stream): - row, _ = self(stream) - return row - - def column(self, stream): - _, column = self(stream) - return column - - -DSR = CSI + '{n}n' # Device Status Report (DSR) -CPR = _CPR(DSR.format(n=6)) - -IL = CSI + '{n}L' # Insert n Line(s) (default = 1) - -DECRST = CSI + '?{n}l' # DEC Private Mode Reset -DECRTCEM = DECRST.format(n=25) # Hide Cursor - -DECSET = CSI + '?{n}h' # DEC Private Mode Set -DECTCEM = DECSET.format(n=25) # Show Cursor - -# possible values: -# 0 = Normal (default) -# 1 = Bold -# 2 = Faint -# 3 = Italic -# 4 = Underlined -# 5 = Slow blink (appears as Bold) -# 6 = Rapid Blink -# 7 = Inverse -# 8 = Invisible, i.e., hidden (VT300) -# 9 = Strike through -# 10 = Primary (default) font -# 20 = Gothic Font -# 21 = Double underline -# 22 = Normal intensity (neither bold nor faint) -# 23 = Not italic -# 24 = Not underlined -# 25 = Steady (not blinking) -# 26 = Proportional spacing -# 27 = Not inverse -# 28 = Visible, i.e., not hidden (VT300) -# 29 = No strike through -# 30 = Set foreground color to Black -# 31 = Set foreground color to Red -# 32 = Set foreground color to Green -# 33 = Set foreground color to Yellow -# 34 = Set foreground color to Blue -# 35 = Set foreground color to Magenta -# 36 = Set foreground color to Cyan -# 37 = Set foreground color to White -# 39 = Set foreground color to default (original) -# 40 = Set background color to Black -# 41 = Set background color to Red -# 42 = Set background color to Green -# 43 = Set background color to Yellow -# 44 = Set background color to Blue -# 45 = Set background color to Magenta -# 46 = Set background color to Cyan -# 47 = Set background color to White -# 49 = Set background color to default (original). -# 50 = Disable proportional spacing -# 51 = Framed -# 52 = Encircled -# 53 = Overlined -# 54 = Neither framed nor encircled -# 55 = Not overlined -# 58 = Set underine color (2;r;g;b) -# 59 = Default underline color -# If 16-color support is compiled, the following apply. -# Assume that xterm’s resources are set so that the ISO color codes are the -# first 8 of a set of 16. Then the aixterm colors are the bright versions of -# the ISO colors: -# 90 = Set foreground color to Black -# 91 = Set foreground color to Red -# 92 = Set foreground color to Green -# 93 = Set foreground color to Yellow -# 94 = Set foreground color to Blue -# 95 = Set foreground color to Magenta -# 96 = Set foreground color to Cyan -# 97 = Set foreground color to White -# 100 = Set background color to Black -# 101 = Set background color to Red -# 102 = Set background color to Green -# 103 = Set background color to Yellow -# 104 = Set background color to Blue -# 105 = Set background color to Magenta -# 106 = Set background color to Cyan -# 107 = Set background color to White -# -# If xterm is compiled with the 16-color support disabled, it supports the -# following, from rxvt: -# 100 = Set foreground and background color to default - -# If 88- or 256-color support is compiled, the following apply. -# 38;5;x = Set foreground color to x -# 48;5;x = Set background color to x -SGR = CSI + '{n}m' # Character Attributes - - -class ENCIRCLED(str): - ''' - Your guess is as good as mine. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=52), args[1], SGR.format(n=54)]) - return super(ENCIRCLED, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes encircled? - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=52), SGR.format(n=54)) - - -class FRAMED(str): - ''' - Your guess is as good as mine. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=51), args[1], SGR.format(n=54)]) - return super(FRAMED, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes Frame? - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=51), SGR.format(n=54)) - - -class GOTHIC(str): - ''' - Changes text font to Gothic - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=20), args[1], SGR.format(n=10)]) - return super(GOTHIC, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Makes text font normal - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=20), SGR.format(n=10)) - - -class ITALIC(str): - ''' - Makes the text italic - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=3), args[1], SGR.format(n=23)]) - return super(ITALIC, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the italic. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=3), SGR.format(n=23)) - - -class STRIKE_THROUGH(str): - ''' - Strikes through the text. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=9), args[1], SGR.format(n=29)]) - return super(STRIKE_THROUGH, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the strike through - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=9), SGR.format(n=29)) - - -class FAST_BLINK(str): - ''' - Makes the text blink fast - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=6), args[1], SGR.format(n=25)]) - return super(FAST_BLINK, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Makes the text steady - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=6), SGR.format(n=25)) - - -class SLOW_BLINK(str): - ''' - Makes the text blonk slowely. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=5), args[1], SGR.format(n=25)]) - return super(SLOW_BLINK, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Makes the text steady - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=5), SGR.format(n=25)) - - -class OVERLINE(str): - ''' - Overlines the text provided. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=53), args[1], SGR.format(n=55)]) - return super(OVERLINE, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the overline from the text - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=53), SGR.format(n=55)) - - -class UNDERLINE(str): - ''' - Underlines the text provided. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=4), args[1], SGR.format(n=24)]) - return super(UNDERLINE, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the underline from the text - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=4), SGR.format(n=24)) - - -class DOUBLE_UNDERLINE(str): - ''' - Double underlines the text provided. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=21), args[1], SGR.format(n=24)]) - return super(DOUBLE_UNDERLINE, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the double underline from the text - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=21), SGR.format(n=24)) - - -class BOLD(str): - ''' - Makes the supplied text BOLD - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=1), args[1], SGR.format(n=22)]) - return super(BOLD, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the BOLD from the text. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=1), SGR.format(n=22)) - - -class FAINT(str): - ''' - Makes the supplied text FAINT - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=2), args[1], SGR.format(n=22)]) - return super(FAINT, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the FAINT from the text. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=2), SGR.format(n=22)) - - -class INVERT_COLORS(str): - ''' - Switches the background and forground colors. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=7), args[1], SGR.format(n=27)]) - return super(INVERT_COLORS, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the color inversion and returns the original text provided. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=7), SGR.format(n=27)) - - -RGB = collections.namedtuple('RGB', ['r', 'g', 'b']) - - -class Color(str): - ''' - Color base class - - This class is a wrapper for the `str` class that adds a couple of - class methods. It makes it easier to add and remove an ansi color escape - sequence from a string of text. - - There are 141 HTML colors that have already been provided however you can - make a custom color if you would like. - - To make a custom color simply subclass this class and override the `_rgb` - class attribute supplying your own RGB value as a tuple (R, G, B) - ''' - _rgb: RGB = RGB(0, 0, 0) - - @classmethod - def fg(cls, text): - ''' - Adds the ansi escape codes to set the foreground color to this color. - ''' - return cls( - ''.join( - [ - CSI, - '38;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=39) - ] - ) - ) - - @classmethod - def bg(cls, text): - ''' - Adds the ansi escape codes to set the background color to this color. - ''' - return cls( - ''.join( - [ - CSI, - '48;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=49) - ] - ) - ) - - @classmethod - def ul(cls, text): - ''' - Adds the ansi escape codes to set the underline color to this color. - ''' - return cls( - ''.join( - [ - CSI, - '58;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=59) - ] - ) - ) - - @property - def raw(self): - ''' - Removes this color from the text provided - ''' - text = self.__str__() - - if text.startswith(CSI + '48;2'): - text = utils.remove_ansi( - text, - CSI + '48;2;{0};{1};{2}m'.format(*self._rgb), - SGR.format(n=49) - ) - elif text.startswith(CSI + '38;2'): - text = utils.remove_ansi( - text, - CSI + '38;2;{0};{1};{2}m'.format(*self._rgb), - SGR.format(n=39) - ) - - else: - text = utils.remove_ansi( - text, - CSI + '58;2;{0};{1};{2}m'.format(*self._rgb), - SGR.format(n=59) - ) - - return text - - -class INDIAN_RED(Color): - _rgb = RGB(205, 92, 92) - - -class LIGHT_CORAL(Color): - _rgb = RGB(240, 128, 128) - - -class SALMON(Color): - _rgb = RGB(250, 128, 114) - - -class DARK_SALMON(Color): - _rgb = RGB(233, 150, 122) - - -class LIGHT_SALMON(Color): - _rgb = RGB(255, 160, 122) - - -class CRIMSON(Color): - _rgb = RGB(220, 20, 60) - - -class RED(Color): - _rgb = RGB(255, 0, 0) - - -class FIRE_BRICK(Color): - _rgb = RGB(178, 34, 34) - - -class DARK_RED(Color): - _rgb = RGB(139, 0, 0) - - -class PINK(Color): - _rgb = RGB(255, 192, 203) - - -class LIGHT_PINK(Color): - _rgb = RGB(255, 182, 193) - - -class HOT_PINK(Color): - _rgb = RGB(255, 105, 180) - - -class DEEP_PINK(Color): - _rgb = RGB(255, 20, 147) - - -class MEDIUM_VIOLET_RED(Color): - _rgb = RGB(199, 21, 133) - - -class PALE_VIOLET_RED(Color): - _rgb = RGB(219, 112, 147) - - -class CORAL(Color): - _rgb = RGB(255, 127, 80) - - -class TOMATO(Color): - _rgb = RGB(255, 99, 71) - - -class ORANGE_RED(Color): - _rgb = RGB(255, 69, 0) - - -class DARK_ORANGE(Color): - _rgb = RGB(255, 140, 0) - - -class ORANGE(Color): - _rgb = RGB(255, 165, 0) - - -class GOLD(Color): - _rgb = RGB(255, 215, 0) - - -class YELLOW(Color): - _rgb = RGB(255, 255, 0) - - -class LIGHT_YELLOW(Color): - _rgb = RGB(255, 255, 224) - - -class LEMON_CHIFFON(Color): - _rgb = RGB(255, 250, 205) - - -class LIGHT_GOLDENROD_YELLOW(Color): - _rgb = RGB(250, 250, 210) - - -class PAPAYA_WHIP(Color): - _rgb = RGB(255, 239, 213) - - -class MOCCASIN(Color): - _rgb = RGB(255, 228, 181) - - -class PEACH_PUFF(Color): - _rgb = RGB(255, 218, 185) - - -class PALE_GOLDENROD(Color): - _rgb = RGB(238, 232, 170) - - -class KHAKI(Color): - _rgb = RGB(240, 230, 140) - - -class DARK_KHAKI(Color): - _rgb = RGB(189, 183, 107) - - -class LAVENDER(Color): - _rgb = RGB(230, 230, 250) - - -class THISTLE(Color): - _rgb = RGB(216, 191, 216) - - -class PLUM(Color): - _rgb = RGB(221, 160, 221) - - -class VIOLET(Color): - _rgb = RGB(238, 130, 238) - - -class ORCHID(Color): - _rgb = RGB(218, 112, 214) - - -class FUCHSIA(Color): - _rgb = RGB(255, 0, 255) - - -class MAGENTA(Color): - _rgb = RGB(255, 0, 255) - - -class MEDIUM_ORCHID(Color): - _rgb = RGB(186, 85, 211) - - -class MEDIUM_PURPLE(Color): - _rgb = RGB(147, 112, 219) - - -class REBECCA_PURPLE(Color): - _rgb = RGB(102, 51, 153) - - -class BLUE_VIOLET(Color): - _rgb = RGB(138, 43, 226) - - -class DARK_VIOLET(Color): - _rgb = RGB(148, 0, 211) - - -class DARK_ORCHID(Color): - _rgb = RGB(153, 50, 204) - - -class DARK_MAGENTA(Color): - _rgb = RGB(139, 0, 139) - - -class PURPLE(Color): - _rgb = RGB(128, 0, 128) - - -class INDIGO(Color): - _rgb = RGB(75, 0, 130) - - -class SLATE_BLUE(Color): - _rgb = RGB(106, 90, 205) - - -class DARK_SLATE_BLUE(Color): - _rgb = RGB(72, 61, 139) - - -class MEDIUM_SLATE_BLUE(Color): - _rgb = RGB(123, 104, 238) - - -class GREEN_YELLOW(Color): - _rgb = RGB(173, 255, 47) - - -class CHARTREUSE(Color): - _rgb = RGB(127, 255, 0) - - -class LAWN_GREEN(Color): - _rgb = RGB(124, 252, 0) - - -class LIME(Color): - _rgb = RGB(0, 255, 0) - - -class LIME_GREEN(Color): - _rgb = RGB(50, 205, 50) - - -class PALE_GREEN(Color): - _rgb = RGB(152, 251, 152) - - -class LIGHT_GREEN(Color): - _rgb = RGB(144, 238, 144) - - -class MEDIUM_SPRING_GREEN(Color): - _rgb = RGB(0, 250, 154) - - -class SPRING_GREEN(Color): - _rgb = RGB(0, 255, 127) - - -class MEDIUM_SEA_GREEN(Color): - _rgb = RGB(60, 179, 113) - - -class SEA_GREEN(Color): - _rgb = RGB(46, 139, 87) - - -class FOREST_GREEN(Color): - _rgb = RGB(34, 139, 34) - - -class GREEN(Color): - _rgb = RGB(0, 128, 0) - - -class DARK_GREEN(Color): - _rgb = RGB(0, 100, 0) - - -class YELLOW_GREEN(Color): - _rgb = RGB(154, 205, 50) - - -class OLIVE_DRAB(Color): - _rgb = RGB(107, 142, 35) - - -class OLIVE(Color): - _rgb = RGB(128, 128, 0) - - -class DARK_OLIVE_GREEN(Color): - _rgb = RGB(85, 107, 47) - - -class MEDIUM_AQUAMARINE(Color): - _rgb = RGB(102, 205, 170) - - -class DARK_SEA_GREEN(Color): - _rgb = RGB(143, 188, 139) - - -class LIGHT_SEA_GREEN(Color): - _rgb = RGB(32, 178, 170) - - -class DARK_CYAN(Color): - _rgb = RGB(0, 139, 139) - - -class TEAL(Color): - _rgb = RGB(0, 128, 128) - - -class AQUA(Color): - _rgb = RGB(0, 255, 255) - - -class CYAN(Color): - _rgb = RGB(0, 255, 255) - - -class LIGHT_CYAN(Color): - _rgb = RGB(224, 255, 255) - - -class PALE_TURQUOISE(Color): - _rgb = RGB(175, 238, 238) - - -class AQUAMARINE(Color): - _rgb = RGB(127, 255, 212) - - -class TURQUOISE(Color): - _rgb = RGB(64, 224, 208) - - -class MEDIUM_TURQUOISE(Color): - _rgb = RGB(72, 209, 204) - - -class DARK_TURQUOISE(Color): - _rgb = RGB(0, 206, 209) - - -class CADET_BLUE(Color): - _rgb = RGB(95, 158, 160) - - -class STEEL_BLUE(Color): - _rgb = RGB(70, 130, 180) - - -class LIGHT_STEEL_BLUE(Color): - _rgb = RGB(176, 196, 222) - - -class POWDER_BLUE(Color): - _rgb = RGB(176, 224, 230) - - -class LIGHT_BLUE(Color): - _rgb = RGB(173, 216, 230) - - -class SKY_BLUE(Color): - _rgb = RGB(135, 206, 235) - - -class LIGHT_SKY_BLUE(Color): - _rgb = RGB(135, 206, 250) - - -class DEEP_SKY_BLUE(Color): - _rgb = RGB(0, 191, 255) - - -class DODGER_BLUE(Color): - _rgb = RGB(30, 144, 255) - - -class CORNFLOWER_BLUE(Color): - _rgb = RGB(100, 149, 237) - - -class ROYAL_BLUE(Color): - _rgb = RGB(65, 105, 225) - - -class BLUE(Color): - _rgb = RGB(0, 0, 255) - - -class MEDIUM_BLUE(Color): - _rgb = RGB(0, 0, 205) - - -class DARK_BLUE(Color): - _rgb = RGB(0, 0, 139) - - -class NAVY(Color): - _rgb = RGB(0, 0, 128) - - -class MIDNIGHT_BLUE(Color): - _rgb = RGB(25, 25, 112) - - -class CORNSILK(Color): - _rgb = RGB(255, 248, 220) - - -class BLANCHED_ALMOND(Color): - _rgb = RGB(255, 235, 205) - - -class BISQUE(Color): - _rgb = RGB(255, 228, 196) - - -class NAVAJO_WHITE(Color): - _rgb = RGB(255, 222, 173) - - -class WHEAT(Color): - _rgb = RGB(245, 222, 179) - - -class BURLY_WOOD(Color): - _rgb = RGB(222, 184, 135) - - -class TAN(Color): - _rgb = RGB(210, 180, 140) - - -class ROSY_BROWN(Color): - _rgb = RGB(188, 143, 143) - - -class SANDY_BROWN(Color): - _rgb = RGB(244, 164, 96) - - -class GOLDENROD(Color): - _rgb = RGB(218, 165, 32) - - -class DARK_GOLDENROD(Color): - _rgb = RGB(184, 134, 11) - - -class PERU(Color): - _rgb = RGB(205, 133, 63) - - -class CHOCOLATE(Color): - _rgb = RGB(210, 105, 30) - - -class SADDLE_BROWN(Color): - _rgb = RGB(139, 69, 19) - - -class SIENNA(Color): - _rgb = RGB(160, 82, 45) - - -class BROWN(Color): - _rgb = RGB(165, 42, 42) - - -class MAROON(Color): - _rgb = RGB(128, 0, 0) - - -class WHITE(Color): - _rgb = RGB(255, 255, 255) - - -class SNOW(Color): - _rgb = RGB(255, 250, 250) - - -class HONEY_DEW(Color): - _rgb = RGB(240, 255, 240) - - -class MINT_CREAM(Color): - _rgb = RGB(245, 255, 250) - - -class AZURE(Color): - _rgb = RGB(240, 255, 255) - - -class ALICE_BLUE(Color): - _rgb = RGB(240, 248, 255) - - -class GHOST_WHITE(Color): - _rgb = RGB(248, 248, 255) - - -class WHITE_SMOKE(Color): - _rgb = RGB(245, 245, 245) - - -class SEA_SHELL(Color): - _rgb = RGB(255, 245, 238) - - -class BEIGE(Color): - _rgb = RGB(245, 245, 220) - - -class OLD_LACE(Color): - _rgb = RGB(253, 245, 230) - - -class FLORAL_WHITE(Color): - _rgb = RGB(255, 250, 240) - - -class IVORY(Color): - _rgb = RGB(255, 255, 240) - - -class ANTIQUE_WHITE(Color): - _rgb = RGB(250, 235, 215) - - -class LINEN(Color): - _rgb = RGB(250, 240, 230) - - -class LAVENDER_BLUSH(Color): - _rgb = RGB(255, 240, 245) - - -class MISTY_ROSE(Color): - _rgb = RGB(255, 228, 225) - - -class GAINSBORO(Color): - _rgb = RGB(220, 220, 220) - - -class LIGHT_GRAY(Color): - _rgb = RGB(211, 211, 211) - - -class SILVER(Color): - _rgb = RGB(192, 192, 192) - - -class DARK_GRAY(Color): - _rgb = RGB(169, 169, 169) - - -class GRAY(Color): - _rgb = RGB(128, 128, 128) - - -class DIM_GRAY(Color): - _rgb = RGB(105, 105, 105) - - -class LIGHT_SLATE_GRAY(Color): - _rgb = RGB(119, 136, 153) - - -class SLATE_GRAY(Color): - _rgb = RGB(112, 128, 144) - - -class DARK_SLATE_GRAY(Color): - _rgb = RGB(47, 79, 79) - - -class BLACK(Color): - _rgb = RGB(0, 0, 0) diff --git a/progressbar/terminal/__init__.py b/progressbar/terminal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py new file mode 100644 index 00000000..d63a8da9 --- /dev/null +++ b/progressbar/terminal/base.py @@ -0,0 +1,353 @@ +from __future__ import annotations + +import collections +import colorsys +import enum +import os +import threading +from collections import defaultdict + +from python_utils import types + +# from .os_functions import getch +# from .. import utils + +ESC = '\x1B' +CSI = ESC + '[' + +CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) + + +class ColorSupport(enum.Enum): + '''Color support for the terminal.''' + NONE = 0 + XTERM = 1 + XTERM_256 = 2 + XTERM_TRUECOLOR = 3 + + @classmethod + def from_env(cls): + '''Get the color support from the environment.''' + if os.getenv('COLORTERM') == 'truecolor': + return cls.XTERM_TRUECOLOR + elif os.getenv('TERM') == 'xterm-256color': + return cls.XTERM_256 + elif os.getenv('TERM') == 'xterm': + return cls.XTERM + else: + return cls.NONE + + +color_support = ColorSupport.from_env() + + +# Report Cursor Position (CPR), response = [row;column] as row;columnR +class _CPR(str): + _response_lock = threading.Lock() + + def __call__(self, stream): + res = '' + + with self._response_lock: + stream.write(str(self)) + stream.flush() + + while not res.endswith('R'): + char = getch() + + if char is not None: + res += char + + res = res[2:-1].split(';') + + res = tuple(int(item) if item.isdigit() else item for item in res) + + if len(res) == 1: + return res[0] + + return res + + def row(self, stream): + row, _ = self(stream) + return row + + def column(self, stream): + _, column = self(stream) + return column + + +DSR = CSI + '{n}n' # Device Status Report (DSR) +CPR = _CPR(DSR.format(n=6)) + +IL = CSI + '{n}L' # Insert n Line(s) (default = 1) + +DECRST = CSI + '?{n}l' # DEC Private Mode Reset +DECRTCEM = DECRST.format(n=25) # Hide Cursor + +DECSET = CSI + '?{n}h' # DEC Private Mode Set +DECTCEM = DECSET.format(n=25) # Show Cursor + + +# possible values: +# 0 = Normal (default) +# 1 = Bold +# 2 = Faint +# 3 = Italic +# 4 = Underlined +# 5 = Slow blink (appears as Bold) +# 6 = Rapid Blink +# 7 = Inverse +# 8 = Invisible, i.e., hidden (VT300) +# 9 = Strike through +# 10 = Primary (default) font +# 20 = Gothic Font +# 21 = Double underline +# 22 = Normal intensity (neither bold nor faint) +# 23 = Not italic +# 24 = Not underlined +# 25 = Steady (not blinking) +# 26 = Proportional spacing +# 27 = Not inverse +# 28 = Visible, i.e., not hidden (VT300) +# 29 = No strike through +# 30 = Set foreground color to Black +# 31 = Set foreground color to Red +# 32 = Set foreground color to Green +# 33 = Set foreground color to Yellow +# 34 = Set foreground color to Blue +# 35 = Set foreground color to Magenta +# 36 = Set foreground color to Cyan +# 37 = Set foreground color to White +# 39 = Set foreground color to default (original) +# 40 = Set background color to Black +# 41 = Set background color to Red +# 42 = Set background color to Green +# 43 = Set background color to Yellow +# 44 = Set background color to Blue +# 45 = Set background color to Magenta +# 46 = Set background color to Cyan +# 47 = Set background color to White +# 49 = Set background color to default (original). +# 50 = Disable proportional spacing +# 51 = Framed +# 52 = Encircled +# 53 = Overlined +# 54 = Neither framed nor encircled +# 55 = Not overlined +# 58 = Set underine color (2;r;g;b) +# 59 = Default underline color +# If 16-color support is compiled, the following apply. +# Assume that xterm’s resources are set so that the ISO color codes are the +# first 8 of a set of 16. Then the aixterm colors are the bright versions of +# the ISO colors: +# 90 = Set foreground color to Black +# 91 = Set foreground color to Red +# 92 = Set foreground color to Green +# 93 = Set foreground color to Yellow +# 94 = Set foreground color to Blue +# 95 = Set foreground color to Magenta +# 96 = Set foreground color to Cyan +# 97 = Set foreground color to White +# 100 = Set background color to Black +# 101 = Set background color to Red +# 102 = Set background color to Green +# 103 = Set background color to Yellow +# 104 = Set background color to Blue +# 105 = Set background color to Magenta +# 106 = Set background color to Cyan +# 107 = Set background color to White +# +# If xterm is compiled with the 16-color support disabled, it supports the +# following, from rxvt: +# 100 = Set foreground and background color to default + +# If 88- or 256-color support is compiled, the following apply. +# 38;5;x = Set foreground color to x +# 48;5;x = Set background color to x + + +class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): + __slots__ = () + + def __str__(self): + return f'rgb({self.red}, {self.green}, {self.blue})' + + @property + def hex(self): + return f'#{self.red:02x}{self.green:02x}{self.blue:02x}' + + @property + def to_ansi_16(self): + # Using int instead of round because it maps slightly better + red = int(self.red / 255) + green = int(self.green / 255) + blue = int(self.blue / 255) + return (blue << 2) | (green << 1) | red + + @property + def to_ansi_256(self): + red = round(self.red / 255 * 5) + green = round(self.green / 255 * 5) + blue = round(self.blue / 255 * 5) + return 16 + 36 * red + 6 * green + blue + + +class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): + __slots__ = () + + @classmethod + def from_rgb(cls, rgb: RGB) -> HLS: + return cls( + *colorsys.rgb_to_hls(rgb.red / 255, rgb.green / 255, rgb.blue / 255) + ) + + +class Color( + collections.namedtuple( + 'Color', [ + 'rgb', + 'hls', + 'name', + 'xterm', + ] + ) +): + ''' + Color base class + + This class contains the colors in RGB (Red, Green, Blue), HLS (Hue, + Lightness, Saturation) and Xterm (8-bit) formats. It also contains the + color name. + + To make a custom color the only required arguments are the RGB values. + The other values will be automatically interpolated from that if needed, + but you can be more explicity if you wish. + ''' + __slots__ = () + + @property + def fg(self): + return SGRColor(self, 38, 39) + + @property + def bg(self): + return SGRColor(self, 48, 49) + + @property + def underline(self): + return SGRColor(self, 58, 59) + + @property + def ansi(self) -> types.Optional[str]: + if color_support is ColorSupport.XTERM_TRUECOLOR: + return f'2;{self.rgb.red};{self.rgb.green};{self.rgb.blue}' + + if self.xterm: + color = self.xterm + elif color_support is ColorSupport.XTERM_256: + color = self.rgb.to_ansi_256 + elif color_support is ColorSupport.XTERM: + color = self.rgb.to_ansi_16 + else: + return None + + return f'5;{color}' + + def __str__(self): + return self.name + + def __repr__(self): + return f'{self.__class__.__name__}({self.name!r})' + + def __hash__(self): + return hash(self.rgb) + + +class Colors: + by_name: defaultdict[str, types.List[Color]] = collections.defaultdict(list) + by_lowername: defaultdict[str, types.List[Color]] = collections.defaultdict( + list + ) + by_hex: defaultdict[str, types.List[Color]] = collections.defaultdict(list) + by_rgb: defaultdict[RGB, types.List[Color]] = collections.defaultdict(list) + by_hls: defaultdict[HLS, types.List[Color]] = collections.defaultdict(list) + by_xterm: dict[int, Color] = dict() + + @classmethod + def register( + cls, + rgb: RGB, + hls: types.Optional[HLS] = None, + name: types.Optional[str] = None, + xterm: types.Optional[int] = None, + ) -> Color: + color = Color(rgb, hls, name, xterm) + + if name: + cls.by_name[name].append(color) + cls.by_lowername[name.lower()].append(color) + + if hls is None: + hls = HLS.from_rgb(rgb) + + cls.by_hex[rgb.hex].append(color) + cls.by_rgb[rgb].append(color) + cls.by_hls[hls].append(color) + + if xterm is not None: + cls.by_xterm[xterm] = color + + return color + + +class SGR: + _start_code: int + _end_code: int + _template = CSI + '{n}m' + __slots__ = '_start_code', '_end_code' + + def __init__(self, start_code: int, end_code: int): + self._start_code = start_code + self._end_code = end_code + + @property + def _start_template(self): + return self._template.format(n=self._start_code) + + @property + def _end_template(self): + return self._template.format(n=self._end_code) + + def __call__(self, text): + return self._start_template + text + self._end_template + + +class SGRColor(SGR): + __slots__ = '_color', '_start_code', '_end_code' + _color_template = CSI + '{n};{color}m' + + def __init__(self, color: Color, start_code: int, end_code: int): + self._color = color + super().__init__(start_code, end_code) + + @property + def _start_template(self): + return self._color_template.format( + n=self._start_code, + color=self._color.ansi + ) + + +encircled = SGR(52, 54) +framed = SGR(51, 54) +overline = SGR(53, 55) +bold = SGR(1, 22) +gothic = SGR(20, 10) +italic = SGR(3, 23) +strike_through = SGR(9, 29) +fast_blink = SGR(6, 25) +slow_blink = SGR(5, 25) +underline = SGR(4, 24) +double_underline = SGR(21, 24) +faint = SGR(2, 22) +inverse = SGR(7, 27) diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py new file mode 100644 index 00000000..ed726aea --- /dev/null +++ b/progressbar/terminal/colors.py @@ -0,0 +1,947 @@ +# Based on: https://www.ditig.com/256-colors-cheat-sheet +from progressbar.terminal import base +from progressbar.terminal.base import Colors, HLS, RGB + +black = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Black', 0) +maroon = Colors.register(RGB(128, 0, 0), HLS(100, 0, 25), 'Maroon', 1) +green = Colors.register(RGB(0, 128, 0), HLS(100, 120, 25), 'Green', 2) +olive = Colors.register(RGB(128, 128, 0), HLS(100, 60, 25), 'Olive', 3) +navy = Colors.register(RGB(0, 0, 128), HLS(100, 240, 25), 'Navy', 4) +purple = Colors.register(RGB(128, 0, 128), HLS(100, 300, 25), 'Purple', 5) +teal = Colors.register(RGB(0, 128, 128), HLS(100, 180, 25), 'Teal', 6) +silver = Colors.register(RGB(192, 192, 192), HLS(0, 0, 75), 'Silver', 7) +grey = Colors.register(RGB(128, 128, 128), HLS(0, 0, 50), 'Grey', 8) +red = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red', 9) +lime = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Lime', 10) +yellow = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow', 11) +blue = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue', 12) +fuchsia = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Fuchsia', 13) +aqua = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Aqua', 14) +white = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'White', 15) +grey0 = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Grey0', 16) +navyBlue = Colors.register(RGB(0, 0, 95), HLS(100, 240, 18), 'NavyBlue', 17) +darkBlue = Colors.register(RGB(0, 0, 135), HLS(100, 240, 26), 'DarkBlue', 18) +blue3 = Colors.register(RGB(0, 0, 175), HLS(100, 240, 34), 'Blue3', 19) +blue3 = Colors.register(RGB(0, 0, 215), HLS(100, 240, 42), 'Blue3', 20) +blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) +darkGreen = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) +deepSkyBlue4 = Colors.register( + RGB(0, 95, 95), + HLS(100, 180, 18), + 'DeepSkyBlue4', + 23 +) +deepSkyBlue4 = Colors.register( + RGB(0, 95, 135), + HLS(100, 97, 26), + 'DeepSkyBlue4', + 24 +) +deepSkyBlue4 = Colors.register( + RGB(0, 95, 175), + HLS(100, 7, 34), + 'DeepSkyBlue4', + 25 +) +dodgerBlue3 = Colors.register( + RGB(0, 95, 215), + HLS(100, 13, 42), + 'DodgerBlue3', + 26 +) +dodgerBlue2 = Colors.register( + RGB(0, 95, 255), + HLS(100, 17, 50), + 'DodgerBlue2', + 27 +) +green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) +springGreen4 = Colors.register( + RGB(0, 135, 95), + HLS(100, 62, 26), + 'SpringGreen4', + 29 +) +turquoise4 = Colors.register( + RGB(0, 135, 135), + HLS(100, 180, 26), + 'Turquoise4', + 30 +) +deepSkyBlue3 = Colors.register( + RGB(0, 135, 175), + HLS(100, 93, 34), + 'DeepSkyBlue3', + 31 +) +deepSkyBlue3 = Colors.register( + RGB(0, 135, 215), + HLS(100, 2, 42), + 'DeepSkyBlue3', + 32 +) +dodgerBlue1 = Colors.register( + RGB(0, 135, 255), + HLS(100, 8, 50), + 'DodgerBlue1', + 33 +) +green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) +springGreen3 = Colors.register( + RGB(0, 175, 95), + HLS(100, 52, 34), + 'SpringGreen3', + 35 +) +darkCyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) +lightSeaGreen = Colors.register( + RGB(0, 175, 175), + HLS(100, 180, 34), + 'LightSeaGreen', + 37 +) +deepSkyBlue2 = Colors.register( + RGB(0, 175, 215), + HLS(100, 91, 42), + 'DeepSkyBlue2', + 38 +) +deepSkyBlue1 = Colors.register( + RGB(0, 175, 255), + HLS(100, 98, 50), + 'DeepSkyBlue1', + 39 +) +green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) +springGreen3 = Colors.register( + RGB(0, 215, 95), + HLS(100, 46, 42), + 'SpringGreen3', + 41 +) +springGreen2 = Colors.register( + RGB(0, 215, 135), + HLS(100, 57, 42), + 'SpringGreen2', + 42 +) +cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) +darkTurquoise = Colors.register( + RGB(0, 215, 215), + HLS(100, 180, 42), + 'DarkTurquoise', + 44 +) +turquoise2 = Colors.register( + RGB(0, 215, 255), + HLS(100, 89, 50), + 'Turquoise2', + 45 +) +green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) +springGreen2 = Colors.register( + RGB(0, 255, 95), + HLS(100, 42, 50), + 'SpringGreen2', + 47 +) +springGreen1 = Colors.register( + RGB(0, 255, 135), + HLS(100, 51, 50), + 'SpringGreen1', + 48 +) +mediumSpringGreen = Colors.register( + RGB(0, 255, 175), + HLS(100, 61, 50), + 'MediumSpringGreen', + 49 +) +cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) +cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) +darkRed = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) +deepPink4 = Colors.register(RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53) +purple4 = Colors.register(RGB(95, 0, 135), HLS(100, 82, 26), 'Purple4', 54) +purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) +purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) +blueViolet = Colors.register( + RGB(95, 0, 255), + HLS(100, 62, 50), + 'BlueViolet', + 57 +) +orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) +grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) +mediumPurple4 = Colors.register( + RGB(95, 95, 135), + HLS(17, 240, 45), + 'MediumPurple4', + 60 +) +slateBlue3 = Colors.register( + RGB(95, 95, 175), + HLS(33, 240, 52), + 'SlateBlue3', + 61 +) +slateBlue3 = Colors.register( + RGB(95, 95, 215), + HLS(60, 240, 60), + 'SlateBlue3', + 62 +) +royalBlue1 = Colors.register( + RGB(95, 95, 255), + HLS(100, 240, 68), + 'RoyalBlue1', + 63 +) +chartreuse4 = Colors.register( + RGB(95, 135, 0), + HLS(100, 7, 26), + 'Chartreuse4', + 64 +) +darkSeaGreen4 = Colors.register( + RGB(95, 135, 95), + HLS(17, 120, 45), + 'DarkSeaGreen4', + 65 +) +paleTurquoise4 = Colors.register( + RGB(95, 135, 135), + HLS(17, 180, 45), + 'PaleTurquoise4', + 66 +) +steelBlue = Colors.register( + RGB(95, 135, 175), + HLS(33, 210, 52), + 'SteelBlue', + 67 +) +steelBlue3 = Colors.register( + RGB(95, 135, 215), + HLS(60, 220, 60), + 'SteelBlue3', + 68 +) +cornflowerBlue = Colors.register( + RGB(95, 135, 255), + HLS(100, 225, 68), + 'CornflowerBlue', + 69 +) +chartreuse3 = Colors.register( + RGB(95, 175, 0), + HLS(100, 7, 34), + 'Chartreuse3', + 70 +) +darkSeaGreen4 = Colors.register( + RGB(95, 175, 95), + HLS(33, 120, 52), + 'DarkSeaGreen4', + 71 +) +cadetBlue = Colors.register( + RGB(95, 175, 135), + HLS(33, 150, 52), + 'CadetBlue', + 72 +) +cadetBlue = Colors.register( + RGB(95, 175, 175), + HLS(33, 180, 52), + 'CadetBlue', + 73 +) +skyBlue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) +steelBlue1 = Colors.register( + RGB(95, 175, 255), + HLS(100, 210, 68), + 'SteelBlue1', + 75 +) +chartreuse3 = Colors.register( + RGB(95, 215, 0), + HLS(100, 3, 42), + 'Chartreuse3', + 76 +) +paleGreen3 = Colors.register( + RGB(95, 215, 95), + HLS(60, 120, 60), + 'PaleGreen3', + 77 +) +seaGreen3 = Colors.register( + RGB(95, 215, 135), + HLS(60, 140, 60), + 'SeaGreen3', + 78 +) +aquamarine3 = Colors.register( + RGB(95, 215, 175), + HLS(60, 160, 60), + 'Aquamarine3', + 79 +) +mediumTurquoise = Colors.register( + RGB(95, 215, 215), + HLS(60, 180, 60), + 'MediumTurquoise', + 80 +) +steelBlue1 = Colors.register( + RGB(95, 215, 255), + HLS(100, 195, 68), + 'SteelBlue1', + 81 +) +chartreuse2 = Colors.register( + RGB(95, 255, 0), + HLS(100, 7, 50), + 'Chartreuse2', + 82 +) +seaGreen2 = Colors.register( + RGB(95, 255, 95), + HLS(100, 120, 68), + 'SeaGreen2', + 83 +) +seaGreen1 = Colors.register( + RGB(95, 255, 135), + HLS(100, 135, 68), + 'SeaGreen1', + 84 +) +seaGreen1 = Colors.register( + RGB(95, 255, 175), + HLS(100, 150, 68), + 'SeaGreen1', + 85 +) +aquamarine1 = Colors.register( + RGB(95, 255, 215), + HLS(100, 165, 68), + 'Aquamarine1', + 86 +) +darkSlateGray2 = Colors.register( + RGB(95, 255, 255), + HLS(100, 180, 68), + 'DarkSlateGray2', + 87 +) +darkRed = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) +deepPink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) +darkMagenta = Colors.register( + RGB(135, 0, 135), + HLS(100, 300, 26), + 'DarkMagenta', + 90 +) +darkMagenta = Colors.register( + RGB(135, 0, 175), + HLS(100, 86, 34), + 'DarkMagenta', + 91 +) +darkViolet = Colors.register( + RGB(135, 0, 215), + HLS(100, 77, 42), + 'DarkViolet', + 92 +) +purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) +orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) +lightPink4 = Colors.register(RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95) +plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) +mediumPurple3 = Colors.register( + RGB(135, 95, 175), + HLS(33, 270, 52), + 'MediumPurple3', + 97 +) +mediumPurple3 = Colors.register( + RGB(135, 95, 215), + HLS(60, 260, 60), + 'MediumPurple3', + 98 +) +slateBlue1 = Colors.register( + RGB(135, 95, 255), + HLS(100, 255, 68), + 'SlateBlue1', + 99 +) +yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) +wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) +grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) +lightSlateGrey = Colors.register( + RGB(135, 135, 175), + HLS(20, 240, 60), + 'LightSlateGrey', + 103 +) +mediumPurple = Colors.register( + RGB(135, 135, 215), + HLS(50, 240, 68), + 'MediumPurple', + 104 +) +lightSlateBlue = Colors.register( + RGB(135, 135, 255), + HLS(100, 240, 76), + 'LightSlateBlue', + 105 +) +yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) +darkOliveGreen3 = Colors.register( + RGB(135, 175, 95), + HLS(33, 90, 52), + 'DarkOliveGreen3', + 107 +) +darkSeaGreen = Colors.register( + RGB(135, 175, 135), + HLS(20, 120, 60), + 'DarkSeaGreen', + 108 +) +lightSkyBlue3 = Colors.register( + RGB(135, 175, 175), + HLS(20, 180, 60), + 'LightSkyBlue3', + 109 +) +lightSkyBlue3 = Colors.register( + RGB(135, 175, 215), + HLS(50, 210, 68), + 'LightSkyBlue3', + 110 +) +skyBlue2 = Colors.register( + RGB(135, 175, 255), + HLS(100, 220, 76), + 'SkyBlue2', + 111 +) +chartreuse2 = Colors.register( + RGB(135, 215, 0), + HLS(100, 2, 42), + 'Chartreuse2', + 112 +) +darkOliveGreen3 = Colors.register( + RGB(135, 215, 95), + HLS(60, 100, 60), + 'DarkOliveGreen3', + 113 +) +paleGreen3 = Colors.register( + RGB(135, 215, 135), + HLS(50, 120, 68), + 'PaleGreen3', + 114 +) +darkSeaGreen3 = Colors.register( + RGB(135, 215, 175), + HLS(50, 150, 68), + 'DarkSeaGreen3', + 115 +) +darkSlateGray3 = Colors.register( + RGB(135, 215, 215), + HLS(50, 180, 68), + 'DarkSlateGray3', + 116 +) +skyBlue1 = Colors.register( + RGB(135, 215, 255), + HLS(100, 200, 76), + 'SkyBlue1', + 117 +) +chartreuse1 = Colors.register( + RGB(135, 255, 0), + HLS(100, 8, 50), + 'Chartreuse1', + 118 +) +lightGreen = Colors.register( + RGB(135, 255, 95), + HLS(100, 105, 68), + 'LightGreen', + 119 +) +lightGreen = Colors.register( + RGB(135, 255, 135), + HLS(100, 120, 76), + 'LightGreen', + 120 +) +paleGreen1 = Colors.register( + RGB(135, 255, 175), + HLS(100, 140, 76), + 'PaleGreen1', + 121 +) +aquamarine1 = Colors.register( + RGB(135, 255, 215), + HLS(100, 160, 76), + 'Aquamarine1', + 122 +) +darkSlateGray1 = Colors.register( + RGB(135, 255, 255), + HLS(100, 180, 76), + 'DarkSlateGray1', + 123 +) +red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) +deepPink4 = Colors.register(RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125) +mediumVioletRed = Colors.register( + RGB(175, 0, 135), + HLS(100, 13, 34), + 'MediumVioletRed', + 126 +) +magenta3 = Colors.register(RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127) +darkViolet = Colors.register( + RGB(175, 0, 215), + HLS(100, 88, 42), + 'DarkViolet', + 128 +) +purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) +darkOrange3 = Colors.register( + RGB(175, 95, 0), + HLS(100, 2, 34), + 'DarkOrange3', + 130 +) +indianRed = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) +hotPink3 = Colors.register(RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132) +mediumOrchid3 = Colors.register( + RGB(175, 95, 175), + HLS(33, 300, 52), + 'MediumOrchid3', + 133 +) +mediumOrchid = Colors.register( + RGB(175, 95, 215), + HLS(60, 280, 60), + 'MediumOrchid', + 134 +) +mediumPurple2 = Colors.register( + RGB(175, 95, 255), + HLS(100, 270, 68), + 'MediumPurple2', + 135 +) +darkGoldenrod = Colors.register( + RGB(175, 135, 0), + HLS(100, 6, 34), + 'DarkGoldenrod', + 136 +) +lightSalmon3 = Colors.register( + RGB(175, 135, 95), + HLS(33, 30, 52), + 'LightSalmon3', + 137 +) +rosyBrown = Colors.register( + RGB(175, 135, 135), + HLS(20, 0, 60), + 'RosyBrown', + 138 +) +grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) +mediumPurple2 = Colors.register( + RGB(175, 135, 215), + HLS(50, 270, 68), + 'MediumPurple2', + 140 +) +mediumPurple1 = Colors.register( + RGB(175, 135, 255), + HLS(100, 260, 76), + 'MediumPurple1', + 141 +) +gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) +darkKhaki = Colors.register( + RGB(175, 175, 95), + HLS(33, 60, 52), + 'DarkKhaki', + 143 +) +navajoWhite3 = Colors.register( + RGB(175, 175, 135), + HLS(20, 60, 60), + 'NavajoWhite3', + 144 +) +grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) +lightSteelBlue3 = Colors.register( + RGB(175, 175, 215), + HLS(33, 240, 76), + 'LightSteelBlue3', + 146 +) +lightSteelBlue = Colors.register( + RGB(175, 175, 255), + HLS(100, 240, 84), + 'LightSteelBlue', + 147 +) +yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) +darkOliveGreen3 = Colors.register( + RGB(175, 215, 95), + HLS(60, 80, 60), + 'DarkOliveGreen3', + 149 +) +darkSeaGreen3 = Colors.register( + RGB(175, 215, 135), + HLS(50, 90, 68), + 'DarkSeaGreen3', + 150 +) +darkSeaGreen2 = Colors.register( + RGB(175, 215, 175), + HLS(33, 120, 76), + 'DarkSeaGreen2', + 151 +) +lightCyan3 = Colors.register( + RGB(175, 215, 215), + HLS(33, 180, 76), + 'LightCyan3', + 152 +) +lightSkyBlue1 = Colors.register( + RGB(175, 215, 255), + HLS(100, 210, 84), + 'LightSkyBlue1', + 153 +) +greenYellow = Colors.register( + RGB(175, 255, 0), + HLS(100, 8, 50), + 'GreenYellow', + 154 +) +darkOliveGreen2 = Colors.register( + RGB(175, 255, 95), + HLS(100, 90, 68), + 'DarkOliveGreen2', + 155 +) +paleGreen1 = Colors.register( + RGB(175, 255, 135), + HLS(100, 100, 76), + 'PaleGreen1', + 156 +) +darkSeaGreen2 = Colors.register( + RGB(175, 255, 175), + HLS(100, 120, 84), + 'DarkSeaGreen2', + 157 +) +darkSeaGreen1 = Colors.register( + RGB(175, 255, 215), + HLS(100, 150, 84), + 'DarkSeaGreen1', + 158 +) +paleTurquoise1 = Colors.register( + RGB(175, 255, 255), + HLS(100, 180, 84), + 'PaleTurquoise1', + 159 +) +red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) +deepPink3 = Colors.register(RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161) +deepPink3 = Colors.register( + RGB(215, 0, 135), + HLS(100, 22, 42), + 'DeepPink3', + 162 +) +magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) +magenta3 = Colors.register(RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164) +magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) +darkOrange3 = Colors.register( + RGB(215, 95, 0), + HLS(100, 6, 42), + 'DarkOrange3', + 166 +) +indianRed = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) +hotPink3 = Colors.register(RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168) +hotPink2 = Colors.register(RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169) +orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) +mediumOrchid1 = Colors.register( + RGB(215, 95, 255), + HLS(100, 285, 68), + 'MediumOrchid1', + 171 +) +orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) +lightSalmon3 = Colors.register( + RGB(215, 135, 95), + HLS(60, 20, 60), + 'LightSalmon3', + 173 +) +lightPink3 = Colors.register( + RGB(215, 135, 135), + HLS(50, 0, 68), + 'LightPink3', + 174 +) +pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) +plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) +violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) +gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) +lightGoldenrod3 = Colors.register( + RGB(215, 175, 95), + HLS(60, 40, 60), + 'LightGoldenrod3', + 179 +) +tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) +mistyRose3 = Colors.register( + RGB(215, 175, 175), + HLS(33, 0, 76), + 'MistyRose3', + 181 +) +thistle3 = Colors.register( + RGB(215, 175, 215), + HLS(33, 300, 76), + 'Thistle3', + 182 +) +plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) +yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) +khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) +lightGoldenrod2 = Colors.register( + RGB(215, 215, 135), + HLS(50, 60, 68), + 'LightGoldenrod2', + 186 +) +lightYellow3 = Colors.register( + RGB(215, 215, 175), + HLS(33, 60, 76), + 'LightYellow3', + 187 +) +grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) +lightSteelBlue1 = Colors.register( + RGB(215, 215, 255), + HLS(100, 240, 92), + 'LightSteelBlue1', + 189 +) +yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) +darkOliveGreen1 = Colors.register( + RGB(215, 255, 95), + HLS(100, 75, 68), + 'DarkOliveGreen1', + 191 +) +darkOliveGreen1 = Colors.register( + RGB(215, 255, 135), + HLS(100, 80, 76), + 'DarkOliveGreen1', + 192 +) +darkSeaGreen1 = Colors.register( + RGB(215, 255, 175), + HLS(100, 90, 84), + 'DarkSeaGreen1', + 193 +) +honeydew2 = Colors.register( + RGB(215, 255, 215), + HLS(100, 120, 92), + 'Honeydew2', + 194 +) +lightCyan1 = Colors.register( + RGB(215, 255, 255), + HLS(100, 180, 92), + 'LightCyan1', + 195 +) +red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) +deepPink2 = Colors.register(RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197) +deepPink1 = Colors.register( + RGB(255, 0, 135), + HLS(100, 28, 50), + 'DeepPink1', + 198 +) +deepPink1 = Colors.register( + RGB(255, 0, 175), + HLS(100, 18, 50), + 'DeepPink1', + 199 +) +magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) +magenta1 = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201) +orangeRed1 = Colors.register( + RGB(255, 95, 0), + HLS(100, 2, 50), + 'OrangeRed1', + 202 +) +indianRed1 = Colors.register( + RGB(255, 95, 95), + HLS(100, 0, 68), + 'IndianRed1', + 203 +) +indianRed1 = Colors.register( + RGB(255, 95, 135), + HLS(100, 345, 68), + 'IndianRed1', + 204 +) +hotPink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) +hotPink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) +mediumOrchid1 = Colors.register( + RGB(255, 95, 255), + HLS(100, 300, 68), + 'MediumOrchid1', + 207 +) +darkOrange = Colors.register( + RGB(255, 135, 0), + HLS(100, 1, 50), + 'DarkOrange', + 208 +) +salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) +lightCoral = Colors.register( + RGB(255, 135, 135), + HLS(100, 0, 76), + 'LightCoral', + 210 +) +paleVioletRed1 = Colors.register( + RGB(255, 135, 175), + HLS(100, 340, 76), + 'PaleVioletRed1', + 211 +) +orchid2 = Colors.register(RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212) +orchid1 = Colors.register(RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213) +orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) +sandyBrown = Colors.register( + RGB(255, 175, 95), + HLS(100, 30, 68), + 'SandyBrown', + 215 +) +lightSalmon1 = Colors.register( + RGB(255, 175, 135), + HLS(100, 20, 76), + 'LightSalmon1', + 216 +) +lightPink1 = Colors.register( + RGB(255, 175, 175), + HLS(100, 0, 84), + 'LightPink1', + 217 +) +pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) +plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) +gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) +lightGoldenrod2 = Colors.register( + RGB(255, 215, 95), + HLS(100, 45, 68), + 'LightGoldenrod2', + 221 +) +lightGoldenrod2 = Colors.register( + RGB(255, 215, 135), + HLS(100, 40, 76), + 'LightGoldenrod2', + 222 +) +navajoWhite1 = Colors.register( + RGB(255, 215, 175), + HLS(100, 30, 84), + 'NavajoWhite1', + 223 +) +mistyRose1 = Colors.register( + RGB(255, 215, 215), + HLS(100, 0, 92), + 'MistyRose1', + 224 +) +thistle1 = Colors.register( + RGB(255, 215, 255), + HLS(100, 300, 92), + 'Thistle1', + 225 +) +yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) +lightGoldenrod1 = Colors.register( + RGB(255, 255, 95), + HLS(100, 60, 68), + 'LightGoldenrod1', + 227 +) +khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) +wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) +cornsilk1 = Colors.register( + RGB(255, 255, 215), + HLS(100, 60, 92), + 'Cornsilk1', + 230 +) +grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) +grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) +grey7 = Colors.register(RGB(18, 18, 18), HLS(0, 0, 7), 'Grey7', 233) +grey11 = Colors.register(RGB(28, 28, 28), HLS(0, 0, 10), 'Grey11', 234) +grey15 = Colors.register(RGB(38, 38, 38), HLS(0, 0, 14), 'Grey15', 235) +grey19 = Colors.register(RGB(48, 48, 48), HLS(0, 0, 18), 'Grey19', 236) +grey23 = Colors.register(RGB(58, 58, 58), HLS(0, 0, 22), 'Grey23', 237) +grey27 = Colors.register(RGB(68, 68, 68), HLS(0, 0, 26), 'Grey27', 238) +grey30 = Colors.register(RGB(78, 78, 78), HLS(0, 0, 30), 'Grey30', 239) +grey35 = Colors.register(RGB(88, 88, 88), HLS(0, 0, 34), 'Grey35', 240) +grey39 = Colors.register(RGB(98, 98, 98), HLS(0, 0, 37), 'Grey39', 241) +grey42 = Colors.register(RGB(108, 108, 108), HLS(0, 0, 40), 'Grey42', 242) +grey46 = Colors.register(RGB(118, 118, 118), HLS(0, 0, 46), 'Grey46', 243) +grey50 = Colors.register(RGB(128, 128, 128), HLS(0, 0, 50), 'Grey50', 244) +grey54 = Colors.register(RGB(138, 138, 138), HLS(0, 0, 54), 'Grey54', 245) +grey58 = Colors.register(RGB(148, 148, 148), HLS(0, 0, 58), 'Grey58', 246) +grey62 = Colors.register(RGB(158, 158, 158), HLS(0, 0, 61), 'Grey62', 247) +grey66 = Colors.register(RGB(168, 168, 168), HLS(0, 0, 65), 'Grey66', 248) +grey70 = Colors.register(RGB(178, 178, 178), HLS(0, 0, 69), 'Grey70', 249) +grey74 = Colors.register(RGB(188, 188, 188), HLS(0, 0, 73), 'Grey74', 250) +grey78 = Colors.register(RGB(198, 198, 198), HLS(0, 0, 77), 'Grey78', 251) +grey82 = Colors.register(RGB(208, 208, 208), HLS(0, 0, 81), 'Grey82', 252) +grey85 = Colors.register(RGB(218, 218, 218), HLS(0, 0, 85), 'Grey85', 253) +grey89 = Colors.register(RGB(228, 228, 228), HLS(0, 0, 89), 'Grey89', 254) +grey93 = Colors.register(RGB(238, 238, 238), HLS(0, 0, 93), 'Grey93', 255) + +if __name__ == '__main__': + red = Colors.register(RGB(255, 128, 128)) + # red = Colors.register(RGB(255, 100, 100)) + for i in base.ColorSupport: + base.color_support = i + print(i, red.fg('RED!'), red.bg('RED!'), red.underline('RED!')) From fd708bfea3105912b06e4c1307c8f162ade4363a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 6 Dec 2022 03:11:14 +0100 Subject: [PATCH 076/118] Revert "removed unneeded os functions for now" This reverts commit e2996be8590ebe1a4fe191bbb15041fdb9fa834d. --- progressbar/os_functions/__init__.py | 24 +++++ progressbar/os_functions/nix.py | 15 +++ progressbar/os_functions/windows.py | 144 +++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 progressbar/os_functions/__init__.py create mode 100644 progressbar/os_functions/nix.py create mode 100644 progressbar/os_functions/windows.py diff --git a/progressbar/os_functions/__init__.py b/progressbar/os_functions/__init__.py new file mode 100644 index 00000000..8b442283 --- /dev/null +++ b/progressbar/os_functions/__init__.py @@ -0,0 +1,24 @@ +import sys + +if sys.platform.startswith('win'): + from .windows import ( + getch as _getch, + set_console_mode as _set_console_mode, + reset_console_mode as _reset_console_mode + ) + +else: + from .nix import getch as _getch + + def _reset_console_mode(): + pass + + + def _set_console_mode(): + pass + + +getch = _getch +reset_console_mode = _reset_console_mode +set_console_mode = _set_console_mode + diff --git a/progressbar/os_functions/nix.py b/progressbar/os_functions/nix.py new file mode 100644 index 00000000..e46fbdf0 --- /dev/null +++ b/progressbar/os_functions/nix.py @@ -0,0 +1,15 @@ +import sys +import tty +import termios + + +def getch(): + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + return ch diff --git a/progressbar/os_functions/windows.py b/progressbar/os_functions/windows.py new file mode 100644 index 00000000..edac0696 --- /dev/null +++ b/progressbar/os_functions/windows.py @@ -0,0 +1,144 @@ +import ctypes +from ctypes.wintypes import ( + DWORD as _DWORD, + HANDLE as _HANDLE, + BOOL as _BOOL, + WORD as _WORD, + UINT as _UINT, + WCHAR as _WCHAR, + CHAR as _CHAR, + SHORT as _SHORT +) + +_kernel32 = ctypes.windll.Kernel32 + +_ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 +_ENABLE_PROCESSED_OUTPUT = 0x0001 +_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + +_STD_INPUT_HANDLE = _DWORD(-10) +_STD_OUTPUT_HANDLE = _DWORD(-11) + + +_GetConsoleMode = _kernel32.GetConsoleMode +_GetConsoleMode.restype = _BOOL + +_SetConsoleMode = _kernel32.SetConsoleMode +_SetConsoleMode.restype = _BOOL + +_GetStdHandle = _kernel32.GetStdHandle +_GetStdHandle.restype = _HANDLE + +_ReadConsoleInput = _kernel32.ReadConsoleInputA +_ReadConsoleInput.restype = _BOOL + + +_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) +_input_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) + +_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) +_output_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) + + +class _COORD(ctypes.Structure): + _fields_ = [ + ('X', _SHORT), + ('Y', _SHORT) + ] + + +class _FOCUS_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('bSetFocus', _BOOL) + ] + + +class _KEY_EVENT_RECORD(ctypes.Structure): + class _uchar(ctypes.Union): + _fields_ = [ + ('UnicodeChar', _WCHAR), + ('AsciiChar', _CHAR) + ] + + _fields_ = [ + ('bKeyDown', _BOOL), + ('wRepeatCount', _WORD), + ('wVirtualKeyCode', _WORD), + ('wVirtualScanCode', _WORD), + ('uChar', _uchar), + ('dwControlKeyState', _DWORD) + ] + + +class _MENU_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwCommandId', _UINT) + ] + + +class _MOUSE_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwMousePosition', _COORD), + ('dwButtonState', _DWORD), + ('dwControlKeyState', _DWORD), + ('dwEventFlags', _DWORD) + ] + + +class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): + _fields_ = [ + ('dwSize', _COORD) + ] + + +class _INPUT_RECORD(ctypes.Structure): + class _Event(ctypes.Union): + _fields_ = [ + ('KeyEvent', _KEY_EVENT_RECORD), + ('MouseEvent', _MOUSE_EVENT_RECORD), + ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), + ('MenuEvent', _MENU_EVENT_RECORD), + ('FocusEvent', _FOCUS_EVENT_RECORD) + ] + + _fields_ = [ + ('EventType', _WORD), + ('Event', _Event) + ] + + +def reset_console_mode(): + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) + + +def set_console_mode(): + mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) + + mode = ( + _output_mode.value | + _ENABLE_PROCESSED_OUTPUT | + _ENABLE_VIRTUAL_TERMINAL_PROCESSING + ) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) + + +def getch(): + lpBuffer = (_INPUT_RECORD * 2)() + nLength = _DWORD(2) + lpNumberOfEventsRead = _DWORD() + + _ReadConsoleInput( + _HANDLE(_hConsoleInput), + lpBuffer, nLength, + ctypes.byref(lpNumberOfEventsRead) + ) + + char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') + if char == '\x00': + return None + + return char From 203c4873ad5a7be295e3544cf89577c46531bffc Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 8 Dec 2022 23:32:42 +0100 Subject: [PATCH 077/118] moved os specific code --- progressbar/terminal/base.py | 3 +-- progressbar/{os_functions => terminal/os_specific}/__init__.py | 2 +- .../{os_functions/nix.py => terminal/os_specific/posix.py} | 0 progressbar/{os_functions => terminal/os_specific}/windows.py | 0 pytest.ini | 1 + 5 files changed, 3 insertions(+), 3 deletions(-) rename progressbar/{os_functions => terminal/os_specific}/__init__.py (90%) rename progressbar/{os_functions/nix.py => terminal/os_specific/posix.py} (100%) rename progressbar/{os_functions => terminal/os_specific}/windows.py (100%) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index d63a8da9..77373794 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -9,8 +9,7 @@ from python_utils import types -# from .os_functions import getch -# from .. import utils +from .os_specific import getch ESC = '\x1B' CSI = ESC + '[' diff --git a/progressbar/os_functions/__init__.py b/progressbar/terminal/os_specific/__init__.py similarity index 90% rename from progressbar/os_functions/__init__.py rename to progressbar/terminal/os_specific/__init__.py index 8b442283..782ca9cd 100644 --- a/progressbar/os_functions/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -8,7 +8,7 @@ ) else: - from .nix import getch as _getch + from .posix import getch as _getch def _reset_console_mode(): pass diff --git a/progressbar/os_functions/nix.py b/progressbar/terminal/os_specific/posix.py similarity index 100% rename from progressbar/os_functions/nix.py rename to progressbar/terminal/os_specific/posix.py diff --git a/progressbar/os_functions/windows.py b/progressbar/terminal/os_specific/windows.py similarity index 100% rename from progressbar/os_functions/windows.py rename to progressbar/terminal/os_specific/windows.py diff --git a/pytest.ini b/pytest.ini index bdfd4dec..d6a47d53 100644 --- a/pytest.ini +++ b/pytest.ini @@ -19,6 +19,7 @@ norecursedirs = dist .ropeproject .tox + progressbar/terminal/os_specific filterwarnings = ignore::DeprecationWarning From ac9859535179a8cc6621ff84e52f97c2c7b5c3be Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 12 Jan 2023 13:40:13 +0100 Subject: [PATCH 078/118] Added basic multiple progressbar support --- README.rst | 85 +++++++++++++++++++++++++---------------- progressbar/__init__.py | 2 + progressbar/bar.py | 8 ++++ progressbar/multi.py | 25 ++++++++++++ 4 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 progressbar/multi.py diff --git a/README.rst b/README.rst index 94b33333..26695e96 100644 --- a/README.rst +++ b/README.rst @@ -238,55 +238,72 @@ Bar with wide Chinese (or other multibyte) characters for i in bar(range(10)): time.sleep(0.1) -Showing multiple (threaded) independent progress bars in parallel +Showing multiple independent progress bars in parallel ============================================================================== -While this method works fine and will continue to work fine, a smarter and -fully automatic version of this is currently being made: -https://github.com/WoLpH/python-progressbar/issues/176 - .. code:: python import random import sys - import threading import time import progressbar - output_lock = threading.Lock() + BARS = 5 + N = 100 + # Construct the list of progress bars with the `line_offset` so they draw + # below each other + bars = [] + for i in range(BARS): + bars.append( + progressbar.ProgressBar( + max_value=N, + # We add 1 to the line offset to account for the `print_fd` + line_offset=i + 1, + max_error=False, + ) + ) - class LineOffsetStreamWrapper: - UP = '\033[F' - DOWN = '\033[B' + # Create a file descriptor for regular printing as well + print_fd = progressbar.LineOffsetStreamWrapper(sys.stdout, 0) - def __init__(self, lines=0, stream=sys.stderr): - self.stream = stream - self.lines = lines + # The progress bar updates, normally you would do something useful here + for i in range(N * BARS): + time.sleep(0.005) - def write(self, data): - with output_lock: - self.stream.write(self.UP * self.lines) - self.stream.write(data) - self.stream.write(self.DOWN * self.lines) - self.stream.flush() + # Increment one of the progress bars at random + bars[random.randrange(0, BARS)].increment() - def __getattr__(self, name): - return getattr(self.stream, name) + # Print a status message to the `print_fd` below the progress bars + print(f'Hi, we are at update {i+1} of {N * BARS}', file=print_fd) + # Cleanup the bars + for bar in bars: + bar.finish() - bars = [] - for i in range(5): - bars.append( - progressbar.ProgressBar( - fd=LineOffsetStreamWrapper(i), - max_value=1000, - ) - ) + # Add a newline to make sure the next print starts on a new line + print() - if i: - print('Reserve a line for the progressbar') +****************************************************************************** + +Naturally we can do this from separate threads as well: + +.. code:: python + + import random + import threading + import time + + import progressbar + + BARS = 5 + N = 100 + + # Create the bars with the given line offset + bars = [] + for line_offset in range(BARS): + bars.append(progressbar.ProgressBar(line_offset=line_offset, max_value=N)) class Worker(threading.Thread): @@ -295,10 +312,12 @@ https://github.com/WoLpH/python-progressbar/issues/176 self.bar = bar def run(self): - for i in range(1000): - time.sleep(random.random() / 100) + for i in range(N): + time.sleep(random.random() / 25) self.bar.update(i) for bar in bars: Worker(bar).start() + + print() diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 33d7c719..b47d0541 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,6 +35,7 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin +from .multi import LineOffsetStreamWrapper __date__ = str(date.today()) __all__ = [ @@ -73,4 +74,5 @@ 'NullBar', '__author__', '__version__', + 'LineOffsetStreamWrapper', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index b1a5c96b..7ebc08f0 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -16,6 +16,7 @@ from . import ( base, + multi, utils, widgets, widgets as widgets_module, # Avoid name collision @@ -143,6 +144,7 @@ def __init__( is_terminal: bool | None = None, line_breaks: bool | None = None, enable_colors: bool | None = None, + line_offset: int = 0, **kwargs, ): if fd is sys.stdout: @@ -151,6 +153,9 @@ def __init__( elif fd is sys.stderr: fd = utils.streams.original_stderr + if line_offset: + fd = multi.LineOffsetStreamWrapper(line_offset, fd) + self.fd = fd self.is_ansi_terminal = utils.is_ansi_terminal(fd) @@ -385,6 +390,9 @@ class ProgressBar( from a label using `format='{variables.my_var}'`. These values can be updated using `bar.update(my_var='newValue')` This can also be used to set initial values for variables' widgets + line_offset (int): The number of lines to offset the progressbar from + your current line. This is useful if you have other output or + multiple progressbars A common way of using it is like: diff --git a/progressbar/multi.py b/progressbar/multi.py new file mode 100644 index 00000000..59f2db8e --- /dev/null +++ b/progressbar/multi.py @@ -0,0 +1,25 @@ +import sys + + +class LineOffsetStreamWrapper: + UP = '\033[F' + DOWN = '\033[B' + + def __init__(self, lines=0, stream=sys.stderr): + self.stream = stream + self.lines = lines + + def write(self, data): + # Move the cursor up + self.stream.write(self.UP * self.lines) + # Print a carriage return to reset the cursor position + self.stream.write('\r') + # Print the data without newlines so we don't change the position + self.stream.write(data.rstrip('\n')) + # Move the cursor down + self.stream.write(self.DOWN * self.lines) + + self.stream.flush() + + def __getattr__(self, name): + return getattr(self.stream, name) From a4876d97a377fe928c0c43684066aa11c9f01c1f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 23 Jan 2023 02:44:53 +0100 Subject: [PATCH 079/118] Added fully functional multiple threaded progressbar support with print support --- progressbar/__init__.py | 5 +- progressbar/bar.py | 32 ++- progressbar/base.py | 4 +- progressbar/multi.py | 353 +++++++++++++++++++++++++++++-- progressbar/terminal/__init__.py | 1 + progressbar/terminal/base.py | 225 +++++++++++--------- progressbar/terminal/stream.py | 129 +++++++++++ 7 files changed, 623 insertions(+), 126 deletions(-) create mode 100644 progressbar/terminal/stream.py diff --git a/progressbar/__init__.py b/progressbar/__init__.py index b47d0541..117124c2 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,7 +35,8 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin -from .multi import LineOffsetStreamWrapper +from .multi import MultiBar +from .terminal.stream import LineOffsetStreamWrapper __date__ = str(date.today()) __all__ = [ @@ -74,5 +75,5 @@ 'NullBar', '__author__', '__version__', - 'LineOffsetStreamWrapper', + 'MultiBar', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index 7ebc08f0..b2826652 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import itertools import logging import math import os @@ -14,9 +15,9 @@ from python_utils import converters, types +import progressbar.terminal.stream from . import ( base, - multi, utils, widgets, widgets as widgets_module, # Avoid name collision @@ -120,9 +121,25 @@ def __getstate__(self): def data(self) -> types.Dict[str, types.Any]: raise NotImplementedError() + def started(self) -> bool: + return self._started or self._finished + + def finished(self) -> bool: + return self._finished + class ProgressBarBase(types.Iterable, ProgressBarMixinBase): - pass + _index_counter = itertools.count() + index: int = -1 + label: str = '' + + def __init__(self, **kwargs): + self.index = next(self._index_counter) + super().__init__(**kwargs) + + def __repr__(self): + label = f': {self.label}' if self.label else '' + return f'<{self.__class__.__name__}#{self.index}{label}>' class DefaultFdMixin(ProgressBarMixinBase): @@ -154,7 +171,10 @@ def __init__( fd = utils.streams.original_stderr if line_offset: - fd = multi.LineOffsetStreamWrapper(line_offset, fd) + fd = progressbar.terminal.stream.LineOffsetStreamWrapper( + line_offset, + fd + ) self.fd = fd self.is_ansi_terminal = utils.is_ansi_terminal(fd) @@ -183,6 +203,9 @@ def __init__( ProgressBarMixinBase.__init__(self, **kwargs) + def print(self, *args, **kwargs): + print(*args, file=self.fd, **kwargs) + def update(self, *args, **kwargs): ProgressBarMixinBase.update(self, *args, **kwargs) @@ -435,6 +458,7 @@ class ProgressBar( # update every 50 milliseconds (up to a 20 times per second) _MINIMUM_UPDATE_INTERVAL: float = 0.050 _last_update_time: types.Optional[float] = None + paused: bool = False def __init__( self, @@ -757,6 +781,8 @@ def increment(self, value=1, *args, **kwargs): def _needs_update(self): 'Returns whether the ProgressBar should redraw the line.' + if self.paused: + return False delta = timeit.default_timer() - self._last_update_timer if delta < self.min_poll_interval: # Prevent updating too often diff --git a/progressbar/base.py b/progressbar/base.py index 8639f557..32a95783 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -26,5 +26,5 @@ class Undefined(metaclass=FalseMeta): except AttributeError: from typing.io import IO, TextIO # type: ignore -assert IO -assert TextIO +assert IO is not None +assert TextIO is not None diff --git a/progressbar/multi.py b/progressbar/multi.py index 59f2db8e..d4d13979 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -1,25 +1,342 @@ +from __future__ import annotations + +import enum +import io +import itertools +import operator import sys +import threading +import time +import timeit +import typing +from datetime import timedelta + +import python_utils + +from . import bar, terminal +from .terminal import stream + +SortKeyFunc = typing.Callable[[bar.ProgressBar], typing.Any] + + +class SortKey(str, enum.Enum): + ''' + Sort keys for the MultiBar + + This is a string enum, so you can use any + progressbar attribute or property as a sort key. + + Note that the multibar defaults to lazily rendering only the changed + progressbars. This means that sorting by dynamic attributes such as + `value` might result in more rendering which can have a small performance + impact. + ''' + CREATED = 'index' + LABEL = 'label' + VALUE = 'value' + PERCENTAGE = 'percentage' + + +class MultiBar(dict[str, bar.ProgressBar]): + fd: typing.TextIO + _buffer: io.StringIO + + #: The format for the label to append/prepend to the progressbar + label_format: str + #: Automatically prepend the label to the progressbars + prepend_label: bool + #: Automatically append the label to the progressbars + append_label: bool + #: If `initial_format` is `None`, the progressbar rendering is used + # which will *start* the progressbar. That means the progressbar will + # have no knowledge of your data and will run as an infinite progressbar. + initial_format: str | None + #: If `finished_format` is `None`, the progressbar rendering is used. + finished_format: str | None + + #: The multibar updates at a fixed interval regardless of the progressbar + # updates + update_interval: float + remove_finished: float | None + + #: The kwargs passed to the progressbar constructor + progressbar_kwargs: typing.Dict[str, typing.Any] + + #: The progressbar sorting key function + sort_keyfunc: SortKeyFunc + + _previous_output: list[str] + _finished_at: dict[bar.ProgressBar, float] + _labeled: set[bar.ProgressBar] + _print_lock: threading.RLock = threading.RLock() + _thread: threading.Thread | None = None + _thread_finished: threading.Event = threading.Event() + _thread_closed: threading.Event = threading.Event() + + def __init__( + self, + bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, + fd=sys.stderr, + prepend_label: bool = True, + append_label: bool = False, + label_format='{label:20.20} ', + initial_format: str | None = '{label:20.20} Not yet started', + finished_format: str | None = None, + update_interval: float = 1 / 60.0, # 60fps + show_initial: bool = True, + show_finished: bool = True, + remove_finished: timedelta | float = timedelta(seconds=3600), + sort_key: str | SortKey = SortKey.CREATED, + sort_reverse: bool = True, + sort_keyfunc: SortKeyFunc | None = None, + **progressbar_kwargs, + ): + self.fd = fd + + self.prepend_label = prepend_label + self.append_label = append_label + self.label_format = label_format + self.initial_format = initial_format + self.finished_format = finished_format + + self.update_interval = update_interval + + self.show_initial = show_initial + self.show_finished = show_finished + self.remove_finished = python_utils.delta_to_seconds_or_none( + remove_finished + ) + + self.progressbar_kwargs = progressbar_kwargs + + if sort_keyfunc is None: + sort_keyfunc = operator.attrgetter(sort_key) + + self.sort_keyfunc = sort_keyfunc + self.sort_reverse = sort_reverse + + self._labeled = set() + self._finished_at = {} + self._previous_output = [] + self._buffer = io.StringIO() + + super().__init__(bars or {}) + + def __setitem__(self, key: str, value: bar.ProgressBar): + '''Add a progressbar to the multibar''' + if value.label != key: + value.label = key + value.fd = stream.LastLineStream(self.fd) + value.paused = True + value.print = self.print + + # Just in case someone is using a progressbar with a custom + # constructor and forgot to call the super constructor + if value.index == -1: + value.index = next(value._index_counter) + + super().__setitem__(key, value) + + def __delitem__(self, key): + '''Remove a progressbar from the multibar''' + super().__delitem__(key) + self._finished_at.pop(key, None) + self._labeled.discard(key) + + def __getitem__(self, item): + '''Get (and create if needed) a progressbar from the multibar''' + try: + return super().__getitem__(item) + except KeyError: + progress = bar.ProgressBar(**self.progressbar_kwargs) + self[item] = progress + return progress + + def _label_bar(self, bar: bar.ProgressBar): + if bar in self._labeled: + return + + assert bar.widgets, 'Cannot prepend label to empty progressbar' + self._labeled.add(bar) + + if self.prepend_label: + bar.widgets.insert(0, self.label_format.format(label=bar.label)) + + if self.append_label and bar not in self._labeled: + bar.widgets.append(self.label_format.format(label=bar.label)) + + def render(self, flush: bool = True, force: bool = False): + '''Render the multibar to the given stream''' + now = timeit.default_timer() + expired = now - self.remove_finished if self.remove_finished else None + output = [] + for bar in self.get_sorted_bars(): + if not bar.started() and not self.show_initial: + continue + + def update(force=True, write=True): + self._label_bar(bar) + bar.update(force=force) + if write: + output.append(bar.fd.line) + + if bar.finished(): + if bar not in self._finished_at: + self._finished_at[bar] = now + # Force update to get the finished format + update(write=False) + + if self.remove_finished: + if expired >= self._finished_at[bar]: + del self[bar.label] + continue + + if not self.show_finished: + continue + + if bar.finished(): + if self.finished_format is None: + update(force=False) + else: + output.append(self.finished_format.format(label=bar.label)) + elif bar.started(): + update() + else: + if self.initial_format is None: + bar.start() + update() + else: + output.append(self.initial_format.format(label=bar.label)) + + with self._print_lock: + # Clear the previous output if progressbars have been removed + for i in range(len(output), len(self._previous_output)): + self._buffer.write(terminal.clear_line(i + 1)) + + # Add empty lines to the end of the output if progressbars have been + # added + for i in range(len(self._previous_output), len(output)): + # Adding a new line so we don't overwrite previous output + self._buffer.write('\n') + + for i, (previous, current) in enumerate( + itertools.zip_longest( + self._previous_output, + output, + fillvalue='' + ) + ): + if previous != current or force: + self.print( + '\r' + current.strip(), + offset=i + 1, + end='', + clear=False, + flush=False, + ) + + self._previous_output = output + + if flush: + self.flush() + + def print( + self, + *args, + end='\n', + offset=None, + flush=True, + clear=True, + **kwargs + ): + ''' + Print to the progressbar stream without overwriting the progressbars + + Args: + end: The string to append to the end of the output + offset: The number of lines to offset the output by. If None, the + output will be printed above the progressbars + flush: Whether to flush the output to the stream + clear: If True, the line will be cleared before printing. + **kwargs: Additional keyword arguments to pass to print + ''' + with self._print_lock: + if offset is None: + offset = len(self._previous_output) + + if not clear: + self._buffer.write(terminal.PREVIOUS_LINE(offset)) + + if clear: + self._buffer.write(terminal.PREVIOUS_LINE(offset)) + self._buffer.write(terminal.CLEAR_LINE_ALL()) + + print(*args, **kwargs, file=self._buffer, end=end) + + if clear: + self._buffer.write(terminal.CLEAR_SCREEN_TILL_END()) + for line in self._previous_output: + self._buffer.write(line.strip()) + self._buffer.write('\n') + + else: + self._buffer.write(terminal.NEXT_LINE(offset)) + + if flush: + self.flush() + + def flush(self): + self.fd.write(self._buffer.getvalue()) + self._buffer.truncate(0) + self.fd.flush() + + def run(self, join=True): + ''' + Start the multibar render loop and run the progressbars until they + have force _thread_finished + ''' + while not self._thread_finished.is_set(): + self.render() + time.sleep(self.update_interval) + + if join or self._thread_closed.is_set(): + # If the thread is closed, we need to check if force progressbars + # have finished. If they have, we can exit the loop + for bar_ in self.values(): + if not bar_.finished(): + break + else: + # Render one last time to make sure the progressbars are + # correctly finished + self.render(force=True) + return + def start(self): + assert not self._thread, 'Multibar already started' + self._thread_closed.set() + self._thread = threading.Thread(target=self.run, args=(False,)) + self._thread.start() -class LineOffsetStreamWrapper: - UP = '\033[F' - DOWN = '\033[B' + def join(self, timeout=None): + if self._thread is not None: + self._thread_closed.set() + self._thread.join(timeout=timeout) + self._thread = None - def __init__(self, lines=0, stream=sys.stderr): - self.stream = stream - self.lines = lines + def stop(self, timeout: float | None = None): + self._thread_finished.set() + self.join(timeout=timeout) - def write(self, data): - # Move the cursor up - self.stream.write(self.UP * self.lines) - # Print a carriage return to reset the cursor position - self.stream.write('\r') - # Print the data without newlines so we don't change the position - self.stream.write(data.rstrip('\n')) - # Move the cursor down - self.stream.write(self.DOWN * self.lines) + def get_sorted_bars(self): + return sorted( + self.values(), + key=self.sort_keyfunc, + reverse=self.sort_reverse, + ) - self.stream.flush() + def __enter__(self): + self.start() + return self - def __getattr__(self, name): - return getattr(self.stream, name) + def __exit__(self, exc_type, exc_val, exc_tb): + self.join() diff --git a/progressbar/terminal/__init__.py b/progressbar/terminal/__init__.py index e69de29b..4b40b38c 100644 --- a/progressbar/terminal/__init__.py +++ b/progressbar/terminal/__init__.py @@ -0,0 +1 @@ +from .base import * # noqa diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 77373794..95c46307 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -12,9 +12,126 @@ from .os_specific import getch ESC = '\x1B' -CSI = ESC + '[' -CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) + +class CSI: + _code: str + _template = ESC + '[{args}{code}' + + def __init__(self, code, *default_args): + self._code = code + self._default_args = default_args + + def __call__(self, *args): + return self._template.format( + args=';'.join(map(str, args or self._default_args)), + code=self._code + ) + + def __str__(self): + return self() + + +class CSINoArg(CSI): + + def __call__(self): + return super().__call__() + + +#: Cursor Position [row;column] (default = [1,1]) +CUP = CSI('H', 1, 1) + +#: Cursor Up Ps Times (default = 1) (CUU) +UP = CSI('A', 1) + +#: Cursor Down Ps Times (default = 1) (CUD) +DOWN = CSI('B', 1) + +#: Cursor Forward Ps Times (default = 1) (CUF) +RIGHT = CSI('C', 1) + +#: Cursor Backward Ps Times (default = 1) (CUB) +LEFT = CSI('D', 1) + +#: Cursor Next Line Ps Times (default = 1) (CNL) +#: Same as Cursor Down Ps Times +NEXT_LINE = CSI('E', 1) + +#: Cursor Preceding Line Ps Times (default = 1) (CPL) +#: Same as Cursor Up Ps Times +PREVIOUS_LINE = CSI('F', 1) + +#: Cursor Character Absolute [column] (default = [row,1]) (CHA) +COLUMN = CSI('G', 1) + +#: Erase in Display (ED) +CLEAR_SCREEN = CSI('J', 0) + +#: Erase till end of screen +CLEAR_SCREEN_TILL_END = CSINoArg('0J') + +#: Erase till start of screen +CLEAR_SCREEN_TILL_START = CSINoArg('1J') + +#: Erase whole screen +CLEAR_SCREEN_ALL = CSINoArg('2J') + +#: Erase whole screen and history +CLEAR_SCREEN_ALL_AND_HISTORY = CSINoArg('3J') + +#: Erase in Line (EL) +CLEAR_LINE_ALL = CSI('K') + +#: Erase in Line from Cursor to End of Line (default) +CLEAR_LINE_RIGHT = CSINoArg('0K') + +#: Erase in Line from Cursor to Beginning of Line +CLEAR_LINE_LEFT = CSINoArg('1K') + +#: Erase Line containing Cursor +CLEAR_LINE = CSINoArg('2K') + +#: Scroll up Ps lines (default = 1) (SU) +#: Scroll down Ps lines (default = 1) (SD) +SCROLL_UP = CSI('S') +SCROLL_DOWN = CSI('T') + +#: Save Cursor Position (SCP) +SAVE_CURSOR = CSINoArg('s') + +#: Restore Cursor Position (RCP) +RESTORE_CURSOR = CSINoArg('u') + +#: Cursor Visibility (DECTCEM) +HIDE_CURSOR = CSINoArg('?25l') +SHOW_CURSOR = CSINoArg('?25h') + + +# +# UP = CSI + '{n}A' # Cursor Up +# DOWN = CSI + '{n}B' # Cursor Down +# RIGHT = CSI + '{n}C' # Cursor Forward +# LEFT = CSI + '{n}D' # Cursor Backward +# NEXT = CSI + '{n}E' # Cursor Next Line +# PREV = CSI + '{n}F' # Cursor Previous Line +# MOVE_COLUMN = CSI + '{n}G' # Cursor Horizontal Absolute +# MOVE = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [ +# 1,1]) +# +# CLEAR = CSI + '{n}J' # Clear (part of) the screen +# CLEAR_BOTTOM = CLEAR.format(n=0) # Clear from cursor to end of screen +# CLEAR_TOP = CLEAR.format(n=1) # Clear from cursor to beginning of screen +# CLEAR_SCREEN = CLEAR.format(n=2) # Clear Screen +# CLEAR_WIPE = CLEAR.format(n=3) # Clear Screen and scrollback buffer +# +# CLEAR_LINE = CSI + '{n}K' # Erase in Line +# CLEAR_LINE_RIGHT = CLEAR_LINE.format(n=0) # Clear from cursor to end of line +# CLEAR_LINE_LEFT = CLEAR_LINE.format(n=1) # Clear from cursor to beginning +# of line +# CLEAR_LINE_ALL = CLEAR_LINE.format(n=2) # Clear Line + +def clear_line(n): + return UP(n) + CLEAR_LINE_ALL() + DOWN(n) class ColorSupport(enum.Enum): @@ -75,96 +192,6 @@ def column(self, stream): return column -DSR = CSI + '{n}n' # Device Status Report (DSR) -CPR = _CPR(DSR.format(n=6)) - -IL = CSI + '{n}L' # Insert n Line(s) (default = 1) - -DECRST = CSI + '?{n}l' # DEC Private Mode Reset -DECRTCEM = DECRST.format(n=25) # Hide Cursor - -DECSET = CSI + '?{n}h' # DEC Private Mode Set -DECTCEM = DECSET.format(n=25) # Show Cursor - - -# possible values: -# 0 = Normal (default) -# 1 = Bold -# 2 = Faint -# 3 = Italic -# 4 = Underlined -# 5 = Slow blink (appears as Bold) -# 6 = Rapid Blink -# 7 = Inverse -# 8 = Invisible, i.e., hidden (VT300) -# 9 = Strike through -# 10 = Primary (default) font -# 20 = Gothic Font -# 21 = Double underline -# 22 = Normal intensity (neither bold nor faint) -# 23 = Not italic -# 24 = Not underlined -# 25 = Steady (not blinking) -# 26 = Proportional spacing -# 27 = Not inverse -# 28 = Visible, i.e., not hidden (VT300) -# 29 = No strike through -# 30 = Set foreground color to Black -# 31 = Set foreground color to Red -# 32 = Set foreground color to Green -# 33 = Set foreground color to Yellow -# 34 = Set foreground color to Blue -# 35 = Set foreground color to Magenta -# 36 = Set foreground color to Cyan -# 37 = Set foreground color to White -# 39 = Set foreground color to default (original) -# 40 = Set background color to Black -# 41 = Set background color to Red -# 42 = Set background color to Green -# 43 = Set background color to Yellow -# 44 = Set background color to Blue -# 45 = Set background color to Magenta -# 46 = Set background color to Cyan -# 47 = Set background color to White -# 49 = Set background color to default (original). -# 50 = Disable proportional spacing -# 51 = Framed -# 52 = Encircled -# 53 = Overlined -# 54 = Neither framed nor encircled -# 55 = Not overlined -# 58 = Set underine color (2;r;g;b) -# 59 = Default underline color -# If 16-color support is compiled, the following apply. -# Assume that xterm’s resources are set so that the ISO color codes are the -# first 8 of a set of 16. Then the aixterm colors are the bright versions of -# the ISO colors: -# 90 = Set foreground color to Black -# 91 = Set foreground color to Red -# 92 = Set foreground color to Green -# 93 = Set foreground color to Yellow -# 94 = Set foreground color to Blue -# 95 = Set foreground color to Magenta -# 96 = Set foreground color to Cyan -# 97 = Set foreground color to White -# 100 = Set background color to Black -# 101 = Set background color to Red -# 102 = Set background color to Green -# 103 = Set background color to Yellow -# 104 = Set background color to Blue -# 105 = Set background color to Magenta -# 106 = Set background color to Cyan -# 107 = Set background color to White -# -# If xterm is compiled with the 16-color support disabled, it supports the -# following, from rxvt: -# 100 = Set foreground and background color to default - -# If 88- or 256-color support is compiled, the following apply. -# 38;5;x = Set foreground color to x -# 48;5;x = Set background color to x - - class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): __slots__ = () @@ -299,10 +326,10 @@ def register( return color -class SGR: +class SGR(CSI): _start_code: int _end_code: int - _template = CSI + '{n}m' + _code = 'm' __slots__ = '_start_code', '_end_code' def __init__(self, start_code: int, end_code: int): @@ -311,11 +338,11 @@ def __init__(self, start_code: int, end_code: int): @property def _start_template(self): - return self._template.format(n=self._start_code) + return super().__call__(self._start_code) @property def _end_template(self): - return self._template.format(n=self._end_code) + return super().__call__(self._end_code) def __call__(self, text): return self._start_template + text + self._end_template @@ -323,7 +350,6 @@ def __call__(self, text): class SGRColor(SGR): __slots__ = '_color', '_start_code', '_end_code' - _color_template = CSI + '{n};{color}m' def __init__(self, color: Color, start_code: int, end_code: int): self._color = color @@ -331,10 +357,7 @@ def __init__(self, color: Color, start_code: int, end_code: int): @property def _start_template(self): - return self._color_template.format( - n=self._start_code, - color=self._color.ansi - ) + return CSI.__call__(self, self._start_code, self._color.ansi) encircled = SGR(52, 54) diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py new file mode 100644 index 00000000..c0ccff4c --- /dev/null +++ b/progressbar/terminal/stream.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import sys +from types import TracebackType +from typing import Iterable, Iterator, Type + +from progressbar import base + + +class TextIOOutputWrapper(base.TextIO): + + def __init__(self, stream: base.TextIO): + self.stream = stream + + def close(self) -> None: + self.stream.close() + + def fileno(self) -> int: + return self.stream.fileno() + + def flush(self) -> None: + pass + + def isatty(self) -> bool: + return self.stream.isatty() + + def read(self, __n: int = -1) -> str: + return self.stream.read(__n) + + def readable(self) -> bool: + return self.stream.readable() + + def readline(self, __limit: int = -1) -> str: + return self.stream.readline(__limit) + + def readlines(self, __hint: int = ...) -> list[str]: + return self.stream.readlines(__hint) + + def seek(self, __offset: int, __whence: int = ...) -> int: + return self.stream.seek(__offset, __whence) + + def seekable(self) -> bool: + return self.stream.seekable() + + def tell(self) -> int: + return self.stream.tell() + + def truncate(self, __size: int | None = ...) -> int: + return self.stream.truncate(__size) + + def writable(self) -> bool: + return self.stream.writable() + + def writelines(self, __lines: Iterable[str]) -> None: + return self.stream.writelines(__lines) + + def __next__(self) -> str: + return self.stream.__next__() + + def __iter__(self) -> Iterator[str]: + return self.stream.__iter__() + + def __exit__( + self, + __t: Type[BaseException] | None, + __value: BaseException | None, + __traceback: TracebackType | None + ) -> None: + return self.stream.__exit__(__t, __value, __traceback) + + def __enter__(self) -> base.TextIO: + return self.stream.__enter__() + + +class LineOffsetStreamWrapper(TextIOOutputWrapper): + UP = '\033[F' + DOWN = '\033[B' + + def __init__(self, lines=0, stream=sys.stderr): + self.lines = lines + super().__init__(stream) + + def write(self, data): + # Move the cursor up + self.stream.write(self.UP * self.lines) + # Print a carriage return to reset the cursor position + self.stream.write('\r') + # Print the data without newlines so we don't change the position + self.stream.write(data.rstrip('\n')) + # Move the cursor down + self.stream.write(self.DOWN * self.lines) + + self.flush() + + +class LastLineStream(TextIOOutputWrapper): + + line: str = '' + + def seekable(self) -> bool: + return False + + def readable(self) -> bool: + return True + + def read(self, __n: int = -1) -> str: + return self.line[:__n] + + def readline(self, __limit: int = -1) -> str: + return self.line[:__limit] + + def write(self, data): + self.line = data + + def truncate(self, __size: int | None = None) -> int: + if __size is None: + self.line = '' + else: + self.line = self.line[:__size] + + return len(self.line) + + def writelines(self, __lines: Iterable[str]) -> None: + line = '' + # Walk through the lines and take the last one + for line in __lines: + pass + + self.line = line From f0533d65d8355e2abbe1e744b7af4381b5f6998d Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 23 Jan 2023 15:40:55 +0100 Subject: [PATCH 080/118] added example for new multithreaded parallel progressbars --- README.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.rst b/README.rst index 26695e96..6c01f2a8 100644 --- a/README.rst +++ b/README.rst @@ -152,6 +152,38 @@ In most cases the following will work as well, as long as you initialize the logging.error('Got %d', i) time.sleep(0.2) +Multiple (threaded) progressbars +============================================================================== + +.. code:: python + + import random + import threading + import time + + import progressbar + + BARS = 5 + N = 50 + + + def do_something(bar): + for i in bar(range(N)): + # Sleep up to 0.1 seconds + time.sleep(random.random() * 0.1) + + # print messages at random intervals to show how extra output works + if random.random() > 0.9: + bar.print('random message for bar', bar, i) + + + with progressbar.MultiBar() as multibar: + for i in range(BARS): + # Get a progressbar + bar = multibar[f'Thread label here {i}'] + # Create a thread and pass the progressbar + threading.Thread(target=do_something, args=(bar,)).start() + Context wrapper ============================================================================== .. code:: python From 803d72a434a88418ec5711daad034f628bba19a9 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 28 Jan 2023 03:25:10 +0100 Subject: [PATCH 081/118] Adding a splash of colour --- progressbar/bar.py | 39 +++++-- progressbar/terminal/base.py | 180 ++++++++++++++++++++++++++++++--- progressbar/terminal/colors.py | 40 +++++++- progressbar/widgets.py | 60 +++++++++-- tox.ini | 7 +- 5 files changed, 291 insertions(+), 35 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index b2826652..126dff97 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -18,7 +18,7 @@ import progressbar.terminal.stream from . import ( base, - utils, + terminal, utils, widgets, widgets as widgets_module, # Avoid name collision ) @@ -152,15 +152,23 @@ class DefaultFdMixin(ProgressBarMixinBase): #: Whether to print line breaks. This is useful for logging the #: progressbar. When disabled the current line is overwritten. line_breaks: bool = True - #: Enable or disable colors. Defaults to auto detection - enable_colors: bool = False + # : Specify the type and number of colors to support. Defaults to auto + # detection based on the file descriptor type (i.e. interactive terminal) + # environment variables such as `COLORTERM` and `TERM`. Color output can + # be forced in non-interactive terminals using the + # `PROGRESSBAR_ENABLE_COLORS` environment variable which can also be used + # to force a specific number of colors by specifying `24bit`, `256` or `16`. + # For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. + # For 256 color support you can use `TERM=xterm-256color`. + # For 16 colorsupport you can use `TERM=xterm`. + enable_colors: terminal.ColorSupport | bool | None = terminal.color_support def __init__( self, fd: base.IO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, - enable_colors: bool | None = None, + enable_colors: terminal.ColorSupport | None = None, line_offset: int = 0, **kwargs, ): @@ -195,11 +203,28 @@ def __init__( # Check if ANSI escape characters are enabled (suitable for iteractive # terminals), or should be stripped off (suitable for log files) if enable_colors is None: - enable_colors = utils.env_flag( - 'PROGRESSBAR_ENABLE_COLORS', self.is_ansi_terminal + colors = ( + utils.env_flag('PROGRESSBAR_ENABLE_COLORS'), + utils.env_flag('FORCE_COLOR'), + self.is_ansi_terminal, ) - self.enable_colors = bool(enable_colors) + for color_enabled in colors: + if color_enabled is not None: + if color_enabled: + enable_colors = terminal.color_support + else: + enable_colors = terminal.ColorSupport.NONE + break + + elif enable_colors is True: + enable_colors = terminal.color_support + elif enable_colors is False: + enable_colors = terminal.ColorSupport.NONE + elif enable_colors not in terminal.ColorSupport: + raise ValueError(f'Invalid color support value: {enable_colors}') + + self.enable_colors = enable_colors ProgressBarMixinBase.__init__(self, **kwargs) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 95c46307..dacf404c 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -1,5 +1,6 @@ from __future__ import annotations +import abc import collections import colorsys import enum @@ -7,7 +8,7 @@ import threading from collections import defaultdict -from python_utils import types +from python_utils import converters, types from .os_specific import getch @@ -134,24 +135,54 @@ def clear_line(n): return UP(n) + CLEAR_LINE_ALL() + DOWN(n) -class ColorSupport(enum.Enum): +class ColorSupport(enum.IntEnum): '''Color support for the terminal.''' NONE = 0 - XTERM = 1 - XTERM_256 = 2 - XTERM_TRUECOLOR = 3 + XTERM = 16 + XTERM_256 = 256 + XTERM_TRUECOLOR = 16777216 @classmethod def from_env(cls): - '''Get the color support from the environment.''' - if os.getenv('COLORTERM') == 'truecolor': + '''Get the color support from the environment. + + If any of the environment variables contain `24bit` or `truecolor`, + we will enable true color/24 bit support. If they contain `256`, we + will enable 256 color/8 bit support. If they contain `xterm`, we will + enable 16 color support. Otherwise, we will assume no color support. + + If `JUPYTER_COLUMNS` or `JUPYTER_LINES` is set, we will assume true + color support. + + Note that the highest available value will be used! Having + `COLORTERM=truecolor` will override `TERM=xterm-256color`. + ''' + variables = ( + 'FORCE_COLOR', + 'PROGRESSBAR_ENABLE_COLORS', + 'COLORTERM', + 'TERM', + ) + + if os.environ.get('JUPYTER_COLUMNS') or os.environ.get('JUPYTER_LINES'): + # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR - elif os.getenv('TERM') == 'xterm-256color': - return cls.XTERM_256 - elif os.getenv('TERM') == 'xterm': - return cls.XTERM - else: - return cls.NONE + + support = cls.NONE + for variable in variables: + value = os.environ.get(variable) + if value is None: + continue + elif value in {'truecolor', '24bit'}: + # Truecolor support, we don't need to check anything else. + support = cls.XTERM_TRUECOLOR + break + elif '256' in value: + support = max(cls.XTERM_256, support) + elif value == 'xterm': + support = max(cls.XTERM, support) + + return support color_support = ColorSupport.from_env() @@ -196,6 +227,10 @@ class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): __slots__ = () def __str__(self): + return self.rgb + + @property + def rgb(self): return f'rgb({self.red}, {self.green}, {self.blue})' @property @@ -217,6 +252,13 @@ def to_ansi_256(self): blue = round(self.blue / 255 * 5) return 16 + 36 * red + 6 * green + blue + def interpolate(self, end: RGB, step: float) -> RGB: + return RGB( + int(self.red + (end.red - self.red) * step), + int(self.green + (end.green - self.green) * step), + int(self.blue + (end.blue - self.blue) * step), + ) + class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): __slots__ = () @@ -227,6 +269,18 @@ def from_rgb(cls, rgb: RGB) -> HLS: *colorsys.rgb_to_hls(rgb.red / 255, rgb.green / 255, rgb.blue / 255) ) + def interpolate(self, end: HLS, step: float) -> HLS: + return HLS( + self.hue + (end.hue - self.hue) * step, + self.lightness + (end.lightness - self.lightness) * step, + self.saturation + (end.saturation - self.saturation) * step, + ) + + +class ColorBase(abc.ABC): + + def get_color(self, value: float) -> Color: + raise NotImplementedError() class Color( collections.namedtuple( @@ -236,7 +290,8 @@ class Color( 'name', 'xterm', ] - ) + ), + ColorBase, ): ''' Color base class @@ -251,6 +306,9 @@ class Color( ''' __slots__ = () + def __call__(self, value: str) -> str: + return self.fg(value) + @property def fg(self): return SGRColor(self, 38, 39) @@ -279,6 +337,14 @@ def ansi(self) -> types.Optional[str]: return f'5;{color}' + def interpolate(self, end: Color, step: float) -> Color: + return Color( + self.rgb.interpolate(end.rgb, step), + self.hls.interpolate(end.hls, step), + self.name if step < 0.5 else end.name, + self.xterm if step < 0.5 else end.xterm, + ) + def __str__(self): return self.name @@ -325,6 +391,92 @@ def register( return color + @classmethod + def interpolate(cls, color_a: Color, color_b: Color, step: float) -> Color: + return color_a.interpolate(color_b, step) + + +class ColorGradient(ColorBase): + def __init__( + self, + *colors: Color, + interpolate=Colors.interpolate + ): + assert colors + self.colors = colors + self.interpolate = interpolate + + def __call__(self, value: float): + return self.get_color(value) + + def get_color(self, value: float) -> Color: + 'Map a value from 0 to 1 to a color' + if value <= 0: + return self.colors[0] + elif value >= 1: + return self.colors[-1] + + max_color_idx = len(self.colors) - 1 + if max_color_idx == 0: + return self.colors[0] + elif self.interpolate: + index = round(converters.remap(value, 0, 1, 0, max_color_idx - 1)) + step = converters.remap( + value, + index / (max_color_idx), + (index + 1) / (max_color_idx), + 0, + 1, + ) + color = self.interpolate( + self.colors[index], + self.colors[index + 1], + float(step), + ) + else: + index = round(converters.remap(value, 0, 1, 0, max_color_idx)) + color = self.colors[index] + + return color + + +OptionalColor = Color | ColorGradient | None + + +def get_color(value: float, color: OptionalColor) -> Color | None: + if isinstance(color, ColorGradient): + color = color(value) + return color + + +def apply_colors( + text: str, + value: float | None = None, + *, + fg: OptionalColor = None, + bg: OptionalColor = None, + fg_none: Color | None = None, + bg_none: Color | None = None, +) -> str: + if fg is None and bg is None: + return text + + if value is None: + if fg_none is not None: + text = fg_none.fg(text) + if bg_none is not None: + text = bg_none.bg(text) + else: + fg = get_color(value, fg) + bg = get_color(value, bg) + + if fg is not None: + text = fg.fg(text) + if bg is not None: + text = bg.bg(text) + + return text + class SGR(CSI): _start_code: int diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index ed726aea..dacbac28 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -1,6 +1,7 @@ # Based on: https://www.ditig.com/256-colors-cheat-sheet -from progressbar.terminal import base -from progressbar.terminal.base import Colors, HLS, RGB +import os + +from progressbar.terminal.base import ColorGradient, Colors, HLS, RGB black = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Black', 0) maroon = Colors.register(RGB(128, 0, 0), HLS(100, 0, 25), 'Maroon', 1) @@ -939,9 +940,44 @@ grey89 = Colors.register(RGB(228, 228, 228), HLS(0, 0, 89), 'Grey89', 254) grey93 = Colors.register(RGB(238, 238, 238), HLS(0, 0, 93), 'Grey93', 255) +dark_gradient = ColorGradient( + red1, + orangeRed1, + darkOrange, + orange1, + yellow1, + yellow2, + greenYellow, + green1, +) +light_gradient = ColorGradient( + red1, + orangeRed1, + darkOrange, + orange1, + gold3, + darkOliveGreen3, + yellow4, + green3, +) +bg_gradient = ColorGradient(black) + +# Check if the background is light or dark. This is by no means a foolproof +# method, but there is no reliable way to detect this. +if os.environ.get('COLORFGBG', '15;0').split(';')[-1] == str(white.xterm): + print('light background') + # Light background + gradient = light_gradient +else: + print('dark background') + # Default, expect a dark background + gradient = dark_gradient + if __name__ == '__main__': red = Colors.register(RGB(255, 128, 128)) # red = Colors.register(RGB(255, 100, 100)) + from progressbar.terminal import base + for i in base.ColorSupport: base.color_support = i print(i, red.fg('RED!'), red.bg('RED!'), red.underline('RED!')) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index b13d9f33..c4897e17 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -10,7 +10,8 @@ from python_utils import converters, types -from . import base, utils +from . import base, terminal, utils +from .terminal import colors if types.TYPE_CHECKING: from .bar import ProgressBarMixinBase @@ -353,8 +354,9 @@ def __init__( ): self.samples = samples self.key_prefix = ( - key_prefix if key_prefix else self.__class__.__name__ - ) + '_' + key_prefix if key_prefix else + self.__class__.__name__ + ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): @@ -671,7 +673,6 @@ class AnimatedMarker(TimeSensitiveWidgetBase): '''An animated marker for the progress bar which defaults to appear as if it were rotating. ''' - def __init__( self, markers='|/-\\', @@ -739,6 +740,10 @@ def __call__( class Percentage(FormatWidgetMixin, WidgetBase): '''Displays the current percentage as a number with a percent sign.''' + fg_na: terminal.Color | None = colors.yellow + fg_value: terminal.OptionalColor | None = colors.gradient + bg_na: terminal.Color | None = colors.black + bg_value: terminal.OptionalColor | None = None def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): self.na = na @@ -751,13 +756,28 @@ def get_format( # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: - return self.na - - return FormatWidgetMixin.get_format(self, progress, data, format) + output = self.na + value = None + else: + value = percentage / 100 + output = FormatWidgetMixin.get_format(self, progress, data, format) + + return terminal.apply_colors( + output, + value, + fg=self.fg_value, + bg=self.bg_value, + fg_none=self.fg_na, + bg_none=self.bg_na, + ) class SimpleProgress(FormatWidgetMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' + fg_na: terminal.Color | None = colors.yellow + fg_value: terminal.OptionalColor | None = colors.gradient + bg_na: terminal.Color | None = colors.black + bg_value: terminal.OptionalColor | None = None max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], @@ -777,9 +797,11 @@ def __call__( ): # If max_value is not available, display N/A if data.get('max_value'): - data['max_value_s'] = data.get('max_value') + data['max_value_s'] = data['max_value'] + value = data.get('value', 0) / data['max_value'] else: data['max_value_s'] = 'N/A' + value = None # if value is not available it's the zeroth iteration if data.get('value'): @@ -817,11 +839,20 @@ def __call__( if max_width: # pragma: no branch formatted = formatted.rjust(max_width) - return formatted + return terminal.apply_colors( + formatted, + value, + fg=self.fg_value, + bg=self.bg_value, + fg_none=self.fg_na, + bg_none=self.bg_na, + ) class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' + fg_value: terminal.OptionalColor | None = colors.gradient + bg_value: terminal.OptionalColor | None = None def __init__( self, @@ -869,11 +900,22 @@ def __call__( # Make sure we ignore invisible characters when filling width += len(marker) - progress.custom_len(marker) + if marker and width > 0: + value = len(marker) / width + else: + value = 0 + if self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) + marker = terminal.apply_colors( + marker, + value, + fg=self.fg_value, + bg=self.bg_value, + ) return left + marker + right diff --git a/tox.ini b/tox.ini index 99be8934..3472275b 100644 --- a/tox.ini +++ b/tox.ini @@ -39,9 +39,11 @@ deps = black commands = black --skip-string-normalization --line-length 79 {toxinidir}/progressbar [testenv:docs] -changedir = basepython = python3 deps = -r{toxinidir}/docs/requirements.txt +allowlist_externals = + rm + mkdir whitelist_externals = rm cd @@ -51,8 +53,7 @@ commands = mkdir -p docs/_static sphinx-apidoc -e -o docs/ progressbar rm -f docs/modules.rst - rm -f docs/progressbar.rst - sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html {posargs} + sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html {posargs} [flake8] ignore = W391, W504, E741, W503, E131 From 1c432ff584af57bed6fe59afd3eab9c27ea065c3 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 28 Jan 2023 03:31:21 +0100 Subject: [PATCH 082/118] fixed docs build --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 3472275b..3ffed050 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,7 @@ deps = black commands = black --skip-string-normalization --line-length 79 {toxinidir}/progressbar [testenv:docs] +changedir = basepython = python3 deps = -r{toxinidir}/docs/requirements.txt allowlist_externals = From 58724abfa2c75445660306c58c570dc2a89b1969 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 28 Jan 2023 22:23:03 +0100 Subject: [PATCH 083/118] Improved colour support --- progressbar/bar.py | 2 +- progressbar/terminal/base.py | 11 ++++--- progressbar/terminal/colors.py | 4 +-- progressbar/widgets.py | 60 ++++++++++++++++++++-------------- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 126dff97..e1aa2bc6 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -610,7 +610,7 @@ def init(self): self._last_update_timer = timeit.default_timer() @property - def percentage(self): + def percentage(self) -> float | None: '''Return current percentage, returns None if no max_value is given >>> progress = ProgressBar() diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index dacf404c..afa5c8dd 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -11,6 +11,7 @@ from python_utils import converters, types from .os_specific import getch +from .. import base ESC = '\x1B' @@ -411,7 +412,7 @@ def __call__(self, value: float): def get_color(self, value: float) -> Color: 'Map a value from 0 to 1 to a color' - if value <= 0: + if value is base.Undefined or value is base.UnknownLength or value <= 0: return self.colors[0] elif value >= 1: return self.colors[-1] @@ -451,7 +452,7 @@ def get_color(value: float, color: OptionalColor) -> Color | None: def apply_colors( text: str, - value: float | None = None, + percentage: float | None = None, *, fg: OptionalColor = None, bg: OptionalColor = None, @@ -461,14 +462,14 @@ def apply_colors( if fg is None and bg is None: return text - if value is None: + if percentage is None: if fg_none is not None: text = fg_none.fg(text) if bg_none is not None: text = bg_none.bg(text) else: - fg = get_color(value, fg) - bg = get_color(value, bg) + fg = get_color(percentage * 0.01, fg) + bg = get_color(percentage * 0.01, bg) if fg is not None: text = fg.fg(text) diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index dacbac28..5024a3bc 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -965,13 +965,13 @@ # Check if the background is light or dark. This is by no means a foolproof # method, but there is no reliable way to detect this. if os.environ.get('COLORFGBG', '15;0').split(';')[-1] == str(white.xterm): - print('light background') # Light background gradient = light_gradient + primary = black else: - print('dark background') # Default, expect a dark background gradient = dark_gradient + primary = white if __name__ == '__main__': red = Colors.register(RGB(255, 128, 128)) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index c4897e17..5227744c 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -46,7 +46,7 @@ def create_wrapper(wrapper): >>> print(create_wrapper(('a', 'b'))) a{}b ''' - if isinstance(wrapper, tuple) and len(wrapper) == 2: + if isinstance(wrapper, tuple) and utils.len_color(wrapper) == 2: a, b = wrapper wrapper = (a or '') + '{}' + (b or '') elif not wrapper: @@ -392,7 +392,7 @@ def __call__( sample_times.pop(0) sample_values.pop(0) else: - if len(sample_times) > self.samples: + if progress.custom_len(sample_times) > self.samples: sample_times.pop(0) sample_values.pop(0) @@ -673,6 +673,7 @@ class AnimatedMarker(TimeSensitiveWidgetBase): '''An animated marker for the progress bar which defaults to appear as if it were rotating. ''' + def __init__( self, markers='|/-\\', @@ -696,7 +697,7 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): if progress.end_time: return self.default - marker = self.markers[data['updates'] % len(self.markers)] + marker = self.markers[data['updates'] % utils.len_color(self.markers)] if self.marker_wrap: marker = self.marker_wrap.format(marker) @@ -745,6 +746,9 @@ class Percentage(FormatWidgetMixin, WidgetBase): bg_na: terminal.Color | None = colors.black bg_value: terminal.OptionalColor | None = None + def _uses_colors(self): + return self.fg_na or self.fg_value or self.bg_na or self.bg_value + def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): self.na = na FormatWidgetMixin.__init__(self, format=format, **kwargs) @@ -757,14 +761,12 @@ def get_format( percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: output = self.na - value = None else: - value = percentage / 100 output = FormatWidgetMixin.get_format(self, progress, data, format) return terminal.apply_colors( output, - value, + data.get('percentage'), fg=self.fg_value, bg=self.bg_value, fg_none=self.fg_na, @@ -798,10 +800,8 @@ def __call__( # If max_value is not available, display N/A if data.get('max_value'): data['max_value_s'] = data['max_value'] - value = data.get('value', 0) / data['max_value'] else: data['max_value_s'] = 'N/A' - value = None # if value is not available it's the zeroth iteration if data.get('value'): @@ -841,7 +841,7 @@ def __call__( return terminal.apply_colors( formatted, - value, + data.get('percentage'), fg=self.fg_value, bg=self.bg_value, fg_none=self.fg_na, @@ -898,25 +898,28 @@ def __call__( fill = converters.to_unicode(self.fill(progress, data, width)) # Make sure we ignore invisible characters when filling - width += len(marker) - progress.custom_len(marker) - - if marker and width > 0: - value = len(marker) / width - else: - value = 0 + width += utils.len_color(marker) - progress.custom_len(marker) if self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) - marker = terminal.apply_colors( - marker, - value, + marker = self.apply_colors(progress, data, marker) + return left + marker + right + + def apply_colors( + self, + progress: ProgressBarMixinBase, + data: Data, output: str + ): + output = terminal.apply_colors( + output, + percentage=data.get('percentage'), fg=self.fg_value, bg=self.bg_value, ) - return left + marker + right + return output class ReverseBar(Bar): @@ -1023,7 +1026,7 @@ class VariableMixin: def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError('Variable(): argument must be a string') - if len(name.split()) > 1: + if utils.len_color(name.split()) > 1: raise ValueError('Variable(): argument must be single word') self.name = name @@ -1103,7 +1106,7 @@ def __init__( ) def get_values(self, progress: ProgressBarMixinBase, data: Data): - ranges = [0.0] * len(self.markers) + ranges = [0.0] * progress.custom_len(self.markers) for value in data['variables'][self.name] or []: if not isinstance(value, (int, float)): # Progress is (value, max) @@ -1116,7 +1119,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): % value ) - range_ = value * (len(ranges) - 1) + range_ = value * (progress.custom_len(ranges) - 1) pos = int(range_) frac = range_ % 1 ranges[pos] += 1 - frac @@ -1200,14 +1203,16 @@ def __call__( marker = self.markers[-1] * int(num_chars) - marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) + marker_idx = int( + (num_chars % 1) * (progress.custom_len(self.markers) - 1) + ) if marker_idx: marker += self.markers[marker_idx] marker = converters.to_unicode(marker) # Make sure we ignore invisible characters when filling - width += len(marker) - progress.custom_len(marker) + width += progress.custom_len(marker) - progress.custom_len(marker) marker = marker.ljust(width, self.markers[0]) return left + marker + right @@ -1234,7 +1239,12 @@ def __call__( # type: ignore center_len = progress.custom_len(center) center_left = int((width - center_len) / 2) center_right = center_left + center_len - return bar[:center_left] + center + bar[center_right:] + + return bar[:center_left] + center + self.apply_colors( + progress, + data, + bar[center_right:] + ) class PercentageLabelBar(Percentage, FormatLabelBar): From ff65a296b3483ad37714a404d777090e3f6d1d3a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 29 Jan 2023 15:57:56 +0100 Subject: [PATCH 084/118] Updated readme to add new PyCharm details --- README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 6c01f2a8..d76756c1 100644 --- a/README.rst +++ b/README.rst @@ -75,10 +75,8 @@ automatically enable features like auto-resizing when the system supports it. Known issues ****************************************************************************** -Due to limitations in both the IDLE shell and the Jetbrains (Pycharm) shells this progressbar cannot function properly within those. - +- The Jetbrains (PyCharm, etc) editors work out of the box, but for more advanced features such as the `MultiBar` support you will need to enable the "Enable terminal in output console" checkbox in the Run dialog. - The IDLE editor doesn't support these types of progress bars at all: https://bugs.python.org/issue23220 -- The Jetbrains (Pycharm) editors partially work but break with fast output. As a workaround make sure you only write to either `sys.stdout` (regular print) or `sys.stderr` at the same time. If you do plan to use both, make sure you wait about ~200 milliseconds for the next output or it will break regularly. Linked issue: https://github.com/WoLpH/python-progressbar/issues/115 - Jupyter notebooks buffer `sys.stdout` which can cause mixed output. This issue can be resolved easily using: `import sys; sys.stdout.flush()`. Linked issue: https://github.com/WoLpH/python-progressbar/issues/173 ****************************************************************************** From 5d3b7caf22194ad66c063669a5f1d0108be1283f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 2 Feb 2023 09:22:04 +0100 Subject: [PATCH 085/118] Fixed several tests and a few bugs --- progressbar/__init__.py | 3 +- progressbar/bar.py | 13 ++-- progressbar/base.py | 2 +- progressbar/multi.py | 11 +-- progressbar/terminal/base.py | 1 + progressbar/utils.py | 4 +- progressbar/widgets.py | 145 ++++++++++++++++++++--------------- tests/test_color.py | 39 ++++++++++ tests/test_flush.py | 1 + tests/test_multibar.py | 84 ++++++++++++++++++++ tests/test_progressbar.py | 3 + 11 files changed, 231 insertions(+), 75 deletions(-) create mode 100644 tests/test_color.py diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 117124c2..e43c4cf6 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,8 +35,8 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin -from .multi import MultiBar from .terminal.stream import LineOffsetStreamWrapper +from .multi import SortKey, MultiBar __date__ = str(date.today()) __all__ = [ @@ -76,4 +76,5 @@ '__author__', '__version__', 'MultiBar', + 'SortKey', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index e1aa2bc6..9208333f 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -118,11 +118,11 @@ def __del__(self): def __getstate__(self): return self.__dict__ - def data(self) -> types.Dict[str, types.Any]: + def data(self) -> types.Dict[str, types.Any]: # pragma: no cover raise NotImplementedError() def started(self) -> bool: - return self._started or self._finished + return self._finished or self._started def finished(self) -> bool: return self._finished @@ -209,7 +209,7 @@ def __init__( self.is_ansi_terminal, ) - for color_enabled in colors: + for color_enabled in colors: # pragma: no branch if color_enabled is not None: if color_enabled: enable_colors = terminal.color_support @@ -218,10 +218,13 @@ def __init__( break elif enable_colors is True: - enable_colors = terminal.color_support + enable_colors = terminal.ColorSupport.XTERM_256 elif enable_colors is False: enable_colors = terminal.ColorSupport.NONE - elif enable_colors not in terminal.ColorSupport: + elif isinstance(enable_colors, terminal.ColorSupport): + # `enable_colors` is already a valid value + pass + else: raise ValueError(f'Invalid color support value: {enable_colors}') self.enable_colors = enable_colors diff --git a/progressbar/base.py b/progressbar/base.py index 32a95783..8e007914 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -23,7 +23,7 @@ class Undefined(metaclass=FalseMeta): try: # pragma: no cover IO = types.IO # type: ignore TextIO = types.TextIO # type: ignore -except AttributeError: +except AttributeError: # pragma: no cover from typing.io import IO, TextIO # type: ignore assert IO is not None diff --git a/progressbar/multi.py b/progressbar/multi.py index d4d13979..3e4a93de 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -124,7 +124,7 @@ def __init__( def __setitem__(self, key: str, value: bar.ProgressBar): '''Add a progressbar to the multibar''' - if value.label != key: + if value.label != key: # pragma: no branch value.label = key value.fd = stream.LastLineStream(self.fd) value.paused = True @@ -153,16 +153,16 @@ def __getitem__(self, item): return progress def _label_bar(self, bar: bar.ProgressBar): - if bar in self._labeled: + if bar in self._labeled: # pragma: no branch return assert bar.widgets, 'Cannot prepend label to empty progressbar' self._labeled.add(bar) - if self.prepend_label: + if self.prepend_label: # pragma: no branch bar.widgets.insert(0, self.label_format.format(label=bar.label)) - if self.append_label and bar not in self._labeled: + if self.append_label and bar not in self._labeled: # pragma: no branch bar.widgets.append(self.label_format.format(label=bar.label)) def render(self, flush: bool = True, force: bool = False): @@ -300,7 +300,8 @@ def run(self, join=True): time.sleep(self.update_interval) if join or self._thread_closed.is_set(): - # If the thread is closed, we need to check if force progressbars + # If the thread is closed, we need to check if force + # progressbars # have finished. If they have, we can exit the loop for bar_ in self.values(): if not bar_.finished(): diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index afa5c8dd..366373ee 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -458,6 +458,7 @@ def apply_colors( bg: OptionalColor = None, fg_none: Color | None = None, bg_none: Color | None = None, + **kwargs: types.Any, ) -> str: if fg is None and bg is None: return text diff --git a/progressbar/utils.py b/progressbar/utils.py index 7f84a91d..256dd98c 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -157,8 +157,10 @@ def no_color(value: StringT) -> StringT: if isinstance(value, bytes): pattern: bytes = '\\\u001b\\[.*?[@-~]'.encode() return re.sub(pattern, b'', value) # type: ignore - else: + elif isinstance(value, str): return re.sub(u'\x1b\\[.*?[@-~]', '', value) # type: ignore + else: + raise TypeError('`value` must be a string or bytes, got %r' % value) def len_color(value: types.StringTypes) -> int: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 5227744c..57264ed5 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -7,6 +7,7 @@ import pprint import sys import typing +from typing import Callable from python_utils import converters, types @@ -46,7 +47,7 @@ def create_wrapper(wrapper): >>> print(create_wrapper(('a', 'b'))) a{}b ''' - if isinstance(wrapper, tuple) and utils.len_color(wrapper) == 2: + if isinstance(wrapper, tuple) and len(wrapper) == 2: a, b = wrapper wrapper = (a or '') + '{}' + (b or '') elif not wrapper: @@ -223,6 +224,51 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: progress - a reference to the calling ProgressBar ''' + _fixed_colors: dict[str, terminal.Color | None] = dict() + _gradient_colors: dict[str, terminal.OptionalColor | None] = dict() + _len: Callable[[str | bytes], int] = len + + @functools.cached_property + def uses_colors(self): + for key, value in self._gradient_colors.items(): + if value is not None: + return True + + for key, value in self._fixed_colors.items(): + if value is not None: + return True + + return False + + def _apply_colors(self, text: str, data: Data) -> str: + if self.uses_colors: + return terminal.apply_colors( + text, + data.get('percentage'), + **self._gradient_colors, + **self._fixed_colors, + ) + else: + return text + + def __init__( + self, + *args, + fixed_colors=None, + gradient_colors=None, + **kwargs + ): + if fixed_colors is not None: + self._fixed_colors.update(fixed_colors) + + if gradient_colors is not None: + self._gradient_colors.update(gradient_colors) + + if self.uses_colors: + self._len = utils.len_color + + super().__init__(*args, **kwargs) + class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): '''The base class for all variable width widgets. @@ -392,7 +438,7 @@ def __call__( sample_times.pop(0) sample_values.pop(0) else: - if progress.custom_len(sample_times) > self.samples: + if len(sample_times) > self.samples: sample_times.pop(0) sample_values.pop(0) @@ -697,7 +743,7 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): if progress.end_time: return self.default - marker = self.markers[data['updates'] % utils.len_color(self.markers)] + marker = self.markers[data['updates'] % len(self.markers)] if self.marker_wrap: marker = self.marker_wrap.format(marker) @@ -739,15 +785,19 @@ def __call__( return FormatWidgetMixin.__call__(self, progress, data, format) -class Percentage(FormatWidgetMixin, WidgetBase): - '''Displays the current percentage as a number with a percent sign.''' - fg_na: terminal.Color | None = colors.yellow - fg_value: terminal.OptionalColor | None = colors.gradient - bg_na: terminal.Color | None = colors.black - bg_value: terminal.OptionalColor | None = None +class ColoredMixin: + _fixed_colors: dict[str, terminal.Color | None] = dict( + fg_none=colors.yellow, + bg_none=None, + ) + _gradient_colors: dict[str, terminal.OptionalColor | None] = dict( + fg=colors.gradient, + bg=None, + ) - def _uses_colors(self): - return self.fg_na or self.fg_value or self.bg_na or self.bg_value + +class Percentage(FormatWidgetMixin, ColoredMixin, WidgetBase): + '''Displays the current percentage as a number with a percent sign.''' def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): self.na = na @@ -764,23 +814,11 @@ def get_format( else: output = FormatWidgetMixin.get_format(self, progress, data, format) - return terminal.apply_colors( - output, - data.get('percentage'), - fg=self.fg_value, - bg=self.bg_value, - fg_none=self.fg_na, - bg_none=self.bg_na, - ) + return self._apply_colors(output, data) -class SimpleProgress(FormatWidgetMixin, WidgetBase): +class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' - fg_na: terminal.Color | None = colors.yellow - fg_value: terminal.OptionalColor | None = colors.gradient - bg_na: terminal.Color | None = colors.black - bg_value: terminal.OptionalColor | None = None - max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], types.Optional[int], @@ -839,20 +877,13 @@ def __call__( if max_width: # pragma: no branch formatted = formatted.rjust(max_width) - return terminal.apply_colors( - formatted, - data.get('percentage'), - fg=self.fg_value, - bg=self.bg_value, - fg_none=self.fg_na, - bg_none=self.bg_na, - ) + return self._apply_colors(formatted, data) class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' - fg_value: terminal.OptionalColor | None = colors.gradient - bg_value: terminal.OptionalColor | None = None + fg: terminal.OptionalColor | None = colors.gradient + bg: terminal.OptionalColor | None = None def __init__( self, @@ -888,6 +919,7 @@ def __call__( progress: ProgressBarMixinBase, data: Data, width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents''' @@ -898,28 +930,17 @@ def __call__( fill = converters.to_unicode(self.fill(progress, data, width)) # Make sure we ignore invisible characters when filling - width += utils.len_color(marker) - progress.custom_len(marker) + width += len(marker) - progress.custom_len(marker) if self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) - marker = self.apply_colors(progress, data, marker) - return left + marker + right + if color: + marker = self._apply_colors(marker, data) - def apply_colors( - self, - progress: ProgressBarMixinBase, - data: Data, output: str - ): - output = terminal.apply_colors( - output, - percentage=data.get('percentage'), - fg=self.fg_value, - bg=self.bg_value, - ) - return output + return left + marker + right class ReverseBar(Bar): @@ -1026,7 +1047,7 @@ class VariableMixin: def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError('Variable(): argument must be a string') - if utils.len_color(name.split()) > 1: + if len(name.split()) > 1: raise ValueError('Variable(): argument must be single word') self.name = name @@ -1106,7 +1127,7 @@ def __init__( ) def get_values(self, progress: ProgressBarMixinBase, data: Data): - ranges = [0.0] * progress.custom_len(self.markers) + ranges = [0.0] * len(self.markers) for value in data['variables'][self.name] or []: if not isinstance(value, (int, float)): # Progress is (value, max) @@ -1119,7 +1140,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): % value ) - range_ = value * (progress.custom_len(ranges) - 1) + range_ = value * (len(ranges) - 1) pos = int(range_) frac = range_ % 1 ranges[pos] += 1 - frac @@ -1203,16 +1224,14 @@ def __call__( marker = self.markers[-1] * int(num_chars) - marker_idx = int( - (num_chars % 1) * (progress.custom_len(self.markers) - 1) - ) + marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) if marker_idx: marker += self.markers[marker_idx] marker = converters.to_unicode(marker) # Make sure we ignore invisible characters when filling - width += progress.custom_len(marker) - progress.custom_len(marker) + width += len(marker) - progress.custom_len(marker) marker = marker.ljust(width, self.markers[0]) return left + marker + right @@ -1233,17 +1252,19 @@ def __call__( # type: ignore format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) - bar = Bar.__call__(self, progress, data, width) + bar = Bar.__call__(self, progress, data, width, color=False) # Aligns the center of the label to the center of the bar center_len = progress.custom_len(center) center_left = int((width - center_len) / 2) center_right = center_left + center_len - return bar[:center_left] + center + self.apply_colors( - progress, - data, - bar[center_right:] + return self._apply_colors( + bar[:center_left], data, + ) + self._apply_colors( + center, data, + ) + self._apply_colors( + bar[center_right:], data, ) diff --git a/tests/test_color.py b/tests/test_color.py new file mode 100644 index 00000000..3b5f5a15 --- /dev/null +++ b/tests/test_color.py @@ -0,0 +1,39 @@ +import pytest + +import progressbar +from progressbar import terminal + + +@pytest.mark.parametrize( + 'variable', [ + 'PROGRESSBAR_ENABLE_COLORS', + 'FORCE_COLOR', + ] +) +def test_color_environment_variables(monkeypatch, variable): + monkeypatch.setattr( + terminal, + 'color_support', + terminal.ColorSupport.XTERM_256, + ) + + monkeypatch.setenv(variable, '1') + bar = progressbar.ProgressBar() + assert bar.enable_colors + + monkeypatch.setenv(variable, '0') + bar = progressbar.ProgressBar() + assert not bar.enable_colors + +def test_enable_colors_flags(): + bar = progressbar.ProgressBar(enable_colors=True) + assert bar.enable_colors + + bar = progressbar.ProgressBar(enable_colors=False) + assert not bar.enable_colors + + bar = progressbar.ProgressBar(enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR) + assert bar.enable_colors + + with pytest.raises(ValueError): + progressbar.ProgressBar(enable_colors=12345) diff --git a/tests/test_flush.py b/tests/test_flush.py index 69dc4e30..f6336d8d 100644 --- a/tests/test_flush.py +++ b/tests/test_flush.py @@ -5,6 +5,7 @@ def test_flush(): '''Left justify using the terminal width''' p = progressbar.ProgressBar(poll_interval=0.001) + p.print('hello') for i in range(10): print('pre-updates', p.updates) diff --git a/tests/test_multibar.py b/tests/test_multibar.py index fe1c569f..4865ae3e 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -1,4 +1,8 @@ +import threading +import time + import pytest + import progressbar @@ -18,3 +22,83 @@ def test_multi_progress_bar_out_of_range(): def test_multi_progress_bar_fill_left(): import examples return examples.multi_progress_bar_example(False) + + +def test_multibar(): + bars = 3 + N = 10 + multibar = progressbar.MultiBar(sort_keyfunc=lambda bar: bar.label) + multibar.start() + multibar.append_label = False + multibar.prepend_label = True + + # Test handling of progressbars that don't call the super constructors + bar = progressbar.ProgressBar(max_value=N) + bar.index = -1 + multibar['x'] = bar + bar.start() + # Test twice for other code paths + multibar['x'] = bar + multibar._label_bar(bar) + multibar._label_bar(bar) + bar.finish() + del multibar['x'] + + multibar.append_label = True + + def do_something(bar): + for j in bar(range(N)): + time.sleep(0.01) + bar.update(j) + + for i in range(bars): + thread = threading.Thread( + target=do_something, + args=(multibar['bar {}'.format(i)],) + ) + thread.start() + + for bar in multibar.values(): + for j in range(N): + bar.update(j) + time.sleep(0.002) + + multibar.join(0.1) + multibar.stop(0.1) + + +@pytest.mark.parametrize( + 'sort_key', [ + None, + 'index', + 'label', + 'value', + 'percentage', + progressbar.SortKey.CREATED, + progressbar.SortKey.LABEL, + progressbar.SortKey.VALUE, + progressbar.SortKey.PERCENTAGE, + ] +) +def test_multibar_sorting(sort_key): + bars = 3 + N = 10 + + with progressbar.MultiBar() as multibar: + for i in range(bars): + label = 'bar {}'.format(i) + multibar[label] = progressbar.ProgressBar(max_value=N) + + for bar in multibar.values(): + for j in bar(range(N)): + assert bar.started() + time.sleep(0.002) + + for bar in multibar.values(): + assert bar.finished() + + +def test_offset_bar(): + with progressbar.ProgressBar(line_offset=2) as bar: + for i in range(100): + bar.update(i) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index 32083eb0..3e20ab63 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -63,6 +63,9 @@ def test_dirty(): bar = progressbar.ProgressBar() bar.start() + assert bar.started() for i in range(10): bar.update(i) bar.finish(dirty=True) + assert bar.finished() + assert bar.started() From a11c353aa8cc89412fed4d3249b1146a803d822c Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 13 Mar 2023 22:14:01 +0100 Subject: [PATCH 086/118] added security contact information --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index d76756c1..434b5756 100644 --- a/README.rst +++ b/README.rst @@ -71,6 +71,14 @@ of widgets: The progressbar module is very easy to use, yet very powerful. It will also automatically enable features like auto-resizing when the system supports it. +****************************************************************************** +Security contact information +****************************************************************************** + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. + ****************************************************************************** Known issues ****************************************************************************** From e2df59e914eb9073f3c42058073a74c1803beed2 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Fri, 17 Mar 2023 22:05:08 +0100 Subject: [PATCH 087/118] flake8 compliance --- progressbar/__init__.py | 1 + progressbar/bar.py | 37 +- progressbar/multi.py | 51 +- progressbar/terminal/base.py | 39 +- progressbar/terminal/colors.py | 728 +++++-------------- progressbar/terminal/os_specific/__init__.py | 4 +- progressbar/terminal/os_specific/windows.py | 46 +- progressbar/terminal/stream.py | 4 +- progressbar/widgets.py | 33 +- tests/test_color.py | 5 +- 10 files changed, 281 insertions(+), 667 deletions(-) diff --git a/progressbar/__init__.py b/progressbar/__init__.py index e43c4cf6..55525543 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -75,6 +75,7 @@ 'NullBar', '__author__', '__version__', + 'LineOffsetStreamWrapper', 'MultiBar', 'SortKey', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index 9208333f..d9616f68 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -18,7 +18,8 @@ import progressbar.terminal.stream from . import ( base, - terminal, utils, + terminal, + utils, widgets, widgets as widgets_module, # Avoid name collision ) @@ -152,15 +153,16 @@ class DefaultFdMixin(ProgressBarMixinBase): #: Whether to print line breaks. This is useful for logging the #: progressbar. When disabled the current line is overwritten. line_breaks: bool = True - # : Specify the type and number of colors to support. Defaults to auto - # detection based on the file descriptor type (i.e. interactive terminal) - # environment variables such as `COLORTERM` and `TERM`. Color output can - # be forced in non-interactive terminals using the - # `PROGRESSBAR_ENABLE_COLORS` environment variable which can also be used - # to force a specific number of colors by specifying `24bit`, `256` or `16`. - # For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. - # For 256 color support you can use `TERM=xterm-256color`. - # For 16 colorsupport you can use `TERM=xterm`. + #: Specify the type and number of colors to support. Defaults to auto + #: detection based on the file descriptor type (i.e. interactive terminal) + #: environment variables such as `COLORTERM` and `TERM`. Color output can + #: be forced in non-interactive terminals using the + #: `PROGRESSBAR_ENABLE_COLORS` environment variable which can also be used + #: to force a specific number of colors by specifying `24bit`, `256` or + #: `16`. + #: For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. + #: For 256 color support you can use `TERM=xterm-256color`. + #: For 16 colorsupport you can use `TERM=xterm`. enable_colors: terminal.ColorSupport | bool | None = terminal.color_support def __init__( @@ -180,8 +182,7 @@ def __init__( if line_offset: fd = progressbar.terminal.stream.LineOffsetStreamWrapper( - line_offset, - fd + line_offset, fd ) self.fd = fd @@ -493,7 +494,8 @@ def __init__( min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, widgets: types.Optional[ - types.Sequence[widgets_module.WidgetBase | str]] = None, + types.Sequence[widgets_module.WidgetBase | str] + ] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, @@ -708,7 +710,7 @@ def data(self) -> types.Dict[str, types.Any]: total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.0), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -841,9 +843,10 @@ def update(self, value=None, force=False, **kwargs): self.start() return self.update(value, force=force, **kwargs) - if value is not None and value is not base.UnknownLength and isinstance( - value, - int + if ( + value is not None + and value is not base.UnknownLength + and isinstance(value, int) ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update diff --git a/progressbar/multi.py b/progressbar/multi.py index 3e4a93de..dff82d60 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -31,6 +31,7 @@ class SortKey(str, enum.Enum): `value` might result in more rendering which can have a small performance impact. ''' + CREATED = 'index' LABEL = 'label' VALUE = 'value' @@ -170,60 +171,62 @@ def render(self, flush: bool = True, force: bool = False): now = timeit.default_timer() expired = now - self.remove_finished if self.remove_finished else None output = [] - for bar in self.get_sorted_bars(): - if not bar.started() and not self.show_initial: + for bar_ in self.get_sorted_bars(): + if not bar_.started() and not self.show_initial: continue def update(force=True, write=True): - self._label_bar(bar) - bar.update(force=force) + self._label_bar(bar_) + bar_.update(force=force) if write: - output.append(bar.fd.line) + output.append(bar_.fd.line) - if bar.finished(): - if bar not in self._finished_at: - self._finished_at[bar] = now + if bar_.finished(): + if bar_ not in self._finished_at: + self._finished_at[bar_] = now # Force update to get the finished format update(write=False) if self.remove_finished: - if expired >= self._finished_at[bar]: - del self[bar.label] + if expired >= self._finished_at[bar_]: + del self[bar_.label] continue if not self.show_finished: continue - if bar.finished(): + if bar_.finished(): if self.finished_format is None: update(force=False) else: - output.append(self.finished_format.format(label=bar.label)) - elif bar.started(): + output.append( + self.finished_format.format( + label=bar_.label + ) + ) + elif bar_.started(): update() else: if self.initial_format is None: - bar.start() + bar_.start() update() else: - output.append(self.initial_format.format(label=bar.label)) + output.append(self.initial_format.format(label=bar_.label)) with self._print_lock: # Clear the previous output if progressbars have been removed for i in range(len(output), len(self._previous_output)): self._buffer.write(terminal.clear_line(i + 1)) - # Add empty lines to the end of the output if progressbars have been - # added + # Add empty lines to the end of the output if progressbars have + # been added for i in range(len(self._previous_output), len(output)): # Adding a new line so we don't overwrite previous output self._buffer.write('\n') for i, (previous, current) in enumerate( itertools.zip_longest( - self._previous_output, - output, - fillvalue='' + self._previous_output, output, fillvalue='' ) ): if previous != current or force: @@ -241,13 +244,7 @@ def update(force=True, write=True): self.flush() def print( - self, - *args, - end='\n', - offset=None, - flush=True, - clear=True, - **kwargs + self, *args, end='\n', offset=None, flush=True, clear=True, **kwargs ): ''' Print to the progressbar stream without overwriting the progressbars diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 366373ee..b31e902e 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -27,7 +27,7 @@ def __init__(self, code, *default_args): def __call__(self, *args): return self._template.format( args=';'.join(map(str, args or self._default_args)), - code=self._code + code=self._code, ) def __str__(self): @@ -35,7 +35,6 @@ def __str__(self): class CSINoArg(CSI): - def __call__(self): return super().__call__() @@ -132,12 +131,14 @@ def __call__(self): # of line # CLEAR_LINE_ALL = CLEAR_LINE.format(n=2) # Clear Line + def clear_line(n): return UP(n) + CLEAR_LINE_ALL() + DOWN(n) class ColorSupport(enum.IntEnum): '''Color support for the terminal.''' + NONE = 0 XTERM = 16 XTERM_256 = 256 @@ -165,7 +166,9 @@ def from_env(cls): 'TERM', ) - if os.environ.get('JUPYTER_COLUMNS') or os.environ.get('JUPYTER_LINES'): + if os.environ.get('JUPYTER_COLUMNS') or os.environ.get( + 'JUPYTER_LINES' + ): # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR @@ -267,7 +270,9 @@ class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): @classmethod def from_rgb(cls, rgb: RGB) -> HLS: return cls( - *colorsys.rgb_to_hls(rgb.red / 255, rgb.green / 255, rgb.blue / 255) + *colorsys.rgb_to_hls( + rgb.red / 255, rgb.green / 255, rgb.blue / 255 + ) ) def interpolate(self, end: HLS, step: float) -> HLS: @@ -279,18 +284,19 @@ def interpolate(self, end: HLS, step: float) -> HLS: class ColorBase(abc.ABC): - def get_color(self, value: float) -> Color: raise NotImplementedError() + class Color( collections.namedtuple( - 'Color', [ + 'Color', + [ 'rgb', 'hls', 'name', 'xterm', - ] + ], ), ColorBase, ): @@ -305,6 +311,7 @@ class Color( The other values will be automatically interpolated from that if needed, but you can be more explicity if you wish. ''' + __slots__ = () def __call__(self, value: str) -> str: @@ -357,10 +364,12 @@ def __hash__(self): class Colors: - by_name: defaultdict[str, types.List[Color]] = collections.defaultdict(list) - by_lowername: defaultdict[str, types.List[Color]] = collections.defaultdict( + by_name: defaultdict[str, types.List[Color]] = collections.defaultdict( list ) + by_lowername: defaultdict[ + str, types.List[Color] + ] = collections.defaultdict(list) by_hex: defaultdict[str, types.List[Color]] = collections.defaultdict(list) by_rgb: defaultdict[RGB, types.List[Color]] = collections.defaultdict(list) by_hls: defaultdict[HLS, types.List[Color]] = collections.defaultdict(list) @@ -398,11 +407,7 @@ def interpolate(cls, color_a: Color, color_b: Color, step: float) -> Color: class ColorGradient(ColorBase): - def __init__( - self, - *colors: Color, - interpolate=Colors.interpolate - ): + def __init__(self, *colors: Color, interpolate=Colors.interpolate): assert colors self.colors = colors self.interpolate = interpolate @@ -412,7 +417,11 @@ def __call__(self, value: float): def get_color(self, value: float) -> Color: 'Map a value from 0 to 1 to a color' - if value is base.Undefined or value is base.UnknownLength or value <= 0: + if ( + value is base.Undefined + or value is base.UnknownLength + or value <= 0 + ): return self.colors[0] elif value >= 1: return self.colors[-1] diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index 5024a3bc..f05328a6 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -27,136 +27,73 @@ blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) darkGreen = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) deepSkyBlue4 = Colors.register( - RGB(0, 95, 95), - HLS(100, 180, 18), - 'DeepSkyBlue4', - 23 + RGB(0, 95, 95), HLS(100, 180, 18), 'DeepSkyBlue4', 23 ) deepSkyBlue4 = Colors.register( - RGB(0, 95, 135), - HLS(100, 97, 26), - 'DeepSkyBlue4', - 24 + RGB(0, 95, 135), HLS(100, 97, 26), 'DeepSkyBlue4', 24 ) deepSkyBlue4 = Colors.register( - RGB(0, 95, 175), - HLS(100, 7, 34), - 'DeepSkyBlue4', - 25 + RGB(0, 95, 175), HLS(100, 7, 34), 'DeepSkyBlue4', 25 ) dodgerBlue3 = Colors.register( - RGB(0, 95, 215), - HLS(100, 13, 42), - 'DodgerBlue3', - 26 + RGB(0, 95, 215), HLS(100, 13, 42), 'DodgerBlue3', 26 ) dodgerBlue2 = Colors.register( - RGB(0, 95, 255), - HLS(100, 17, 50), - 'DodgerBlue2', - 27 + RGB(0, 95, 255), HLS(100, 17, 50), 'DodgerBlue2', 27 ) green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) springGreen4 = Colors.register( - RGB(0, 135, 95), - HLS(100, 62, 26), - 'SpringGreen4', - 29 + RGB(0, 135, 95), HLS(100, 62, 26), 'SpringGreen4', 29 ) turquoise4 = Colors.register( - RGB(0, 135, 135), - HLS(100, 180, 26), - 'Turquoise4', - 30 + RGB(0, 135, 135), HLS(100, 180, 26), 'Turquoise4', 30 ) deepSkyBlue3 = Colors.register( - RGB(0, 135, 175), - HLS(100, 93, 34), - 'DeepSkyBlue3', - 31 + RGB(0, 135, 175), HLS(100, 93, 34), 'DeepSkyBlue3', 31 ) deepSkyBlue3 = Colors.register( - RGB(0, 135, 215), - HLS(100, 2, 42), - 'DeepSkyBlue3', - 32 + RGB(0, 135, 215), HLS(100, 2, 42), 'DeepSkyBlue3', 32 ) dodgerBlue1 = Colors.register( - RGB(0, 135, 255), - HLS(100, 8, 50), - 'DodgerBlue1', - 33 + RGB(0, 135, 255), HLS(100, 8, 50), 'DodgerBlue1', 33 ) green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) springGreen3 = Colors.register( - RGB(0, 175, 95), - HLS(100, 52, 34), - 'SpringGreen3', - 35 + RGB(0, 175, 95), HLS(100, 52, 34), 'SpringGreen3', 35 ) darkCyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) lightSeaGreen = Colors.register( - RGB(0, 175, 175), - HLS(100, 180, 34), - 'LightSeaGreen', - 37 + RGB(0, 175, 175), HLS(100, 180, 34), 'LightSeaGreen', 37 ) deepSkyBlue2 = Colors.register( - RGB(0, 175, 215), - HLS(100, 91, 42), - 'DeepSkyBlue2', - 38 + RGB(0, 175, 215), HLS(100, 91, 42), 'DeepSkyBlue2', 38 ) deepSkyBlue1 = Colors.register( - RGB(0, 175, 255), - HLS(100, 98, 50), - 'DeepSkyBlue1', - 39 + RGB(0, 175, 255), HLS(100, 98, 50), 'DeepSkyBlue1', 39 ) green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) springGreen3 = Colors.register( - RGB(0, 215, 95), - HLS(100, 46, 42), - 'SpringGreen3', - 41 + RGB(0, 215, 95), HLS(100, 46, 42), 'SpringGreen3', 41 ) springGreen2 = Colors.register( - RGB(0, 215, 135), - HLS(100, 57, 42), - 'SpringGreen2', - 42 + RGB(0, 215, 135), HLS(100, 57, 42), 'SpringGreen2', 42 ) cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) darkTurquoise = Colors.register( - RGB(0, 215, 215), - HLS(100, 180, 42), - 'DarkTurquoise', - 44 + RGB(0, 215, 215), HLS(100, 180, 42), 'DarkTurquoise', 44 ) turquoise2 = Colors.register( - RGB(0, 215, 255), - HLS(100, 89, 50), - 'Turquoise2', - 45 + RGB(0, 215, 255), HLS(100, 89, 50), 'Turquoise2', 45 ) green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) springGreen2 = Colors.register( - RGB(0, 255, 95), - HLS(100, 42, 50), - 'SpringGreen2', - 47 + RGB(0, 255, 95), HLS(100, 42, 50), 'SpringGreen2', 47 ) springGreen1 = Colors.register( - RGB(0, 255, 135), - HLS(100, 51, 50), - 'SpringGreen1', - 48 + RGB(0, 255, 135), HLS(100, 51, 50), 'SpringGreen1', 48 ) mediumSpringGreen = Colors.register( - RGB(0, 255, 175), - HLS(100, 61, 50), - 'MediumSpringGreen', - 49 + RGB(0, 255, 175), HLS(100, 61, 50), 'MediumSpringGreen', 49 ) cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) @@ -166,753 +103,432 @@ purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) blueViolet = Colors.register( - RGB(95, 0, 255), - HLS(100, 62, 50), - 'BlueViolet', - 57 + RGB(95, 0, 255), HLS(100, 62, 50), 'BlueViolet', 57 ) orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) mediumPurple4 = Colors.register( - RGB(95, 95, 135), - HLS(17, 240, 45), - 'MediumPurple4', - 60 + RGB(95, 95, 135), HLS(17, 240, 45), 'MediumPurple4', 60 ) slateBlue3 = Colors.register( - RGB(95, 95, 175), - HLS(33, 240, 52), - 'SlateBlue3', - 61 + RGB(95, 95, 175), HLS(33, 240, 52), 'SlateBlue3', 61 ) slateBlue3 = Colors.register( - RGB(95, 95, 215), - HLS(60, 240, 60), - 'SlateBlue3', - 62 + RGB(95, 95, 215), HLS(60, 240, 60), 'SlateBlue3', 62 ) royalBlue1 = Colors.register( - RGB(95, 95, 255), - HLS(100, 240, 68), - 'RoyalBlue1', - 63 + RGB(95, 95, 255), HLS(100, 240, 68), 'RoyalBlue1', 63 ) chartreuse4 = Colors.register( - RGB(95, 135, 0), - HLS(100, 7, 26), - 'Chartreuse4', - 64 + RGB(95, 135, 0), HLS(100, 7, 26), 'Chartreuse4', 64 ) darkSeaGreen4 = Colors.register( - RGB(95, 135, 95), - HLS(17, 120, 45), - 'DarkSeaGreen4', - 65 + RGB(95, 135, 95), HLS(17, 120, 45), 'DarkSeaGreen4', 65 ) paleTurquoise4 = Colors.register( - RGB(95, 135, 135), - HLS(17, 180, 45), - 'PaleTurquoise4', - 66 + RGB(95, 135, 135), HLS(17, 180, 45), 'PaleTurquoise4', 66 ) steelBlue = Colors.register( - RGB(95, 135, 175), - HLS(33, 210, 52), - 'SteelBlue', - 67 + RGB(95, 135, 175), HLS(33, 210, 52), 'SteelBlue', 67 ) steelBlue3 = Colors.register( - RGB(95, 135, 215), - HLS(60, 220, 60), - 'SteelBlue3', - 68 + RGB(95, 135, 215), HLS(60, 220, 60), 'SteelBlue3', 68 ) cornflowerBlue = Colors.register( - RGB(95, 135, 255), - HLS(100, 225, 68), - 'CornflowerBlue', - 69 + RGB(95, 135, 255), HLS(100, 225, 68), 'CornflowerBlue', 69 ) chartreuse3 = Colors.register( - RGB(95, 175, 0), - HLS(100, 7, 34), - 'Chartreuse3', - 70 + RGB(95, 175, 0), HLS(100, 7, 34), 'Chartreuse3', 70 ) darkSeaGreen4 = Colors.register( - RGB(95, 175, 95), - HLS(33, 120, 52), - 'DarkSeaGreen4', - 71 + RGB(95, 175, 95), HLS(33, 120, 52), 'DarkSeaGreen4', 71 ) cadetBlue = Colors.register( - RGB(95, 175, 135), - HLS(33, 150, 52), - 'CadetBlue', - 72 + RGB(95, 175, 135), HLS(33, 150, 52), 'CadetBlue', 72 ) cadetBlue = Colors.register( - RGB(95, 175, 175), - HLS(33, 180, 52), - 'CadetBlue', - 73 + RGB(95, 175, 175), HLS(33, 180, 52), 'CadetBlue', 73 ) skyBlue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) steelBlue1 = Colors.register( - RGB(95, 175, 255), - HLS(100, 210, 68), - 'SteelBlue1', - 75 + RGB(95, 175, 255), HLS(100, 210, 68), 'SteelBlue1', 75 ) chartreuse3 = Colors.register( - RGB(95, 215, 0), - HLS(100, 3, 42), - 'Chartreuse3', - 76 + RGB(95, 215, 0), HLS(100, 3, 42), 'Chartreuse3', 76 ) paleGreen3 = Colors.register( - RGB(95, 215, 95), - HLS(60, 120, 60), - 'PaleGreen3', - 77 + RGB(95, 215, 95), HLS(60, 120, 60), 'PaleGreen3', 77 ) seaGreen3 = Colors.register( - RGB(95, 215, 135), - HLS(60, 140, 60), - 'SeaGreen3', - 78 + RGB(95, 215, 135), HLS(60, 140, 60), 'SeaGreen3', 78 ) aquamarine3 = Colors.register( - RGB(95, 215, 175), - HLS(60, 160, 60), - 'Aquamarine3', - 79 + RGB(95, 215, 175), HLS(60, 160, 60), 'Aquamarine3', 79 ) mediumTurquoise = Colors.register( - RGB(95, 215, 215), - HLS(60, 180, 60), - 'MediumTurquoise', - 80 + RGB(95, 215, 215), HLS(60, 180, 60), 'MediumTurquoise', 80 ) steelBlue1 = Colors.register( - RGB(95, 215, 255), - HLS(100, 195, 68), - 'SteelBlue1', - 81 + RGB(95, 215, 255), HLS(100, 195, 68), 'SteelBlue1', 81 ) chartreuse2 = Colors.register( - RGB(95, 255, 0), - HLS(100, 7, 50), - 'Chartreuse2', - 82 + RGB(95, 255, 0), HLS(100, 7, 50), 'Chartreuse2', 82 ) seaGreen2 = Colors.register( - RGB(95, 255, 95), - HLS(100, 120, 68), - 'SeaGreen2', - 83 + RGB(95, 255, 95), HLS(100, 120, 68), 'SeaGreen2', 83 ) seaGreen1 = Colors.register( - RGB(95, 255, 135), - HLS(100, 135, 68), - 'SeaGreen1', - 84 + RGB(95, 255, 135), HLS(100, 135, 68), 'SeaGreen1', 84 ) seaGreen1 = Colors.register( - RGB(95, 255, 175), - HLS(100, 150, 68), - 'SeaGreen1', - 85 + RGB(95, 255, 175), HLS(100, 150, 68), 'SeaGreen1', 85 ) aquamarine1 = Colors.register( - RGB(95, 255, 215), - HLS(100, 165, 68), - 'Aquamarine1', - 86 + RGB(95, 255, 215), HLS(100, 165, 68), 'Aquamarine1', 86 ) darkSlateGray2 = Colors.register( - RGB(95, 255, 255), - HLS(100, 180, 68), - 'DarkSlateGray2', - 87 + RGB(95, 255, 255), HLS(100, 180, 68), 'DarkSlateGray2', 87 ) darkRed = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) deepPink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) darkMagenta = Colors.register( - RGB(135, 0, 135), - HLS(100, 300, 26), - 'DarkMagenta', - 90 + RGB(135, 0, 135), HLS(100, 300, 26), 'DarkMagenta', 90 ) darkMagenta = Colors.register( - RGB(135, 0, 175), - HLS(100, 86, 34), - 'DarkMagenta', - 91 + RGB(135, 0, 175), HLS(100, 86, 34), 'DarkMagenta', 91 ) darkViolet = Colors.register( - RGB(135, 0, 215), - HLS(100, 77, 42), - 'DarkViolet', - 92 + RGB(135, 0, 215), HLS(100, 77, 42), 'DarkViolet', 92 ) purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) -lightPink4 = Colors.register(RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95) +lightPink4 = Colors.register( + RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95 +) plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) mediumPurple3 = Colors.register( - RGB(135, 95, 175), - HLS(33, 270, 52), - 'MediumPurple3', - 97 + RGB(135, 95, 175), HLS(33, 270, 52), 'MediumPurple3', 97 ) mediumPurple3 = Colors.register( - RGB(135, 95, 215), - HLS(60, 260, 60), - 'MediumPurple3', - 98 + RGB(135, 95, 215), HLS(60, 260, 60), 'MediumPurple3', 98 ) slateBlue1 = Colors.register( - RGB(135, 95, 255), - HLS(100, 255, 68), - 'SlateBlue1', - 99 + RGB(135, 95, 255), HLS(100, 255, 68), 'SlateBlue1', 99 ) yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) lightSlateGrey = Colors.register( - RGB(135, 135, 175), - HLS(20, 240, 60), - 'LightSlateGrey', - 103 + RGB(135, 135, 175), HLS(20, 240, 60), 'LightSlateGrey', 103 ) mediumPurple = Colors.register( - RGB(135, 135, 215), - HLS(50, 240, 68), - 'MediumPurple', - 104 + RGB(135, 135, 215), HLS(50, 240, 68), 'MediumPurple', 104 ) lightSlateBlue = Colors.register( - RGB(135, 135, 255), - HLS(100, 240, 76), - 'LightSlateBlue', - 105 + RGB(135, 135, 255), HLS(100, 240, 76), 'LightSlateBlue', 105 ) yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) darkOliveGreen3 = Colors.register( - RGB(135, 175, 95), - HLS(33, 90, 52), - 'DarkOliveGreen3', - 107 + RGB(135, 175, 95), HLS(33, 90, 52), 'DarkOliveGreen3', 107 ) darkSeaGreen = Colors.register( - RGB(135, 175, 135), - HLS(20, 120, 60), - 'DarkSeaGreen', - 108 + RGB(135, 175, 135), HLS(20, 120, 60), 'DarkSeaGreen', 108 ) lightSkyBlue3 = Colors.register( - RGB(135, 175, 175), - HLS(20, 180, 60), - 'LightSkyBlue3', - 109 + RGB(135, 175, 175), HLS(20, 180, 60), 'LightSkyBlue3', 109 ) lightSkyBlue3 = Colors.register( - RGB(135, 175, 215), - HLS(50, 210, 68), - 'LightSkyBlue3', - 110 + RGB(135, 175, 215), HLS(50, 210, 68), 'LightSkyBlue3', 110 ) skyBlue2 = Colors.register( - RGB(135, 175, 255), - HLS(100, 220, 76), - 'SkyBlue2', - 111 + RGB(135, 175, 255), HLS(100, 220, 76), 'SkyBlue2', 111 ) chartreuse2 = Colors.register( - RGB(135, 215, 0), - HLS(100, 2, 42), - 'Chartreuse2', - 112 + RGB(135, 215, 0), HLS(100, 2, 42), 'Chartreuse2', 112 ) darkOliveGreen3 = Colors.register( - RGB(135, 215, 95), - HLS(60, 100, 60), - 'DarkOliveGreen3', - 113 + RGB(135, 215, 95), HLS(60, 100, 60), 'DarkOliveGreen3', 113 ) paleGreen3 = Colors.register( - RGB(135, 215, 135), - HLS(50, 120, 68), - 'PaleGreen3', - 114 + RGB(135, 215, 135), HLS(50, 120, 68), 'PaleGreen3', 114 ) darkSeaGreen3 = Colors.register( - RGB(135, 215, 175), - HLS(50, 150, 68), - 'DarkSeaGreen3', - 115 + RGB(135, 215, 175), HLS(50, 150, 68), 'DarkSeaGreen3', 115 ) darkSlateGray3 = Colors.register( - RGB(135, 215, 215), - HLS(50, 180, 68), - 'DarkSlateGray3', - 116 + RGB(135, 215, 215), HLS(50, 180, 68), 'DarkSlateGray3', 116 ) skyBlue1 = Colors.register( - RGB(135, 215, 255), - HLS(100, 200, 76), - 'SkyBlue1', - 117 + RGB(135, 215, 255), HLS(100, 200, 76), 'SkyBlue1', 117 ) chartreuse1 = Colors.register( - RGB(135, 255, 0), - HLS(100, 8, 50), - 'Chartreuse1', - 118 + RGB(135, 255, 0), HLS(100, 8, 50), 'Chartreuse1', 118 ) lightGreen = Colors.register( - RGB(135, 255, 95), - HLS(100, 105, 68), - 'LightGreen', - 119 + RGB(135, 255, 95), HLS(100, 105, 68), 'LightGreen', 119 ) lightGreen = Colors.register( - RGB(135, 255, 135), - HLS(100, 120, 76), - 'LightGreen', - 120 + RGB(135, 255, 135), HLS(100, 120, 76), 'LightGreen', 120 ) paleGreen1 = Colors.register( - RGB(135, 255, 175), - HLS(100, 140, 76), - 'PaleGreen1', - 121 + RGB(135, 255, 175), HLS(100, 140, 76), 'PaleGreen1', 121 ) aquamarine1 = Colors.register( - RGB(135, 255, 215), - HLS(100, 160, 76), - 'Aquamarine1', - 122 + RGB(135, 255, 215), HLS(100, 160, 76), 'Aquamarine1', 122 ) darkSlateGray1 = Colors.register( - RGB(135, 255, 255), - HLS(100, 180, 76), - 'DarkSlateGray1', - 123 + RGB(135, 255, 255), HLS(100, 180, 76), 'DarkSlateGray1', 123 ) red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) -deepPink4 = Colors.register(RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125) +deepPink4 = Colors.register( + RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125 +) mediumVioletRed = Colors.register( - RGB(175, 0, 135), - HLS(100, 13, 34), - 'MediumVioletRed', - 126 + RGB(175, 0, 135), HLS(100, 13, 34), 'MediumVioletRed', 126 +) +magenta3 = Colors.register( + RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127 ) -magenta3 = Colors.register(RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127) darkViolet = Colors.register( - RGB(175, 0, 215), - HLS(100, 88, 42), - 'DarkViolet', - 128 + RGB(175, 0, 215), HLS(100, 88, 42), 'DarkViolet', 128 ) purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) darkOrange3 = Colors.register( - RGB(175, 95, 0), - HLS(100, 2, 34), - 'DarkOrange3', - 130 + RGB(175, 95, 0), HLS(100, 2, 34), 'DarkOrange3', 130 ) indianRed = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) -hotPink3 = Colors.register(RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132) +hotPink3 = Colors.register( + RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132 +) mediumOrchid3 = Colors.register( - RGB(175, 95, 175), - HLS(33, 300, 52), - 'MediumOrchid3', - 133 + RGB(175, 95, 175), HLS(33, 300, 52), 'MediumOrchid3', 133 ) mediumOrchid = Colors.register( - RGB(175, 95, 215), - HLS(60, 280, 60), - 'MediumOrchid', - 134 + RGB(175, 95, 215), HLS(60, 280, 60), 'MediumOrchid', 134 ) mediumPurple2 = Colors.register( - RGB(175, 95, 255), - HLS(100, 270, 68), - 'MediumPurple2', - 135 + RGB(175, 95, 255), HLS(100, 270, 68), 'MediumPurple2', 135 ) darkGoldenrod = Colors.register( - RGB(175, 135, 0), - HLS(100, 6, 34), - 'DarkGoldenrod', - 136 + RGB(175, 135, 0), HLS(100, 6, 34), 'DarkGoldenrod', 136 ) lightSalmon3 = Colors.register( - RGB(175, 135, 95), - HLS(33, 30, 52), - 'LightSalmon3', - 137 + RGB(175, 135, 95), HLS(33, 30, 52), 'LightSalmon3', 137 ) rosyBrown = Colors.register( - RGB(175, 135, 135), - HLS(20, 0, 60), - 'RosyBrown', - 138 + RGB(175, 135, 135), HLS(20, 0, 60), 'RosyBrown', 138 ) grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) mediumPurple2 = Colors.register( - RGB(175, 135, 215), - HLS(50, 270, 68), - 'MediumPurple2', - 140 + RGB(175, 135, 215), HLS(50, 270, 68), 'MediumPurple2', 140 ) mediumPurple1 = Colors.register( - RGB(175, 135, 255), - HLS(100, 260, 76), - 'MediumPurple1', - 141 + RGB(175, 135, 255), HLS(100, 260, 76), 'MediumPurple1', 141 ) gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) darkKhaki = Colors.register( - RGB(175, 175, 95), - HLS(33, 60, 52), - 'DarkKhaki', - 143 + RGB(175, 175, 95), HLS(33, 60, 52), 'DarkKhaki', 143 ) navajoWhite3 = Colors.register( - RGB(175, 175, 135), - HLS(20, 60, 60), - 'NavajoWhite3', - 144 + RGB(175, 175, 135), HLS(20, 60, 60), 'NavajoWhite3', 144 ) grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) lightSteelBlue3 = Colors.register( - RGB(175, 175, 215), - HLS(33, 240, 76), - 'LightSteelBlue3', - 146 + RGB(175, 175, 215), HLS(33, 240, 76), 'LightSteelBlue3', 146 ) lightSteelBlue = Colors.register( - RGB(175, 175, 255), - HLS(100, 240, 84), - 'LightSteelBlue', - 147 + RGB(175, 175, 255), HLS(100, 240, 84), 'LightSteelBlue', 147 ) yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) darkOliveGreen3 = Colors.register( - RGB(175, 215, 95), - HLS(60, 80, 60), - 'DarkOliveGreen3', - 149 + RGB(175, 215, 95), HLS(60, 80, 60), 'DarkOliveGreen3', 149 ) darkSeaGreen3 = Colors.register( - RGB(175, 215, 135), - HLS(50, 90, 68), - 'DarkSeaGreen3', - 150 + RGB(175, 215, 135), HLS(50, 90, 68), 'DarkSeaGreen3', 150 ) darkSeaGreen2 = Colors.register( - RGB(175, 215, 175), - HLS(33, 120, 76), - 'DarkSeaGreen2', - 151 + RGB(175, 215, 175), HLS(33, 120, 76), 'DarkSeaGreen2', 151 ) lightCyan3 = Colors.register( - RGB(175, 215, 215), - HLS(33, 180, 76), - 'LightCyan3', - 152 + RGB(175, 215, 215), HLS(33, 180, 76), 'LightCyan3', 152 ) lightSkyBlue1 = Colors.register( - RGB(175, 215, 255), - HLS(100, 210, 84), - 'LightSkyBlue1', - 153 + RGB(175, 215, 255), HLS(100, 210, 84), 'LightSkyBlue1', 153 ) greenYellow = Colors.register( - RGB(175, 255, 0), - HLS(100, 8, 50), - 'GreenYellow', - 154 + RGB(175, 255, 0), HLS(100, 8, 50), 'GreenYellow', 154 ) darkOliveGreen2 = Colors.register( - RGB(175, 255, 95), - HLS(100, 90, 68), - 'DarkOliveGreen2', - 155 + RGB(175, 255, 95), HLS(100, 90, 68), 'DarkOliveGreen2', 155 ) paleGreen1 = Colors.register( - RGB(175, 255, 135), - HLS(100, 100, 76), - 'PaleGreen1', - 156 + RGB(175, 255, 135), HLS(100, 100, 76), 'PaleGreen1', 156 ) darkSeaGreen2 = Colors.register( - RGB(175, 255, 175), - HLS(100, 120, 84), - 'DarkSeaGreen2', - 157 + RGB(175, 255, 175), HLS(100, 120, 84), 'DarkSeaGreen2', 157 ) darkSeaGreen1 = Colors.register( - RGB(175, 255, 215), - HLS(100, 150, 84), - 'DarkSeaGreen1', - 158 + RGB(175, 255, 215), HLS(100, 150, 84), 'DarkSeaGreen1', 158 ) paleTurquoise1 = Colors.register( - RGB(175, 255, 255), - HLS(100, 180, 84), - 'PaleTurquoise1', - 159 + RGB(175, 255, 255), HLS(100, 180, 84), 'PaleTurquoise1', 159 ) red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) -deepPink3 = Colors.register(RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161) deepPink3 = Colors.register( - RGB(215, 0, 135), - HLS(100, 22, 42), - 'DeepPink3', - 162 + RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161 +) +deepPink3 = Colors.register( + RGB(215, 0, 135), HLS(100, 22, 42), 'DeepPink3', 162 ) magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) -magenta3 = Colors.register(RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164) +magenta3 = Colors.register( + RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164 +) magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) darkOrange3 = Colors.register( - RGB(215, 95, 0), - HLS(100, 6, 42), - 'DarkOrange3', - 166 + RGB(215, 95, 0), HLS(100, 6, 42), 'DarkOrange3', 166 ) indianRed = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) -hotPink3 = Colors.register(RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168) -hotPink2 = Colors.register(RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169) +hotPink3 = Colors.register( + RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168 +) +hotPink2 = Colors.register( + RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169 +) orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) mediumOrchid1 = Colors.register( - RGB(215, 95, 255), - HLS(100, 285, 68), - 'MediumOrchid1', - 171 + RGB(215, 95, 255), HLS(100, 285, 68), 'MediumOrchid1', 171 ) orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) lightSalmon3 = Colors.register( - RGB(215, 135, 95), - HLS(60, 20, 60), - 'LightSalmon3', - 173 + RGB(215, 135, 95), HLS(60, 20, 60), 'LightSalmon3', 173 ) lightPink3 = Colors.register( - RGB(215, 135, 135), - HLS(50, 0, 68), - 'LightPink3', - 174 + RGB(215, 135, 135), HLS(50, 0, 68), 'LightPink3', 174 ) pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) lightGoldenrod3 = Colors.register( - RGB(215, 175, 95), - HLS(60, 40, 60), - 'LightGoldenrod3', - 179 + RGB(215, 175, 95), HLS(60, 40, 60), 'LightGoldenrod3', 179 ) tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) mistyRose3 = Colors.register( - RGB(215, 175, 175), - HLS(33, 0, 76), - 'MistyRose3', - 181 + RGB(215, 175, 175), HLS(33, 0, 76), 'MistyRose3', 181 ) thistle3 = Colors.register( - RGB(215, 175, 215), - HLS(33, 300, 76), - 'Thistle3', - 182 + RGB(215, 175, 215), HLS(33, 300, 76), 'Thistle3', 182 ) plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) lightGoldenrod2 = Colors.register( - RGB(215, 215, 135), - HLS(50, 60, 68), - 'LightGoldenrod2', - 186 + RGB(215, 215, 135), HLS(50, 60, 68), 'LightGoldenrod2', 186 ) lightYellow3 = Colors.register( - RGB(215, 215, 175), - HLS(33, 60, 76), - 'LightYellow3', - 187 + RGB(215, 215, 175), HLS(33, 60, 76), 'LightYellow3', 187 ) grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) lightSteelBlue1 = Colors.register( - RGB(215, 215, 255), - HLS(100, 240, 92), - 'LightSteelBlue1', - 189 + RGB(215, 215, 255), HLS(100, 240, 92), 'LightSteelBlue1', 189 ) yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) darkOliveGreen1 = Colors.register( - RGB(215, 255, 95), - HLS(100, 75, 68), - 'DarkOliveGreen1', - 191 + RGB(215, 255, 95), HLS(100, 75, 68), 'DarkOliveGreen1', 191 ) darkOliveGreen1 = Colors.register( - RGB(215, 255, 135), - HLS(100, 80, 76), - 'DarkOliveGreen1', - 192 + RGB(215, 255, 135), HLS(100, 80, 76), 'DarkOliveGreen1', 192 ) darkSeaGreen1 = Colors.register( - RGB(215, 255, 175), - HLS(100, 90, 84), - 'DarkSeaGreen1', - 193 + RGB(215, 255, 175), HLS(100, 90, 84), 'DarkSeaGreen1', 193 ) honeydew2 = Colors.register( - RGB(215, 255, 215), - HLS(100, 120, 92), - 'Honeydew2', - 194 + RGB(215, 255, 215), HLS(100, 120, 92), 'Honeydew2', 194 ) lightCyan1 = Colors.register( - RGB(215, 255, 255), - HLS(100, 180, 92), - 'LightCyan1', - 195 + RGB(215, 255, 255), HLS(100, 180, 92), 'LightCyan1', 195 ) red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) -deepPink2 = Colors.register(RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197) +deepPink2 = Colors.register( + RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197 +) deepPink1 = Colors.register( - RGB(255, 0, 135), - HLS(100, 28, 50), - 'DeepPink1', - 198 + RGB(255, 0, 135), HLS(100, 28, 50), 'DeepPink1', 198 ) deepPink1 = Colors.register( - RGB(255, 0, 175), - HLS(100, 18, 50), - 'DeepPink1', - 199 + RGB(255, 0, 175), HLS(100, 18, 50), 'DeepPink1', 199 ) magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) -magenta1 = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201) +magenta1 = Colors.register( + RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201 +) orangeRed1 = Colors.register( - RGB(255, 95, 0), - HLS(100, 2, 50), - 'OrangeRed1', - 202 + RGB(255, 95, 0), HLS(100, 2, 50), 'OrangeRed1', 202 ) indianRed1 = Colors.register( - RGB(255, 95, 95), - HLS(100, 0, 68), - 'IndianRed1', - 203 + RGB(255, 95, 95), HLS(100, 0, 68), 'IndianRed1', 203 ) indianRed1 = Colors.register( - RGB(255, 95, 135), - HLS(100, 345, 68), - 'IndianRed1', - 204 + RGB(255, 95, 135), HLS(100, 345, 68), 'IndianRed1', 204 ) hotPink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) hotPink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) mediumOrchid1 = Colors.register( - RGB(255, 95, 255), - HLS(100, 300, 68), - 'MediumOrchid1', - 207 + RGB(255, 95, 255), HLS(100, 300, 68), 'MediumOrchid1', 207 ) darkOrange = Colors.register( - RGB(255, 135, 0), - HLS(100, 1, 50), - 'DarkOrange', - 208 + RGB(255, 135, 0), HLS(100, 1, 50), 'DarkOrange', 208 ) salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) lightCoral = Colors.register( - RGB(255, 135, 135), - HLS(100, 0, 76), - 'LightCoral', - 210 + RGB(255, 135, 135), HLS(100, 0, 76), 'LightCoral', 210 ) paleVioletRed1 = Colors.register( - RGB(255, 135, 175), - HLS(100, 340, 76), - 'PaleVioletRed1', - 211 + RGB(255, 135, 175), HLS(100, 340, 76), 'PaleVioletRed1', 211 +) +orchid2 = Colors.register( + RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212 +) +orchid1 = Colors.register( + RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213 ) -orchid2 = Colors.register(RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212) -orchid1 = Colors.register(RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213) orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) sandyBrown = Colors.register( - RGB(255, 175, 95), - HLS(100, 30, 68), - 'SandyBrown', - 215 + RGB(255, 175, 95), HLS(100, 30, 68), 'SandyBrown', 215 ) lightSalmon1 = Colors.register( - RGB(255, 175, 135), - HLS(100, 20, 76), - 'LightSalmon1', - 216 + RGB(255, 175, 135), HLS(100, 20, 76), 'LightSalmon1', 216 ) lightPink1 = Colors.register( - RGB(255, 175, 175), - HLS(100, 0, 84), - 'LightPink1', - 217 + RGB(255, 175, 175), HLS(100, 0, 84), 'LightPink1', 217 ) pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) lightGoldenrod2 = Colors.register( - RGB(255, 215, 95), - HLS(100, 45, 68), - 'LightGoldenrod2', - 221 + RGB(255, 215, 95), HLS(100, 45, 68), 'LightGoldenrod2', 221 ) lightGoldenrod2 = Colors.register( - RGB(255, 215, 135), - HLS(100, 40, 76), - 'LightGoldenrod2', - 222 + RGB(255, 215, 135), HLS(100, 40, 76), 'LightGoldenrod2', 222 ) navajoWhite1 = Colors.register( - RGB(255, 215, 175), - HLS(100, 30, 84), - 'NavajoWhite1', - 223 + RGB(255, 215, 175), HLS(100, 30, 84), 'NavajoWhite1', 223 ) mistyRose1 = Colors.register( - RGB(255, 215, 215), - HLS(100, 0, 92), - 'MistyRose1', - 224 + RGB(255, 215, 215), HLS(100, 0, 92), 'MistyRose1', 224 ) thistle1 = Colors.register( - RGB(255, 215, 255), - HLS(100, 300, 92), - 'Thistle1', - 225 + RGB(255, 215, 255), HLS(100, 300, 92), 'Thistle1', 225 ) yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) lightGoldenrod1 = Colors.register( - RGB(255, 255, 95), - HLS(100, 60, 68), - 'LightGoldenrod1', - 227 + RGB(255, 255, 95), HLS(100, 60, 68), 'LightGoldenrod1', 227 ) khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) cornsilk1 = Colors.register( - RGB(255, 255, 215), - HLS(100, 60, 92), - 'Cornsilk1', - 230 + RGB(255, 255, 215), HLS(100, 60, 92), 'Cornsilk1', 230 ) grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) diff --git a/progressbar/terminal/os_specific/__init__.py b/progressbar/terminal/os_specific/__init__.py index 782ca9cd..4cff9feb 100644 --- a/progressbar/terminal/os_specific/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -4,7 +4,7 @@ from .windows import ( getch as _getch, set_console_mode as _set_console_mode, - reset_console_mode as _reset_console_mode + reset_console_mode as _reset_console_mode, ) else: @@ -13,7 +13,6 @@ def _reset_console_mode(): pass - def _set_console_mode(): pass @@ -21,4 +20,3 @@ def _set_console_mode(): getch = _getch reset_console_mode = _reset_console_mode set_console_mode = _set_console_mode - diff --git a/progressbar/terminal/os_specific/windows.py b/progressbar/terminal/os_specific/windows.py index edac0696..6084f3b1 100644 --- a/progressbar/terminal/os_specific/windows.py +++ b/progressbar/terminal/os_specific/windows.py @@ -7,7 +7,7 @@ UINT as _UINT, WCHAR as _WCHAR, CHAR as _CHAR, - SHORT as _SHORT + SHORT as _SHORT, ) _kernel32 = ctypes.windll.Kernel32 @@ -43,24 +43,16 @@ class _COORD(ctypes.Structure): - _fields_ = [ - ('X', _SHORT), - ('Y', _SHORT) - ] + _fields_ = [('X', _SHORT), ('Y', _SHORT)] class _FOCUS_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('bSetFocus', _BOOL) - ] + _fields_ = [('bSetFocus', _BOOL)] class _KEY_EVENT_RECORD(ctypes.Structure): class _uchar(ctypes.Union): - _fields_ = [ - ('UnicodeChar', _WCHAR), - ('AsciiChar', _CHAR) - ] + _fields_ = [('UnicodeChar', _WCHAR), ('AsciiChar', _CHAR)] _fields_ = [ ('bKeyDown', _BOOL), @@ -68,14 +60,12 @@ class _uchar(ctypes.Union): ('wVirtualKeyCode', _WORD), ('wVirtualScanCode', _WORD), ('uChar', _uchar), - ('dwControlKeyState', _DWORD) + ('dwControlKeyState', _DWORD), ] class _MENU_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('dwCommandId', _UINT) - ] + _fields_ = [('dwCommandId', _UINT)] class _MOUSE_EVENT_RECORD(ctypes.Structure): @@ -83,14 +73,12 @@ class _MOUSE_EVENT_RECORD(ctypes.Structure): ('dwMousePosition', _COORD), ('dwButtonState', _DWORD), ('dwControlKeyState', _DWORD), - ('dwEventFlags', _DWORD) + ('dwEventFlags', _DWORD), ] class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): - _fields_ = [ - ('dwSize', _COORD) - ] + _fields_ = [('dwSize', _COORD)] class _INPUT_RECORD(ctypes.Structure): @@ -100,13 +88,10 @@ class _Event(ctypes.Union): ('MouseEvent', _MOUSE_EVENT_RECORD), ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), ('MenuEvent', _MENU_EVENT_RECORD), - ('FocusEvent', _FOCUS_EVENT_RECORD) + ('FocusEvent', _FOCUS_EVENT_RECORD), ] - _fields_ = [ - ('EventType', _WORD), - ('Event', _Event) - ] + _fields_ = [('EventType', _WORD), ('Event', _Event)] def reset_console_mode(): @@ -119,9 +104,9 @@ def set_console_mode(): _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) mode = ( - _output_mode.value | - _ENABLE_PROCESSED_OUTPUT | - _ENABLE_VIRTUAL_TERMINAL_PROCESSING + _output_mode.value + | _ENABLE_PROCESSED_OUTPUT + | _ENABLE_VIRTUAL_TERMINAL_PROCESSING ) _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) @@ -133,8 +118,9 @@ def getch(): _ReadConsoleInput( _HANDLE(_hConsoleInput), - lpBuffer, nLength, - ctypes.byref(lpNumberOfEventsRead) + lpBuffer, + nLength, + ctypes.byref(lpNumberOfEventsRead), ) char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index c0ccff4c..ecf8a6d3 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -8,7 +8,6 @@ class TextIOOutputWrapper(base.TextIO): - def __init__(self, stream: base.TextIO): self.stream = stream @@ -64,7 +63,7 @@ def __exit__( self, __t: Type[BaseException] | None, __value: BaseException | None, - __traceback: TracebackType | None + __traceback: TracebackType | None, ) -> None: return self.stream.__exit__(__t, __value, __traceback) @@ -94,7 +93,6 @@ def write(self, data): class LastLineStream(TextIOOutputWrapper): - line: str = '' def seekable(self) -> bool: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 57264ed5..fc09f5ba 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -252,11 +252,7 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, - *args, - fixed_colors=None, - gradient_colors=None, - **kwargs + self, *args, fixed_colors=None, gradient_colors=None, **kwargs ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -400,9 +396,8 @@ def __init__( ): self.samples = samples self.key_prefix = ( - key_prefix if key_prefix else - self.__class__.__name__ - ) + '_' + key_prefix if key_prefix else self.__class__.__name__ + ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): @@ -465,7 +460,6 @@ def __init__( format_NA='ETA: N/A', **kwargs, ): - if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -819,6 +813,7 @@ def get_format( class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' + max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], types.Optional[int], @@ -882,6 +877,7 @@ def __call__( class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' + fg: terminal.OptionalColor | None = colors.gradient bg: terminal.OptionalColor | None = None @@ -1259,12 +1255,19 @@ def __call__( # type: ignore center_left = int((width - center_len) / 2) center_right = center_left + center_len - return self._apply_colors( - bar[:center_left], data, - ) + self._apply_colors( - center, data, - ) + self._apply_colors( - bar[center_right:], data, + return ( + self._apply_colors( + bar[:center_left], + data, + ) + + self._apply_colors( + center, + data, + ) + + self._apply_colors( + bar[center_right:], + data, + ) ) diff --git a/tests/test_color.py b/tests/test_color.py index 3b5f5a15..e1b2c487 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -25,6 +25,7 @@ def test_color_environment_variables(monkeypatch, variable): bar = progressbar.ProgressBar() assert not bar.enable_colors + def test_enable_colors_flags(): bar = progressbar.ProgressBar(enable_colors=True) assert bar.enable_colors @@ -32,7 +33,9 @@ def test_enable_colors_flags(): bar = progressbar.ProgressBar(enable_colors=False) assert not bar.enable_colors - bar = progressbar.ProgressBar(enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR) + bar = progressbar.ProgressBar( + enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR + ) assert bar.enable_colors with pytest.raises(ValueError): From a66da3402334a9fbc30f8fc1f4028e197f13dd38 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 18 Mar 2023 12:25:49 +0100 Subject: [PATCH 088/118] Python 3.8 compatible --- progressbar/multi.py | 2 +- progressbar/terminal/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/progressbar/multi.py b/progressbar/multi.py index dff82d60..7922a812 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -38,7 +38,7 @@ class SortKey(str, enum.Enum): PERCENTAGE = 'percentage' -class MultiBar(dict[str, bar.ProgressBar]): +class MultiBar(typing.Dict[str, bar.ProgressBar]): fd: typing.TextIO _buffer: io.StringIO diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index b31e902e..469e37fa 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -450,7 +450,7 @@ def get_color(self, value: float) -> Color: return color -OptionalColor = Color | ColorGradient | None +OptionalColor = types.Union[Color, ColorGradient, None] def get_color(value: float, color: OptionalColor) -> Color | None: From 041dab30b8eba243b19ea685c77ebc13f43ad124 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Aug 2023 03:09:26 +0200 Subject: [PATCH 089/118] updated stale file --- .github/workflows/stale.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3169ca3b..0740d1a1 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,9 +12,4 @@ jobs: - uses: actions/stale@v8 with: days-before-stale: 30 - exempt-issue-labels: | - in-progress - help-wanted - pinned - security - enhancement \ No newline at end of file + exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement From 1e549e4752b26724c946c98b98e2067eefb6d251 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Mon, 21 Aug 2023 11:44:51 +0800 Subject: [PATCH 090/118] Fix typos Found via `codespell -L datas` --- docs/conf.py | 2 +- progressbar/terminal/base.py | 2 +- progressbar/widgets.py | 2 +- tests/conftest.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 15d5ba34..757b45af 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -303,7 +303,7 @@ # The format is a list of tuples containing the path and title. # epub_pre_files = [] -# HTML files shat should be inserted after the pages created by sphinx. +# HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 469e37fa..0f7fac55 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -309,7 +309,7 @@ class Color( To make a custom color the only required arguments are the RGB values. The other values will be automatically interpolated from that if needed, - but you can be more explicity if you wish. + but you can be more explicitly if you wish. ''' __slots__ = () diff --git a/progressbar/widgets.py b/progressbar/widgets.py index fc09f5ba..1487731e 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -207,7 +207,7 @@ class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): Variables available: - min_width: Only display the widget if at least `min_width` is left - max_width: Only display the widget if at most `max_width` is left - - weight: Widgets with a higher `weigth` will be calculated before widgets + - weight: Widgets with a higher `weight` will be calculated before widgets with a lower one - copy: Copy this widget when initializing the progress bar so the progressbar can be reused. Some widgets such as the FormatCustomText diff --git a/tests/conftest.py b/tests/conftest.py index 88832759..65e0cbf9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,7 +31,7 @@ def small_interval(monkeypatch): @pytest.fixture(autouse=True) def sleep_faster(monkeypatch): # The timezone offset in seconds, add 10 seconds to make sure we don't - # accidently get the wrong hour + # accidentally get the wrong hour offset_seconds = (datetime.now() - datetime.utcnow()).seconds + 10 offset_hours = int(offset_seconds / 3600) From f8af70e1399ec0b8385fe3f8983fff7f1f265296 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 21 Aug 2023 03:18:22 +0200 Subject: [PATCH 091/118] Removing old stale file --- .github/stale.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index fcf5a157..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - in-progress - - help-wanted - - pinned - - security - - enhancement -# Label to use when marking an issue as stale -staleLabel: no-activity -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false From 9900b1b816b9c77d50eed76c4e7c1532b6849a43 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 27 Aug 2023 22:40:35 +0200 Subject: [PATCH 092/118] code style improvements, added ruff, etc... --- docs/_theme/flask_theme_support.py | 145 ++++----- docs/conf.py | 34 +- examples.py | 232 +++++++++----- progressbar/__init__.py | 2 + progressbar/bar.py | 76 ++--- progressbar/multi.py | 9 +- progressbar/shortcuts.py | 4 +- progressbar/utils.py | 4 + progressbar/widgets.py | 432 ++++++++++++++------------ pyrightconfig.json | 12 +- setup.cfg | 22 ++ setup.py | 3 +- tests/conftest.py | 6 +- tests/original_examples.py | 151 ++++++--- tests/test_backwards_compatibility.py | 2 +- tests/test_color.py | 57 +++- tests/test_custom_widgets.py | 31 +- tests/test_data.py | 31 +- tests/test_empty.py | 1 - tests/test_end.py | 15 +- tests/test_flush.py | 1 - tests/test_iterators.py | 15 +- tests/test_monitor_progress.py | 278 ++++++++++------- tests/test_multibar.py | 78 ++++- tests/test_progressbar.py | 5 +- tests/test_samples.py | 4 +- tests/test_speed.py | 50 +-- tests/test_terminal.py | 44 ++- tests/test_timed.py | 30 +- tests/test_timer.py | 43 ++- tests/test_unicode.py | 14 +- tests/test_unknown_length.py | 6 +- tests/test_utils.py | 31 +- tests/test_widgets.py | 18 +- tests/test_with.py | 1 - tox.ini | 25 +- 36 files changed, 1181 insertions(+), 731 deletions(-) diff --git a/docs/_theme/flask_theme_support.py b/docs/_theme/flask_theme_support.py index 555c116d..0dcf53b7 100644 --- a/docs/_theme/flask_theme_support.py +++ b/docs/_theme/flask_theme_support.py @@ -1,7 +1,19 @@ # flasky extensions. flasky pygments style based on tango style from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal +from pygments.token import ( + Keyword, + Name, + Comment, + String, + Error, + Number, + Operator, + Generic, + Whitespace, + Punctuation, + Other, + Literal, +) class FlaskyStyle(Style): @@ -11,76 +23,67 @@ class FlaskyStyle(Style): styles = { # No corresponding class for the following: # Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - - Punctuation: "bold #000000", # class: 'p' - + Whitespace: "underline #f8f8f8", # class: 'w' + Error: "#a40000 border:#ef2929", # class: 'err' + Other: "#000000", # class 'x' + Comment: "italic #8f5902", # class: 'c' + Comment.Preproc: "noitalic", # class: 'cp' + Keyword: "bold #004461", # class: 'k' + Keyword.Constant: "bold #004461", # class: 'kc' + Keyword.Declaration: "bold #004461", # class: 'kd' + Keyword.Namespace: "bold #004461", # class: 'kn' + Keyword.Pseudo: "bold #004461", # class: 'kp' + Keyword.Reserved: "bold #004461", # class: 'kr' + Keyword.Type: "bold #004461", # class: 'kt' + Operator: "#582800", # class: 'o' + Operator.Word: "bold #004461", # class: 'ow' - like keywords + Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - - Number: "#990000", # class: 'm' - - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' + Name: "#000000", # class: 'n' + Name.Attribute: "#c4a000", # class: 'na' - to be revised + Name.Builtin: "#004461", # class: 'nb' + Name.Builtin.Pseudo: "#3465a4", # class: 'bp' + Name.Class: "#000000", # class: 'nc' - to be revised + Name.Constant: "#000000", # class: 'no' - to be revised + Name.Decorator: "#888", # class: 'nd' - to be revised + Name.Entity: "#ce5c00", # class: 'ni' + Name.Exception: "bold #cc0000", # class: 'ne' + Name.Function: "#000000", # class: 'nf' + Name.Property: "#000000", # class: 'py' + Name.Label: "#f57900", # class: 'nl' + Name.Namespace: "#000000", # class: 'nn' - to be revised + Name.Other: "#000000", # class: 'nx' + Name.Tag: "bold #004461", # class: 'nt' - like a keyword + Name.Variable: "#000000", # class: 'nv' - to be revised + Name.Variable.Class: "#000000", # class: 'vc' - to be revised + Name.Variable.Global: "#000000", # class: 'vg' - to be revised + Name.Variable.Instance: "#000000", # class: 'vi' - to be revised + Number: "#990000", # class: 'm' + Literal: "#000000", # class: 'l' + Literal.Date: "#000000", # class: 'ld' + String: "#4e9a06", # class: 's' + String.Backtick: "#4e9a06", # class: 'sb' + String.Char: "#4e9a06", # class: 'sc' + String.Doc: "italic #8f5902", # class: 'sd' - like a comment + String.Double: "#4e9a06", # class: 's2' + String.Escape: "#4e9a06", # class: 'se' + String.Heredoc: "#4e9a06", # class: 'sh' + String.Interpol: "#4e9a06", # class: 'si' + String.Other: "#4e9a06", # class: 'sx' + String.Regex: "#4e9a06", # class: 'sr' + String.Single: "#4e9a06", # class: 's1' + String.Symbol: "#4e9a06", # class: 'ss' + Generic: "#000000", # class: 'g' + Generic.Deleted: "#a40000", # class: 'gd' + Generic.Emph: "italic #000000", # class: 'ge' + Generic.Error: "#ef2929", # class: 'gr' + Generic.Heading: "bold #000080", # class: 'gh' + Generic.Inserted: "#00A000", # class: 'gi' + Generic.Output: "#888", # class: 'go' + Generic.Prompt: "#745334", # class: 'gp' + Generic.Strong: "bold #000000", # class: 'gs' + Generic.Subheading: "bold #800080", # class: 'gu' + Generic.Traceback: "bold #a40000", # class: 'gt' } diff --git a/docs/conf.py b/docs/conf.py index 757b45af..ecc74ba7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) -from progressbar import __about__ as metadata +from progressbar import __about__ as metadata # noqa: E402 # -- General configuration ----------------------------------------------- @@ -198,10 +198,8 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. #'preamble': '', } @@ -209,8 +207,13 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', '%s.tex' % project_slug, u'%s Documentation' % project, - metadata.__author__, 'manual'), + ( + 'index', + '%s.tex' % project_slug, + u'%s Documentation' % project, + metadata.__author__, + 'manual', + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -239,8 +242,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', project_slug.lower(), u'%s Documentation' % project, - [metadata.__author__], 1) + ( + 'index', + project_slug.lower(), + u'%s Documentation' % project, + [metadata.__author__], + 1, + ) ] # If true, show URL addresses after external links. @@ -253,9 +261,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', project_slug, u'%s Documentation' % project, - metadata.__author__, project_slug, 'One line description of project.', - 'Miscellaneous'), + ( + 'index', + project_slug, + u'%s Documentation' % project, + metadata.__author__, + project_slug, + 'One line description of project.', + 'Miscellaneous', + ), ] # Documents to append as an appendix to all manuals. diff --git a/examples.py b/examples.py index 1c033839..2a15b920 100644 --- a/examples.py +++ b/examples.py @@ -31,7 +31,7 @@ def wrapped(*args, **kwargs): @example def fast_example(): - ''' Updates bar really quickly to cause flickering ''' + '''Updates bar really quickly to cause flickering''' with progressbar.ProgressBar(widgets=[progressbar.Bar()]) as bar: for i in range(100): bar.update(int(i / 10), force=True) @@ -96,12 +96,14 @@ def color_bar_example(): def color_bar_animated_marker_example(): widgets = [ # Colored animated marker with colored fill: - progressbar.Bar(marker=progressbar.AnimatedMarker( - fill='x', - # fill='█', - fill_wrap='\x1b[32m{}\x1b[39m', - marker_wrap='\x1b[31m{}\x1b[39m', - )), + progressbar.Bar( + marker=progressbar.AnimatedMarker( + fill='x', + # fill='█', + fill_wrap='\x1b[32m{}\x1b[39m', + marker_wrap='\x1b[31m{}\x1b[39m', + ) + ), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start() for i in range(10): @@ -117,7 +119,7 @@ def multi_range_bar_example(): '\x1b[32m█\x1b[39m', # Done '\x1b[33m#\x1b[39m', # Processing '\x1b[31m.\x1b[39m', # Scheduling - ' ' # Not started + ' ', # Not started ] widgets = [progressbar.MultiRangeBar("amounts", markers=markers)] amounts = [0] * (len(markers) - 1) + [25] @@ -150,7 +152,8 @@ def multi_progress_bar_example(left=True): widgets = [ progressbar.Percentage(), - ' ', progressbar.MultiProgressBar('jobs', fill_left=left), + ' ', + progressbar.MultiProgressBar('jobs', fill_left=left), ] max_value = sum([total for progress, total in jobs]) @@ -202,10 +205,14 @@ def percentage_label_bar_example(): @example def file_transfer_example(): widgets = [ - 'Test: ', progressbar.Percentage(), - ' ', progressbar.Bar(marker=progressbar.RotatingMarker()), - ' ', progressbar.ETA(), - ' ', progressbar.FileTransferSpeed(), + 'Test: ', + progressbar.Percentage(), + ' ', + progressbar.Bar(marker=progressbar.RotatingMarker()), + ' ', + progressbar.ETA(), + ' ', + progressbar.FileTransferSpeed(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=1000).start() for i in range(100): @@ -220,16 +227,20 @@ class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): ''' It's bigger between 45 and 80 percent ''' + def update(self, bar): if 45 < bar.percentage() < 80: return 'Bigger Now ' + progressbar.FileTransferSpeed.update( - self, bar) + self, bar + ) else: return progressbar.FileTransferSpeed.update(self, bar) widgets = [ CrazyFileTransferSpeed(), - ' <<<', progressbar.Bar(), '>>> ', + ' <<<', + progressbar.Bar(), + '>>> ', progressbar.Percentage(), ' ', progressbar.ETA(), @@ -246,8 +257,10 @@ def update(self, bar): @example def double_bar_example(): widgets = [ - progressbar.Bar('>'), ' ', - progressbar.ETA(), ' ', + progressbar.Bar('>'), + ' ', + progressbar.ETA(), + ' ', progressbar.ReverseBar('<'), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=1000).start() @@ -261,10 +274,14 @@ def double_bar_example(): @example def basic_file_transfer(): widgets = [ - 'Test: ', progressbar.Percentage(), - ' ', progressbar.Bar(marker='0', left='[', right=']'), - ' ', progressbar.ETA(), - ' ', progressbar.FileTransferSpeed(), + 'Test: ', + progressbar.Percentage(), + ' ', + progressbar.Bar(marker='0', left='[', right=']'), + ' ', + progressbar.ETA(), + ' ', + progressbar.FileTransferSpeed(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=500) bar.start() @@ -315,26 +332,34 @@ def progress_with_unavailable_max(): @example def animated_marker(): bar = progressbar.ProgressBar( - widgets=['Working: ', progressbar.AnimatedMarker()]) + widgets=['Working: ', progressbar.AnimatedMarker()] + ) for i in bar((i for i in range(5))): time.sleep(0.1) @example def filling_bar_animated_marker(): - bar = progressbar.ProgressBar(widgets=[ - progressbar.Bar( - marker=progressbar.AnimatedMarker(fill='#'), - ), - ]) + bar = progressbar.ProgressBar( + widgets=[ + progressbar.Bar( + marker=progressbar.AnimatedMarker(fill='#'), + ), + ] + ) for i in bar(range(15)): time.sleep(0.1) @example def counter_and_timer(): - widgets = ['Processed: ', progressbar.Counter('Counter: %(value)05d'), - ' lines (', progressbar.Timer(), ')'] + widgets = [ + 'Processed: ', + progressbar.Counter('Counter: %(value)05d'), + ' lines (', + progressbar.Timer(), + ')', + ] bar = progressbar.ProgressBar(widgets=widgets) for i in bar((i for i in range(15))): time.sleep(0.1) @@ -342,8 +367,9 @@ def counter_and_timer(): @example def format_label(): - widgets = [progressbar.FormatLabel( - 'Processed: %(value)d lines (in: %(elapsed)s)')] + widgets = [ + progressbar.FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)') + ] bar = progressbar.ProgressBar(widgets=widgets) for i in bar((i for i in range(15))): time.sleep(0.1) @@ -406,8 +432,10 @@ def format_label_bouncer(): @example def format_label_rotating_bouncer(): - widgets = [progressbar.FormatLabel('Animated Bouncer: value %(value)d - '), - progressbar.BouncingBar(marker=progressbar.RotatingMarker())] + widgets = [ + progressbar.FormatLabel('Animated Bouncer: value %(value)d - '), + progressbar.BouncingBar(marker=progressbar.RotatingMarker()), + ] bar = progressbar.ProgressBar(widgets=widgets) for i in bar((i for i in range(18))): @@ -416,8 +444,9 @@ def format_label_rotating_bouncer(): @example def with_right_justify(): - with progressbar.ProgressBar(max_value=10, term_width=20, - left_justify=False) as progress: + with progressbar.ProgressBar( + max_value=10, term_width=20, left_justify=False + ) as progress: assert progress.term_width is not None for i in range(10): progress.update(i) @@ -467,16 +496,21 @@ def negative_maximum(): @example def rotating_bouncing_marker(): widgets = [progressbar.BouncingBar(marker=progressbar.RotatingMarker())] - with progressbar.ProgressBar(widgets=widgets, max_value=20, - term_width=10) as progress: + with progressbar.ProgressBar( + widgets=widgets, max_value=20, term_width=10 + ) as progress: for i in range(20): time.sleep(0.1) progress.update(i) - widgets = [progressbar.BouncingBar(marker=progressbar.RotatingMarker(), - fill_left=False)] - with progressbar.ProgressBar(widgets=widgets, max_value=20, - term_width=10) as progress: + widgets = [ + progressbar.BouncingBar( + marker=progressbar.RotatingMarker(), fill_left=False + ) + ] + with progressbar.ProgressBar( + widgets=widgets, max_value=20, term_width=10 + ) as progress: for i in range(20): time.sleep(0.1) progress.update(i) @@ -484,10 +518,13 @@ def rotating_bouncing_marker(): @example def incrementing_bar(): - bar = progressbar.ProgressBar(widgets=[ - progressbar.Percentage(), - progressbar.Bar(), - ], max_value=10).start() + bar = progressbar.ProgressBar( + widgets=[ + progressbar.Percentage(), + progressbar.Bar(), + ], + max_value=10, + ).start() for i in range(10): # do something time.sleep(0.1) @@ -498,13 +535,18 @@ def incrementing_bar(): @example def increment_bar_with_output_redirection(): widgets = [ - 'Test: ', progressbar.Percentage(), - ' ', progressbar.Bar(marker=progressbar.RotatingMarker()), - ' ', progressbar.ETA(), - ' ', progressbar.FileTransferSpeed(), + 'Test: ', + progressbar.Percentage(), + ' ', + progressbar.Bar(marker=progressbar.RotatingMarker()), + ' ', + progressbar.ETA(), + ' ', + progressbar.FileTransferSpeed(), ] - bar = progressbar.ProgressBar(widgets=widgets, max_value=100, - redirect_stdout=True).start() + bar = progressbar.ProgressBar( + widgets=widgets, max_value=100, redirect_stdout=True + ).start() for i in range(10): # do something time.sleep(0.01) @@ -517,12 +559,18 @@ def increment_bar_with_output_redirection(): def eta_types_demonstration(): widgets = [ progressbar.Percentage(), - ' ETA: ', progressbar.ETA(), - ' Adaptive ETA: ', progressbar.AdaptiveETA(), - ' Absolute ETA: ', progressbar.AbsoluteETA(), - ' Transfer Speed: ', progressbar.FileTransferSpeed(), - ' Adaptive Transfer Speed: ', progressbar.AdaptiveTransferSpeed(), - ' ', progressbar.Bar(), + ' ETA: ', + progressbar.ETA(), + ' Adaptive ETA: ', + progressbar.AdaptiveETA(), + ' Absolute ETA: ', + progressbar.AbsoluteETA(), + ' Transfer Speed: ', + progressbar.FileTransferSpeed(), + ' Adaptive Transfer Speed: ', + progressbar.AdaptiveTransferSpeed(), + ' ', + progressbar.Bar(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=500) bar.start() @@ -540,10 +588,14 @@ def eta_types_demonstration(): @example def adaptive_eta_without_value_change(): # Testing progressbar.AdaptiveETA when the value doesn't actually change - bar = progressbar.ProgressBar(widgets=[ - progressbar.AdaptiveETA(), - progressbar.AdaptiveTransferSpeed(), - ], max_value=2, poll_interval=0.0001) + bar = progressbar.ProgressBar( + widgets=[ + progressbar.AdaptiveETA(), + progressbar.AdaptiveTransferSpeed(), + ], + max_value=2, + poll_interval=0.0001, + ) bar.start() for i in range(100): bar.update(1) @@ -564,10 +616,14 @@ def iterator_with_max_value(): @example def eta(): widgets = [ - 'Test: ', progressbar.Percentage(), - ' | ETA: ', progressbar.ETA(), - ' | AbsoluteETA: ', progressbar.AbsoluteETA(), - ' | AdaptiveETA: ', progressbar.AdaptiveETA(), + 'Test: ', + progressbar.Percentage(), + ' | ETA: ', + progressbar.ETA(), + ' | AbsoluteETA: ', + progressbar.AbsoluteETA(), + ' | AdaptiveETA: ', + progressbar.AdaptiveETA(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=50).start() for i in range(50): @@ -622,14 +678,16 @@ def user_variables(): num_subtasks = sum(len(x) for x in tasks.values()) with progressbar.ProgressBar( - prefix='{variables.task} >> {variables.subtask}', - variables={'task': '--', 'subtask': '--'}, - max_value=10 * num_subtasks) as bar: + prefix='{variables.task} >> {variables.subtask}', + variables={'task': '--', 'subtask': '--'}, + max_value=10 * num_subtasks, + ) as bar: for tasks_name, subtasks in tasks.items(): for subtask_name in subtasks: for i in range(10): - bar.update(bar.value + 1, task=tasks_name, - subtask=subtask_name) + bar.update( + bar.value + 1, task=tasks_name, subtask=subtask_name + ) time.sleep(0.1) @@ -643,11 +701,13 @@ def format_custom_text(): ), ) - bar = progressbar.ProgressBar(widgets=[ - format_custom_text, - ' :: ', - progressbar.Percentage(), - ]) + bar = progressbar.ProgressBar( + widgets=[ + format_custom_text, + ' :: ', + progressbar.Percentage(), + ] + ) for i in bar(range(25)): format_custom_text.update_mapping(eggs=i * 2) time.sleep(0.1) @@ -666,9 +726,13 @@ def gen(): for x in range(200): yield None - widgets = [progressbar.AdaptiveETA(), ' ', - progressbar.ETA(), ' ', - progressbar.Timer()] + widgets = [ + progressbar.AdaptiveETA(), + ' ', + progressbar.ETA(), + ' ', + progressbar.Timer(), + ] bar = progressbar.ProgressBar(widgets=widgets) for i in bar(gen()): @@ -681,9 +745,14 @@ def gen(): for x in range(200): yield None - widgets = [progressbar.Counter(), ' ', - progressbar.Percentage(), ' ', - progressbar.SimpleProgress(), ' '] + widgets = [ + progressbar.Counter(), + ' ', + progressbar.Percentage(), + ' ', + progressbar.SimpleProgress(), + ' ', + ] bar = progressbar.ProgressBar(widgets=widgets) for i in bar(gen()): @@ -693,7 +762,6 @@ def gen(): def test(*tests): if tests: for example in examples: - for test in tests: if test in example.__name__: example() diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 55525543..4a43eb67 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,6 +35,7 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin +from .widgets import SliceableDeque from .terminal.stream import LineOffsetStreamWrapper from .multi import SortKey, MultiBar @@ -78,4 +79,5 @@ 'LineOffsetStreamWrapper', 'MultiBar', 'SortKey', + 'SliceableDeque', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index d9616f68..dcfdb333 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -166,13 +166,13 @@ class DefaultFdMixin(ProgressBarMixinBase): enable_colors: terminal.ColorSupport | bool | None = terminal.color_support def __init__( - self, - fd: base.IO = sys.stderr, - is_terminal: bool | None = None, - line_breaks: bool | None = None, - enable_colors: terminal.ColorSupport | None = None, - line_offset: int = 0, - **kwargs, + self, + fd: base.IO = sys.stderr, + is_terminal: bool | None = None, + line_breaks: bool | None = None, + enable_colors: terminal.ColorSupport | None = None, + line_offset: int = 0, + **kwargs, ): if fd is sys.stdout: fd = utils.streams.original_stdout @@ -282,7 +282,7 @@ def _format_widgets(self): for index, widget in enumerate(self.widgets): if isinstance( - widget, widgets.WidgetBase + widget, widgets.WidgetBase ) and not widget.check_size(self): continue elif isinstance(widget, widgets.AutoWidthWidgetBase): @@ -359,10 +359,10 @@ class StdRedirectMixin(DefaultFdMixin): _stderr: base.IO def __init__( - self, - redirect_stderr: bool = False, - redirect_stdout: bool = False, - **kwargs, + self, + redirect_stderr: bool = False, + redirect_stdout: bool = False, + **kwargs, ): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr @@ -490,23 +490,23 @@ class ProgressBar( paused: bool = False def __init__( - self, - min_value: T = 0, - max_value: T | types.Type[base.UnknownLength] | None = None, - widgets: types.Optional[ - types.Sequence[widgets_module.WidgetBase | str] - ] = None, - left_justify: bool = True, - initial_value: T = 0, - poll_interval: types.Optional[float] = None, - widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None, - custom_len: types.Callable[[str], int] = utils.len_color, - max_error=True, - prefix=None, - suffix=None, - variables=None, - min_poll_interval=None, - **kwargs, + self, + min_value: T = 0, + max_value: T | types.Type[base.UnknownLength] | None = None, + widgets: types.Optional[ + types.Sequence[widgets_module.WidgetBase | str] + ] = None, + left_justify: bool = True, + initial_value: T = 0, + poll_interval: types.Optional[float] = None, + widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None, + custom_len: types.Callable[[str], int] = utils.len_color, + max_error=True, + prefix=None, + suffix=None, + variables=None, + min_poll_interval=None, + **kwargs, ): ''' Initializes a progress bar with sane defaults @@ -573,8 +573,8 @@ def __init__( min_poll_interval, default=None ) self._MINIMUM_UPDATE_INTERVAL = ( - utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) - or self._MINIMUM_UPDATE_INTERVAL + utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) + or self._MINIMUM_UPDATE_INTERVAL ) # Note that the _MINIMUM_UPDATE_INTERVAL sets the minimum in case of @@ -710,7 +710,7 @@ def data(self) -> types.Dict[str, types.Any]: total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.0), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -844,9 +844,9 @@ def update(self, value=None, force=False, **kwargs): return self.update(value, force=force, **kwargs) if ( - value is not None - and value is not base.UnknownLength - and isinstance(value, int) + value is not None + and value is not base.UnknownLength + and isinstance(value, int) ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update @@ -961,9 +961,9 @@ def start(self, max_value=None, init=True): self.next_update = 0 if ( - self.max_value is not base.UnknownLength - and self.max_value is not None - and self.max_value < 0 # type: ignore + self.max_value is not base.UnknownLength + and self.max_value is not None + and self.max_value < 0 # type: ignore ): raise ValueError('max_value out of range, got %r' % self.max_value) diff --git a/progressbar/multi.py b/progressbar/multi.py index 7922a812..5cec34e1 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -158,12 +158,13 @@ def _label_bar(self, bar: bar.ProgressBar): return assert bar.widgets, 'Cannot prepend label to empty progressbar' - self._labeled.add(bar) if self.prepend_label: # pragma: no branch + self._labeled.add(bar) bar.widgets.insert(0, self.label_format.format(label=bar.label)) if self.append_label and bar not in self._labeled: # pragma: no branch + self._labeled.add(bar) bar.widgets.append(self.label_format.format(label=bar.label)) def render(self, flush: bool = True, force: bool = False): @@ -187,7 +188,7 @@ def update(force=True, write=True): # Force update to get the finished format update(write=False) - if self.remove_finished: + if self.remove_finished and expired is not None: if expired >= self._finished_at[bar_]: del self[bar_.label] continue @@ -200,9 +201,7 @@ def update(force=True, write=True): update(force=False) else: output.append( - self.finished_format.format( - label=bar_.label - ) + self.finished_format.format(label=bar_.label) ) elif bar_.started(): update() diff --git a/progressbar/shortcuts.py b/progressbar/shortcuts.py index 9e1502dd..dd61c9cb 100644 --- a/progressbar/shortcuts.py +++ b/progressbar/shortcuts.py @@ -8,7 +8,7 @@ def progressbar( widgets=None, prefix=None, suffix=None, - **kwargs + **kwargs, ): progressbar = bar.ProgressBar( min_value=min_value, @@ -16,7 +16,7 @@ def progressbar( widgets=widgets, prefix=prefix, suffix=suffix, - **kwargs + **kwargs, ) for result in progressbar(iterator): diff --git a/progressbar/utils.py b/progressbar/utils.py index 256dd98c..a1d99fec 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -153,6 +153,10 @@ def no_color(value: StringT) -> StringT: 'abc' >>> str(no_color('\u001b[1234]abc')) 'abc' + >>> no_color(123) + Traceback (most recent call last): + ... + TypeError: `value` must be a string or bytes, got 123 ''' if isinstance(value, bytes): pattern: bytes = '\\\u001b\\[.*?[@-~]'.encode() diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 1487731e..944221cc 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -7,7 +7,7 @@ import pprint import sys import typing -from typing import Callable +from collections import deque from python_utils import converters, types @@ -24,6 +24,41 @@ Data = types.Dict[str, types.Any] FormatString = typing.Optional[str] +T = typing.TypeVar('T') + + +class SliceableDeque(typing.Generic[T], deque): + def __getitem__( + self, + index: typing.Union[int, slice], + ) -> typing.Union[T, deque[T]]: + if isinstance(index, slice): + start, stop, step = index.indices(len(self)) + return self.__class__(self[i] for i in range(start, stop, step)) + else: + return super().__getitem__(index) + + def pop(self, index=-1) -> T: + # We need to allow for an index but a deque only allows the removal of + # the first or last item. + if index == 0: + return super().popleft() + elif index == -1 or index == len(self) - 1: + return super().pop() + else: + raise IndexError( + 'Only index 0 and the last index (`N-1` or `-1`) are supported' + ) + + def __eq__(self, other): + # Allow for comparison with a list or tuple + if isinstance(other, list): + return list(self) == other + elif isinstance(other, tuple): + return tuple(self) == other + else: + return super().__eq__(other) + def string_or_lambda(input_): if isinstance(input_, str): @@ -83,8 +118,8 @@ def wrap(*args, **kwargs): def create_marker(marker, wrap=None): def _marker(progress, data, width): if ( - progress.max_value is not base.UnknownLength - and progress.max_value > 0 + progress.max_value is not base.UnknownLength + and progress.max_value > 0 ): length = int(progress.value / progress.max_value * width) return marker * length @@ -94,7 +129,7 @@ def _marker(progress, data, width): if isinstance(marker, str): marker = converters.to_unicode(marker) assert ( - utils.len_color(marker) == 1 + utils.len_color(marker) == 1 ), 'Markers are required to be 1 char' return wrapper(_marker, wrap) else: @@ -122,18 +157,18 @@ def __init__(self, format: str, new_style: bool = False, **kwargs): self.format = format def get_format( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: return format or self.format def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: '''Formats the widget into a string''' format = self.get_format(progress, data, format) @@ -226,16 +261,16 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: _fixed_colors: dict[str, terminal.Color | None] = dict() _gradient_colors: dict[str, terminal.OptionalColor | None] = dict() - _len: Callable[[str | bytes], int] = len + _len: typing.Callable[[str | bytes], int] = len @functools.cached_property def uses_colors(self): - for key, value in self._gradient_colors.items(): - if value is not None: + for key, value in self._gradient_colors.items(): # pragma: no branch + if value is not None: # pragma: no branch return True - for key, value in self._fixed_colors.items(): - if value is not None: + for key, value in self._fixed_colors.items(): # pragma: no branch + if value is not None: # pragma: no branch return True return False @@ -252,7 +287,7 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, *args, fixed_colors=None, gradient_colors=None, **kwargs + self, *args, fixed_colors=None, gradient_colors=None, **kwargs ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -276,10 +311,10 @@ class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): @abc.abstractmethod def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ) -> str: '''Updates the widget providing the total width the widget must fill. @@ -325,10 +360,10 @@ def __init__(self, format: str, **kwargs): WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): for name, (key, transform) in self.mapping.items(): try: @@ -382,32 +417,37 @@ class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): >>> samples = SamplesMixin(samples=datetime.timedelta(seconds=1)) >>> _, value = samples(progress, None) >>> value - [1, 1] + SliceableDeque([1, 1]) >>> samples(progress, None, True) == (datetime.timedelta(seconds=1), 0) True ''' def __init__( - self, - samples=datetime.timedelta(seconds=2), - key_prefix=None, - **kwargs, + self, + samples=datetime.timedelta(seconds=2), + key_prefix=None, + **kwargs, ): self.samples = samples self.key_prefix = ( - key_prefix if key_prefix else self.__class__.__name__ - ) + '_' + key_prefix if key_prefix else self.__class__.__name__ + ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): - return progress.extra.setdefault(self.key_prefix + 'sample_times', []) + return progress.extra.setdefault( + self.key_prefix + 'sample_times', SliceableDeque() + ) def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): - return progress.extra.setdefault(self.key_prefix + 'sample_values', []) + return progress.extra.setdefault( + self.key_prefix + 'sample_values', SliceableDeque() + ) def __call__( - self, progress: ProgressBarMixinBase, data: Data, delta: bool = False + self, progress: ProgressBarMixinBase, data: Data, + delta: bool = False ): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -426,9 +466,9 @@ def __call__( minimum_time = progress.last_update_time - self.samples minimum_value = sample_values[-1] while ( - sample_times[2:] - and minimum_time > sample_times[1] - and minimum_value > sample_values[1] + sample_times[2:] + and minimum_time > sample_times[1] + and minimum_value > sample_values[1] ): sample_times.pop(0) sample_values.pop(0) @@ -452,13 +492,13 @@ class ETA(Timer): '''WidgetBase which attempts to estimate the time of arrival.''' def __init__( - self, - format_not_started='ETA: --:--:--', - format_finished='Time: %(elapsed)8s', - format='ETA: %(eta)8s', - format_zero='ETA: 00:00:00', - format_NA='ETA: N/A', - **kwargs, + self, + format_not_started='ETA: --:--:--', + format_finished='Time: %(elapsed)8s', + format='ETA: %(eta)8s', + format_zero='ETA: 00:00:00', + format_NA='ETA: N/A', + **kwargs, ): if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -471,7 +511,7 @@ def __init__( self.format_NA = format_NA def _calculate_eta( - self, progress: ProgressBarMixinBase, data: Data, value, elapsed + self, progress: ProgressBarMixinBase, data: Data, value, elapsed ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: @@ -485,11 +525,11 @@ def _calculate_eta( return eta_seconds def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): '''Updates the widget to show the ETA or total time when finished.''' if value is None: @@ -532,7 +572,7 @@ class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' def _calculate_eta( - self, progress: ProgressBarMixinBase, data: Data, value, elapsed + self, progress: ProgressBarMixinBase, data: Data, value, elapsed ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() @@ -542,11 +582,11 @@ def _calculate_eta( return datetime.datetime.max def __init__( - self, - format_not_started='Estimated finish time: ----/--/-- --:--:--', - format_finished='Finished at: %(elapsed)s', - format='Estimated finish time: %(eta)s', - **kwargs, + self, + format_not_started='Estimated finish time: ----/--/-- --:--:--', + format_finished='Finished at: %(elapsed)s', + format='Estimated finish time: %(eta)s', + **kwargs, ): ETA.__init__( self, @@ -569,11 +609,11 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, progress, data, delta=True @@ -594,12 +634,12 @@ class DataSize(FormatWidgetMixin, WidgetBase): ''' def __init__( - self, - variable='value', - format='%(scaled)5.1f %(prefix)s%(unit)s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + variable='value', + format='%(scaled)5.1f %(prefix)s%(unit)s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.variable = variable self.unit = unit @@ -608,10 +648,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data[self.variable] if value is not None: @@ -632,12 +672,12 @@ class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' def __init__( - self, - format='%(scaled)5.1f %(prefix)s%(unit)-s/s', - inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + format='%(scaled)5.1f %(prefix)s%(unit)-s/s', + inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.unit = unit self.prefixes = prefixes @@ -650,11 +690,11 @@ def _speed(self, value, elapsed): return utils.scale_1024(speed, len(self.prefixes)) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): '''Updates the widget with the current SI prefixed speed.''' if value is None: @@ -665,10 +705,10 @@ def __call__( ) if ( - value is not None - and elapsed is not None - and elapsed > 2e-6 - and value > 2e-6 + value is not None + and elapsed is not None + and elapsed > 2e-6 + and value > 2e-6 ): # =~ 0 scaled, power = self._speed(value, elapsed) else: @@ -697,11 +737,11 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, progress, data, delta=True @@ -715,13 +755,13 @@ class AnimatedMarker(TimeSensitiveWidgetBase): ''' def __init__( - self, - markers='|/-\\', - default=None, - fill='', - marker_wrap=None, - fill_wrap=None, - **kwargs, + self, + markers='|/-\\', + default=None, + fill='', + marker_wrap=None, + fill_wrap=None, + **kwargs, ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) @@ -774,7 +814,7 @@ def __init__(self, format='%(value)d', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def __call__( - self, progress: ProgressBarMixinBase, data: Data, format=None + self, progress: ProgressBarMixinBase, data: Data, format=None ): return FormatWidgetMixin.__call__(self, progress, data, format) @@ -799,7 +839,7 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def get_format( - self, progress: ProgressBarMixinBase, data: Data, format=None + self, progress: ProgressBarMixinBase, data: Data, format=None ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) @@ -828,7 +868,7 @@ def __init__(self, format=DEFAULT_FORMAT, **kwargs): self.max_width_cache['default'] = self.max_width or 0 def __call__( - self, progress: ProgressBarMixinBase, data: Data, format=None + self, progress: ProgressBarMixinBase, data: Data, format=None ): # If max_value is not available, display N/A if data.get('max_value'): @@ -882,14 +922,14 @@ class Bar(AutoWidthWidgetBase): bg: terminal.OptionalColor | None = None def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=True, - marker_wrap=None, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=True, + marker_wrap=None, + **kwargs, ): '''Creates a customizable progress bar. @@ -911,11 +951,11 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents''' @@ -943,13 +983,13 @@ class ReverseBar(Bar): '''A bar which has a marker that goes from right to left''' def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=False, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=False, + **kwargs, ): '''Creates a customizable progress bar. @@ -976,10 +1016,10 @@ class BouncingBar(Bar, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(milliseconds=100) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ): '''Updates the progress bar and its subcomponents''' @@ -1013,10 +1053,10 @@ class FormatCustomText(FormatWidgetMixin, WidgetBase): copy = False def __init__( - self, - format: str, - mapping: types.Optional[types.Dict[str, types.Any]] = None, - **kwargs, + self, + format: str, + mapping: types.Optional[types.Dict[str, types.Any]] = None, + **kwargs, ): self.format = format self.mapping = mapping or self.mapping @@ -1027,10 +1067,10 @@ def update_mapping(self, **mapping: types.Dict[str, types.Any]): self.mapping.update(mapping) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): return FormatWidgetMixin.__call__( self, progress, self.mapping, format or self.format @@ -1072,10 +1112,10 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): return data['variables'][self.name] or [] def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ): '''Updates the progress bar and its subcomponents''' @@ -1108,12 +1148,12 @@ def __call__( class MultiProgressBar(MultiRangeBar): def __init__( - self, - name, - # NOTE: the markers are not whitespace even though some - # terminals don't show the characters correctly! - markers=' ▁▂▃▄▅▆▇█', - **kwargs, + self, + name, + # NOTE: the markers are not whitespace even though some + # terminals don't show the characters correctly! + markers=' ▁▂▃▄▅▆▇█', + **kwargs, ): MultiRangeBar.__init__( self, @@ -1175,11 +1215,11 @@ class GranularBar(AutoWidthWidgetBase): ''' def __init__( - self, - markers=GranularMarkers.smooth, - left='|', - right='|', - **kwargs, + self, + markers=GranularMarkers.smooth, + left='|', + right='|', + **kwargs, ): '''Creates a customizable progress bar. @@ -1196,10 +1236,10 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) @@ -1209,8 +1249,8 @@ def __call__( # mypy doesn't get that the first part of the if statement makes sure # we get the correct type if ( - max_value is not base.UnknownLength - and max_value > 0 # type: ignore + max_value is not base.UnknownLength + and max_value > 0 # type: ignore ): percent = progress.value / max_value # type: ignore else: @@ -1241,11 +1281,11 @@ def __init__(self, format, **kwargs): Bar.__init__(self, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) bar = Bar.__call__(self, progress, data, width, color=False) @@ -1256,18 +1296,18 @@ def __call__( # type: ignore center_right = center_left + center_len return ( - self._apply_colors( - bar[:center_left], - data, - ) - + self._apply_colors( - center, - data, - ) - + self._apply_colors( - bar[center_right:], - data, - ) + self._apply_colors( + bar[:center_left], + data, + ) + + self._apply_colors( + center, + data, + ) + + self._apply_colors( + bar[center_right:], + data, + ) ) @@ -1281,11 +1321,11 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): FormatLabelBar.__init__(self, format, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): return super().__call__(progress, data, width, format=format) @@ -1294,12 +1334,12 @@ class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' def __init__( - self, - name, - format='{name}: {formatted_value}', - width=6, - precision=3, - **kwargs, + self, + name, + format='{name}: {formatted_value}', + width=6, + precision=3, + **kwargs, ): '''Creates a Variable associated with the given name.''' self.format = format @@ -1309,10 +1349,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data['variables'][self.name] context = data.copy() @@ -1350,20 +1390,20 @@ class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(seconds=1) def __init__( - self, - format='Current Time: %(current_time)s', - microseconds=False, - **kwargs, + self, + format='Current Time: %(current_time)s', + microseconds=False, + **kwargs, ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): data['current_time'] = self.current_time() data['current_datetime'] = self.current_datetime() diff --git a/pyrightconfig.json b/pyrightconfig.json index 58d8fa22..5e0a8207 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,11 +1,5 @@ { - "include": [ - "progressbar" - ], - "exclude": [ - "examples" - ], - "ignore": [ - "docs" - ], + "include": ["progressbar"], + "exclude": ["examples"], + "ignore": ["docs"], } diff --git a/setup.cfg b/setup.cfg index a67b32e4..cc0059c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,3 +6,25 @@ universal = 1 [upload] sign = 1 + +[codespell] +skip = */htmlcov,./docs/_build,*.asc + +ignore-words-list = datas + +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + .eggs + .tox + +extend-ignore = + W391, + E203, + +[black] +line-length = 79 +skip-string-normalization = true \ No newline at end of file diff --git a/setup.py b/setup.py index 850df2de..25015e61 100644 --- a/setup.py +++ b/setup.py @@ -23,8 +23,7 @@ with open('README.rst') as fh: readme = fh.read() else: - readme = \ - 'See http://pypi.python.org/pypi/%(__package_name__)s/' % about + readme = 'See http://pypi.python.org/pypi/%(__package_name__)s/' % about if __name__ == '__main__': setup( diff --git a/tests/conftest.py b/tests/conftest.py index 65e0cbf9..3d587bb9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,14 +17,16 @@ def pytest_configure(config): logging.basicConfig( - level=LOG_LEVELS.get(config.option.verbose, logging.DEBUG)) + level=LOG_LEVELS.get(config.option.verbose, logging.DEBUG) + ) @pytest.fixture(autouse=True) def small_interval(monkeypatch): # Remove the update limit for tests by default monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1e-6) + progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1e-6 + ) monkeypatch.setattr(timeit, 'default_timer', time.time) diff --git a/tests/original_examples.py b/tests/original_examples.py index 2e521e9d..97803819 100644 --- a/tests/original_examples.py +++ b/tests/original_examples.py @@ -4,15 +4,32 @@ import sys import time -from progressbar import AnimatedMarker, Bar, BouncingBar, Counter, ETA, \ - AdaptiveETA, FileTransferSpeed, FormatLabel, Percentage, \ - ProgressBar, ReverseBar, RotatingMarker, \ - SimpleProgress, Timer, UnknownLength +from progressbar import ( + AnimatedMarker, + Bar, + BouncingBar, + Counter, + ETA, + AdaptiveETA, + FileTransferSpeed, + FormatLabel, + Percentage, + ProgressBar, + ReverseBar, + RotatingMarker, + SimpleProgress, + Timer, + UnknownLength, +) examples = [] + + def example(fn): - try: name = 'Example %d' % int(fn.__name__[7:]) - except: name = fn.__name__ + try: + name = 'Example %d' % int(fn.__name__[7:]) + except Exception: + name = fn.__name__ def wrapped(): try: @@ -25,65 +42,94 @@ def wrapped(): examples.append(wrapped) return wrapped + @example def example0(): pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=300).start() for i in range(300): time.sleep(0.01) - pbar.update(i+1) + pbar.update(i + 1) pbar.finish() + @example def example1(): - widgets = ['Test: ', Percentage(), ' ', Bar(marker=RotatingMarker()), - ' ', ETA(), ' ', FileTransferSpeed()] + widgets = [ + 'Test: ', + Percentage(), + ' ', + Bar(marker=RotatingMarker()), + ' ', + ETA(), + ' ', + FileTransferSpeed(), + ] pbar = ProgressBar(widgets=widgets, maxval=10000).start() for i in range(1000): # do something - pbar.update(10*i+1) + pbar.update(10 * i + 1) pbar.finish() + @example def example2(): class CrazyFileTransferSpeed(FileTransferSpeed): """It's bigger between 45 and 80 percent.""" + def update(self, pbar): if 45 < pbar.percentage() < 80: - return 'Bigger Now ' + FileTransferSpeed.update(self,pbar) + return 'Bigger Now ' + FileTransferSpeed.update(self, pbar) else: - return FileTransferSpeed.update(self,pbar) + return FileTransferSpeed.update(self, pbar) - widgets = [CrazyFileTransferSpeed(),' <<<', Bar(), '>>> ', - Percentage(),' ', ETA()] + widgets = [ + CrazyFileTransferSpeed(), + ' <<<', + Bar(), + '>>> ', + Percentage(), + ' ', + ETA(), + ] pbar = ProgressBar(widgets=widgets, maxval=10000) # maybe do something pbar.start() for i in range(2000): # do something - pbar.update(5*i+1) + pbar.update(5 * i + 1) pbar.finish() + @example def example3(): widgets = [Bar('>'), ' ', ETA(), ' ', ReverseBar('<')] pbar = ProgressBar(widgets=widgets, maxval=10000).start() for i in range(1000): # do something - pbar.update(10*i+1) + pbar.update(10 * i + 1) pbar.finish() + @example def example4(): - widgets = ['Test: ', Percentage(), ' ', - Bar(marker='0',left='[',right=']'), - ' ', ETA(), ' ', FileTransferSpeed()] + widgets = [ + 'Test: ', + Percentage(), + ' ', + Bar(marker='0', left='[', right=']'), + ' ', + ETA(), + ' ', + FileTransferSpeed(), + ] pbar = ProgressBar(widgets=widgets, maxval=500) pbar.start() - for i in range(100,500+1,50): + for i in range(100, 500 + 1, 50): time.sleep(0.2) pbar.update(i) pbar.finish() + @example def example5(): pbar = ProgressBar(widgets=[SimpleProgress()], maxval=17).start() @@ -92,6 +138,7 @@ def example5(): pbar.update(i + 1) pbar.finish() + @example def example6(): pbar = ProgressBar().start() @@ -100,23 +147,27 @@ def example6(): pbar.update(i + 1) pbar.finish() + @example def example7(): pbar = ProgressBar() # Progressbar can guess maxval automatically. for i in pbar(range(80)): time.sleep(0.01) + @example def example8(): pbar = ProgressBar(maxval=80) # Progressbar can't guess maxval. for i in pbar((i for i in range(80))): time.sleep(0.01) + @example def example9(): pbar = ProgressBar(widgets=['Working: ', AnimatedMarker()]) for i in pbar((i for i in range(50))): - time.sleep(.08) + time.sleep(0.08) + @example def example10(): @@ -125,6 +176,7 @@ def example10(): for i in pbar((i for i in range(150))): time.sleep(0.1) + @example def example11(): widgets = [FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)')] @@ -132,6 +184,7 @@ def example11(): for i in pbar((i for i in range(150))): time.sleep(0.1) + @example def example12(): widgets = ['Balloon: ', AnimatedMarker(markers='.oO@* ')] @@ -139,6 +192,7 @@ def example12(): for i in pbar((i for i in range(24))): time.sleep(0.3) + @example def example13(): # You may need python 3.x to see this correctly @@ -147,7 +201,9 @@ def example13(): pbar = ProgressBar(widgets=widgets) for i in pbar((i for i in range(24))): time.sleep(0.3) - except UnicodeError: sys.stdout.write('Unicode error: skipping example') + except UnicodeError: + sys.stdout.write('Unicode error: skipping example') + @example def example14(): @@ -157,7 +213,9 @@ def example14(): pbar = ProgressBar(widgets=widgets) for i in pbar((i for i in range(24))): time.sleep(0.3) - except UnicodeError: sys.stdout.write('Unicode error: skipping example') + except UnicodeError: + sys.stdout.write('Unicode error: skipping example') + @example def example15(): @@ -167,7 +225,9 @@ def example15(): pbar = ProgressBar(widgets=widgets) for i in pbar((i for i in range(24))): time.sleep(0.3) - except UnicodeError: sys.stdout.write('Unicode error: skipping example') + except UnicodeError: + sys.stdout.write('Unicode error: skipping example') + @example def example16(): @@ -176,21 +236,22 @@ def example16(): for i in pbar((i for i in range(180))): time.sleep(0.05) + @example def example17(): - widgets = [FormatLabel('Animated Bouncer: value %(value)d - '), - BouncingBar(marker=RotatingMarker())] + widgets = [ + FormatLabel('Animated Bouncer: value %(value)d - '), + BouncingBar(marker=RotatingMarker()), + ] pbar = ProgressBar(widgets=widgets) for i in pbar((i for i in range(180))): time.sleep(0.05) + @example def example18(): - widgets = [Percentage(), - ' ', Bar(), - ' ', ETA(), - ' ', AdaptiveETA()] + widgets = [Percentage(), ' ', Bar(), ' ', ETA(), ' ', AdaptiveETA()] pbar = ProgressBar(widgets=widgets, maxval=500) pbar.start() for i in range(500): @@ -198,21 +259,29 @@ def example18(): pbar.update(i + 1) pbar.finish() + @example def example19(): - pbar = ProgressBar() - for i in pbar([]): - pass - pbar.finish() + pbar = ProgressBar() + for i in pbar([]): + pass + pbar.finish() + @example def example20(): """Widgets that behave differently when length is unknown""" - widgets = ['[When length is unknown at first]', - ' Progress: ', SimpleProgress(), - ', Percent: ', Percentage(), - ' ', ETA(), - ' ', AdaptiveETA()] + widgets = [ + '[When length is unknown at first]', + ' Progress: ', + SimpleProgress(), + ', Percent: ', + Percentage(), + ' ', + ETA(), + ' ', + AdaptiveETA(), + ] pbar = ProgressBar(widgets=widgets, maxval=UnknownLength) pbar.start() for i in range(20): @@ -222,8 +291,10 @@ def example20(): pbar.update(i + 1) pbar.finish() + if __name__ == '__main__': try: - for example in examples: example() + for example in examples: + example() except KeyboardInterrupt: sys.stdout.write('\nQuitting examples.\n') diff --git a/tests/test_backwards_compatibility.py b/tests/test_backwards_compatibility.py index 027c3f9e..5e66318c 100644 --- a/tests/test_backwards_compatibility.py +++ b/tests/test_backwards_compatibility.py @@ -6,7 +6,7 @@ def test_progressbar_1_widgets(): widgets = [ progressbar.AdaptiveETA(format="Time left: %s"), progressbar.Timer(format="Time passed: %s"), - progressbar.Bar() + progressbar.Bar(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=100).start() diff --git a/tests/test_color.py b/tests/test_color.py index e1b2c487..2478e713 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -5,10 +5,11 @@ @pytest.mark.parametrize( - 'variable', [ + 'variable', + [ 'PROGRESSBAR_ENABLE_COLORS', 'FORCE_COLOR', - ] + ], ) def test_color_environment_variables(monkeypatch, variable): monkeypatch.setattr( @@ -40,3 +41,55 @@ def test_enable_colors_flags(): with pytest.raises(ValueError): progressbar.ProgressBar(enable_colors=12345) + + +class _TestFixedColorSupport(progressbar.widgets.WidgetBase): + _fixed_colors = dict( + fg_none=progressbar.widgets.colors.yellow, + bg_none=None, + ) + + def __call__(self, *args, **kwargs): + pass + + +class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): + _gradient_colors = dict( + fg=progressbar.widgets.colors.gradient, + bg=None, + ) + + def __call__(self, *args, **kwargs): + pass + + +@pytest.mark.parametrize( + 'widget', + [ + progressbar.Percentage, + progressbar.SimpleProgress, + _TestFixedColorSupport, + _TestFixedGradientSupport, + ], +) +def test_color_widgets(widget): + assert widget().uses_colors + print(f'{widget} has colors? {widget.uses_colors}') + + +@pytest.mark.parametrize( + 'widget', + [ + progressbar.Counter, + ], +) +def test_no_color_widgets(widget): + assert not widget().uses_colors + print(f'{widget} has colors? {widget.uses_colors}') + + assert widget( + fixed_colors=_TestFixedColorSupport._fixed_colors + ).uses_colors + assert widget( + gradient_colors=_TestFixedGradientSupport._gradient_colors + ).uses_colors diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index e757ded5..1d3fd517 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -10,8 +10,9 @@ class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): def update(self, pbar): if 45 < pbar.percentage() < 80: - return 'Bigger Now ' + progressbar.FileTransferSpeed.update(self, - pbar) + return 'Bigger Now ' + progressbar.FileTransferSpeed.update( + self, pbar + ) else: return progressbar.FileTransferSpeed.update(self, pbar) @@ -39,9 +40,13 @@ def test_crazy_file_transfer_speed_widget(): def test_variable_widget_widget(): widgets = [ - ' [', progressbar.Timer(), '] ', + ' [', + progressbar.Timer(), + '] ', progressbar.Bar(), - ' (', progressbar.ETA(), ') ', + ' (', + progressbar.ETA(), + ') ', progressbar.Variable('loss'), progressbar.Variable('text'), progressbar.Variable('error', precision=None), @@ -49,13 +54,16 @@ def test_variable_widget_widget(): progressbar.Variable('predefined'), ] - p = progressbar.ProgressBar(widgets=widgets, max_value=1000, - variables=dict(predefined='predefined')) + p = progressbar.ProgressBar( + widgets=widgets, + max_value=1000, + variables=dict(predefined='predefined'), + ) p.start() print('time', time, time.sleep) for i in range(0, 200, 5): time.sleep(0.1) - p.update(i + 1, loss=.5, text='spam', error=1) + p.update(i + 1, loss=0.5, text='spam', error=1) i += 1 p.update(i, text=None) @@ -77,11 +85,12 @@ def test_format_custom_text_widget(): ), ) - bar = progressbar.ProgressBar(widgets=[ - widget, - ]) + bar = progressbar.ProgressBar( + widgets=[ + widget, + ] + ) for i in bar(range(5)): widget.update_mapping(eggs=i * 2) assert widget.mapping['eggs'] == bar.widgets[0].mapping['eggs'] - diff --git a/tests/test_data.py b/tests/test_data.py index 039cffbb..f7566390 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -2,20 +2,23 @@ import progressbar -@pytest.mark.parametrize('value,expected', [ - (None, ' 0.0 B'), - (1, ' 1.0 B'), - (2 ** 10 - 1, '1023.0 B'), - (2 ** 10 + 0, ' 1.0 KiB'), - (2 ** 20, ' 1.0 MiB'), - (2 ** 30, ' 1.0 GiB'), - (2 ** 40, ' 1.0 TiB'), - (2 ** 50, ' 1.0 PiB'), - (2 ** 60, ' 1.0 EiB'), - (2 ** 70, ' 1.0 ZiB'), - (2 ** 80, ' 1.0 YiB'), - (2 ** 90, '1024.0 YiB'), -]) +@pytest.mark.parametrize( + 'value,expected', + [ + (None, ' 0.0 B'), + (1, ' 1.0 B'), + (2**10 - 1, '1023.0 B'), + (2**10 + 0, ' 1.0 KiB'), + (2**20, ' 1.0 MiB'), + (2**30, ' 1.0 GiB'), + (2**40, ' 1.0 TiB'), + (2**50, ' 1.0 PiB'), + (2**60, ' 1.0 EiB'), + (2**70, ' 1.0 ZiB'), + (2**80, ' 1.0 YiB'), + (2**90, '1024.0 YiB'), + ], +) def test_data_size(value, expected): widget = progressbar.DataSize() assert widget(None, dict(value=value)) == expected diff --git a/tests/test_empty.py b/tests/test_empty.py index de6bf09a..ad0a430a 100644 --- a/tests/test_empty.py +++ b/tests/test_empty.py @@ -9,4 +9,3 @@ def test_empty_list(): def test_empty_iterator(): for x in progressbar.ProgressBar(max_value=0)(iter([])): print(x) - diff --git a/tests/test_end.py b/tests/test_end.py index 75d45723..29c232f3 100644 --- a/tests/test_end.py +++ b/tests/test_end.py @@ -6,26 +6,26 @@ def large_interval(monkeypatch): # Remove the update limit for tests by default monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 0.1) + progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 0.1 + ) def test_end(): m = 24514315 p = progressbar.ProgressBar( - widgets=[progressbar.Percentage(), progressbar.Bar()], - max_value=m + widgets=[progressbar.Percentage(), progressbar.Bar()], max_value=m ) for x in range(0, m, 8192): p.update(x) data = p.data() - assert data['percentage'] < 100. + assert data['percentage'] < 100.0 p.finish() data = p.data() - assert data['percentage'] >= 100. + assert data['percentage'] >= 100.0 assert p.value == m @@ -42,10 +42,11 @@ def test_end_100(monkeypatch): data = p.data() import pprint + pprint.pprint(data) - assert data['percentage'] < 100. + assert data['percentage'] < 100.0 p.finish() data = p.data() - assert data['percentage'] >= 100. + assert data['percentage'] >= 100.0 diff --git a/tests/test_flush.py b/tests/test_flush.py index f6336d8d..2c342900 100644 --- a/tests/test_flush.py +++ b/tests/test_flush.py @@ -14,4 +14,3 @@ def test_flush(): if i > 5: time.sleep(0.1) print('post-updates', p.updates) - diff --git a/tests/test_iterators.py b/tests/test_iterators.py index b32c529e..13aec3c4 100644 --- a/tests/test_iterators.py +++ b/tests/test_iterators.py @@ -29,12 +29,14 @@ def test_iterator_without_max_value_error(): def test_iterator_without_max_value(): '''Progressbar can't guess max_value.''' - p = progressbar.ProgressBar(widgets=[ - progressbar.AnimatedMarker(), - progressbar.FormatLabel('%(value)d'), - progressbar.BouncingBar(), - progressbar.BouncingBar(marker=progressbar.RotatingMarker()), - ]) + p = progressbar.ProgressBar( + widgets=[ + progressbar.AnimatedMarker(), + progressbar.FormatLabel('%(value)d'), + progressbar.BouncingBar(), + progressbar.BouncingBar(marker=progressbar.RotatingMarker()), + ] + ) for i in p((i for i in range(10))): time.sleep(0.001) @@ -55,4 +57,3 @@ def test_adding_value(): p.increment(2) with pytest.raises(ValueError): p += 5 - diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index 5dd6f5ee..bac41258 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -4,7 +4,6 @@ pytest_plugins = 'pytester' - SCRIPT = ''' import sys sys.path.append({progressbar_path!r}) @@ -23,9 +22,17 @@ ''' -def _create_script(widgets=None, items=list(range(9)), - loop_code='fake_time.tick(1)', term_width=60, - **kwargs): +def _non_empty_lines(lines): + return [line for line in lines if line.strip()] + + +def _create_script( + widgets=None, + items=list(range(9)), + loop_code='fake_time.tick(1)', + term_width=60, + **kwargs, +): kwargs['term_width'] = term_width # Reindent the loop code @@ -40,8 +47,9 @@ def _create_script(widgets=None, items=list(range(9)), widgets=widgets, kwargs=kwargs, loop_code=indent.join(loop_code), - progressbar_path=os.path.dirname(os.path.dirname( - progressbar.__file__)), + progressbar_path=os.path.dirname( + os.path.dirname(progressbar.__file__) + ), ) print('# Script:') print('#' * 78) @@ -52,126 +60,160 @@ def _create_script(widgets=None, items=list(range(9)), def test_list_example(testdir): - ''' Run the simple example code in a python subprocess and then compare its - stderr to what we expect to see from it. We run it in a subprocess to - best capture its stderr. We expect to see match_lines in order in the - output. This test is just a sanity check to ensure that the progress - bar progresses from 1 to 10, it does not make sure that the ''' - - result = testdir.runpython(testdir.makepyfile(_create_script( - term_width=65, - ))) - result.stderr.lines = [l.rstrip() for l in result.stderr.lines - if l.strip()] + '''Run the simple example code in a python subprocess and then compare its + stderr to what we expect to see from it. We run it in a subprocess to + best capture its stderr. We expect to see match_lines in order in the + output. This test is just a sanity check to ensure that the progress + bar progresses from 1 to 10, it does not make sure that the''' + + result = testdir.runpython( + testdir.makepyfile( + _create_script( + term_width=65, + ) + ) + ) + result.stderr.lines = [ + line.rstrip() for line in _non_empty_lines(result.stderr.lines) + ] pprint.pprint(result.stderr.lines, width=70) - result.stderr.fnmatch_lines([ - ' 0% (0 of 9) | | Elapsed Time: ?:00:00 ETA: --:--:--', - ' 11% (1 of 9) |# | Elapsed Time: ?:00:01 ETA: ?:00:08', - ' 22% (2 of 9) |## | Elapsed Time: ?:00:02 ETA: ?:00:07', - ' 33% (3 of 9) |#### | Elapsed Time: ?:00:03 ETA: ?:00:06', - ' 44% (4 of 9) |##### | Elapsed Time: ?:00:04 ETA: ?:00:05', - ' 55% (5 of 9) |###### | Elapsed Time: ?:00:05 ETA: ?:00:04', - ' 66% (6 of 9) |######## | Elapsed Time: ?:00:06 ETA: ?:00:03', - ' 77% (7 of 9) |######### | Elapsed Time: ?:00:07 ETA: ?:00:02', - ' 88% (8 of 9) |########## | Elapsed Time: ?:00:08 ETA: ?:00:01', - '100% (9 of 9) |############| Elapsed Time: ?:00:09 Time: ?:00:09', - ]) + result.stderr.fnmatch_lines( + [ + ' 0% (0 of 9) | | Elapsed Time: ?:00:00 ETA: --:--:--', + ' 11% (1 of 9) |# | Elapsed Time: ?:00:01 ETA: ?:00:08', + ' 22% (2 of 9) |## | Elapsed Time: ?:00:02 ETA: ?:00:07', + ' 33% (3 of 9) |#### | Elapsed Time: ?:00:03 ETA: ?:00:06', + ' 44% (4 of 9) |##### | Elapsed Time: ?:00:04 ETA: ?:00:05', + ' 55% (5 of 9) |###### | Elapsed Time: ?:00:05 ETA: ?:00:04', + ' 66% (6 of 9) |######## | Elapsed Time: ?:00:06 ETA: ?:00:03', + ' 77% (7 of 9) |######### | Elapsed Time: ?:00:07 ETA: ?:00:02', + ' 88% (8 of 9) |########## | Elapsed Time: ?:00:08 ETA: ?:00:01', + '100% (9 of 9) |############| Elapsed Time: ?:00:09 Time: ?:00:09', + ] + ) def test_generator_example(testdir): - ''' Run the simple example code in a python subprocess and then compare its - stderr to what we expect to see from it. We run it in a subprocess to - best capture its stderr. We expect to see match_lines in order in the - output. This test is just a sanity check to ensure that the progress - bar progresses from 1 to 10, it does not make sure that the ''' - result = testdir.runpython(testdir.makepyfile(_create_script( - items='iter(range(9))', - ))) - result.stderr.lines = [l for l in result.stderr.lines if l.strip()] + '''Run the simple example code in a python subprocess and then compare its + stderr to what we expect to see from it. We run it in a subprocess to + best capture its stderr. We expect to see match_lines in order in the + output. This test is just a sanity check to ensure that the progress + bar progresses from 1 to 10, it does not make sure that the''' + result = testdir.runpython( + testdir.makepyfile( + _create_script( + items='iter(range(9))', + ) + ) + ) + result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) lines = [] for i in range(9): lines.append( - r'[/\\|\-]\s+\|\s*#\s*\| %(i)d Elapsed Time: \d:00:%(i)02d' % - dict(i=i)) + r'[/\\|\-]\s+\|\s*#\s*\| %(i)d Elapsed Time: \d:00:%(i)02d' + % dict(i=i) + ) result.stderr.re_match_lines(lines) def test_rapid_updates(testdir): - ''' Run some example code that updates 10 times, then sleeps .1 seconds, - this is meant to test that the progressbar progresses normally with - this sample code, since there were issues with it in the past ''' - - result = testdir.runpython(testdir.makepyfile(_create_script( - term_width=60, - items=list(range(10)), - loop_code=''' + '''Run some example code that updates 10 times, then sleeps .1 seconds, + this is meant to test that the progressbar progresses normally with + this sample code, since there were issues with it in the past''' + + result = testdir.runpython( + testdir.makepyfile( + _create_script( + term_width=60, + items=list(range(10)), + loop_code=''' if i < 5: fake_time.tick(1) else: fake_time.tick(2) - ''' - ))) - result.stderr.lines = [l for l in result.stderr.lines if l.strip()] + ''', + ) + ) + ) + result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) - result.stderr.fnmatch_lines([ - ' 0% (0 of 10) | | Elapsed Time: ?:00:00 ETA: --:--:--', - ' 10% (1 of 10) | | Elapsed Time: ?:00:01 ETA: ?:00:09', - ' 20% (2 of 10) |# | Elapsed Time: ?:00:02 ETA: ?:00:08', - ' 30% (3 of 10) |# | Elapsed Time: ?:00:03 ETA: ?:00:07', - ' 40% (4 of 10) |## | Elapsed Time: ?:00:04 ETA: ?:00:06', - ' 50% (5 of 10) |### | Elapsed Time: ?:00:05 ETA: ?:00:05', - ' 60% (6 of 10) |### | Elapsed Time: ?:00:07 ETA: ?:00:06', - ' 70% (7 of 10) |#### | Elapsed Time: ?:00:09 ETA: ?:00:06', - ' 80% (8 of 10) |#### | Elapsed Time: ?:00:11 ETA: ?:00:04', - ' 90% (9 of 10) |##### | Elapsed Time: ?:00:13 ETA: ?:00:02', - '100% (10 of 10) |#####| Elapsed Time: ?:00:15 Time: ?:00:15' - ]) + result.stderr.fnmatch_lines( + [ + ' 0% (0 of 10) | | Elapsed Time: ?:00:00 ETA: --:--:--', + ' 10% (1 of 10) | | Elapsed Time: ?:00:01 ETA: ?:00:09', + ' 20% (2 of 10) |# | Elapsed Time: ?:00:02 ETA: ?:00:08', + ' 30% (3 of 10) |# | Elapsed Time: ?:00:03 ETA: ?:00:07', + ' 40% (4 of 10) |## | Elapsed Time: ?:00:04 ETA: ?:00:06', + ' 50% (5 of 10) |### | Elapsed Time: ?:00:05 ETA: ?:00:05', + ' 60% (6 of 10) |### | Elapsed Time: ?:00:07 ETA: ?:00:06', + ' 70% (7 of 10) |#### | Elapsed Time: ?:00:09 ETA: ?:00:06', + ' 80% (8 of 10) |#### | Elapsed Time: ?:00:11 ETA: ?:00:04', + ' 90% (9 of 10) |##### | Elapsed Time: ?:00:13 ETA: ?:00:02', + '100% (10 of 10) |#####| Elapsed Time: ?:00:15 Time: ?:00:15', + ] + ) def test_non_timed(testdir): - result = testdir.runpython(testdir.makepyfile(_create_script( - widgets='[progressbar.Percentage(), progressbar.Bar()]', - items=list(range(5)), - ))) - result.stderr.lines = [l for l in result.stderr.lines if l.strip()] + result = testdir.runpython( + testdir.makepyfile( + _create_script( + widgets='[progressbar.Percentage(), progressbar.Bar()]', + items=list(range(5)), + ) + ) + ) + result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) - result.stderr.fnmatch_lines([ - ' 0%| |', - ' 20%|########## |', - ' 40%|##################### |', - ' 60%|################################ |', - ' 80%|########################################### |', - '100%|######################################################|', - ]) + result.stderr.fnmatch_lines( + [ + ' 0%| |', + ' 20%|########## |', + ' 40%|##################### |', + ' 60%|################################ |', + ' 80%|########################################### |', + '100%|######################################################|', + ] + ) def test_line_breaks(testdir): - result = testdir.runpython(testdir.makepyfile(_create_script( - widgets='[progressbar.Percentage(), progressbar.Bar()]', - line_breaks=True, - items=list(range(5)), - ))) + result = testdir.runpython( + testdir.makepyfile( + _create_script( + widgets='[progressbar.Percentage(), progressbar.Bar()]', + line_breaks=True, + items=list(range(5)), + ) + ) + ) pprint.pprint(result.stderr.str(), width=70) - assert result.stderr.str() == u'\n'.join(( - u' 0%| |', - u' 20%|########## |', - u' 40%|##################### |', - u' 60%|################################ |', - u' 80%|########################################### |', - u'100%|######################################################|', - u'100%|######################################################|', - )) + assert result.stderr.str() == u'\n'.join( + ( + u' 0%| |', + u' 20%|########## |', + u' 40%|##################### |', + u' 60%|################################ |', + u' 80%|########################################### |', + u'100%|######################################################|', + u'100%|######################################################|', + ) + ) def test_no_line_breaks(testdir): - result = testdir.runpython(testdir.makepyfile(_create_script( - widgets='[progressbar.Percentage(), progressbar.Bar()]', - line_breaks=False, - items=list(range(5)), - ))) + result = testdir.runpython( + testdir.makepyfile( + _create_script( + widgets='[progressbar.Percentage(), progressbar.Bar()]', + line_breaks=False, + items=list(range(5)), + ) + ) + ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ u'', @@ -182,16 +224,20 @@ def test_no_line_breaks(testdir): u' 80%|########################################### |', u'100%|######################################################|', u'', - u'100%|######################################################|' + u'100%|######################################################|', ] def test_percentage_label_bar(testdir): - result = testdir.runpython(testdir.makepyfile(_create_script( - widgets='[progressbar.PercentageLabelBar()]', - line_breaks=False, - items=list(range(5)), - ))) + result = testdir.runpython( + testdir.makepyfile( + _create_script( + widgets='[progressbar.PercentageLabelBar()]', + line_breaks=False, + items=list(range(5)), + ) + ) + ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ u'', @@ -202,16 +248,20 @@ def test_percentage_label_bar(testdir): u'|###########################80%################ |', u'|###########################100%###########################|', u'', - u'|###########################100%###########################|' + u'|###########################100%###########################|', ] def test_granular_bar(testdir): - result = testdir.runpython(testdir.makepyfile(_create_script( - widgets='[progressbar.GranularBar(markers=" .oO")]', - line_breaks=False, - items=list(range(5)), - ))) + result = testdir.runpython( + testdir.makepyfile( + _create_script( + widgets='[progressbar.GranularBar(markers=" .oO")]', + line_breaks=False, + items=list(range(5)), + ) + ) + ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ u'', @@ -222,7 +272,7 @@ def test_granular_bar(testdir): u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO. |', u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', u'', - u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|' + u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', ] @@ -232,12 +282,14 @@ def test_colors(testdir): widgets=['\033[92mgreen\033[0m'], ) - result = testdir.runpython(testdir.makepyfile(_create_script( - enable_colors=True, **kwargs))) + result = testdir.runpython( + testdir.makepyfile(_create_script(enable_colors=True, **kwargs)) + ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [u'\x1b[92mgreen\x1b[0m'] * 3 - result = testdir.runpython(testdir.makepyfile(_create_script( - enable_colors=False, **kwargs))) + result = testdir.runpython( + testdir.makepyfile(_create_script(enable_colors=False, **kwargs)) + ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [u'green'] * 3 diff --git a/tests/test_multibar.py b/tests/test_multibar.py index 4865ae3e..f0993dfe 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -5,6 +5,10 @@ import progressbar +N = 10 +BARS = 3 +SLEEP = 0.002 + def test_multi_progress_bar_out_of_range(): widgets = [ @@ -21,14 +25,21 @@ def test_multi_progress_bar_out_of_range(): def test_multi_progress_bar_fill_left(): import examples + return examples.multi_progress_bar_example(False) def test_multibar(): - bars = 3 - N = 10 - multibar = progressbar.MultiBar(sort_keyfunc=lambda bar: bar.label) + multibar = progressbar.MultiBar( + sort_keyfunc=lambda bar: bar.label, + remove_finished=0.005, + ) + multibar.show_initial = False + multibar.render(force=True) + multibar.show_initial = True + multibar.render(force=True) multibar.start() + multibar.append_label = False multibar.prepend_label = True @@ -44,31 +55,45 @@ def test_multibar(): bar.finish() del multibar['x'] + multibar.prepend_label = False multibar.append_label = True + append_bar = progressbar.ProgressBar(max_value=N) + append_bar.start() + multibar._label_bar(append_bar) + multibar['append'] = append_bar + multibar.render(force=True) + def do_something(bar): for j in bar(range(N)): time.sleep(0.01) bar.update(j) - for i in range(bars): + for i in range(BARS): thread = threading.Thread( - target=do_something, - args=(multibar['bar {}'.format(i)],) + target=do_something, args=(multibar['bar {}'.format(i)],) ) thread.start() - for bar in multibar.values(): + for bar in list(multibar.values()): for j in range(N): bar.update(j) - time.sleep(0.002) + time.sleep(SLEEP) + + multibar.render(force=True) + + multibar.remove_finished = False + multibar.show_finished = False + append_bar.finish() + multibar.render(force=True) multibar.join(0.1) multibar.stop(0.1) @pytest.mark.parametrize( - 'sort_key', [ + 'sort_key', + [ None, 'index', 'label', @@ -78,21 +103,18 @@ def do_something(bar): progressbar.SortKey.LABEL, progressbar.SortKey.VALUE, progressbar.SortKey.PERCENTAGE, - ] + ], ) def test_multibar_sorting(sort_key): - bars = 3 - N = 10 - with progressbar.MultiBar() as multibar: - for i in range(bars): + for i in range(BARS): label = 'bar {}'.format(i) multibar[label] = progressbar.ProgressBar(max_value=N) for bar in multibar.values(): for j in bar(range(N)): assert bar.started() - time.sleep(0.002) + time.sleep(SLEEP) for bar in multibar.values(): assert bar.finished() @@ -100,5 +122,29 @@ def test_multibar_sorting(sort_key): def test_offset_bar(): with progressbar.ProgressBar(line_offset=2) as bar: - for i in range(100): + for i in range(N): bar.update(i) + + +def test_multibar_show_finished(): + multibar = progressbar.MultiBar(show_finished=True) + multibar['bar'] = progressbar.ProgressBar(max_value=N) + multibar.render(force=True) + with progressbar.MultiBar(show_finished=False) as multibar: + multibar.finished_format = 'finished: {label}' + + for i in range(3): + multibar['bar {}'.format(i)] = progressbar.ProgressBar(max_value=N) + + for bar in multibar.values(): + for i in range(N): + bar.update(i) + time.sleep(SLEEP) + + multibar.render(force=True) + + +def test_multibar_show_initial(): + multibar = progressbar.MultiBar(show_initial=False) + multibar['bar'] = progressbar.ProgressBar(max_value=N) + multibar.render(force=True) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index 3e20ab63..00aa0caa 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -8,8 +8,10 @@ import examples except ImportError: import sys + sys.path.append('..') import examples + sys.path.remove('..') @@ -24,8 +26,7 @@ def test_examples(monkeypatch): @pytest.mark.filterwarnings('ignore:.*maxval.*:DeprecationWarning') @pytest.mark.parametrize('example', original_examples.examples) def test_original_examples(example, monkeypatch): - monkeypatch.setattr(progressbar.ProgressBar, - '_MINIMUM_UPDATE_INTERVAL', 1) + monkeypatch.setattr(progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1) monkeypatch.setattr(time, 'sleep', lambda t: None) example() diff --git a/tests/test_samples.py b/tests/test_samples.py index 4e553c29..71e42ea1 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -36,7 +36,9 @@ def test_numeric_samples(): bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 16), 16) - assert samples_widget(bar, None)[1] == [4, 5, 8, 10, 20] + assert samples_widget(bar, None)[1] == progressbar.SliceableDeque( + [4, 5, 8, 10, 20] + ) def test_timedelta_samples(): diff --git a/tests/test_speed.py b/tests/test_speed.py index d7a338b3..dc8ad6f1 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -2,26 +2,34 @@ import progressbar -@pytest.mark.parametrize('total_seconds_elapsed,value,expected', [ - (1, 0, ' 0.0 s/B'), - (1, 0.01, '100.0 s/B'), - (1, 0.1, ' 0.1 B/s'), - (1, 1, ' 1.0 B/s'), - (1, 2 ** 10 - 1, '1023.0 B/s'), - (1, 2 ** 10 + 0, ' 1.0 KiB/s'), - (1, 2 ** 20, ' 1.0 MiB/s'), - (1, 2 ** 30, ' 1.0 GiB/s'), - (1, 2 ** 40, ' 1.0 TiB/s'), - (1, 2 ** 50, ' 1.0 PiB/s'), - (1, 2 ** 60, ' 1.0 EiB/s'), - (1, 2 ** 70, ' 1.0 ZiB/s'), - (1, 2 ** 80, ' 1.0 YiB/s'), - (1, 2 ** 90, '1024.0 YiB/s'), -]) +@pytest.mark.parametrize( + 'total_seconds_elapsed,value,expected', + [ + (1, 0, ' 0.0 s/B'), + (1, 0.01, '100.0 s/B'), + (1, 0.1, ' 0.1 B/s'), + (1, 1, ' 1.0 B/s'), + (1, 2**10 - 1, '1023.0 B/s'), + (1, 2**10 + 0, ' 1.0 KiB/s'), + (1, 2**20, ' 1.0 MiB/s'), + (1, 2**30, ' 1.0 GiB/s'), + (1, 2**40, ' 1.0 TiB/s'), + (1, 2**50, ' 1.0 PiB/s'), + (1, 2**60, ' 1.0 EiB/s'), + (1, 2**70, ' 1.0 ZiB/s'), + (1, 2**80, ' 1.0 YiB/s'), + (1, 2**90, '1024.0 YiB/s'), + ], +) def test_file_transfer_speed(total_seconds_elapsed, value, expected): widget = progressbar.FileTransferSpeed() - assert widget(None, dict( - total_seconds_elapsed=total_seconds_elapsed, - value=value, - )) == expected - + assert ( + widget( + None, + dict( + total_seconds_elapsed=total_seconds_elapsed, + value=value, + ), + ) + == expected + ) diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 997bb0d6..395e618f 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -9,7 +9,10 @@ def test_left_justify(): '''Left justify using the terminal width''' p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())], - max_value=100, term_width=20, left_justify=True) + max_value=100, + term_width=20, + left_justify=True, + ) assert p.term_width is not None for i in range(100): @@ -20,7 +23,10 @@ def test_right_justify(): '''Right justify using the terminal width''' p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())], - max_value=100, term_width=20, left_justify=False) + max_value=100, + term_width=20, + left_justify=False, + ) assert p.term_width is not None for i in range(100): @@ -38,12 +44,17 @@ def fake_signal(signal, func): try: import fcntl + monkeypatch.setattr(fcntl, 'ioctl', ioctl) monkeypatch.setattr(signal, 'signal', fake_signal) p = progressbar.ProgressBar( widgets=[ - progressbar.BouncingBar(marker=progressbar.RotatingMarker())], - max_value=100, left_justify=True, term_width=None) + progressbar.BouncingBar(marker=progressbar.RotatingMarker()) + ], + max_value=100, + left_justify=True, + term_width=None, + ) assert p.term_width is not None for i in range(100): @@ -56,7 +67,9 @@ def test_fill_right(): '''Right justify using the terminal width''' p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(fill_left=False)], - max_value=100, term_width=20) + max_value=100, + term_width=20, + ) assert p.term_width is not None for i in range(100): @@ -67,7 +80,9 @@ def test_fill_left(): '''Right justify using the terminal width''' p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(fill_left=True)], - max_value=100, term_width=20) + max_value=100, + term_width=20, + ) assert p.term_width is not None for i in range(100): @@ -79,9 +94,8 @@ def test_no_fill(monkeypatch): bar = progressbar.BouncingBar() bar.INTERVAL = timedelta(seconds=1) p = progressbar.ProgressBar( - widgets=[bar], - max_value=progressbar.UnknownLength, - term_width=20) + widgets=[bar], max_value=progressbar.UnknownLength, term_width=20 + ) assert p.term_width is not None for i in range(30): @@ -91,8 +105,9 @@ def test_no_fill(monkeypatch): def test_stdout_redirection(): - p = progressbar.ProgressBar(fd=sys.stdout, max_value=10, - redirect_stdout=True) + p = progressbar.ProgressBar( + fd=sys.stdout, max_value=10, redirect_stdout=True + ) for i in range(10): print('', file=sys.stdout) @@ -118,8 +133,9 @@ def test_stderr_redirection(): def test_stdout_stderr_redirection(): - p = progressbar.ProgressBar(max_value=10, redirect_stdout=True, - redirect_stderr=True) + p = progressbar.ProgressBar( + max_value=10, redirect_stdout=True, redirect_stderr=True + ) p.start() for i in range(10): @@ -140,6 +156,7 @@ def fake_signal(signal, func): try: import fcntl + monkeypatch.setattr(fcntl, 'ioctl', ioctl) monkeypatch.setattr(signal, 'signal', fake_signal) @@ -153,4 +170,3 @@ def fake_signal(signal, func): p.finish() except ImportError: pass # Skip on Windows - diff --git a/tests/test_timed.py b/tests/test_timed.py index 6753f537..cf34cd2d 100644 --- a/tests/test_timed.py +++ b/tests/test_timed.py @@ -8,8 +8,9 @@ def test_timer(): widgets = [ progressbar.Timer(), ] - p = progressbar.ProgressBar(max_value=2, widgets=widgets, - poll_interval=0.0001) + p = progressbar.ProgressBar( + max_value=2, widgets=widgets, poll_interval=0.0001 + ) p.start() p.update() @@ -25,8 +26,9 @@ def test_eta(): widgets = [ progressbar.ETA(), ] - p = progressbar.ProgressBar(min_value=0, max_value=2, widgets=widgets, - poll_interval=0.0001) + p = progressbar.ProgressBar( + min_value=0, max_value=2, widgets=widgets, poll_interval=0.0001 + ) p.start() time.sleep(0.001) @@ -68,8 +70,9 @@ def test_adaptive_transfer_speed(): widgets = [ progressbar.AdaptiveTransferSpeed(), ] - p = progressbar.ProgressBar(max_value=2, widgets=widgets, - poll_interval=0.0001) + p = progressbar.ProgressBar( + max_value=2, widgets=widgets, poll_interval=0.0001 + ) p.start() p.update(1) @@ -100,8 +103,9 @@ def calculate_eta(self, value, elapsed): return 0, 0 monkeypatch.setattr(progressbar.FileTransferSpeed, '_speed', calculate_eta) - monkeypatch.setattr(progressbar.AdaptiveTransferSpeed, '_speed', - calculate_eta) + monkeypatch.setattr( + progressbar.AdaptiveTransferSpeed, '_speed', calculate_eta + ) for widget in widgets: widget.INTERVAL = interval @@ -144,8 +148,9 @@ def test_non_changing_eta(): progressbar.ETA(), progressbar.AdaptiveTransferSpeed(), ] - p = progressbar.ProgressBar(max_value=2, widgets=widgets, - poll_interval=0.0001) + p = progressbar.ProgressBar( + max_value=2, widgets=widgets, poll_interval=0.0001 + ) p.start() p.update(1) @@ -156,9 +161,10 @@ def test_non_changing_eta(): def test_eta_not_available(): """ - When ETA is not available (data coming from a generator), - ETAs should not raise exceptions. + When ETA is not available (data coming from a generator), + ETAs should not raise exceptions. """ + def gen(): for x in range(200): yield x diff --git a/tests/test_timer.py b/tests/test_timer.py index bc51c64a..4e439a27 100644 --- a/tests/test_timer.py +++ b/tests/test_timer.py @@ -4,29 +4,39 @@ import progressbar -@pytest.mark.parametrize('poll_interval,expected', [ - (1, 1), - (timedelta(seconds=1), 1), - (0.001, 0.001), - (timedelta(microseconds=1000), 0.001), -]) -@pytest.mark.parametrize('parameter', [ - 'poll_interval', - 'min_poll_interval', -]) +@pytest.mark.parametrize( + 'poll_interval,expected', + [ + (1, 1), + (timedelta(seconds=1), 1), + (0.001, 0.001), + (timedelta(microseconds=1000), 0.001), + ], +) +@pytest.mark.parametrize( + 'parameter', + [ + 'poll_interval', + 'min_poll_interval', + ], +) def test_poll_interval(parameter, poll_interval, expected): # Test int, float and timedelta intervals bar = progressbar.ProgressBar(**{parameter: poll_interval}) assert getattr(bar, parameter) == expected -@pytest.mark.parametrize('interval', [ - 1, - timedelta(seconds=1), -]) +@pytest.mark.parametrize( + 'interval', + [ + 1, + timedelta(seconds=1), + ], +) def test_intervals(monkeypatch, interval): - monkeypatch.setattr(progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', - interval) + monkeypatch.setattr( + progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', interval + ) bar = progressbar.ProgressBar(max_value=100) # Initially there should be no last_update_time @@ -45,4 +55,3 @@ def test_intervals(monkeypatch, interval): bar._last_update_time -= 2 bar.update(3) assert bar.last_update_time != last_update_time - diff --git a/tests/test_unicode.py b/tests/test_unicode.py index 3b8e4aec..0d70fae3 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -6,11 +6,14 @@ from python_utils import converters -@pytest.mark.parametrize('name,markers', [ - ('line arrows', u'←↖↑↗→↘↓↙'), - ('block arrows', u'◢◣◤◥'), - ('wheels', u'◐◓◑◒'), -]) +@pytest.mark.parametrize( + 'name,markers', + [ + ('line arrows', u'←↖↑↗→↘↓↙'), + ('block arrows', u'◢◣◤◥'), + ('wheels', u'◐◓◑◒'), + ], +) @pytest.mark.parametrize('as_unicode', [True, False]) def test_markers(name, markers, as_unicode): if as_unicode: @@ -26,4 +29,3 @@ def test_markers(name, markers, as_unicode): bar._MINIMUM_UPDATE_INTERVAL = 1e-12 for i in bar((i for i in range(24))): time.sleep(0.001) - diff --git a/tests/test_unknown_length.py b/tests/test_unknown_length.py index fe08e209..454d73df 100644 --- a/tests/test_unknown_length.py +++ b/tests/test_unknown_length.py @@ -2,8 +2,10 @@ def test_unknown_length(): - pb = progressbar.ProgressBar(widgets=[progressbar.AnimatedMarker()], - max_value=progressbar.UnknownLength) + pb = progressbar.ProgressBar( + widgets=[progressbar.AnimatedMarker()], + max_value=progressbar.UnknownLength, + ) assert pb.max_value is progressbar.UnknownLength diff --git a/tests/test_utils.py b/tests/test_utils.py index 0ff4a7a1..980072de 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,20 +3,23 @@ import progressbar -@pytest.mark.parametrize('value,expected', [ - (None, None), - ('', None), - ('1', True), - ('y', True), - ('t', True), - ('yes', True), - ('true', True), - ('0', False), - ('n', False), - ('f', False), - ('no', False), - ('false', False), -]) +@pytest.mark.parametrize( + 'value,expected', + [ + (None, None), + ('', None), + ('1', True), + ('y', True), + ('t', True), + ('yes', True), + ('true', True), + ('0', False), + ('n', False), + ('f', False), + ('no', False), + ('false', False), + ], +) def test_env_flag(value, expected, monkeypatch): if value is not None: monkeypatch.setenv('TEST_ENV', value) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index a38574da..592d869a 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -35,7 +35,7 @@ def test_widgets_small_values(): p.finish() -@pytest.mark.parametrize('max_value', [10 ** 6, 10 ** 8]) +@pytest.mark.parametrize('max_value', [10**6, 10**8]) def test_widgets_large_values(max_value): widgets = [ 'Test: ', @@ -50,7 +50,7 @@ def test_widgets_large_values(max_value): progressbar.FileTransferSpeed(), ] p = progressbar.ProgressBar(widgets=widgets, max_value=max_value).start() - for i in range(0, 10 ** 6, 10 ** 4): + for i in range(0, 10**6, 10**4): time.sleep(1) p.update(i + 1) p.finish() @@ -95,7 +95,7 @@ def test_all_widgets_small_values(max_value): p.finish() -@pytest.mark.parametrize('max_value', [10 ** 6, 10 ** 7]) +@pytest.mark.parametrize('max_value', [10**6, 10**7]) def test_all_widgets_large_values(max_value): widgets = [ progressbar.Timer(), @@ -120,7 +120,7 @@ def test_all_widgets_large_values(max_value): time.sleep(1) p.update() - for i in range(0, 10 ** 6, 10 ** 4): + for i in range(0, 10**6, 10**4): time.sleep(1) p.update(i) @@ -144,8 +144,9 @@ def test_all_widgets_min_width(min_width, term_width): progressbar.Bar(min_width=min_width), progressbar.ReverseBar(min_width=min_width), progressbar.BouncingBar(min_width=min_width), - progressbar.FormatCustomText('Custom %(text)s', dict(text='text'), - min_width=min_width), + progressbar.FormatCustomText( + 'Custom %(text)s', dict(text='text'), min_width=min_width + ), progressbar.DynamicMessage('custom', min_width=min_width), progressbar.CurrentTime(min_width=min_width), ] @@ -178,8 +179,9 @@ def test_all_widgets_max_width(max_width, term_width): progressbar.Bar(max_width=max_width), progressbar.ReverseBar(max_width=max_width), progressbar.BouncingBar(max_width=max_width), - progressbar.FormatCustomText('Custom %(text)s', dict(text='text'), - max_width=max_width), + progressbar.FormatCustomText( + 'Custom %(text)s', dict(text='text'), max_width=max_width + ), progressbar.DynamicMessage('custom', max_width=max_width), progressbar.CurrentTime(max_width=max_width), ] diff --git a/tests/test_with.py b/tests/test_with.py index 1fd2a1f6..a7c60239 100644 --- a/tests/test_with.py +++ b/tests/test_with.py @@ -17,4 +17,3 @@ def test_with_extra_start(): with progressbar.ProgressBar(max_value=10) as p: p.start() p.start() - diff --git a/tox.ini b/tox.ini index 3ffed050..9e681c86 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,20 @@ [tox] -envlist = py37, py38, py39, py310, flake8, docs, black, mypy, pyright +envlist = + py37 + py38 + py39 + py310 + flake8 + docs + black + mypy + pyright + ruff + codespell skip_missing_interpreters = True [testenv] basepython = - py36: python3.6 - py37: python3.7 py38: python3.8 py39: python3.9 py310: python3.10 @@ -63,3 +72,13 @@ exclude = progressbar/six.py tests/original_examples.py +[testenv:ruff] +commands = ruff check . +deps = ruff +skip_install = true + +[testenv:codespell] +commands = codespell . +deps = codespell +skip_install = true +command = codespell \ No newline at end of file From 43fc6dbb567c4461340bdde4cceea0e5ed7b2528 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Aug 2023 03:09:18 +0200 Subject: [PATCH 093/118] updated stale file --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 0740d1a1..7f0aca19 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,7 +3,7 @@ name: Close stale issues and pull requests on: workflow_dispatch: schedule: - - cron: '0 0 * * *' # Run every day at midnight + - cron: "0 0 * * *" # Run every day at midnight jobs: stale: From ad9dda9d1c81612719ec8ee5baae02035e97eb24 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Aug 2023 23:50:02 +0200 Subject: [PATCH 094/118] updated stale file --- .github/workflows/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7f0aca19..09d9ff20 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,3 +13,4 @@ jobs: with: days-before-stale: 30 exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement + exempt-all-pr-assignees: true From 5b2b30f6bccfca3ac08ee9c4e9d1f5c9b9fdc6b0 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Aug 2023 23:51:27 +0200 Subject: [PATCH 095/118] updated stale file From a37af38986a453087242d0379801e996ba749430 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 12 Sep 2023 13:42:18 +0200 Subject: [PATCH 096/118] Many linting, typing and build system improvements. Nearly done now :) --- .github/workflows/stale.yml | 3 +- MANIFEST.in | 6 +- docs/conf.py | 1 + docs/progressbar.multi.rst | 7 + docs/progressbar.rst | 31 + docs/progressbar.terminal.base.rst | 7 + docs/progressbar.terminal.colors.rst | 7 + ...progressbar.terminal.os_specific.posix.rst | 7 + docs/progressbar.terminal.os_specific.rst | 19 + ...ogressbar.terminal.os_specific.windows.rst | 7 + docs/progressbar.terminal.rst | 28 + docs/progressbar.terminal.stream.rst | 7 + progressbar/__about__.py | 2 +- progressbar/__init__.py | 68 +- progressbar/bar.py | 149 ++--- progressbar/base.py | 7 +- progressbar/multi.py | 73 ++- progressbar/shortcuts.py | 3 +- progressbar/terminal/__init__.py | 2 +- progressbar/terminal/base.py | 70 +- progressbar/terminal/colors.py | 599 +++++++++--------- progressbar/terminal/os_specific/__init__.py | 2 +- progressbar/terminal/os_specific/posix.py | 2 +- progressbar/terminal/os_specific/windows.py | 71 ++- progressbar/terminal/stream.py | 6 +- progressbar/utils.py | 95 ++- progressbar/widgets.py | 243 ++++--- pyproject.toml | 219 +++++++ setup.cfg | 30 - setup.py | 72 --- tox.ini | 16 +- 31 files changed, 1044 insertions(+), 815 deletions(-) create mode 100644 docs/progressbar.multi.rst create mode 100644 docs/progressbar.rst create mode 100644 docs/progressbar.terminal.base.rst create mode 100644 docs/progressbar.terminal.colors.rst create mode 100644 docs/progressbar.terminal.os_specific.posix.rst create mode 100644 docs/progressbar.terminal.os_specific.rst create mode 100644 docs/progressbar.terminal.os_specific.windows.rst create mode 100644 docs/progressbar.terminal.rst create mode 100644 docs/progressbar.terminal.stream.rst create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 09d9ff20..7101b3f5 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,7 +3,7 @@ name: Close stale issues and pull requests on: workflow_dispatch: schedule: - - cron: "0 0 * * *" # Run every day at midnight + - cron: '0 0 * * *' # Run every day at midnight jobs: stale: @@ -14,3 +14,4 @@ jobs: days-before-stale: 30 exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement exempt-all-pr-assignees: true + diff --git a/MANIFEST.in b/MANIFEST.in index eecfc0de..f387924e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,6 @@ +recursive-exclude *.pyc +recursive-exclude *.pyo +recursive-exclude *.html include AUTHORS.rst include CHANGES.rst include CONTRIBUTING.rst @@ -7,6 +10,3 @@ include examples.py include requirements.txt include Makefile include pytest.ini -recursive-include tests * -recursive-exclude *.pyc -recursive-exclude *.pyo diff --git a/docs/conf.py b/docs/conf.py index ecc74ba7..140f7cd7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,6 +72,7 @@ # # The short X.Y version. version = metadata.__version__ +assert version == '4.3b0', version # The full version, including alpha/beta/rc tags. release = metadata.__version__ diff --git a/docs/progressbar.multi.rst b/docs/progressbar.multi.rst new file mode 100644 index 00000000..5d8b85fd --- /dev/null +++ b/docs/progressbar.multi.rst @@ -0,0 +1,7 @@ +progressbar.multi module +======================== + +.. automodule:: progressbar.multi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.rst b/docs/progressbar.rst new file mode 100644 index 00000000..674f6b64 --- /dev/null +++ b/docs/progressbar.rst @@ -0,0 +1,31 @@ +progressbar package +=================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + progressbar.terminal + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + progressbar.bar + progressbar.base + progressbar.multi + progressbar.shortcuts + progressbar.utils + progressbar.widgets + +Module contents +--------------- + +.. automodule:: progressbar + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.base.rst b/docs/progressbar.terminal.base.rst new file mode 100644 index 00000000..8114b8cf --- /dev/null +++ b/docs/progressbar.terminal.base.rst @@ -0,0 +1,7 @@ +progressbar.terminal.base module +================================ + +.. automodule:: progressbar.terminal.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.colors.rst b/docs/progressbar.terminal.colors.rst new file mode 100644 index 00000000..d03706f7 --- /dev/null +++ b/docs/progressbar.terminal.colors.rst @@ -0,0 +1,7 @@ +progressbar.terminal.colors module +================================== + +.. automodule:: progressbar.terminal.colors + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.os_specific.posix.rst b/docs/progressbar.terminal.os_specific.posix.rst new file mode 100644 index 00000000..7d1ec491 --- /dev/null +++ b/docs/progressbar.terminal.os_specific.posix.rst @@ -0,0 +1,7 @@ +progressbar.terminal.os\_specific.posix module +============================================== + +.. automodule:: progressbar.terminal.os_specific.posix + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.os_specific.rst b/docs/progressbar.terminal.os_specific.rst new file mode 100644 index 00000000..456ef9cc --- /dev/null +++ b/docs/progressbar.terminal.os_specific.rst @@ -0,0 +1,19 @@ +progressbar.terminal.os\_specific package +========================================= + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + progressbar.terminal.os_specific.posix + progressbar.terminal.os_specific.windows + +Module contents +--------------- + +.. automodule:: progressbar.terminal.os_specific + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.os_specific.windows.rst b/docs/progressbar.terminal.os_specific.windows.rst new file mode 100644 index 00000000..0595e93a --- /dev/null +++ b/docs/progressbar.terminal.os_specific.windows.rst @@ -0,0 +1,7 @@ +progressbar.terminal.os\_specific.windows module +================================================ + +.. automodule:: progressbar.terminal.os_specific.windows + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.rst b/docs/progressbar.terminal.rst new file mode 100644 index 00000000..dba09353 --- /dev/null +++ b/docs/progressbar.terminal.rst @@ -0,0 +1,28 @@ +progressbar.terminal package +============================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + progressbar.terminal.os_specific + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + progressbar.terminal.base + progressbar.terminal.colors + progressbar.terminal.stream + +Module contents +--------------- + +.. automodule:: progressbar.terminal + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/progressbar.terminal.stream.rst b/docs/progressbar.terminal.stream.rst new file mode 100644 index 00000000..2bb3b355 --- /dev/null +++ b/docs/progressbar.terminal.stream.rst @@ -0,0 +1,7 @@ +progressbar.terminal.stream module +================================== + +.. automodule:: progressbar.terminal.stream + :members: + :undoc-members: + :show-inheritance: diff --git a/progressbar/__about__.py b/progressbar/__about__.py index a5d57b2e..fd8affc2 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -18,7 +18,7 @@ ''' A Python Progressbar library to provide visual (yet text based) progress to long running operations. -'''.strip().split() +'''.strip().split(), ) __email__ = 'wolph@wol.ph' __version__ = '4.3b.0' diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 4a43eb67..49be705f 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -1,43 +1,41 @@ from datetime import date -from .__about__ import __author__ -from .__about__ import __version__ -from .bar import DataTransferBar -from .bar import NullBar -from .bar import ProgressBar +from .__about__ import __author__, __version__ +from .bar import DataTransferBar, NullBar, ProgressBar from .base import UnknownLength +from .multi import MultiBar, SortKey from .shortcuts import progressbar -from .utils import len_color -from .utils import streams -from .widgets import AbsoluteETA -from .widgets import AdaptiveETA -from .widgets import AdaptiveTransferSpeed -from .widgets import AnimatedMarker -from .widgets import Bar -from .widgets import BouncingBar -from .widgets import Counter -from .widgets import CurrentTime -from .widgets import DataSize -from .widgets import DynamicMessage -from .widgets import ETA -from .widgets import FileTransferSpeed -from .widgets import FormatCustomText -from .widgets import FormatLabel -from .widgets import FormatLabelBar -from .widgets import GranularBar -from .widgets import MultiProgressBar -from .widgets import MultiRangeBar -from .widgets import Percentage -from .widgets import PercentageLabelBar -from .widgets import ReverseBar -from .widgets import RotatingMarker -from .widgets import SimpleProgress -from .widgets import Timer -from .widgets import Variable -from .widgets import VariableMixin -from .widgets import SliceableDeque from .terminal.stream import LineOffsetStreamWrapper -from .multi import SortKey, MultiBar +from .utils import len_color, streams +from .widgets import ( + ETA, + AbsoluteETA, + AdaptiveETA, + AdaptiveTransferSpeed, + AnimatedMarker, + Bar, + BouncingBar, + Counter, + CurrentTime, + DataSize, + DynamicMessage, + FileTransferSpeed, + FormatCustomText, + FormatLabel, + FormatLabelBar, + GranularBar, + MultiProgressBar, + MultiRangeBar, + Percentage, + PercentageLabelBar, + ReverseBar, + RotatingMarker, + SimpleProgress, + SliceableDeque, + Timer, + Variable, + VariableMixin, +) __date__ = str(date.today()) __all__ = [ diff --git a/progressbar/bar.py b/progressbar/bar.py index dcfdb333..ae249d52 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -11,11 +11,11 @@ import warnings from copy import deepcopy from datetime import datetime -from typing import Type from python_utils import converters, types import progressbar.terminal.stream + from . import ( base, terminal, @@ -100,13 +100,13 @@ def set_last_update_time(self, value: types.Optional[datetime]): last_update_time = property(get_last_update_time, set_last_update_time) - def __init__(self, **kwargs): + def __init__(self, **kwargs): # noqa: B027 pass def start(self, **kwargs): self._started = True - def update(self, value=None): + def update(self, value=None): # noqa: B027 pass def finish(self): # pragma: no cover @@ -176,33 +176,50 @@ def __init__( ): if fd is sys.stdout: fd = utils.streams.original_stdout - elif fd is sys.stderr: fd = utils.streams.original_stderr - if line_offset: - fd = progressbar.terminal.stream.LineOffsetStreamWrapper( - line_offset, fd - ) - + fd = self._apply_line_offset(fd, line_offset) self.fd = fd self.is_ansi_terminal = utils.is_ansi_terminal(fd) + self.is_terminal = self._determine_is_terminal(fd, is_terminal) + self.line_breaks = self._determine_line_breaks(line_breaks) + self.enable_colors = self._determine_enable_colors(enable_colors) - # Check if this is an interactive terminal - self.is_terminal = utils.is_terminal( - fd, is_terminal or self.is_ansi_terminal - ) + super().__init__(**kwargs) + + def _apply_line_offset(self, fd: base.IO, line_offset: int) -> base.IO: + if line_offset: + return progressbar.terminal.stream.LineOffsetStreamWrapper( + line_offset, + fd, + ) + else: + return fd - # Check if it should overwrite the current line (suitable for - # iteractive terminals) or write line breaks (suitable for log files) + def _determine_is_terminal( + self, + fd: base.IO, + is_terminal: bool | None, + ) -> bool: + if is_terminal is not None: + return utils.is_terminal(fd, is_terminal) + else: + return utils.is_ansi_terminal(fd) + + def _determine_line_breaks(self, line_breaks: bool | None) -> bool: if line_breaks is None: - line_breaks = utils.env_flag( - 'PROGRESSBAR_LINE_BREAKS', not self.is_terminal + return utils.env_flag( + 'PROGRESSBAR_LINE_BREAKS', + not self.is_terminal, ) - self.line_breaks = bool(line_breaks) + else: + return bool(line_breaks) - # Check if ANSI escape characters are enabled (suitable for iteractive - # terminals), or should be stripped off (suitable for log files) + def _determine_enable_colors( + self, + enable_colors: terminal.ColorSupport | None, + ) -> terminal.ColorSupport: if enable_colors is None: colors = ( utils.env_flag('PROGRESSBAR_ENABLE_COLORS'), @@ -210,7 +227,7 @@ def __init__( self.is_ansi_terminal, ) - for color_enabled in colors: # pragma: no branch + for color_enabled in colors: if color_enabled is not None: if color_enabled: enable_colors = terminal.color_support @@ -222,15 +239,10 @@ def __init__( enable_colors = terminal.ColorSupport.XTERM_256 elif enable_colors is False: enable_colors = terminal.ColorSupport.NONE - elif isinstance(enable_colors, terminal.ColorSupport): - # `enable_colors` is already a valid value - pass - else: + elif not isinstance(enable_colors, terminal.ColorSupport): raise ValueError(f'Invalid color support value: {enable_colors}') - self.enable_colors = enable_colors - - ProgressBarMixinBase.__init__(self, **kwargs) + return enable_colors def print(self, *args, **kwargs): print(*args, file=self.fd, **kwargs) @@ -242,10 +254,7 @@ def update(self, *args, **kwargs): if not self.enable_colors: line = utils.no_color(line) - if self.line_breaks: - line = line.rstrip() + '\n' - else: - line = '\r' + line + line = line.rstrip() + '\n' if self.line_breaks else '\r' + line try: # pragma: no cover self.fd.write(line) @@ -265,8 +274,7 @@ def finish(self, *args, **kwargs): # pragma: no cover self.fd.flush() def _format_line(self): - 'Joins the widgets and justifies the line' - + 'Joins the widgets and justifies the line.' widgets = ''.join(self._to_unicode(self._format_widgets())) if self.left_justify: @@ -282,7 +290,8 @@ def _format_widgets(self): for index, widget in enumerate(self.widgets): if isinstance( - widget, widgets.WidgetBase + widget, + widgets.WidgetBase, ) and not widget.check_size(self): continue elif isinstance(widget, widgets.AutoWidthWidgetBase): @@ -335,7 +344,6 @@ def __init__(self, term_width: int | None = None, **kwargs): def _handle_resize(self, signum=None, frame=None): 'Tries to catch resize signals sent from the terminal.' - w, h = utils.get_terminal_size() self.term_width = w @@ -483,7 +491,7 @@ class ProgressBar( _iterable: types.Optional[types.Iterator] - _DEFAULT_MAXVAL: Type[base.UnknownLength] = base.UnknownLength + _DEFAULT_MAXVAL: type[base.UnknownLength] = base.UnknownLength # update every 50 milliseconds (up to a 20 times per second) _MINIMUM_UPDATE_INTERVAL: float = 0.050 _last_update_time: types.Optional[float] = None @@ -508,9 +516,7 @@ def __init__( min_poll_interval=None, **kwargs, ): - ''' - Initializes a progress bar with sane defaults - ''' + '''Initializes a progress bar with sane defaults.''' StdRedirectMixin.__init__(self, **kwargs) ResizableMixin.__init__(self, **kwargs) ProgressBarBase.__init__(self, **kwargs) @@ -519,6 +525,7 @@ def __init__( 'The usage of `maxval` is deprecated, please use ' '`max_value` instead', DeprecationWarning, + stacklevel=1, ) max_value = kwargs.get('maxval') @@ -527,16 +534,14 @@ def __init__( 'The usage of `poll` is deprecated, please use ' '`poll_interval` instead', DeprecationWarning, + stacklevel=1, ) poll_interval = kwargs.get('poll') - if max_value: - # mypy doesn't understand that a boolean check excludes - # `UnknownLength` - if min_value > max_value: # type: ignore - raise ValueError( - 'Max value needs to be bigger than the min ' 'value' - ) + if max_value and min_value > max_value: + raise ValueError( + 'Max value needs to be bigger than the min value', + ) self.min_value = min_value # Legacy issue, `max_value` can be `None` before execution. After # that it either has a value or is `UnknownLength` @@ -570,7 +575,8 @@ def __init__( # (downloading a 1GiB file for example) this adds up. poll_interval = utils.deltas_to_seconds(poll_interval, default=None) min_poll_interval = utils.deltas_to_seconds( - min_poll_interval, default=None + min_poll_interval, + default=None, ) self._MINIMUM_UPDATE_INTERVAL = ( utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) @@ -589,9 +595,9 @@ def __init__( # A dictionary of names that can be used by Variable and FormatWidget self.variables = utils.AttributeDict(variables or {}) for widget in self.widgets: - if isinstance(widget, widgets_module.VariableMixin): - if widget.name not in self.variables: - self.variables[widget.name] = None + if isinstance(widget, widgets_module.VariableMixin) \ + and widget.name not in self.variables: + self.variables[widget.name] = None @property def dynamic_messages(self): # pragma: no cover @@ -604,7 +610,7 @@ def dynamic_messages(self, value): # pragma: no cover def init(self): ''' (re)initialize values to original state so the progressbar can be - used (again) + used (again). ''' self.previous_value = None self.last_update_time = None @@ -616,7 +622,7 @@ def init(self): @property def percentage(self) -> float | None: - '''Return current percentage, returns None if no max_value is given + '''Return current percentage, returns None if no max_value is given. >>> progress = ProgressBar() >>> progress.max_value = 10 @@ -682,7 +688,7 @@ def data(self) -> types.Dict[str, types.Any]: is available - `dynamic_messages`: Deprecated, use `variables` instead. - `variables`: Dictionary of user-defined variables for the - :py:class:`~progressbar.widgets.Variable`'s + :py:class:`~progressbar.widgets.Variable`'s. ''' self._last_update_time = time.time() @@ -734,7 +740,7 @@ def default_widgets(self): widgets.Percentage(**self.widget_kwargs), ' ', widgets.SimpleProgress( - format='(%s)' % widgets.SimpleProgress.DEFAULT_FORMAT, + format=f'({widgets.SimpleProgress.DEFAULT_FORMAT})', **self.widget_kwargs, ), ' ', @@ -756,7 +762,7 @@ def default_widgets(self): ] def __call__(self, iterable, max_value=None): - 'Use a ProgressBar to iterate through an iterable' + 'Use a ProgressBar to iterate through an iterable.' if max_value is not None: self.max_value = max_value elif self.max_value is None: @@ -783,13 +789,14 @@ def __next__(self): else: self.update(self.value + 1) - return value except StopIteration: self.finish() raise except GeneratorExit: # pragma: no cover self.finish(dirty=True) raise + else: + return value def __exit__(self, exc_type, exc_value, traceback): self.finish(dirty=bool(exc_type)) @@ -853,15 +860,13 @@ def update(self, value=None, force=False, **kwargs): pass elif self.min_value > value: # type: ignore raise ValueError( - 'Value %s is too small. Should be between %s and %s' - % (value, self.min_value, self.max_value) - ) + f'Value {value} is too small. Should be ' + f'between {self.min_value} and {self.max_value}') elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( - 'Value %s is too large. Should be between %s and %s' - % (value, self.min_value, self.max_value) - ) + f'Value {value} is too large. Should be between ' + f'{self.min_value} and {self.max_value}') else: value = self.max_value @@ -873,9 +878,8 @@ def update(self, value=None, force=False, **kwargs): for key in kwargs: if key not in self.variables: raise TypeError( - 'update() got an unexpected variable name as argument ' - '{0!r}'.format(key) - ) + f'update() got an unexpected variable name as argument ' + f'{key!r}') elif self.variables[key] != kwargs[key]: self.variables[key] = kwargs[key] variables_changed = True @@ -888,6 +892,8 @@ def update(self, value=None, force=False, **kwargs): # Only flush if something was actually written self.fd.flush() + return None + return None def start(self, max_value=None, init=True): '''Starts measuring time, and prints the bar at 0%. @@ -930,7 +936,8 @@ def start(self, max_value=None, init=True): if self.prefix: self.widgets.insert( - 0, widgets.FormatLabel(self.prefix, new_style=True) + 0, + widgets.FormatLabel(self.prefix, new_style=True), ) # Unset the prefix variable after applying so an extra start() # won't keep copying it @@ -938,7 +945,7 @@ def start(self, max_value=None, init=True): if self.suffix: self.widgets.append( - widgets.FormatLabel(self.suffix, new_style=True) + widgets.FormatLabel(self.suffix, new_style=True), ) # Unset the suffix variable after applying so an extra start() # won't keep copying it @@ -988,7 +995,6 @@ def finish(self, end='\n', dirty=False): dirty (bool): When True the progressbar kept the current state and won't be set to 100 percent ''' - if not dirty: self.end_time = datetime.now() self.update(self.max_value, force=True) @@ -1001,12 +1007,13 @@ def finish(self, end='\n', dirty=False): def currval(self): ''' Legacy method to make progressbar-2 compatible with the original - progressbar package + progressbar package. ''' warnings.warn( 'The usage of `currval` is deprecated, please use ' '`value` instead', DeprecationWarning, + stacklevel=1, ) return self.value @@ -1043,7 +1050,7 @@ def default_widgets(self): class NullBar(ProgressBar): ''' Progress bar that does absolutely nothing. Useful for single verbosity - flags + flags. ''' def start(self, *args, **kwargs): diff --git a/progressbar/base.py b/progressbar/base.py index 8e007914..f3f2ef57 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -1,12 +1,13 @@ -# -*- mode: python; coding: utf-8 -*- from python_utils import types class FalseMeta(type): - def __bool__(self): # pragma: no cover + @classmethod + def __bool__(cls): # pragma: no cover return False - def __cmp__(self, other): # pragma: no cover + @classmethod + def __cmp__(cls, other): # pragma: no cover return -1 __nonzero__ = __bool__ diff --git a/progressbar/multi.py b/progressbar/multi.py index 5cec34e1..e5143f1f 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -21,7 +21,7 @@ class SortKey(str, enum.Enum): ''' - Sort keys for the MultiBar + Sort keys for the MultiBar. This is a string enum, so you can use any progressbar attribute or property as a sort key. @@ -61,7 +61,7 @@ class MultiBar(typing.Dict[str, bar.ProgressBar]): remove_finished: float | None #: The kwargs passed to the progressbar constructor - progressbar_kwargs: typing.Dict[str, typing.Any] + progressbar_kwargs: dict[str, typing.Any] #: The progressbar sorting key function sort_keyfunc: SortKeyFunc @@ -75,22 +75,22 @@ class MultiBar(typing.Dict[str, bar.ProgressBar]): _thread_closed: threading.Event = threading.Event() def __init__( - self, - bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, - fd=sys.stderr, - prepend_label: bool = True, - append_label: bool = False, - label_format='{label:20.20} ', - initial_format: str | None = '{label:20.20} Not yet started', - finished_format: str | None = None, - update_interval: float = 1 / 60.0, # 60fps - show_initial: bool = True, - show_finished: bool = True, - remove_finished: timedelta | float = timedelta(seconds=3600), - sort_key: str | SortKey = SortKey.CREATED, - sort_reverse: bool = True, - sort_keyfunc: SortKeyFunc | None = None, - **progressbar_kwargs, + self, + bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, + fd=sys.stderr, + prepend_label: bool = True, + append_label: bool = False, + label_format='{label:20.20} ', + initial_format: str | None = '{label:20.20} Not yet started', + finished_format: str | None = None, + update_interval: float = 1 / 60.0, # 60fps + show_initial: bool = True, + show_finished: bool = True, + remove_finished: timedelta | float = timedelta(seconds=3600), + sort_key: str | SortKey = SortKey.CREATED, + sort_reverse: bool = True, + sort_keyfunc: SortKeyFunc | None = None, + **progressbar_kwargs, ): self.fd = fd @@ -105,7 +105,7 @@ def __init__( self.show_initial = show_initial self.show_finished = show_finished self.remove_finished = python_utils.delta_to_seconds_or_none( - remove_finished + remove_finished, ) self.progressbar_kwargs = progressbar_kwargs @@ -124,7 +124,7 @@ def __init__( super().__init__(bars or {}) def __setitem__(self, key: str, value: bar.ProgressBar): - '''Add a progressbar to the multibar''' + '''Add a progressbar to the multibar.''' if value.label != key: # pragma: no branch value.label = key value.fd = stream.LastLineStream(self.fd) @@ -139,13 +139,13 @@ def __setitem__(self, key: str, value: bar.ProgressBar): super().__setitem__(key, value) def __delitem__(self, key): - '''Remove a progressbar from the multibar''' + '''Remove a progressbar from the multibar.''' super().__delitem__(key) self._finished_at.pop(key, None) self._labeled.discard(key) def __getitem__(self, item): - '''Get (and create if needed) a progressbar from the multibar''' + '''Get (and create if needed) a progressbar from the multibar.''' try: return super().__getitem__(item) except KeyError: @@ -168,7 +168,7 @@ def _label_bar(self, bar: bar.ProgressBar): bar.widgets.append(self.label_format.format(label=bar.label)) def render(self, flush: bool = True, force: bool = False): - '''Render the multibar to the given stream''' + '''Render the multibar to the given stream.''' now = timeit.default_timer() expired = now - self.remove_finished if self.remove_finished else None output = [] @@ -188,10 +188,12 @@ def update(force=True, write=True): # Force update to get the finished format update(write=False) - if self.remove_finished and expired is not None: - if expired >= self._finished_at[bar_]: - del self[bar_.label] - continue + if ( + self.remove_finished + and expired is not None + and expired >= self._finished_at[bar_]): + del self[bar_.label] + continue if not self.show_finished: continue @@ -201,7 +203,7 @@ def update(force=True, write=True): update(force=False) else: output.append( - self.finished_format.format(label=bar_.label) + self.finished_format.format(label=bar_.label), ) elif bar_.started(): update() @@ -219,14 +221,14 @@ def update(force=True, write=True): # Add empty lines to the end of the output if progressbars have # been added - for i in range(len(self._previous_output), len(output)): + for _ in range(len(self._previous_output), len(output)): # Adding a new line so we don't overwrite previous output self._buffer.write('\n') for i, (previous, current) in enumerate( - itertools.zip_longest( - self._previous_output, output, fillvalue='' - ) + itertools.zip_longest( + self._previous_output, output, fillvalue='', + ), ): if previous != current or force: self.print( @@ -243,10 +245,11 @@ def update(force=True, write=True): self.flush() def print( - self, *args, end='\n', offset=None, flush=True, clear=True, **kwargs + self, *args, end='\n', offset=None, flush=True, clear=True, + **kwargs, ): ''' - Print to the progressbar stream without overwriting the progressbars + Print to the progressbar stream without overwriting the progressbars. Args: end: The string to append to the end of the output @@ -289,7 +292,7 @@ def flush(self): def run(self, join=True): ''' Start the multibar render loop and run the progressbars until they - have force _thread_finished + have force _thread_finished. ''' while not self._thread_finished.is_set(): self.render() diff --git a/progressbar/shortcuts.py b/progressbar/shortcuts.py index dd61c9cb..b16f19af 100644 --- a/progressbar/shortcuts.py +++ b/progressbar/shortcuts.py @@ -19,5 +19,4 @@ def progressbar( **kwargs, ) - for result in progressbar(iterator): - yield result + yield from progressbar(iterator) diff --git a/progressbar/terminal/__init__.py b/progressbar/terminal/__init__.py index 4b40b38c..ba4f9c90 100644 --- a/progressbar/terminal/__init__.py +++ b/progressbar/terminal/__init__.py @@ -1 +1 @@ -from .base import * # noqa +from .base import * # noqa F403 diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 0f7fac55..ec0c5556 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -8,10 +8,14 @@ import threading from collections import defaultdict +# Ruff is being stupid and doesn't understand `ClassVar` if it comes from the +# `types` module +from typing import ClassVar + from python_utils import converters, types -from .os_specific import getch from .. import base +from .os_specific import getch ESC = '\x1B' @@ -167,7 +171,7 @@ def from_env(cls): ) if os.environ.get('JUPYTER_COLUMNS') or os.environ.get( - 'JUPYTER_LINES' + 'JUPYTER_LINES', ): # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR @@ -271,8 +275,8 @@ class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): def from_rgb(cls, rgb: RGB) -> HLS: return cls( *colorsys.rgb_to_hls( - rgb.red / 255, rgb.green / 255, rgb.blue / 255 - ) + rgb.red / 255, rgb.green / 255, rgb.blue / 255, + ), ) def interpolate(self, end: HLS, step: float) -> HLS: @@ -284,6 +288,7 @@ def interpolate(self, end: HLS, step: float) -> HLS: class ColorBase(abc.ABC): + @abc.abstractmethod def get_color(self, value: float) -> Color: raise NotImplementedError() @@ -301,7 +306,7 @@ class Color( ColorBase, ): ''' - Color base class + Color base class. This class contains the colors in RGB (Red, Green, Blue), HLS (Hue, Lightness, Saturation) and Xterm (8-bit) formats. It also contains the @@ -364,24 +369,25 @@ def __hash__(self): class Colors: - by_name: defaultdict[str, types.List[Color]] = collections.defaultdict( - list - ) - by_lowername: defaultdict[ - str, types.List[Color] - ] = collections.defaultdict(list) - by_hex: defaultdict[str, types.List[Color]] = collections.defaultdict(list) - by_rgb: defaultdict[RGB, types.List[Color]] = collections.defaultdict(list) - by_hls: defaultdict[HLS, types.List[Color]] = collections.defaultdict(list) - by_xterm: dict[int, Color] = dict() + by_name: ClassVar[ + defaultdict[str, types.List[Color]]] = collections.defaultdict(list) + by_lowername: ClassVar[ + defaultdict[str, types.List[Color]]] = collections.defaultdict(list) + by_hex: ClassVar[defaultdict[str, types.List[Color]]] = ( + collections.defaultdict(list)) + by_rgb: ClassVar[defaultdict[RGB, types.List[Color]]] = ( + collections.defaultdict(list)) + by_hls: ClassVar[defaultdict[HLS, types.List[Color]]] = ( + collections.defaultdict(list)) + by_xterm: ClassVar[dict[int, Color]] = dict() @classmethod def register( - cls, - rgb: RGB, - hls: types.Optional[HLS] = None, - name: types.Optional[str] = None, - xterm: types.Optional[int] = None, + cls, + rgb: RGB, + hls: types.Optional[HLS] = None, + name: types.Optional[str] = None, + xterm: types.Optional[int] = None, ) -> Color: color = Color(rgb, hls, name, xterm) @@ -416,12 +422,8 @@ def __call__(self, value: float): return self.get_color(value) def get_color(self, value: float) -> Color: - 'Map a value from 0 to 1 to a color' - if ( - value is base.Undefined - or value is base.UnknownLength - or value <= 0 - ): + 'Map a value from 0 to 1 to a color.' + if value == base.Undefined or value == base.UnknownLength or value <= 0: return self.colors[0] elif value >= 1: return self.colors[-1] @@ -460,14 +462,14 @@ def get_color(value: float, color: OptionalColor) -> Color | None: def apply_colors( - text: str, - percentage: float | None = None, - *, - fg: OptionalColor = None, - bg: OptionalColor = None, - fg_none: Color | None = None, - bg_none: Color | None = None, - **kwargs: types.Any, + text: str, + percentage: float | None = None, + *, + fg: OptionalColor = None, + bg: OptionalColor = None, + fg_none: Color | None = None, + bg_none: Color | None = None, + **kwargs: types.Any, ) -> str: if fg is None and bg is None: return text diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index f05328a6..885cd062 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -1,7 +1,7 @@ # Based on: https://www.ditig.com/256-colors-cheat-sheet import os -from progressbar.terminal.base import ColorGradient, Colors, HLS, RGB +from progressbar.terminal.base import HLS, RGB, ColorGradient, Colors black = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Black', 0) maroon = Colors.register(RGB(128, 0, 0), HLS(100, 0, 25), 'Maroon', 1) @@ -20,515 +20,515 @@ aqua = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Aqua', 14) white = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'White', 15) grey0 = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Grey0', 16) -navyBlue = Colors.register(RGB(0, 0, 95), HLS(100, 240, 18), 'NavyBlue', 17) -darkBlue = Colors.register(RGB(0, 0, 135), HLS(100, 240, 26), 'DarkBlue', 18) +navy_blue = Colors.register(RGB(0, 0, 95), HLS(100, 240, 18), 'NavyBlue', 17) +dark_blue = Colors.register(RGB(0, 0, 135), HLS(100, 240, 26), 'DarkBlue', 18) blue3 = Colors.register(RGB(0, 0, 175), HLS(100, 240, 34), 'Blue3', 19) blue3 = Colors.register(RGB(0, 0, 215), HLS(100, 240, 42), 'Blue3', 20) blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) -darkGreen = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) -deepSkyBlue4 = Colors.register( - RGB(0, 95, 95), HLS(100, 180, 18), 'DeepSkyBlue4', 23 +dark_green = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) +deep_sky_blue4 = Colors.register( + RGB(0, 95, 95), HLS(100, 180, 18), 'DeepSkyBlue4', 23, ) -deepSkyBlue4 = Colors.register( - RGB(0, 95, 135), HLS(100, 97, 26), 'DeepSkyBlue4', 24 +deep_sky_blue4 = Colors.register( + RGB(0, 95, 135), HLS(100, 97, 26), 'DeepSkyBlue4', 24, ) -deepSkyBlue4 = Colors.register( - RGB(0, 95, 175), HLS(100, 7, 34), 'DeepSkyBlue4', 25 +deep_sky_blue4 = Colors.register( + RGB(0, 95, 175), HLS(100, 7, 34), 'DeepSkyBlue4', 25, ) -dodgerBlue3 = Colors.register( - RGB(0, 95, 215), HLS(100, 13, 42), 'DodgerBlue3', 26 +dodger_blue3 = Colors.register( + RGB(0, 95, 215), HLS(100, 13, 42), 'DodgerBlue3', 26, ) -dodgerBlue2 = Colors.register( - RGB(0, 95, 255), HLS(100, 17, 50), 'DodgerBlue2', 27 +dodger_blue2 = Colors.register( + RGB(0, 95, 255), HLS(100, 17, 50), 'DodgerBlue2', 27, ) green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) -springGreen4 = Colors.register( - RGB(0, 135, 95), HLS(100, 62, 26), 'SpringGreen4', 29 +spring_green4 = Colors.register( + RGB(0, 135, 95), HLS(100, 62, 26), 'SpringGreen4', 29, ) turquoise4 = Colors.register( - RGB(0, 135, 135), HLS(100, 180, 26), 'Turquoise4', 30 + RGB(0, 135, 135), HLS(100, 180, 26), 'Turquoise4', 30, ) -deepSkyBlue3 = Colors.register( - RGB(0, 135, 175), HLS(100, 93, 34), 'DeepSkyBlue3', 31 +deep_sky_blue3 = Colors.register( + RGB(0, 135, 175), HLS(100, 93, 34), 'DeepSkyBlue3', 31, ) -deepSkyBlue3 = Colors.register( - RGB(0, 135, 215), HLS(100, 2, 42), 'DeepSkyBlue3', 32 +deep_sky_blue3 = Colors.register( + RGB(0, 135, 215), HLS(100, 2, 42), 'DeepSkyBlue3', 32, ) -dodgerBlue1 = Colors.register( - RGB(0, 135, 255), HLS(100, 8, 50), 'DodgerBlue1', 33 +dodger_blue1 = Colors.register( + RGB(0, 135, 255), HLS(100, 8, 50), 'DodgerBlue1', 33, ) green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) -springGreen3 = Colors.register( - RGB(0, 175, 95), HLS(100, 52, 34), 'SpringGreen3', 35 +spring_green3 = Colors.register( + RGB(0, 175, 95), HLS(100, 52, 34), 'SpringGreen3', 35, ) -darkCyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) -lightSeaGreen = Colors.register( - RGB(0, 175, 175), HLS(100, 180, 34), 'LightSeaGreen', 37 +dark_cyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) +light_sea_green = Colors.register( + RGB(0, 175, 175), HLS(100, 180, 34), 'LightSeaGreen', 37, ) -deepSkyBlue2 = Colors.register( - RGB(0, 175, 215), HLS(100, 91, 42), 'DeepSkyBlue2', 38 +deep_sky_blue2 = Colors.register( + RGB(0, 175, 215), HLS(100, 91, 42), 'DeepSkyBlue2', 38, ) -deepSkyBlue1 = Colors.register( - RGB(0, 175, 255), HLS(100, 98, 50), 'DeepSkyBlue1', 39 +deep_sky_blue1 = Colors.register( + RGB(0, 175, 255), HLS(100, 98, 50), 'DeepSkyBlue1', 39, ) green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) -springGreen3 = Colors.register( - RGB(0, 215, 95), HLS(100, 46, 42), 'SpringGreen3', 41 +spring_green3 = Colors.register( + RGB(0, 215, 95), HLS(100, 46, 42), 'SpringGreen3', 41, ) -springGreen2 = Colors.register( - RGB(0, 215, 135), HLS(100, 57, 42), 'SpringGreen2', 42 +spring_green2 = Colors.register( + RGB(0, 215, 135), HLS(100, 57, 42), 'SpringGreen2', 42, ) cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) -darkTurquoise = Colors.register( - RGB(0, 215, 215), HLS(100, 180, 42), 'DarkTurquoise', 44 +dark_turquoise = Colors.register( + RGB(0, 215, 215), HLS(100, 180, 42), 'DarkTurquoise', 44, ) turquoise2 = Colors.register( - RGB(0, 215, 255), HLS(100, 89, 50), 'Turquoise2', 45 + RGB(0, 215, 255), HLS(100, 89, 50), 'Turquoise2', 45, ) green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) -springGreen2 = Colors.register( - RGB(0, 255, 95), HLS(100, 42, 50), 'SpringGreen2', 47 +spring_green2 = Colors.register( + RGB(0, 255, 95), HLS(100, 42, 50), 'SpringGreen2', 47, ) -springGreen1 = Colors.register( - RGB(0, 255, 135), HLS(100, 51, 50), 'SpringGreen1', 48 +spring_green1 = Colors.register( + RGB(0, 255, 135), HLS(100, 51, 50), 'SpringGreen1', 48, ) -mediumSpringGreen = Colors.register( - RGB(0, 255, 175), HLS(100, 61, 50), 'MediumSpringGreen', 49 +medium_spring_green = Colors.register( + RGB(0, 255, 175), HLS(100, 61, 50), 'MediumSpringGreen', 49, ) cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) -darkRed = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) -deepPink4 = Colors.register(RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53) +dark_red = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) +deep_pink4 = Colors.register(RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53) purple4 = Colors.register(RGB(95, 0, 135), HLS(100, 82, 26), 'Purple4', 54) purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) -blueViolet = Colors.register( - RGB(95, 0, 255), HLS(100, 62, 50), 'BlueViolet', 57 +blue_violet = Colors.register( + RGB(95, 0, 255), HLS(100, 62, 50), 'BlueViolet', 57, ) orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) -mediumPurple4 = Colors.register( - RGB(95, 95, 135), HLS(17, 240, 45), 'MediumPurple4', 60 +medium_purple4 = Colors.register( + RGB(95, 95, 135), HLS(17, 240, 45), 'MediumPurple4', 60, ) -slateBlue3 = Colors.register( - RGB(95, 95, 175), HLS(33, 240, 52), 'SlateBlue3', 61 +slate_blue3 = Colors.register( + RGB(95, 95, 175), HLS(33, 240, 52), 'SlateBlue3', 61, ) -slateBlue3 = Colors.register( - RGB(95, 95, 215), HLS(60, 240, 60), 'SlateBlue3', 62 +slate_blue3 = Colors.register( + RGB(95, 95, 215), HLS(60, 240, 60), 'SlateBlue3', 62, ) -royalBlue1 = Colors.register( - RGB(95, 95, 255), HLS(100, 240, 68), 'RoyalBlue1', 63 +royal_blue1 = Colors.register( + RGB(95, 95, 255), HLS(100, 240, 68), 'RoyalBlue1', 63, ) chartreuse4 = Colors.register( - RGB(95, 135, 0), HLS(100, 7, 26), 'Chartreuse4', 64 + RGB(95, 135, 0), HLS(100, 7, 26), 'Chartreuse4', 64, ) -darkSeaGreen4 = Colors.register( - RGB(95, 135, 95), HLS(17, 120, 45), 'DarkSeaGreen4', 65 +dark_sea_green4 = Colors.register( + RGB(95, 135, 95), HLS(17, 120, 45), 'DarkSeaGreen4', 65, ) -paleTurquoise4 = Colors.register( - RGB(95, 135, 135), HLS(17, 180, 45), 'PaleTurquoise4', 66 +pale_turquoise4 = Colors.register( + RGB(95, 135, 135), HLS(17, 180, 45), 'PaleTurquoise4', 66, ) -steelBlue = Colors.register( - RGB(95, 135, 175), HLS(33, 210, 52), 'SteelBlue', 67 +steel_blue = Colors.register( + RGB(95, 135, 175), HLS(33, 210, 52), 'SteelBlue', 67, ) -steelBlue3 = Colors.register( - RGB(95, 135, 215), HLS(60, 220, 60), 'SteelBlue3', 68 +steel_blue3 = Colors.register( + RGB(95, 135, 215), HLS(60, 220, 60), 'SteelBlue3', 68, ) -cornflowerBlue = Colors.register( - RGB(95, 135, 255), HLS(100, 225, 68), 'CornflowerBlue', 69 +cornflower_blue = Colors.register( + RGB(95, 135, 255), HLS(100, 225, 68), 'CornflowerBlue', 69, ) chartreuse3 = Colors.register( - RGB(95, 175, 0), HLS(100, 7, 34), 'Chartreuse3', 70 + RGB(95, 175, 0), HLS(100, 7, 34), 'Chartreuse3', 70, ) -darkSeaGreen4 = Colors.register( - RGB(95, 175, 95), HLS(33, 120, 52), 'DarkSeaGreen4', 71 +dark_sea_green4 = Colors.register( + RGB(95, 175, 95), HLS(33, 120, 52), 'DarkSeaGreen4', 71, ) -cadetBlue = Colors.register( - RGB(95, 175, 135), HLS(33, 150, 52), 'CadetBlue', 72 +cadet_blue = Colors.register( + RGB(95, 175, 135), HLS(33, 150, 52), 'CadetBlue', 72, ) -cadetBlue = Colors.register( - RGB(95, 175, 175), HLS(33, 180, 52), 'CadetBlue', 73 +cadet_blue = Colors.register( + RGB(95, 175, 175), HLS(33, 180, 52), 'CadetBlue', 73, ) -skyBlue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) -steelBlue1 = Colors.register( - RGB(95, 175, 255), HLS(100, 210, 68), 'SteelBlue1', 75 +sky_blue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) +steel_blue1 = Colors.register( + RGB(95, 175, 255), HLS(100, 210, 68), 'SteelBlue1', 75, ) chartreuse3 = Colors.register( - RGB(95, 215, 0), HLS(100, 3, 42), 'Chartreuse3', 76 + RGB(95, 215, 0), HLS(100, 3, 42), 'Chartreuse3', 76, ) -paleGreen3 = Colors.register( - RGB(95, 215, 95), HLS(60, 120, 60), 'PaleGreen3', 77 +pale_green3 = Colors.register( + RGB(95, 215, 95), HLS(60, 120, 60), 'PaleGreen3', 77, ) -seaGreen3 = Colors.register( - RGB(95, 215, 135), HLS(60, 140, 60), 'SeaGreen3', 78 +sea_green3 = Colors.register( + RGB(95, 215, 135), HLS(60, 140, 60), 'SeaGreen3', 78, ) aquamarine3 = Colors.register( - RGB(95, 215, 175), HLS(60, 160, 60), 'Aquamarine3', 79 + RGB(95, 215, 175), HLS(60, 160, 60), 'Aquamarine3', 79, ) -mediumTurquoise = Colors.register( - RGB(95, 215, 215), HLS(60, 180, 60), 'MediumTurquoise', 80 +medium_turquoise = Colors.register( + RGB(95, 215, 215), HLS(60, 180, 60), 'MediumTurquoise', 80, ) -steelBlue1 = Colors.register( - RGB(95, 215, 255), HLS(100, 195, 68), 'SteelBlue1', 81 +steel_blue1 = Colors.register( + RGB(95, 215, 255), HLS(100, 195, 68), 'SteelBlue1', 81, ) chartreuse2 = Colors.register( - RGB(95, 255, 0), HLS(100, 7, 50), 'Chartreuse2', 82 + RGB(95, 255, 0), HLS(100, 7, 50), 'Chartreuse2', 82, ) -seaGreen2 = Colors.register( - RGB(95, 255, 95), HLS(100, 120, 68), 'SeaGreen2', 83 +sea_green2 = Colors.register( + RGB(95, 255, 95), HLS(100, 120, 68), 'SeaGreen2', 83, ) -seaGreen1 = Colors.register( - RGB(95, 255, 135), HLS(100, 135, 68), 'SeaGreen1', 84 +sea_green1 = Colors.register( + RGB(95, 255, 135), HLS(100, 135, 68), 'SeaGreen1', 84, ) -seaGreen1 = Colors.register( - RGB(95, 255, 175), HLS(100, 150, 68), 'SeaGreen1', 85 +sea_green1 = Colors.register( + RGB(95, 255, 175), HLS(100, 150, 68), 'SeaGreen1', 85, ) aquamarine1 = Colors.register( - RGB(95, 255, 215), HLS(100, 165, 68), 'Aquamarine1', 86 + RGB(95, 255, 215), HLS(100, 165, 68), 'Aquamarine1', 86, ) -darkSlateGray2 = Colors.register( - RGB(95, 255, 255), HLS(100, 180, 68), 'DarkSlateGray2', 87 +dark_slate_gray2 = Colors.register( + RGB(95, 255, 255), HLS(100, 180, 68), 'DarkSlateGray2', 87, ) -darkRed = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) -deepPink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) -darkMagenta = Colors.register( - RGB(135, 0, 135), HLS(100, 300, 26), 'DarkMagenta', 90 +dark_red = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) +deep_pink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) +dark_magenta = Colors.register( + RGB(135, 0, 135), HLS(100, 300, 26), 'DarkMagenta', 90, ) -darkMagenta = Colors.register( - RGB(135, 0, 175), HLS(100, 86, 34), 'DarkMagenta', 91 +dark_magenta = Colors.register( + RGB(135, 0, 175), HLS(100, 86, 34), 'DarkMagenta', 91, ) -darkViolet = Colors.register( - RGB(135, 0, 215), HLS(100, 77, 42), 'DarkViolet', 92 +dark_violet = Colors.register( + RGB(135, 0, 215), HLS(100, 77, 42), 'DarkViolet', 92, ) purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) -lightPink4 = Colors.register( - RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95 +light_pink4 = Colors.register( + RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95, ) plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) -mediumPurple3 = Colors.register( - RGB(135, 95, 175), HLS(33, 270, 52), 'MediumPurple3', 97 +medium_purple3 = Colors.register( + RGB(135, 95, 175), HLS(33, 270, 52), 'MediumPurple3', 97, ) -mediumPurple3 = Colors.register( - RGB(135, 95, 215), HLS(60, 260, 60), 'MediumPurple3', 98 +medium_purple3 = Colors.register( + RGB(135, 95, 215), HLS(60, 260, 60), 'MediumPurple3', 98, ) -slateBlue1 = Colors.register( - RGB(135, 95, 255), HLS(100, 255, 68), 'SlateBlue1', 99 +slate_blue1 = Colors.register( + RGB(135, 95, 255), HLS(100, 255, 68), 'SlateBlue1', 99, ) yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) -lightSlateGrey = Colors.register( - RGB(135, 135, 175), HLS(20, 240, 60), 'LightSlateGrey', 103 +light_slate_grey = Colors.register( + RGB(135, 135, 175), HLS(20, 240, 60), 'LightSlateGrey', 103, ) -mediumPurple = Colors.register( - RGB(135, 135, 215), HLS(50, 240, 68), 'MediumPurple', 104 +medium_purple = Colors.register( + RGB(135, 135, 215), HLS(50, 240, 68), 'MediumPurple', 104, ) -lightSlateBlue = Colors.register( - RGB(135, 135, 255), HLS(100, 240, 76), 'LightSlateBlue', 105 +light_slate_blue = Colors.register( + RGB(135, 135, 255), HLS(100, 240, 76), 'LightSlateBlue', 105, ) yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) -darkOliveGreen3 = Colors.register( - RGB(135, 175, 95), HLS(33, 90, 52), 'DarkOliveGreen3', 107 +dark_olive_green3 = Colors.register( + RGB(135, 175, 95), HLS(33, 90, 52), 'DarkOliveGreen3', 107, ) -darkSeaGreen = Colors.register( - RGB(135, 175, 135), HLS(20, 120, 60), 'DarkSeaGreen', 108 +dark_sea_green = Colors.register( + RGB(135, 175, 135), HLS(20, 120, 60), 'DarkSeaGreen', 108, ) -lightSkyBlue3 = Colors.register( - RGB(135, 175, 175), HLS(20, 180, 60), 'LightSkyBlue3', 109 +light_sky_blue3 = Colors.register( + RGB(135, 175, 175), HLS(20, 180, 60), 'LightSkyBlue3', 109, ) -lightSkyBlue3 = Colors.register( - RGB(135, 175, 215), HLS(50, 210, 68), 'LightSkyBlue3', 110 +light_sky_blue3 = Colors.register( + RGB(135, 175, 215), HLS(50, 210, 68), 'LightSkyBlue3', 110, ) -skyBlue2 = Colors.register( - RGB(135, 175, 255), HLS(100, 220, 76), 'SkyBlue2', 111 +sky_blue2 = Colors.register( + RGB(135, 175, 255), HLS(100, 220, 76), 'SkyBlue2', 111, ) chartreuse2 = Colors.register( - RGB(135, 215, 0), HLS(100, 2, 42), 'Chartreuse2', 112 + RGB(135, 215, 0), HLS(100, 2, 42), 'Chartreuse2', 112, ) -darkOliveGreen3 = Colors.register( - RGB(135, 215, 95), HLS(60, 100, 60), 'DarkOliveGreen3', 113 +dark_olive_green3 = Colors.register( + RGB(135, 215, 95), HLS(60, 100, 60), 'DarkOliveGreen3', 113, ) -paleGreen3 = Colors.register( - RGB(135, 215, 135), HLS(50, 120, 68), 'PaleGreen3', 114 +pale_green3 = Colors.register( + RGB(135, 215, 135), HLS(50, 120, 68), 'PaleGreen3', 114, ) -darkSeaGreen3 = Colors.register( - RGB(135, 215, 175), HLS(50, 150, 68), 'DarkSeaGreen3', 115 +dark_sea_green3 = Colors.register( + RGB(135, 215, 175), HLS(50, 150, 68), 'DarkSeaGreen3', 115, ) -darkSlateGray3 = Colors.register( - RGB(135, 215, 215), HLS(50, 180, 68), 'DarkSlateGray3', 116 +dark_slate_gray3 = Colors.register( + RGB(135, 215, 215), HLS(50, 180, 68), 'DarkSlateGray3', 116, ) -skyBlue1 = Colors.register( - RGB(135, 215, 255), HLS(100, 200, 76), 'SkyBlue1', 117 +sky_blue1 = Colors.register( + RGB(135, 215, 255), HLS(100, 200, 76), 'SkyBlue1', 117, ) chartreuse1 = Colors.register( - RGB(135, 255, 0), HLS(100, 8, 50), 'Chartreuse1', 118 + RGB(135, 255, 0), HLS(100, 8, 50), 'Chartreuse1', 118, ) -lightGreen = Colors.register( - RGB(135, 255, 95), HLS(100, 105, 68), 'LightGreen', 119 +light_green = Colors.register( + RGB(135, 255, 95), HLS(100, 105, 68), 'LightGreen', 119, ) -lightGreen = Colors.register( - RGB(135, 255, 135), HLS(100, 120, 76), 'LightGreen', 120 +light_green = Colors.register( + RGB(135, 255, 135), HLS(100, 120, 76), 'LightGreen', 120, ) -paleGreen1 = Colors.register( - RGB(135, 255, 175), HLS(100, 140, 76), 'PaleGreen1', 121 +pale_green1 = Colors.register( + RGB(135, 255, 175), HLS(100, 140, 76), 'PaleGreen1', 121, ) aquamarine1 = Colors.register( - RGB(135, 255, 215), HLS(100, 160, 76), 'Aquamarine1', 122 + RGB(135, 255, 215), HLS(100, 160, 76), 'Aquamarine1', 122, ) -darkSlateGray1 = Colors.register( - RGB(135, 255, 255), HLS(100, 180, 76), 'DarkSlateGray1', 123 +dark_slate_gray1 = Colors.register( + RGB(135, 255, 255), HLS(100, 180, 76), 'DarkSlateGray1', 123, ) red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) -deepPink4 = Colors.register( - RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125 +deep_pink4 = Colors.register( + RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125, ) -mediumVioletRed = Colors.register( - RGB(175, 0, 135), HLS(100, 13, 34), 'MediumVioletRed', 126 +medium_violet_red = Colors.register( + RGB(175, 0, 135), HLS(100, 13, 34), 'MediumVioletRed', 126, ) magenta3 = Colors.register( - RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127 + RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127, ) -darkViolet = Colors.register( - RGB(175, 0, 215), HLS(100, 88, 42), 'DarkViolet', 128 +dark_violet = Colors.register( + RGB(175, 0, 215), HLS(100, 88, 42), 'DarkViolet', 128, ) purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) -darkOrange3 = Colors.register( - RGB(175, 95, 0), HLS(100, 2, 34), 'DarkOrange3', 130 +dark_orange3 = Colors.register( + RGB(175, 95, 0), HLS(100, 2, 34), 'DarkOrange3', 130, ) -indianRed = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) -hotPink3 = Colors.register( - RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132 +indian_red = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) +hot_pink3 = Colors.register( + RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132, ) -mediumOrchid3 = Colors.register( - RGB(175, 95, 175), HLS(33, 300, 52), 'MediumOrchid3', 133 +medium_orchid3 = Colors.register( + RGB(175, 95, 175), HLS(33, 300, 52), 'MediumOrchid3', 133, ) -mediumOrchid = Colors.register( - RGB(175, 95, 215), HLS(60, 280, 60), 'MediumOrchid', 134 +medium_orchid = Colors.register( + RGB(175, 95, 215), HLS(60, 280, 60), 'MediumOrchid', 134, ) -mediumPurple2 = Colors.register( - RGB(175, 95, 255), HLS(100, 270, 68), 'MediumPurple2', 135 +medium_purple2 = Colors.register( + RGB(175, 95, 255), HLS(100, 270, 68), 'MediumPurple2', 135, ) -darkGoldenrod = Colors.register( - RGB(175, 135, 0), HLS(100, 6, 34), 'DarkGoldenrod', 136 +dark_goldenrod = Colors.register( + RGB(175, 135, 0), HLS(100, 6, 34), 'DarkGoldenrod', 136, ) -lightSalmon3 = Colors.register( - RGB(175, 135, 95), HLS(33, 30, 52), 'LightSalmon3', 137 +light_salmon3 = Colors.register( + RGB(175, 135, 95), HLS(33, 30, 52), 'LightSalmon3', 137, ) -rosyBrown = Colors.register( - RGB(175, 135, 135), HLS(20, 0, 60), 'RosyBrown', 138 +rosy_brown = Colors.register( + RGB(175, 135, 135), HLS(20, 0, 60), 'RosyBrown', 138, ) grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) -mediumPurple2 = Colors.register( - RGB(175, 135, 215), HLS(50, 270, 68), 'MediumPurple2', 140 +medium_purple2 = Colors.register( + RGB(175, 135, 215), HLS(50, 270, 68), 'MediumPurple2', 140, ) -mediumPurple1 = Colors.register( - RGB(175, 135, 255), HLS(100, 260, 76), 'MediumPurple1', 141 +medium_purple1 = Colors.register( + RGB(175, 135, 255), HLS(100, 260, 76), 'MediumPurple1', 141, ) gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) -darkKhaki = Colors.register( - RGB(175, 175, 95), HLS(33, 60, 52), 'DarkKhaki', 143 +dark_khaki = Colors.register( + RGB(175, 175, 95), HLS(33, 60, 52), 'DarkKhaki', 143, ) -navajoWhite3 = Colors.register( - RGB(175, 175, 135), HLS(20, 60, 60), 'NavajoWhite3', 144 +navajo_white3 = Colors.register( + RGB(175, 175, 135), HLS(20, 60, 60), 'NavajoWhite3', 144, ) grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) -lightSteelBlue3 = Colors.register( - RGB(175, 175, 215), HLS(33, 240, 76), 'LightSteelBlue3', 146 +light_steel_blue3 = Colors.register( + RGB(175, 175, 215), HLS(33, 240, 76), 'LightSteelBlue3', 146, ) -lightSteelBlue = Colors.register( - RGB(175, 175, 255), HLS(100, 240, 84), 'LightSteelBlue', 147 +light_steel_blue = Colors.register( + RGB(175, 175, 255), HLS(100, 240, 84), 'LightSteelBlue', 147, ) yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) -darkOliveGreen3 = Colors.register( - RGB(175, 215, 95), HLS(60, 80, 60), 'DarkOliveGreen3', 149 +dark_olive_green3 = Colors.register( + RGB(175, 215, 95), HLS(60, 80, 60), 'DarkOliveGreen3', 149, ) -darkSeaGreen3 = Colors.register( - RGB(175, 215, 135), HLS(50, 90, 68), 'DarkSeaGreen3', 150 +dark_sea_green3 = Colors.register( + RGB(175, 215, 135), HLS(50, 90, 68), 'DarkSeaGreen3', 150, ) -darkSeaGreen2 = Colors.register( - RGB(175, 215, 175), HLS(33, 120, 76), 'DarkSeaGreen2', 151 +dark_sea_green2 = Colors.register( + RGB(175, 215, 175), HLS(33, 120, 76), 'DarkSeaGreen2', 151, ) -lightCyan3 = Colors.register( - RGB(175, 215, 215), HLS(33, 180, 76), 'LightCyan3', 152 +light_cyan3 = Colors.register( + RGB(175, 215, 215), HLS(33, 180, 76), 'LightCyan3', 152, ) -lightSkyBlue1 = Colors.register( - RGB(175, 215, 255), HLS(100, 210, 84), 'LightSkyBlue1', 153 +light_sky_blue1 = Colors.register( + RGB(175, 215, 255), HLS(100, 210, 84), 'LightSkyBlue1', 153, ) -greenYellow = Colors.register( - RGB(175, 255, 0), HLS(100, 8, 50), 'GreenYellow', 154 +green_yellow = Colors.register( + RGB(175, 255, 0), HLS(100, 8, 50), 'GreenYellow', 154, ) -darkOliveGreen2 = Colors.register( - RGB(175, 255, 95), HLS(100, 90, 68), 'DarkOliveGreen2', 155 +dark_olive_green2 = Colors.register( + RGB(175, 255, 95), HLS(100, 90, 68), 'DarkOliveGreen2', 155, ) -paleGreen1 = Colors.register( - RGB(175, 255, 135), HLS(100, 100, 76), 'PaleGreen1', 156 +pale_green1 = Colors.register( + RGB(175, 255, 135), HLS(100, 100, 76), 'PaleGreen1', 156, ) -darkSeaGreen2 = Colors.register( - RGB(175, 255, 175), HLS(100, 120, 84), 'DarkSeaGreen2', 157 +dark_sea_green2 = Colors.register( + RGB(175, 255, 175), HLS(100, 120, 84), 'DarkSeaGreen2', 157, ) -darkSeaGreen1 = Colors.register( - RGB(175, 255, 215), HLS(100, 150, 84), 'DarkSeaGreen1', 158 +dark_sea_green1 = Colors.register( + RGB(175, 255, 215), HLS(100, 150, 84), 'DarkSeaGreen1', 158, ) -paleTurquoise1 = Colors.register( - RGB(175, 255, 255), HLS(100, 180, 84), 'PaleTurquoise1', 159 +pale_turquoise1 = Colors.register( + RGB(175, 255, 255), HLS(100, 180, 84), 'PaleTurquoise1', 159, ) red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) -deepPink3 = Colors.register( - RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161 +deep_pink3 = Colors.register( + RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161, ) -deepPink3 = Colors.register( - RGB(215, 0, 135), HLS(100, 22, 42), 'DeepPink3', 162 +deep_pink3 = Colors.register( + RGB(215, 0, 135), HLS(100, 22, 42), 'DeepPink3', 162, ) magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) magenta3 = Colors.register( - RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164 + RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164, ) magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) -darkOrange3 = Colors.register( - RGB(215, 95, 0), HLS(100, 6, 42), 'DarkOrange3', 166 +dark_orange3 = Colors.register( + RGB(215, 95, 0), HLS(100, 6, 42), 'DarkOrange3', 166, ) -indianRed = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) -hotPink3 = Colors.register( - RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168 +indian_red = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) +hot_pink3 = Colors.register( + RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168, ) -hotPink2 = Colors.register( - RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169 +hot_pink2 = Colors.register( + RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169, ) orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) -mediumOrchid1 = Colors.register( - RGB(215, 95, 255), HLS(100, 285, 68), 'MediumOrchid1', 171 +medium_orchid1 = Colors.register( + RGB(215, 95, 255), HLS(100, 285, 68), 'MediumOrchid1', 171, ) orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) -lightSalmon3 = Colors.register( - RGB(215, 135, 95), HLS(60, 20, 60), 'LightSalmon3', 173 +light_salmon3 = Colors.register( + RGB(215, 135, 95), HLS(60, 20, 60), 'LightSalmon3', 173, ) -lightPink3 = Colors.register( - RGB(215, 135, 135), HLS(50, 0, 68), 'LightPink3', 174 +light_pink3 = Colors.register( + RGB(215, 135, 135), HLS(50, 0, 68), 'LightPink3', 174, ) pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) -lightGoldenrod3 = Colors.register( - RGB(215, 175, 95), HLS(60, 40, 60), 'LightGoldenrod3', 179 +light_goldenrod3 = Colors.register( + RGB(215, 175, 95), HLS(60, 40, 60), 'LightGoldenrod3', 179, ) tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) -mistyRose3 = Colors.register( - RGB(215, 175, 175), HLS(33, 0, 76), 'MistyRose3', 181 +misty_rose3 = Colors.register( + RGB(215, 175, 175), HLS(33, 0, 76), 'MistyRose3', 181, ) thistle3 = Colors.register( - RGB(215, 175, 215), HLS(33, 300, 76), 'Thistle3', 182 + RGB(215, 175, 215), HLS(33, 300, 76), 'Thistle3', 182, ) plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) -lightGoldenrod2 = Colors.register( - RGB(215, 215, 135), HLS(50, 60, 68), 'LightGoldenrod2', 186 +light_goldenrod2 = Colors.register( + RGB(215, 215, 135), HLS(50, 60, 68), 'LightGoldenrod2', 186, ) -lightYellow3 = Colors.register( - RGB(215, 215, 175), HLS(33, 60, 76), 'LightYellow3', 187 +light_yellow3 = Colors.register( + RGB(215, 215, 175), HLS(33, 60, 76), 'LightYellow3', 187, ) grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) -lightSteelBlue1 = Colors.register( - RGB(215, 215, 255), HLS(100, 240, 92), 'LightSteelBlue1', 189 +light_steel_blue1 = Colors.register( + RGB(215, 215, 255), HLS(100, 240, 92), 'LightSteelBlue1', 189, ) yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) -darkOliveGreen1 = Colors.register( - RGB(215, 255, 95), HLS(100, 75, 68), 'DarkOliveGreen1', 191 +dark_olive_green1 = Colors.register( + RGB(215, 255, 95), HLS(100, 75, 68), 'DarkOliveGreen1', 191, ) -darkOliveGreen1 = Colors.register( - RGB(215, 255, 135), HLS(100, 80, 76), 'DarkOliveGreen1', 192 +dark_olive_green1 = Colors.register( + RGB(215, 255, 135), HLS(100, 80, 76), 'DarkOliveGreen1', 192, ) -darkSeaGreen1 = Colors.register( - RGB(215, 255, 175), HLS(100, 90, 84), 'DarkSeaGreen1', 193 +dark_sea_green1 = Colors.register( + RGB(215, 255, 175), HLS(100, 90, 84), 'DarkSeaGreen1', 193, ) honeydew2 = Colors.register( - RGB(215, 255, 215), HLS(100, 120, 92), 'Honeydew2', 194 + RGB(215, 255, 215), HLS(100, 120, 92), 'Honeydew2', 194, ) -lightCyan1 = Colors.register( - RGB(215, 255, 255), HLS(100, 180, 92), 'LightCyan1', 195 +light_cyan1 = Colors.register( + RGB(215, 255, 255), HLS(100, 180, 92), 'LightCyan1', 195, ) red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) -deepPink2 = Colors.register( - RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197 +deep_pink2 = Colors.register( + RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197, ) -deepPink1 = Colors.register( - RGB(255, 0, 135), HLS(100, 28, 50), 'DeepPink1', 198 +deep_pink1 = Colors.register( + RGB(255, 0, 135), HLS(100, 28, 50), 'DeepPink1', 198, ) -deepPink1 = Colors.register( - RGB(255, 0, 175), HLS(100, 18, 50), 'DeepPink1', 199 +deep_pink1 = Colors.register( + RGB(255, 0, 175), HLS(100, 18, 50), 'DeepPink1', 199, ) magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) magenta1 = Colors.register( - RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201 + RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201, ) -orangeRed1 = Colors.register( - RGB(255, 95, 0), HLS(100, 2, 50), 'OrangeRed1', 202 +orange_red1 = Colors.register( + RGB(255, 95, 0), HLS(100, 2, 50), 'OrangeRed1', 202, ) -indianRed1 = Colors.register( - RGB(255, 95, 95), HLS(100, 0, 68), 'IndianRed1', 203 +indian_red1 = Colors.register( + RGB(255, 95, 95), HLS(100, 0, 68), 'IndianRed1', 203, ) -indianRed1 = Colors.register( - RGB(255, 95, 135), HLS(100, 345, 68), 'IndianRed1', 204 +indian_red1 = Colors.register( + RGB(255, 95, 135), HLS(100, 345, 68), 'IndianRed1', 204, ) -hotPink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) -hotPink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) -mediumOrchid1 = Colors.register( - RGB(255, 95, 255), HLS(100, 300, 68), 'MediumOrchid1', 207 +hot_pink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) +hot_pink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) +medium_orchid1 = Colors.register( + RGB(255, 95, 255), HLS(100, 300, 68), 'MediumOrchid1', 207, ) -darkOrange = Colors.register( - RGB(255, 135, 0), HLS(100, 1, 50), 'DarkOrange', 208 +dark_orange = Colors.register( + RGB(255, 135, 0), HLS(100, 1, 50), 'DarkOrange', 208, ) salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) -lightCoral = Colors.register( - RGB(255, 135, 135), HLS(100, 0, 76), 'LightCoral', 210 +light_coral = Colors.register( + RGB(255, 135, 135), HLS(100, 0, 76), 'LightCoral', 210, ) -paleVioletRed1 = Colors.register( - RGB(255, 135, 175), HLS(100, 340, 76), 'PaleVioletRed1', 211 +pale_violet_red1 = Colors.register( + RGB(255, 135, 175), HLS(100, 340, 76), 'PaleVioletRed1', 211, ) orchid2 = Colors.register( - RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212 + RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212, ) orchid1 = Colors.register( - RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213 + RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213, ) orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) -sandyBrown = Colors.register( - RGB(255, 175, 95), HLS(100, 30, 68), 'SandyBrown', 215 +sandy_brown = Colors.register( + RGB(255, 175, 95), HLS(100, 30, 68), 'SandyBrown', 215, ) -lightSalmon1 = Colors.register( - RGB(255, 175, 135), HLS(100, 20, 76), 'LightSalmon1', 216 +light_salmon1 = Colors.register( + RGB(255, 175, 135), HLS(100, 20, 76), 'LightSalmon1', 216, ) -lightPink1 = Colors.register( - RGB(255, 175, 175), HLS(100, 0, 84), 'LightPink1', 217 +light_pink1 = Colors.register( + RGB(255, 175, 175), HLS(100, 0, 84), 'LightPink1', 217, ) pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) -lightGoldenrod2 = Colors.register( - RGB(255, 215, 95), HLS(100, 45, 68), 'LightGoldenrod2', 221 +light_goldenrod2 = Colors.register( + RGB(255, 215, 95), HLS(100, 45, 68), 'LightGoldenrod2', 221, ) -lightGoldenrod2 = Colors.register( - RGB(255, 215, 135), HLS(100, 40, 76), 'LightGoldenrod2', 222 +light_goldenrod2 = Colors.register( + RGB(255, 215, 135), HLS(100, 40, 76), 'LightGoldenrod2', 222, ) -navajoWhite1 = Colors.register( - RGB(255, 215, 175), HLS(100, 30, 84), 'NavajoWhite1', 223 +navajo_white1 = Colors.register( + RGB(255, 215, 175), HLS(100, 30, 84), 'NavajoWhite1', 223, ) -mistyRose1 = Colors.register( - RGB(255, 215, 215), HLS(100, 0, 92), 'MistyRose1', 224 +misty_rose1 = Colors.register( + RGB(255, 215, 215), HLS(100, 0, 92), 'MistyRose1', 224, ) thistle1 = Colors.register( - RGB(255, 215, 255), HLS(100, 300, 92), 'Thistle1', 225 + RGB(255, 215, 255), HLS(100, 300, 92), 'Thistle1', 225, ) yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) -lightGoldenrod1 = Colors.register( - RGB(255, 255, 95), HLS(100, 60, 68), 'LightGoldenrod1', 227 +light_goldenrod1 = Colors.register( + RGB(255, 255, 95), HLS(100, 60, 68), 'LightGoldenrod1', 227, ) khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) cornsilk1 = Colors.register( - RGB(255, 255, 215), HLS(100, 60, 92), 'Cornsilk1', 230 + RGB(255, 255, 215), HLS(100, 60, 92), 'Cornsilk1', 230, ) grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) @@ -558,21 +558,21 @@ dark_gradient = ColorGradient( red1, - orangeRed1, - darkOrange, + orange_red1, + dark_orange, orange1, yellow1, yellow2, - greenYellow, + green_yellow, green1, ) light_gradient = ColorGradient( red1, - orangeRed1, - darkOrange, + orange_red1, + dark_orange, orange1, gold3, - darkOliveGreen3, + dark_olive_green3, yellow4, green3, ) @@ -596,4 +596,9 @@ for i in base.ColorSupport: base.color_support = i - print(i, red.fg('RED!'), red.bg('RED!'), red.underline('RED!')) + print( # noqa: T201 + i, + red.fg('RED!'), + red.bg('RED!'), + red.underline('RED!'), + ) diff --git a/progressbar/terminal/os_specific/__init__.py b/progressbar/terminal/os_specific/__init__.py index 4cff9feb..3d27cf5c 100644 --- a/progressbar/terminal/os_specific/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -3,8 +3,8 @@ if sys.platform.startswith('win'): from .windows import ( getch as _getch, - set_console_mode as _set_console_mode, reset_console_mode as _reset_console_mode, + set_console_mode as _set_console_mode, ) else: diff --git a/progressbar/terminal/os_specific/posix.py b/progressbar/terminal/os_specific/posix.py index e46fbdf0..e9bd475e 100644 --- a/progressbar/terminal/os_specific/posix.py +++ b/progressbar/terminal/os_specific/posix.py @@ -1,6 +1,6 @@ import sys -import tty import termios +import tty def getch(): diff --git a/progressbar/terminal/os_specific/windows.py b/progressbar/terminal/os_specific/windows.py index 6084f3b1..0342efbb 100644 --- a/progressbar/terminal/os_specific/windows.py +++ b/progressbar/terminal/os_specific/windows.py @@ -1,13 +1,20 @@ +# ruff: noqa: N801 +''' +Windows specific code for the terminal. + +Note that the naming convention here is non-pythonic because we are +matching the Windows API naming. +''' import ctypes from ctypes.wintypes import ( + BOOL as _BOOL, + CHAR as _CHAR, DWORD as _DWORD, HANDLE as _HANDLE, - BOOL as _BOOL, - WORD as _WORD, + SHORT as _SHORT, UINT as _UINT, WCHAR as _WCHAR, - CHAR as _CHAR, - SHORT as _SHORT, + WORD as _WORD, ) _kernel32 = ctypes.windll.Kernel32 @@ -33,97 +40,97 @@ _ReadConsoleInput.restype = _BOOL -_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) +_h_console_input = _GetStdHandle(_STD_INPUT_HANDLE) _input_mode = _DWORD() -_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) +_GetConsoleMode(_HANDLE(_h_console_input), ctypes.byref(_input_mode)) -_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) +_h_console_output = _GetStdHandle(_STD_OUTPUT_HANDLE) _output_mode = _DWORD() -_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) +_GetConsoleMode(_HANDLE(_h_console_output), ctypes.byref(_output_mode)) class _COORD(ctypes.Structure): - _fields_ = [('X', _SHORT), ('Y', _SHORT)] + _fields_ = (('X', _SHORT), ('Y', _SHORT)) class _FOCUS_EVENT_RECORD(ctypes.Structure): - _fields_ = [('bSetFocus', _BOOL)] + _fields_ = (('bSetFocus', _BOOL)) class _KEY_EVENT_RECORD(ctypes.Structure): class _uchar(ctypes.Union): - _fields_ = [('UnicodeChar', _WCHAR), ('AsciiChar', _CHAR)] + _fields_ = (('UnicodeChar', _WCHAR), ('AsciiChar', _CHAR)) - _fields_ = [ + _fields_ = ( ('bKeyDown', _BOOL), ('wRepeatCount', _WORD), ('wVirtualKeyCode', _WORD), ('wVirtualScanCode', _WORD), ('uChar', _uchar), ('dwControlKeyState', _DWORD), - ] + ) class _MENU_EVENT_RECORD(ctypes.Structure): - _fields_ = [('dwCommandId', _UINT)] + _fields_ = (('dwCommandId', _UINT)) class _MOUSE_EVENT_RECORD(ctypes.Structure): - _fields_ = [ + _fields_ = ( ('dwMousePosition', _COORD), ('dwButtonState', _DWORD), ('dwControlKeyState', _DWORD), ('dwEventFlags', _DWORD), - ] + ) class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): - _fields_ = [('dwSize', _COORD)] + _fields_ = (('dwSize', _COORD)) class _INPUT_RECORD(ctypes.Structure): class _Event(ctypes.Union): - _fields_ = [ + _fields_ = ( ('KeyEvent', _KEY_EVENT_RECORD), ('MouseEvent', _MOUSE_EVENT_RECORD), ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), ('MenuEvent', _MENU_EVENT_RECORD), ('FocusEvent', _FOCUS_EVENT_RECORD), - ] + ) - _fields_ = [('EventType', _WORD), ('Event', _Event)] + _fields_ = (('EventType', _WORD), ('Event', _Event)) def reset_console_mode(): - _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) - _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) + _SetConsoleMode(_HANDLE(_h_console_input), _DWORD(_input_mode.value)) + _SetConsoleMode(_HANDLE(_h_console_output), _DWORD(_output_mode.value)) def set_console_mode(): mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT - _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) + _SetConsoleMode(_HANDLE(_h_console_input), _DWORD(mode)) mode = ( _output_mode.value | _ENABLE_PROCESSED_OUTPUT | _ENABLE_VIRTUAL_TERMINAL_PROCESSING ) - _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) + _SetConsoleMode(_HANDLE(_h_console_output), _DWORD(mode)) def getch(): - lpBuffer = (_INPUT_RECORD * 2)() - nLength = _DWORD(2) - lpNumberOfEventsRead = _DWORD() + lp_buffer = (_INPUT_RECORD * 2)() + n_length = _DWORD(2) + lp_number_of_events_read = _DWORD() _ReadConsoleInput( - _HANDLE(_hConsoleInput), - lpBuffer, - nLength, - ctypes.byref(lpNumberOfEventsRead), + _HANDLE(_h_console_input), + lp_buffer, + n_length, + ctypes.byref(lp_number_of_events_read), ) - char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') + char = lp_buffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') if char == '\x00': return None diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index ecf8a6d3..fcd53d22 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -2,7 +2,7 @@ import sys from types import TracebackType -from typing import Iterable, Iterator, Type +from typing import Iterable, Iterator from progressbar import base @@ -61,7 +61,7 @@ def __iter__(self) -> Iterator[str]: def __exit__( self, - __t: Type[BaseException] | None, + __t: type[BaseException] | None, __value: BaseException | None, __traceback: TracebackType | None, ) -> None: @@ -121,7 +121,7 @@ def truncate(self, __size: int | None = None) -> int: def writelines(self, __lines: Iterable[str]) -> None: line = '' # Walk through the lines and take the last one - for line in __lines: + for _ in __lines: pass self.line = line diff --git a/progressbar/utils.py b/progressbar/utils.py index a1d99fec..b9f45f4e 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import atexit +import contextlib import datetime import io import logging @@ -8,14 +9,14 @@ import re import sys from types import TracebackType -from typing import Iterable, Iterator, Type +from typing import Iterable, Iterator from python_utils import types from python_utils.converters import scale_1024 from python_utils.terminal import get_terminal_size from python_utils.time import epoch, format_time, timedelta_to_seconds -from progressbar import base +from progressbar import base, terminal if types.TYPE_CHECKING: from .bar import ProgressBar, ProgressBarMixinBase @@ -39,20 +40,20 @@ 'tmux', 'vt(10[02]|220|320)', ) -ANSI_TERM_RE = re.compile('^({})'.format('|'.join(ANSI_TERMS)), re.IGNORECASE) +ANSI_TERM_RE = re.compile(f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE) def is_ansi_terminal( - fd: base.IO, is_terminal: bool | None = None + fd: base.IO, is_terminal: bool | None = None, ) -> bool: # pragma: no cover if is_terminal is None: # Jupyter Notebooks define this variable and support progress bars if 'JPY_PARENT_PID' in os.environ: is_terminal = True - # This works for newer versions of pycharm only. older versions there - # is no way to check. + # This works for newer versions of pycharm only. With older versions + # there is no way to check. elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get( - 'PYTEST_CURRENT_TEST' + 'PYTEST_CURRENT_TEST', ): is_terminal = True @@ -103,7 +104,7 @@ def deltas_to_seconds( default: types.Optional[types.Type[ValueError]] = ValueError, ) -> int | float | None: ''' - Convert timedeltas and seconds as int to seconds as float while coalescing + Convert timedeltas and seconds as int to seconds as float while coalescing. >>> deltas_to_seconds(datetime.timedelta(seconds=1, milliseconds=234)) 1.234 @@ -145,10 +146,10 @@ def deltas_to_seconds( def no_color(value: StringT) -> StringT: ''' - Return the `value` without ANSI escape codes + Return the `value` without ANSI escape codes. - >>> no_color(b'\u001b[1234]abc') == b'abc' - True + >>> no_color(b'\u001b[1234]abc') + b'abc' >>> str(no_color(u'\u001b[1234]abc')) 'abc' >>> str(no_color('\u001b[1234]abc')) @@ -159,17 +160,17 @@ def no_color(value: StringT) -> StringT: TypeError: `value` must be a string or bytes, got 123 ''' if isinstance(value, bytes): - pattern: bytes = '\\\u001b\\[.*?[@-~]'.encode() + pattern: bytes = bytes(terminal.ESC, 'ascii') + b'\\[.*?[@-~]' return re.sub(pattern, b'', value) # type: ignore elif isinstance(value, str): - return re.sub(u'\x1b\\[.*?[@-~]', '', value) # type: ignore + return re.sub('\x1b\\[.*?[@-~]', '', value) # type: ignore else: raise TypeError('`value` must be a string or bytes, got %r' % value) def len_color(value: types.StringTypes) -> int: ''' - Return the length of `value` without ANSI escape codes + Return the length of `value` without ANSI escape codes. >>> len_color(b'\u001b[1234]abc') 3 @@ -184,7 +185,7 @@ def len_color(value: types.StringTypes) -> int: def env_flag(name: str, default: bool | None = None) -> bool | None: ''' Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, - on/off, and returns it as a boolean + on/off, and returns it as a boolean. If the environment variable is not defined, or has an unknown value, returns `default` @@ -235,8 +236,7 @@ def flush(self) -> None: self.buffer.flush() def _flush(self) -> None: - value = self.buffer.getvalue() - if value: + if value := self.buffer.getvalue(): self.flush() self.target.write(value) self.buffer.seek(0) @@ -247,7 +247,7 @@ def _flush(self) -> None: self.flush_target() def flush_target(self) -> None: # pragma: no cover - if not self.target.closed and getattr(self.target, 'flush'): + if not self.target.closed and self.target.flush: self.target.flush() def __enter__(self) -> WrappingIO: @@ -301,7 +301,7 @@ def __iter__(self) -> Iterator[str]: def __exit__( self, - __t: Type[BaseException] | None, + __t: type[BaseException] | None, __value: BaseException | None, __traceback: TracebackType | None, ) -> None: @@ -309,7 +309,7 @@ def __exit__( class StreamWrapper: - '''Wrap stdout and stderr globally''' + '''Wrap stdout and stderr globally.''' stdout: base.TextIO | WrappingIO stderr: base.TextIO | WrappingIO @@ -357,10 +357,9 @@ def start_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: def stop_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: if bar: # pragma: no branch - try: + with contextlib.suppress(KeyError): self.listeners.remove(bar) - except KeyError: - pass + self.capturing -= 1 self.update_capturing() @@ -387,7 +386,7 @@ def wrap_stdout(self) -> WrappingIO: if not self.wrapped_stdout: self.stdout = sys.stdout = WrappingIO( # type: ignore - self.original_stdout, listeners=self.listeners + self.original_stdout, listeners=self.listeners, ) self.wrapped_stdout += 1 @@ -398,7 +397,7 @@ def wrap_stderr(self) -> WrappingIO: if not self.wrapped_stderr: self.stderr = sys.stderr = WrappingIO( # type: ignore - self.original_stderr, listeners=self.listeners + self.original_stderr, listeners=self.listeners, ) self.wrapped_stderr += 1 @@ -442,27 +441,25 @@ def needs_clear(self) -> bool: # pragma: no cover return stderr_needs_clear or stdout_needs_clear def flush(self) -> None: - if self.wrapped_stdout: # pragma: no branch - if isinstance(self.stdout, WrappingIO): # pragma: no branch - try: - self.stdout._flush() - except io.UnsupportedOperation: # pragma: no cover - self.wrapped_stdout = False - logger.warning( - 'Disabling stdout redirection, %r is not seekable', - sys.stdout, - ) - - if self.wrapped_stderr: # pragma: no branch - if isinstance(self.stderr, WrappingIO): # pragma: no branch - try: - self.stderr._flush() - except io.UnsupportedOperation: # pragma: no cover - self.wrapped_stderr = False - logger.warning( - 'Disabling stderr redirection, %r is not seekable', - sys.stderr, - ) + if self.wrapped_stdout and isinstance(self.stdout, WrappingIO): + try: + self.stdout._flush() + except io.UnsupportedOperation: # pragma: no cover + self.wrapped_stdout = False + logger.warning( + 'Disabling stdout redirection, %r is not seekable', + sys.stdout, + ) + + if self.wrapped_stderr and isinstance(self.stderr, WrappingIO): + try: + self.stderr._flush() + except io.UnsupportedOperation: # pragma: no cover + self.wrapped_stderr = False + logger.warning( + 'Disabling stderr redirection, %r is not seekable', + sys.stderr, + ) def excepthook(self, exc_type, exc_value, exc_traceback): self.original_excepthook(exc_type, exc_value, exc_traceback) @@ -471,7 +468,7 @@ def excepthook(self, exc_type, exc_value, exc_traceback): class AttributeDict(dict): ''' - A dict that can be accessed with .attribute + A dict that can be accessed with .attribute. >>> attrs = AttributeDict(spam=123) @@ -519,7 +516,7 @@ def __getattr__(self, name: str) -> int: if name in self: return self[name] else: - raise AttributeError("No such attribute: " + name) + raise AttributeError(f'No such attribute: {name}') def __setattr__(self, name: str, value: int) -> None: self[name] = value @@ -528,7 +525,7 @@ def __delattr__(self, name: str) -> None: if name in self: del self[name] else: - raise AttributeError("No such attribute: " + name) + raise AttributeError(f'No such attribute: {name}') logger = logging.getLogger(__name__) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 944221cc..f1834531 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -1,14 +1,17 @@ -# -*- coding: utf-8 -*- from __future__ import annotations import abc +import contextlib import datetime import functools -import pprint -import sys +import logging import typing from collections import deque +# Ruff is being stupid and doesn't understand `ClassVar` if it comes from the +# `types` module +from typing import ClassVar + from python_utils import converters, types from . import base, terminal, utils @@ -17,6 +20,8 @@ if types.TYPE_CHECKING: from .bar import ProgressBarMixinBase +logger = logging.getLogger(__name__) + MAX_DATE = datetime.date.max MAX_TIME = datetime.time.max MAX_DATETIME = datetime.datetime.max @@ -30,8 +35,8 @@ class SliceableDeque(typing.Generic[T], deque): def __getitem__( self, - index: typing.Union[int, slice], - ) -> typing.Union[T, deque[T]]: + index: int | slice, + ) -> T | deque[T]: if isinstance(index, slice): start, stop, step = index.indices(len(self)) return self.__class__(self[i] for i in range(start, stop, step)) @@ -43,11 +48,11 @@ def pop(self, index=-1) -> T: # the first or last item. if index == 0: return super().popleft() - elif index == -1 or index == len(self) - 1: + elif index in {-1, len(self) - 1}: return super().pop() else: raise IndexError( - 'Only index 0 and the last index (`N-1` or `-1`) are supported' + 'Only index 0 and the last index (`N-1` or `-1`) are supported', ) def __eq__(self, other): @@ -72,7 +77,7 @@ def render_input(progress, data, width): def create_wrapper(wrapper): - '''Convert a wrapper tuple or format string to a format string + '''Convert a wrapper tuple or format string to a format string. >>> create_wrapper('') @@ -86,14 +91,14 @@ def create_wrapper(wrapper): a, b = wrapper wrapper = (a or '') + '{}' + (b or '') elif not wrapper: - return + return None if isinstance(wrapper, str): assert '{}' in wrapper, 'Expected string with {} for formatting' else: - raise RuntimeError( - 'Pass either a begin/end string as a tuple or a' - ' template string with {}' + raise RuntimeError( # noqa: TRY004 + 'Pass either a begin/end string as a tuple or a template string ' + 'with `{}`', ) return wrapper @@ -101,7 +106,7 @@ def create_wrapper(wrapper): def wrapper(function, wrapper_): '''Wrap the output of a function in a template string or a tuple with - begin/end strings + begin/end strings. ''' wrapper_ = create_wrapper(wrapper_) @@ -137,7 +142,7 @@ def _marker(progress, data, width): class FormatWidgetMixin(abc.ABC): - '''Mixin to format widgets using a formatstring + '''Mixin to format widgets using a formatstring. Variables available: - max_value: The maximum value (can be None with iterators) @@ -170,16 +175,16 @@ def __call__( data: Data, format: types.Optional[str] = None, ) -> str: - '''Formats the widget into a string''' - format = self.get_format(progress, data, format) + '''Formats the widget into a string.''' + format_ = self.get_format(progress, data, format) try: if self.new_style: - return format.format(**data) + return format_.format(**data) else: - return format % data + return format_ % data except (TypeError, KeyError): - print('Error while formatting %r' % format, file=sys.stderr) - pprint.pprint(data, stream=sys.stderr) + logger.exception('Error while formatting %r with data: %r', + format_, data) raise @@ -213,16 +218,18 @@ def __init__(self, min_width=None, max_width=None, **kwargs): self.max_width = max_width def check_size(self, progress: ProgressBarMixinBase): - if self.min_width and self.min_width > progress.term_width: + max_width = self.max_width + min_width = self.min_width + if min_width and min_width > progress.term_width: return False - elif self.max_width and self.max_width < progress.term_width: + elif max_width and max_width < progress.term_width: # noqa: SIM103 return False else: return True class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): - '''The base class for all widgets + '''The base class for all widgets. The ProgressBar will call the widget's update value when the widget should be updated. The widget's size may change between calls, but the widget may @@ -259,21 +266,18 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: progress - a reference to the calling ProgressBar ''' - _fixed_colors: dict[str, terminal.Color | None] = dict() - _gradient_colors: dict[str, terminal.OptionalColor | None] = dict() + _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict() + _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | None]] = ( + dict()) _len: typing.Callable[[str | bytes], int] = len @functools.cached_property def uses_colors(self): - for key, value in self._gradient_colors.items(): # pragma: no branch - if value is not None: # pragma: no branch - return True - - for key, value in self._fixed_colors.items(): # pragma: no branch + for value in self._gradient_colors.values(): # pragma: no branch if value is not None: # pragma: no branch return True - return False + return any(value is not None for value in self._fixed_colors.values()) def _apply_colors(self, text: str, data: Data) -> str: if self.uses_colors: @@ -287,7 +291,7 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, *args, fixed_colors=None, gradient_colors=None, **kwargs + self, *args, fixed_colors=None, gradient_colors=None, **kwargs, ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -334,7 +338,7 @@ class TimeSensitiveWidgetBase(WidgetBase, metaclass=abc.ABCMeta): class FormatLabel(FormatWidgetMixin, WidgetBase): - '''Displays a formatted label + '''Displays a formatted label. >>> label = FormatLabel('%(value)s', min_width=5, max_width=10) >>> class Progress: @@ -345,15 +349,15 @@ class FormatLabel(FormatWidgetMixin, WidgetBase): ''' - mapping = { - 'finished': ('end_time', None), - 'last_update': ('last_update_time', None), - 'max': ('max_value', None), - 'seconds': ('seconds_elapsed', None), - 'start': ('start_time', None), - 'elapsed': ('total_seconds_elapsed', utils.format_time), - 'value': ('value', None), - } + mapping: ClassVar[types.Dict[str, types.Tuple[str, types.Any]]] = dict( + finished=('end_time', None), + last_update=('last_update_time', None), + max=('max_value', None), + seconds=('seconds_elapsed', None), + start=('start_time', None), + elapsed=('total_seconds_elapsed', utils.format_time), + value=('value', None), + ) def __init__(self, format: str, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) @@ -366,13 +370,11 @@ def __call__( format: types.Optional[str] = None, ): for name, (key, transform) in self.mapping.items(): - try: + with contextlib.suppress(KeyError, ValueError, IndexError): if transform is None: data[name] = data[key] else: data[name] = transform(data[key]) - except (KeyError, ValueError, IndexError): # pragma: no cover - pass return FormatWidgetMixin.__call__(self, progress, data, format) @@ -393,7 +395,7 @@ def __init__(self, format='Elapsed Time: %(elapsed)s', **kwargs): class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): ''' - Mixing for widgets that average multiple measurements + Mixing for widgets that average multiple measurements. Note that samples can be either an integer or a timedelta to indicate a certain amount of time @@ -431,23 +433,23 @@ def __init__( ): self.samples = samples self.key_prefix = ( - key_prefix if key_prefix else self.__class__.__name__ + key_prefix or self.__class__.__name__ ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault( - self.key_prefix + 'sample_times', SliceableDeque() + f'{self.key_prefix}sample_times', SliceableDeque(), ) def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault( - self.key_prefix + 'sample_values', SliceableDeque() + f'{self.key_prefix}sample_values', SliceableDeque(), ) def __call__( self, progress: ProgressBarMixinBase, data: Data, - delta: bool = False + delta: bool = False, ): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -472,15 +474,13 @@ def __call__( ): sample_times.pop(0) sample_values.pop(0) - else: - if len(sample_times) > self.samples: - sample_times.pop(0) - sample_values.pop(0) + elif len(sample_times) > self.samples: + sample_times.pop(0) + sample_values.pop(0) if delta: - delta_time = sample_times[-1] - sample_times[0] - delta_value = sample_values[-1] - sample_values[0] - if delta_time: + if delta_time := sample_times[-1] - sample_times[0]: + delta_value = sample_values[-1] - sample_values[0] return delta_time, delta_value else: return None, None @@ -497,7 +497,7 @@ def __init__( format_finished='Time: %(elapsed)8s', format='ETA: %(eta)8s', format_zero='ETA: 00:00:00', - format_NA='ETA: N/A', + format_na='ETA: N/A', **kwargs, ): if '%s' in format and '%(eta)s' not in format: @@ -508,21 +508,19 @@ def __init__( self.format_finished = format_finished self.format = format self.format_zero = format_zero - self.format_NA = format_NA + self.format_NA = format_na def _calculate_eta( - self, progress: ProgressBarMixinBase, data: Data, value, elapsed + self, progress: ProgressBarMixinBase, data: Data, value, elapsed, ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: # The max() prevents zero division errors per_item = elapsed.total_seconds() / max(value, 1e-6) remaining = progress.max_value - data['value'] - eta_seconds = remaining * per_item + return remaining * per_item else: - eta_seconds = 0 - - return eta_seconds + return 0 def __call__( self, @@ -538,41 +536,39 @@ def __call__( if elapsed is None: elapsed = data['time_elapsed'] - ETA_NA = False + eta_na = False try: data['eta_seconds'] = self._calculate_eta( - progress, data, value=value, elapsed=elapsed + progress, data, value=value, elapsed=elapsed, ) except TypeError: data['eta_seconds'] = None - ETA_NA = True + eta_na = True data['eta'] = None if data['eta_seconds']: - try: + with contextlib.suppress(ValueError, OverflowError): data['eta'] = utils.format_time(data['eta_seconds']) - except (ValueError, OverflowError): # pragma: no cover - pass if data['value'] == progress.min_value: - format = self.format_not_started + fmt = self.format_not_started elif progress.end_time: - format = self.format_finished + fmt = self.format_finished elif data['eta']: - format = self.format - elif ETA_NA: - format = self.format_NA + fmt = self.format + elif eta_na: + fmt = self.format_NA else: - format = self.format_zero + fmt = self.format_zero - return Timer.__call__(self, progress, data, format=format) + return Timer.__call__(self, progress, data, format=fmt) class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' def _calculate_eta( - self, progress: ProgressBarMixinBase, data: Data, value, elapsed + self, progress: ProgressBarMixinBase, data: Data, value, elapsed, ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() @@ -616,7 +612,7 @@ def __call__( elapsed=None, ): elapsed, value = SamplesMixin.__call__( - self, progress, data, delta=True + self, progress, data, delta=True, ) if not elapsed: value = None @@ -701,7 +697,7 @@ def __call__( value = data['value'] elapsed = utils.deltas_to_seconds( - total_seconds_elapsed, data['total_seconds_elapsed'] + total_seconds_elapsed, data['total_seconds_elapsed'], ) if ( @@ -721,7 +717,7 @@ def __call__( data['scaled'] = scaled data['prefix'] = self.prefixes[0] return FormatWidgetMixin.__call__( - self, progress, data, self.inverse_format + self, progress, data, self.inverse_format, ) else: data['scaled'] = scaled @@ -730,7 +726,7 @@ def __call__( class AdaptiveTransferSpeed(FileTransferSpeed, SamplesMixin): - '''Widget for showing the transfer speed based on the last X samples''' + '''Widget for showing the transfer speed based on the last X samples.''' def __init__(self, **kwargs): FileTransferSpeed.__init__(self, **kwargs) @@ -744,7 +740,7 @@ def __call__( total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( - self, progress, data, delta=True + self, progress, data, delta=True, ) return FileTransferSpeed.__call__(self, progress, data, value, elapsed) @@ -772,8 +768,8 @@ def __init__( def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): '''Updates the widget to show the next marker or the first marker when - finished''' - + finished. + ''' if progress.end_time: return self.default @@ -807,27 +803,24 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): class Counter(FormatWidgetMixin, WidgetBase): - '''Displays the current count''' + '''Displays the current count.''' def __init__(self, format='%(value)d', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) def __call__( - self, progress: ProgressBarMixinBase, data: Data, format=None + self, progress: ProgressBarMixinBase, data: Data, format=None, ): return FormatWidgetMixin.__call__(self, progress, data, format) class ColoredMixin: - _fixed_colors: dict[str, terminal.Color | None] = dict( - fg_none=colors.yellow, - bg_none=None, - ) - _gradient_colors: dict[str, terminal.OptionalColor | None] = dict( - fg=colors.gradient, - bg=None, - ) + _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict( + fg_none=colors.yellow, bg_none=None) + _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | + None]] = dict(fg=colors.gradient, + bg=None) class Percentage(FormatWidgetMixin, ColoredMixin, WidgetBase): @@ -839,7 +832,7 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def get_format( - self, progress: ProgressBarMixinBase, data: Data, format=None + self, progress: ProgressBarMixinBase, data: Data, format=None, ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) @@ -852,7 +845,7 @@ def get_format( class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): - '''Returns progress as a count of the total (e.g.: "5 of 47")''' + '''Returns progress as a count of the total (e.g.: "5 of 47").''' max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], @@ -864,11 +857,10 @@ class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): def __init__(self, format=DEFAULT_FORMAT, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - self.max_width_cache = dict() - self.max_width_cache['default'] = self.max_width or 0 + self.max_width_cache = dict(default=self.max_width or 0) def __call__( - self, progress: ProgressBarMixinBase, data: Data, format=None + self, progress: ProgressBarMixinBase, data: Data, format=None, ): # If max_value is not available, display N/A if data.get('max_value'): @@ -883,13 +875,13 @@ def __call__( data['value_s'] = 0 formatted = FormatWidgetMixin.__call__( - self, progress, data, format=format + self, progress, data, format=format, ) # Guess the maximum width from the min and max value key = progress.min_value, progress.max_value max_width: types.Optional[int] = self.max_width_cache.get( - key, self.max_width + key, self.max_width, ) if not max_width: temporary_data = data.copy() @@ -898,12 +890,14 @@ def __call__( continue temporary_data['value'] = value - width = progress.custom_len( - FormatWidgetMixin.__call__( - self, progress, temporary_data, format=format - ) - ) - if width: # pragma: no branch + if width := progress.custom_len( + FormatWidgetMixin.__call__( + self, + progress, + temporary_data, + format=format, + ), + ): max_width = max(max_width or 0, width) self.max_width_cache[key] = max_width @@ -941,7 +935,6 @@ def __init__( fill - character to use for the empty part of the progress bar fill_left - whether to fill from the left or the right ''' - self.marker = create_marker(marker, marker_wrap) self.left = string_or_lambda(left) self.right = string_or_lambda(right) @@ -957,8 +950,7 @@ def __call__( width: int = 0, color=True, ): - '''Updates the progress bar and its subcomponents''' - + '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) @@ -980,7 +972,7 @@ def __call__( class ReverseBar(Bar): - '''A bar which has a marker that goes from right to left''' + '''A bar which has a marker that goes from right to left.''' def __init__( self, @@ -1021,8 +1013,7 @@ def __call__( data: Data, width: int = 0, ): - '''Updates the progress bar and its subcomponents''' - + '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) @@ -1032,7 +1023,7 @@ def __call__( if width: # pragma: no branch value = int( - data['total_seconds_elapsed'] / self.INTERVAL.total_seconds() + data['total_seconds_elapsed'] / self.INTERVAL.total_seconds(), ) a = value % width @@ -1049,7 +1040,7 @@ def __call__( class FormatCustomText(FormatWidgetMixin, WidgetBase): - mapping: types.Dict[str, types.Any] = {} + mapping: ClassVar[types.Dict[str, types.Any]] = dict() copy = False def __init__( @@ -1073,12 +1064,12 @@ def __call__( format: types.Optional[str] = None, ): return FormatWidgetMixin.__call__( - self, progress, self.mapping, format or self.format + self, progress, self.mapping, format or self.format, ) class VariableMixin: - '''Mixin to display a custom user variable''' + '''Mixin to display a custom user variable.''' def __init__(self, name, **kwargs): if not isinstance(name, str): @@ -1090,7 +1081,7 @@ def __init__(self, name, **kwargs): class MultiRangeBar(Bar, VariableMixin): ''' - A bar with multiple sub-ranges, each represented by a different symbol + A bar with multiple sub-ranges, each represented by a different symbol. The various ranges are represented on a user-defined variable, formatted as @@ -1117,8 +1108,7 @@ def __call__( data: Data, width: int = 0, ): - '''Updates the progress bar and its subcomponents''' - + '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) @@ -1172,9 +1162,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): if not 0 <= value <= 1: raise ValueError( - 'Range value needs to be in the range [0..1], got %s' - % value - ) + f'Range value needs to be in the range [0..1], got {value}') range_ = value * (len(ranges) - 1) pos = int(range_) @@ -1260,8 +1248,7 @@ def __call__( marker = self.markers[-1] * int(num_chars) - marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) - if marker_idx: + if marker_idx := int((num_chars % 1) * (len(self.markers) - 1)): marker += self.markers[marker_idx] marker = converters.to_unicode(marker) @@ -1370,7 +1357,7 @@ def __call__( except (TypeError, ValueError): if value: context['formatted_value'] = '{value:{width}}'.format( - **context + **context, ) else: context['formatted_value'] = '-' * self.width @@ -1381,8 +1368,6 @@ def __call__( class DynamicMessage(Variable): '''Kept for backwards compatibility, please use `Variable` instead.''' - pass - class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): '''Widget which displays the current (date)time with seconds resolution.''' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..4337bde0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,219 @@ +[tool.ruff] +target-version = 'py38' + +extend-exclude = ['tests'] +src = ['lmo'] + +format = 'grouped' +ignore = [ + 'A001', # Variable {name} is shadowing a Python builtin + 'A002', # Argument {name} is shadowing a Python builtin + 'A003', # Class attribute {name} is shadowing a Python builtin + 'B023', # function-uses-loop-variable + 'B024', # `FormatWidgetMixin` is an abstract base class, but it has no abstract methods + 'D205', # blank-line-after-summary + 'D212', # multi-line-summary-first-line + 'RET505', # Unnecessary `else` after `return` statement + 'TRY003', # Avoid specifying long messages outside the exception class + 'RET507', # Unnecessary `elif` after `continue` statement + 'C405', # Unnecessary {obj_type} literal (rewrite as a set literal) + 'C406', # Unnecessary {obj_type} literal (rewrite as a dict literal) + 'C408', # Unnecessary {obj_type} call (rewrite as a literal) + 'SIM114', # Combine `if` branches using logical `or` operator + 'RET506', # Unnecessary `else` after `raise` statement +] +line-length = 80 +select = [ + 'A', # flake8-builtins + 'ASYNC', # flake8 async checker + 'B', # flake8-bugbear + 'C4', # flake8-comprehensions + 'C90', # mccabe + 'COM', # flake8-commas + + ## Require docstrings for all public methods, would be good to enable at some point + # 'D', # pydocstyle + + 'E', # pycodestyle error ('W' for warning) + 'F', # pyflakes + 'FA', # flake8-future-annotations + 'I', # isort + 'ICN', # flake8-import-conventions + 'INP', # flake8-no-pep420 + 'ISC', # flake8-implicit-str-concat + 'N', # pep8-naming + 'NPY', # NumPy-specific rules + 'PERF', # perflint, + 'PIE', # flake8-pie + 'Q', # flake8-quotes + + 'RET', # flake8-return + 'RUF', # Ruff-specific rules + 'SIM', # flake8-simplify + 'T20', # flake8-print + 'TD', # flake8-todos + 'TRY', # tryceratops + 'UP', # pyupgrade +] + +[tool.ruff.pydocstyle] +convention = 'google' +ignore-decorators = ['typing.overload'] + +[tool.ruff.isort] +case-sensitive = true +combine-as-imports = true +force-wrap-aliases = true + +[tool.ruff.flake8-quotes] +docstring-quotes = 'single' +inline-quotes = 'single' +multiline-quotes = 'single' + +[project] +authors = [{ name = 'Rick van Hattem (Wolph)', email = 'wolph@wol.ph' }] +dynamic = ['version'] +keywords = [ + 'REPL', + 'animated', + 'bar', + 'color', + 'console', + 'duration', + 'efficient', + 'elapsed', + 'eta', + 'feedback', + 'live', + 'meter', + 'monitor', + 'monitoring', + 'multi-threaded', + 'progress', + 'progress-bar', + 'progressbar', + 'progressmeter', + 'python', + 'rate', + 'simple', + 'speed', + 'spinner', + 'stats', + 'terminal', + 'throughput', + 'time', + 'visual', +] +license = { text = 'BSD-3-Clause' } +name = 'progressbar2' +requires-python = '>=3.8' + +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Development Status :: 6 - Mature', + 'Environment :: Console', + 'Environment :: MacOS X', + 'Environment :: Other Environment', + 'Environment :: Win32 (MS Windows)', + 'Environment :: X11 Applications', + 'Framework :: IPython', + 'Framework :: Jupyter', + 'Intended Audience :: Developers', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Other Audience', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: MacOS', + 'Operating System :: Microsoft :: MS-DOS', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: Microsoft', + 'Operating System :: POSIX :: BSD :: FreeBSD', + 'Operating System :: POSIX :: BSD', + 'Operating System :: POSIX :: Linux', + 'Operating System :: POSIX :: SunOS/Solaris', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: Implementation :: IronPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Programming Language :: Python :: Implementation', + 'Programming Language :: Python', + 'Programming Language :: Unix Shell', + 'Topic :: Desktop Environment', + 'Topic :: Education :: Computer Aided Instruction (CAI)', + 'Topic :: Education :: Testing', + 'Topic :: Office/Business', + 'Topic :: Other/Nonlisted Topic', + 'Topic :: Software Development :: Build Tools', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Pre-processors', + 'Topic :: Software Development :: User Interfaces', + 'Topic :: System :: Installation/Setup', + 'Topic :: System :: Logging', + 'Topic :: System :: Monitoring', + 'Topic :: System :: Shells', + 'Topic :: Terminals', + 'Topic :: Utilities', +] +description = 'A Python Progressbar library to provide visual (yet text based) progress to long running operations.' +readme = 'README.rst' + +dependencies = ['python-utils >= 3.4.5'] + +[tool.setuptools.dynamic] +version = { attr = 'progressbar.__about__.__version__' } + +[tool.setuptools.packages.find] +exclude = ['docs', 'tests'] + +[tool.setuptools] +include-package-data = true + +[project.scripts] +cli-name = 'progressbar.cli:main' + +[project.optional-dependencies] +docs = ['sphinx>=1.8.5'] +tests = [ + 'dill>=0.3.6', + 'flake8>=3.7.7', + 'freezegun>=0.3.11', + 'pytest-cov>=2.6.1', + 'pytest-mypy', + 'pytest>=4.6.9', + 'sphinx>=1.8.5', +] + +[project.urls] +bugs = 'https://github.com/wolph/python-progressbar/issues' +documentation = 'https://progressbar-2.readthedocs.io/en/latest/' +repository = 'https://github.com/wolph/python-progressbar/' + +[build-system] +build-backend = 'setuptools.build_meta' +requires = ['setuptools', 'setuptools-scm', 'wheel'] + +[tool.codespell] +skip = '*/htmlcov,./docs/_build,*.asc' + +ignore-words-list = 'datas' + +[tool.black] +line-length = 79 +skip-string-normalization = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index cc0059c7..00000000 --- a/setup.cfg +++ /dev/null @@ -1,30 +0,0 @@ -[metadata] -description-file = README.rst - -[bdist_wheel] -universal = 1 - -[upload] -sign = 1 - -[codespell] -skip = */htmlcov,./docs/_build,*.asc - -ignore-words-list = datas - -[flake8] -exclude = - .git, - __pycache__, - build, - dist, - .eggs - .tox - -extend-ignore = - W391, - E203, - -[black] -line-length = 79 -skip-string-normalization = true \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 25015e61..00000000 --- a/setup.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys - -from setuptools import setup, find_packages - -# To prevent importing about and thereby breaking the coverage info we use this -# exec hack -about = {} -with open('progressbar/__about__.py', encoding='utf8') as fp: - exec(fp.read(), about) - - -install_reqs = [] -if sys.argv[-1] == 'info': - for k, v in about.items(): - print('%s: %s' % (k, v)) - sys.exit() - -if os.path.isfile('README.rst'): - with open('README.rst') as fh: - readme = fh.read() -else: - readme = 'See http://pypi.python.org/pypi/%(__package_name__)s/' % about - -if __name__ == '__main__': - setup( - name='progressbar2', - version=about['__version__'], - author=about['__author__'], - author_email=about['__email__'], - description=about['__description__'], - url=about['__url__'], - license=about['__license__'], - keywords=about['__title__'], - packages=find_packages(exclude=['docs']), - long_description=readme, - include_package_data=True, - install_requires=[ - 'python-utils>=3.4.5', - ], - setup_requires=['setuptools'], - zip_safe=False, - extras_require={ - 'docs': [ - 'sphinx>=1.8.5', - ], - 'tests': [ - 'flake8>=3.7.7', - 'pytest>=4.6.9', - 'pytest-cov>=2.6.1', - 'pytest-mypy', - 'freezegun>=0.3.11', - 'sphinx>=1.8.5', - 'dill>=0.3.6', - ], - }, - python_requires='>=3.7.0', - classifiers=[ - 'Development Status :: 6 - Mature', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Natural Language :: English', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: Implementation :: PyPy', - ], - ) diff --git a/tox.ini b/tox.ini index 9e681c86..1a859167 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ envlist = py38 py39 py310 - flake8 docs black mypy @@ -24,12 +23,6 @@ deps = -r{toxinidir}/tests/requirements.txt commands = py.test --basetemp="{envtmpdir}" --confcutdir=.. {posargs} changedir = tests -[testenv:flake8] -changedir = -basepython = python3 -deps = flake8 -commands = flake8 {toxinidir}/progressbar {toxinidir}/tests {toxinidir}/examples.py - [testenv:mypy] changedir = basepython = python3 @@ -65,13 +58,6 @@ commands = rm -f docs/modules.rst sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html {posargs} -[flake8] -ignore = W391, W504, E741, W503, E131 -exclude = - docs, - progressbar/six.py - tests/original_examples.py - [testenv:ruff] commands = ruff check . deps = ruff @@ -81,4 +67,4 @@ skip_install = true commands = codespell . deps = codespell skip_install = true -command = codespell \ No newline at end of file +command = codespell From 870942405ad178a11078d9b94eb2be3e951141b8 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Sep 2023 02:50:15 +0200 Subject: [PATCH 097/118] Fixed #284: Error when multibar key is empty --- progressbar/multi.py | 8 ++++---- tests/test_multibar.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/progressbar/multi.py b/progressbar/multi.py index e5143f1f..eca882c8 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -125,7 +125,7 @@ def __init__( def __setitem__(self, key: str, value: bar.ProgressBar): '''Add a progressbar to the multibar.''' - if value.label != key: # pragma: no branch + if value.label != key or not key: # pragma: no branch value.label = key value.fd = stream.LastLineStream(self.fd) value.paused = True @@ -144,13 +144,13 @@ def __delitem__(self, key): self._finished_at.pop(key, None) self._labeled.discard(key) - def __getitem__(self, item): + def __getitem__(self, key): '''Get (and create if needed) a progressbar from the multibar.''' try: - return super().__getitem__(item) + return super().__getitem__(key) except KeyError: progress = bar.ProgressBar(**self.progressbar_kwargs) - self[item] = progress + self[key] = progress return progress def _label_bar(self, bar: bar.ProgressBar): diff --git a/tests/test_multibar.py b/tests/test_multibar.py index f0993dfe..29b72a7a 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -148,3 +148,15 @@ def test_multibar_show_initial(): multibar = progressbar.MultiBar(show_initial=False) multibar['bar'] = progressbar.ProgressBar(max_value=N) multibar.render(force=True) + + +def test_multibar_empty_key(): + multibar = progressbar.MultiBar() + multibar[''] = progressbar.ProgressBar(max_value=N) + + for name in multibar: + assert name == '' + bar = multibar[name] + bar.update(1) + + multibar.render(force=True) \ No newline at end of file From db11fc0be3c0eb8179dcd11e4aac39e147ba2ce2 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 20 Sep 2023 11:18:58 +0200 Subject: [PATCH 098/118] Many linting and code quality changes --- pyproject.toml | 77 --------------- ruff.toml | 76 ++++++++++++++ tests/conftest.py | 12 +-- tests/original_examples.py | 31 +++--- tests/test_backwards_compatibility.py | 5 +- tests/test_color.py | 19 ++-- tests/test_custom_widgets.py | 7 +- tests/test_data.py | 2 +- tests/test_dill_pickle.py | 1 - tests/test_end.py | 8 +- tests/test_failure.py | 5 +- tests/test_flush.py | 1 + tests/test_iterators.py | 15 +-- tests/test_large_values.py | 1 + tests/test_monitor_progress.py | 136 +++++++++++++------------- tests/test_multibar.py | 12 +-- tests/test_progressbar.py | 11 ++- tests/test_samples.py | 6 +- tests/test_speed.py | 2 +- tests/test_stream.py | 13 +-- tests/test_terminal.py | 13 +-- tests/test_timed.py | 24 ++--- tests/test_timer.py | 4 +- tests/test_unicode.py | 12 +-- tests/test_unknown_length.py | 2 +- tests/test_utils.py | 3 +- tests/test_widgets.py | 18 ++-- tests/test_wrappingio.py | 1 - 28 files changed, 263 insertions(+), 254 deletions(-) create mode 100644 ruff.toml diff --git a/pyproject.toml b/pyproject.toml index 4337bde0..4b8d81a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,74 +1,3 @@ -[tool.ruff] -target-version = 'py38' - -extend-exclude = ['tests'] -src = ['lmo'] - -format = 'grouped' -ignore = [ - 'A001', # Variable {name} is shadowing a Python builtin - 'A002', # Argument {name} is shadowing a Python builtin - 'A003', # Class attribute {name} is shadowing a Python builtin - 'B023', # function-uses-loop-variable - 'B024', # `FormatWidgetMixin` is an abstract base class, but it has no abstract methods - 'D205', # blank-line-after-summary - 'D212', # multi-line-summary-first-line - 'RET505', # Unnecessary `else` after `return` statement - 'TRY003', # Avoid specifying long messages outside the exception class - 'RET507', # Unnecessary `elif` after `continue` statement - 'C405', # Unnecessary {obj_type} literal (rewrite as a set literal) - 'C406', # Unnecessary {obj_type} literal (rewrite as a dict literal) - 'C408', # Unnecessary {obj_type} call (rewrite as a literal) - 'SIM114', # Combine `if` branches using logical `or` operator - 'RET506', # Unnecessary `else` after `raise` statement -] -line-length = 80 -select = [ - 'A', # flake8-builtins - 'ASYNC', # flake8 async checker - 'B', # flake8-bugbear - 'C4', # flake8-comprehensions - 'C90', # mccabe - 'COM', # flake8-commas - - ## Require docstrings for all public methods, would be good to enable at some point - # 'D', # pydocstyle - - 'E', # pycodestyle error ('W' for warning) - 'F', # pyflakes - 'FA', # flake8-future-annotations - 'I', # isort - 'ICN', # flake8-import-conventions - 'INP', # flake8-no-pep420 - 'ISC', # flake8-implicit-str-concat - 'N', # pep8-naming - 'NPY', # NumPy-specific rules - 'PERF', # perflint, - 'PIE', # flake8-pie - 'Q', # flake8-quotes - - 'RET', # flake8-return - 'RUF', # Ruff-specific rules - 'SIM', # flake8-simplify - 'T20', # flake8-print - 'TD', # flake8-todos - 'TRY', # tryceratops - 'UP', # pyupgrade -] - -[tool.ruff.pydocstyle] -convention = 'google' -ignore-decorators = ['typing.overload'] - -[tool.ruff.isort] -case-sensitive = true -combine-as-imports = true -force-wrap-aliases = true - -[tool.ruff.flake8-quotes] -docstring-quotes = 'single' -inline-quotes = 'single' -multiline-quotes = 'single' [project] authors = [{ name = 'Rick van Hattem (Wolph)', email = 'wolph@wol.ph' }] @@ -119,7 +48,6 @@ classifiers = [ 'Framework :: IPython', 'Framework :: Jupyter', 'Intended Audience :: Developers', - 'Intended Audience :: Developers', 'Intended Audience :: Education', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Other Audience', @@ -140,17 +68,12 @@ classifiers = [ 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: IronPython', 'Programming Language :: Python :: Implementation :: PyPy', - 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation', 'Programming Language :: Python', 'Programming Language :: Unix Shell', diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..5b52328b --- /dev/null +++ b/ruff.toml @@ -0,0 +1,76 @@ +# We keep the ruff configuration separate so it can easily be shared across +# all projects + +target-version = 'py38' + +#extend-exclude = ['tests'] +src = ['progressbar'] + +format = 'grouped' +ignore = [ + 'A001', # Variable {name} is shadowing a Python builtin + 'A002', # Argument {name} is shadowing a Python builtin + 'A003', # Class attribute {name} is shadowing a Python builtin + 'B023', # function-uses-loop-variable + 'B024', # `FormatWidgetMixin` is an abstract base class, but it has no abstract methods + 'D205', # blank-line-after-summary + 'D212', # multi-line-summary-first-line + 'RET505', # Unnecessary `else` after `return` statement + 'TRY003', # Avoid specifying long messages outside the exception class + 'RET507', # Unnecessary `elif` after `continue` statement + 'C405', # Unnecessary {obj_type} literal (rewrite as a set literal) + 'C406', # Unnecessary {obj_type} literal (rewrite as a dict literal) + 'C408', # Unnecessary {obj_type} call (rewrite as a literal) + 'SIM114', # Combine `if` branches using logical `or` operator + 'RET506', # Unnecessary `else` after `raise` statement +] +line-length = 80 +select = [ + 'A', # flake8-builtins + 'ASYNC', # flake8 async checker + 'B', # flake8-bugbear + 'C4', # flake8-comprehensions + 'C90', # mccabe + 'COM', # flake8-commas + + ## Require docstrings for all public methods, would be good to enable at some point + # 'D', # pydocstyle + + 'E', # pycodestyle error ('W' for warning) + 'F', # pyflakes + 'FA', # flake8-future-annotations + 'I', # isort + 'ICN', # flake8-import-conventions + 'INP', # flake8-no-pep420 + 'ISC', # flake8-implicit-str-concat + 'N', # pep8-naming + 'NPY', # NumPy-specific rules + 'PERF', # perflint, + 'PIE', # flake8-pie + 'Q', # flake8-quotes + + 'RET', # flake8-return + 'RUF', # Ruff-specific rules + 'SIM', # flake8-simplify + 'T20', # flake8-print + 'TD', # flake8-todos + 'TRY', # tryceratops + 'UP', # pyupgrade +] + +[per-file-ignores] +'tests/*' = ['INP001', 'T201', 'T203'] + +[pydocstyle] +convention = 'google' +ignore-decorators = ['typing.overload'] + +[isort] +case-sensitive = true +combine-as-imports = true +force-wrap-aliases = true + +[flake8-quotes] +docstring-quotes = 'single' +inline-quotes = 'single' +multiline-quotes = 'single' diff --git a/tests/conftest.py b/tests/conftest.py index 3d587bb9..d2a91261 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,11 @@ +import logging import time import timeit -import pytest -import logging -import freezegun -import progressbar from datetime import datetime +import freezegun +import progressbar +import pytest LOG_LEVELS = { '0': logging.ERROR, @@ -17,7 +17,7 @@ def pytest_configure(config): logging.basicConfig( - level=LOG_LEVELS.get(config.option.verbose, logging.DEBUG) + level=LOG_LEVELS.get(config.option.verbose, logging.DEBUG), ) @@ -25,7 +25,7 @@ def pytest_configure(config): def small_interval(monkeypatch): # Remove the update limit for tests by default monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1e-6 + progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1e-6, ) monkeypatch.setattr(timeit, 'default_timer', time.time) diff --git a/tests/original_examples.py b/tests/original_examples.py index 97803819..7f745d03 100644 --- a/tests/original_examples.py +++ b/tests/original_examples.py @@ -1,16 +1,15 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- import sys import time from progressbar import ( + ETA, + AdaptiveETA, AnimatedMarker, Bar, BouncingBar, Counter, - ETA, - AdaptiveETA, FileTransferSpeed, FormatLabel, Percentage, @@ -151,21 +150,21 @@ def example6(): @example def example7(): pbar = ProgressBar() # Progressbar can guess maxval automatically. - for i in pbar(range(80)): + for _i in pbar(range(80)): time.sleep(0.01) @example def example8(): pbar = ProgressBar(maxval=80) # Progressbar can't guess maxval. - for i in pbar((i for i in range(80))): + for _i in pbar(i for i in range(80)): time.sleep(0.01) @example def example9(): pbar = ProgressBar(widgets=['Working: ', AnimatedMarker()]) - for i in pbar((i for i in range(50))): + for _i in pbar(i for i in range(50)): time.sleep(0.08) @@ -173,7 +172,7 @@ def example9(): def example10(): widgets = ['Processed: ', Counter(), ' lines (', Timer(), ')'] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(150))): + for _i in pbar(i for i in range(150)): time.sleep(0.1) @@ -181,7 +180,7 @@ def example10(): def example11(): widgets = [FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(150))): + for _i in pbar(i for i in range(150)): time.sleep(0.1) @@ -189,7 +188,7 @@ def example11(): def example12(): widgets = ['Balloon: ', AnimatedMarker(markers='.oO@* ')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _i in pbar(i for i in range(24)): time.sleep(0.3) @@ -199,7 +198,7 @@ def example13(): try: widgets = ['Arrows: ', AnimatedMarker(markers='←↖↑↗→↘↓↙')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _i in pbar(i for i in range(24)): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -211,7 +210,7 @@ def example14(): try: widgets = ['Arrows: ', AnimatedMarker(markers='◢◣◤◥')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _i in pbar(i for i in range(24)): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -223,7 +222,7 @@ def example15(): try: widgets = ['Wheels: ', AnimatedMarker(markers='◐◓◑◒')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _i in pbar(i for i in range(24)): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -233,7 +232,7 @@ def example15(): def example16(): widgets = [FormatLabel('Bouncer: value %(value)d - '), BouncingBar()] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(180))): + for _i in pbar(i for i in range(180)): time.sleep(0.05) @@ -245,7 +244,7 @@ def example17(): ] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(180))): + for _i in pbar(i for i in range(180)): time.sleep(0.05) @@ -263,14 +262,14 @@ def example18(): @example def example19(): pbar = ProgressBar() - for i in pbar([]): + for _i in pbar([]): pass pbar.finish() @example def example20(): - """Widgets that behave differently when length is unknown""" + '''Widgets that behave differently when length is unknown''' widgets = [ '[When length is unknown at first]', ' Progress: ', diff --git a/tests/test_backwards_compatibility.py b/tests/test_backwards_compatibility.py index 5e66318c..1f9a7a6e 100644 --- a/tests/test_backwards_compatibility.py +++ b/tests/test_backwards_compatibility.py @@ -1,11 +1,12 @@ import time + import progressbar def test_progressbar_1_widgets(): widgets = [ - progressbar.AdaptiveETA(format="Time left: %s"), - progressbar.Timer(format="Time passed: %s"), + progressbar.AdaptiveETA(format='Time left: %s'), + progressbar.Timer(format='Time passed: %s'), progressbar.Bar(), ] diff --git a/tests/test_color.py b/tests/test_color.py index 2478e713..d05f5f0d 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,6 +1,9 @@ -import pytest +from __future__ import annotations + +import typing import progressbar +import pytest from progressbar import terminal @@ -35,7 +38,7 @@ def test_enable_colors_flags(): assert not bar.enable_colors bar = progressbar.ProgressBar( - enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR + enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR, ) assert bar.enable_colors @@ -44,7 +47,7 @@ def test_enable_colors_flags(): class _TestFixedColorSupport(progressbar.widgets.WidgetBase): - _fixed_colors = dict( + _fixed_colors: typing.ClassVar[dict[str, terminal.Color | None]] = dict( fg_none=progressbar.widgets.colors.yellow, bg_none=None, ) @@ -54,10 +57,12 @@ def __call__(self, *args, **kwargs): class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): - _gradient_colors = dict( + _gradient_colors: typing.ClassVar[dict[str, terminal.ColorGradient | + None]] = ( + dict( fg=progressbar.widgets.colors.gradient, bg=None, - ) + )) def __call__(self, *args, **kwargs): pass @@ -88,8 +93,8 @@ def test_no_color_widgets(widget): print(f'{widget} has colors? {widget.uses_colors}') assert widget( - fixed_colors=_TestFixedColorSupport._fixed_colors + fixed_colors=_TestFixedColorSupport._fixed_colors, ).uses_colors assert widget( - gradient_colors=_TestFixedGradientSupport._gradient_colors + gradient_colors=_TestFixedGradientSupport._gradient_colors, ).uses_colors diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index 1d3fd517..e24449e2 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -1,8 +1,7 @@ import time -import pytest - import progressbar +import pytest class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): @@ -11,7 +10,7 @@ class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): def update(self, pbar): if 45 < pbar.percentage() < 80: return 'Bigger Now ' + progressbar.FileTransferSpeed.update( - self, pbar + self, pbar, ) else: return progressbar.FileTransferSpeed.update(self, pbar) @@ -88,7 +87,7 @@ def test_format_custom_text_widget(): bar = progressbar.ProgressBar( widgets=[ widget, - ] + ], ) for i in bar(range(5)): diff --git a/tests/test_data.py b/tests/test_data.py index f7566390..ef6f5a3a 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,5 +1,5 @@ -import pytest import progressbar +import pytest @pytest.mark.parametrize( diff --git a/tests/test_dill_pickle.py b/tests/test_dill_pickle.py index bfa1da4b..7c748d5e 100644 --- a/tests/test_dill_pickle.py +++ b/tests/test_dill_pickle.py @@ -1,5 +1,4 @@ import dill - import progressbar diff --git a/tests/test_end.py b/tests/test_end.py index 29c232f3..b8cbc309 100644 --- a/tests/test_end.py +++ b/tests/test_end.py @@ -1,19 +1,19 @@ -import pytest import progressbar +import pytest @pytest.fixture(autouse=True) def large_interval(monkeypatch): # Remove the update limit for tests by default monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 0.1 + progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 0.1, ) def test_end(): m = 24514315 p = progressbar.ProgressBar( - widgets=[progressbar.Percentage(), progressbar.Bar()], max_value=m + widgets=[progressbar.Percentage(), progressbar.Bar()], max_value=m, ) for x in range(0, m, 8192): @@ -37,7 +37,7 @@ def test_end_100(monkeypatch): max_value=103, ) - for x in range(0, 102): + for x in range(102): p.update(x) data = p.data() diff --git a/tests/test_failure.py b/tests/test_failure.py index a389da4b..cee84b78 100644 --- a/tests/test_failure.py +++ b/tests/test_failure.py @@ -1,6 +1,7 @@ import time -import pytest + import progressbar +import pytest def test_missing_format_values(): @@ -64,7 +65,7 @@ def test_one_max_value(): def test_changing_max_value(): '''Changing max_value? No problem''' p = progressbar.ProgressBar(max_value=10)(range(20), max_value=20) - for i in p: + for _i in p: time.sleep(1) diff --git a/tests/test_flush.py b/tests/test_flush.py index 2c342900..014b690a 100644 --- a/tests/test_flush.py +++ b/tests/test_flush.py @@ -1,4 +1,5 @@ import time + import progressbar diff --git a/tests/test_iterators.py b/tests/test_iterators.py index 13aec3c4..c690e299 100644 --- a/tests/test_iterators.py +++ b/tests/test_iterators.py @@ -1,19 +1,20 @@ import time -import pytest + import progressbar +import pytest def test_list(): '''Progressbar can guess max_value automatically.''' p = progressbar.ProgressBar() - for i in p(range(10)): + for _i in p(range(10)): time.sleep(0.001) def test_iterator_with_max_value(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar(max_value=10) - for i in p((i for i in range(10))): + for _i in p(i for i in range(10)): time.sleep(0.001) @@ -21,7 +22,7 @@ def test_iterator_without_max_value_error(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar() - for i in p((i for i in range(10))): + for _i in p(i for i in range(10)): time.sleep(0.001) assert p.max_value is progressbar.UnknownLength @@ -35,9 +36,9 @@ def test_iterator_without_max_value(): progressbar.FormatLabel('%(value)d'), progressbar.BouncingBar(), progressbar.BouncingBar(marker=progressbar.RotatingMarker()), - ] + ], ) - for i in p((i for i in range(10))): + for _i in p(i for i in range(10)): time.sleep(0.001) @@ -45,7 +46,7 @@ def test_iterator_with_incorrect_max_value(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar(max_value=10) with pytest.raises(ValueError): - for i in p((i for i in range(20))): + for _i in p(i for i in range(20)): time.sleep(0.001) diff --git a/tests/test_large_values.py b/tests/test_large_values.py index 9a7704f4..f251c32e 100644 --- a/tests/test_large_values.py +++ b/tests/test_large_values.py @@ -1,4 +1,5 @@ import time + import progressbar diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index bac41258..4d19eea8 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -1,5 +1,6 @@ import os import pprint + import progressbar pytest_plugins = 'pytester' @@ -28,11 +29,13 @@ def _non_empty_lines(lines): def _create_script( widgets=None, - items=list(range(9)), + items=None, loop_code='fake_time.tick(1)', term_width=60, **kwargs, ): + if items is None: + items = list(range(9)) kwargs['term_width'] = term_width # Reindent the loop code @@ -48,7 +51,7 @@ def _create_script( kwargs=kwargs, loop_code=indent.join(loop_code), progressbar_path=os.path.dirname( - os.path.dirname(progressbar.__file__) + os.path.dirname(progressbar.__file__), ), ) print('# Script:') @@ -70,8 +73,8 @@ def test_list_example(testdir): testdir.makepyfile( _create_script( term_width=65, - ) - ) + ), + ), ) result.stderr.lines = [ line.rstrip() for line in _non_empty_lines(result.stderr.lines) @@ -89,7 +92,7 @@ def test_list_example(testdir): ' 77% (7 of 9) |######### | Elapsed Time: ?:00:07 ETA: ?:00:02', ' 88% (8 of 9) |########## | Elapsed Time: ?:00:08 ETA: ?:00:01', '100% (9 of 9) |############| Elapsed Time: ?:00:09 Time: ?:00:09', - ] + ], ) @@ -103,19 +106,16 @@ def test_generator_example(testdir): testdir.makepyfile( _create_script( items='iter(range(9))', - ) - ) + ), + ), ) result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) - lines = [] - for i in range(9): - lines.append( - r'[/\\|\-]\s+\|\s*#\s*\| %(i)d Elapsed Time: \d:00:%(i)02d' - % dict(i=i) - ) - + lines = [ + r'[/\\|\-]\s+\|\s*#\s*\| %(i)d Elapsed Time: \d:00:%(i)02d' % dict(i=i) + for i in range(9) + ] result.stderr.re_match_lines(lines) @@ -135,8 +135,8 @@ def test_rapid_updates(testdir): else: fake_time.tick(2) ''', - ) - ) + ), + ), ) result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) @@ -153,7 +153,7 @@ def test_rapid_updates(testdir): ' 80% (8 of 10) |#### | Elapsed Time: ?:00:11 ETA: ?:00:04', ' 90% (9 of 10) |##### | Elapsed Time: ?:00:13 ETA: ?:00:02', '100% (10 of 10) |#####| Elapsed Time: ?:00:15 Time: ?:00:15', - ] + ], ) @@ -163,8 +163,8 @@ def test_non_timed(testdir): _create_script( widgets='[progressbar.Percentage(), progressbar.Bar()]', items=list(range(5)), - ) - ) + ), + ), ) result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) @@ -176,7 +176,7 @@ def test_non_timed(testdir): ' 60%|################################ |', ' 80%|########################################### |', '100%|######################################################|', - ] + ], ) @@ -187,20 +187,20 @@ def test_line_breaks(testdir): widgets='[progressbar.Percentage(), progressbar.Bar()]', line_breaks=True, items=list(range(5)), - ) - ) + ), + ), ) pprint.pprint(result.stderr.str(), width=70) - assert result.stderr.str() == u'\n'.join( + assert result.stderr.str() == '\n'.join( ( - u' 0%| |', - u' 20%|########## |', - u' 40%|##################### |', - u' 60%|################################ |', - u' 80%|########################################### |', - u'100%|######################################################|', - u'100%|######################################################|', - ) + ' 0%| |', + ' 20%|########## |', + ' 40%|##################### |', + ' 60%|################################ |', + ' 80%|########################################### |', + '100%|######################################################|', + '100%|######################################################|', + ), ) @@ -211,20 +211,20 @@ def test_no_line_breaks(testdir): widgets='[progressbar.Percentage(), progressbar.Bar()]', line_breaks=False, items=list(range(5)), - ) - ) + ), + ), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ - u'', - u' 0%| |', - u' 20%|########## |', - u' 40%|##################### |', - u' 60%|################################ |', - u' 80%|########################################### |', - u'100%|######################################################|', - u'', - u'100%|######################################################|', + '', + ' 0%| |', + ' 20%|########## |', + ' 40%|##################### |', + ' 60%|################################ |', + ' 80%|########################################### |', + '100%|######################################################|', + '', + '100%|######################################################|', ] @@ -235,20 +235,20 @@ def test_percentage_label_bar(testdir): widgets='[progressbar.PercentageLabelBar()]', line_breaks=False, items=list(range(5)), - ) - ) + ), + ), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ - u'', - u'| 0% |', - u'|########### 20% |', - u'|####################### 40% |', - u'|###########################60%#### |', - u'|###########################80%################ |', - u'|###########################100%###########################|', - u'', - u'|###########################100%###########################|', + '', + '| 0% |', + '|########### 20% |', + '|####################### 40% |', + '|###########################60%#### |', + '|###########################80%################ |', + '|###########################100%###########################|', + '', + '|###########################100%###########################|', ] @@ -259,20 +259,20 @@ def test_granular_bar(testdir): widgets='[progressbar.GranularBar(markers=" .oO")]', line_breaks=False, items=list(range(5)), - ) - ) + ), + ), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ - u'', - u'| |', - u'|OOOOOOOOOOO. |', - u'|OOOOOOOOOOOOOOOOOOOOOOO |', - u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo |', - u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO. |', - u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', - u'', - u'|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', + '', + '| |', + '|OOOOOOOOOOO. |', + '|OOOOOOOOOOOOOOOOOOOOOOO |', + '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo |', + '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO. |', + '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', + '', + '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', ] @@ -283,13 +283,13 @@ def test_colors(testdir): ) result = testdir.runpython( - testdir.makepyfile(_create_script(enable_colors=True, **kwargs)) + testdir.makepyfile(_create_script(enable_colors=True, **kwargs)), ) pprint.pprint(result.stderr.lines, width=70) - assert result.stderr.lines == [u'\x1b[92mgreen\x1b[0m'] * 3 + assert result.stderr.lines == ['\x1b[92mgreen\x1b[0m'] * 3 result = testdir.runpython( - testdir.makepyfile(_create_script(enable_colors=False, **kwargs)) + testdir.makepyfile(_create_script(enable_colors=False, **kwargs)), ) pprint.pprint(result.stderr.lines, width=70) - assert result.stderr.lines == [u'green'] * 3 + assert result.stderr.lines == ['green'] * 3 diff --git a/tests/test_multibar.py b/tests/test_multibar.py index 29b72a7a..0798bae1 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -1,9 +1,8 @@ import threading import time -import pytest - import progressbar +import pytest N = 10 BARS = 3 @@ -71,7 +70,8 @@ def do_something(bar): for i in range(BARS): thread = threading.Thread( - target=do_something, args=(multibar['bar {}'.format(i)],) + target=do_something, + args=(multibar[f'bar {i}'],), ) thread.start() @@ -108,11 +108,11 @@ def do_something(bar): def test_multibar_sorting(sort_key): with progressbar.MultiBar() as multibar: for i in range(BARS): - label = 'bar {}'.format(i) + label = f'bar {i}' multibar[label] = progressbar.ProgressBar(max_value=N) for bar in multibar.values(): - for j in bar(range(N)): + for _j in bar(range(N)): assert bar.started() time.sleep(SLEEP) @@ -134,7 +134,7 @@ def test_multibar_show_finished(): multibar.finished_format = 'finished: {label}' for i in range(3): - multibar['bar {}'.format(i)] = progressbar.ProgressBar(max_value=N) + multibar[f'bar {i}'] = progressbar.ProgressBar(max_value=N) for bar in multibar.values(): for i in range(N): diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index 00aa0caa..14ead38a 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -1,7 +1,9 @@ +import contextlib import time -import pytest -import progressbar + import original_examples +import progressbar +import pytest # Import hack to allow for parallel Tox try: @@ -17,10 +19,9 @@ def test_examples(monkeypatch): for example in examples.examples: - try: + with contextlib.suppress(ValueError): example() - except ValueError: - pass + @pytest.mark.filterwarnings('ignore:.*maxval.*:DeprecationWarning') diff --git a/tests/test_samples.py b/tests/test_samples.py index 71e42ea1..eeaa9181 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -1,6 +1,6 @@ import time -from datetime import timedelta -from datetime import datetime +from datetime import datetime, timedelta + import progressbar from progressbar import widgets @@ -37,7 +37,7 @@ def test_numeric_samples(): assert samples_widget(bar, None, True) == (timedelta(0, 16), 16) assert samples_widget(bar, None)[1] == progressbar.SliceableDeque( - [4, 5, 8, 10, 20] + [4, 5, 8, 10, 20], ) diff --git a/tests/test_speed.py b/tests/test_speed.py index dc8ad6f1..0496daf5 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -1,5 +1,5 @@ -import pytest import progressbar +import pytest @pytest.mark.parametrize( diff --git a/tests/test_stream.py b/tests/test_stream.py index 6dcfcf7c..f641b662 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -1,12 +1,13 @@ import io import sys -import pytest + import progressbar +import pytest def test_nowrap(): # Make sure we definitely unwrap - for i in range(5): + for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) stdout = sys.stdout @@ -23,13 +24,13 @@ def test_nowrap(): assert stderr == sys.stderr # Make sure we definitely unwrap - for i in range(5): + for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) def test_wrap(): # Make sure we definitely unwrap - for i in range(5): + for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) stdout = sys.stdout @@ -50,7 +51,7 @@ def test_wrap(): assert stderr == sys.stderr # Make sure we definitely unwrap - for i in range(5): + for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) @@ -58,7 +59,7 @@ def test_excepthook(): progressbar.streams.wrap(stderr=True, stdout=True) try: - raise RuntimeError() + raise RuntimeError() # noqa: TRY301 except RuntimeError: progressbar.streams.excepthook(*sys.exc_info()) diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 395e618f..0f2620b0 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -1,9 +1,10 @@ +import signal import sys import time -import signal -import progressbar from datetime import timedelta +import progressbar + def test_left_justify(): '''Left justify using the terminal width''' @@ -49,7 +50,7 @@ def fake_signal(signal, func): monkeypatch.setattr(signal, 'signal', fake_signal) p = progressbar.ProgressBar( widgets=[ - progressbar.BouncingBar(marker=progressbar.RotatingMarker()) + progressbar.BouncingBar(marker=progressbar.RotatingMarker()), ], max_value=100, left_justify=True, @@ -94,7 +95,7 @@ def test_no_fill(monkeypatch): bar = progressbar.BouncingBar() bar.INTERVAL = timedelta(seconds=1) p = progressbar.ProgressBar( - widgets=[bar], max_value=progressbar.UnknownLength, term_width=20 + widgets=[bar], max_value=progressbar.UnknownLength, term_width=20, ) assert p.term_width is not None @@ -106,7 +107,7 @@ def test_no_fill(monkeypatch): def test_stdout_redirection(): p = progressbar.ProgressBar( - fd=sys.stdout, max_value=10, redirect_stdout=True + fd=sys.stdout, max_value=10, redirect_stdout=True, ) for i in range(10): @@ -134,7 +135,7 @@ def test_stderr_redirection(): def test_stdout_stderr_redirection(): p = progressbar.ProgressBar( - max_value=10, redirect_stdout=True, redirect_stderr=True + max_value=10, redirect_stdout=True, redirect_stderr=True, ) p.start() diff --git a/tests/test_timed.py b/tests/test_timed.py index cf34cd2d..385391a5 100644 --- a/tests/test_timed.py +++ b/tests/test_timed.py @@ -1,5 +1,6 @@ -import time import datetime +import time + import progressbar @@ -9,7 +10,7 @@ def test_timer(): progressbar.Timer(), ] p = progressbar.ProgressBar( - max_value=2, widgets=widgets, poll_interval=0.0001 + max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() @@ -27,7 +28,7 @@ def test_eta(): progressbar.ETA(), ] p = progressbar.ProgressBar( - min_value=0, max_value=2, widgets=widgets, poll_interval=0.0001 + min_value=0, max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() @@ -59,7 +60,7 @@ def test_adaptive_eta(): ) p.start() - for i in range(20): + for _i in range(20): p.update(1) time.sleep(0.001) p.finish() @@ -71,7 +72,7 @@ def test_adaptive_transfer_speed(): progressbar.AdaptiveTransferSpeed(), ] p = progressbar.ProgressBar( - max_value=2, widgets=widgets, poll_interval=0.0001 + max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() @@ -104,7 +105,7 @@ def calculate_eta(self, value, elapsed): monkeypatch.setattr(progressbar.FileTransferSpeed, '_speed', calculate_eta) monkeypatch.setattr( - progressbar.AdaptiveTransferSpeed, '_speed', calculate_eta + progressbar.AdaptiveTransferSpeed, '_speed', calculate_eta, ) for widget in widgets: @@ -149,7 +150,7 @@ def test_non_changing_eta(): progressbar.AdaptiveTransferSpeed(), ] p = progressbar.ProgressBar( - max_value=2, widgets=widgets, poll_interval=0.0001 + max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() @@ -160,17 +161,16 @@ def test_non_changing_eta(): def test_eta_not_available(): - """ + ''' When ETA is not available (data coming from a generator), ETAs should not raise exceptions. - """ + ''' def gen(): - for x in range(200): - yield x + yield from range(200) widgets = [progressbar.AdaptiveETA(), progressbar.ETA()] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar(gen()): + for _i in bar(gen()): pass diff --git a/tests/test_timer.py b/tests/test_timer.py index 4e439a27..dc928786 100644 --- a/tests/test_timer.py +++ b/tests/test_timer.py @@ -1,7 +1,7 @@ -import pytest from datetime import timedelta import progressbar +import pytest @pytest.mark.parametrize( @@ -35,7 +35,7 @@ def test_poll_interval(parameter, poll_interval, expected): ) def test_intervals(monkeypatch, interval): monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', interval + progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', interval, ) bar = progressbar.ProgressBar(max_value=100) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index 0d70fae3..a92727e3 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -1,17 +1,17 @@ -# -*- coding: utf-8 -*- import time -import pytest + import progressbar +import pytest from python_utils import converters @pytest.mark.parametrize( 'name,markers', [ - ('line arrows', u'←↖↑↗→↘↓↙'), - ('block arrows', u'◢◣◤◥'), - ('wheels', u'◐◓◑◒'), + ('line arrows', '←↖↑↗→↘↓↙'), + ('block arrows', '◢◣◤◥'), + ('wheels', '◐◓◑◒'), ], ) @pytest.mark.parametrize('as_unicode', [True, False]) @@ -27,5 +27,5 @@ def test_markers(name, markers, as_unicode): ] bar = progressbar.ProgressBar(widgets=widgets) bar._MINIMUM_UPDATE_INTERVAL = 1e-12 - for i in bar((i for i in range(24))): + for _i in bar(i for i in range(24)): time.sleep(0.001) diff --git a/tests/test_unknown_length.py b/tests/test_unknown_length.py index 454d73df..77e3f84d 100644 --- a/tests/test_unknown_length.py +++ b/tests/test_unknown_length.py @@ -27,4 +27,4 @@ def test_unknown_length_at_start(): pb2 = progressbar.ProgressBar().start(max_value=progressbar.UnknownLength) for w in pb2.widgets: print(type(w), repr(w)) - assert any([isinstance(w, progressbar.Bar) for w in pb2.widgets]) + assert any(isinstance(w, progressbar.Bar) for w in pb2.widgets) diff --git a/tests/test_utils.py b/tests/test_utils.py index 980072de..6f28aeb6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,7 @@ import io -import pytest + import progressbar +import pytest @pytest.mark.parametrize( diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 592d869a..467c6e5f 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -1,7 +1,7 @@ import time -import pytest -import progressbar +import progressbar +import pytest max_values = [None, 10, progressbar.UnknownLength] @@ -57,12 +57,12 @@ def test_widgets_large_values(max_value): def test_format_widget(): - widgets = [] - for mapping in progressbar.FormatLabel.mapping: - widgets.append(progressbar.FormatLabel('%%(%s)r' % mapping)) - + widgets = [ + progressbar.FormatLabel('%%(%s)r' % mapping) + for mapping in progressbar.FormatLabel.mapping + ] p = progressbar.ProgressBar(widgets=widgets) - for i in p(range(10)): + for _ in p(range(10)): time.sleep(1) @@ -145,7 +145,7 @@ def test_all_widgets_min_width(min_width, term_width): progressbar.ReverseBar(min_width=min_width), progressbar.BouncingBar(min_width=min_width), progressbar.FormatCustomText( - 'Custom %(text)s', dict(text='text'), min_width=min_width + 'Custom %(text)s', dict(text='text'), min_width=min_width, ), progressbar.DynamicMessage('custom', min_width=min_width), progressbar.CurrentTime(min_width=min_width), @@ -180,7 +180,7 @@ def test_all_widgets_max_width(max_width, term_width): progressbar.ReverseBar(max_width=max_width), progressbar.BouncingBar(max_width=max_width), progressbar.FormatCustomText( - 'Custom %(text)s', dict(text='text'), max_width=max_width + 'Custom %(text)s', dict(text='text'), max_width=max_width, ), progressbar.DynamicMessage('custom', max_width=max_width), progressbar.CurrentTime(max_width=max_width), diff --git a/tests/test_wrappingio.py b/tests/test_wrappingio.py index 8a352872..b868321c 100644 --- a/tests/test_wrappingio.py +++ b/tests/test_wrappingio.py @@ -2,7 +2,6 @@ import sys import pytest - from progressbar import utils From d2d13443d010e6bdf963555b8ce7f46fabcd0b28 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 21 Sep 2023 12:43:23 +0200 Subject: [PATCH 099/118] made ruff happy --- progressbar/bar.py | 105 +++++++++++++++++++++++++------------------ progressbar/multi.py | 85 ++++++++++++++++++++--------------- pyproject.toml | 2 +- 3 files changed, 111 insertions(+), 81 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index ae249d52..7782e227 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -66,8 +66,17 @@ class ProgressBarMixinBase(abc.ABC): #: no updates min_poll_interval: float + #: Deprecated: The number of intervals that can fit on the screen with a + #: minimum of 100 + num_intervals: int = 0 + #: Deprecated: The `next_update` is kept for compatibility with external + #: libs: https://github.com/WoLpH/python-progressbar/issues/207 + next_update: int = 0 + #: Current progress (min_value <= value <= max_value) value: T + #: Previous progress value + previous_value: types.Optional[T] #: The minimum/start value for the progress bar min_value: T #: Maximum (and final) value. Beyond this value an error will be raised @@ -848,7 +857,6 @@ def update(self, value=None, force=False, **kwargs): 'Updates the ProgressBar to a new value.' if self.start_time is None: self.start() - return self.update(value, force=force, **kwargs) if ( value is not None @@ -874,26 +882,32 @@ def update(self, value=None, force=False, **kwargs): self.value = value # type: ignore # Save the updated values for dynamic messages + variables_changed = self._update_variables(kwargs) + + if self._needs_update() or variables_changed or force: + self._update_parents(value) + + def _update_variables(self, kwargs): variables_changed = False - for key in kwargs: + for key, value_ in kwargs.items(): if key not in self.variables: raise TypeError( - f'update() got an unexpected variable name as argument ' - f'{key!r}') - elif self.variables[key] != kwargs[key]: + 'update() got an unexpected variable name as argument ' + '{key!r}', + ) + elif self.variables[key] != value_: self.variables[key] = kwargs[key] variables_changed = True + return variables_changed - if self._needs_update() or variables_changed or force: - self.updates += 1 - ResizableMixin.update(self, value=value) - ProgressBarBase.update(self, value=value) - StdRedirectMixin.update(self, value=value) # type: ignore + def _update_parents(self, value): + self.updates += 1 + ResizableMixin.update(self, value=value) + ProgressBarBase.update(self, value=value) + StdRedirectMixin.update(self, value=value) # type: ignore - # Only flush if something was actually written - self.fd.flush() - return None - return None + # Only flush if something was actually written + self.fd.flush() def start(self, max_value=None, init=True): '''Starts measuring time, and prints the bar at 0%. @@ -902,9 +916,9 @@ def start(self, max_value=None, init=True): Args: max_value (int): The maximum value of the progressbar - reinit (bool): Initialize the progressbar, this is useful if you + init (bool): (Re)Initialize the progressbar, this is useful if you wish to reuse the same progressbar but can be disabled if - data needs to be passed along to the next run + data needs to be persisted between runs >>> pbar = ProgressBar().start() >>> for i in range(100): @@ -934,6 +948,29 @@ def start(self, max_value=None, init=True): if not self.widgets: self.widgets = self.default_widgets() + self._init_prefix() + self._init_suffix() + self._calculate_poll_interval() + self._verify_max_value() + + now = datetime.now() + self.start_time = self.initial_start_time or now + self.last_update_time = now + self._last_update_timer = timeit.default_timer() + self.update(self.min_value, force=True) + + return self + + def _init_suffix(self): + if self.suffix: + self.widgets.append( + widgets.FormatLabel(self.suffix, new_style=True), + ) + # Unset the suffix variable after applying so an extra start() + # won't keep copying it + self.suffix = None + + def _init_prefix(self): if self.prefix: self.widgets.insert( 0, @@ -943,14 +980,16 @@ def start(self, max_value=None, init=True): # won't keep copying it self.prefix = None - if self.suffix: - self.widgets.append( - widgets.FormatLabel(self.suffix, new_style=True), - ) - # Unset the suffix variable after applying so an extra start() - # won't keep copying it - self.suffix = None + def _verify_max_value(self): + if ( + self.max_value is not base.UnknownLength + and self.max_value is not None + and self.max_value < 0 # type: ignore + ): + raise ValueError('max_value out of range, got %r' % self.max_value) + def _calculate_poll_interval(self): + self.num_intervals = max(100, self.term_width) for widget in self.widgets: interval: int | float | None = utils.deltas_to_seconds( getattr(widget, 'INTERVAL', None), @@ -962,26 +1001,6 @@ def start(self, max_value=None, init=True): interval, ) - self.num_intervals = max(100, self.term_width) - # The `next_update` is kept for compatibility with external libs: - # https://github.com/WoLpH/python-progressbar/issues/207 - self.next_update = 0 - - if ( - self.max_value is not base.UnknownLength - and self.max_value is not None - and self.max_value < 0 # type: ignore - ): - raise ValueError('max_value out of range, got %r' % self.max_value) - - now = datetime.now() - self.start_time = self.initial_start_time or now - self.last_update_time = now - self._last_update_timer = timeit.default_timer() - self.update(self.min_value, force=True) - - return self - def finish(self, end='\n', dirty=False): ''' Puts the ProgressBar bar in the finished state. diff --git a/progressbar/multi.py b/progressbar/multi.py index eca882c8..d63d3d4f 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -12,6 +12,7 @@ from datetime import timedelta import python_utils +from python_utils import decorators from . import bar, terminal from .terminal import stream @@ -171,48 +172,14 @@ def render(self, flush: bool = True, force: bool = False): '''Render the multibar to the given stream.''' now = timeit.default_timer() expired = now - self.remove_finished if self.remove_finished else None + + # sourcery skip: list-comprehension output = [] for bar_ in self.get_sorted_bars(): if not bar_.started() and not self.show_initial: continue - def update(force=True, write=True): - self._label_bar(bar_) - bar_.update(force=force) - if write: - output.append(bar_.fd.line) - - if bar_.finished(): - if bar_ not in self._finished_at: - self._finished_at[bar_] = now - # Force update to get the finished format - update(write=False) - - if ( - self.remove_finished - and expired is not None - and expired >= self._finished_at[bar_]): - del self[bar_.label] - continue - - if not self.show_finished: - continue - - if bar_.finished(): - if self.finished_format is None: - update(force=False) - else: - output.append( - self.finished_format.format(label=bar_.label), - ) - elif bar_.started(): - update() - else: - if self.initial_format is None: - bar_.start() - update() - else: - output.append(self.initial_format.format(label=bar_.label)) + output += self._render_bar(bar_, expired=expired, now=now) with self._print_lock: # Clear the previous output if progressbars have been removed @@ -244,6 +211,50 @@ def update(force=True, write=True): if flush: self.flush() + @decorators.listify() + def _render_bar(self, bar_: bar.ProgressBar, now, expired) -> str | None: + def update(force=True, write=True): + self._label_bar(bar_) + bar_.update(force=force) + if write: + yield bar_.fd.line + + if bar_.finished(): + yield from self._render_finished_bar(bar_, now, expired, update) + + elif bar_.started(): + update() + else: + if self.initial_format is None: + bar_.start() + update() + else: + yield self.initial_format.format(label=bar_.label) + + def _render_finished_bar( + self, bar_: bar.ProgressBar, now, expired, update, + ) -> str | None: + if bar_ not in self._finished_at: + self._finished_at[bar_] = now + # Force update to get the finished format + update(write=False) + + if ( + self.remove_finished + and expired is not None + and expired >= self._finished_at[bar_]): + del self[bar_.label] + return + + if not self.show_finished: + return + + if bar_.finished(): + if self.finished_format is None: + update(force=False) + else: + yield self.finished_format.format(label=bar_.label) + def print( self, *args, end='\n', offset=None, flush=True, clear=True, **kwargs, diff --git a/pyproject.toml b/pyproject.toml index 4b8d81a6..e871e981 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,7 @@ repository = 'https://github.com/wolph/python-progressbar/' [build-system] build-backend = 'setuptools.build_meta' -requires = ['setuptools', 'setuptools-scm', 'wheel'] +requires = ['setuptools', 'setuptools-scm'] [tool.codespell] skip = '*/htmlcov,./docs/_build,*.asc' From ffca1aef62190efa6485c23bbbc07d6bc6b9540b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 26 Sep 2023 03:06:16 +0200 Subject: [PATCH 100/118] Many linting and code quality changes --- docs/_theme/flask_theme_support.py | 126 ++++++++++++++--------------- docs/conf.py | 20 ++--- 2 files changed, 71 insertions(+), 75 deletions(-) diff --git a/docs/_theme/flask_theme_support.py b/docs/_theme/flask_theme_support.py index 0dcf53b7..c11997c7 100644 --- a/docs/_theme/flask_theme_support.py +++ b/docs/_theme/flask_theme_support.py @@ -17,73 +17,73 @@ class FlaskyStyle(Style): - background_color = "#f8f8f8" - default_style = "" + background_color = '#f8f8f8' + default_style = '' styles = { # No corresponding class for the following: - # Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - Punctuation: "bold #000000", # class: 'p' + # Text: '', # class: '' + Whitespace: 'underline #f8f8f8', # class: 'w' + Error: '#a40000 border:#ef2929', # class: 'err' + Other: '#000000', # class 'x' + Comment: 'italic #8f5902', # class: 'c' + Comment.Preproc: 'noitalic', # class: 'cp' + Keyword: 'bold #004461', # class: 'k' + Keyword.Constant: 'bold #004461', # class: 'kc' + Keyword.Declaration: 'bold #004461', # class: 'kd' + Keyword.Namespace: 'bold #004461', # class: 'kn' + Keyword.Pseudo: 'bold #004461', # class: 'kp' + Keyword.Reserved: 'bold #004461', # class: 'kr' + Keyword.Type: 'bold #004461', # class: 'kt' + Operator: '#582800', # class: 'o' + Operator.Word: 'bold #004461', # class: 'ow' - like keywords + Punctuation: 'bold #000000', # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - Number: "#990000", # class: 'm' - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' + Name: '#000000', # class: 'n' + Name.Attribute: '#c4a000', # class: 'na' - to be revised + Name.Builtin: '#004461', # class: 'nb' + Name.Builtin.Pseudo: '#3465a4', # class: 'bp' + Name.Class: '#000000', # class: 'nc' - to be revised + Name.Constant: '#000000', # class: 'no' - to be revised + Name.Decorator: '#888', # class: 'nd' - to be revised + Name.Entity: '#ce5c00', # class: 'ni' + Name.Exception: 'bold #cc0000', # class: 'ne' + Name.Function: '#000000', # class: 'nf' + Name.Property: '#000000', # class: 'py' + Name.Label: '#f57900', # class: 'nl' + Name.Namespace: '#000000', # class: 'nn' - to be revised + Name.Other: '#000000', # class: 'nx' + Name.Tag: 'bold #004461', # class: 'nt' - like a keyword + Name.Variable: '#000000', # class: 'nv' - to be revised + Name.Variable.Class: '#000000', # class: 'vc' - to be revised + Name.Variable.Global: '#000000', # class: 'vg' - to be revised + Name.Variable.Instance: '#000000', # class: 'vi' - to be revised + Number: '#990000', # class: 'm' + Literal: '#000000', # class: 'l' + Literal.Date: '#000000', # class: 'ld' + String: '#4e9a06', # class: 's' + String.Backtick: '#4e9a06', # class: 'sb' + String.Char: '#4e9a06', # class: 'sc' + String.Doc: 'italic #8f5902', # class: 'sd' - like a comment + String.Double: '#4e9a06', # class: 's2' + String.Escape: '#4e9a06', # class: 'se' + String.Heredoc: '#4e9a06', # class: 'sh' + String.Interpol: '#4e9a06', # class: 'si' + String.Other: '#4e9a06', # class: 'sx' + String.Regex: '#4e9a06', # class: 'sr' + String.Single: '#4e9a06', # class: 's1' + String.Symbol: '#4e9a06', # class: 'ss' + Generic: '#000000', # class: 'g' + Generic.Deleted: '#a40000', # class: 'gd' + Generic.Emph: 'italic #000000', # class: 'ge' + Generic.Error: '#ef2929', # class: 'gr' + Generic.Heading: 'bold #000080', # class: 'gh' + Generic.Inserted: '#00A000', # class: 'gi' + Generic.Output: '#888', # class: 'go' + Generic.Prompt: '#745334', # class: 'gp' + Generic.Strong: 'bold #000000', # class: 'gs' + Generic.Subheading: 'bold #800080', # class: 'gu' + Generic.Traceback: 'bold #a40000', # class: 'gt' } diff --git a/docs/conf.py b/docs/conf.py index 140f7cd7..300d9dc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,10 +61,7 @@ # General information about the project. project = u'Progress Bar' project_slug = ''.join(project.capitalize().split()) -copyright = u'%s, %s' % ( - datetime.date.today().year, - metadata.__author__, -) +copyright = f'{datetime.date.today().year}, {metadata.__author__}' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -72,7 +69,6 @@ # # The short X.Y version. version = metadata.__version__ -assert version == '4.3b0', version # The full version, including alpha/beta/rc tags. release = metadata.__version__ @@ -191,7 +187,7 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project_slug +htmlhelp_basename = f'{project_slug}doc' # -- Options for LaTeX output -------------------------------------------- @@ -210,11 +206,11 @@ latex_documents = [ ( 'index', - '%s.tex' % project_slug, - u'%s Documentation' % project, + f'{project_slug}.tex', + f'{project} Documentation', metadata.__author__, 'manual', - ), + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -246,7 +242,7 @@ ( 'index', project_slug.lower(), - u'%s Documentation' % project, + f'{project} Documentation', [metadata.__author__], 1, ) @@ -265,12 +261,12 @@ ( 'index', project_slug, - u'%s Documentation' % project, + f'{project} Documentation', metadata.__author__, project_slug, 'One line description of project.', 'Miscellaneous', - ), + ) ] # Documents to append as an appendix to all manuals. From 3ed46d18c11c990f5dc5d4b9ed6dc898d6abc4a1 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 26 Sep 2023 03:08:24 +0200 Subject: [PATCH 101/118] Test improvements --- examples.py | 5 +++-- pyproject.toml | 12 ++++++++++-- ruff.toml | 3 ++- tests/test_color.py | 13 ++++++------- tests/test_custom_widgets.py | 4 +--- tests/test_iterators.py | 8 ++++---- tests/test_unicode.py | 4 ++-- tox.ini | 8 +------- 8 files changed, 29 insertions(+), 28 deletions(-) diff --git a/examples.py b/examples.py index 2a15b920..569c1acf 100644 --- a/examples.py +++ b/examples.py @@ -5,10 +5,11 @@ import random import sys import time +import typing import progressbar -examples = [] +examples: typing.List[typing.Callable[[typing.Any], typing.Any]] = [] def example(fn): @@ -778,4 +779,4 @@ def test(*tests): try: test(*sys.argv[1:]) except KeyboardInterrupt: - sys.stdout('\nQuitting examples.\n') + sys.stdout.write('\nQuitting examples.\n') diff --git a/pyproject.toml b/pyproject.toml index e871e981..20d0ef9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ classifiers = [ description = 'A Python Progressbar library to provide visual (yet text based) progress to long running operations.' readme = 'README.rst' -dependencies = ['python-utils >= 3.4.5'] +dependencies = ['python-utils >= 3.8.0'] [tool.setuptools.dynamic] version = { attr = 'progressbar.__about__.__version__' } @@ -112,7 +112,7 @@ include-package-data = true cli-name = 'progressbar.cli:main' [project.optional-dependencies] -docs = ['sphinx>=1.8.5'] +docs = ['sphinx>=1.8.5', 'sphinx-autodoc-typehints>=1.6.0'] tests = [ 'dill>=0.3.6', 'flake8>=3.7.7', @@ -140,3 +140,11 @@ ignore-words-list = 'datas' [tool.black] line-length = 79 skip-string-normalization = true + +[tool.mypy] +packages = ['progressbar', 'tests'] +exclude = [ + 'docs', + 'tests/original_examples.py', + 'examples.py', +] diff --git a/ruff.toml b/ruff.toml index 5b52328b..8ff7284c 100644 --- a/ruff.toml +++ b/ruff.toml @@ -3,7 +3,6 @@ target-version = 'py38' -#extend-exclude = ['tests'] src = ['progressbar'] format = 'grouped' @@ -60,6 +59,8 @@ select = [ [per-file-ignores] 'tests/*' = ['INP001', 'T201', 'T203'] +'examples.py' = ['T201'] +'docs/*' = ['*'] [pydocstyle] convention = 'google' diff --git a/tests/test_color.py b/tests/test_color.py index d05f5f0d..a8fbb5e6 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -2,9 +2,10 @@ import typing -import progressbar import pytest -from progressbar import terminal + +import progressbar +from progressbar import terminal, widgets @pytest.mark.parametrize( @@ -47,7 +48,7 @@ def test_enable_colors_flags(): class _TestFixedColorSupport(progressbar.widgets.WidgetBase): - _fixed_colors: typing.ClassVar[dict[str, terminal.Color | None]] = dict( + _fixed_colors: typing.ClassVar[widgets.TFixedColors] = widgets.TFixedColors( fg_none=progressbar.widgets.colors.yellow, bg_none=None, ) @@ -57,12 +58,10 @@ def __call__(self, *args, **kwargs): class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): - _gradient_colors: typing.ClassVar[dict[str, terminal.ColorGradient | - None]] = ( - dict( + _gradient_colors: typing.ClassVar[widgets.TGradientColors] = widgets.TGradientColors( fg=progressbar.widgets.colors.gradient, bg=None, - )) + ) def __call__(self, *args, **kwargs): pass diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index e24449e2..dfe5fc8c 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -9,9 +9,7 @@ class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): def update(self, pbar): if 45 < pbar.percentage() < 80: - return 'Bigger Now ' + progressbar.FileTransferSpeed.update( - self, pbar, - ) + return f'Bigger Now {progressbar.FileTransferSpeed.update(self, pbar)}' else: return progressbar.FileTransferSpeed.update(self, pbar) diff --git a/tests/test_iterators.py b/tests/test_iterators.py index c690e299..ba48661f 100644 --- a/tests/test_iterators.py +++ b/tests/test_iterators.py @@ -14,7 +14,7 @@ def test_list(): def test_iterator_with_max_value(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar(max_value=10) - for _i in p(i for i in range(10)): + for _i in p(iter(range(10))): time.sleep(0.001) @@ -22,7 +22,7 @@ def test_iterator_without_max_value_error(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar() - for _i in p(i for i in range(10)): + for _i in p(iter(range(10))): time.sleep(0.001) assert p.max_value is progressbar.UnknownLength @@ -38,7 +38,7 @@ def test_iterator_without_max_value(): progressbar.BouncingBar(marker=progressbar.RotatingMarker()), ], ) - for _i in p(i for i in range(10)): + for _i in p(iter(range(10))): time.sleep(0.001) @@ -46,7 +46,7 @@ def test_iterator_with_incorrect_max_value(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar(max_value=10) with pytest.raises(ValueError): - for _i in p(i for i in range(20)): + for _i in p(iter(range(20))): time.sleep(0.001) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index a92727e3..674bdcc4 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -22,10 +22,10 @@ def test_markers(name, markers, as_unicode): markers = converters.to_str(markers) widgets = [ - '%s: ' % name.capitalize(), + f'{name.capitalize()}: ', progressbar.AnimatedMarker(markers=markers), ] bar = progressbar.ProgressBar(widgets=widgets) bar._MINIMUM_UPDATE_INTERVAL = 1e-12 - for _i in bar(i for i in range(24)): + for _i in bar(iter(range(24))): time.sleep(0.001) diff --git a/tox.ini b/tox.ini index 1a859167..583dbe38 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] envlist = - py37 py38 py39 py310 + py311 docs black mypy @@ -13,12 +13,6 @@ envlist = skip_missing_interpreters = True [testenv] -basepython = - py38: python3.8 - py39: python3.9 - py310: python3.10 - pypy3: pypy3 - deps = -r{toxinidir}/tests/requirements.txt commands = py.test --basetemp="{envtmpdir}" --confcutdir=.. {posargs} changedir = tests From d8a7653141d49809d978ac55d8f96482f85c39da Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 26 Sep 2023 14:33:11 +0200 Subject: [PATCH 102/118] Many linting and code style improvements, nearly done now --- progressbar/__init__.py | 2 - progressbar/bar.py | 136 ++-- progressbar/multi.py | 95 ++- progressbar/terminal/base.py | 62 +- progressbar/terminal/colors.py | 782 +++++++++++++++---- progressbar/terminal/os_specific/__init__.py | 1 + progressbar/terminal/os_specific/windows.py | 8 +- progressbar/terminal/stream.py | 8 +- progressbar/utils.py | 34 +- progressbar/widgets.py | 34 +- pyproject.toml | 2 +- pytest.ini | 10 +- 12 files changed, 815 insertions(+), 359 deletions(-) diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 49be705f..1de833d0 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -31,7 +31,6 @@ ReverseBar, RotatingMarker, SimpleProgress, - SliceableDeque, Timer, Variable, VariableMixin, @@ -77,5 +76,4 @@ 'LineOffsetStreamWrapper', 'MultiBar', 'SortKey', - 'SliceableDeque', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index 7782e227..d502964e 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import contextlib import itertools import logging import math @@ -159,6 +160,9 @@ class DefaultFdMixin(ProgressBarMixinBase): #: compatible we will automatically enable `colors` and disable #: `line_breaks`. is_ansi_terminal: bool = False + #: Whether the file descriptor is a terminal or not. This is used to + #: determine whether to use ANSI escape codes or not. + is_terminal: bool #: Whether to print line breaks. This is useful for logging the #: progressbar. When disabled the current line is overwritten. line_breaks: bool = True @@ -175,13 +179,13 @@ class DefaultFdMixin(ProgressBarMixinBase): enable_colors: terminal.ColorSupport | bool | None = terminal.color_support def __init__( - self, - fd: base.IO = sys.stderr, - is_terminal: bool | None = None, - line_breaks: bool | None = None, - enable_colors: terminal.ColorSupport | None = None, - line_offset: int = 0, - **kwargs, + self, + fd: base.IO = sys.stderr, + is_terminal: bool | None = None, + line_breaks: bool | None = None, + enable_colors: terminal.ColorSupport | None = None, + line_offset: int = 0, + **kwargs, ): if fd is sys.stdout: fd = utils.streams.original_stdout @@ -201,15 +205,15 @@ def _apply_line_offset(self, fd: base.IO, line_offset: int) -> base.IO: if line_offset: return progressbar.terminal.stream.LineOffsetStreamWrapper( line_offset, - fd, + types.cast(base.TextIO, fd), ) else: return fd def _determine_is_terminal( - self, - fd: base.IO, - is_terminal: bool | None, + self, + fd: base.IO, + is_terminal: bool | None, ) -> bool: if is_terminal is not None: return utils.is_terminal(fd, is_terminal) @@ -226,8 +230,8 @@ def _determine_line_breaks(self, line_breaks: bool | None) -> bool: return bool(line_breaks) def _determine_enable_colors( - self, - enable_colors: terminal.ColorSupport | None, + self, + enable_colors: terminal.ColorSupport | None, ) -> terminal.ColorSupport: if enable_colors is None: colors = ( @@ -243,6 +247,10 @@ def _determine_enable_colors( else: enable_colors = terminal.ColorSupport.NONE break + else: # pragma: no cover + # This scenario should never occur because `is_ansi_terminal` + # should always be `True` or `False` + raise ValueError('Unable to determine color support') elif enable_colors is True: enable_colors = terminal.ColorSupport.XTERM_256 @@ -253,10 +261,10 @@ def _determine_enable_colors( return enable_colors - def print(self, *args, **kwargs): + def print(self, *args: types.Any, **kwargs: types.Any) -> None: print(*args, file=self.fd, **kwargs) - def update(self, *args, **kwargs): + def update(self, *args: types.Any, **kwargs: types.Any) -> None: ProgressBarMixinBase.update(self, *args, **kwargs) line: str = converters.to_unicode(self._format_line()) @@ -270,7 +278,9 @@ def update(self, *args, **kwargs): except UnicodeEncodeError: # pragma: no cover self.fd.write(line.encode('ascii', 'replace')) - def finish(self, *args, **kwargs): # pragma: no cover + def finish( + self, *args: types.Any, **kwargs: types.Any + ) -> None: # pragma: no cover if self._finished: return @@ -299,8 +309,8 @@ def _format_widgets(self): for index, widget in enumerate(self.widgets): if isinstance( - widget, - widgets.WidgetBase, + widget, + widgets.WidgetBase, ) and not widget.check_size(self): continue elif isinstance(widget, widgets.AutoWidthWidgetBase): @@ -341,15 +351,13 @@ def __init__(self, term_width: int | None = None, **kwargs): if term_width: self.term_width = term_width else: # pragma: no cover - try: + with contextlib.suppress(Exception): self._handle_resize() import signal self._prev_handle = signal.getsignal(signal.SIGWINCH) signal.signal(signal.SIGWINCH, self._handle_resize) self.signal_set = True - except Exception: - pass def _handle_resize(self, signum=None, frame=None): 'Tries to catch resize signals sent from the terminal.' @@ -359,12 +367,10 @@ def _handle_resize(self, signum=None, frame=None): def finish(self): # pragma: no cover ProgressBarMixinBase.finish(self) if self.signal_set: - try: + with contextlib.suppress(Exception): import signal signal.signal(signal.SIGWINCH, self._prev_handle) - except Exception: # pragma no cover - pass class StdRedirectMixin(DefaultFdMixin): @@ -376,10 +382,10 @@ class StdRedirectMixin(DefaultFdMixin): _stderr: base.IO def __init__( - self, - redirect_stderr: bool = False, - redirect_stdout: bool = False, - **kwargs, + self, + redirect_stderr: bool = False, + redirect_stdout: bool = False, + **kwargs, ): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr @@ -507,24 +513,24 @@ class ProgressBar( paused: bool = False def __init__( - self, - min_value: T = 0, - max_value: T | types.Type[base.UnknownLength] | None = None, - widgets: types.Optional[ - types.Sequence[widgets_module.WidgetBase | str] - ] = None, - left_justify: bool = True, - initial_value: T = 0, - poll_interval: types.Optional[float] = None, - widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None, - custom_len: types.Callable[[str], int] = utils.len_color, - max_error=True, - prefix=None, - suffix=None, - variables=None, - min_poll_interval=None, - **kwargs, - ): + self, + min_value: T = 0, + max_value: T | types.Type[base.UnknownLength] | None = None, + widgets: types.Optional[ + types.Sequence[widgets_module.WidgetBase | str] + ] = None, + left_justify: bool = True, + initial_value: T = 0, + poll_interval: types.Optional[float] = None, + widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None, + custom_len: types.Callable[[str], int] = utils.len_color, + max_error=True, + prefix=None, + suffix=None, + variables=None, + min_poll_interval=None, + **kwargs, + ): # sourcery skip: low-code-quality '''Initializes a progress bar with sane defaults.''' StdRedirectMixin.__init__(self, **kwargs) ResizableMixin.__init__(self, **kwargs) @@ -547,7 +553,7 @@ def __init__( ) poll_interval = kwargs.get('poll') - if max_value and min_value > max_value: + if max_value and min_value > types.cast(T, max_value): raise ValueError( 'Max value needs to be bigger than the min value', ) @@ -588,8 +594,8 @@ def __init__( default=None, ) self._MINIMUM_UPDATE_INTERVAL = ( - utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) - or self._MINIMUM_UPDATE_INTERVAL + utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) + or self._MINIMUM_UPDATE_INTERVAL ) # Note that the _MINIMUM_UPDATE_INTERVAL sets the minimum in case of @@ -604,8 +610,10 @@ def __init__( # A dictionary of names that can be used by Variable and FormatWidget self.variables = utils.AttributeDict(variables or {}) for widget in self.widgets: - if isinstance(widget, widgets_module.VariableMixin) \ - and widget.name not in self.variables: + if ( + isinstance(widget, widgets_module.VariableMixin) + and widget.name not in self.variables + ): self.variables[widget.name] = None @property @@ -725,7 +733,7 @@ def data(self) -> types.Dict[str, types.Any]: total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.0), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -840,16 +848,12 @@ def _needs_update(self): # Update if value increment is not large enough to # add more bars to progressbar (according to current # terminal width) - try: + with contextlib.suppress(Exception): divisor: float = self.max_value / self.term_width # type: ignore value_divisor = self.value // divisor # type: ignore pvalue_divisor = self.previous_value // divisor # type: ignore if value_divisor != pvalue_divisor: return True - except Exception: - # ignore any division errors - pass - # No need to redraw yet return False @@ -859,9 +863,9 @@ def update(self, value=None, force=False, **kwargs): self.start() if ( - value is not None - and value is not base.UnknownLength - and isinstance(value, int) + value is not None + and value is not base.UnknownLength + and isinstance(value, int) ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update @@ -869,12 +873,14 @@ def update(self, value=None, force=False, **kwargs): elif self.min_value > value: # type: ignore raise ValueError( f'Value {value} is too small. Should be ' - f'between {self.min_value} and {self.max_value}') + f'between {self.min_value} and {self.max_value}' + ) elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( f'Value {value} is too large. Should be between ' - f'{self.min_value} and {self.max_value}') + f'{self.min_value} and {self.max_value}' + ) else: value = self.max_value @@ -982,9 +988,9 @@ def _init_prefix(self): def _verify_max_value(self): if ( - self.max_value is not base.UnknownLength - and self.max_value is not None - and self.max_value < 0 # type: ignore + self.max_value is not base.UnknownLength + and self.max_value is not None + and self.max_value < 0 # type: ignore ): raise ValueError('max_value out of range, got %r' % self.max_value) diff --git a/progressbar/multi.py b/progressbar/multi.py index d63d3d4f..d40e5516 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -10,6 +10,7 @@ import timeit import typing from datetime import timedelta +from typing import List, Any import python_utils from python_utils import decorators @@ -76,22 +77,22 @@ class MultiBar(typing.Dict[str, bar.ProgressBar]): _thread_closed: threading.Event = threading.Event() def __init__( - self, - bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, - fd=sys.stderr, - prepend_label: bool = True, - append_label: bool = False, - label_format='{label:20.20} ', - initial_format: str | None = '{label:20.20} Not yet started', - finished_format: str | None = None, - update_interval: float = 1 / 60.0, # 60fps - show_initial: bool = True, - show_finished: bool = True, - remove_finished: timedelta | float = timedelta(seconds=3600), - sort_key: str | SortKey = SortKey.CREATED, - sort_reverse: bool = True, - sort_keyfunc: SortKeyFunc | None = None, - **progressbar_kwargs, + self, + bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, + fd=sys.stderr, + prepend_label: bool = True, + append_label: bool = False, + label_format='{label:20.20} ', + initial_format: str | None = '{label:20.20} Not yet started', + finished_format: str | None = None, + update_interval: float = 1 / 60.0, # 60fps + show_initial: bool = True, + show_finished: bool = True, + remove_finished: timedelta | float = timedelta(seconds=3600), + sort_key: str | SortKey = SortKey.CREATED, + sort_reverse: bool = True, + sort_keyfunc: SortKeyFunc | None = None, + **progressbar_kwargs, ): self.fd = fd @@ -124,20 +125,21 @@ def __init__( super().__init__(bars or {}) - def __setitem__(self, key: str, value: bar.ProgressBar): + def __setitem__(self, key: str, bar: bar.ProgressBar): '''Add a progressbar to the multibar.''' - if value.label != key or not key: # pragma: no branch - value.label = key - value.fd = stream.LastLineStream(self.fd) - value.paused = True - value.print = self.print + if bar.label != key or not key: # pragma: no branch + bar.label = key + bar.fd = stream.LastLineStream(self.fd) + bar.paused = True + # Essentially `bar.print = self.print`, but `mypy` doesn't like that + setattr(bar, 'print', self.print) # Just in case someone is using a progressbar with a custom # constructor and forgot to call the super constructor - if value.index == -1: - value.index = next(value._index_counter) + if bar.index == -1: + bar.index = next(bar._index_counter) - super().__setitem__(key, value) + super().__setitem__(key, bar) def __delitem__(self, key): '''Remove a progressbar from the multibar.''' @@ -174,12 +176,14 @@ def render(self, flush: bool = True, force: bool = False): expired = now - self.remove_finished if self.remove_finished else None # sourcery skip: list-comprehension - output = [] + output: list[str] = [] for bar_ in self.get_sorted_bars(): if not bar_.started() and not self.show_initial: continue - output += self._render_bar(bar_, expired=expired, now=now) + output.extend( + iter(self._render_bar(bar_, expired=expired, now=now)) + ) with self._print_lock: # Clear the previous output if progressbars have been removed @@ -193,9 +197,11 @@ def render(self, flush: bool = True, force: bool = False): self._buffer.write('\n') for i, (previous, current) in enumerate( - itertools.zip_longest( - self._previous_output, output, fillvalue='', - ), + itertools.zip_longest( + self._previous_output, + output, + fillvalue='', + ), ): if previous != current or force: self.print( @@ -211,8 +217,9 @@ def render(self, flush: bool = True, force: bool = False): if flush: self.flush() - @decorators.listify() - def _render_bar(self, bar_: bar.ProgressBar, now, expired) -> str | None: + def _render_bar( + self, bar_: bar.ProgressBar, now, expired + ) -> typing.Iterable[str]: def update(force=True, write=True): self._label_bar(bar_) bar_.update(force=force) @@ -232,17 +239,22 @@ def update(force=True, write=True): yield self.initial_format.format(label=bar_.label) def _render_finished_bar( - self, bar_: bar.ProgressBar, now, expired, update, - ) -> str | None: + self, + bar_: bar.ProgressBar, + now, + expired, + update, + ) -> typing.Iterable[str]: if bar_ not in self._finished_at: self._finished_at[bar_] = now # Force update to get the finished format update(write=False) if ( - self.remove_finished - and expired is not None - and expired >= self._finished_at[bar_]): + self.remove_finished + and expired is not None + and expired >= self._finished_at[bar_] + ): del self[bar_.label] return @@ -256,8 +268,13 @@ def _render_finished_bar( yield self.finished_format.format(label=bar_.label) def print( - self, *args, end='\n', offset=None, flush=True, clear=True, - **kwargs, + self, + *args, + end='\n', + offset=None, + flush=True, + clear=True, + **kwargs, ): ''' Print to the progressbar stream without overwriting the progressbars. diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index ec0c5556..709ddf92 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -171,7 +171,7 @@ def from_env(cls): ) if os.environ.get('JUPYTER_COLUMNS') or os.environ.get( - 'JUPYTER_LINES', + 'JUPYTER_LINES', ): # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR @@ -275,7 +275,9 @@ class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): def from_rgb(cls, rgb: RGB) -> HLS: return cls( *colorsys.rgb_to_hls( - rgb.red / 255, rgb.green / 255, rgb.blue / 255, + rgb.red / 255, + rgb.green / 255, + rgb.blue / 255, ), ) @@ -288,7 +290,6 @@ def interpolate(self, end: HLS, step: float) -> HLS: class ColorBase(abc.ABC): - @abc.abstractmethod def get_color(self, value: float) -> Color: raise NotImplementedError() @@ -370,24 +371,29 @@ def __hash__(self): class Colors: by_name: ClassVar[ - defaultdict[str, types.List[Color]]] = collections.defaultdict(list) + defaultdict[str, types.List[Color]] + ] = collections.defaultdict(list) by_lowername: ClassVar[ - defaultdict[str, types.List[Color]]] = collections.defaultdict(list) - by_hex: ClassVar[defaultdict[str, types.List[Color]]] = ( - collections.defaultdict(list)) - by_rgb: ClassVar[defaultdict[RGB, types.List[Color]]] = ( - collections.defaultdict(list)) - by_hls: ClassVar[defaultdict[HLS, types.List[Color]]] = ( - collections.defaultdict(list)) + defaultdict[str, types.List[Color]] + ] = collections.defaultdict(list) + by_hex: ClassVar[ + defaultdict[str, types.List[Color]] + ] = collections.defaultdict(list) + by_rgb: ClassVar[ + defaultdict[RGB, types.List[Color]] + ] = collections.defaultdict(list) + by_hls: ClassVar[ + defaultdict[HLS, types.List[Color]] + ] = collections.defaultdict(list) by_xterm: ClassVar[dict[int, Color]] = dict() @classmethod def register( - cls, - rgb: RGB, - hls: types.Optional[HLS] = None, - name: types.Optional[str] = None, - xterm: types.Optional[int] = None, + cls, + rgb: RGB, + hls: types.Optional[HLS] = None, + name: types.Optional[str] = None, + xterm: types.Optional[int] = None, ) -> Color: color = Color(rgb, hls, name, xterm) @@ -418,12 +424,16 @@ def __init__(self, *colors: Color, interpolate=Colors.interpolate): self.colors = colors self.interpolate = interpolate - def __call__(self, value: float): + def __call__(self, value: float) -> Color: return self.get_color(value) def get_color(self, value: float) -> Color: 'Map a value from 0 to 1 to a color.' - if value == base.Undefined or value == base.UnknownLength or value <= 0: + if ( + value == base.Undefined + or value == base.UnknownLength + or value <= 0 + ): return self.colors[0] elif value >= 1: return self.colors[-1] @@ -462,14 +472,14 @@ def get_color(value: float, color: OptionalColor) -> Color | None: def apply_colors( - text: str, - percentage: float | None = None, - *, - fg: OptionalColor = None, - bg: OptionalColor = None, - fg_none: Color | None = None, - bg_none: Color | None = None, - **kwargs: types.Any, + text: str, + percentage: float | None = None, + *, + fg: OptionalColor = None, + bg: OptionalColor = None, + fg_none: Color | None = None, + bg_none: Color | None = None, + **kwargs: types.Any, ) -> str: if fg is None and bg is None: return text diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index 885cd062..fbed929d 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -27,508 +27,966 @@ blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) dark_green = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) deep_sky_blue4 = Colors.register( - RGB(0, 95, 95), HLS(100, 180, 18), 'DeepSkyBlue4', 23, + RGB(0, 95, 95), + HLS(100, 180, 18), + 'DeepSkyBlue4', + 23, ) deep_sky_blue4 = Colors.register( - RGB(0, 95, 135), HLS(100, 97, 26), 'DeepSkyBlue4', 24, + RGB(0, 95, 135), + HLS(100, 97, 26), + 'DeepSkyBlue4', + 24, ) deep_sky_blue4 = Colors.register( - RGB(0, 95, 175), HLS(100, 7, 34), 'DeepSkyBlue4', 25, + RGB(0, 95, 175), + HLS(100, 7, 34), + 'DeepSkyBlue4', + 25, ) dodger_blue3 = Colors.register( - RGB(0, 95, 215), HLS(100, 13, 42), 'DodgerBlue3', 26, + RGB(0, 95, 215), + HLS(100, 13, 42), + 'DodgerBlue3', + 26, ) dodger_blue2 = Colors.register( - RGB(0, 95, 255), HLS(100, 17, 50), 'DodgerBlue2', 27, + RGB(0, 95, 255), + HLS(100, 17, 50), + 'DodgerBlue2', + 27, ) green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) spring_green4 = Colors.register( - RGB(0, 135, 95), HLS(100, 62, 26), 'SpringGreen4', 29, + RGB(0, 135, 95), + HLS(100, 62, 26), + 'SpringGreen4', + 29, ) turquoise4 = Colors.register( - RGB(0, 135, 135), HLS(100, 180, 26), 'Turquoise4', 30, + RGB(0, 135, 135), + HLS(100, 180, 26), + 'Turquoise4', + 30, ) deep_sky_blue3 = Colors.register( - RGB(0, 135, 175), HLS(100, 93, 34), 'DeepSkyBlue3', 31, + RGB(0, 135, 175), + HLS(100, 93, 34), + 'DeepSkyBlue3', + 31, ) deep_sky_blue3 = Colors.register( - RGB(0, 135, 215), HLS(100, 2, 42), 'DeepSkyBlue3', 32, + RGB(0, 135, 215), + HLS(100, 2, 42), + 'DeepSkyBlue3', + 32, ) dodger_blue1 = Colors.register( - RGB(0, 135, 255), HLS(100, 8, 50), 'DodgerBlue1', 33, + RGB(0, 135, 255), + HLS(100, 8, 50), + 'DodgerBlue1', + 33, ) green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) spring_green3 = Colors.register( - RGB(0, 175, 95), HLS(100, 52, 34), 'SpringGreen3', 35, + RGB(0, 175, 95), + HLS(100, 52, 34), + 'SpringGreen3', + 35, ) dark_cyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) light_sea_green = Colors.register( - RGB(0, 175, 175), HLS(100, 180, 34), 'LightSeaGreen', 37, + RGB(0, 175, 175), + HLS(100, 180, 34), + 'LightSeaGreen', + 37, ) deep_sky_blue2 = Colors.register( - RGB(0, 175, 215), HLS(100, 91, 42), 'DeepSkyBlue2', 38, + RGB(0, 175, 215), + HLS(100, 91, 42), + 'DeepSkyBlue2', + 38, ) deep_sky_blue1 = Colors.register( - RGB(0, 175, 255), HLS(100, 98, 50), 'DeepSkyBlue1', 39, + RGB(0, 175, 255), + HLS(100, 98, 50), + 'DeepSkyBlue1', + 39, ) green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) spring_green3 = Colors.register( - RGB(0, 215, 95), HLS(100, 46, 42), 'SpringGreen3', 41, + RGB(0, 215, 95), + HLS(100, 46, 42), + 'SpringGreen3', + 41, ) spring_green2 = Colors.register( - RGB(0, 215, 135), HLS(100, 57, 42), 'SpringGreen2', 42, + RGB(0, 215, 135), + HLS(100, 57, 42), + 'SpringGreen2', + 42, ) cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) dark_turquoise = Colors.register( - RGB(0, 215, 215), HLS(100, 180, 42), 'DarkTurquoise', 44, + RGB(0, 215, 215), + HLS(100, 180, 42), + 'DarkTurquoise', + 44, ) turquoise2 = Colors.register( - RGB(0, 215, 255), HLS(100, 89, 50), 'Turquoise2', 45, + RGB(0, 215, 255), + HLS(100, 89, 50), + 'Turquoise2', + 45, ) green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) spring_green2 = Colors.register( - RGB(0, 255, 95), HLS(100, 42, 50), 'SpringGreen2', 47, + RGB(0, 255, 95), + HLS(100, 42, 50), + 'SpringGreen2', + 47, ) spring_green1 = Colors.register( - RGB(0, 255, 135), HLS(100, 51, 50), 'SpringGreen1', 48, + RGB(0, 255, 135), + HLS(100, 51, 50), + 'SpringGreen1', + 48, ) medium_spring_green = Colors.register( - RGB(0, 255, 175), HLS(100, 61, 50), 'MediumSpringGreen', 49, + RGB(0, 255, 175), + HLS(100, 61, 50), + 'MediumSpringGreen', + 49, ) cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) dark_red = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) -deep_pink4 = Colors.register(RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53) +deep_pink4 = Colors.register( + RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53 +) purple4 = Colors.register(RGB(95, 0, 135), HLS(100, 82, 26), 'Purple4', 54) purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) blue_violet = Colors.register( - RGB(95, 0, 255), HLS(100, 62, 50), 'BlueViolet', 57, + RGB(95, 0, 255), + HLS(100, 62, 50), + 'BlueViolet', + 57, ) orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) medium_purple4 = Colors.register( - RGB(95, 95, 135), HLS(17, 240, 45), 'MediumPurple4', 60, + RGB(95, 95, 135), + HLS(17, 240, 45), + 'MediumPurple4', + 60, ) slate_blue3 = Colors.register( - RGB(95, 95, 175), HLS(33, 240, 52), 'SlateBlue3', 61, + RGB(95, 95, 175), + HLS(33, 240, 52), + 'SlateBlue3', + 61, ) slate_blue3 = Colors.register( - RGB(95, 95, 215), HLS(60, 240, 60), 'SlateBlue3', 62, + RGB(95, 95, 215), + HLS(60, 240, 60), + 'SlateBlue3', + 62, ) royal_blue1 = Colors.register( - RGB(95, 95, 255), HLS(100, 240, 68), 'RoyalBlue1', 63, + RGB(95, 95, 255), + HLS(100, 240, 68), + 'RoyalBlue1', + 63, ) chartreuse4 = Colors.register( - RGB(95, 135, 0), HLS(100, 7, 26), 'Chartreuse4', 64, + RGB(95, 135, 0), + HLS(100, 7, 26), + 'Chartreuse4', + 64, ) dark_sea_green4 = Colors.register( - RGB(95, 135, 95), HLS(17, 120, 45), 'DarkSeaGreen4', 65, + RGB(95, 135, 95), + HLS(17, 120, 45), + 'DarkSeaGreen4', + 65, ) pale_turquoise4 = Colors.register( - RGB(95, 135, 135), HLS(17, 180, 45), 'PaleTurquoise4', 66, + RGB(95, 135, 135), + HLS(17, 180, 45), + 'PaleTurquoise4', + 66, ) steel_blue = Colors.register( - RGB(95, 135, 175), HLS(33, 210, 52), 'SteelBlue', 67, + RGB(95, 135, 175), + HLS(33, 210, 52), + 'SteelBlue', + 67, ) steel_blue3 = Colors.register( - RGB(95, 135, 215), HLS(60, 220, 60), 'SteelBlue3', 68, + RGB(95, 135, 215), + HLS(60, 220, 60), + 'SteelBlue3', + 68, ) cornflower_blue = Colors.register( - RGB(95, 135, 255), HLS(100, 225, 68), 'CornflowerBlue', 69, + RGB(95, 135, 255), + HLS(100, 225, 68), + 'CornflowerBlue', + 69, ) chartreuse3 = Colors.register( - RGB(95, 175, 0), HLS(100, 7, 34), 'Chartreuse3', 70, + RGB(95, 175, 0), + HLS(100, 7, 34), + 'Chartreuse3', + 70, ) dark_sea_green4 = Colors.register( - RGB(95, 175, 95), HLS(33, 120, 52), 'DarkSeaGreen4', 71, + RGB(95, 175, 95), + HLS(33, 120, 52), + 'DarkSeaGreen4', + 71, ) cadet_blue = Colors.register( - RGB(95, 175, 135), HLS(33, 150, 52), 'CadetBlue', 72, + RGB(95, 175, 135), + HLS(33, 150, 52), + 'CadetBlue', + 72, ) cadet_blue = Colors.register( - RGB(95, 175, 175), HLS(33, 180, 52), 'CadetBlue', 73, + RGB(95, 175, 175), + HLS(33, 180, 52), + 'CadetBlue', + 73, +) +sky_blue3 = Colors.register( + RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74 ) -sky_blue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) steel_blue1 = Colors.register( - RGB(95, 175, 255), HLS(100, 210, 68), 'SteelBlue1', 75, + RGB(95, 175, 255), + HLS(100, 210, 68), + 'SteelBlue1', + 75, ) chartreuse3 = Colors.register( - RGB(95, 215, 0), HLS(100, 3, 42), 'Chartreuse3', 76, + RGB(95, 215, 0), + HLS(100, 3, 42), + 'Chartreuse3', + 76, ) pale_green3 = Colors.register( - RGB(95, 215, 95), HLS(60, 120, 60), 'PaleGreen3', 77, + RGB(95, 215, 95), + HLS(60, 120, 60), + 'PaleGreen3', + 77, ) sea_green3 = Colors.register( - RGB(95, 215, 135), HLS(60, 140, 60), 'SeaGreen3', 78, + RGB(95, 215, 135), + HLS(60, 140, 60), + 'SeaGreen3', + 78, ) aquamarine3 = Colors.register( - RGB(95, 215, 175), HLS(60, 160, 60), 'Aquamarine3', 79, + RGB(95, 215, 175), + HLS(60, 160, 60), + 'Aquamarine3', + 79, ) medium_turquoise = Colors.register( - RGB(95, 215, 215), HLS(60, 180, 60), 'MediumTurquoise', 80, + RGB(95, 215, 215), + HLS(60, 180, 60), + 'MediumTurquoise', + 80, ) steel_blue1 = Colors.register( - RGB(95, 215, 255), HLS(100, 195, 68), 'SteelBlue1', 81, + RGB(95, 215, 255), + HLS(100, 195, 68), + 'SteelBlue1', + 81, ) chartreuse2 = Colors.register( - RGB(95, 255, 0), HLS(100, 7, 50), 'Chartreuse2', 82, + RGB(95, 255, 0), + HLS(100, 7, 50), + 'Chartreuse2', + 82, ) sea_green2 = Colors.register( - RGB(95, 255, 95), HLS(100, 120, 68), 'SeaGreen2', 83, + RGB(95, 255, 95), + HLS(100, 120, 68), + 'SeaGreen2', + 83, ) sea_green1 = Colors.register( - RGB(95, 255, 135), HLS(100, 135, 68), 'SeaGreen1', 84, + RGB(95, 255, 135), + HLS(100, 135, 68), + 'SeaGreen1', + 84, ) sea_green1 = Colors.register( - RGB(95, 255, 175), HLS(100, 150, 68), 'SeaGreen1', 85, + RGB(95, 255, 175), + HLS(100, 150, 68), + 'SeaGreen1', + 85, ) aquamarine1 = Colors.register( - RGB(95, 255, 215), HLS(100, 165, 68), 'Aquamarine1', 86, + RGB(95, 255, 215), + HLS(100, 165, 68), + 'Aquamarine1', + 86, ) dark_slate_gray2 = Colors.register( - RGB(95, 255, 255), HLS(100, 180, 68), 'DarkSlateGray2', 87, + RGB(95, 255, 255), + HLS(100, 180, 68), + 'DarkSlateGray2', + 87, ) dark_red = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) -deep_pink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) +deep_pink4 = Colors.register( + RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89 +) dark_magenta = Colors.register( - RGB(135, 0, 135), HLS(100, 300, 26), 'DarkMagenta', 90, + RGB(135, 0, 135), + HLS(100, 300, 26), + 'DarkMagenta', + 90, ) dark_magenta = Colors.register( - RGB(135, 0, 175), HLS(100, 86, 34), 'DarkMagenta', 91, + RGB(135, 0, 175), + HLS(100, 86, 34), + 'DarkMagenta', + 91, ) dark_violet = Colors.register( - RGB(135, 0, 215), HLS(100, 77, 42), 'DarkViolet', 92, + RGB(135, 0, 215), + HLS(100, 77, 42), + 'DarkViolet', + 92, ) purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) light_pink4 = Colors.register( - RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95, + RGB(135, 95, 95), + HLS(17, 0, 45), + 'LightPink4', + 95, ) plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) medium_purple3 = Colors.register( - RGB(135, 95, 175), HLS(33, 270, 52), 'MediumPurple3', 97, + RGB(135, 95, 175), + HLS(33, 270, 52), + 'MediumPurple3', + 97, ) medium_purple3 = Colors.register( - RGB(135, 95, 215), HLS(60, 260, 60), 'MediumPurple3', 98, + RGB(135, 95, 215), + HLS(60, 260, 60), + 'MediumPurple3', + 98, ) slate_blue1 = Colors.register( - RGB(135, 95, 255), HLS(100, 255, 68), 'SlateBlue1', 99, + RGB(135, 95, 255), + HLS(100, 255, 68), + 'SlateBlue1', + 99, ) yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) light_slate_grey = Colors.register( - RGB(135, 135, 175), HLS(20, 240, 60), 'LightSlateGrey', 103, + RGB(135, 135, 175), + HLS(20, 240, 60), + 'LightSlateGrey', + 103, ) medium_purple = Colors.register( - RGB(135, 135, 215), HLS(50, 240, 68), 'MediumPurple', 104, + RGB(135, 135, 215), + HLS(50, 240, 68), + 'MediumPurple', + 104, ) light_slate_blue = Colors.register( - RGB(135, 135, 255), HLS(100, 240, 76), 'LightSlateBlue', 105, + RGB(135, 135, 255), + HLS(100, 240, 76), + 'LightSlateBlue', + 105, ) yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) dark_olive_green3 = Colors.register( - RGB(135, 175, 95), HLS(33, 90, 52), 'DarkOliveGreen3', 107, + RGB(135, 175, 95), + HLS(33, 90, 52), + 'DarkOliveGreen3', + 107, ) dark_sea_green = Colors.register( - RGB(135, 175, 135), HLS(20, 120, 60), 'DarkSeaGreen', 108, + RGB(135, 175, 135), + HLS(20, 120, 60), + 'DarkSeaGreen', + 108, ) light_sky_blue3 = Colors.register( - RGB(135, 175, 175), HLS(20, 180, 60), 'LightSkyBlue3', 109, + RGB(135, 175, 175), + HLS(20, 180, 60), + 'LightSkyBlue3', + 109, ) light_sky_blue3 = Colors.register( - RGB(135, 175, 215), HLS(50, 210, 68), 'LightSkyBlue3', 110, + RGB(135, 175, 215), + HLS(50, 210, 68), + 'LightSkyBlue3', + 110, ) sky_blue2 = Colors.register( - RGB(135, 175, 255), HLS(100, 220, 76), 'SkyBlue2', 111, + RGB(135, 175, 255), + HLS(100, 220, 76), + 'SkyBlue2', + 111, ) chartreuse2 = Colors.register( - RGB(135, 215, 0), HLS(100, 2, 42), 'Chartreuse2', 112, + RGB(135, 215, 0), + HLS(100, 2, 42), + 'Chartreuse2', + 112, ) dark_olive_green3 = Colors.register( - RGB(135, 215, 95), HLS(60, 100, 60), 'DarkOliveGreen3', 113, + RGB(135, 215, 95), + HLS(60, 100, 60), + 'DarkOliveGreen3', + 113, ) pale_green3 = Colors.register( - RGB(135, 215, 135), HLS(50, 120, 68), 'PaleGreen3', 114, + RGB(135, 215, 135), + HLS(50, 120, 68), + 'PaleGreen3', + 114, ) dark_sea_green3 = Colors.register( - RGB(135, 215, 175), HLS(50, 150, 68), 'DarkSeaGreen3', 115, + RGB(135, 215, 175), + HLS(50, 150, 68), + 'DarkSeaGreen3', + 115, ) dark_slate_gray3 = Colors.register( - RGB(135, 215, 215), HLS(50, 180, 68), 'DarkSlateGray3', 116, + RGB(135, 215, 215), + HLS(50, 180, 68), + 'DarkSlateGray3', + 116, ) sky_blue1 = Colors.register( - RGB(135, 215, 255), HLS(100, 200, 76), 'SkyBlue1', 117, + RGB(135, 215, 255), + HLS(100, 200, 76), + 'SkyBlue1', + 117, ) chartreuse1 = Colors.register( - RGB(135, 255, 0), HLS(100, 8, 50), 'Chartreuse1', 118, + RGB(135, 255, 0), + HLS(100, 8, 50), + 'Chartreuse1', + 118, ) light_green = Colors.register( - RGB(135, 255, 95), HLS(100, 105, 68), 'LightGreen', 119, + RGB(135, 255, 95), + HLS(100, 105, 68), + 'LightGreen', + 119, ) light_green = Colors.register( - RGB(135, 255, 135), HLS(100, 120, 76), 'LightGreen', 120, + RGB(135, 255, 135), + HLS(100, 120, 76), + 'LightGreen', + 120, ) pale_green1 = Colors.register( - RGB(135, 255, 175), HLS(100, 140, 76), 'PaleGreen1', 121, + RGB(135, 255, 175), + HLS(100, 140, 76), + 'PaleGreen1', + 121, ) aquamarine1 = Colors.register( - RGB(135, 255, 215), HLS(100, 160, 76), 'Aquamarine1', 122, + RGB(135, 255, 215), + HLS(100, 160, 76), + 'Aquamarine1', + 122, ) dark_slate_gray1 = Colors.register( - RGB(135, 255, 255), HLS(100, 180, 76), 'DarkSlateGray1', 123, + RGB(135, 255, 255), + HLS(100, 180, 76), + 'DarkSlateGray1', + 123, ) red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) deep_pink4 = Colors.register( - RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125, + RGB(175, 0, 95), + HLS(100, 27, 34), + 'DeepPink4', + 125, ) medium_violet_red = Colors.register( - RGB(175, 0, 135), HLS(100, 13, 34), 'MediumVioletRed', 126, + RGB(175, 0, 135), + HLS(100, 13, 34), + 'MediumVioletRed', + 126, ) magenta3 = Colors.register( - RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127, + RGB(175, 0, 175), + HLS(100, 300, 34), + 'Magenta3', + 127, ) dark_violet = Colors.register( - RGB(175, 0, 215), HLS(100, 88, 42), 'DarkViolet', 128, + RGB(175, 0, 215), + HLS(100, 88, 42), + 'DarkViolet', + 128, ) purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) dark_orange3 = Colors.register( - RGB(175, 95, 0), HLS(100, 2, 34), 'DarkOrange3', 130, + RGB(175, 95, 0), + HLS(100, 2, 34), + 'DarkOrange3', + 130, +) +indian_red = Colors.register( + RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131 ) -indian_red = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) hot_pink3 = Colors.register( - RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132, + RGB(175, 95, 135), + HLS(33, 330, 52), + 'HotPink3', + 132, ) medium_orchid3 = Colors.register( - RGB(175, 95, 175), HLS(33, 300, 52), 'MediumOrchid3', 133, + RGB(175, 95, 175), + HLS(33, 300, 52), + 'MediumOrchid3', + 133, ) medium_orchid = Colors.register( - RGB(175, 95, 215), HLS(60, 280, 60), 'MediumOrchid', 134, + RGB(175, 95, 215), + HLS(60, 280, 60), + 'MediumOrchid', + 134, ) medium_purple2 = Colors.register( - RGB(175, 95, 255), HLS(100, 270, 68), 'MediumPurple2', 135, + RGB(175, 95, 255), + HLS(100, 270, 68), + 'MediumPurple2', + 135, ) dark_goldenrod = Colors.register( - RGB(175, 135, 0), HLS(100, 6, 34), 'DarkGoldenrod', 136, + RGB(175, 135, 0), + HLS(100, 6, 34), + 'DarkGoldenrod', + 136, ) light_salmon3 = Colors.register( - RGB(175, 135, 95), HLS(33, 30, 52), 'LightSalmon3', 137, + RGB(175, 135, 95), + HLS(33, 30, 52), + 'LightSalmon3', + 137, ) rosy_brown = Colors.register( - RGB(175, 135, 135), HLS(20, 0, 60), 'RosyBrown', 138, + RGB(175, 135, 135), + HLS(20, 0, 60), + 'RosyBrown', + 138, ) grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) medium_purple2 = Colors.register( - RGB(175, 135, 215), HLS(50, 270, 68), 'MediumPurple2', 140, + RGB(175, 135, 215), + HLS(50, 270, 68), + 'MediumPurple2', + 140, ) medium_purple1 = Colors.register( - RGB(175, 135, 255), HLS(100, 260, 76), 'MediumPurple1', 141, + RGB(175, 135, 255), + HLS(100, 260, 76), + 'MediumPurple1', + 141, ) gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) dark_khaki = Colors.register( - RGB(175, 175, 95), HLS(33, 60, 52), 'DarkKhaki', 143, + RGB(175, 175, 95), + HLS(33, 60, 52), + 'DarkKhaki', + 143, ) navajo_white3 = Colors.register( - RGB(175, 175, 135), HLS(20, 60, 60), 'NavajoWhite3', 144, + RGB(175, 175, 135), + HLS(20, 60, 60), + 'NavajoWhite3', + 144, ) grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) light_steel_blue3 = Colors.register( - RGB(175, 175, 215), HLS(33, 240, 76), 'LightSteelBlue3', 146, + RGB(175, 175, 215), + HLS(33, 240, 76), + 'LightSteelBlue3', + 146, ) light_steel_blue = Colors.register( - RGB(175, 175, 255), HLS(100, 240, 84), 'LightSteelBlue', 147, + RGB(175, 175, 255), + HLS(100, 240, 84), + 'LightSteelBlue', + 147, ) yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) dark_olive_green3 = Colors.register( - RGB(175, 215, 95), HLS(60, 80, 60), 'DarkOliveGreen3', 149, + RGB(175, 215, 95), + HLS(60, 80, 60), + 'DarkOliveGreen3', + 149, ) dark_sea_green3 = Colors.register( - RGB(175, 215, 135), HLS(50, 90, 68), 'DarkSeaGreen3', 150, + RGB(175, 215, 135), + HLS(50, 90, 68), + 'DarkSeaGreen3', + 150, ) dark_sea_green2 = Colors.register( - RGB(175, 215, 175), HLS(33, 120, 76), 'DarkSeaGreen2', 151, + RGB(175, 215, 175), + HLS(33, 120, 76), + 'DarkSeaGreen2', + 151, ) light_cyan3 = Colors.register( - RGB(175, 215, 215), HLS(33, 180, 76), 'LightCyan3', 152, + RGB(175, 215, 215), + HLS(33, 180, 76), + 'LightCyan3', + 152, ) light_sky_blue1 = Colors.register( - RGB(175, 215, 255), HLS(100, 210, 84), 'LightSkyBlue1', 153, + RGB(175, 215, 255), + HLS(100, 210, 84), + 'LightSkyBlue1', + 153, ) green_yellow = Colors.register( - RGB(175, 255, 0), HLS(100, 8, 50), 'GreenYellow', 154, + RGB(175, 255, 0), + HLS(100, 8, 50), + 'GreenYellow', + 154, ) dark_olive_green2 = Colors.register( - RGB(175, 255, 95), HLS(100, 90, 68), 'DarkOliveGreen2', 155, + RGB(175, 255, 95), + HLS(100, 90, 68), + 'DarkOliveGreen2', + 155, ) pale_green1 = Colors.register( - RGB(175, 255, 135), HLS(100, 100, 76), 'PaleGreen1', 156, + RGB(175, 255, 135), + HLS(100, 100, 76), + 'PaleGreen1', + 156, ) dark_sea_green2 = Colors.register( - RGB(175, 255, 175), HLS(100, 120, 84), 'DarkSeaGreen2', 157, + RGB(175, 255, 175), + HLS(100, 120, 84), + 'DarkSeaGreen2', + 157, ) dark_sea_green1 = Colors.register( - RGB(175, 255, 215), HLS(100, 150, 84), 'DarkSeaGreen1', 158, + RGB(175, 255, 215), + HLS(100, 150, 84), + 'DarkSeaGreen1', + 158, ) pale_turquoise1 = Colors.register( - RGB(175, 255, 255), HLS(100, 180, 84), 'PaleTurquoise1', 159, + RGB(175, 255, 255), + HLS(100, 180, 84), + 'PaleTurquoise1', + 159, ) red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) deep_pink3 = Colors.register( - RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161, + RGB(215, 0, 95), + HLS(100, 33, 42), + 'DeepPink3', + 161, ) deep_pink3 = Colors.register( - RGB(215, 0, 135), HLS(100, 22, 42), 'DeepPink3', 162, + RGB(215, 0, 135), + HLS(100, 22, 42), + 'DeepPink3', + 162, ) magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) magenta3 = Colors.register( - RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164, + RGB(215, 0, 215), + HLS(100, 300, 42), + 'Magenta3', + 164, ) magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) dark_orange3 = Colors.register( - RGB(215, 95, 0), HLS(100, 6, 42), 'DarkOrange3', 166, + RGB(215, 95, 0), + HLS(100, 6, 42), + 'DarkOrange3', + 166, +) +indian_red = Colors.register( + RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167 ) -indian_red = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) hot_pink3 = Colors.register( - RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168, + RGB(215, 95, 135), + HLS(60, 340, 60), + 'HotPink3', + 168, ) hot_pink2 = Colors.register( - RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169, + RGB(215, 95, 175), + HLS(60, 320, 60), + 'HotPink2', + 169, ) orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) medium_orchid1 = Colors.register( - RGB(215, 95, 255), HLS(100, 285, 68), 'MediumOrchid1', 171, + RGB(215, 95, 255), + HLS(100, 285, 68), + 'MediumOrchid1', + 171, ) orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) light_salmon3 = Colors.register( - RGB(215, 135, 95), HLS(60, 20, 60), 'LightSalmon3', 173, + RGB(215, 135, 95), + HLS(60, 20, 60), + 'LightSalmon3', + 173, ) light_pink3 = Colors.register( - RGB(215, 135, 135), HLS(50, 0, 68), 'LightPink3', 174, + RGB(215, 135, 135), + HLS(50, 0, 68), + 'LightPink3', + 174, ) pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) light_goldenrod3 = Colors.register( - RGB(215, 175, 95), HLS(60, 40, 60), 'LightGoldenrod3', 179, + RGB(215, 175, 95), + HLS(60, 40, 60), + 'LightGoldenrod3', + 179, ) tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) misty_rose3 = Colors.register( - RGB(215, 175, 175), HLS(33, 0, 76), 'MistyRose3', 181, + RGB(215, 175, 175), + HLS(33, 0, 76), + 'MistyRose3', + 181, ) thistle3 = Colors.register( - RGB(215, 175, 215), HLS(33, 300, 76), 'Thistle3', 182, + RGB(215, 175, 215), + HLS(33, 300, 76), + 'Thistle3', + 182, ) plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) light_goldenrod2 = Colors.register( - RGB(215, 215, 135), HLS(50, 60, 68), 'LightGoldenrod2', 186, + RGB(215, 215, 135), + HLS(50, 60, 68), + 'LightGoldenrod2', + 186, ) light_yellow3 = Colors.register( - RGB(215, 215, 175), HLS(33, 60, 76), 'LightYellow3', 187, + RGB(215, 215, 175), + HLS(33, 60, 76), + 'LightYellow3', + 187, ) grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) light_steel_blue1 = Colors.register( - RGB(215, 215, 255), HLS(100, 240, 92), 'LightSteelBlue1', 189, + RGB(215, 215, 255), + HLS(100, 240, 92), + 'LightSteelBlue1', + 189, ) yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) dark_olive_green1 = Colors.register( - RGB(215, 255, 95), HLS(100, 75, 68), 'DarkOliveGreen1', 191, + RGB(215, 255, 95), + HLS(100, 75, 68), + 'DarkOliveGreen1', + 191, ) dark_olive_green1 = Colors.register( - RGB(215, 255, 135), HLS(100, 80, 76), 'DarkOliveGreen1', 192, + RGB(215, 255, 135), + HLS(100, 80, 76), + 'DarkOliveGreen1', + 192, ) dark_sea_green1 = Colors.register( - RGB(215, 255, 175), HLS(100, 90, 84), 'DarkSeaGreen1', 193, + RGB(215, 255, 175), + HLS(100, 90, 84), + 'DarkSeaGreen1', + 193, ) honeydew2 = Colors.register( - RGB(215, 255, 215), HLS(100, 120, 92), 'Honeydew2', 194, + RGB(215, 255, 215), + HLS(100, 120, 92), + 'Honeydew2', + 194, ) light_cyan1 = Colors.register( - RGB(215, 255, 255), HLS(100, 180, 92), 'LightCyan1', 195, + RGB(215, 255, 255), + HLS(100, 180, 92), + 'LightCyan1', + 195, ) red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) deep_pink2 = Colors.register( - RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197, + RGB(255, 0, 95), + HLS(100, 37, 50), + 'DeepPink2', + 197, ) deep_pink1 = Colors.register( - RGB(255, 0, 135), HLS(100, 28, 50), 'DeepPink1', 198, + RGB(255, 0, 135), + HLS(100, 28, 50), + 'DeepPink1', + 198, ) deep_pink1 = Colors.register( - RGB(255, 0, 175), HLS(100, 18, 50), 'DeepPink1', 199, + RGB(255, 0, 175), + HLS(100, 18, 50), + 'DeepPink1', + 199, ) magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) magenta1 = Colors.register( - RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201, + RGB(255, 0, 255), + HLS(100, 300, 50), + 'Magenta1', + 201, ) orange_red1 = Colors.register( - RGB(255, 95, 0), HLS(100, 2, 50), 'OrangeRed1', 202, + RGB(255, 95, 0), + HLS(100, 2, 50), + 'OrangeRed1', + 202, ) indian_red1 = Colors.register( - RGB(255, 95, 95), HLS(100, 0, 68), 'IndianRed1', 203, + RGB(255, 95, 95), + HLS(100, 0, 68), + 'IndianRed1', + 203, ) indian_red1 = Colors.register( - RGB(255, 95, 135), HLS(100, 345, 68), 'IndianRed1', 204, + RGB(255, 95, 135), + HLS(100, 345, 68), + 'IndianRed1', + 204, +) +hot_pink = Colors.register( + RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205 +) +hot_pink = Colors.register( + RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206 ) -hot_pink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) -hot_pink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) medium_orchid1 = Colors.register( - RGB(255, 95, 255), HLS(100, 300, 68), 'MediumOrchid1', 207, + RGB(255, 95, 255), + HLS(100, 300, 68), + 'MediumOrchid1', + 207, ) dark_orange = Colors.register( - RGB(255, 135, 0), HLS(100, 1, 50), 'DarkOrange', 208, + RGB(255, 135, 0), + HLS(100, 1, 50), + 'DarkOrange', + 208, ) salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) light_coral = Colors.register( - RGB(255, 135, 135), HLS(100, 0, 76), 'LightCoral', 210, + RGB(255, 135, 135), + HLS(100, 0, 76), + 'LightCoral', + 210, ) pale_violet_red1 = Colors.register( - RGB(255, 135, 175), HLS(100, 340, 76), 'PaleVioletRed1', 211, + RGB(255, 135, 175), + HLS(100, 340, 76), + 'PaleVioletRed1', + 211, ) orchid2 = Colors.register( - RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212, + RGB(255, 135, 215), + HLS(100, 320, 76), + 'Orchid2', + 212, ) orchid1 = Colors.register( - RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213, + RGB(255, 135, 255), + HLS(100, 300, 76), + 'Orchid1', + 213, ) orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) sandy_brown = Colors.register( - RGB(255, 175, 95), HLS(100, 30, 68), 'SandyBrown', 215, + RGB(255, 175, 95), + HLS(100, 30, 68), + 'SandyBrown', + 215, ) light_salmon1 = Colors.register( - RGB(255, 175, 135), HLS(100, 20, 76), 'LightSalmon1', 216, + RGB(255, 175, 135), + HLS(100, 20, 76), + 'LightSalmon1', + 216, ) light_pink1 = Colors.register( - RGB(255, 175, 175), HLS(100, 0, 84), 'LightPink1', 217, + RGB(255, 175, 175), + HLS(100, 0, 84), + 'LightPink1', + 217, ) pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) light_goldenrod2 = Colors.register( - RGB(255, 215, 95), HLS(100, 45, 68), 'LightGoldenrod2', 221, + RGB(255, 215, 95), + HLS(100, 45, 68), + 'LightGoldenrod2', + 221, ) light_goldenrod2 = Colors.register( - RGB(255, 215, 135), HLS(100, 40, 76), 'LightGoldenrod2', 222, + RGB(255, 215, 135), + HLS(100, 40, 76), + 'LightGoldenrod2', + 222, ) navajo_white1 = Colors.register( - RGB(255, 215, 175), HLS(100, 30, 84), 'NavajoWhite1', 223, + RGB(255, 215, 175), + HLS(100, 30, 84), + 'NavajoWhite1', + 223, ) misty_rose1 = Colors.register( - RGB(255, 215, 215), HLS(100, 0, 92), 'MistyRose1', 224, + RGB(255, 215, 215), + HLS(100, 0, 92), + 'MistyRose1', + 224, ) thistle1 = Colors.register( - RGB(255, 215, 255), HLS(100, 300, 92), 'Thistle1', 225, + RGB(255, 215, 255), + HLS(100, 300, 92), + 'Thistle1', + 225, ) yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) light_goldenrod1 = Colors.register( - RGB(255, 255, 95), HLS(100, 60, 68), 'LightGoldenrod1', 227, + RGB(255, 255, 95), + HLS(100, 60, 68), + 'LightGoldenrod1', + 227, ) khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) cornsilk1 = Colors.register( - RGB(255, 255, 215), HLS(100, 60, 92), 'Cornsilk1', 230, + RGB(255, 255, 215), + HLS(100, 60, 92), + 'Cornsilk1', + 230, ) grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) @@ -588,17 +1046,3 @@ # Default, expect a dark background gradient = dark_gradient primary = white - -if __name__ == '__main__': - red = Colors.register(RGB(255, 128, 128)) - # red = Colors.register(RGB(255, 100, 100)) - from progressbar.terminal import base - - for i in base.ColorSupport: - base.color_support = i - print( # noqa: T201 - i, - red.fg('RED!'), - red.bg('RED!'), - red.underline('RED!'), - ) diff --git a/progressbar/terminal/os_specific/__init__.py b/progressbar/terminal/os_specific/__init__.py index 3d27cf5c..e036597f 100644 --- a/progressbar/terminal/os_specific/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -1,3 +1,4 @@ +__test__ = False import sys if sys.platform.startswith('win'): diff --git a/progressbar/terminal/os_specific/windows.py b/progressbar/terminal/os_specific/windows.py index 0342efbb..fd19ad51 100644 --- a/progressbar/terminal/os_specific/windows.py +++ b/progressbar/terminal/os_specific/windows.py @@ -17,7 +17,7 @@ WORD as _WORD, ) -_kernel32 = ctypes.windll.Kernel32 +_kernel32 = ctypes.windll.Kernel32 # type: ignore _ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 _ENABLE_PROCESSED_OUTPUT = 0x0001 @@ -54,7 +54,7 @@ class _COORD(ctypes.Structure): class _FOCUS_EVENT_RECORD(ctypes.Structure): - _fields_ = (('bSetFocus', _BOOL)) + _fields_ = ('bSetFocus', _BOOL) class _KEY_EVENT_RECORD(ctypes.Structure): @@ -72,7 +72,7 @@ class _uchar(ctypes.Union): class _MENU_EVENT_RECORD(ctypes.Structure): - _fields_ = (('dwCommandId', _UINT)) + _fields_ = ('dwCommandId', _UINT) class _MOUSE_EVENT_RECORD(ctypes.Structure): @@ -85,7 +85,7 @@ class _MOUSE_EVENT_RECORD(ctypes.Structure): class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): - _fields_ = (('dwSize', _COORD)) + _fields_ = ('dwSize', _COORD) class _INPUT_RECORD(ctypes.Structure): diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index fcd53d22..5204e90b 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -32,10 +32,10 @@ def readable(self) -> bool: def readline(self, __limit: int = -1) -> str: return self.stream.readline(__limit) - def readlines(self, __hint: int = ...) -> list[str]: + def readlines(self, __hint: int = -1) -> list[str]: return self.stream.readlines(__hint) - def seek(self, __offset: int, __whence: int = ...) -> int: + def seek(self, __offset: int, __whence: int = 0) -> int: return self.stream.seek(__offset, __whence) def seekable(self) -> bool: @@ -44,7 +44,7 @@ def seekable(self) -> bool: def tell(self) -> int: return self.stream.tell() - def truncate(self, __size: int | None = ...) -> int: + def truncate(self, __size: int | None = None) -> int: return self.stream.truncate(__size) def writable(self) -> bool: @@ -121,7 +121,7 @@ def truncate(self, __size: int | None = None) -> int: def writelines(self, __lines: Iterable[str]) -> None: line = '' # Walk through the lines and take the last one - for _ in __lines: + for line in __lines: pass self.line = line diff --git a/progressbar/utils.py b/progressbar/utils.py index b9f45f4e..7e325566 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -10,6 +10,7 @@ import sys from types import TracebackType from typing import Iterable, Iterator +import typing from python_utils import types from python_utils.converters import scale_1024 @@ -21,11 +22,12 @@ if types.TYPE_CHECKING: from .bar import ProgressBar, ProgressBarMixinBase -assert timedelta_to_seconds -assert get_terminal_size -assert format_time -assert scale_1024 -assert epoch +# Make sure these are available for import +assert timedelta_to_seconds is not None +assert get_terminal_size is not None +assert format_time is not None +assert scale_1024 is not None +assert epoch is not None StringT = types.TypeVar('StringT', bound=types.StringTypes) @@ -44,7 +46,8 @@ def is_ansi_terminal( - fd: base.IO, is_terminal: bool | None = None, + fd: base.IO, + is_terminal: bool | None = None, ) -> bool: # pragma: no cover if is_terminal is None: # Jupyter Notebooks define this variable and support progress bars @@ -182,7 +185,17 @@ def len_color(value: types.StringTypes) -> int: return len(no_color(value)) +@typing.overload +def env_flag(name: str, default: bool) -> bool: + ... + + +@typing.overload def env_flag(name: str, default: bool | None = None) -> bool | None: + ... + + +def env_flag(name, default=None): ''' Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, on/off, and returns it as a boolean. @@ -247,7 +260,7 @@ def _flush(self) -> None: self.flush_target() def flush_target(self) -> None: # pragma: no cover - if not self.target.closed and self.target.flush: + if not self.target.closed and getattr(self.target, 'flush', None): self.target.flush() def __enter__(self) -> WrappingIO: @@ -360,7 +373,6 @@ def stop_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: with contextlib.suppress(KeyError): self.listeners.remove(bar) - self.capturing -= 1 self.update_capturing() @@ -386,7 +398,8 @@ def wrap_stdout(self) -> WrappingIO: if not self.wrapped_stdout: self.stdout = sys.stdout = WrappingIO( # type: ignore - self.original_stdout, listeners=self.listeners, + self.original_stdout, + listeners=self.listeners, ) self.wrapped_stdout += 1 @@ -397,7 +410,8 @@ def wrap_stderr(self) -> WrappingIO: if not self.wrapped_stderr: self.stderr = sys.stderr = WrappingIO( # type: ignore - self.original_stderr, listeners=self.listeners, + self.original_stderr, + listeners=self.listeners, ) self.wrapped_stderr += 1 diff --git a/progressbar/widgets.py b/progressbar/widgets.py index f1834531..b5460e5f 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -13,6 +13,7 @@ from typing import ClassVar from python_utils import converters, types +from python_utils.containers import SliceableDeque from . import base, terminal, utils from .terminal import colors @@ -32,39 +33,6 @@ T = typing.TypeVar('T') -class SliceableDeque(typing.Generic[T], deque): - def __getitem__( - self, - index: int | slice, - ) -> T | deque[T]: - if isinstance(index, slice): - start, stop, step = index.indices(len(self)) - return self.__class__(self[i] for i in range(start, stop, step)) - else: - return super().__getitem__(index) - - def pop(self, index=-1) -> T: - # We need to allow for an index but a deque only allows the removal of - # the first or last item. - if index == 0: - return super().popleft() - elif index in {-1, len(self) - 1}: - return super().pop() - else: - raise IndexError( - 'Only index 0 and the last index (`N-1` or `-1`) are supported', - ) - - def __eq__(self, other): - # Allow for comparison with a list or tuple - if isinstance(other, list): - return list(self) == other - elif isinstance(other, tuple): - return tuple(self) == other - else: - return super().__eq__(other) - - def string_or_lambda(input_): if isinstance(input_, str): diff --git a/pyproject.toml b/pyproject.toml index 20d0ef9f..bdc5578d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ classifiers = [ description = 'A Python Progressbar library to provide visual (yet text based) progress to long running operations.' readme = 'README.rst' -dependencies = ['python-utils >= 3.8.0'] +dependencies = ['python-utils >= 3.8.1'] [tool.setuptools.dynamic] version = { attr = 'progressbar.__about__.__version__' } diff --git a/pytest.ini b/pytest.ini index d6a47d53..6f47a01a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -11,15 +11,13 @@ addopts = --doctest-modules norecursedirs = - .svn - _build - tmp* - docs + .* + _* build dist - .ropeproject - .tox + docs progressbar/terminal/os_specific + tmp* filterwarnings = ignore::DeprecationWarning From 7f00648ecaf8f230840af25fda0993a3ed36ded2 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 26 Sep 2023 14:48:23 +0200 Subject: [PATCH 103/118] Ruff and pytest are finally happy --- progressbar/bar.py | 6 +- progressbar/multi.py | 8 +- progressbar/terminal/colors.py | 14 +- progressbar/terminal/os_specific/__init__.py | 1 - progressbar/terminal/stream.py | 2 +- progressbar/utils.py | 2 +- progressbar/widgets.py | 503 +++++++++++-------- ruff.toml | 1 - tests/test_color.py | 6 +- tests/test_custom_widgets.py | 3 +- tests/test_samples.py | 3 +- 11 files changed, 306 insertions(+), 243 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index d502964e..c3f56d33 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -279,7 +279,7 @@ def update(self, *args: types.Any, **kwargs: types.Any) -> None: self.fd.write(line.encode('ascii', 'replace')) def finish( - self, *args: types.Any, **kwargs: types.Any + self, *args: types.Any, **kwargs: types.Any, ) -> None: # pragma: no cover if self._finished: return @@ -873,13 +873,13 @@ def update(self, value=None, force=False, **kwargs): elif self.min_value > value: # type: ignore raise ValueError( f'Value {value} is too small. Should be ' - f'between {self.min_value} and {self.max_value}' + f'between {self.min_value} and {self.max_value}', ) elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( f'Value {value} is too large. Should be between ' - f'{self.min_value} and {self.max_value}' + f'{self.min_value} and {self.max_value}', ) else: value = self.max_value diff --git a/progressbar/multi.py b/progressbar/multi.py index d40e5516..679dc537 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -10,10 +10,8 @@ import timeit import typing from datetime import timedelta -from typing import List, Any import python_utils -from python_utils import decorators from . import bar, terminal from .terminal import stream @@ -132,7 +130,7 @@ def __setitem__(self, key: str, bar: bar.ProgressBar): bar.fd = stream.LastLineStream(self.fd) bar.paused = True # Essentially `bar.print = self.print`, but `mypy` doesn't like that - setattr(bar, 'print', self.print) + bar.print = self.print # Just in case someone is using a progressbar with a custom # constructor and forgot to call the super constructor @@ -182,7 +180,7 @@ def render(self, flush: bool = True, force: bool = False): continue output.extend( - iter(self._render_bar(bar_, expired=expired, now=now)) + iter(self._render_bar(bar_, expired=expired, now=now)), ) with self._print_lock: @@ -218,7 +216,7 @@ def render(self, flush: bool = True, force: bool = False): self.flush() def _render_bar( - self, bar_: bar.ProgressBar, now, expired + self, bar_: bar.ProgressBar, now, expired, ) -> typing.Iterable[str]: def update(force=True, write=True): self._label_bar(bar_) diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index fbed929d..f65e874d 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -162,7 +162,7 @@ cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) dark_red = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) deep_pink4 = Colors.register( - RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53 + RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53, ) purple4 = Colors.register(RGB(95, 0, 135), HLS(100, 82, 26), 'Purple4', 54) purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) @@ -260,7 +260,7 @@ 73, ) sky_blue3 = Colors.register( - RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74 + RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74, ) steel_blue1 = Colors.register( RGB(95, 175, 255), @@ -342,7 +342,7 @@ ) dark_red = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) deep_pink4 = Colors.register( - RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89 + RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89, ) dark_magenta = Colors.register( RGB(135, 0, 135), @@ -546,7 +546,7 @@ 130, ) indian_red = Colors.register( - RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131 + RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131, ) hot_pink3 = Colors.register( RGB(175, 95, 135), @@ -724,7 +724,7 @@ 166, ) indian_red = Colors.register( - RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167 + RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167, ) hot_pink3 = Colors.register( RGB(215, 95, 135), @@ -879,10 +879,10 @@ 204, ) hot_pink = Colors.register( - RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205 + RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205, ) hot_pink = Colors.register( - RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206 + RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206, ) medium_orchid1 = Colors.register( RGB(255, 95, 255), diff --git a/progressbar/terminal/os_specific/__init__.py b/progressbar/terminal/os_specific/__init__.py index e036597f..3d27cf5c 100644 --- a/progressbar/terminal/os_specific/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -1,4 +1,3 @@ -__test__ = False import sys if sys.platform.startswith('win'): diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index 5204e90b..a2fbe2fc 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -121,7 +121,7 @@ def truncate(self, __size: int | None = None) -> int: def writelines(self, __lines: Iterable[str]) -> None: line = '' # Walk through the lines and take the last one - for line in __lines: + for line in __lines: # noqa: B007 pass self.line = line diff --git a/progressbar/utils.py b/progressbar/utils.py index 7e325566..83116c84 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -8,9 +8,9 @@ import os import re import sys +import typing from types import TracebackType from typing import Iterable, Iterator -import typing from python_utils import types from python_utils.converters import scale_1024 diff --git a/progressbar/widgets.py b/progressbar/widgets.py index b5460e5f..a317d907 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -6,14 +6,12 @@ import functools import logging import typing -from collections import deque # Ruff is being stupid and doesn't understand `ClassVar` if it comes from the # `types` module from typing import ClassVar -from python_utils import converters, types -from python_utils.containers import SliceableDeque +from python_utils import containers, converters, types from . import base, terminal, utils from .terminal import colors @@ -91,8 +89,8 @@ def wrap(*args, **kwargs): def create_marker(marker, wrap=None): def _marker(progress, data, width): if ( - progress.max_value is not base.UnknownLength - and progress.max_value > 0 + progress.max_value is not base.UnknownLength + and progress.max_value > 0 ): length = int(progress.value / progress.max_value * width) return marker * length @@ -102,7 +100,7 @@ def _marker(progress, data, width): if isinstance(marker, str): marker = converters.to_unicode(marker) assert ( - utils.len_color(marker) == 1 + utils.len_color(marker) == 1 ), 'Markers are required to be 1 char' return wrapper(_marker, wrap) else: @@ -130,18 +128,18 @@ def __init__(self, format: str, new_style: bool = False, **kwargs): self.format = format def get_format( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: return format or self.format def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: '''Formats the widget into a string.''' format_ = self.get_format(progress, data, format) @@ -151,8 +149,9 @@ def __call__( else: return format_ % data except (TypeError, KeyError): - logger.exception('Error while formatting %r with data: %r', - format_, data) + logger.exception( + 'Error while formatting %r with data: %r', format_, data, + ) raise @@ -196,6 +195,16 @@ def check_size(self, progress: ProgressBarMixinBase): return True +class TGradientColors(typing.TypedDict): + fg: types.Optional[terminal.OptionalColor | None] + bg: types.Optional[terminal.OptionalColor | None] + + +class TFixedColors(typing.TypedDict): + fg_none: types.Optional[terminal.Color | None] + bg_none: types.Optional[terminal.Color | None] + + class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): '''The base class for all widgets. @@ -234,9 +243,15 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: progress - a reference to the calling ProgressBar ''' - _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict() - _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | None]] = ( - dict()) + _fixed_colors: ClassVar[TFixedColors] = TFixedColors( + fg_none=None, bg_none=None, + ) + _gradient_colors: ClassVar[TGradientColors] = TGradientColors( + fg=None, bg=None, + ) + # _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict() + # _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | None]] = ( + # dict()) _len: typing.Callable[[str | bytes], int] = len @functools.cached_property @@ -259,7 +274,11 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, *args, fixed_colors=None, gradient_colors=None, **kwargs, + self, + *args, + fixed_colors=None, + gradient_colors=None, + **kwargs, ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -283,10 +302,10 @@ class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): @abc.abstractmethod def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ) -> str: '''Updates the widget providing the total width the widget must fill. @@ -332,10 +351,10 @@ def __init__(self, format: str, **kwargs): WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): for name, (key, transform) in self.mapping.items(): with contextlib.suppress(KeyError, ValueError, IndexError): @@ -394,30 +413,30 @@ class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): ''' def __init__( - self, - samples=datetime.timedelta(seconds=2), - key_prefix=None, - **kwargs, + self, + samples=datetime.timedelta(seconds=2), + key_prefix=None, + **kwargs, ): self.samples = samples - self.key_prefix = ( - key_prefix or self.__class__.__name__ - ) + '_' + self.key_prefix = (key_prefix or self.__class__.__name__) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault( - f'{self.key_prefix}sample_times', SliceableDeque(), + f'{self.key_prefix}sample_times', containers.SliceableDeque(), ) def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault( - f'{self.key_prefix}sample_values', SliceableDeque(), + f'{self.key_prefix}sample_values', containers.SliceableDeque(), ) def __call__( - self, progress: ProgressBarMixinBase, data: Data, - delta: bool = False, + self, + progress: ProgressBarMixinBase, + data: Data, + delta: bool = False, ): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -436,9 +455,9 @@ def __call__( minimum_time = progress.last_update_time - self.samples minimum_value = sample_values[-1] while ( - sample_times[2:] - and minimum_time > sample_times[1] - and minimum_value > sample_values[1] + sample_times[2:] + and minimum_time > sample_times[1] + and minimum_value > sample_values[1] ): sample_times.pop(0) sample_values.pop(0) @@ -460,13 +479,13 @@ class ETA(Timer): '''WidgetBase which attempts to estimate the time of arrival.''' def __init__( - self, - format_not_started='ETA: --:--:--', - format_finished='Time: %(elapsed)8s', - format='ETA: %(eta)8s', - format_zero='ETA: 00:00:00', - format_na='ETA: N/A', - **kwargs, + self, + format_not_started='ETA: --:--:--', + format_finished='Time: %(elapsed)8s', + format='ETA: %(eta)8s', + format_zero='ETA: 00:00:00', + format_na='ETA: N/A', + **kwargs, ): if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -479,7 +498,11 @@ def __init__( self.format_NA = format_na def _calculate_eta( - self, progress: ProgressBarMixinBase, data: Data, value, elapsed, + self, + progress: ProgressBarMixinBase, + data: Data, + value, + elapsed, ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: @@ -491,11 +514,11 @@ def _calculate_eta( return 0 def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): '''Updates the widget to show the ETA or total time when finished.''' if value is None: @@ -507,7 +530,10 @@ def __call__( eta_na = False try: data['eta_seconds'] = self._calculate_eta( - progress, data, value=value, elapsed=elapsed, + progress, + data, + value=value, + elapsed=elapsed, ) except TypeError: data['eta_seconds'] = None @@ -536,7 +562,11 @@ class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' def _calculate_eta( - self, progress: ProgressBarMixinBase, data: Data, value, elapsed, + self, + progress: ProgressBarMixinBase, + data: Data, + value, + elapsed, ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() @@ -546,11 +576,11 @@ def _calculate_eta( return datetime.datetime.max def __init__( - self, - format_not_started='Estimated finish time: ----/--/-- --:--:--', - format_finished='Finished at: %(elapsed)s', - format='Estimated finish time: %(eta)s', - **kwargs, + self, + format_not_started='Estimated finish time: ----/--/-- --:--:--', + format_finished='Finished at: %(elapsed)s', + format='Estimated finish time: %(eta)s', + **kwargs, ): ETA.__init__( self, @@ -573,14 +603,17 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): elapsed, value = SamplesMixin.__call__( - self, progress, data, delta=True, + self, + progress, + data, + delta=True, ) if not elapsed: value = None @@ -598,12 +631,12 @@ class DataSize(FormatWidgetMixin, WidgetBase): ''' def __init__( - self, - variable='value', - format='%(scaled)5.1f %(prefix)s%(unit)s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + variable='value', + format='%(scaled)5.1f %(prefix)s%(unit)s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.variable = variable self.unit = unit @@ -612,10 +645,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data[self.variable] if value is not None: @@ -636,12 +669,12 @@ class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' def __init__( - self, - format='%(scaled)5.1f %(prefix)s%(unit)-s/s', - inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + format='%(scaled)5.1f %(prefix)s%(unit)-s/s', + inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.unit = unit self.prefixes = prefixes @@ -654,25 +687,26 @@ def _speed(self, value, elapsed): return utils.scale_1024(speed, len(self.prefixes)) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): '''Updates the widget with the current SI prefixed speed.''' if value is None: value = data['value'] elapsed = utils.deltas_to_seconds( - total_seconds_elapsed, data['total_seconds_elapsed'], + total_seconds_elapsed, + data['total_seconds_elapsed'], ) if ( - value is not None - and elapsed is not None - and elapsed > 2e-6 - and value > 2e-6 + value is not None + and elapsed is not None + and elapsed > 2e-6 + and value > 2e-6 ): # =~ 0 scaled, power = self._speed(value, elapsed) else: @@ -685,7 +719,10 @@ def __call__( data['scaled'] = scaled data['prefix'] = self.prefixes[0] return FormatWidgetMixin.__call__( - self, progress, data, self.inverse_format, + self, + progress, + data, + self.inverse_format, ) else: data['scaled'] = scaled @@ -701,14 +738,17 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( - self, progress, data, delta=True, + self, + progress, + data, + delta=True, ) return FileTransferSpeed.__call__(self, progress, data, value, elapsed) @@ -719,13 +759,13 @@ class AnimatedMarker(TimeSensitiveWidgetBase): ''' def __init__( - self, - markers='|/-\\', - default=None, - fill='', - marker_wrap=None, - fill_wrap=None, - **kwargs, + self, + markers='|/-\\', + default=None, + fill='', + marker_wrap=None, + fill_wrap=None, + **kwargs, ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) @@ -778,17 +818,26 @@ def __init__(self, format='%(value)d', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def __call__( - self, progress: ProgressBarMixinBase, data: Data, format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): return FormatWidgetMixin.__call__(self, progress, data, format) class ColoredMixin: - _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict( - fg_none=colors.yellow, bg_none=None) - _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | - None]] = dict(fg=colors.gradient, - bg=None) + _fixed_colors: ClassVar[TFixedColors] = TFixedColors( + fg_none=colors.yellow, bg_none=None, + ) + _gradient_colors: ClassVar[TGradientColors] = TGradientColors( + fg=colors.gradient, bg=None, + ) + # _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict( + # fg_none=colors.yellow, bg_none=None) + # _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | + # None]] = dict(fg=colors.gradient, + # bg=None) class Percentage(FormatWidgetMixin, ColoredMixin, WidgetBase): @@ -800,7 +849,10 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def get_format( - self, progress: ProgressBarMixinBase, data: Data, format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) @@ -828,7 +880,10 @@ def __init__(self, format=DEFAULT_FORMAT, **kwargs): self.max_width_cache = dict(default=self.max_width or 0) def __call__( - self, progress: ProgressBarMixinBase, data: Data, format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): # If max_value is not available, display N/A if data.get('max_value'): @@ -843,13 +898,17 @@ def __call__( data['value_s'] = 0 formatted = FormatWidgetMixin.__call__( - self, progress, data, format=format, + self, + progress, + data, + format=format, ) # Guess the maximum width from the min and max value key = progress.min_value, progress.max_value max_width: types.Optional[int] = self.max_width_cache.get( - key, self.max_width, + key, + self.max_width, ) if not max_width: temporary_data = data.copy() @@ -859,12 +918,12 @@ def __call__( temporary_data['value'] = value if width := progress.custom_len( - FormatWidgetMixin.__call__( - self, - progress, - temporary_data, - format=format, - ), + FormatWidgetMixin.__call__( + self, + progress, + temporary_data, + format=format, + ), ): max_width = max(max_width or 0, width) @@ -884,14 +943,14 @@ class Bar(AutoWidthWidgetBase): bg: terminal.OptionalColor | None = None def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=True, - marker_wrap=None, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=True, + marker_wrap=None, + **kwargs, ): '''Creates a customizable progress bar. @@ -912,11 +971,11 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -943,13 +1002,13 @@ class ReverseBar(Bar): '''A bar which has a marker that goes from right to left.''' def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=False, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=False, + **kwargs, ): '''Creates a customizable progress bar. @@ -976,10 +1035,11 @@ class BouncingBar(Bar, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(milliseconds=100) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1012,10 +1072,10 @@ class FormatCustomText(FormatWidgetMixin, WidgetBase): copy = False def __init__( - self, - format: str, - mapping: types.Optional[types.Dict[str, types.Any]] = None, - **kwargs, + self, + format: str, + mapping: types.Optional[types.Dict[str, types.Any]] = None, + **kwargs, ): self.format = format self.mapping = mapping or self.mapping @@ -1026,13 +1086,16 @@ def update_mapping(self, **mapping: types.Dict[str, types.Any]): self.mapping.update(mapping) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): return FormatWidgetMixin.__call__( - self, progress, self.mapping, format or self.format, + self, + progress, + self.mapping, + format or self.format, ) @@ -1071,10 +1134,11 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): return data['variables'][self.name] or [] def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1106,12 +1170,12 @@ def __call__( class MultiProgressBar(MultiRangeBar): def __init__( - self, - name, - # NOTE: the markers are not whitespace even though some - # terminals don't show the characters correctly! - markers=' ▁▂▃▄▅▆▇█', - **kwargs, + self, + name, + # NOTE: the markers are not whitespace even though some + # terminals don't show the characters correctly! + markers=' ▁▂▃▄▅▆▇█', + **kwargs, ): MultiRangeBar.__init__( self, @@ -1130,7 +1194,8 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): if not 0 <= value <= 1: raise ValueError( - f'Range value needs to be in the range [0..1], got {value}') + f'Range value needs to be in the range [0..1], got {value}', + ) range_ = value * (len(ranges) - 1) pos = int(range_) @@ -1171,11 +1236,11 @@ class GranularBar(AutoWidthWidgetBase): ''' def __init__( - self, - markers=GranularMarkers.smooth, - left='|', - right='|', - **kwargs, + self, + markers=GranularMarkers.smooth, + left='|', + right='|', + **kwargs, ): '''Creates a customizable progress bar. @@ -1192,10 +1257,10 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) @@ -1205,8 +1270,8 @@ def __call__( # mypy doesn't get that the first part of the if statement makes sure # we get the correct type if ( - max_value is not base.UnknownLength - and max_value > 0 # type: ignore + max_value is not base.UnknownLength + and max_value > 0 # type: ignore ): percent = progress.value / max_value # type: ignore else: @@ -1236,11 +1301,11 @@ def __init__(self, format, **kwargs): Bar.__init__(self, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) bar = Bar.__call__(self, progress, data, width, color=False) @@ -1251,18 +1316,18 @@ def __call__( # type: ignore center_right = center_left + center_len return ( - self._apply_colors( - bar[:center_left], - data, - ) - + self._apply_colors( - center, - data, - ) - + self._apply_colors( - bar[center_right:], - data, - ) + self._apply_colors( + bar[:center_left], + data, + ) + + self._apply_colors( + center, + data, + ) + + self._apply_colors( + bar[center_right:], + data, + ) ) @@ -1276,11 +1341,11 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): FormatLabelBar.__init__(self, format, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): return super().__call__(progress, data, width, format=format) @@ -1289,12 +1354,12 @@ class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' def __init__( - self, - name, - format='{name}: {formatted_value}', - width=6, - precision=3, - **kwargs, + self, + name, + format='{name}: {formatted_value}', + width=6, + precision=3, + **kwargs, ): '''Creates a Variable associated with the given name.''' self.format = format @@ -1304,10 +1369,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data['variables'][self.name] context = data.copy() @@ -1343,20 +1408,20 @@ class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(seconds=1) def __init__( - self, - format='Current Time: %(current_time)s', - microseconds=False, - **kwargs, + self, + format='Current Time: %(current_time)s', + microseconds=False, + **kwargs, ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): data['current_time'] = self.current_time() data['current_datetime'] = self.current_datetime() diff --git a/ruff.toml b/ruff.toml index 8ff7284c..e8ef64f0 100644 --- a/ruff.toml +++ b/ruff.toml @@ -60,7 +60,6 @@ select = [ [per-file-ignores] 'tests/*' = ['INP001', 'T201', 'T203'] 'examples.py' = ['T201'] -'docs/*' = ['*'] [pydocstyle] convention = 'google' diff --git a/tests/test_color.py b/tests/test_color.py index a8fbb5e6..4683cfec 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -2,9 +2,8 @@ import typing -import pytest - import progressbar +import pytest from progressbar import terminal, widgets @@ -58,7 +57,8 @@ def __call__(self, *args, **kwargs): class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): - _gradient_colors: typing.ClassVar[widgets.TGradientColors] = widgets.TGradientColors( + _gradient_colors: typing.ClassVar[ + widgets.TGradientColors] = widgets.TGradientColors( fg=progressbar.widgets.colors.gradient, bg=None, ) diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index dfe5fc8c..477aef30 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -9,7 +9,8 @@ class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): def update(self, pbar): if 45 < pbar.percentage() < 80: - return f'Bigger Now {progressbar.FileTransferSpeed.update(self, pbar)}' + value = progressbar.FileTransferSpeed.update(self, pbar) + return f'Bigger Now {value}' else: return progressbar.FileTransferSpeed.update(self, pbar) diff --git a/tests/test_samples.py b/tests/test_samples.py index eeaa9181..5ab388bd 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -3,6 +3,7 @@ import progressbar from progressbar import widgets +from python_utils.containers import SliceableDeque def test_numeric_samples(): @@ -36,7 +37,7 @@ def test_numeric_samples(): bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 16), 16) - assert samples_widget(bar, None)[1] == progressbar.SliceableDeque( + assert samples_widget(bar, None)[1] == SliceableDeque( [4, 5, 8, 10, 20], ) From a4eb6ca0de7c9256c28869febb1262569b339f53 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 3 Oct 2023 03:50:36 +0200 Subject: [PATCH 104/118] increased test coverage --- .coveragerc | 1 + progressbar/bar.py | 34 +- progressbar/env.py | 159 ++++++++++ progressbar/terminal/__init__.py | 3 + progressbar/terminal/base.py | 131 +++----- progressbar/terminal/colors.py | 514 +++++++++++++++---------------- progressbar/terminal/stream.py | 15 +- progressbar/utils.py | 128 +------- progressbar/widgets.py | 2 +- pytest.ini | 2 +- tests/conftest.py | 4 +- tests/test_color.py | 217 ++++++++++++- tests/test_end.py | 7 +- tests/test_monitor_progress.py | 10 +- tests/test_multibar.py | 2 +- tests/test_progressbar.py | 1 - tests/test_stream.py | 46 +++ tests/test_terminal.py | 21 +- tests/test_timed.py | 21 +- tests/test_timer.py | 4 +- tests/test_unicode.py | 1 - tests/test_utils.py | 38 +-- tests/test_widgets.py | 8 +- 23 files changed, 843 insertions(+), 526 deletions(-) create mode 100644 progressbar/env.py diff --git a/.coveragerc b/.coveragerc index e5ec1f57..0dcf6a85 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,3 +23,4 @@ exclude_lines = if 0: if __name__ == .__main__.: if types.TYPE_CHECKING: + @typing.overload diff --git a/progressbar/bar.py b/progressbar/bar.py index c3f56d33..fa78300a 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -15,11 +15,12 @@ from python_utils import converters, types +import progressbar.terminal +import progressbar.env import progressbar.terminal.stream from . import ( base, - terminal, utils, widgets, widgets as widgets_module, # Avoid name collision @@ -176,14 +177,14 @@ class DefaultFdMixin(ProgressBarMixinBase): #: For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. #: For 256 color support you can use `TERM=xterm-256color`. #: For 16 colorsupport you can use `TERM=xterm`. - enable_colors: terminal.ColorSupport | bool | None = terminal.color_support + enable_colors: progressbar.env.ColorSupport | bool | None = progressbar.env.COLOR_SUPPORT def __init__( self, fd: base.IO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, - enable_colors: terminal.ColorSupport | None = None, + enable_colors: progressbar.env.ColorSupport | None = None, line_offset: int = 0, **kwargs, ): @@ -194,7 +195,7 @@ def __init__( fd = self._apply_line_offset(fd, line_offset) self.fd = fd - self.is_ansi_terminal = utils.is_ansi_terminal(fd) + self.is_ansi_terminal = progressbar.env.is_ansi_terminal(fd) self.is_terminal = self._determine_is_terminal(fd, is_terminal) self.line_breaks = self._determine_line_breaks(line_breaks) self.enable_colors = self._determine_enable_colors(enable_colors) @@ -216,13 +217,13 @@ def _determine_is_terminal( is_terminal: bool | None, ) -> bool: if is_terminal is not None: - return utils.is_terminal(fd, is_terminal) + return progressbar.env.is_terminal(fd, is_terminal) else: - return utils.is_ansi_terminal(fd) + return progressbar.env.is_ansi_terminal(fd) def _determine_line_breaks(self, line_breaks: bool | None) -> bool: if line_breaks is None: - return utils.env_flag( + return progressbar.env.env_flag( 'PROGRESSBAR_LINE_BREAKS', not self.is_terminal, ) @@ -231,21 +232,21 @@ def _determine_line_breaks(self, line_breaks: bool | None) -> bool: def _determine_enable_colors( self, - enable_colors: terminal.ColorSupport | None, - ) -> terminal.ColorSupport: + enable_colors: progressbar.env.ColorSupport | None, + ) -> progressbar.env.ColorSupport: if enable_colors is None: colors = ( - utils.env_flag('PROGRESSBAR_ENABLE_COLORS'), - utils.env_flag('FORCE_COLOR'), + progressbar.env.env_flag('PROGRESSBAR_ENABLE_COLORS'), + progressbar.env.env_flag('FORCE_COLOR'), self.is_ansi_terminal, ) for color_enabled in colors: if color_enabled is not None: if color_enabled: - enable_colors = terminal.color_support + enable_colors = progressbar.env.COLOR_SUPPORT else: - enable_colors = terminal.ColorSupport.NONE + enable_colors = progressbar.env.ColorSupport.NONE break else: # pragma: no cover # This scenario should never occur because `is_ansi_terminal` @@ -253,10 +254,11 @@ def _determine_enable_colors( raise ValueError('Unable to determine color support') elif enable_colors is True: - enable_colors = terminal.ColorSupport.XTERM_256 + enable_colors = progressbar.env.ColorSupport.XTERM_256 elif enable_colors is False: - enable_colors = terminal.ColorSupport.NONE - elif not isinstance(enable_colors, terminal.ColorSupport): + enable_colors = progressbar.env.ColorSupport.NONE + elif not isinstance(enable_colors, + progressbar.env.ColorSupport): raise ValueError(f'Invalid color support value: {enable_colors}') return enable_colors diff --git a/progressbar/env.py b/progressbar/env.py new file mode 100644 index 00000000..b3094f40 --- /dev/null +++ b/progressbar/env.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import enum +import os +import re +import typing + +from . import base + + +@typing.overload +def env_flag(name: str, default: bool) -> bool: + ... + + +@typing.overload +def env_flag(name: str, default: bool | None = None) -> bool | None: + ... + + +def env_flag(name, default=None): + ''' + Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, + on/off, and returns it as a boolean. + + If the environment variable is not defined, or has an unknown value, + returns `default` + ''' + v = os.getenv(name) + if v and v.lower() in ('y', 'yes', 't', 'true', 'on', '1'): + return True + if v and v.lower() in ('n', 'no', 'f', 'false', 'off', '0'): + return False + return default + + +class ColorSupport(enum.IntEnum): + '''Color support for the terminal.''' + + NONE = 0 + XTERM = 16 + XTERM_256 = 256 + XTERM_TRUECOLOR = 16777216 + + @classmethod + def from_env(cls): + '''Get the color support from the environment. + + If any of the environment variables contain `24bit` or `truecolor`, + we will enable true color/24 bit support. If they contain `256`, we + will enable 256 color/8 bit support. If they contain `xterm`, we will + enable 16 color support. Otherwise, we will assume no color support. + + If `JUPYTER_COLUMNS` or `JUPYTER_LINES` is set, we will assume true + color support. + + Note that the highest available value will be used! Having + `COLORTERM=truecolor` will override `TERM=xterm-256color`. + ''' + variables = ( + 'FORCE_COLOR', + 'PROGRESSBAR_ENABLE_COLORS', + 'COLORTERM', + 'TERM', + ) + + if os.environ.get('JUPYTER_COLUMNS') or os.environ.get( + 'JUPYTER_LINES', + ): + # Jupyter notebook always supports true color. + return cls.XTERM_TRUECOLOR + + support = cls.NONE + for variable in variables: + value = os.environ.get(variable) + if value is None: + continue + elif value in {'truecolor', '24bit'}: + # Truecolor support, we don't need to check anything else. + support = cls.XTERM_TRUECOLOR + break + elif '256' in value: + support = max(cls.XTERM_256, support) + elif value == 'xterm': + support = max(cls.XTERM, support) + + return support + + +def is_ansi_terminal( + fd: base.IO, + is_terminal: bool | None = None, +) -> bool: # pragma: no cover + if is_terminal is None: + # Jupyter Notebooks define this variable and support progress bars + if 'JPY_PARENT_PID' in os.environ: + is_terminal = True + # This works for newer versions of pycharm only. With older versions + # there is no way to check. + elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get( + 'PYTEST_CURRENT_TEST', + ): + is_terminal = True + + if is_terminal is None: + # check if we are writing to a terminal or not. typically a file object + # is going to return False if the instance has been overridden and + # isatty has not been defined we have no way of knowing so we will not + # use ansi. ansi terminals will typically define one of the 2 + # environment variables. + try: + is_tty = fd.isatty() + # Try and match any of the huge amount of Linux/Unix ANSI consoles + if is_tty and ANSI_TERM_RE.match(os.environ.get('TERM', '')): + is_terminal = True + # ANSICON is a Windows ANSI compatible console + elif 'ANSICON' in os.environ: + is_terminal = True + else: + is_terminal = None + except Exception: + is_terminal = False + + return bool(is_terminal) + + +def is_terminal(fd: base.IO, is_terminal: bool | None = None) -> bool: + if is_terminal is None: + # Full ansi support encompasses what we expect from a terminal + is_terminal = is_ansi_terminal(fd) or None + + if is_terminal is None: + # Allow a environment variable override + is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None) + + if is_terminal is None: # pragma: no cover + # Bare except because a lot can go wrong on different systems. If we do + # get a TTY we know this is a valid terminal + try: + is_terminal = fd.isatty() + except Exception: + is_terminal = False + + return bool(is_terminal) + + +COLOR_SUPPORT = ColorSupport.from_env() +ANSI_TERMS = ( + '([xe]|bv)term', + '(sco)?ansi', + 'cygwin', + 'konsole', + 'linux', + 'rxvt', + 'screen', + 'tmux', + 'vt(10[02]|220|320)', +) +ANSI_TERM_RE = re.compile(f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE) diff --git a/progressbar/terminal/__init__.py b/progressbar/terminal/__init__.py index ba4f9c90..89c18539 100644 --- a/progressbar/terminal/__init__.py +++ b/progressbar/terminal/__init__.py @@ -1 +1,4 @@ +from __future__ import annotations + from .base import * # noqa F403 +from .stream import TextIOOutputWrapper, LineOffsetStreamWrapper, LastLineStream diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 709ddf92..aef7dad7 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -3,8 +3,6 @@ import abc import collections import colorsys -import enum -import os import threading from collections import defaultdict @@ -14,7 +12,7 @@ from python_utils import converters, types -from .. import base +from .. import base as pbase, env from .os_specific import getch ESC = '\x1B' @@ -140,64 +138,8 @@ def clear_line(n): return UP(n) + CLEAR_LINE_ALL() + DOWN(n) -class ColorSupport(enum.IntEnum): - '''Color support for the terminal.''' - - NONE = 0 - XTERM = 16 - XTERM_256 = 256 - XTERM_TRUECOLOR = 16777216 - - @classmethod - def from_env(cls): - '''Get the color support from the environment. - - If any of the environment variables contain `24bit` or `truecolor`, - we will enable true color/24 bit support. If they contain `256`, we - will enable 256 color/8 bit support. If they contain `xterm`, we will - enable 16 color support. Otherwise, we will assume no color support. - - If `JUPYTER_COLUMNS` or `JUPYTER_LINES` is set, we will assume true - color support. - - Note that the highest available value will be used! Having - `COLORTERM=truecolor` will override `TERM=xterm-256color`. - ''' - variables = ( - 'FORCE_COLOR', - 'PROGRESSBAR_ENABLE_COLORS', - 'COLORTERM', - 'TERM', - ) - - if os.environ.get('JUPYTER_COLUMNS') or os.environ.get( - 'JUPYTER_LINES', - ): - # Jupyter notebook always supports true color. - return cls.XTERM_TRUECOLOR - - support = cls.NONE - for variable in variables: - value = os.environ.get(variable) - if value is None: - continue - elif value in {'truecolor', '24bit'}: - # Truecolor support, we don't need to check anything else. - support = cls.XTERM_TRUECOLOR - break - elif '256' in value: - support = max(cls.XTERM_256, support) - elif value == 'xterm': - support = max(cls.XTERM, support) - - return support - - -color_support = ColorSupport.from_env() - - # Report Cursor Position (CPR), response = [row;column] as row;columnR -class _CPR(str): +class _CPR(str): # pragma: no cover _response_lock = threading.Lock() def __call__(self, stream): @@ -268,21 +210,30 @@ def interpolate(self, end: RGB, step: float) -> RGB: ) -class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): +class HSL(collections.namedtuple('HSL', ['hue', 'saturation', 'lightness'])): + ''' + Hue, Saturation, Lightness color. + + Hue is a value between 0 and 360, saturation and lightness are between 0(%) + and 100(%). + + ''' __slots__ = () @classmethod - def from_rgb(cls, rgb: RGB) -> HLS: + def from_rgb(cls, rgb: RGB) -> HSL: + ''' + Convert a 0-255 RGB color to a 0-255 HLS color. + ''' + hls = colorsys.rgb_to_hls(rgb.red/255,rgb.green/255,rgb.blue/255) return cls( - *colorsys.rgb_to_hls( - rgb.red / 255, - rgb.green / 255, - rgb.blue / 255, - ), + round(hls[0] * 360), + round(hls[2] * 100), + round(hls[1] * 100), ) - def interpolate(self, end: HLS, step: float) -> HLS: - return HLS( + def interpolate(self, end: HSL, step: float) -> HSL: + return HSL( self.hue + (end.hue - self.hue) * step, self.lightness + (end.lightness - self.lightness) * step, self.saturation + (end.saturation - self.saturation) * step, @@ -309,7 +260,7 @@ class Color( ''' Color base class. - This class contains the colors in RGB (Red, Green, Blue), HLS (Hue, + This class contains the colors in RGB (Red, Green, Blue), HSL (Hue, Lightness, Saturation) and Xterm (8-bit) formats. It also contains the color name. @@ -337,16 +288,16 @@ def underline(self): @property def ansi(self) -> types.Optional[str]: - if color_support is ColorSupport.XTERM_TRUECOLOR: + if env.COLOR_SUPPORT is env.ColorSupport.XTERM_TRUECOLOR: # pragma: no branch return f'2;{self.rgb.red};{self.rgb.green};{self.rgb.blue}' - if self.xterm: + if self.xterm: # pragma: no branch color = self.xterm - elif color_support is ColorSupport.XTERM_256: + elif env.COLOR_SUPPORT is env.ColorSupport.XTERM_256: # pragma: no branch color = self.rgb.to_ansi_256 - elif color_support is ColorSupport.XTERM: + elif env.COLOR_SUPPORT is env.ColorSupport.XTERM: # pragma: no branch color = self.rgb.to_ansi_16 - else: + else: # pragma: no branch return None return f'5;{color}' @@ -383,7 +334,7 @@ class Colors: defaultdict[RGB, types.List[Color]] ] = collections.defaultdict(list) by_hls: ClassVar[ - defaultdict[HLS, types.List[Color]] + defaultdict[HSL, types.List[Color]] ] = collections.defaultdict(list) by_xterm: ClassVar[dict[int, Color]] = dict() @@ -391,7 +342,7 @@ class Colors: def register( cls, rgb: RGB, - hls: types.Optional[HLS] = None, + hls: types.Optional[HSL] = None, name: types.Optional[str] = None, xterm: types.Optional[int] = None, ) -> Color: @@ -402,7 +353,7 @@ def register( cls.by_lowername[name.lower()].append(color) if hls is None: - hls = HLS.from_rgb(rgb) + hls = HSL.from_rgb(rgb) cls.by_hex[rgb.hex].append(color) cls.by_rgb[rgb].append(color) @@ -430,8 +381,8 @@ def __call__(self, value: float) -> Color: def get_color(self, value: float) -> Color: 'Map a value from 0 to 1 to a color.' if ( - value == base.Undefined - or value == base.UnknownLength + value == pbase.Undefined + or value == pbase.UnknownLength or value <= 0 ): return self.colors[0] @@ -442,7 +393,12 @@ def get_color(self, value: float) -> Color: if max_color_idx == 0: return self.colors[0] elif self.interpolate: - index = round(converters.remap(value, 0, 1, 0, max_color_idx - 1)) + if max_color_idx > 1: + index = round( + converters.remap(value, 0, 1, 0, max_color_idx - 1)) + else: + index = 0 + step = converters.remap( value, index / (max_color_idx), @@ -481,21 +437,24 @@ def apply_colors( bg_none: Color | None = None, **kwargs: types.Any, ) -> str: - if fg is None and bg is None: - return text + '''Apply colors/gradients to a string depending on the given percentage. + When percentage is `None`, the `fg_none` and `bg_none` colors will be used. + Otherwise, the `fg` and `bg` colors will be used. If the colors are + gradients, the color will be interpolated depending on the percentage. + ''' if percentage is None: if fg_none is not None: text = fg_none.fg(text) if bg_none is not None: text = bg_none.bg(text) - else: + elif fg is not None or bg is not None: fg = get_color(percentage * 0.01, fg) bg = get_color(percentage * 0.01, bg) - if fg is not None: + if fg is not None: # pragma: no branch text = fg.fg(text) - if bg is not None: + if bg is not None: # pragma: no branch text = bg.bg(text) return text diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index f65e874d..62548eee 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -1,1018 +1,1018 @@ # Based on: https://www.ditig.com/256-colors-cheat-sheet import os -from progressbar.terminal.base import HLS, RGB, ColorGradient, Colors +from progressbar.terminal.base import HSL, RGB, ColorGradient, Colors -black = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Black', 0) -maroon = Colors.register(RGB(128, 0, 0), HLS(100, 0, 25), 'Maroon', 1) -green = Colors.register(RGB(0, 128, 0), HLS(100, 120, 25), 'Green', 2) -olive = Colors.register(RGB(128, 128, 0), HLS(100, 60, 25), 'Olive', 3) -navy = Colors.register(RGB(0, 0, 128), HLS(100, 240, 25), 'Navy', 4) -purple = Colors.register(RGB(128, 0, 128), HLS(100, 300, 25), 'Purple', 5) -teal = Colors.register(RGB(0, 128, 128), HLS(100, 180, 25), 'Teal', 6) -silver = Colors.register(RGB(192, 192, 192), HLS(0, 0, 75), 'Silver', 7) -grey = Colors.register(RGB(128, 128, 128), HLS(0, 0, 50), 'Grey', 8) -red = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red', 9) -lime = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Lime', 10) -yellow = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow', 11) -blue = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue', 12) -fuchsia = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Fuchsia', 13) -aqua = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Aqua', 14) -white = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'White', 15) -grey0 = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Grey0', 16) -navy_blue = Colors.register(RGB(0, 0, 95), HLS(100, 240, 18), 'NavyBlue', 17) -dark_blue = Colors.register(RGB(0, 0, 135), HLS(100, 240, 26), 'DarkBlue', 18) -blue3 = Colors.register(RGB(0, 0, 175), HLS(100, 240, 34), 'Blue3', 19) -blue3 = Colors.register(RGB(0, 0, 215), HLS(100, 240, 42), 'Blue3', 20) -blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) -dark_green = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) +black = Colors.register(RGB(0, 0, 0), HSL(0, 0, 0), 'Black', 0) +maroon = Colors.register(RGB(128, 0, 0), HSL(0, 100, 25), 'Maroon', 1) +green = Colors.register(RGB(0, 128, 0), HSL(120, 100, 25), 'Green', 2) +olive = Colors.register(RGB(128, 128, 0), HSL(60, 100, 25), 'Olive', 3) +navy = Colors.register(RGB(0, 0, 128), HSL(240, 100, 25), 'Navy', 4) +purple = Colors.register(RGB(128, 0, 128), HSL(300, 100, 25), 'Purple', 5) +teal = Colors.register(RGB(0, 128, 128), HSL(180, 100, 25), 'Teal', 6) +silver = Colors.register(RGB(192, 192, 192), HSL(0, 0, 75), 'Silver', 7) +grey = Colors.register(RGB(128, 128, 128), HSL(0, 0, 50), 'Grey', 8) +red = Colors.register(RGB(255, 0, 0), HSL(0, 100, 50), 'Red', 9) +lime = Colors.register(RGB(0, 255, 0), HSL(120, 100, 50), 'Lime', 10) +yellow = Colors.register(RGB(255, 255, 0), HSL(60, 100, 50), 'Yellow', 11) +blue = Colors.register(RGB(0, 0, 255), HSL(240, 100, 50), 'Blue', 12) +fuchsia = Colors.register(RGB(255, 0, 255), HSL(300, 100, 50), 'Fuchsia', 13) +aqua = Colors.register(RGB(0, 255, 255), HSL(180, 100, 50), 'Aqua', 14) +white = Colors.register(RGB(255, 255, 255), HSL(0, 0, 100), 'White', 15) +grey0 = Colors.register(RGB(0, 0, 0), HSL(0, 0, 0), 'Grey0', 16) +navy_blue = Colors.register(RGB(0, 0, 95), HSL(240, 100, 18), 'NavyBlue', 17) +dark_blue = Colors.register(RGB(0, 0, 135), HSL(240, 100, 26), 'DarkBlue', 18) +blue3 = Colors.register(RGB(0, 0, 175), HSL(240, 100, 34), 'Blue3', 19) +blue3 = Colors.register(RGB(0, 0, 215), HSL(240, 100, 42), 'Blue3', 20) +blue1 = Colors.register(RGB(0, 0, 255), HSL(240, 100, 50), 'Blue1', 21) +dark_green = Colors.register(RGB(0, 95, 0), HSL(120, 100, 18), 'DarkGreen', 22) deep_sky_blue4 = Colors.register( RGB(0, 95, 95), - HLS(100, 180, 18), + HSL(180, 100, 18), 'DeepSkyBlue4', 23, ) deep_sky_blue4 = Colors.register( RGB(0, 95, 135), - HLS(100, 97, 26), + HSL(97, 100, 26), 'DeepSkyBlue4', 24, ) deep_sky_blue4 = Colors.register( RGB(0, 95, 175), - HLS(100, 7, 34), + HSL(7, 100, 34), 'DeepSkyBlue4', 25, ) dodger_blue3 = Colors.register( RGB(0, 95, 215), - HLS(100, 13, 42), + HSL(13, 100, 42), 'DodgerBlue3', 26, ) dodger_blue2 = Colors.register( RGB(0, 95, 255), - HLS(100, 17, 50), + HSL(17, 100, 50), 'DodgerBlue2', 27, ) -green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) +green4 = Colors.register(RGB(0, 135, 0), HSL(120, 100, 26), 'Green4', 28) spring_green4 = Colors.register( RGB(0, 135, 95), - HLS(100, 62, 26), + HSL(62, 100, 26), 'SpringGreen4', 29, ) turquoise4 = Colors.register( RGB(0, 135, 135), - HLS(100, 180, 26), + HSL(180, 100, 26), 'Turquoise4', 30, ) deep_sky_blue3 = Colors.register( RGB(0, 135, 175), - HLS(100, 93, 34), + HSL(93, 100, 34), 'DeepSkyBlue3', 31, ) deep_sky_blue3 = Colors.register( RGB(0, 135, 215), - HLS(100, 2, 42), + HSL(2, 100, 42), 'DeepSkyBlue3', 32, ) dodger_blue1 = Colors.register( RGB(0, 135, 255), - HLS(100, 8, 50), + HSL(8, 100, 50), 'DodgerBlue1', 33, ) -green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) +green3 = Colors.register(RGB(0, 175, 0), HSL(120, 100, 34), 'Green3', 34) spring_green3 = Colors.register( RGB(0, 175, 95), - HLS(100, 52, 34), + HSL(52, 100, 34), 'SpringGreen3', 35, ) -dark_cyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) +dark_cyan = Colors.register(RGB(0, 175, 135), HSL(66, 100, 34), 'DarkCyan', 36) light_sea_green = Colors.register( RGB(0, 175, 175), - HLS(100, 180, 34), + HSL(180, 100, 34), 'LightSeaGreen', 37, ) deep_sky_blue2 = Colors.register( RGB(0, 175, 215), - HLS(100, 91, 42), + HSL(91, 100, 42), 'DeepSkyBlue2', 38, ) deep_sky_blue1 = Colors.register( RGB(0, 175, 255), - HLS(100, 98, 50), + HSL(98, 100, 50), 'DeepSkyBlue1', 39, ) -green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) +green3 = Colors.register(RGB(0, 215, 0), HSL(120, 100, 42), 'Green3', 40) spring_green3 = Colors.register( RGB(0, 215, 95), - HLS(100, 46, 42), + HSL(46, 100, 42), 'SpringGreen3', 41, ) spring_green2 = Colors.register( RGB(0, 215, 135), - HLS(100, 57, 42), + HSL(57, 100, 42), 'SpringGreen2', 42, ) -cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) +cyan3 = Colors.register(RGB(0, 215, 175), HSL(68, 100, 42), 'Cyan3', 43) dark_turquoise = Colors.register( RGB(0, 215, 215), - HLS(100, 180, 42), + HSL(180, 100, 42), 'DarkTurquoise', 44, ) turquoise2 = Colors.register( RGB(0, 215, 255), - HLS(100, 89, 50), + HSL(89, 100, 50), 'Turquoise2', 45, ) -green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) +green1 = Colors.register(RGB(0, 255, 0), HSL(120, 100, 50), 'Green1', 46) spring_green2 = Colors.register( RGB(0, 255, 95), - HLS(100, 42, 50), + HSL(42, 100, 50), 'SpringGreen2', 47, ) spring_green1 = Colors.register( RGB(0, 255, 135), - HLS(100, 51, 50), + HSL(51, 100, 50), 'SpringGreen1', 48, ) medium_spring_green = Colors.register( RGB(0, 255, 175), - HLS(100, 61, 50), + HSL(61, 100, 50), 'MediumSpringGreen', 49, ) -cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) -cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) -dark_red = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) +cyan2 = Colors.register(RGB(0, 255, 215), HSL(70, 100, 50), 'Cyan2', 50) +cyan1 = Colors.register(RGB(0, 255, 255), HSL(180, 100, 50), 'Cyan1', 51) +dark_red = Colors.register(RGB(95, 0, 0), HSL(0, 100, 18), 'DarkRed', 52) deep_pink4 = Colors.register( - RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53, + RGB(95, 0, 95), HSL(300, 100, 18), 'DeepPink4', 53, ) -purple4 = Colors.register(RGB(95, 0, 135), HLS(100, 82, 26), 'Purple4', 54) -purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) -purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) +purple4 = Colors.register(RGB(95, 0, 135), HSL(82, 100, 26), 'Purple4', 54) +purple4 = Colors.register(RGB(95, 0, 175), HSL(72, 100, 34), 'Purple4', 55) +purple3 = Colors.register(RGB(95, 0, 215), HSL(66, 100, 42), 'Purple3', 56) blue_violet = Colors.register( RGB(95, 0, 255), - HLS(100, 62, 50), + HSL(62, 100, 50), 'BlueViolet', 57, ) -orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) -grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) +orange4 = Colors.register(RGB(95, 95, 0), HSL(60, 100, 18), 'Orange4', 58) +grey37 = Colors.register(RGB(95, 95, 95), HSL(0, 0, 37), 'Grey37', 59) medium_purple4 = Colors.register( RGB(95, 95, 135), - HLS(17, 240, 45), + HSL(240, 17, 45), 'MediumPurple4', 60, ) slate_blue3 = Colors.register( RGB(95, 95, 175), - HLS(33, 240, 52), + HSL(240, 33, 52), 'SlateBlue3', 61, ) slate_blue3 = Colors.register( RGB(95, 95, 215), - HLS(60, 240, 60), + HSL(240, 60, 60), 'SlateBlue3', 62, ) royal_blue1 = Colors.register( RGB(95, 95, 255), - HLS(100, 240, 68), + HSL(240, 100, 68), 'RoyalBlue1', 63, ) chartreuse4 = Colors.register( RGB(95, 135, 0), - HLS(100, 7, 26), + HSL(7, 100, 26), 'Chartreuse4', 64, ) dark_sea_green4 = Colors.register( RGB(95, 135, 95), - HLS(17, 120, 45), + HSL(120, 17, 45), 'DarkSeaGreen4', 65, ) pale_turquoise4 = Colors.register( RGB(95, 135, 135), - HLS(17, 180, 45), + HSL(180, 17, 45), 'PaleTurquoise4', 66, ) steel_blue = Colors.register( RGB(95, 135, 175), - HLS(33, 210, 52), + HSL(210, 33, 52), 'SteelBlue', 67, ) steel_blue3 = Colors.register( RGB(95, 135, 215), - HLS(60, 220, 60), + HSL(220, 60, 60), 'SteelBlue3', 68, ) cornflower_blue = Colors.register( RGB(95, 135, 255), - HLS(100, 225, 68), + HSL(225, 100, 68), 'CornflowerBlue', 69, ) chartreuse3 = Colors.register( RGB(95, 175, 0), - HLS(100, 7, 34), + HSL(7, 100, 34), 'Chartreuse3', 70, ) dark_sea_green4 = Colors.register( RGB(95, 175, 95), - HLS(33, 120, 52), + HSL(120, 33, 52), 'DarkSeaGreen4', 71, ) cadet_blue = Colors.register( RGB(95, 175, 135), - HLS(33, 150, 52), + HSL(150, 33, 52), 'CadetBlue', 72, ) cadet_blue = Colors.register( RGB(95, 175, 175), - HLS(33, 180, 52), + HSL(180, 33, 52), 'CadetBlue', 73, ) sky_blue3 = Colors.register( - RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74, + RGB(95, 175, 215), HSL(200, 60, 60), 'SkyBlue3', 74, ) steel_blue1 = Colors.register( RGB(95, 175, 255), - HLS(100, 210, 68), + HSL(210, 100, 68), 'SteelBlue1', 75, ) chartreuse3 = Colors.register( RGB(95, 215, 0), - HLS(100, 3, 42), + HSL(3, 100, 42), 'Chartreuse3', 76, ) pale_green3 = Colors.register( RGB(95, 215, 95), - HLS(60, 120, 60), + HSL(120, 60, 60), 'PaleGreen3', 77, ) sea_green3 = Colors.register( RGB(95, 215, 135), - HLS(60, 140, 60), + HSL(140, 60, 60), 'SeaGreen3', 78, ) aquamarine3 = Colors.register( RGB(95, 215, 175), - HLS(60, 160, 60), + HSL(160, 60, 60), 'Aquamarine3', 79, ) medium_turquoise = Colors.register( RGB(95, 215, 215), - HLS(60, 180, 60), + HSL(180, 60, 60), 'MediumTurquoise', 80, ) steel_blue1 = Colors.register( RGB(95, 215, 255), - HLS(100, 195, 68), + HSL(195, 100, 68), 'SteelBlue1', 81, ) chartreuse2 = Colors.register( RGB(95, 255, 0), - HLS(100, 7, 50), + HSL(7, 100, 50), 'Chartreuse2', 82, ) sea_green2 = Colors.register( RGB(95, 255, 95), - HLS(100, 120, 68), + HSL(120, 100, 68), 'SeaGreen2', 83, ) sea_green1 = Colors.register( RGB(95, 255, 135), - HLS(100, 135, 68), + HSL(135, 100, 68), 'SeaGreen1', 84, ) sea_green1 = Colors.register( RGB(95, 255, 175), - HLS(100, 150, 68), + HSL(150, 100, 68), 'SeaGreen1', 85, ) aquamarine1 = Colors.register( RGB(95, 255, 215), - HLS(100, 165, 68), + HSL(165, 100, 68), 'Aquamarine1', 86, ) dark_slate_gray2 = Colors.register( RGB(95, 255, 255), - HLS(100, 180, 68), + HSL(180, 100, 68), 'DarkSlateGray2', 87, ) -dark_red = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) +dark_red = Colors.register(RGB(135, 0, 0), HSL(0, 100, 26), 'DarkRed', 88) deep_pink4 = Colors.register( - RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89, + RGB(135, 0, 95), HSL(17, 100, 26), 'DeepPink4', 89, ) dark_magenta = Colors.register( RGB(135, 0, 135), - HLS(100, 300, 26), + HSL(300, 100, 26), 'DarkMagenta', 90, ) dark_magenta = Colors.register( RGB(135, 0, 175), - HLS(100, 86, 34), + HSL(86, 100, 34), 'DarkMagenta', 91, ) dark_violet = Colors.register( RGB(135, 0, 215), - HLS(100, 77, 42), + HSL(77, 100, 42), 'DarkViolet', 92, ) -purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) -orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) +purple = Colors.register(RGB(135, 0, 255), HSL(71, 100, 50), 'Purple', 93) +orange4 = Colors.register(RGB(135, 95, 0), HSL(2, 100, 26), 'Orange4', 94) light_pink4 = Colors.register( RGB(135, 95, 95), - HLS(17, 0, 45), + HSL(0, 17, 45), 'LightPink4', 95, ) -plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) +plum4 = Colors.register(RGB(135, 95, 135), HSL(300, 17, 45), 'Plum4', 96) medium_purple3 = Colors.register( RGB(135, 95, 175), - HLS(33, 270, 52), + HSL(270, 33, 52), 'MediumPurple3', 97, ) medium_purple3 = Colors.register( RGB(135, 95, 215), - HLS(60, 260, 60), + HSL(260, 60, 60), 'MediumPurple3', 98, ) slate_blue1 = Colors.register( RGB(135, 95, 255), - HLS(100, 255, 68), + HSL(255, 100, 68), 'SlateBlue1', 99, ) -yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) -wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) -grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) +yellow4 = Colors.register(RGB(135, 135, 0), HSL(60, 100, 26), 'Yellow4', 100) +wheat4 = Colors.register(RGB(135, 135, 95), HSL(60, 17, 45), 'Wheat4', 101) +grey53 = Colors.register(RGB(135, 135, 135), HSL(0, 0, 52), 'Grey53', 102) light_slate_grey = Colors.register( RGB(135, 135, 175), - HLS(20, 240, 60), + HSL(240, 20, 60), 'LightSlateGrey', 103, ) medium_purple = Colors.register( RGB(135, 135, 215), - HLS(50, 240, 68), + HSL(240, 50, 68), 'MediumPurple', 104, ) light_slate_blue = Colors.register( RGB(135, 135, 255), - HLS(100, 240, 76), + HSL(240, 100, 76), 'LightSlateBlue', 105, ) -yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) +yellow4 = Colors.register(RGB(135, 175, 0), HSL(3, 100, 34), 'Yellow4', 106) dark_olive_green3 = Colors.register( RGB(135, 175, 95), - HLS(33, 90, 52), + HSL(90, 33, 52), 'DarkOliveGreen3', 107, ) dark_sea_green = Colors.register( RGB(135, 175, 135), - HLS(20, 120, 60), + HSL(120, 20, 60), 'DarkSeaGreen', 108, ) light_sky_blue3 = Colors.register( RGB(135, 175, 175), - HLS(20, 180, 60), + HSL(180, 20, 60), 'LightSkyBlue3', 109, ) light_sky_blue3 = Colors.register( RGB(135, 175, 215), - HLS(50, 210, 68), + HSL(210, 50, 68), 'LightSkyBlue3', 110, ) sky_blue2 = Colors.register( RGB(135, 175, 255), - HLS(100, 220, 76), + HSL(220, 100, 76), 'SkyBlue2', 111, ) chartreuse2 = Colors.register( RGB(135, 215, 0), - HLS(100, 2, 42), + HSL(2, 100, 42), 'Chartreuse2', 112, ) dark_olive_green3 = Colors.register( RGB(135, 215, 95), - HLS(60, 100, 60), + HSL(100, 60, 60), 'DarkOliveGreen3', 113, ) pale_green3 = Colors.register( RGB(135, 215, 135), - HLS(50, 120, 68), + HSL(120, 50, 68), 'PaleGreen3', 114, ) dark_sea_green3 = Colors.register( RGB(135, 215, 175), - HLS(50, 150, 68), + HSL(150, 50, 68), 'DarkSeaGreen3', 115, ) dark_slate_gray3 = Colors.register( RGB(135, 215, 215), - HLS(50, 180, 68), + HSL(180, 50, 68), 'DarkSlateGray3', 116, ) sky_blue1 = Colors.register( RGB(135, 215, 255), - HLS(100, 200, 76), + HSL(200, 100, 76), 'SkyBlue1', 117, ) chartreuse1 = Colors.register( RGB(135, 255, 0), - HLS(100, 8, 50), + HSL(8, 100, 50), 'Chartreuse1', 118, ) light_green = Colors.register( RGB(135, 255, 95), - HLS(100, 105, 68), + HSL(105, 100, 68), 'LightGreen', 119, ) light_green = Colors.register( RGB(135, 255, 135), - HLS(100, 120, 76), + HSL(120, 100, 76), 'LightGreen', 120, ) pale_green1 = Colors.register( RGB(135, 255, 175), - HLS(100, 140, 76), + HSL(140, 100, 76), 'PaleGreen1', 121, ) aquamarine1 = Colors.register( RGB(135, 255, 215), - HLS(100, 160, 76), + HSL(160, 100, 76), 'Aquamarine1', 122, ) dark_slate_gray1 = Colors.register( RGB(135, 255, 255), - HLS(100, 180, 76), + HSL(180, 100, 76), 'DarkSlateGray1', 123, ) -red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) +red3 = Colors.register(RGB(175, 0, 0), HSL(0, 100, 34), 'Red3', 124) deep_pink4 = Colors.register( RGB(175, 0, 95), - HLS(100, 27, 34), + HSL(27, 100, 34), 'DeepPink4', 125, ) medium_violet_red = Colors.register( RGB(175, 0, 135), - HLS(100, 13, 34), + HSL(13, 100, 34), 'MediumVioletRed', 126, ) magenta3 = Colors.register( RGB(175, 0, 175), - HLS(100, 300, 34), + HSL(300, 100, 34), 'Magenta3', 127, ) dark_violet = Colors.register( RGB(175, 0, 215), - HLS(100, 88, 42), + HSL(88, 100, 42), 'DarkViolet', 128, ) -purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) +purple = Colors.register(RGB(175, 0, 255), HSL(81, 100, 50), 'Purple', 129) dark_orange3 = Colors.register( RGB(175, 95, 0), - HLS(100, 2, 34), + HSL(2, 100, 34), 'DarkOrange3', 130, ) indian_red = Colors.register( - RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131, + RGB(175, 95, 95), HSL(0, 33, 52), 'IndianRed', 131, ) hot_pink3 = Colors.register( RGB(175, 95, 135), - HLS(33, 330, 52), + HSL(330, 33, 52), 'HotPink3', 132, ) medium_orchid3 = Colors.register( RGB(175, 95, 175), - HLS(33, 300, 52), + HSL(300, 33, 52), 'MediumOrchid3', 133, ) medium_orchid = Colors.register( RGB(175, 95, 215), - HLS(60, 280, 60), + HSL(280, 60, 60), 'MediumOrchid', 134, ) medium_purple2 = Colors.register( RGB(175, 95, 255), - HLS(100, 270, 68), + HSL(270, 100, 68), 'MediumPurple2', 135, ) dark_goldenrod = Colors.register( RGB(175, 135, 0), - HLS(100, 6, 34), + HSL(6, 100, 34), 'DarkGoldenrod', 136, ) light_salmon3 = Colors.register( RGB(175, 135, 95), - HLS(33, 30, 52), + HSL(30, 33, 52), 'LightSalmon3', 137, ) rosy_brown = Colors.register( RGB(175, 135, 135), - HLS(20, 0, 60), + HSL(0, 20, 60), 'RosyBrown', 138, ) -grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) +grey63 = Colors.register(RGB(175, 135, 175), HSL(300, 20, 60), 'Grey63', 139) medium_purple2 = Colors.register( RGB(175, 135, 215), - HLS(50, 270, 68), + HSL(270, 50, 68), 'MediumPurple2', 140, ) medium_purple1 = Colors.register( RGB(175, 135, 255), - HLS(100, 260, 76), + HSL(260, 100, 76), 'MediumPurple1', 141, ) -gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) +gold3 = Colors.register(RGB(175, 175, 0), HSL(60, 100, 34), 'Gold3', 142) dark_khaki = Colors.register( RGB(175, 175, 95), - HLS(33, 60, 52), + HSL(60, 33, 52), 'DarkKhaki', 143, ) navajo_white3 = Colors.register( RGB(175, 175, 135), - HLS(20, 60, 60), + HSL(60, 20, 60), 'NavajoWhite3', 144, ) -grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) +grey69 = Colors.register(RGB(175, 175, 175), HSL(0, 0, 68), 'Grey69', 145) light_steel_blue3 = Colors.register( RGB(175, 175, 215), - HLS(33, 240, 76), + HSL(240, 33, 76), 'LightSteelBlue3', 146, ) light_steel_blue = Colors.register( RGB(175, 175, 255), - HLS(100, 240, 84), + HSL(240, 100, 84), 'LightSteelBlue', 147, ) -yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) +yellow3 = Colors.register(RGB(175, 215, 0), HSL(1, 100, 42), 'Yellow3', 148) dark_olive_green3 = Colors.register( RGB(175, 215, 95), - HLS(60, 80, 60), + HSL(80, 60, 60), 'DarkOliveGreen3', 149, ) dark_sea_green3 = Colors.register( RGB(175, 215, 135), - HLS(50, 90, 68), + HSL(90, 50, 68), 'DarkSeaGreen3', 150, ) dark_sea_green2 = Colors.register( RGB(175, 215, 175), - HLS(33, 120, 76), + HSL(120, 33, 76), 'DarkSeaGreen2', 151, ) light_cyan3 = Colors.register( RGB(175, 215, 215), - HLS(33, 180, 76), + HSL(180, 33, 76), 'LightCyan3', 152, ) light_sky_blue1 = Colors.register( RGB(175, 215, 255), - HLS(100, 210, 84), + HSL(210, 100, 84), 'LightSkyBlue1', 153, ) green_yellow = Colors.register( RGB(175, 255, 0), - HLS(100, 8, 50), + HSL(8, 100, 50), 'GreenYellow', 154, ) dark_olive_green2 = Colors.register( RGB(175, 255, 95), - HLS(100, 90, 68), + HSL(90, 100, 68), 'DarkOliveGreen2', 155, ) pale_green1 = Colors.register( RGB(175, 255, 135), - HLS(100, 100, 76), + HSL(100, 100, 76), 'PaleGreen1', 156, ) dark_sea_green2 = Colors.register( RGB(175, 255, 175), - HLS(100, 120, 84), + HSL(120, 100, 84), 'DarkSeaGreen2', 157, ) dark_sea_green1 = Colors.register( RGB(175, 255, 215), - HLS(100, 150, 84), + HSL(150, 100, 84), 'DarkSeaGreen1', 158, ) pale_turquoise1 = Colors.register( RGB(175, 255, 255), - HLS(100, 180, 84), + HSL(180, 100, 84), 'PaleTurquoise1', 159, ) -red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) +red3 = Colors.register(RGB(215, 0, 0), HSL(0, 100, 42), 'Red3', 160) deep_pink3 = Colors.register( RGB(215, 0, 95), - HLS(100, 33, 42), + HSL(33, 100, 42), 'DeepPink3', 161, ) deep_pink3 = Colors.register( RGB(215, 0, 135), - HLS(100, 22, 42), + HSL(22, 100, 42), 'DeepPink3', 162, ) -magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) +magenta3 = Colors.register(RGB(215, 0, 175), HSL(11, 100, 42), 'Magenta3', 163) magenta3 = Colors.register( RGB(215, 0, 215), - HLS(100, 300, 42), + HSL(300, 100, 42), 'Magenta3', 164, ) -magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) +magenta2 = Colors.register(RGB(215, 0, 255), HSL(90, 100, 50), 'Magenta2', 165) dark_orange3 = Colors.register( RGB(215, 95, 0), - HLS(100, 6, 42), + HSL(6, 100, 42), 'DarkOrange3', 166, ) indian_red = Colors.register( - RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167, + RGB(215, 95, 95), HSL(0, 60, 60), 'IndianRed', 167, ) hot_pink3 = Colors.register( RGB(215, 95, 135), - HLS(60, 340, 60), + HSL(340, 60, 60), 'HotPink3', 168, ) hot_pink2 = Colors.register( RGB(215, 95, 175), - HLS(60, 320, 60), + HSL(320, 60, 60), 'HotPink2', 169, ) -orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) +orchid = Colors.register(RGB(215, 95, 215), HSL(300, 60, 60), 'Orchid', 170) medium_orchid1 = Colors.register( RGB(215, 95, 255), - HLS(100, 285, 68), + HSL(285, 100, 68), 'MediumOrchid1', 171, ) -orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) +orange3 = Colors.register(RGB(215, 135, 0), HSL(7, 100, 42), 'Orange3', 172) light_salmon3 = Colors.register( RGB(215, 135, 95), - HLS(60, 20, 60), + HSL(20, 60, 60), 'LightSalmon3', 173, ) light_pink3 = Colors.register( RGB(215, 135, 135), - HLS(50, 0, 68), + HSL(0, 50, 68), 'LightPink3', 174, ) -pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) -plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) -violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) -gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) +pink3 = Colors.register(RGB(215, 135, 175), HSL(330, 50, 68), 'Pink3', 175) +plum3 = Colors.register(RGB(215, 135, 215), HSL(300, 50, 68), 'Plum3', 176) +violet = Colors.register(RGB(215, 135, 255), HSL(280, 100, 76), 'Violet', 177) +gold3 = Colors.register(RGB(215, 175, 0), HSL(8, 100, 42), 'Gold3', 178) light_goldenrod3 = Colors.register( RGB(215, 175, 95), - HLS(60, 40, 60), + HSL(40, 60, 60), 'LightGoldenrod3', 179, ) -tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) +tan = Colors.register(RGB(215, 175, 135), HSL(30, 50, 68), 'Tan', 180) misty_rose3 = Colors.register( RGB(215, 175, 175), - HLS(33, 0, 76), + HSL(0, 33, 76), 'MistyRose3', 181, ) thistle3 = Colors.register( RGB(215, 175, 215), - HLS(33, 300, 76), + HSL(300, 33, 76), 'Thistle3', 182, ) -plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) -yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) -khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) +plum2 = Colors.register(RGB(215, 175, 255), HSL(270, 100, 84), 'Plum2', 183) +yellow3 = Colors.register(RGB(215, 215, 0), HSL(60, 100, 42), 'Yellow3', 184) +khaki3 = Colors.register(RGB(215, 215, 95), HSL(60, 60, 60), 'Khaki3', 185) light_goldenrod2 = Colors.register( RGB(215, 215, 135), - HLS(50, 60, 68), + HSL(60, 50, 68), 'LightGoldenrod2', 186, ) light_yellow3 = Colors.register( RGB(215, 215, 175), - HLS(33, 60, 76), + HSL(60, 33, 76), 'LightYellow3', 187, ) -grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) +grey84 = Colors.register(RGB(215, 215, 215), HSL(0, 0, 84), 'Grey84', 188) light_steel_blue1 = Colors.register( RGB(215, 215, 255), - HLS(100, 240, 92), + HSL(240, 100, 92), 'LightSteelBlue1', 189, ) -yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) +yellow2 = Colors.register(RGB(215, 255, 0), HSL(9, 100, 50), 'Yellow2', 190) dark_olive_green1 = Colors.register( RGB(215, 255, 95), - HLS(100, 75, 68), + HSL(75, 100, 68), 'DarkOliveGreen1', 191, ) dark_olive_green1 = Colors.register( RGB(215, 255, 135), - HLS(100, 80, 76), + HSL(80, 100, 76), 'DarkOliveGreen1', 192, ) dark_sea_green1 = Colors.register( RGB(215, 255, 175), - HLS(100, 90, 84), + HSL(90, 100, 84), 'DarkSeaGreen1', 193, ) honeydew2 = Colors.register( RGB(215, 255, 215), - HLS(100, 120, 92), + HSL(120, 100, 92), 'Honeydew2', 194, ) light_cyan1 = Colors.register( RGB(215, 255, 255), - HLS(100, 180, 92), + HSL(180, 100, 92), 'LightCyan1', 195, ) -red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) +red1 = Colors.register(RGB(255, 0, 0), HSL(0, 100, 50), 'Red1', 196) deep_pink2 = Colors.register( RGB(255, 0, 95), - HLS(100, 37, 50), + HSL(37, 100, 50), 'DeepPink2', 197, ) deep_pink1 = Colors.register( RGB(255, 0, 135), - HLS(100, 28, 50), + HSL(28, 100, 50), 'DeepPink1', 198, ) deep_pink1 = Colors.register( RGB(255, 0, 175), - HLS(100, 18, 50), + HSL(18, 100, 50), 'DeepPink1', 199, ) -magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) +magenta2 = Colors.register(RGB(255, 0, 215), HSL(9, 100, 50), 'Magenta2', 200) magenta1 = Colors.register( RGB(255, 0, 255), - HLS(100, 300, 50), + HSL(300, 100, 50), 'Magenta1', 201, ) orange_red1 = Colors.register( RGB(255, 95, 0), - HLS(100, 2, 50), + HSL(2, 100, 50), 'OrangeRed1', 202, ) indian_red1 = Colors.register( RGB(255, 95, 95), - HLS(100, 0, 68), + HSL(0, 100, 68), 'IndianRed1', 203, ) indian_red1 = Colors.register( RGB(255, 95, 135), - HLS(100, 345, 68), + HSL(345, 100, 68), 'IndianRed1', 204, ) hot_pink = Colors.register( - RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205, + RGB(255, 95, 175), HSL(330, 100, 68), 'HotPink', 205, ) hot_pink = Colors.register( - RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206, + RGB(255, 95, 215), HSL(315, 100, 68), 'HotPink', 206, ) medium_orchid1 = Colors.register( RGB(255, 95, 255), - HLS(100, 300, 68), + HSL(300, 100, 68), 'MediumOrchid1', 207, ) dark_orange = Colors.register( RGB(255, 135, 0), - HLS(100, 1, 50), + HSL(1, 100, 50), 'DarkOrange', 208, ) -salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) +salmon1 = Colors.register(RGB(255, 135, 95), HSL(15, 100, 68), 'Salmon1', 209) light_coral = Colors.register( RGB(255, 135, 135), - HLS(100, 0, 76), + HSL(0, 100, 76), 'LightCoral', 210, ) pale_violet_red1 = Colors.register( RGB(255, 135, 175), - HLS(100, 340, 76), + HSL(340, 100, 76), 'PaleVioletRed1', 211, ) orchid2 = Colors.register( RGB(255, 135, 215), - HLS(100, 320, 76), + HSL(320, 100, 76), 'Orchid2', 212, ) orchid1 = Colors.register( RGB(255, 135, 255), - HLS(100, 300, 76), + HSL(300, 100, 76), 'Orchid1', 213, ) -orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) +orange1 = Colors.register(RGB(255, 175, 0), HSL(1, 100, 50), 'Orange1', 214) sandy_brown = Colors.register( RGB(255, 175, 95), - HLS(100, 30, 68), + HSL(30, 100, 68), 'SandyBrown', 215, ) light_salmon1 = Colors.register( RGB(255, 175, 135), - HLS(100, 20, 76), + HSL(20, 100, 76), 'LightSalmon1', 216, ) light_pink1 = Colors.register( RGB(255, 175, 175), - HLS(100, 0, 84), + HSL(0, 100, 84), 'LightPink1', 217, ) -pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) -plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) -gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) +pink1 = Colors.register(RGB(255, 175, 215), HSL(330, 100, 84), 'Pink1', 218) +plum1 = Colors.register(RGB(255, 175, 255), HSL(300, 100, 84), 'Plum1', 219) +gold1 = Colors.register(RGB(255, 215, 0), HSL(0, 100, 50), 'Gold1', 220) light_goldenrod2 = Colors.register( RGB(255, 215, 95), - HLS(100, 45, 68), + HSL(45, 100, 68), 'LightGoldenrod2', 221, ) light_goldenrod2 = Colors.register( RGB(255, 215, 135), - HLS(100, 40, 76), + HSL(40, 100, 76), 'LightGoldenrod2', 222, ) navajo_white1 = Colors.register( RGB(255, 215, 175), - HLS(100, 30, 84), + HSL(30, 100, 84), 'NavajoWhite1', 223, ) misty_rose1 = Colors.register( RGB(255, 215, 215), - HLS(100, 0, 92), + HSL(0, 100, 92), 'MistyRose1', 224, ) thistle1 = Colors.register( RGB(255, 215, 255), - HLS(100, 300, 92), + HSL(300, 100, 92), 'Thistle1', 225, ) -yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) +yellow1 = Colors.register(RGB(255, 255, 0), HSL(60, 100, 50), 'Yellow1', 226) light_goldenrod1 = Colors.register( RGB(255, 255, 95), - HLS(100, 60, 68), + HSL(60, 100, 68), 'LightGoldenrod1', 227, ) -khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) -wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) +khaki1 = Colors.register(RGB(255, 255, 135), HSL(60, 100, 76), 'Khaki1', 228) +wheat1 = Colors.register(RGB(255, 255, 175), HSL(60, 100, 84), 'Wheat1', 229) cornsilk1 = Colors.register( RGB(255, 255, 215), - HLS(100, 60, 92), + HSL(60, 100, 92), 'Cornsilk1', 230, ) -grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) -grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) -grey7 = Colors.register(RGB(18, 18, 18), HLS(0, 0, 7), 'Grey7', 233) -grey11 = Colors.register(RGB(28, 28, 28), HLS(0, 0, 10), 'Grey11', 234) -grey15 = Colors.register(RGB(38, 38, 38), HLS(0, 0, 14), 'Grey15', 235) -grey19 = Colors.register(RGB(48, 48, 48), HLS(0, 0, 18), 'Grey19', 236) -grey23 = Colors.register(RGB(58, 58, 58), HLS(0, 0, 22), 'Grey23', 237) -grey27 = Colors.register(RGB(68, 68, 68), HLS(0, 0, 26), 'Grey27', 238) -grey30 = Colors.register(RGB(78, 78, 78), HLS(0, 0, 30), 'Grey30', 239) -grey35 = Colors.register(RGB(88, 88, 88), HLS(0, 0, 34), 'Grey35', 240) -grey39 = Colors.register(RGB(98, 98, 98), HLS(0, 0, 37), 'Grey39', 241) -grey42 = Colors.register(RGB(108, 108, 108), HLS(0, 0, 40), 'Grey42', 242) -grey46 = Colors.register(RGB(118, 118, 118), HLS(0, 0, 46), 'Grey46', 243) -grey50 = Colors.register(RGB(128, 128, 128), HLS(0, 0, 50), 'Grey50', 244) -grey54 = Colors.register(RGB(138, 138, 138), HLS(0, 0, 54), 'Grey54', 245) -grey58 = Colors.register(RGB(148, 148, 148), HLS(0, 0, 58), 'Grey58', 246) -grey62 = Colors.register(RGB(158, 158, 158), HLS(0, 0, 61), 'Grey62', 247) -grey66 = Colors.register(RGB(168, 168, 168), HLS(0, 0, 65), 'Grey66', 248) -grey70 = Colors.register(RGB(178, 178, 178), HLS(0, 0, 69), 'Grey70', 249) -grey74 = Colors.register(RGB(188, 188, 188), HLS(0, 0, 73), 'Grey74', 250) -grey78 = Colors.register(RGB(198, 198, 198), HLS(0, 0, 77), 'Grey78', 251) -grey82 = Colors.register(RGB(208, 208, 208), HLS(0, 0, 81), 'Grey82', 252) -grey85 = Colors.register(RGB(218, 218, 218), HLS(0, 0, 85), 'Grey85', 253) -grey89 = Colors.register(RGB(228, 228, 228), HLS(0, 0, 89), 'Grey89', 254) -grey93 = Colors.register(RGB(238, 238, 238), HLS(0, 0, 93), 'Grey93', 255) +grey100 = Colors.register(RGB(255, 255, 255), HSL(0, 0, 100), 'Grey100', 231) +grey3 = Colors.register(RGB(8, 8, 8), HSL(0, 0, 3), 'Grey3', 232) +grey7 = Colors.register(RGB(18, 18, 18), HSL(0, 0, 7), 'Grey7', 233) +grey11 = Colors.register(RGB(28, 28, 28), HSL(0, 0, 10), 'Grey11', 234) +grey15 = Colors.register(RGB(38, 38, 38), HSL(0, 0, 14), 'Grey15', 235) +grey19 = Colors.register(RGB(48, 48, 48), HSL(0, 0, 18), 'Grey19', 236) +grey23 = Colors.register(RGB(58, 58, 58), HSL(0, 0, 22), 'Grey23', 237) +grey27 = Colors.register(RGB(68, 68, 68), HSL(0, 0, 26), 'Grey27', 238) +grey30 = Colors.register(RGB(78, 78, 78), HSL(0, 0, 30), 'Grey30', 239) +grey35 = Colors.register(RGB(88, 88, 88), HSL(0, 0, 34), 'Grey35', 240) +grey39 = Colors.register(RGB(98, 98, 98), HSL(0, 0, 37), 'Grey39', 241) +grey42 = Colors.register(RGB(108, 108, 108), HSL(0, 0, 40), 'Grey42', 242) +grey46 = Colors.register(RGB(118, 118, 118), HSL(0, 0, 46), 'Grey46', 243) +grey50 = Colors.register(RGB(128, 128, 128), HSL(0, 0, 50), 'Grey50', 244) +grey54 = Colors.register(RGB(138, 138, 138), HSL(0, 0, 54), 'Grey54', 245) +grey58 = Colors.register(RGB(148, 148, 148), HSL(0, 0, 58), 'Grey58', 246) +grey62 = Colors.register(RGB(158, 158, 158), HSL(0, 0, 61), 'Grey62', 247) +grey66 = Colors.register(RGB(168, 168, 168), HSL(0, 0, 65), 'Grey66', 248) +grey70 = Colors.register(RGB(178, 178, 178), HSL(0, 0, 69), 'Grey70', 249) +grey74 = Colors.register(RGB(188, 188, 188), HSL(0, 0, 73), 'Grey74', 250) +grey78 = Colors.register(RGB(198, 198, 198), HSL(0, 0, 77), 'Grey78', 251) +grey82 = Colors.register(RGB(208, 208, 208), HSL(0, 0, 81), 'Grey82', 252) +grey85 = Colors.register(RGB(218, 218, 218), HSL(0, 0, 85), 'Grey85', 253) +grey89 = Colors.register(RGB(228, 228, 228), HSL(0, 0, 89), 'Grey89', 254) +grey93 = Colors.register(RGB(238, 238, 238), HSL(0, 0, 93), 'Grey93', 255) dark_gradient = ColorGradient( red1, diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index a2fbe2fc..bb9ad98d 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -7,7 +7,7 @@ from progressbar import base -class TextIOOutputWrapper(base.TextIO): +class TextIOOutputWrapper(base.TextIO): # pragma: no cover def __init__(self, stream: base.TextIO): self.stream = stream @@ -102,10 +102,16 @@ def readable(self) -> bool: return True def read(self, __n: int = -1) -> str: - return self.line[:__n] + if __n < 0: + return self.line + else: + return self.line[:__n] def readline(self, __limit: int = -1) -> str: - return self.line[:__limit] + if __limit < 0: + return self.line + else: + return self.line[:__limit] def write(self, data): self.line = data @@ -117,6 +123,9 @@ def truncate(self, __size: int | None = None) -> int: self.line = self.line[:__size] return len(self.line) + + def __iter__(self) -> Iterator[str]: + yield self.line def writelines(self, __lines: Iterable[str]) -> None: line = '' diff --git a/progressbar/utils.py b/progressbar/utils.py index 83116c84..48d2f302 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -8,7 +8,6 @@ import os import re import sys -import typing from types import TracebackType from typing import Iterable, Iterator @@ -17,7 +16,7 @@ from python_utils.terminal import get_terminal_size from python_utils.time import epoch, format_time, timedelta_to_seconds -from progressbar import base, terminal +from progressbar import base, env, terminal if types.TYPE_CHECKING: from .bar import ProgressBar, ProgressBarMixinBase @@ -31,80 +30,10 @@ StringT = types.TypeVar('StringT', bound=types.StringTypes) -ANSI_TERMS = ( - '([xe]|bv)term', - '(sco)?ansi', - 'cygwin', - 'konsole', - 'linux', - 'rxvt', - 'screen', - 'tmux', - 'vt(10[02]|220|320)', -) -ANSI_TERM_RE = re.compile(f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE) - - -def is_ansi_terminal( - fd: base.IO, - is_terminal: bool | None = None, -) -> bool: # pragma: no cover - if is_terminal is None: - # Jupyter Notebooks define this variable and support progress bars - if 'JPY_PARENT_PID' in os.environ: - is_terminal = True - # This works for newer versions of pycharm only. With older versions - # there is no way to check. - elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get( - 'PYTEST_CURRENT_TEST', - ): - is_terminal = True - - if is_terminal is None: - # check if we are writing to a terminal or not. typically a file object - # is going to return False if the instance has been overridden and - # isatty has not been defined we have no way of knowing so we will not - # use ansi. ansi terminals will typically define one of the 2 - # environment variables. - try: - is_tty = fd.isatty() - # Try and match any of the huge amount of Linux/Unix ANSI consoles - if is_tty and ANSI_TERM_RE.match(os.environ.get('TERM', '')): - is_terminal = True - # ANSICON is a Windows ANSI compatible console - elif 'ANSICON' in os.environ: - is_terminal = True - else: - is_terminal = None - except Exception: - is_terminal = False - - return bool(is_terminal) - - -def is_terminal(fd: base.IO, is_terminal: bool | None = None) -> bool: - if is_terminal is None: - # Full ansi support encompasses what we expect from a terminal - is_terminal = is_ansi_terminal(fd) or None - - if is_terminal is None: - # Allow a environment variable override - is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None) - - if is_terminal is None: # pragma: no cover - # Bare except because a lot can go wrong on different systems. If we do - # get a TTY we know this is a valid terminal - try: - is_terminal = fd.isatty() - except Exception: - is_terminal = False - - return bool(is_terminal) - def deltas_to_seconds( - *deltas, - default: types.Optional[types.Type[ValueError]] = ValueError, + *deltas, + default: types.Optional[types.Type[ValueError]] = ValueError, ) -> int | float | None: ''' Convert timedeltas and seconds as int to seconds as float while coalescing. @@ -185,32 +114,6 @@ def len_color(value: types.StringTypes) -> int: return len(no_color(value)) -@typing.overload -def env_flag(name: str, default: bool) -> bool: - ... - - -@typing.overload -def env_flag(name: str, default: bool | None = None) -> bool | None: - ... - - -def env_flag(name, default=None): - ''' - Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, - on/off, and returns it as a boolean. - - If the environment variable is not defined, or has an unknown value, - returns `default` - ''' - v = os.getenv(name) - if v and v.lower() in ('y', 'yes', 't', 'true', 'on', '1'): - return True - if v and v.lower() in ('n', 'no', 'f', 'false', 'off', '0'): - return False - return default - - class WrappingIO: buffer: io.StringIO target: base.IO @@ -219,10 +122,10 @@ class WrappingIO: needs_clear: bool = False def __init__( - self, - target: base.IO, - capturing: bool = False, - listeners: types.Optional[types.Set[ProgressBar]] = None, + self, + target: base.IO, + capturing: bool = False, + listeners: types.Optional[types.Set[ProgressBar]] = None, ) -> None: self.buffer = io.StringIO() self.target = target @@ -313,10 +216,10 @@ def __iter__(self) -> Iterator[str]: return self.target.__iter__() def __exit__( - self, - __t: type[BaseException] | None, - __value: BaseException | None, - __traceback: TracebackType | None, + self, + __t: type[BaseException] | None, + __value: BaseException | None, + __traceback: TracebackType | None, ) -> None: self.close() @@ -334,11 +237,6 @@ class StreamWrapper: ], None, ] - # original_excepthook: types.Callable[ - # [ - # types.Type[BaseException], - # BaseException, TracebackType | None, - # ], None] | None wrapped_stdout: int = 0 wrapped_stderr: int = 0 wrapped_excepthook: int = 0 @@ -355,10 +253,10 @@ def __init__(self): self.capturing = 0 self.listeners = set() - if env_flag('WRAP_STDOUT', default=False): # pragma: no cover + if env.env_flag('WRAP_STDOUT', default=False): # pragma: no cover self.wrap_stdout() - if env_flag('WRAP_STDERR', default=False): # pragma: no cover + if env.env_flag('WRAP_STDERR', default=False): # pragma: no cover self.wrap_stderr() def start_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index a317d907..4e5d1493 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -917,7 +917,7 @@ def __call__( continue temporary_data['value'] = value - if width := progress.custom_len( + if width := progress.custom_len( # pragma: no branch FormatWidgetMixin.__call__( self, progress, diff --git a/pytest.ini b/pytest.ini index 6f47a01a..e6ab0af1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,8 +5,8 @@ python_files = addopts = --cov progressbar - --cov-report term-missing --cov-report html + --cov-report term-missing --no-cov-on-fail --doctest-modules diff --git a/tests/conftest.py b/tests/conftest.py index d2a91261..6a53b802 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,7 +25,9 @@ def pytest_configure(config): def small_interval(monkeypatch): # Remove the update limit for tests by default monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1e-6, + progressbar.ProgressBar, + '_MINIMUM_UPDATE_INTERVAL', + 1e-6, ) monkeypatch.setattr(timeit, 'default_timer', time.time) diff --git a/tests/test_color.py b/tests/test_color.py index 4683cfec..e88cb091 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -4,7 +4,11 @@ import progressbar import pytest -from progressbar import terminal, widgets + +import progressbar.terminal +import progressbar.env +from progressbar import env, terminal, widgets +from progressbar.terminal import Colors, apply_colors, colors @pytest.mark.parametrize( @@ -16,9 +20,9 @@ ) def test_color_environment_variables(monkeypatch, variable): monkeypatch.setattr( - terminal, - 'color_support', - terminal.ColorSupport.XTERM_256, + env, + 'COLOR_SUPPORT', + progressbar.env.ColorSupport.XTERM_256, ) monkeypatch.setenv(variable, '1') @@ -30,6 +34,46 @@ def test_color_environment_variables(monkeypatch, variable): assert not bar.enable_colors +@pytest.mark.parametrize( + 'variable', + [ + 'FORCE_COLOR', + 'PROGRESSBAR_ENABLE_COLORS', + 'COLORTERM', + 'TERM', + ] +) +@pytest.mark.parametrize( + 'value', + [ + '', + 'truecolor', + '24bit', + '256', + 'xterm-256', + 'xterm', + ] + ) +def test_color_support_from_env(monkeypatch, variable, value): + monkeypatch.setenv('JUPYTER_COLUMNS', '') + monkeypatch.setenv('JUPYTER_LINES', '') + + monkeypatch.setenv(variable, value) + progressbar.env.ColorSupport.from_env() + + +@pytest.mark.parametrize( + 'variable', + [ + 'JUPYTER_COLUMNS', + 'JUPYTER_LINES', + ], +) +def test_color_support_from_env_jupyter(monkeypatch, variable): + monkeypatch.setenv(variable, '80') + progressbar.env.ColorSupport.from_env() + + def test_enable_colors_flags(): bar = progressbar.ProgressBar(enable_colors=True) assert bar.enable_colors @@ -38,7 +82,7 @@ def test_enable_colors_flags(): assert not bar.enable_colors bar = progressbar.ProgressBar( - enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR, + enable_colors=progressbar.env.ColorSupport.XTERM_TRUECOLOR, ) assert bar.enable_colors @@ -47,7 +91,9 @@ def test_enable_colors_flags(): class _TestFixedColorSupport(progressbar.widgets.WidgetBase): - _fixed_colors: typing.ClassVar[widgets.TFixedColors] = widgets.TFixedColors( + _fixed_colors: typing.ClassVar[ + widgets.TFixedColors + ] = widgets.TFixedColors( fg_none=progressbar.widgets.colors.yellow, bg_none=None, ) @@ -58,7 +104,8 @@ def __call__(self, *args, **kwargs): class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): _gradient_colors: typing.ClassVar[ - widgets.TGradientColors] = widgets.TGradientColors( + widgets.TGradientColors + ] = widgets.TGradientColors( fg=progressbar.widgets.colors.gradient, bg=None, ) @@ -81,6 +128,27 @@ def test_color_widgets(widget): print(f'{widget} has colors? {widget.uses_colors}') +def test_color_gradient(): + gradient = terminal.ColorGradient(colors.red) + assert gradient.get_color(0) == gradient.get_color(-1) + assert gradient.get_color(1) == gradient.get_color(2) + + assert gradient.get_color(0.5) == colors.red + + gradient = terminal.ColorGradient(colors.red, colors.yellow) + assert gradient.get_color(0) == colors.red + assert gradient.get_color(1) == colors.yellow + assert gradient.get_color(0.5) != colors.red + assert gradient.get_color(0.5) != colors.yellow + + gradient = terminal.ColorGradient( + colors.red, colors.yellow, interpolate=False, + ) + assert gradient.get_color(0) == colors.red + assert gradient.get_color(1) == colors.yellow + assert gradient.get_color(0.5) == colors.red + + @pytest.mark.parametrize( 'widget', [ @@ -97,3 +165,138 @@ def test_no_color_widgets(widget): assert widget( gradient_colors=_TestFixedGradientSupport._gradient_colors, ).uses_colors + + +def test_colors(): + for colors_ in Colors.by_rgb.values(): + for color in colors_: + rgb = color.rgb + assert rgb.rgb + assert rgb.hex + assert rgb.to_ansi_16 is not None + assert rgb.to_ansi_256 is not None + assert color.underline + assert color.fg + assert color.bg + assert str(color) + assert str(rgb) + + +def test_color(): + color = colors.red + assert color('x') == color.fg('x') != 'x' + assert color.fg('x') != color.bg('x') != 'x' + assert color.fg('x') != color.underline('x') != 'x' + # Color hashes are based on the RGB value + assert hash(color) == hash(terminal.Color(color.rgb, None, None, None)) + Colors.register(color.rgb) + + +@pytest.mark.parametrize( + 'rgb,hls', + [ + (terminal.RGB(0, 0, 0), terminal.HSL(0, 0, 0)), + (terminal.RGB(255, 255, 255), terminal.HSL(0, 0, 100)), + (terminal.RGB(255, 0, 0), terminal.HSL(0, 100, 50)), + (terminal.RGB(0, 255, 0), terminal.HSL(120, 100, 50)), + (terminal.RGB(0, 0, 255), terminal.HSL(240, 100, 50)), + (terminal.RGB(255, 255, 0), terminal.HSL(60, 100, 50)), + (terminal.RGB(0, 255, 255), terminal.HSL(180, 100, 50)), + (terminal.RGB(255, 0, 255), terminal.HSL(300, 100, 50)), + (terminal.RGB(128, 128, 128), terminal.HSL(0, 0, 50)), + (terminal.RGB(128, 0, 0), terminal.HSL(0, 100, 25)), + (terminal.RGB(128, 128, 0), terminal.HSL(60, 100, 25)), + (terminal.RGB(0, 128, 0), terminal.HSL(120, 100, 25)), + (terminal.RGB(128, 0, 128), terminal.HSL(300, 100, 25)), + (terminal.RGB(0, 128, 128), terminal.HSL(180, 100, 25)), + (terminal.RGB(0, 0, 128), terminal.HSL(240, 100, 25)), + (terminal.RGB(192, 192, 192), terminal.HSL(0, 0, 75)), + ], +) +def test_rgb_to_hls(rgb, hls): + assert terminal.HSL.from_rgb(rgb) == hls + + +@pytest.mark.parametrize( + 'text, fg, bg, fg_none, bg_none, percentage, expected', + [ + ('test', None, None, None, None, None, 'test'), + ('test', None, None, None, None, 1, 'test'), + ( + 'test', + None, + None, + None, + colors.red, + None, + '\x1b[48;5;9mtest\x1b[49m', + ), + ( + 'test', + None, + colors.green, + None, + colors.red, + None, + '\x1b[48;5;9mtest\x1b[49m', + ), + ('test', None, colors.red, None, None, 1, '\x1b[48;5;9mtest\x1b[49m'), + ('test', None, colors.red, None, None, None, 'test'), + ( + 'test', + colors.green, + None, + colors.red, + None, + None, + '\x1b[38;5;9mtest\x1b[39m', + ), + ( + 'test', + colors.green, + colors.red, + None, + None, + 1, + '\x1b[48;5;9m\x1b[38;5;2mtest\x1b[39m\x1b[49m', + ), + ('test', colors.red, None, None, None, 1, '\x1b[38;5;9mtest\x1b[39m'), + ('test', colors.red, None, None, None, None, 'test'), + ('test', colors.red, colors.red, None, None, None, 'test'), + ( + 'test', + colors.red, + colors.yellow, + None, + None, + 1, + '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', + ), + ( + 'test', + colors.red, + colors.yellow, + None, + None, + 1, + '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', + ), + ], +) +def test_apply_colors(text, fg, bg, fg_none, bg_none, percentage, expected, monkeypatch): + monkeypatch.setattr( + env, + 'COLOR_SUPPORT', + progressbar.env.ColorSupport.XTERM_256, + ) + assert ( + apply_colors( + text, + fg=fg, + bg=bg, + fg_none=fg_none, + bg_none=bg_none, + percentage=percentage, + ) + == expected + ) diff --git a/tests/test_end.py b/tests/test_end.py index b8cbc309..e5af3f60 100644 --- a/tests/test_end.py +++ b/tests/test_end.py @@ -6,14 +6,17 @@ def large_interval(monkeypatch): # Remove the update limit for tests by default monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 0.1, + progressbar.ProgressBar, + '_MINIMUM_UPDATE_INTERVAL', + 0.1, ) def test_end(): m = 24514315 p = progressbar.ProgressBar( - widgets=[progressbar.Percentage(), progressbar.Bar()], max_value=m, + widgets=[progressbar.Percentage(), progressbar.Bar()], + max_value=m, ) for x in range(0, m, 8192): diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index 4d19eea8..71052546 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -28,11 +28,11 @@ def _non_empty_lines(lines): def _create_script( - widgets=None, - items=None, - loop_code='fake_time.tick(1)', - term_width=60, - **kwargs, + widgets=None, + items=None, + loop_code='fake_time.tick(1)', + term_width=60, + **kwargs, ): if items is None: items = list(range(9)) diff --git a/tests/test_multibar.py b/tests/test_multibar.py index 0798bae1..1d52f9c6 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -159,4 +159,4 @@ def test_multibar_empty_key(): bar = multibar[name] bar.update(1) - multibar.render(force=True) \ No newline at end of file + multibar.render(force=True) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index 14ead38a..bc94327b 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -23,7 +23,6 @@ def test_examples(monkeypatch): example() - @pytest.mark.filterwarnings('ignore:.*maxval.*:DeprecationWarning') @pytest.mark.parametrize('example', original_examples.examples) def test_original_examples(example, monkeypatch): diff --git a/tests/test_stream.py b/tests/test_stream.py index f641b662..127a6a07 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -3,6 +3,9 @@ import progressbar import pytest +from progressbar import terminal + +from progressbar.terminal.stream import LastLineStream def test_nowrap(): @@ -101,3 +104,46 @@ def test_fd_as_standard_streams(stream): with progressbar.ProgressBar(fd=stream) as pb: for i in range(101): pb.update(i) + + +def test_line_offset_stream_wrapper(): + stream = terminal.LineOffsetStreamWrapper(5, io.StringIO()) + stream.write('Hello World!') + + +def test_last_line_stream_methods(): + stream = terminal.LastLineStream(io.StringIO()) + + # Test write method + stream.write('Hello World!') + assert stream.read() == 'Hello World!' + assert stream.read(5) == 'Hello' + + # Test flush method + stream.flush() + assert stream.line == 'Hello World!' + assert stream.readline() == 'Hello World!' + assert stream.readline(5) == 'Hello' + + # Test truncate method + stream.truncate(5) + assert stream.line == 'Hello' + stream.truncate() + assert stream.line == '' + + # Test seekable/readable + assert not stream.seekable() + assert stream.readable() + + stream.writelines(['a', 'b', 'c']) + assert stream.read() == 'c' + + assert list(stream) == ['c'] + + with stream: + stream.write('Hello World!') + assert stream.read() == 'Hello World!' + assert stream.read(5) == 'Hello' + + # Test close method + stream.close() \ No newline at end of file diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 0f2620b0..ad61b7fa 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -4,6 +4,7 @@ from datetime import timedelta import progressbar +from progressbar import terminal def test_left_justify(): @@ -95,7 +96,9 @@ def test_no_fill(monkeypatch): bar = progressbar.BouncingBar() bar.INTERVAL = timedelta(seconds=1) p = progressbar.ProgressBar( - widgets=[bar], max_value=progressbar.UnknownLength, term_width=20, + widgets=[bar], + max_value=progressbar.UnknownLength, + term_width=20, ) assert p.term_width is not None @@ -107,7 +110,9 @@ def test_no_fill(monkeypatch): def test_stdout_redirection(): p = progressbar.ProgressBar( - fd=sys.stdout, max_value=10, redirect_stdout=True, + fd=sys.stdout, + max_value=10, + redirect_stdout=True, ) for i in range(10): @@ -135,7 +140,9 @@ def test_stderr_redirection(): def test_stdout_stderr_redirection(): p = progressbar.ProgressBar( - max_value=10, redirect_stdout=True, redirect_stderr=True, + max_value=10, + redirect_stdout=True, + redirect_stderr=True, ) p.start() @@ -171,3 +178,11 @@ def fake_signal(signal, func): p.finish() except ImportError: pass # Skip on Windows + + +def test_base(): + assert str(terminal.CUP) + assert str(terminal.CLEAR_SCREEN_ALL_AND_HISTORY) + + terminal.clear_line(0) + terminal.clear_line(1) diff --git a/tests/test_timed.py b/tests/test_timed.py index 385391a5..4d71ec64 100644 --- a/tests/test_timed.py +++ b/tests/test_timed.py @@ -10,7 +10,9 @@ def test_timer(): progressbar.Timer(), ] p = progressbar.ProgressBar( - max_value=2, widgets=widgets, poll_interval=0.0001, + max_value=2, + widgets=widgets, + poll_interval=0.0001, ) p.start() @@ -28,7 +30,10 @@ def test_eta(): progressbar.ETA(), ] p = progressbar.ProgressBar( - min_value=0, max_value=2, widgets=widgets, poll_interval=0.0001, + min_value=0, + max_value=2, + widgets=widgets, + poll_interval=0.0001, ) p.start() @@ -72,7 +77,9 @@ def test_adaptive_transfer_speed(): progressbar.AdaptiveTransferSpeed(), ] p = progressbar.ProgressBar( - max_value=2, widgets=widgets, poll_interval=0.0001, + max_value=2, + widgets=widgets, + poll_interval=0.0001, ) p.start() @@ -105,7 +112,9 @@ def calculate_eta(self, value, elapsed): monkeypatch.setattr(progressbar.FileTransferSpeed, '_speed', calculate_eta) monkeypatch.setattr( - progressbar.AdaptiveTransferSpeed, '_speed', calculate_eta, + progressbar.AdaptiveTransferSpeed, + '_speed', + calculate_eta, ) for widget in widgets: @@ -150,7 +159,9 @@ def test_non_changing_eta(): progressbar.AdaptiveTransferSpeed(), ] p = progressbar.ProgressBar( - max_value=2, widgets=widgets, poll_interval=0.0001, + max_value=2, + widgets=widgets, + poll_interval=0.0001, ) p.start() diff --git a/tests/test_timer.py b/tests/test_timer.py index dc928786..b6cab792 100644 --- a/tests/test_timer.py +++ b/tests/test_timer.py @@ -35,7 +35,9 @@ def test_poll_interval(parameter, poll_interval, expected): ) def test_intervals(monkeypatch, interval): monkeypatch.setattr( - progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', interval, + progressbar.ProgressBar, + '_MINIMUM_UPDATE_INTERVAL', + interval, ) bar = progressbar.ProgressBar(max_value=100) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index 674bdcc4..98c740f3 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -1,4 +1,3 @@ - import time import progressbar diff --git a/tests/test_utils.py b/tests/test_utils.py index 6f28aeb6..dd51e5cd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,8 @@ import progressbar import pytest +import progressbar.env + @pytest.mark.parametrize( 'value,expected', @@ -24,11 +26,11 @@ def test_env_flag(value, expected, monkeypatch): if value is not None: monkeypatch.setenv('TEST_ENV', value) - assert progressbar.utils.env_flag('TEST_ENV') == expected + assert progressbar.env.env_flag('TEST_ENV') == expected if value: monkeypatch.setenv('TEST_ENV', value.upper()) - assert progressbar.utils.env_flag('TEST_ENV') == expected + assert progressbar.env.env_flag('TEST_ENV') == expected monkeypatch.undo() @@ -39,25 +41,25 @@ def test_is_terminal(monkeypatch): monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) monkeypatch.delenv('JPY_PARENT_PID', raising=False) - assert progressbar.utils.is_terminal(fd) is False - assert progressbar.utils.is_terminal(fd, True) is True - assert progressbar.utils.is_terminal(fd, False) is False + assert progressbar.env.is_terminal(fd) is False + assert progressbar.env.is_terminal(fd, True) is True + assert progressbar.env.is_terminal(fd, False) is False monkeypatch.setenv('JPY_PARENT_PID', '123') - assert progressbar.utils.is_terminal(fd) is True + assert progressbar.env.is_terminal(fd) is True monkeypatch.delenv('JPY_PARENT_PID') # Sanity check - assert progressbar.utils.is_terminal(fd) is False + assert progressbar.env.is_terminal(fd) is False monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') - assert progressbar.utils.is_terminal(fd) is True + assert progressbar.env.is_terminal(fd) is True monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') - assert progressbar.utils.is_terminal(fd) is False + assert progressbar.env.is_terminal(fd) is False monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') # Sanity check - assert progressbar.utils.is_terminal(fd) is False + assert progressbar.env.is_terminal(fd) is False def test_is_ansi_terminal(monkeypatch): @@ -66,22 +68,22 @@ def test_is_ansi_terminal(monkeypatch): monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) monkeypatch.delenv('JPY_PARENT_PID', raising=False) - assert progressbar.utils.is_ansi_terminal(fd) is False - assert progressbar.utils.is_ansi_terminal(fd, True) is True - assert progressbar.utils.is_ansi_terminal(fd, False) is False + assert progressbar.env.is_ansi_terminal(fd) is False + assert progressbar.env.is_ansi_terminal(fd, True) is True + assert progressbar.env.is_ansi_terminal(fd, False) is False monkeypatch.setenv('JPY_PARENT_PID', '123') - assert progressbar.utils.is_ansi_terminal(fd) is True + assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.delenv('JPY_PARENT_PID') # Sanity check - assert progressbar.utils.is_ansi_terminal(fd) is False + assert progressbar.env.is_ansi_terminal(fd) is False monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') - assert progressbar.utils.is_ansi_terminal(fd) is False + assert progressbar.env.is_ansi_terminal(fd) is False monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') - assert progressbar.utils.is_ansi_terminal(fd) is False + assert progressbar.env.is_ansi_terminal(fd) is False monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') # Sanity check - assert progressbar.utils.is_ansi_terminal(fd) is False + assert progressbar.env.is_ansi_terminal(fd) is False diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 467c6e5f..9872f0be 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -145,7 +145,9 @@ def test_all_widgets_min_width(min_width, term_width): progressbar.ReverseBar(min_width=min_width), progressbar.BouncingBar(min_width=min_width), progressbar.FormatCustomText( - 'Custom %(text)s', dict(text='text'), min_width=min_width, + 'Custom %(text)s', + dict(text='text'), + min_width=min_width, ), progressbar.DynamicMessage('custom', min_width=min_width), progressbar.CurrentTime(min_width=min_width), @@ -180,7 +182,9 @@ def test_all_widgets_max_width(max_width, term_width): progressbar.ReverseBar(max_width=max_width), progressbar.BouncingBar(max_width=max_width), progressbar.FormatCustomText( - 'Custom %(text)s', dict(text='text'), max_width=max_width, + 'Custom %(text)s', + dict(text='text'), + max_width=max_width, ), progressbar.DynamicMessage('custom', max_width=max_width), progressbar.CurrentTime(max_width=max_width), From 4f1efba8577c225d5b4e4280ae004d8762674f0b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 23 Nov 2023 17:44:06 +0100 Subject: [PATCH 105/118] ignoring irrelevant edge-cases from coverage --- progressbar/multi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/progressbar/multi.py b/progressbar/multi.py index 679dc537..3e105e3c 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -186,7 +186,7 @@ def render(self, flush: bool = True, force: bool = False): with self._print_lock: # Clear the previous output if progressbars have been removed for i in range(len(output), len(self._previous_output)): - self._buffer.write(terminal.clear_line(i + 1)) + self._buffer.write(terminal.clear_line(i + 1)) # pragma: no cover # Add empty lines to the end of the output if progressbars have # been added @@ -201,7 +201,7 @@ def render(self, flush: bool = True, force: bool = False): fillvalue='', ), ): - if previous != current or force: + if previous != current or force: # pragma: no branch self.print( '\r' + current.strip(), offset=i + 1, @@ -212,7 +212,7 @@ def render(self, flush: bool = True, force: bool = False): self._previous_output = output - if flush: + if flush: # pragma: no branch self.flush() def _render_bar( From 200aaf78842536f15c60c081451c6fc688534c61 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 23 Nov 2023 17:44:37 +0100 Subject: [PATCH 106/118] added job status widget to fix #285 --- examples.py | 12 + progressbar/__init__.py | 2 + progressbar/widgets.py | 552 ++++++++++++++++++++++++---------------- 3 files changed, 348 insertions(+), 218 deletions(-) diff --git a/examples.py b/examples.py index 569c1acf..905541fa 100644 --- a/examples.py +++ b/examples.py @@ -56,6 +56,18 @@ def templated_shortcut_example(): time.sleep(0.1) +@example +def job_status_example(): + with progressbar.ProgressBar( + redirect_stdout=True, + widgets=[progressbar.widgets.JobStatusBar('status')], + ) as bar: + for i in range(30): + print('random', random.random()) + bar.increment(status=random.random() > 0.5) + time.sleep(0.1) + + @example def with_example_stdout_redirection(): with progressbar.ProgressBar(max_value=10, redirect_stdout=True) as p: diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 1de833d0..22b26c55 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -34,6 +34,7 @@ Timer, Variable, VariableMixin, + JobStatusBar, ) __date__ = str(date.today()) @@ -76,4 +77,5 @@ 'LineOffsetStreamWrapper', 'MultiBar', 'SortKey', + 'JobStatusBar', ] diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 4e5d1493..41dd9a95 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -6,7 +6,6 @@ import functools import logging import typing - # Ruff is being stupid and doesn't understand `ClassVar` if it comes from the # `types` module from typing import ClassVar @@ -89,8 +88,8 @@ def wrap(*args, **kwargs): def create_marker(marker, wrap=None): def _marker(progress, data, width): if ( - progress.max_value is not base.UnknownLength - and progress.max_value > 0 + progress.max_value is not base.UnknownLength + and progress.max_value > 0 ): length = int(progress.value / progress.max_value * width) return marker * length @@ -100,7 +99,7 @@ def _marker(progress, data, width): if isinstance(marker, str): marker = converters.to_unicode(marker) assert ( - utils.len_color(marker) == 1 + utils.len_color(marker) == 1 ), 'Markers are required to be 1 char' return wrapper(_marker, wrap) else: @@ -128,18 +127,18 @@ def __init__(self, format: str, new_style: bool = False, **kwargs): self.format = format def get_format( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: return format or self.format def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: '''Formats the widget into a string.''' format_ = self.get_format(progress, data, format) @@ -274,11 +273,11 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, - *args, - fixed_colors=None, - gradient_colors=None, - **kwargs, + self, + *args, + fixed_colors=None, + gradient_colors=None, + **kwargs, ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -302,10 +301,10 @@ class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): @abc.abstractmethod def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ) -> str: '''Updates the widget providing the total width the widget must fill. @@ -351,10 +350,10 @@ def __init__(self, format: str, **kwargs): WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): for name, (key, transform) in self.mapping.items(): with contextlib.suppress(KeyError, ValueError, IndexError): @@ -413,10 +412,10 @@ class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): ''' def __init__( - self, - samples=datetime.timedelta(seconds=2), - key_prefix=None, - **kwargs, + self, + samples=datetime.timedelta(seconds=2), + key_prefix=None, + **kwargs, ): self.samples = samples self.key_prefix = (key_prefix or self.__class__.__name__) + '_' @@ -433,10 +432,10 @@ def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): ) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - delta: bool = False, + self, + progress: ProgressBarMixinBase, + data: Data, + delta: bool = False, ): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -455,9 +454,9 @@ def __call__( minimum_time = progress.last_update_time - self.samples minimum_value = sample_values[-1] while ( - sample_times[2:] - and minimum_time > sample_times[1] - and minimum_value > sample_values[1] + sample_times[2:] + and minimum_time > sample_times[1] + and minimum_value > sample_values[1] ): sample_times.pop(0) sample_values.pop(0) @@ -479,13 +478,13 @@ class ETA(Timer): '''WidgetBase which attempts to estimate the time of arrival.''' def __init__( - self, - format_not_started='ETA: --:--:--', - format_finished='Time: %(elapsed)8s', - format='ETA: %(eta)8s', - format_zero='ETA: 00:00:00', - format_na='ETA: N/A', - **kwargs, + self, + format_not_started='ETA: --:--:--', + format_finished='Time: %(elapsed)8s', + format='ETA: %(eta)8s', + format_zero='ETA: 00:00:00', + format_na='ETA: N/A', + **kwargs, ): if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -498,11 +497,11 @@ def __init__( self.format_NA = format_na def _calculate_eta( - self, - progress: ProgressBarMixinBase, - data: Data, - value, - elapsed, + self, + progress: ProgressBarMixinBase, + data: Data, + value, + elapsed, ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: @@ -514,11 +513,11 @@ def _calculate_eta( return 0 def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): '''Updates the widget to show the ETA or total time when finished.''' if value is None: @@ -562,11 +561,11 @@ class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' def _calculate_eta( - self, - progress: ProgressBarMixinBase, - data: Data, - value, - elapsed, + self, + progress: ProgressBarMixinBase, + data: Data, + value, + elapsed, ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() @@ -576,11 +575,11 @@ def _calculate_eta( return datetime.datetime.max def __init__( - self, - format_not_started='Estimated finish time: ----/--/-- --:--:--', - format_finished='Finished at: %(elapsed)s', - format='Estimated finish time: %(eta)s', - **kwargs, + self, + format_not_started='Estimated finish time: ----/--/-- --:--:--', + format_finished='Finished at: %(elapsed)s', + format='Estimated finish time: %(eta)s', + **kwargs, ): ETA.__init__( self, @@ -603,11 +602,11 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, @@ -631,12 +630,12 @@ class DataSize(FormatWidgetMixin, WidgetBase): ''' def __init__( - self, - variable='value', - format='%(scaled)5.1f %(prefix)s%(unit)s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + variable='value', + format='%(scaled)5.1f %(prefix)s%(unit)s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.variable = variable self.unit = unit @@ -645,10 +644,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data[self.variable] if value is not None: @@ -669,12 +668,12 @@ class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' def __init__( - self, - format='%(scaled)5.1f %(prefix)s%(unit)-s/s', - inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + format='%(scaled)5.1f %(prefix)s%(unit)-s/s', + inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.unit = unit self.prefixes = prefixes @@ -687,11 +686,11 @@ def _speed(self, value, elapsed): return utils.scale_1024(speed, len(self.prefixes)) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): '''Updates the widget with the current SI prefixed speed.''' if value is None: @@ -703,10 +702,10 @@ def __call__( ) if ( - value is not None - and elapsed is not None - and elapsed > 2e-6 - and value > 2e-6 + value is not None + and elapsed is not None + and elapsed > 2e-6 + and value > 2e-6 ): # =~ 0 scaled, power = self._speed(value, elapsed) else: @@ -738,11 +737,11 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, @@ -759,13 +758,13 @@ class AnimatedMarker(TimeSensitiveWidgetBase): ''' def __init__( - self, - markers='|/-\\', - default=None, - fill='', - marker_wrap=None, - fill_wrap=None, - **kwargs, + self, + markers='|/-\\', + default=None, + fill='', + marker_wrap=None, + fill_wrap=None, + **kwargs, ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) @@ -818,10 +817,10 @@ def __init__(self, format='%(value)d', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): return FormatWidgetMixin.__call__(self, progress, data, format) @@ -849,10 +848,10 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def get_format( - self, - progress: ProgressBarMixinBase, - data: Data, - format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) @@ -880,10 +879,10 @@ def __init__(self, format=DEFAULT_FORMAT, **kwargs): self.max_width_cache = dict(default=self.max_width or 0) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): # If max_value is not available, display N/A if data.get('max_value'): @@ -918,12 +917,12 @@ def __call__( temporary_data['value'] = value if width := progress.custom_len( # pragma: no branch - FormatWidgetMixin.__call__( - self, - progress, - temporary_data, - format=format, - ), + FormatWidgetMixin.__call__( + self, + progress, + temporary_data, + format=format, + ), ): max_width = max(max_width or 0, width) @@ -943,14 +942,14 @@ class Bar(AutoWidthWidgetBase): bg: terminal.OptionalColor | None = None def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=True, - marker_wrap=None, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=True, + marker_wrap=None, + **kwargs, ): '''Creates a customizable progress bar. @@ -971,11 +970,11 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1002,13 +1001,13 @@ class ReverseBar(Bar): '''A bar which has a marker that goes from right to left.''' def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=False, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=False, + **kwargs, ): '''Creates a customizable progress bar. @@ -1035,11 +1034,11 @@ class BouncingBar(Bar, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(milliseconds=100) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1072,10 +1071,10 @@ class FormatCustomText(FormatWidgetMixin, WidgetBase): copy = False def __init__( - self, - format: str, - mapping: types.Optional[types.Dict[str, types.Any]] = None, - **kwargs, + self, + format: str, + mapping: types.Optional[types.Dict[str, types.Any]] = None, + **kwargs, ): self.format = format self.mapping = mapping or self.mapping @@ -1086,10 +1085,10 @@ def update_mapping(self, **mapping: types.Dict[str, types.Any]): self.mapping.update(mapping) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): return FormatWidgetMixin.__call__( self, @@ -1134,11 +1133,11 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): return data['variables'][self.name] or [] def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1170,12 +1169,12 @@ def __call__( class MultiProgressBar(MultiRangeBar): def __init__( - self, - name, - # NOTE: the markers are not whitespace even though some - # terminals don't show the characters correctly! - markers=' ▁▂▃▄▅▆▇█', - **kwargs, + self, + name, + # NOTE: the markers are not whitespace even though some + # terminals don't show the characters correctly! + markers=' ▁▂▃▄▅▆▇█', + **kwargs, ): MultiRangeBar.__init__( self, @@ -1236,11 +1235,11 @@ class GranularBar(AutoWidthWidgetBase): ''' def __init__( - self, - markers=GranularMarkers.smooth, - left='|', - right='|', - **kwargs, + self, + markers=GranularMarkers.smooth, + left='|', + right='|', + **kwargs, ): '''Creates a customizable progress bar. @@ -1257,10 +1256,10 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) @@ -1270,8 +1269,8 @@ def __call__( # mypy doesn't get that the first part of the if statement makes sure # we get the correct type if ( - max_value is not base.UnknownLength - and max_value > 0 # type: ignore + max_value is not base.UnknownLength + and max_value > 0 # type: ignore ): percent = progress.value / max_value # type: ignore else: @@ -1301,11 +1300,11 @@ def __init__(self, format, **kwargs): Bar.__init__(self, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) bar = Bar.__call__(self, progress, data, width, color=False) @@ -1316,18 +1315,18 @@ def __call__( # type: ignore center_right = center_left + center_len return ( - self._apply_colors( - bar[:center_left], - data, - ) - + self._apply_colors( - center, - data, - ) - + self._apply_colors( - bar[center_right:], - data, - ) + self._apply_colors( + bar[:center_left], + data, + ) + + self._apply_colors( + center, + data, + ) + + self._apply_colors( + bar[center_right:], + data, + ) ) @@ -1341,11 +1340,11 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): FormatLabelBar.__init__(self, format, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): return super().__call__(progress, data, width, format=format) @@ -1354,12 +1353,12 @@ class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' def __init__( - self, - name, - format='{name}: {formatted_value}', - width=6, - precision=3, - **kwargs, + self, + name, + format='{name}: {formatted_value}', + width=6, + precision=3, + **kwargs, ): '''Creates a Variable associated with the given name.''' self.format = format @@ -1369,10 +1368,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data['variables'][self.name] context = data.copy() @@ -1408,20 +1407,20 @@ class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(seconds=1) def __init__( - self, - format='Current Time: %(current_time)s', - microseconds=False, - **kwargs, + self, + format='Current Time: %(current_time)s', + microseconds=False, + **kwargs, ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): data['current_time'] = self.current_time() data['current_datetime'] = self.current_datetime() @@ -1437,3 +1436,120 @@ def current_datetime(self): def current_time(self): return self.current_datetime().time() + + +class JobStatusBar(Bar, VariableMixin): + ''' + Widget which displays the job status as markers on the bar. + + The status updates can be given either as a boolean or as a string. If it's + a string, it will be displayed as-is. If it's a boolean, it will be + displayed as a marker (default: '█' for success, 'X' for failure) + configurable through the `success_marker` and `failure_marker` parameters. + + Args: + name: The name of the variable to use for the status updates. + left: The left border of the bar. + right: The right border of the bar. + fill: The fill character of the bar. + fill_left: Whether to fill the bar from the left or the right. + success_fg_color: The foreground color to use for successful jobs. + success_bg_color: The background color to use for successful jobs. + success_marker: The marker to use for successful jobs. + failure_fg_color: The foreground color to use for failed jobs. + failure_bg_color: The background color to use for failed jobs. + failure_marker: The marker to use for failed jobs. + ''' + + success_fg_color: terminal.OptionalColor | None = colors.green + success_bg_color: terminal.OptionalColor | None = None + success_marker: str = '█' + failure_fg_color: terminal.OptionalColor | None = colors.red + failure_bg_color: terminal.OptionalColor | None = None + failure_marker: str = 'X' + job_markers: list[str] + + def __init__( + self, + name: str, + left='|', + right='|', + fill=' ', + fill_left=True, + success_fg_color=colors.green, + success_bg_color=None, + success_marker='█', + failure_fg_color=colors.red, + failure_bg_color=None, + failure_marker='X', + **kwargs, + ): + VariableMixin.__init__(self, name) + self.name = name + self.job_markers = [] + self.left = string_or_lambda(left) + self.right = string_or_lambda(right) + self.fill = string_or_lambda(fill) + self.success_fg_color = success_fg_color + self.success_bg_color = success_bg_color + self.success_marker = success_marker + self.failure_fg_color = failure_fg_color + self.failure_bg_color = failure_bg_color + self.failure_marker = failure_marker + + Bar.__init__( + self, + left=left, + right=right, + fill=fill, + fill_left=fill_left, + **kwargs, + ) + + def __call__( + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, + ): + left = converters.to_unicode(self.left(progress, data, width)) + right = converters.to_unicode(self.right(progress, data, width)) + width -= progress.custom_len(left) + progress.custom_len(right) + + status: str | bool | None = data['variables'].get(self.name) + + if width and status is not None: + if status is True: + marker = self.success_marker + fg_color = self.success_fg_color + bg_color = self.success_bg_color + elif status is False: + marker = self.failure_marker + fg_color = self.failure_fg_color + bg_color = self.failure_bg_color + else: + marker = status + fg_color = bg_color = None + + marker = converters.to_unicode(marker) + if fg_color: + marker = fg_color.fg(marker) + if bg_color: + marker = bg_color.bg(marker) + + self.job_markers.append(marker) + marker = ''.join(self.job_markers) + width -= progress.custom_len(marker) + + fill = converters.to_unicode(self.fill(progress, data, width)) + fill = self._apply_colors(fill * width, data) + + if self.fill_left: + marker += fill + else: + marker = fill + marker + else: + marker = '' + + return left + marker + right From 609cfc17b895a1a343c75d48d229c70cefbf3a20 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 11 Dec 2023 01:07:35 +0100 Subject: [PATCH 107/118] Improved coverage --- progressbar/multi.py | 9 ++-- pytest.ini | 6 ++- tests/test_multibar.py | 97 ++++++++++++++++++++++++++++++++++++--- tests/test_progressbar.py | 10 ++-- 4 files changed, 103 insertions(+), 19 deletions(-) diff --git a/progressbar/multi.py b/progressbar/multi.py index 5e6fd360..86a2e982 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -223,7 +223,7 @@ def _render_bar( now, expired, ) -> typing.Iterable[str]: - def update(force=True, write=True): + def update(force=True, write=True): # pragma: no cover self._label_bar(bar_) bar_.update(force=force) if write: @@ -325,15 +325,14 @@ def run(self, join=True): Start the multibar render loop and run the progressbars until they have force _thread_finished. ''' - while not self._thread_finished.is_set(): + while not self._thread_finished.is_set(): # pragma: no branch self.render() time.sleep(self.update_interval) if join or self._thread_closed.is_set(): - # If the thread is closed, we need to check if force - # progressbars + # If the thread is closed, we need to check if the progressbars # have finished. If they have, we can exit the loop - for bar_ in self.values(): + for bar_ in self.values(): # pragma: no cover if not bar_.finished(): break else: diff --git a/pytest.ini b/pytest.ini index e6ab0af1..f0e8df40 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,8 +5,10 @@ python_files = addopts = --cov progressbar - --cov-report html - --cov-report term-missing + --cov-report=html + --cov-report=term-missing + --cov-report=xml + --cov-append --no-cov-on-fail --doctest-modules diff --git a/tests/test_multibar.py b/tests/test_multibar.py index 1d52f9c6..8ce99460 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -1,4 +1,5 @@ import threading +import random import time import progressbar @@ -22,12 +23,6 @@ def test_multi_progress_bar_out_of_range(): bar.update(multivalues=[-1]) -def test_multi_progress_bar_fill_left(): - import examples - - return examples.multi_progress_bar_example(False) - - def test_multibar(): multibar = progressbar.MultiBar( sort_keyfunc=lambda bar: bar.label, @@ -160,3 +155,93 @@ def test_multibar_empty_key(): bar.update(1) multibar.render(force=True) + + +def test_multibar_print(): + + bars = 5 + n = 10 + + + def print_sometimes(bar, probability): + for i in bar(range(n)): + # Sleep up to 0.1 seconds + time.sleep(random.random() * 0.1) + + # print messages at random intervals to show how extra output works + if random.random() < probability: + bar.print('random message for bar', bar, i) + + with progressbar.MultiBar() as multibar: + for i in range(bars): + # Get a progressbar + bar = multibar[f'Thread label here {i}'] + bar.max_error = False + # Create a thread and pass the progressbar + # Print never, sometimes and always + threading.Thread(target=print_sometimes, args=(bar, 0)).start() + threading.Thread(target=print_sometimes, args=(bar, 0.5)).start() + threading.Thread(target=print_sometimes, args=(bar, 1)).start() + + + for i in range(5): + multibar.print(f'{i}', flush=False) + + multibar.update(force=True, flush=False) + multibar.update(force=True, flush=True) + +def test_multibar_no_format(): + with progressbar.MultiBar(initial_format=None, finished_format=None) as multibar: + bar = multibar['a'] + + for i in bar(range(5)): + bar.print(i) + + +def test_multibar_finished(): + multibar = progressbar.MultiBar(initial_format=None, finished_format=None) + bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) + bar2 = multibar['bar2'] + multibar.render(force=True) + multibar.print('Hi') + multibar.render(force=True, flush=False) + + for i in range(6): + bar.update(i) + bar2.update(i) + + multibar.render(force=True) + + + +def test_multibar_finished_format(): + multibar = progressbar.MultiBar(finished_format='Finished {label}', show_finished=True) + bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) + bar2 = multibar['bar2'] + multibar.render(force=True) + multibar.print('Hi') + multibar.render(force=True, flush=False) + bar.start() + bar2.start() + multibar.render(force=True) + multibar.print('Hi') + multibar.render(force=True, flush=False) + + for i in range(6): + bar.update(i) + bar2.update(i) + + multibar.render(force=True) + + +def test_multibar_threads(): + multibar = progressbar.MultiBar(finished_format=None, show_finished=True) + bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) + multibar.start() + time.sleep(0.1) + bar.update(3) + time.sleep(0.1) + multibar.join() + bar.finish() + multibar.join() + multibar.render(force=True) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index d25baa64..f3fb10d7 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -1,3 +1,4 @@ +import os import contextlib import time @@ -11,10 +12,11 @@ except ImportError: import sys - sys.path.append('..') + _project_dir = os.path.dirname(os.path.dirname(__file__)) + sys.path.append(_project_dir) import examples - sys.path.remove('..') + sys.path.remove(_project_dir) def test_examples(monkeypatch): @@ -40,8 +42,6 @@ def test_examples_nullbar(monkeypatch, example): def test_reuse(): - import progressbar - bar = progressbar.ProgressBar() bar.start() for i in range(10): @@ -60,8 +60,6 @@ def test_reuse(): def test_dirty(): - import progressbar - bar = progressbar.ProgressBar() bar.start() assert bar.started() From b8dbc1223adaf2818101361323abbafaa786f18a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 17 Dec 2023 03:15:00 +0100 Subject: [PATCH 108/118] Improved coverage --- .coveragerc | 26 ------- examples.py | 9 ++- progressbar/multi.py | 4 +- progressbar/terminal/colors.py | 3 +- progressbar/widgets.py | 14 ++-- pyproject.toml | 32 ++++++++ pytest.ini | 1 + tests/test_color.py | 135 ++++++++++++++++++++------------- tests/test_job_status.py | 20 +++++ 9 files changed, 153 insertions(+), 91 deletions(-) delete mode 100644 .coveragerc create mode 100644 tests/test_job_status.py diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0dcf6a85..00000000 --- a/.coveragerc +++ /dev/null @@ -1,26 +0,0 @@ -[run] -branch = True -source = - progressbar - tests -omit = - */mock/* - */nose/* - .tox/* -[paths] -source = - progressbar -[report] -fail_under = 100 -exclude_lines = - pragma: no cover - @abc.abstractmethod - def __repr__ - if self.debug: - if settings.DEBUG - raise AssertionError - raise NotImplementedError - if 0: - if __name__ == .__main__.: - if types.TYPE_CHECKING: - @typing.overload diff --git a/examples.py b/examples.py index 905541fa..8b7247c9 100644 --- a/examples.py +++ b/examples.py @@ -64,7 +64,14 @@ def job_status_example(): ) as bar: for i in range(30): print('random', random.random()) - bar.increment(status=random.random() > 0.5) + # Roughly 1/3 probability for each status ;) + # Yes... probability is confusing at times + if random.random() > 0.66: + bar.increment(status=True) + elif random.random() > 0.5: + bar.increment(status=False) + else: + bar.increment(status=None) time.sleep(0.1) diff --git a/progressbar/multi.py b/progressbar/multi.py index 86a2e982..247e011c 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -264,10 +264,10 @@ def _render_finished_bar( if not self.show_finished: return - if bar_.finished(): + if bar_.finished(): # pragma: no branch if self.finished_format is None: update(force=False) - else: + else: # pragma: no cover yield self.finished_format.format(label=bar_.label) def print( diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index 0c5b7665..53354acc 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -1059,7 +1059,8 @@ # Check if the background is light or dark. This is by no means a foolproof # method, but there is no reliable way to detect this. -if os.environ.get('COLORFGBG', '15;0').split(';')[-1] == str(white.xterm): +_colorfgbg = os.environ.get('COLORFGBG', '15;0').split(';') +if _colorfgbg[-1] == str(white.xterm): # pragma: no cover # Light background gradient = light_gradient primary = black diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 0cee995e..98669f2b 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -1211,7 +1211,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): if frac: ranges[pos + 1] += frac - if self.fill_left: + if self.fill_left: # pragma: no branch ranges = list(reversed(ranges)) return ranges @@ -1532,18 +1532,18 @@ def __call__( marker = self.success_marker fg_color = self.success_fg_color bg_color = self.success_bg_color - elif status is False: + elif status is False: # pragma: no branch marker = self.failure_marker fg_color = self.failure_fg_color bg_color = self.failure_bg_color - else: + else: # pragma: no cover marker = status fg_color = bg_color = None marker = converters.to_unicode(marker) - if fg_color: + if fg_color: # pragma: no branch marker = fg_color.fg(marker) - if bg_color: + if bg_color: # pragma: no cover marker = bg_color.bg(marker) self.job_markers.append(marker) @@ -1553,9 +1553,9 @@ def __call__( fill = converters.to_unicode(self.fill(progress, data, width)) fill = self._apply_colors(fill * width, data) - if self.fill_left: + if self.fill_left: # pragma: no branch marker += fill - else: + else: # pragma: no cover marker = fill + marker else: marker = '' diff --git a/pyproject.toml b/pyproject.toml index c628a389..31c193a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,3 +148,35 @@ exclude = [ '^tests/original_examples.py$', '^examples.py$', ] + +[tool.coverage.run] +branch = true +source = [ + 'progressbar', + 'tests', +] +omit = [ + '*/mock/*', + '*/nose/*', + '.tox/*', + '*/os_specific/*', +] +[tool.coverage.paths] +source = [ + 'progressbar', +] +[tool.coverage.report] +fail_under = 100 +exclude_lines = [ + 'pragma: no cover', + '@abc.abstractmethod', + 'def __repr__', + 'if self.debug:', + 'if settings.DEBUG', + 'raise AssertionError', + 'raise NotImplementedError', + 'if 0:', + 'if __name__ == .__main__.:', + 'if types.TYPE_CHECKING:', + '@typing.overload', +] diff --git a/pytest.ini b/pytest.ini index f0e8df40..d88864e3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,6 +9,7 @@ addopts = --cov-report=term-missing --cov-report=xml --cov-append + --cov-config=pyproject.toml --no-cov-on-fail --doctest-modules diff --git a/tests/test_color.py b/tests/test_color.py index 38fb3973..bef3d4e1 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -2,12 +2,13 @@ import typing +import pytest + import progressbar import progressbar.env import progressbar.terminal -import pytest from progressbar import env, terminal, widgets -from progressbar.terminal import Colors, apply_colors, colors +from progressbar.terminal import apply_colors, Colors, colors @pytest.mark.parametrize( @@ -52,7 +53,7 @@ def test_color_environment_variables(monkeypatch, variable): 'xterm-256', 'xterm', ], - ) +) def test_color_support_from_env(monkeypatch, variable, value): monkeypatch.setenv('JUPYTER_COLUMNS', '') monkeypatch.setenv('JUPYTER_LINES', '') @@ -222,63 +223,63 @@ def test_rgb_to_hls(rgb, hls): ('test', None, None, None, None, None, 'test'), ('test', None, None, None, None, 1, 'test'), ( - 'test', - None, - None, - None, - colors.red, - None, - '\x1b[48;5;9mtest\x1b[49m', + 'test', + None, + None, + None, + colors.red, + None, + '\x1b[48;5;9mtest\x1b[49m', ), ( - 'test', - None, - colors.green, - None, - colors.red, - None, - '\x1b[48;5;9mtest\x1b[49m', + 'test', + None, + colors.green, + None, + colors.red, + None, + '\x1b[48;5;9mtest\x1b[49m', ), ('test', None, colors.red, None, None, 1, '\x1b[48;5;9mtest\x1b[49m'), ('test', None, colors.red, None, None, None, 'test'), ( - 'test', - colors.green, - None, - colors.red, - None, - None, - '\x1b[38;5;9mtest\x1b[39m', + 'test', + colors.green, + None, + colors.red, + None, + None, + '\x1b[38;5;9mtest\x1b[39m', ), ( - 'test', - colors.green, - colors.red, - None, - None, - 1, - '\x1b[48;5;9m\x1b[38;5;2mtest\x1b[39m\x1b[49m', + 'test', + colors.green, + colors.red, + None, + None, + 1, + '\x1b[48;5;9m\x1b[38;5;2mtest\x1b[39m\x1b[49m', ), ('test', colors.red, None, None, None, 1, '\x1b[38;5;9mtest\x1b[39m'), ('test', colors.red, None, None, None, None, 'test'), ('test', colors.red, colors.red, None, None, None, 'test'), ( - 'test', - colors.red, - colors.yellow, - None, - None, - 1, - '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', + 'test', + colors.red, + colors.yellow, + None, + None, + 1, + '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', ), ( - 'test', - colors.red, - colors.yellow, - None, - None, - 1, - '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', + 'test', + colors.red, + colors.yellow, + None, + None, + 1, + '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', ), ], ) @@ -290,13 +291,39 @@ def test_apply_colors(text, fg, bg, fg_none, bg_none, percentage, expected, progressbar.env.ColorSupport.XTERM_256, ) assert ( - apply_colors( - text, - fg=fg, - bg=bg, - fg_none=fg_none, - bg_none=bg_none, - percentage=percentage, - ) - == expected + apply_colors( + text, + fg=fg, + bg=bg, + fg_none=fg_none, + bg_none=bg_none, + percentage=percentage, + ) + == expected ) + + +def test_ansi_color(monkeypatch): + color = progressbar.terminal.Color( + colors.red.rgb, + colors.red.hls, + 'red-ansi', + None, + ) + + for color_support in { + env.ColorSupport.NONE, + env.ColorSupport.XTERM, + env.ColorSupport.XTERM_256, + env.ColorSupport.XTERM_TRUECOLOR, + }: + monkeypatch.setattr( + env, + 'COLOR_SUPPORT', + color_support, + ) + assert color.ansi is not None or color_support == env.ColorSupport.NONE + + +def test_sgr_call(): + assert progressbar.terminal.encircled('test') == '\x1b[52mtest\x1b[54m' diff --git a/tests/test_job_status.py b/tests/test_job_status.py new file mode 100644 index 00000000..b93ee32b --- /dev/null +++ b/tests/test_job_status.py @@ -0,0 +1,20 @@ +import time + +import pytest + +import progressbar + + +@pytest.mark.parametrize('status', [ + True, + False, + None, +]) +def test_status(status): + with progressbar.ProgressBar( + widgets=[progressbar.widgets.JobStatusBar('status')], + ) as bar: + for _ in range(5): + bar.increment(status=status, force=True) + time.sleep(0.1) + From d6e2849961c076a0b65d6da8de1786d5762edb3f Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 17 Dec 2023 03:21:12 +0100 Subject: [PATCH 109/118] python 3.7 is no longer relevant --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84ccac34..ddac4b2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 From 859fcd183084475a77a354ec95ece73386229203 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 17 Dec 2023 15:29:32 +0100 Subject: [PATCH 110/118] Fixed basic tox/pytest runs --- progressbar/bar.py | 20 +- progressbar/multi.py | 80 +++--- progressbar/terminal/stream.py | 27 +- progressbar/widgets.py | 473 +++++++++++++++++---------------- pyproject.toml | 1 + pytest.ini | 3 +- tests/test_utils.py | 22 ++ tox.ini | 3 +- 8 files changed, 329 insertions(+), 300 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index e3158642..e1abebf6 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -34,6 +34,7 @@ T = types.TypeVar('T') + class ProgressBarMixinBase(abc.ABC): _started = False _finished = False @@ -184,7 +185,7 @@ class DefaultFdMixin(ProgressBarMixinBase): def __init__( self, - fd: base.IO[str] = sys.stderr, + fd: base.TextIO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, enable_colors: progressbar.env.ColorSupport | None = None, @@ -205,11 +206,12 @@ def __init__( super().__init__(**kwargs) - def _apply_line_offset(self, fd: base.IO[T], line_offset: int) -> base.IO[T]: + def _apply_line_offset( + self, fd: base.TextIO, line_offset: int + ) -> base.TextIO: if line_offset: return progressbar.terminal.stream.LineOffsetStreamWrapper( - line_offset, - types.cast(base.TextIO, fd), + line_offset, fd, ) else: return fd @@ -280,7 +282,7 @@ def update(self, *args: types.Any, **kwargs: types.Any) -> None: try: # pragma: no cover self.fd.write(line) except UnicodeEncodeError: # pragma: no cover - self.fd.write(line.encode('ascii', 'replace')) + self.fd.write(types.cast(str, line.encode('ascii', 'replace'))) def finish( self, @@ -921,7 +923,7 @@ def _update_parents(self, value): # Only flush if something was actually written self.fd.flush() - def start(self, max_value=None, init=True): + def start(self, max_value=None, init=True, *args, **kwargs): '''Starts measuring time, and prints the bar at 0%. It returns self so you can use it like this: @@ -952,9 +954,9 @@ def start(self, max_value=None, init=True): if self.max_value is None: self.max_value = self._DEFAULT_MAXVAL - StdRedirectMixin.start(self, max_value=max_value) - ResizableMixin.start(self, max_value=max_value) - ProgressBarBase.start(self, max_value=max_value) + StdRedirectMixin.start(self, max_value=max_value, *args, **kwargs) + ResizableMixin.start(self, max_value=max_value, *args, **kwargs) + ProgressBarBase.start(self, max_value=max_value, *args, **kwargs) # Constructing the default widgets is only done when we know max_value if not self.widgets: diff --git a/progressbar/multi.py b/progressbar/multi.py index 247e011c..482f429c 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -75,22 +75,22 @@ class MultiBar(typing.Dict[str, bar.ProgressBar]): _thread_closed: threading.Event = threading.Event() def __init__( - self, - bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, - fd: typing.TextIO = sys.stderr, - prepend_label: bool = True, - append_label: bool = False, - label_format='{label:20.20} ', - initial_format: str | None = '{label:20.20} Not yet started', - finished_format: str | None = None, - update_interval: float = 1 / 60.0, # 60fps - show_initial: bool = True, - show_finished: bool = True, - remove_finished: timedelta | float = timedelta(seconds=3600), - sort_key: str | SortKey = SortKey.CREATED, - sort_reverse: bool = True, - sort_keyfunc: SortKeyFunc | None = None, - **progressbar_kwargs, + self, + bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, + fd: typing.TextIO = sys.stderr, + prepend_label: bool = True, + append_label: bool = False, + label_format='{label:20.20} ', + initial_format: str | None = '{label:20.20} Not yet started', + finished_format: str | None = None, + update_interval: float = 1 / 60.0, # 60fps + show_initial: bool = True, + show_finished: bool = True, + remove_finished: timedelta | float = timedelta(seconds=3600), + sort_key: str | SortKey = SortKey.CREATED, + sort_reverse: bool = True, + sort_keyfunc: SortKeyFunc | None = None, + **progressbar_kwargs, ): self.fd = fd @@ -197,11 +197,11 @@ def render(self, flush: bool = True, force: bool = False): self._buffer.write('\n') for i, (previous, current) in enumerate( - itertools.zip_longest( - self._previous_output, - output, - fillvalue='', - ), + itertools.zip_longest( + self._previous_output, + output, + fillvalue='', + ), ): if previous != current or force: # pragma: no branch self.print( @@ -218,10 +218,10 @@ def render(self, flush: bool = True, force: bool = False): self.flush() def _render_bar( - self, - bar_: bar.ProgressBar, - now, - expired, + self, + bar_: bar.ProgressBar, + now, + expired, ) -> typing.Iterable[str]: def update(force=True, write=True): # pragma: no cover self._label_bar(bar_) @@ -242,11 +242,11 @@ def update(force=True, write=True): # pragma: no cover yield self.initial_format.format(label=bar_.label) def _render_finished_bar( - self, - bar_: bar.ProgressBar, - now, - expired, - update, + self, + bar_: bar.ProgressBar, + now, + expired, + update, ) -> typing.Iterable[str]: if bar_ not in self._finished_at: self._finished_at[bar_] = now @@ -254,9 +254,9 @@ def _render_finished_bar( update(write=False) if ( - self.remove_finished - and expired is not None - and expired >= self._finished_at[bar_] + self.remove_finished + and expired is not None + and expired >= self._finished_at[bar_] ): del self[bar_.label] return @@ -271,13 +271,13 @@ def _render_finished_bar( yield self.finished_format.format(label=bar_.label) def print( - self, - *args, - end='\n', - offset=None, - flush=True, - clear=True, - **kwargs, + self, + *args, + end='\n', + offset=None, + flush=True, + clear=True, + **kwargs, ): ''' Print to the progressbar stream without overwriting the progressbars. diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index dac6751f..a64b7de6 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys +import typing from types import TracebackType from typing import Iterable, Iterator @@ -23,16 +24,16 @@ def flush(self) -> None: def isatty(self) -> bool: return self.stream.isatty() - def read(self, __n: int = -1) -> str: + def read(self, __n: int = -1) -> typing.AnyStr: return self.stream.read(__n) def readable(self) -> bool: return self.stream.readable() - def readline(self, __limit: int = -1) -> str: + def readline(self, __limit: int = -1) -> typing.AnyStr: return self.stream.readline(__limit) - def readlines(self, __hint: int = -1) -> list[str]: + def readlines(self, __hint: int = -1) -> list[typing.AnyStr]: return self.stream.readlines(__hint) def seek(self, __offset: int, __whence: int = 0) -> int: @@ -50,13 +51,13 @@ def truncate(self, __size: int | None = None) -> int: def writable(self) -> bool: return self.stream.writable() - def writelines(self, __lines: Iterable[str]) -> None: + def writelines(self, __lines: Iterable[typing.AnyStr]) -> None: return self.stream.writelines(__lines) - def __next__(self) -> str: + def __next__(self) -> typing.AnyStr: return self.stream.__next__() - def __iter__(self) -> Iterator[str]: + def __iter__(self) -> Iterator[typing.AnyStr]: return self.stream.__iter__() def __exit__( @@ -93,7 +94,7 @@ def write(self, data): class LastLineStream(TextIOOutputWrapper): - line: str = '' + line: typing.AnyStr = '' def seekable(self) -> bool: return False @@ -101,20 +102,21 @@ def seekable(self) -> bool: def readable(self) -> bool: return True - def read(self, __n: int = -1) -> str: + def read(self, __n: int = -1) -> typing.AnyStr: if __n < 0: return self.line else: return self.line[:__n] - def readline(self, __limit: int = -1) -> str: + def readline(self, __limit: int = -1) -> typing.AnyStr: if __limit < 0: return self.line else: return self.line[:__limit] - def write(self, data): + def write(self, data: typing.AnyStr) -> int: self.line = data + return len(data) def truncate(self, __size: int | None = None) -> int: if __size is None: @@ -124,10 +126,11 @@ def truncate(self, __size: int | None = None) -> int: return len(self.line) - def __iter__(self) -> Iterator[str]: + def __iter__(self) -> typing.Generator[typing.AnyStr, typing.Any, + typing.Any]: yield self.line - def writelines(self, __lines: Iterable[str]) -> None: + def writelines(self, __lines: Iterable[typing.AnyStr]) -> None: line = '' # Walk through the lines and take the last one for line in __lines: # noqa: B007 diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 98669f2b..90545f12 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -6,6 +6,7 @@ import functools import logging import typing + # Ruff is being stupid and doesn't understand `ClassVar` if it comes from the # `types` module from typing import ClassVar @@ -88,8 +89,8 @@ def wrap(*args, **kwargs): def create_marker(marker, wrap=None): def _marker(progress, data, width): if ( - progress.max_value is not base.UnknownLength - and progress.max_value > 0 + progress.max_value is not base.UnknownLength + and progress.max_value > 0 ): length = int(progress.value / progress.max_value * width) return marker * length @@ -99,7 +100,7 @@ def _marker(progress, data, width): if isinstance(marker, str): marker = converters.to_unicode(marker) assert ( - utils.len_color(marker) == 1 + utils.len_color(marker) == 1 ), 'Markers are required to be 1 char' return wrapper(_marker, wrap) else: @@ -127,18 +128,18 @@ def __init__(self, format: str, new_style: bool = False, **kwargs): self.format = format def get_format( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: return format or self.format def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ) -> str: '''Formats the widget into a string.''' format_ = self.get_format(progress, data, format) @@ -277,11 +278,11 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, - *args, - fixed_colors=None, - gradient_colors=None, - **kwargs, + self, + *args, + fixed_colors=None, + gradient_colors=None, + **kwargs, ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -305,10 +306,10 @@ class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): @abc.abstractmethod def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ) -> str: '''Updates the widget providing the total width the widget must fill. @@ -354,10 +355,10 @@ def __init__(self, format: str, **kwargs): WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): for name, (key, transform) in self.mapping.items(): with contextlib.suppress(KeyError, ValueError, IndexError): @@ -416,10 +417,10 @@ class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): ''' def __init__( - self, - samples=datetime.timedelta(seconds=2), - key_prefix=None, - **kwargs, + self, + samples=datetime.timedelta(seconds=2), + key_prefix=None, + **kwargs, ): self.samples = samples self.key_prefix = (key_prefix or self.__class__.__name__) + '_' @@ -438,10 +439,10 @@ def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): ) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - delta: bool = False, + self, + progress: ProgressBarMixinBase, + data: Data, + delta: bool = False, ): sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) @@ -460,9 +461,9 @@ def __call__( minimum_time = progress.last_update_time - self.samples minimum_value = sample_values[-1] while ( - sample_times[2:] - and minimum_time > sample_times[1] - and minimum_value > sample_values[1] + sample_times[2:] + and minimum_time > sample_times[1] + and minimum_value > sample_values[1] ): sample_times.pop(0) sample_values.pop(0) @@ -484,13 +485,13 @@ class ETA(Timer): '''WidgetBase which attempts to estimate the time of arrival.''' def __init__( - self, - format_not_started='ETA: --:--:--', - format_finished='Time: %(elapsed)8s', - format='ETA: %(eta)8s', - format_zero='ETA: 00:00:00', - format_na='ETA: N/A', - **kwargs, + self, + format_not_started='ETA: --:--:--', + format_finished='Time: %(elapsed)8s', + format='ETA: %(eta)8s', + format_zero='ETA: 00:00:00', + format_na='ETA: N/A', + **kwargs, ): if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -503,11 +504,11 @@ def __init__( self.format_NA = format_na def _calculate_eta( - self, - progress: ProgressBarMixinBase, - data: Data, - value, - elapsed, + self, + progress: ProgressBarMixinBase, + data: Data, + value, + elapsed, ): '''Updates the widget to show the ETA or total time when finished.''' if elapsed: @@ -519,11 +520,11 @@ def _calculate_eta( return 0 def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): '''Updates the widget to show the ETA or total time when finished.''' if value is None: @@ -567,11 +568,11 @@ class AbsoluteETA(ETA): '''Widget which attempts to estimate the absolute time of arrival.''' def _calculate_eta( - self, - progress: ProgressBarMixinBase, - data: Data, - value, - elapsed, + self, + progress: ProgressBarMixinBase, + data: Data, + value, + elapsed, ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() @@ -581,11 +582,11 @@ def _calculate_eta( return datetime.datetime.max def __init__( - self, - format_not_started='Estimated finish time: ----/--/-- --:--:--', - format_finished='Finished at: %(elapsed)s', - format='Estimated finish time: %(eta)s', - **kwargs, + self, + format_not_started='Estimated finish time: ----/--/-- --:--:--', + format_finished='Finished at: %(elapsed)s', + format='Estimated finish time: %(eta)s', + **kwargs, ): ETA.__init__( self, @@ -608,11 +609,11 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - value=None, - elapsed=None, + self, + progress: ProgressBarMixinBase, + data: Data, + value=None, + elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, @@ -636,12 +637,12 @@ class DataSize(FormatWidgetMixin, WidgetBase): ''' def __init__( - self, - variable='value', - format='%(scaled)5.1f %(prefix)s%(unit)s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + variable='value', + format='%(scaled)5.1f %(prefix)s%(unit)s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.variable = variable self.unit = unit @@ -650,10 +651,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data[self.variable] if value is not None: @@ -674,12 +675,12 @@ class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): ''' def __init__( - self, - format='%(scaled)5.1f %(prefix)s%(unit)-s/s', - inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', - unit='B', - prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), - **kwargs, + self, + format='%(scaled)5.1f %(prefix)s%(unit)-s/s', + inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', + unit='B', + prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), + **kwargs, ): self.unit = unit self.prefixes = prefixes @@ -692,11 +693,11 @@ def _speed(self, value, elapsed): return utils.scale_1024(speed, len(self.prefixes)) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): '''Updates the widget with the current SI prefixed speed.''' if value is None: @@ -708,10 +709,10 @@ def __call__( ) if ( - value is not None - and elapsed is not None - and elapsed > 2e-6 - and value > 2e-6 + value is not None + and elapsed is not None + and elapsed > 2e-6 + and value > 2e-6 ): # =~ 0 scaled, power = self._speed(value, elapsed) else: @@ -743,11 +744,11 @@ def __init__(self, **kwargs): SamplesMixin.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data, - value=None, - total_seconds_elapsed=None, + self, + progress: ProgressBarMixinBase, + data, + value=None, + total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, @@ -764,13 +765,13 @@ class AnimatedMarker(TimeSensitiveWidgetBase): ''' def __init__( - self, - markers='|/-\\', - default=None, - fill='', - marker_wrap=None, - fill_wrap=None, - **kwargs, + self, + markers='|/-\\', + default=None, + fill='', + marker_wrap=None, + fill_wrap=None, + **kwargs, ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) @@ -823,10 +824,10 @@ def __init__(self, format='%(value)d', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): return FormatWidgetMixin.__call__(self, progress, data, format) @@ -856,10 +857,10 @@ def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): WidgetBase.__init__(self, format=format, **kwargs) def get_format( - self, - progress: ProgressBarMixinBase, - data: Data, - format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) @@ -887,10 +888,10 @@ def __init__(self, format=DEFAULT_FORMAT, **kwargs): self.max_width_cache = dict(default=self.max_width or 0) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format=None, + self, + progress: ProgressBarMixinBase, + data: Data, + format=None, ): # If max_value is not available, display N/A if data.get('max_value'): @@ -925,12 +926,12 @@ def __call__( temporary_data['value'] = value if width := progress.custom_len( # pragma: no branch - FormatWidgetMixin.__call__( - self, - progress, - temporary_data, - format=format, - ), + FormatWidgetMixin.__call__( + self, + progress, + temporary_data, + format=format, + ), ): max_width = max(max_width or 0, width) @@ -950,14 +951,14 @@ class Bar(AutoWidthWidgetBase): bg: terminal.OptionalColor | None = None def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=True, - marker_wrap=None, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=True, + marker_wrap=None, + **kwargs, ): '''Creates a customizable progress bar. @@ -978,11 +979,11 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1009,13 +1010,13 @@ class ReverseBar(Bar): '''A bar which has a marker that goes from right to left.''' def __init__( - self, - marker='#', - left='|', - right='|', - fill=' ', - fill_left=False, - **kwargs, + self, + marker='#', + left='|', + right='|', + fill=' ', + fill_left=False, + **kwargs, ): '''Creates a customizable progress bar. @@ -1042,11 +1043,11 @@ class BouncingBar(Bar, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(milliseconds=100) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1079,10 +1080,10 @@ class FormatCustomText(FormatWidgetMixin, WidgetBase): copy = False def __init__( - self, - format: str, - mapping: types.Optional[types.Dict[str, types.Any]] = None, - **kwargs, + self, + format: str, + mapping: types.Optional[types.Dict[str, types.Any]] = None, + **kwargs, ): self.format = format self.mapping = mapping or self.mapping @@ -1093,10 +1094,10 @@ def update_mapping(self, **mapping: types.Dict[str, types.Any]): self.mapping.update(mapping) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): return FormatWidgetMixin.__call__( self, @@ -1141,11 +1142,11 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): return data['variables'][self.name] or [] def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents.''' left = converters.to_unicode(self.left(progress, data, width)) @@ -1177,12 +1178,12 @@ def __call__( class MultiProgressBar(MultiRangeBar): def __init__( - self, - name, - # NOTE: the markers are not whitespace even though some - # terminals don't show the characters correctly! - markers=' ▁▂▃▄▅▆▇█', - **kwargs, + self, + name, + # NOTE: the markers are not whitespace even though some + # terminals don't show the characters correctly! + markers=' ▁▂▃▄▅▆▇█', + **kwargs, ): MultiRangeBar.__init__( self, @@ -1243,11 +1244,11 @@ class GranularBar(AutoWidthWidgetBase): ''' def __init__( - self, - markers=GranularMarkers.smooth, - left='|', - right='|', - **kwargs, + self, + markers=GranularMarkers.smooth, + left='|', + right='|', + **kwargs, ): '''Creates a customizable progress bar. @@ -1264,10 +1265,10 @@ def __init__( AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) @@ -1277,8 +1278,8 @@ def __call__( # mypy doesn't get that the first part of the if statement makes sure # we get the correct type if ( - max_value is not base.UnknownLength - and max_value > 0 # type: ignore + max_value is not base.UnknownLength + and max_value > 0 # type: ignore ): percent = progress.value / max_value # type: ignore else: @@ -1308,11 +1309,11 @@ def __init__(self, format, **kwargs): Bar.__init__(self, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) bar = Bar.__call__(self, progress, data, width, color=False) @@ -1323,18 +1324,18 @@ def __call__( # type: ignore center_right = center_left + center_len return ( - self._apply_colors( - bar[:center_left], - data, - ) - + self._apply_colors( - center, - data, - ) - + self._apply_colors( - bar[center_right:], - data, - ) + self._apply_colors( + bar[:center_left], + data, + ) + + self._apply_colors( + center, + data, + ) + + self._apply_colors( + bar[center_right:], + data, + ) ) @@ -1348,11 +1349,11 @@ def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): FormatLabelBar.__init__(self, format, **kwargs) def __call__( # type: ignore - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - format: FormatString = None, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + format: FormatString = None, ): return super().__call__(progress, data, width, format=format) @@ -1361,12 +1362,12 @@ class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): '''Displays a custom variable.''' def __init__( - self, - name, - format='{name}: {formatted_value}', - width=6, - precision=3, - **kwargs, + self, + name, + format='{name}: {formatted_value}', + width=6, + precision=3, + **kwargs, ): '''Creates a Variable associated with the given name.''' self.format = format @@ -1376,10 +1377,10 @@ def __init__( WidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): value = data['variables'][self.name] context = data.copy() @@ -1415,20 +1416,20 @@ class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): INTERVAL = datetime.timedelta(seconds=1) def __init__( - self, - format='Current Time: %(current_time)s', - microseconds=False, - **kwargs, + self, + format='Current Time: %(current_time)s', + microseconds=False, + **kwargs, ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - format: types.Optional[str] = None, + self, + progress: ProgressBarMixinBase, + data: Data, + format: types.Optional[str] = None, ): data['current_time'] = self.current_time() data['current_datetime'] = self.current_datetime() @@ -1478,19 +1479,19 @@ class JobStatusBar(Bar, VariableMixin): job_markers: list[str] def __init__( - self, - name: str, - left='|', - right='|', - fill=' ', - fill_left=True, - success_fg_color=colors.green, - success_bg_color=None, - success_marker='█', - failure_fg_color=colors.red, - failure_bg_color=None, - failure_marker='X', - **kwargs, + self, + name: str, + left='|', + right='|', + fill=' ', + fill_left=True, + success_fg_color=colors.green, + success_bg_color=None, + success_marker='█', + failure_fg_color=colors.red, + failure_bg_color=None, + failure_marker='X', + **kwargs, ): VariableMixin.__init__(self, name) self.name = name @@ -1515,11 +1516,11 @@ def __init__( ) def __call__( - self, - progress: ProgressBarMixinBase, - data: Data, - width: int = 0, - color=True, + self, + progress: ProgressBarMixinBase, + data: Data, + width: int = 0, + color=True, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) @@ -1555,7 +1556,7 @@ def __call__( if self.fill_left: # pragma: no branch marker += fill - else: # pragma: no cover + else: # pragma: no cover marker = fill + marker else: marker = '' diff --git a/pyproject.toml b/pyproject.toml index 31c193a4..cb502632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,6 +165,7 @@ omit = [ source = [ 'progressbar', ] + [tool.coverage.report] fail_under = 100 exclude_lines = [ diff --git a/pytest.ini b/pytest.ini index d88864e3..08a11301 100644 --- a/pytest.ini +++ b/pytest.ini @@ -8,8 +8,7 @@ addopts = --cov-report=html --cov-report=term-missing --cov-report=xml - --cov-append - --cov-config=pyproject.toml + --cov-config=./pyproject.toml --no-cov-on-fail --doctest-modules diff --git a/tests/test_utils.py b/tests/test_utils.py index 11a070fb..34bd0da8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -86,3 +86,25 @@ def test_is_ansi_terminal(monkeypatch): # Sanity check assert progressbar.env.is_ansi_terminal(fd) is False + + # Fake TTY mode for environment testing + fd.isatty = lambda: True + monkeypatch.setenv('TERM', 'xterm') + assert progressbar.env.is_ansi_terminal(fd) is True + monkeypatch.setenv('TERM', 'xterm-256') + assert progressbar.env.is_ansi_terminal(fd) is True + monkeypatch.setenv('TERM', 'xterm-256color') + assert progressbar.env.is_ansi_terminal(fd) is True + monkeypatch.setenv('TERM', 'xterm-24bit') + assert progressbar.env.is_ansi_terminal(fd) is True + monkeypatch.delenv('TERM') + + monkeypatch.setenv('ANSICON', 'true') + assert progressbar.env.is_ansi_terminal(fd) is True + monkeypatch.delenv('ANSICON') + assert progressbar.env.is_ansi_terminal(fd) is False + + def raise_error(): + raise RuntimeError('test') + fd.isatty = raise_error + assert progressbar.env.is_ansi_terminal(fd) is False diff --git a/tox.ini b/tox.ini index a0cb30d9..3a4c21bf 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,8 @@ skip_missing_interpreters = True [testenv] deps = -r{toxinidir}/tests/requirements.txt commands = py.test --basetemp="{envtmpdir}" --confcutdir=.. {posargs} -changedir = tests +;changedir = tests +skip_install = true [testenv:mypy] changedir = From ffdcfc9d3d890d152fdc1a0e18842f6cbbb50b55 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 01:10:32 +0100 Subject: [PATCH 111/118] fixed pyright issues --- progressbar/multi.py | 3 ++- progressbar/terminal/base.py | 19 +++++++++++-------- progressbar/terminal/stream.py | 24 ++++++++++++------------ progressbar/widgets.py | 8 ++++---- pyproject.toml | 7 +++++++ pyrightconfig.json | 5 ----- 6 files changed, 36 insertions(+), 30 deletions(-) delete mode 100644 pyrightconfig.json diff --git a/progressbar/multi.py b/progressbar/multi.py index 482f429c..42e3dadd 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -227,7 +227,8 @@ def update(force=True, write=True): # pragma: no cover self._label_bar(bar_) bar_.update(force=force) if write: - yield bar_.fd.line + yield typing.cast( + stream.LastLineStream, bar_.fd).line if bar_.finished(): yield from self._render_finished_bar(bar_, now, expired, update) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 04806ffb..60de893a 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -145,8 +145,8 @@ def clear_line(n): class _CPR(str): # pragma: no cover _response_lock = threading.Lock() - def __call__(self, stream): - res = '' + def __call__(self, stream) -> tuple[int, int]: + res : str = '' with self._response_lock: stream.write(str(self)) @@ -158,14 +158,17 @@ def __call__(self, stream): if char is not None: res += char - res = res[2:-1].split(';') + res_list = res[2:-1].split(';') - res = tuple(int(item) if item.isdigit() else item for item in res) + res_list = tuple(int(item) + if item.isdigit() + else item + for item in res_list) - if len(res) == 1: - return res[0] + if len(res_list) == 1: + return types.cast(tuple[int, int], res_list[0]) - return res + return types.cast(tuple[int, int], tuple(res_list)) def row(self, stream): row, _ = self(stream) @@ -491,7 +494,7 @@ def _start_template(self): def _end_template(self): return super().__call__(self._end_code) - def __call__(self, text): + def __call__(self, text, *args): return self._start_template + text + self._end_template diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index a64b7de6..33e1cec7 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -24,16 +24,16 @@ def flush(self) -> None: def isatty(self) -> bool: return self.stream.isatty() - def read(self, __n: int = -1) -> typing.AnyStr: + def read(self, __n: int = -1) -> str: return self.stream.read(__n) def readable(self) -> bool: return self.stream.readable() - def readline(self, __limit: int = -1) -> typing.AnyStr: + def readline(self, __limit: int = -1) -> str: return self.stream.readline(__limit) - def readlines(self, __hint: int = -1) -> list[typing.AnyStr]: + def readlines(self, __hint: int = -1) -> list[str]: return self.stream.readlines(__hint) def seek(self, __offset: int, __whence: int = 0) -> int: @@ -51,13 +51,13 @@ def truncate(self, __size: int | None = None) -> int: def writable(self) -> bool: return self.stream.writable() - def writelines(self, __lines: Iterable[typing.AnyStr]) -> None: + def writelines(self, __lines: Iterable[str]) -> None: return self.stream.writelines(__lines) - def __next__(self) -> typing.AnyStr: + def __next__(self) -> str: return self.stream.__next__() - def __iter__(self) -> Iterator[typing.AnyStr]: + def __iter__(self) -> Iterator[str]: return self.stream.__iter__() def __exit__( @@ -94,7 +94,7 @@ def write(self, data): class LastLineStream(TextIOOutputWrapper): - line: typing.AnyStr = '' + line: str = '' def seekable(self) -> bool: return False @@ -102,19 +102,19 @@ def seekable(self) -> bool: def readable(self) -> bool: return True - def read(self, __n: int = -1) -> typing.AnyStr: + def read(self, __n: int = -1) -> str: if __n < 0: return self.line else: return self.line[:__n] - def readline(self, __limit: int = -1) -> typing.AnyStr: + def readline(self, __limit: int = -1) -> str: if __limit < 0: return self.line else: return self.line[:__limit] - def write(self, data: typing.AnyStr) -> int: + def write(self, data: str) -> int: self.line = data return len(data) @@ -126,11 +126,11 @@ def truncate(self, __size: int | None = None) -> int: return len(self.line) - def __iter__(self) -> typing.Generator[typing.AnyStr, typing.Any, + def __iter__(self) -> typing.Generator[str, typing.Any, typing.Any]: yield self.line - def writelines(self, __lines: Iterable[typing.AnyStr]) -> None: + def writelines(self, __lines: Iterable[str]) -> None: line = '' # Walk through the lines and take the last one for line in __lines: # noqa: B007 diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 90545f12..40f29724 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -1470,11 +1470,11 @@ class JobStatusBar(Bar, VariableMixin): failure_marker: The marker to use for failed jobs. ''' - success_fg_color: terminal.OptionalColor | None = colors.green - success_bg_color: terminal.OptionalColor | None = None + success_fg_color: terminal.Color | None = colors.green + success_bg_color: terminal.Color | None = None success_marker: str = '█' - failure_fg_color: terminal.OptionalColor | None = colors.red - failure_bg_color: terminal.OptionalColor | None = None + failure_fg_color: terminal.Color | None = colors.red + failure_bg_color: terminal.Color | None = None failure_marker: str = 'X' job_markers: list[str] diff --git a/pyproject.toml b/pyproject.toml index cb502632..b28a4571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,3 +181,10 @@ exclude_lines = [ 'if types.TYPE_CHECKING:', '@typing.overload', ] + +[tool.pyright] +include= ['progressbar'] +exclude= ['examples'] +ignore= ['docs'] + +reportIncompatibleMethodOverride = false \ No newline at end of file diff --git a/pyrightconfig.json b/pyrightconfig.json deleted file mode 100644 index 5e0a8207..00000000 --- a/pyrightconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "include": ["progressbar"], - "exclude": ["examples"], - "ignore": ["docs"], -} From 35f3da4dbbd87ecae5f9c8a604789e8728868a60 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 02:38:14 +0100 Subject: [PATCH 112/118] ruff fixes --- .travis.yml | 33 --------------------------------- progressbar/__init__.py | 2 +- progressbar/bar.py | 11 ++++++----- progressbar/multi.py | 3 +-- progressbar/terminal/base.py | 9 ++++----- progressbar/terminal/stream.py | 3 +-- tests/test_color.py | 5 ++--- tests/test_job_status.py | 3 +-- tests/test_multibar.py | 8 +++++--- tests/test_progressbar.py | 2 +- tox.ini | 2 +- 11 files changed, 23 insertions(+), 58 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1929ed53..00000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -dist: xenial -sudo: false -language: python -python: -- '2.7' -- '3.4' -- '3.5' -- '3.6' -- '3.7' -- '3.8' -- pypy -install: -- pip install -U . -- pip install -U -r tests/requirements.txt -before_script: flake8 progressbar tests examples.py -script: -- py.test -- python examples.py -after_success: -- coveralls -- pip install codecov -- codecov -before_deploy: "python setup.py sdist bdist_wheel" -deploy: - provider: releases - api_key: - secure: DmqlCoHxPh5465T5DQgdFE7Peqy7MVF034n7t/hpV2Lf4LH9fHUo2r1dpICpBIxRuDNCXtM3PJLk59OMqCchpcAlC7VkH6dTOLpigk/IXYtlJVr3cXQUEC0gmPuFsrZ/fpWUR0PBfUD/fBA0RW64xFZ6ksfc+76tdQrKj1appz0= - file: dist/* - file_glob: true - skip_cleanup: true - on: - tags: true - repo: WoLpH/python-progressbar diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 22b26c55..43824995 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -24,6 +24,7 @@ FormatLabel, FormatLabelBar, GranularBar, + JobStatusBar, MultiProgressBar, MultiRangeBar, Percentage, @@ -34,7 +35,6 @@ Timer, Variable, VariableMixin, - JobStatusBar, ) __date__ = str(date.today()) diff --git a/progressbar/bar.py b/progressbar/bar.py index e1abebf6..cab76c4d 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -207,11 +207,12 @@ def __init__( super().__init__(**kwargs) def _apply_line_offset( - self, fd: base.TextIO, line_offset: int + self, fd: base.TextIO, line_offset: int, ) -> base.TextIO: if line_offset: return progressbar.terminal.stream.LineOffsetStreamWrapper( - line_offset, fd, + line_offset, + fd, ) else: return fd @@ -954,9 +955,9 @@ def start(self, max_value=None, init=True, *args, **kwargs): if self.max_value is None: self.max_value = self._DEFAULT_MAXVAL - StdRedirectMixin.start(self, max_value=max_value, *args, **kwargs) - ResizableMixin.start(self, max_value=max_value, *args, **kwargs) - ProgressBarBase.start(self, max_value=max_value, *args, **kwargs) + StdRedirectMixin.start(self, max_value=max_value) + ResizableMixin.start(self, max_value=max_value) + ProgressBarBase.start(self, max_value=max_value) # Constructing the default widgets is only done when we know max_value if not self.widgets: diff --git a/progressbar/multi.py b/progressbar/multi.py index 42e3dadd..be1ca7d9 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -227,8 +227,7 @@ def update(force=True, write=True): # pragma: no cover self._label_bar(bar_) bar_.update(force=force) if write: - yield typing.cast( - stream.LastLineStream, bar_.fd).line + yield typing.cast(stream.LastLineStream, bar_.fd).line if bar_.finished(): yield from self._render_finished_bar(bar_, now, expired, update) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 60de893a..7425a1fd 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -146,7 +146,7 @@ class _CPR(str): # pragma: no cover _response_lock = threading.Lock() def __call__(self, stream) -> tuple[int, int]: - res : str = '' + res: str = '' with self._response_lock: stream.write(str(self)) @@ -160,10 +160,9 @@ def __call__(self, stream) -> tuple[int, int]: res_list = res[2:-1].split(';') - res_list = tuple(int(item) - if item.isdigit() - else item - for item in res_list) + res_list = tuple( + int(item) if item.isdigit() else item for item in res_list + ) if len(res_list) == 1: return types.cast(tuple[int, int], res_list[0]) diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index 33e1cec7..ee02a9d9 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -126,8 +126,7 @@ def truncate(self, __size: int | None = None) -> int: return len(self.line) - def __iter__(self) -> typing.Generator[str, typing.Any, - typing.Any]: + def __iter__(self) -> typing.Generator[str, typing.Any, typing.Any]: yield self.line def writelines(self, __lines: Iterable[str]) -> None: diff --git a/tests/test_color.py b/tests/test_color.py index bef3d4e1..1a6657e6 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -2,13 +2,12 @@ import typing -import pytest - import progressbar import progressbar.env import progressbar.terminal +import pytest from progressbar import env, terminal, widgets -from progressbar.terminal import apply_colors, Colors, colors +from progressbar.terminal import Colors, apply_colors, colors @pytest.mark.parametrize( diff --git a/tests/test_job_status.py b/tests/test_job_status.py index b93ee32b..f22484f5 100644 --- a/tests/test_job_status.py +++ b/tests/test_job_status.py @@ -1,8 +1,7 @@ import time -import pytest - import progressbar +import pytest @pytest.mark.parametrize('status', [ diff --git a/tests/test_multibar.py b/tests/test_multibar.py index 8ce99460..561e44f0 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -1,5 +1,5 @@ -import threading import random +import threading import time import progressbar @@ -191,7 +191,8 @@ def print_sometimes(bar, probability): multibar.update(force=True, flush=True) def test_multibar_no_format(): - with progressbar.MultiBar(initial_format=None, finished_format=None) as multibar: + with progressbar.MultiBar( + initial_format=None, finished_format=None) as multibar: bar = multibar['a'] for i in bar(range(5)): @@ -215,7 +216,8 @@ def test_multibar_finished(): def test_multibar_finished_format(): - multibar = progressbar.MultiBar(finished_format='Finished {label}', show_finished=True) + multibar = progressbar.MultiBar( + finished_format='Finished {label}', show_finished=True) bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) bar2 = multibar['bar2'] multibar.render(force=True) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index f3fb10d7..d418d4c4 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -1,5 +1,5 @@ -import os import contextlib +import os import time import original_examples # type: ignore diff --git a/tox.ini b/tox.ini index 3a4c21bf..aa24da69 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = py311 docs black - mypy + ; mypy pyright ruff codespell From e26bb00038500a03dc4209b5a8d27fc72cbc7b6e Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 02:41:41 +0100 Subject: [PATCH 113/118] fixed codespell --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b28a4571..e026134c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,7 +135,7 @@ requires = ['setuptools', 'setuptools-scm'] [tool.codespell] skip = '*/htmlcov,./docs/_build,*.asc' -ignore-words-list = 'datas' +ignore-words-list = 'datas,numbert' [tool.black] line-length = 79 From 13e33da933e88e99a23450450dcb9232d4296829 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 02:58:17 +0100 Subject: [PATCH 114/118] fixed pyright --- progressbar/bar.py | 4 +++- tox.ini | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index cab76c4d..d7221001 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -207,7 +207,9 @@ def __init__( super().__init__(**kwargs) def _apply_line_offset( - self, fd: base.TextIO, line_offset: int, + self, + fd: base.TextIO, + line_offset: int, ) -> base.TextIO: if line_offset: return progressbar.terminal.stream.LineOffsetStreamWrapper( diff --git a/tox.ini b/tox.ini index aa24da69..b9cfdd76 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,9 @@ commands = mypy {toxinidir}/progressbar [testenv:pyright] changedir = basepython = python3 -deps = pyright +deps = + pyright + python_utils commands = pyright {toxinidir}/progressbar [testenv:black] From 83f88d42edeb134a7a2e609daa46a8f6ee23d7ea Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 03:03:56 +0100 Subject: [PATCH 115/118] codespell fix, maybe? --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index b9cfdd76..5d1f14df 100644 --- a/tox.ini +++ b/tox.ini @@ -61,6 +61,7 @@ deps = ruff skip_install = true [testenv:codespell] +changedir = {toxinidir} commands = codespell . deps = codespell skip_install = true From 4116c07ee9fcf52b1c08cd03fa13d89f1e9c40be Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 03:08:52 +0100 Subject: [PATCH 116/118] something is wrong with codespell on github actions... the config file is not being used --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5d1f14df..a554606a 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = ; mypy pyright ruff - codespell + ; codespell skip_missing_interpreters = True [testenv] From 37d9ee55d8b54feec3bfad6e38cb772480d07243 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 03:17:10 +0100 Subject: [PATCH 117/118] fixed python 3.8 type hints --- progressbar/terminal/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 7425a1fd..8c9b262a 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -165,9 +165,9 @@ def __call__(self, stream) -> tuple[int, int]: ) if len(res_list) == 1: - return types.cast(tuple[int, int], res_list[0]) + return types.cast(types.Tuple[int, int], res_list[0]) - return types.cast(tuple[int, int], tuple(res_list)) + return types.cast(types.Tuple[int, int], tuple(res_list)) def row(self, stream): row, _ = self(stream) From f0c5fa60a3fa6832fd332e909ebc546357aaff0e Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 18 Dec 2023 03:44:34 +0100 Subject: [PATCH 118/118] Incrementing version to v4.3.0 --- progressbar/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progressbar/__about__.py b/progressbar/__about__.py index fd8affc2..5760b8d8 100644 --- a/progressbar/__about__.py +++ b/progressbar/__about__.py @@ -21,7 +21,7 @@ '''.strip().split(), ) __email__ = 'wolph@wol.ph' -__version__ = '4.3b.0' +__version__ = '4.3.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar'