Skip to content

Quality audit PR 4/6: typing modernization#322

Merged
wolph merged 18 commits into
developfrom
quality-audit-4-typing
Jul 6, 2026
Merged

Quality audit PR 4/6: typing modernization#322
wolph merged 18 commits into
developfrom
quality-audit-4-typing

Conversation

@wolph

@wolph wolph commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Fourth of six audit PRs (#319, #320, #321). Annotation-only modernization — zero runtime-semantics changes, proven by the API-surface snapshot staying byte-identical (it records parameter names/kinds; only annotations moved).

What changed

  • PEP 604/585 sweep across every module: types.Optional[X]X | None, types.Dictdict, typing.Union|, types.Callable/Iterable/MutableSequencecollections.abc.*. The python_utils.types alias imports are gone; the codebase now has one way to spell a type.
  • Missing/wrong annotations fixed: widget hot paths (_calculate_eta, Bar.__call__, AnimatedMarker, the marker/wrapper factory functions), MultiBar's public methods, AttributeDict (was claiming int values), stream-listener sets, conftest fixtures. SamplesMixin.samples now admits int (its own doctest passes one — the timedelta-only annotation was wrong).
  • progressbar() shortcut widened from Iterator[T] to Iterable[T]progressbar(range(100)) and lists now type-check; the parameter name stays iterator so keyword callers are untouched.
  • deltas_to_seconds gets @overloads replacing the type[ValueError]-as-sentinel typing lie (runtime behavior identical).
  • ruff target-version py39 → py310, matching requires-python; newly-flagged pyupgrade rules applied.
  • __init__.py export lists guarded: a new test asserts the TYPE_CHECKING block, the lazy-import mapping, and __all__ stay in sync (they were three hand-maintained lists that could silently drift).

Notes

  • mypy (configured but not CI-gating) goes 6 → 11 errors; the new ones are mypy-only fallout of more precise annotations on a pre-existing Liskov divergence in the ETA family. pyright is at 0. The mypy-in-CI question is PR 6 scope.
  • set[ProgressBarMixinBase] (not ProgressBar) for stream listeners — it matches the actual consumer signature.

Verification

574 passed / 100.00% branch coverage (3.14); CI parity (TERM=dumb) green on 3.10 + 3.12; ruff + pyright clean; API snapshot unchanged.

wolph added 9 commits July 6, 2026 05:26
Replace python_utils.types typing aliases with modern spellings:
Optional[X] -> X | None, Union -> |, Dict -> dict, Type -> type,
Iterable/Iterator/Sequence/MutableSequence/Callable ->
collections.abc.*, TypeVar/cast/Any -> typing.*. Drop the now-unused
python_utils.types import. Annotation-only; no runtime semantics change.
Modernize typing aliases: Optional[X] -> X | None, Set -> set,
Type -> type, and Iterable/Iterator/Sequence/Callable/Generator ->
collections.abc.*. Route TYPE_CHECKING/TypeVar through typing; keep
python_utils.types only for its own StringTypes alias in utils.py.
Annotation-only; no runtime semantics change.
Modernize typing aliases in terminal/base.py and terminal/stream.py:
Optional[X] -> X | None, Union -> |, List/Tuple -> list/tuple,
Callable/Generator -> collections.abc.*, cast/Any -> typing.*. Drop the
now-unused python_utils.types import from base.py. Annotation-only.
Modernize typing aliases: Optional[X] -> X | None, Dict/Tuple -> dict/
tuple, Type -> type, Callable -> collections.abc.Callable, Any ->
typing.Any, and route TYPE_CHECKING through typing. Collapse redundant
Optional[X | None] color-dict fields to X | None. Drop the now-unused
python_utils.types import. Annotation-only; no runtime semantics change.
widgets.py: annotate string_or_lambda/create_marker/create_wrapper,
_calculate_eta (ETA/AbsoluteETA), AnimatedMarker.__init__, Bar-family
__call__ color: bool, and SamplesMixin.samples (timedelta | int, per its
docstring). multi.py: return annotations on __setitem__/__getitem__/stop/
get_sorted_bars/__enter__. utils.py: AttributeDict __getattr__/__setattr__
-> Any, and typed WrappingIO/StreamWrapper listeners as
set[ProgressBarMixinBase] (matches start_capturing's parameter type).
conftest.py: fixture annotations. Drop the now-obsolete SamplesMixin
pyright ignore in tests. Annotation-only.
The shortcut only needs an iterable (ProgressBar.__call__ iterates it),
so accept collections.abc.Iterable[T] instead of Iterator[T]. This lets
progressbar(range(10)) type-check. Parameter name 'iterator' kept for
keyword-caller compatibility. Annotation-only.
The old signature (default: Optional[Type[ValueError]]) was a typing lie
(callers pass floats/None as the default). Add @Overloads: with the
ValueError sentinel it returns float (or raises); with any other default
T it returns float | T. Implementation signature stays permissive (Any),
runtime behavior unchanged.
Bump target-version 'py39' -> 'py310'. The only newly-flagged rule is
B905 (zip without strict=); add explicit strict=False at the two call
sites to preserve the stop-at-shortest behavior (no new exception path).
No pyupgrade (UP) autofixes were triggered.
Add tests/test_init_exports.py asserting the three hand-synced lists stay
consistent: every _NAME_TO_MODULE name resolves via getattr, __all__'s
contents equal the mapping plus the eager dunders, and the TYPE_CHECKING
import block (parsed via ast) imports exactly the mapping's names. Reorder
__all__ into its canonical form and document _NAME_TO_MODULE as the source
of truth. __all__ stays a static literal (kept in RUF022 order) so type
checkers still see the re-exports.
Copilot AI review requested due to automatic review settings July 6, 2026 04:11
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Comment thread progressbar/bar.py Dismissed
Comment thread progressbar/bar.py Fixed
Comment thread progressbar/utils.py Fixed
Comment thread progressbar/utils.py Fixed
Comment thread progressbar/utils.py Fixed
Comment thread tests/test_init_exports.py Fixed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the codebase to target Python 3.10, replacing legacy typing constructs with modern syntax (such as union operators and standard collection types) and adding stricter type annotations across multiple modules. It also introduces tests to ensure initialization exports remain synchronized. The review feedback highlights a critical runtime issue where bytes are written directly to a TextIO stream, and suggests several type-annotation improvements in progressbar/widgets.py to eliminate unnecessary casts and prevent potential runtime TypeErrors.

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.

Comment thread progressbar/bar.py Outdated
Comment thread progressbar/widgets.py Outdated
Comment thread progressbar/widgets.py Outdated
Comment thread progressbar/widgets.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR continues the audit series by modernizing type annotations across the project (PEP 604/585/collections.abc), tightening several previously-misleading annotations, and adding a test guard to keep progressbar/__init__.py’s lazy re-export machinery consistent.

Changes:

  • Modernize typing throughout core modules and tests (e.g., X | None, built-in generics, and collections.abc.*).
  • Widen/adjust a few public typings without changing runtime behavior (e.g., shortcuts.progressbar() now accepts Iterable[T]; deltas_to_seconds gains overloads).
  • Add a new test to ensure _NAME_TO_MODULE, the TYPE_CHECKING import block, and __all__ remain synchronized.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_subclass_compat.py Removes now-unnecessary type-check suppression after SamplesMixin.samples typing is corrected.
tests/test_init_exports.py New guard tests for __init__.py export list / lazy mapping consistency.
tests/conftest.py Adds/adjusts fixture annotations and parameter types.
ruff.toml Updates Ruff target-version to py310.
progressbar/widgets.py Broad typing modernization; fixes/expands several widget-related annotations.
progressbar/utils.py Adds overloads for deltas_to_seconds; updates stream-wrapper listener types and other annotations.
progressbar/terminal/stream.py Updates iterator/generator typing imports and annotations.
progressbar/terminal/base.py Typing modernization for terminal/color utilities and related callables/unions.
progressbar/shortcuts.py Widens progressbar() input typing from Iterator[T] to Iterable[T].
progressbar/multi.py Typing modernization for MultiBar public methods and helpers.
progressbar/fast.py Uses collections.abc.Callable for the native formatter hook typing.
progressbar/bar.py Sweeps core bar types toward PEP 604/585/collections.abc style.
progressbar/__init__.py Adds documentation clarifying __all__ synchronization expectations (and is now test-guarded).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread progressbar/utils.py Outdated
Comment thread progressbar/widgets.py
wolph added 9 commits July 6, 2026 06:43
…typing

- Replace the TYPE_CHECKING `from .bar import ProgressBarMixinBase` and its
  listener annotations with a local `_ProgressListener` Protocol, so utils has
  no dependency on bar (CodeQL flagged the type-checking-only edge as a
  module-level cyclic import; no runtime cycle existed). Clears both the
  utils.py and bar.py cyclic-import alerts by removing the only utils->bar edge.
- Give the two deltas_to_seconds @overload stubs docstring bodies instead of
  `...` (CodeQL 'statement has no effect').
- Add `int` to the *deltas parameter type to match the documented/implemented
  behavior. Annotation-only; runtime unchanged.
The UnicodeEncodeError fallback encoded to bytes and wrote them to the
text stream fd (masked by a typing.cast(str, ...)), which raises
TypeError at runtime. Decode the ASCII-encoded bytes back to str before
writing. Fixes a latent bug in the (uncovered) fallback path.
- create_marker: define _marker inside the isinstance(str) branch, closing
  over a fresh str local, so the typing.cast(str, marker) is no longer
  needed (behavior-identical; _marker was only ever wrapped in that branch).
- _calculate_eta (ETA + AbsoluteETA): tighten value to float (callers pass a
  value resolved by _resolve_value_elapsed), dropping the typing.cast(float).
- Drop redundant '| None' from terminal.OptionalColor fields (OptionalColor
  already includes None).
Reference progressbar._NAME_TO_MODULE via an alias instead of mixing
`import progressbar` with `from progressbar import ...` (CodeQL
py/import-and-import-from), matching tests/test_fast_default.py.
ProgressBarMixinBase.__del__ ran finish() but never chained to a super
__del__ (CodeQL 'missing call to superclass __del__', re-surfaced after
the ProgressBarBase base-class change). Add a hasattr-guarded
super().__del__() call: a no-op today (abc.ABC/object define no __del__)
but keeps finalization cooperative and avoids an AttributeError if a base
with __del__ is ever added.
…mport-from)

Remove the plain 'import collections' that CodeQL flagged as mixing with
'from collections import defaultdict'. Keep the from-import (defaultdict is
a public re-export recorded in the API snapshot) and reference defaultdict
bare; 'import collections.abc' stays for collections.abc.Callable.
…ing-call-to-delete)

