[codex] Modernize README demos and progress ergonomics#317
Conversation
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
Move the selection of whether to use N/A% to a separate method.
Include a specialization with a percentage in the center.
`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`
Same idea as fbb2f4d 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`.
There was a problem hiding this comment.
Code Review
This pull request modernizes the progressbar2 library by introducing tqdm-style ergonomic options (such as desc, total, unit, unit_scale, and postfix), adding new Postfix and UnitProgress widgets, and improving print/log coexistence with a reversible logging stream wrapper. It also updates the CLI to support rate limiting and custom display flags, rewrites the README to be demo-led, and adds a script to generate SVG terminal demos. The review feedback identifies three key improvement opportunities: ensuring valid falsy values (like 0 or False) are not omitted in the Postfix widget, wrapping ProgressBarBase.finish in contextlib.suppress during error handling to prevent masking exceptions, and copying logger.handlers to a tuple during iteration to avoid concurrent modification errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| value = data['variables'].get(self.name) | ||
| if not value: | ||
| return '' |
There was a problem hiding this comment.
If value is 0, 0.0, or False, not value evaluates to True, which causes the widget to return an empty string and omit these valid postfix values. Checking for None or empty collections/strings explicitly prevents this issue.
| value = data['variables'].get(self.name) | |
| if not value: | |
| return '' | |
| value = data['variables'].get(self.name) | |
| if value is None or (isinstance(value, (str, dict, list, set)) and not value): | |
| return '' |
| except Exception: | ||
| with contextlib.suppress(Exception): | ||
| StdRedirectMixin.finish(self, end='') | ||
| with contextlib.suppress(Exception): | ||
| ResizableMixin.finish(self) | ||
| ProgressBarBase.finish(self) | ||
| raise |
There was a problem hiding this comment.
If ProgressBarBase.finish(self) raises an exception during error handling, it will mask the original exception being raised. For robust error handling and consistency with the other cleanup calls, it should also be wrapped in contextlib.suppress(Exception).
| except Exception: | |
| with contextlib.suppress(Exception): | |
| StdRedirectMixin.finish(self, end='') | |
| with contextlib.suppress(Exception): | |
| ResizableMixin.finish(self) | |
| ProgressBarBase.finish(self) | |
| raise | |
| except Exception: | |
| with contextlib.suppress(Exception): | |
| StdRedirectMixin.finish(self, end='') | |
| with contextlib.suppress(Exception): | |
| ResizableMixin.finish(self) | |
| with contextlib.suppress(Exception): | |
| ProgressBarBase.finish(self) | |
| raise |
| for logger_ in self._iter_loggers(): | ||
| for handler in logger_.handlers: |
There was a problem hiding this comment.
Iterating over logger_.handlers directly can raise a RuntimeError: list changed size during iteration if another thread adds or removes a handler concurrently. Iterating over a snapshot tuple(logger_.handlers) avoids this race condition.
| for logger_ in self._iter_loggers(): | |
| for handler in logger_.handlers: | |
| for logger_ in self._iter_loggers(): | |
| for handler in tuple(logger_.handlers): |
5fa00aa to
bd91a11
Compare
Summary
docs/superpowersfiles from the PR branch historyValidation
python scripts/render_readme_demos.py --check.tox/py313/bin/python -m pytest tests/test_readme_demos.py -q --no-cov.tox/py313/bin/python -m pytest -q --no-cov.tox/py313/bin/pyright.tox/ruff/bin/ruff check scripts/render_readme_demos.py tests/test_readme_demos.py.tox/ruff/bin/ruff format --check scripts/render_readme_demos.py tests/test_readme_demos.pygit diff --check