CodeQL flagged ProgressBarBase (Iterable + ProgressBarMixinBase multiple
inheritance) for finalizing without an explicit __del__ calling the base
finalizer. Revert the earlier wrong-class guarded super().__del__() in
ProgressBarMixinBase and define ProgressBarBase.__del__ = super().__del__(),
which resolves to the same ProgressBarMixinBase.__del__ reached by
inheritance — behaviourally identical, MI chain now explicit.
…nherited-attribute)

FormatCustomText.__init__ and JobStatusBar.__init__ set instance
attributes (format; name/left/right/fill) that their super().__init__
immediately re-sets from the same values, which CodeQL flags as
overwriting the inherited attribute. Remove the duplicates; the base
initializers (FormatWidgetMixin, Bar, VariableMixin) set them. Verified
attrs identical after construction; no behavior change.
…emantics

Two attempts to satisfy CodeQL's py/missing-call-to-delete approximation
added finalizer complexity with zero behavioral gain: ProgressBarBase
defines no __del__ of its own and runtime attribute lookup reaches
ProgressBarMixinBase.__del__ unconditionally; CodeQL's static super()
model simply cannot follow the MRO past the Iterable hop. Restore the
exact develop semantics (teardown-fragile code should stay simple) and
dismiss the alert as a false positive instead.
@wolph wolph merged commit 4a98f81 into develop Jul 6, 2026
23 checks passed
@wolph wolph deleted the quality-audit-4-typing branch July 6, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants