Skip to content

Conversation

@bmr-cymru
Copy link
Contributor

@bmr-cymru bmr-cymru commented Dec 5, 2025

  • progress: factor out common code and checks in start(), progress(), end()
  • progress: factor out width calc via class vars for static content size
  • progress: build colors with enumerate() rather than zip()
  • progress: add type hints to members
  • progress: drop unused cleared member from Progress
  • progress: add no_clear=False arg to not erase progress bar at end
  • progress: fix wrong ANSI foreground color initialisation
  • test: fix test_term_control_init_success BLACK != GREEN
  • progress: catch BaseException rather than Exception
  • progress: rename QuietProgress -> NullProgress
  • doc: improve snapm._progress doc strings
  • progress: factor out color init and re-raise system exceptions
  • progress: drop no-longer existent _do_start total docstrings
  • progress: don't output initial bar in Progress._do_start()
  • progress: drop redundant self.total = 0 in Progress._do_end()
  • progress: mark done as unused in NullProgress._do_progress()

Resolves: #635
Resolves: #636
Resolves: #637
Resolves: #638
Resolves: #639
Resolves: #640
Resolves: #641
Resolves: #642
Resolves: #643
Resolves: #644
Resolves: #645
Resolves: #648
Resolves: #649
Resolves: #650
Resolves: #651
Resolves: #652
Resolves: #653

Summary by CodeRabbit

  • New Features

    • Added no_clear option to keep final progress line visible.
    • Added configurable sizing defaults (DEFAULT_COLUMNS, PROGRESS_MIN_WIDTH, DEFAULT_WIDTH_FRAC).
    • Two-line progress display with dynamic width/prefix handling.
  • Refactor

    • Introduced ProgressBase with start/progress/end hooks and width calculation.
    • Renamed QuietProgress to NullProgress; updated public exports and factory to consider no_clear/quiet.
    • Improved terminal colour handling and robustness for non-tty/unsupported terminals.
  • Tests

    • Updated tests for renames, lifecycle, colour and no_clear behaviour.

✏️ Tip: You can customize this high-level summary in your review settings.

@bmr-cymru bmr-cymru self-assigned this Dec 5, 2025
@bmr-cymru bmr-cymru added the bug Something isn't working label Dec 5, 2025
@bmr-cymru bmr-cymru added enhancement New feature or request Python Python issues UI/UX User interface and experience Cleanup labels Dec 5, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

Adds a ProgressBase with lifecycle hooks and width-calculation, factors terminal colour init into _init_colors, renames QuietProgress → NullProgress, adds no_clear to Progress, broadens curses init error handling, and updates exports and tests to reflect the new API.

Changes

Cohort / File(s) Summary
Progress core
snapm/_progress.py
Added DEFAULT_COLUMNS, PROGRESS_MIN_WIDTH, DEFAULT_WIDTH_FRAC. Introduced ProgressBase with _calculate_width, _do_start, _check_in_progress, _do_progress, _do_end, and FIXED. Factored colour init into _init_colors, introduced _ANSI_COLORS and _LEGACY_COLORS, switched to enumerate() for colour building, catch BaseException during curses init. Replaced QuietProgress with NullProgress. Added no_clear: bool = False to Progress. Updated SimpleProgress to use the new base and width logic. Updated exports.
Public API surface
snapm/_snapm.py
Updated imports and __all__ to expose NullProgress and ProgressBase; removed QuietProgress from exports.
Tests
tests/test_progress.py
Replaced QuietProgress references with NullProgress; adjusted tests for ProgressBase lifecycle, width/FIXED enforcement, ANSI colour expectations, no_clear behaviour and factory return values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect ProgressBase hook implementations and state-machine enforcement in _check_in_progress.
  • Verify _calculate_width() across width, width_frac and FIXED.
  • Confirm Progress.end() no_clear handling and final-line retention.
  • Review _init_colors() ANSI vs legacy assignments and use of enumerate().
  • Ensure catching BaseException re-raises KeyboardInterrupt/SystemExit where appropriate.

Possibly related PRs

Poem

🐇
I nibble at bars and tidy the line,
Hooks stitched in order, widths now align,
NullProgress naps while colours take flight,
I keep the final glow if you wish it to light —
Hooray, progress hops into sight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.90% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Clean up and enhance progress implementation' is broad but accurately reflects the PR's main focus of refactoring and improving the progress module.
Linked Issues check ✅ Passed The PR comprehensively addresses all 19 linked issues including DRY refactoring, state machine enforcement, width calculation factoring, colour handling fixes, no_clear parameter addition, type hints, removal of unused members, NullProgress renaming, and improved docstrings.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issues and PR objectives; no out-of-scope modifications detected in the progress module refactoring.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bmr-progress-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@packit-as-a-service
Copy link

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo dnf install -y 'dnf*-command(copr)'
  • dnf copr enable packit/snapshotmanager-snapm-646
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

1 similar comment
@packit-as-a-service
Copy link

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo dnf install -y 'dnf*-command(copr)'
  • dnf copr enable packit/snapshotmanager-snapm-646
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_progress.py (1)

91-130: Remove unused variable assignments.

The bp variables are assigned but never used since the ValueError is raised during __init__. The assignment is unnecessary and triggers static analysis warnings (F841).

Apply this diff to remove the unused assignments:

         with self.assertRaisesRegex(ValueError, r"self\.FIXED must be"):
-            bp = BadProgress()
+            BadProgress()

     def test_bad_child_no_header(self):
         class BadProgress(ProgressBase):
             FIXED = 1

             def __init__(self):
                 self.width = self._calculate_width(width=20)

             def _do_start(self, _total: int):
                 pass

             def _do_progress(self, _done: int, _message: str):
                 pass

             def _do_end(self, _message: str):
                 pass

         with self.assertRaisesRegex(ValueError, r"self\.header must be"):
-            bp = BadProgress()
+            BadProgress()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53542ae and 2df6cc0.

📒 Files selected for processing (3)
  • snapm/_progress.py (16 hunks)
  • snapm/_snapm.py (2 hunks)
  • tests/test_progress.py (9 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
🪛 Ruff (0.14.7)
tests/test_progress.py

110-110: Local variable bp is assigned to but never used

Remove assignment to unused variable bp

(F841)


129-129: Local variable bp is assigned to but never used

Remove assignment to unused variable bp

(F841)

snapm/_progress.py

158-158: Do not catch blind exception: BaseException

(BLE001)


261-264: Avoid specifying long messages outside the exception class

(TRY003)


267-270: Avoid specifying long messages outside the exception class

(TRY003)


273-273: Avoid specifying long messages outside the exception class

(TRY003)


297-297: Avoid specifying long messages outside the exception class

(TRY003)


325-325: Avoid specifying long messages outside the exception class

(TRY003)


328-328: Avoid specifying long messages outside the exception class

(TRY003)


331-331: Avoid specifying long messages outside the exception class

(TRY003)


443-443: Avoid specifying long messages outside the exception class

(TRY003)


646-646: Unused method argument: done

(ARG002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (34)
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, fedora41)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora41)
  • GitHub Check: virt_tests (bios, lvm, fedora41)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora41)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (17)
snapm/_snapm.py (1)

25-31: LGTM!

The rename from QuietProgress to NullProgress is applied consistently in both the import and the __all__ export list, aligning with the PR objective to rename whilst preserving the higher-level quiet=True interface.

Also applies to: 2271-2271

tests/test_progress.py (6)

62-74: LGTM!

The test fix correctly validates BLACK (index 0 in _ANSI_COLORS) rather than GREEN, matching the mocked tparm return value of \x1b[30m. This aligns with PR objective #643.


207-241: LGTM!

The test_lifecycle_no_clear test properly validates that when no_clear=True, the progress bar header and final message are preserved in the output (verifying 2 lines). This aligns with PR objective #639.


265-279: LGTM!

The new tests for end_before_start_raises and progress_after_end_raises properly validate the state machine enforcement, ensuring that progress() and end() cannot be called before start(), and that progress() cannot be called after end(). This aligns with PR objective #635.


321-335: LGTM!

The new lifecycle validation tests for SimpleProgress ensure consistent state machine enforcement across all progress implementations.


350-399: LGTM!

The test class is properly renamed to TestNullProgress with all references updated. The new lifecycle validation tests ensure NullProgress also enforces the same state machine semantics as other progress implementations.


402-406: LGTM!

Factory test correctly verifies that quiet=True returns a NullProgress instance.

snapm/_progress.py (10)

17-24: LGTM!

Good extraction of magic numbers into named module-level constants. This improves maintainability and aligns with PR objective #637 for factoring width calculation via class variables.


129-129: LGTM!

The rename to _ANSI_COLORS follows Python naming conventions, and the use of enumerate() is more idiomatic than zip(range(...), ...). This also correctly fixes the ANSI foreground colour initialisation (PR objective #642).

Also applies to: 174-196


150-159: Justified broad exception catch.

The BaseException catch is intentional as documented in the comment and aligns with PR objective #644. The curses module's error class exhibits unusual behaviour that requires this approach. The static analysis warning (BLE001) can be safely ignored here.


221-287: LGTM!

The ProgressBase ABC correctly implements the template method pattern with centralised validation. The FIXED class variable and _calculate_width() method factor out common width calculation logic (PR objective #637), and instance attributes now have type hints (PR objective #640).


289-378: LGTM!

The lifecycle methods (start, progress, end) correctly implement state machine enforcement via _check_in_progress(). The template method pattern delegates subclass-specific behaviour to _do_start(), _do_progress(), and _do_end(). This aligns with PR objectives #635 and #636 for DRY validation and factoring common code.


381-448: LGTM!

The Progress class correctly defines FIXED = 9 for width calculation, adds the no_clear parameter (PR objective #639), calls super().__init__(), and initialises instance attributes with type hints (PR objective #640).


517-540: LGTM!

The _do_end() method correctly implements the no_clear behaviour: when True, it preserves the progress bar on the terminal rather than erasing it. This provides users with a visual reference of the completed progress, as per PR objective #639.


632-664: LGTM!

The rename from QuietProgress to NullProgress aligns with PR objective #645. The class correctly implements the no-op progress pattern, and the unused done parameter in _do_progress() is required to satisfy the abstract method signature.


667-730: LGTM!

The ProgressFactory.get_progress() method correctly adds the no_clear parameter with appropriate documentation, returns NullProgress for quiet mode, and passes no_clear only to Progress (as SimpleProgress doesn't use terminal control sequences).


733-739: LGTM!

The __all__ list correctly exports NullProgress instead of QuietProgress. Note that ProgressBase is not exported, which is appropriate if it's intended only for internal subclassing. If users should be able to create custom progress implementations, consider adding ProgressBase to the exports.

@bmr-cymru
Copy link
Contributor Author

@coderabbitai generate docstrings

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #647

coderabbitai bot added a commit that referenced this pull request Dec 5, 2025
Docstrings generation was requested by @bmr-cymru.

* #646 (comment)

The following files were modified:

* `snapm/_progress.py`
* `tests/test_progress.py`
@bmr-cymru bmr-cymru force-pushed the bmr-progress-improvements branch from 2df6cc0 to 3c779fb Compare December 5, 2025 13:27
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
snapm/_progress.py (1)

131-163: Re-raise KeyboardInterrupt and SystemExit when catching BaseException in TermControl

The current code catches BaseException around curses.setupterm() to work around curses module quirks, but this also suppresses KeyboardInterrupt and SystemExit—both of which inherit directly from BaseException rather than Exception. This defeats normal interrupt handling in CLI tools.

Preserve the broad catch for the curses workaround while re-raising these special cases:

         try:
             curses.setupterm()
         # Attempting to catch curses.error raises 'TypeError: catching classes
         # that do not inherit from BaseException is not allowed' even though
         # it claims to inherit from builtins.Exception:
         #
         #     Help on class error in module _curses:
         #     class error(builtins.Exception)
-        except BaseException:  # pylint: disable=broad-exception-caught
+        except BaseException as exc:  # pylint: disable=broad-exception-caught
+            # Preserve normal interruption/termination semantics.
+            if isinstance(exc, (KeyboardInterrupt, SystemExit)):
+                raise
             return  # pragma: no cover
🧹 Nitpick comments (2)
snapm/_progress.py (2)

232-302: ProgressBase’s width calculation and preconditions look robust

The shared _calculate_width and _check_in_progress logic provides a clean, centralised enforcement of:

  • FIXED initialisation,
  • header presence,
  • mutual exclusivity of width/width_frac,
  • sensible defaults based on terminal columns, and
  • negative / >total checks.

This should make custom subclasses much harder to misuse. Only minor nit: the _do_start docstring still documents a total parameter even though the method is called without arguments; you might want to update that docstring for accuracy at some point.


647-685: NullProgress implementation matches the “no output but full validation” goal

Subclassing ProgressBase and implementing _do_start, _do_progress, and _do_end as pure no-ops is exactly what you want here; all state and argument checks are inherited. One very minor nit is that the done argument is unused; if you want to quieten static analysis you could rename it to _done or add an inline # noqa: ARG002 comment, but this is purely cosmetic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2df6cc0 and 3c779fb.

📒 Files selected for processing (3)
  • snapm/_progress.py (17 hunks)
  • snapm/_snapm.py (2 hunks)
  • tests/test_progress.py (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
snapm/_snapm.py (1)
snapm/_progress.py (1)
  • NullProgress (647-685)
🪛 Ruff (0.14.7)
snapm/_progress.py

162-162: Do not catch blind exception: BaseException

(BLE001)


275-278: Avoid specifying long messages outside the exception class

(TRY003)


281-284: Avoid specifying long messages outside the exception class

(TRY003)


287-287: Avoid specifying long messages outside the exception class

(TRY003)


311-311: Avoid specifying long messages outside the exception class

(TRY003)


340-340: Avoid specifying long messages outside the exception class

(TRY003)


343-343: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Avoid specifying long messages outside the exception class

(TRY003)


456-456: Avoid specifying long messages outside the exception class

(TRY003)


663-663: Unused method argument: done

(ARG002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (38)
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora41)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora41)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, fedora41)
  • GitHub Check: virt_tests (bios, lvm, fedora41)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: python-basic-tests
🔇 Additional comments (13)
snapm/_snapm.py (1)

25-31: Public API rename to NullProgress looks consistent

The import and __all__ update cleanly switch the public symbol from QuietProgress to NullProgress and align with the new implementation and tests. No issues spotted here.

Also applies to: 2191-2291

tests/test_progress.py (7)

54-75: TermControl colour initialisation test matches _ANSI_COLORS mapping

The mocked tigetstr/tparm setup and the tc.BLACK == "\x1b[30m" assertion correctly exercise the updated colour initialisation logic (BLACK mapped to index 0). The test remains fully decoupled from any real terminal and looks stable.


91-131: Good invariants coverage for ProgressBase subclasses

The BadProgress inner classes deliberately violating FIXED and header requirements give precise, fast feedback if future subclasses misuse _calculate_width(). The regexes match the error prefixes, so small wording tweaks won't break the tests. Nicely targeted coverage.


207-241: no_clear lifecycle behaviour is well specified by this test

The test_lifecycle_no_clear case exercises the full start/progress/end flow and asserts that both the header and final message remain visible with exactly two lines of output, which matches the intended semantics for no_clear=True. This is a solid regression test for the new flag.


265-280: State-machine checks for Progress after end are clear and strict

The tests for end before start and progress after end enforce the strengthened state machine behaviour via the shared error message fragment “called before start”. This aligns with the ProgressBase._check_in_progress logic and should prevent subtle misuse.


298-300: SimpleProgress flow and validation tests look good

The expected bar string for 50% at width 50 matches the current formatting logic, and the various negative / out-of-range / pre-start / post-end tests comprehensively exercise SimpleProgress’s shared validation via ProgressBase. Nothing to change here.

Also applies to: 315-336


350-400: NullProgress lifecycle tests accurately capture the no-op contract

The tests ensure NullProgress is:

  • silent during a normal lifecycle, and
  • still subject to the same argument and state validation as other ProgressBase subclasses.

That gives good confidence the “quiet” implementation doesn’t accidentally skip validation.


402-435: Factory quiet-path now correctly returns NullProgress

test_get_progress_quiet and the args-validation test together confirm:

  • quiet=True yields NullProgress, and
  • conflicting width/width_frac arguments are still rejected in the non-quiet path.

This neatly locks in the intended factory semantics.

snapm/_progress.py (5)

17-25: Width and colour refactors in TermControl are sound

The introduction of DEFAULT_COLUMNS, PROGRESS_MIN_WIDTH, and DEFAULT_WIDTH_FRAC gives a clear, centralised policy for width calculation. Splitting _COLORS and _ANSI_COLORS and using enumerate in the colour setup loops correctly aligns indices with the intended terminfo capabilities and matches the updated tests asserting BLACK initialisation. No functional issues spotted here.

Also applies to: 121-130, 174-201


303-382: Lifecycle orchestration in ProgressBase.start/progress/end is coherent

The pattern of:

  • validating total in start(),
  • using _check_in_progress() in both progress() and end(), and
  • having end() drive a final progress(self.total, "") before _do_end()

gives consistent semantics across all subclasses (including NullProgress) and cleanly enforces the “no progress before start / after end / negative / >total” rules now covered by the tests. The internal resetting of total to 0 after _do_end() is also aligned with the test expectations.


409-462: Progress integration with ProgressBase and no_clear behaves as intended

The constructor now cleanly:

  • delegates common initialisation to ProgressBase,
  • sets header, term, stream,
  • validates the required terminal capabilities, and
  • uses _calculate_width() for consistent bar sizing while honouring explicit width/width_frac.

The no_clear flag is correctly honoured in _do_end(), matching the new test_lifecycle_no_clear expectations while preserving the original behaviour when no_clear is False. The Unicode/ASCII fallback for bar characters is unchanged and still safe.

No functional problems seen here.

Also applies to: 476-554


561-645: SimpleProgress’s move onto ProgressBase is straightforward

SimpleProgress now reuses the shared lifecycle and width logic, while keeping its own formatting (BAR, DID, TODO, FIXED). _do_start being a no-op is explicitly documented, and _do_progress / _do_end match the expectations in the updated tests. This significantly reduces duplication without changing observable behaviour.


693-752: ProgressFactory changes correctly wire in NullProgress and no_clear

The factory now:

  • returns NullProgress() when quiet is True,
  • keeps using SimpleProgress for non-tty streams, and
  • passes no_clear through when constructing Progress for real terminals.

This matches both the design goals and the updated tests. Arguments and type hints in the docstring are also up to date.

Also applies to: 755-760

@bmr-cymru bmr-cymru force-pushed the bmr-progress-improvements branch from 3c779fb to b4ead55 Compare December 5, 2025 18:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
snapm/_progress.py (1)

379-389: Minor: Redundant validation in end().

The call to _check_in_progress(self.total) on line 386 validates state, but self.progress(self.total, "") on line 387 will invoke _check_in_progress again internally. This is harmless but slightly redundant.

Consider removing the explicit _check_in_progress call and relying on the one inside progress():

     def end(self, message: Optional[str] = None):
-        self._check_in_progress(self.total)
+        if self.total == 0:
+            theclass = self.__class__.__name__
+            raise ValueError(f"{theclass}.end() called before start()")
         self.progress(self.total, "")
         self._do_end(message)
         self.total = 0

Alternatively, keep the current approach for clarity—it explicitly documents that end() requires an active progress run.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c779fb and b4ead55.

📒 Files selected for processing (3)
  • snapm/_progress.py (16 hunks)
  • snapm/_snapm.py (2 hunks)
  • tests/test_progress.py (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snapm/_snapm.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T20:06:54.993Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Applied to files:

  • snapm/_progress.py
🧬 Code graph analysis (2)
tests/test_progress.py (1)
snapm/_progress.py (23)
  • ProgressBase (243-398)
  • Progress (401-552)
  • SimpleProgress (555-640)
  • NullProgress (643-678)
  • TermControl (27-240)
  • _do_start (329-335)
  • _do_start (485-490)
  • _do_start (593-599)
  • _do_start (648-654)
  • _do_progress (369-377)
  • _do_progress (492-528)
  • _do_progress (601-627)
  • _do_progress (656-667)
  • _do_end (392-398)
  • _do_end (530-552)
  • _do_end (629-640)
  • _do_end (669-678)
  • progress (356-366)
  • end (379-389)
  • render (221-234)
  • start (314-326)
  • ProgressFactory (681-745)
  • get_progress (687-745)
snapm/_progress.py (1)
tests/test_progress.py (6)
  • _do_start (101-102)
  • _do_start (120-121)
  • _do_progress (104-105)
  • _do_progress (123-124)
  • _do_end (107-108)
  • _do_end (126-127)
🪛 Ruff (0.14.7)
snapm/_progress.py

286-289: Avoid specifying long messages outside the exception class

(TRY003)


292-295: Avoid specifying long messages outside the exception class

(TRY003)


298-298: Avoid specifying long messages outside the exception class

(TRY003)


322-322: Avoid specifying long messages outside the exception class

(TRY003)


348-348: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


464-464: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (31)
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: virt_tests (uefi, lvm, fedora41)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora41)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, fedora41)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora41)
  • GitHub Check: python-basic-tests
🔇 Additional comments (22)
tests/test_progress.py (8)

14-21: LGTM!

The imports correctly reflect the refactored module structure, adding ProgressBase and NullProgress whilst removing the deprecated QuietProgress.


62-75: LGTM!

The test correctly validates BLACK colour initialisation using the mocked tparm return value \x1b[30m, fixing the previous incorrect assertion that checked GREEN. This aligns with issue #643.


92-131: LGTM!

The TestProgressBase class provides good coverage for the validation logic in _calculate_width, testing both the FIXED constant requirement and the header attribute requirement. The subclasses correctly implement the abstract methods to isolate the specific validation being tested.


174-216: LGTM!

The lifecycle test correctly validates the new progress flow where start() initialises state without producing output, and subsequent progress() calls render the bar. The assertions properly check for terminal control sequences and message content.


218-262: LGTM!

Good coverage for the new no_clear=True option. The test correctly validates that when no_clear is enabled, the progress bar remains visible after end() is called, as evidenced by checking for both the header and message in the output along with the two-line structure.


286-300: LGTM!

Good additions for state-machine enforcement. These tests validate that end() before start() and progress() after end() correctly raise ValueError, ensuring proper lifecycle sequencing as specified in issue #636.


371-420: LGTM!

The TestNullProgress class correctly validates that NullProgress (renamed from QuietProgress) produces no output whilst still enforcing the same lifecycle validation rules inherited from ProgressBase. This ensures consistent behaviour across all progress implementations.


423-426: LGTM!

The factory test correctly validates that quiet=True now returns a NullProgress instance, aligning with the rename from QuietProgress.

snapm/_progress.py (14)

17-24: LGTM!

Well-documented module-level constants that centralise previously scattered magic numbers. The defaults are sensible and improve maintainability.


128-131: LGTM!

Correctly separates legacy (setf) and ANSI (setaf) colour orderings. The ANSI order follows the standard SGR colour sequence, fixing issue #642.


133-162: LGTM!

The _init_colors method is well-structured, using enumerate() as per issue #638, and correctly handles both legacy terminfo (setf/setb) and ANSI (setaf/setab) colour capabilities. The separation of foreground and background initialisation is clear and maintainable.


187-199: LGTM!

Correctly catches BaseException to handle the curses.error issue documented in the comment (issue #644). Importantly, the code re-raises KeyboardInterrupt and SystemExit to preserve normal termination semantics.


243-261: LGTM!

The ProgressBase ABC provides a clean template method pattern with well-typed instance attributes. The FIXED = -1 sentinel ensures subclasses must override it before width calculation.


262-312: LGTM!

The _calculate_width method correctly validates prerequisites and calculates the progress bar width. The logic for falling back to DEFAULT_COLUMNS when terminal width is unavailable, and enforcing PROGRESS_MIN_WIDTH as a minimum, are sensible safeguards.


314-326: LGTM!

The start method correctly validates total > 0, sets state, and delegates to the _do_start hook. This centralises the validation logic as required by issue #636.


337-354: LGTM!

The _check_in_progress method correctly validates progress state with clear, class-specific error messages. Using the class name in messages aids debugging for subclass-specific issues.


424-469: LGTM!

The Progress class correctly integrates the no_clear option and uses the first_update flag to handle cursor positioning differently for initial vs subsequent progress updates. The super().__init__() call properly chains to ProgressBase.


530-552: LGTM!

The _do_end method correctly implements the no_clear option. When no_clear=False, the progress bar is erased; when no_clear=True, the bar remains visible and only cursor positioning is adjusted before printing the completion message.


555-641: LGTM!

SimpleProgress correctly integrates with ProgressBase, defining FIXED = 12 for its format string and implementing the required hooks. The no-op _do_start is appropriate since there's no terminal setup needed for simple line-by-line output.


643-679: LGTM!

NullProgress correctly inherits from ProgressBase and implements all hooks as no-ops. Since it produces no output, it doesn't need to override FIXED or calculate width, making the inherited defaults appropriate.


686-745: LGTM!

The factory correctly routes to NullProgress when quiet=True, SimpleProgress for non-TTY streams, and Progress with terminal capabilities otherwise. The no_clear parameter is properly passed through to Progress.


748-754: LGTM!

The __all__ list correctly exports the public API. Note that ProgressBase is not exported (which is appropriate for an ABC intended for internal inheritance), though it can still be imported for testing purposes.

…nd()

Use template `start()`, `stop()`, `end()` methods in `ProgressBase` and
delegate to concrete classes via `self._do_*()` hooks.

Resolves: #636

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #637

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #638

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #640

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #641

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
@bmr-cymru bmr-cymru force-pushed the bmr-progress-improvements branch from b4ead55 to b27af6f Compare December 5, 2025 18:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/test_progress.py (1)

218-262: no_clear behaviour is well covered; minor optional strengthening

The test_lifecycle_no_clear exercise of no_clear=True and the check for two output lines containing the header and final message gives good confidence that the last bar is preserved. If you ever want to tighten this further, you could also assert that the final output contains ": 100% [" to confirm the completed-bar rendering without making the test overly brittle.

snapm/_progress.py (2)

416-469: Progress initialisation cleanly uses the base class and new options

Using super().__init__() plus explicit header, term, stream, and width calculation keeps setup straightforward. The capabilities check on CLEAR_EOL, UP, and BOL is a reasonable minimal contract, and the no_clear flag and first_update state integrate neatly with the hook methods. One small follow-up: the __init__ docstring doesn’t yet describe the no_clear parameter, so adding a :param no_clear: entry (similar to ProgressFactory.get_progress) would keep the public API documentation consistent.


285-355: TRY003 warnings are style-only; current messages are acceptable

Ruff’s TRY003 hints here are about long ValueError messages being defined inline rather than on custom exception types. Given these errors are user-facing and already quite clear, I’d treat this as a style choice rather than something to change immediately; if you prefer a quieter linter, you could either suppress TRY003 for this module or gradually refactor to custom exception classes later.

Also applies to: 463-463

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4ead55 and b27af6f.

📒 Files selected for processing (3)
  • snapm/_progress.py (16 hunks)
  • snapm/_snapm.py (2 hunks)
  • tests/test_progress.py (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snapm/_snapm.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
📚 Learning: 2025-10-22T20:06:54.993Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Applied to files:

  • snapm/_progress.py
🪛 Ruff (0.14.7)
snapm/_progress.py

286-289: Avoid specifying long messages outside the exception class

(TRY003)


292-295: Avoid specifying long messages outside the exception class

(TRY003)


298-298: Avoid specifying long messages outside the exception class

(TRY003)


322-322: Avoid specifying long messages outside the exception class

(TRY003)


348-348: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


463-463: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (38)
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora41)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora41)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora41)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora41)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: python-basic-tests
🔇 Additional comments (21)
tests/test_progress.py (8)

58-76: TermControl colour capability test matches new initialisation logic

The updated mocking of tigetstr/tparm and the assertion against tc.BLACK correctly exercise the legacy colour path and ensure index 0 maps to black, which aligns with the new _ANSI_COLORS/_LEGACY_COLORS handling.


92-131: Good coverage of ProgressBase width preconditions

The BadProgress subclasses nicely pin down the requirements that FIXED and header be initialised before _calculate_width() is used. The regexes are tolerant of minor message wording changes while still checking the key phrases.


174-217: Lifecycle test updated correctly for deferred initial render

The test_lifecycle assertions for no output on start(), presence of header and message on progress(), and total reset to 0 after end() all match the new ProgressBase/Progress behaviour of deferring the first draw until the first update and clearing state on completion.


286-307: State-machine tests for Progress align with ProgressBase checks

The new test_end_before_start_raises and test_progress_after_end_raises correctly validate the shared ProgressBase._check_in_progress() semantics: end() before start() and progress() after end() both surface as “called before start”. This is a useful regression guard for the lifecycle logic.


319-341: SimpleProgress expectations and error handling look correct

The updated test_flow string expectation matches the new width calculation and bar formatting, and the negative-progress test now simply asserts a ValueError, which is consistent with the shared base-class validation.


342-369: SimpleProgress inherits the same lifecycle guarantees as Progress

The new tests for end() before start(), progress() after end(), and done > total all exercise the ProgressBase validation path for the simple implementation, ensuring consistent semantics across both progress styles.


371-421: NullProgress tests correctly validate no-op behaviour plus state checks

The TestNullProgress suite verifies that lifecycle methods are callable without output while still enforcing total > 0, non-negative progress, done <= total, and the same “called before start” behaviour for end()/post-end() progress. This is exactly what you want from a quiet/no-op implementation.


423-456: Factory now correctly returns NullProgress for quiet mode

Asserting that get_progress(..., quiet=True) yields a NullProgress instance keeps the tests aligned with the public API rename from QuietProgress and protects the quiet-path behaviour.

snapm/_progress.py (13)

17-25: Centralised width defaults are clear and reusable

Introducing DEFAULT_COLUMNS, PROGRESS_MIN_WIDTH, and DEFAULT_WIDTH_FRAC gives a single source of truth for sizing across implementations and makes tests easier to reason about. Values chosen are sensible for typical terminals.


128-163: Colour initialisation correctly separates legacy and ANSI mappings

The _LEGACY_COLORS vs _ANSI_COLORS split, combined with _init_colors(), matches the expected setf/setb vs setaf/setab orders and ensures ANSI indices no longer get the wrong colour names. The use of _tigetstr() keeps capability handling consistent and the fallback behaviour (empty strings when capabilities are absent) remains intact.


164-211: TermControl initialisation and error handling align with curses quirks

The updated __init__ correctly bails out for non-ttys, uses BaseException to sidestep the odd curses.error inheritance issue while still re-raising KeyboardInterrupt/SystemExit, and then delegates colour setup to _init_colors(). This matches the intent of the linked issues and behaves safely when curses capabilities are missing.


221-235: render() docstring now fully describes substitution behaviour

The expanded docstring clearly documents ${NAME} handling, the empty-string fallback for unknown capabilities, and the $$ escape, which matches the implementation and helps callers understand how to embed control sequences in templates.


243-313: ProgressBase initial state and width calculation look sound

The base __init__ initialises all shared attributes, and _calculate_width() enforces FIXED and header preconditions, mutual exclusivity of width/width_frac, and applies sensible defaults (DEFAULT_COLUMNS, DEFAULT_WIDTH_FRAC, PROGRESS_MIN_WIDTH). The use of self.FIXED + len(self.header) to derive available bar width matches the tests and keeps derived classes consistent.


314-389: Shared lifecycle and range checks in ProgressBase are well factored

start(), _check_in_progress(), progress(), and end() now centralise validation and state transitions (including zeroing total after completion). The error messages are clear and the tests exercise all key paths (non-positive totals, negative/overshoot progress, before-start and after-end calls), delivering the DRY/state-machine guarantees the PR aims for.


484-531: Deferred first render and update prefix logic are consistent

Initialising pbar in _do_start() without emitting output, and then using first_update in _do_progress() to switch from a simple ${BOL} prefix on the first write to ${BOL}${UP}${CLEAR_EOL} for subsequent updates, matches the tests’ expectations and avoids the extra initial bar draw that SimpleProgress never had. The flush-on-write behaviour is preserved.


532-555: no_clear end-of-progress behaviour matches tests and intent

The _do_end() branch for no_clear=False clears both lines of the bar, while the no_clear=True path leaves the completed bar in place and only tidies the current line before printing the optional message. This matches the new tests (including line-count expectations) and fulfils the “leave the bar visible” requirement.


562-594: SimpleProgress correctly adopts ProgressBase while remaining tty-agnostic

The new constants (BAR, DID, TODO, FIXED) and the use of _calculate_width() in __init__ keep behaviour in line with Progress, while _do_start() remaining a no-op preserves the simple, single-line semantics. The stream typing and assignment (term_stream or sys.stdout) look correct and resolve the earlier type-hint concern.


603-643: SimpleProgress update and end hooks remain straightforward and correct

_do_progress() now relies on the shared validation from ProgressBase and keeps its original formatting and flushing behaviour, and _do_end() simply prints the optional message then flushes. This minimalism is appropriate for the non-curses implementation and matches the updated tests.


645-681: NullProgress cleanly implements a no-op ProgressBase subclass

By implementing the three hook methods as no-ops and marking parameters as unused, NullProgress leverages all lifecycle and validation logic from ProgressBase while guaranteeing silent operation. This nicely replaces the old QuietProgress and aligns with the new tests that assert identical state-machine semantics without output.


688-747: ProgressFactory wiring and documentation look consistent with the new API

get_progress() now returns NullProgress for quiet=True, falls back to SimpleProgress for non-tty streams, and constructs Progress for tty streams while passing through width, width_frac, and no_clear (plus optional term_control). The expanded docstring for no_clear clearly explains its effect and notes that it’s ignored by implementations that don’t use TermControl.


750-757: Public export surface matches the refactored progress API

Updating __all__ to include NullProgress and ProgressBase (and drop the old QuietProgress) keeps the module’s public surface aligned with the new class hierarchy and the tests/imports in snapm._snapm and tests/test_progress.py.

Resolves: #639

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #642

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #643

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #644

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #645

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #648

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #649

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #650

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #651

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #652

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
Resolves: #653
Resolves: #635

Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
@bmr-cymru bmr-cymru force-pushed the bmr-progress-improvements branch from b27af6f to 7439b1f Compare December 5, 2025 18:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
snapm/_progress.py (1)

286-289: Consider shorter exception messages (optional).

Static analysis flags these ValueError messages as lengthy. Whilst the descriptive messages aid debugging, you might consider defining custom exception classes or shortening messages if you prefer stricter adherence to style guides.

Also applies to: 292-295, 298-298, 322-322, 348-348, 351-351, 354-354, 470-470

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b27af6f and 7439b1f.

📒 Files selected for processing (3)
  • snapm/_progress.py (16 hunks)
  • snapm/_snapm.py (2 hunks)
  • tests/test_progress.py (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_progress.py
  • snapm/_snapm.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.
📚 Learning: 2025-10-22T20:06:54.993Z
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Applied to files:

  • snapm/_progress.py
🧬 Code graph analysis (1)
snapm/_progress.py (1)
tests/test_progress.py (6)
  • _do_start (101-102)
  • _do_start (120-121)
  • _do_progress (104-105)
  • _do_progress (123-124)
  • _do_end (107-108)
  • _do_end (126-127)
🪛 Ruff (0.14.7)
snapm/_progress.py

286-289: Avoid specifying long messages outside the exception class

(TRY003)


292-295: Avoid specifying long messages outside the exception class

(TRY003)


298-298: Avoid specifying long messages outside the exception class

(TRY003)


322-322: Avoid specifying long messages outside the exception class

(TRY003)


348-348: Avoid specifying long messages outside the exception class

(TRY003)


351-351: Avoid specifying long messages outside the exception class

(TRY003)


354-354: Avoid specifying long messages outside the exception class

(TRY003)


470-470: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (33)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: virt_tests (bios, lvm-thin, fedora41)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora41)
  • GitHub Check: virt_tests (bios, lvm, fedora41)
  • GitHub Check: virt_tests (uefi, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora42)
  • GitHub Check: virt_tests (uefi, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm-thin, centos-stream9)
  • GitHub Check: virt_tests (uefi, lvm-thin, fedora41)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream10)
  • GitHub Check: virt_tests (bios, lvm, centos-stream10)
  • GitHub Check: virt_tests (uefi, lvm, centos-stream9)
  • GitHub Check: virt_tests (bios, lvm, fedora42)
  • GitHub Check: virt_tests (bios, lvm, centos-stream9)
  • GitHub Check: python-basic-tests
  • GitHub Check: container-tests
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-10-x86_64
🔇 Additional comments (12)
snapm/_progress.py (12)

17-24: Well-defined module constants.

These constants support centralised width calculation and provide sensible defaults. The documentation is clear and concise.


128-162: Excellent factoring of colour initialisation.

The new _init_colors method cleanly separates legacy and ANSI colour setup, uses enumerate() for cleaner iteration, and correctly applies _ANSI_COLORS for ANSI foreground colours (fixing issue #642).


195-211: Robust exception handling preserves system exit semantics.

Catching BaseException handles curses-related errors that don't inherit from Exception, whilst the re-raise logic correctly preserves KeyboardInterrupt and SystemExit to maintain normal interruption semantics.


243-312: Solid abstraction with template method pattern.

The ProgressBase class effectively centralises validation and width calculation whilst delegating rendering to subclasses. The _calculate_width method is thorough with appropriate validation, and the FIXED sentinel value enforces proper subclass configuration.


314-397: Clean lifecycle hooks with centralised validation.

The template method pattern correctly separates concerns: public methods handle validation and state transitions whilst abstract _do_* hooks implement subclass-specific behaviour. The _check_in_progress method effectively centralises validation logic.


423-476: Well-implemented no_clear feature.

The no_clear parameter allows users to preserve the final progress display. Type hints are correctly applied, and the width calculation properly delegates to the base class method.


491-537: Clever first_update logic avoids initial bar output.

The conditional prefix handling ensures the first progress update uses only BOL, whilst subsequent updates use BOL + UP + CLEAR_EOL to overwrite the previous display. This matches the behaviour of SimpleProgress and addresses objective #651.


539-561: Correct no_clear end behaviour for two-line display.

When no_clear=False, both lines are properly cleared. When no_clear=True, the progress bar (line 1) is preserved, the message line (line 2) is cleared and repositioned, and an optional completion message can be printed below the progress bar.


564-650: SimpleProgress correctly adapted to new base class.

The class properly implements the ProgressBase hooks with appropriate FIXED value, type hints, and width calculation delegation. The implementation remains straightforward for non-TTY streams.


652-688: Excellent rename to NullProgress.

The rename from QuietProgress to NullProgress better conveys the "no-op" behaviour. Unused parameters are correctly marked with underscore prefixes, and pragma: no cover appropriately excludes these trivial no-ops from coverage.


696-754: Factory correctly updated for new API.

The no_clear parameter is properly threaded through to the Progress class, documentation clearly notes it's ignored by non-terminal implementations, and the factory correctly returns NullProgress for quiet mode whilst maintaining backward compatibility.


757-764: Public API correctly updated.

Exports properly reflect the refactoring: NullProgress replaces QuietProgress, ProgressBase is exposed for custom implementations, and existing classes are maintained. Module constants remain unexported, which appears intentional for internal use.

@bmr-cymru
Copy link
Contributor Author

@coderabbitai: re:

Static analysis flags these ValueError messages as lengthy.

Not for me it doesn't:

[root@f43-test-vm1 snapm]# ruff check snapm
All checks passed!

Anyway, they aren't long. Most are fixed strings, and the few that aren't include the class name for good reason (debugging API violating subclasses). It's not worth defining a new Exception class or putting the class name in a variable and pre-building strings just to satisfy a strict reading of TRY003. Lots of code in the standard library does this with ValueError.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Tip

For best results, initiate chat on the files or code changes.

@bmr-cymru, understood! Thank you for the clarification. If Ruff passes cleanly for you and the messages serve a clear debugging purpose, that's perfectly reasonable. I appreciate you taking the time to explain the rationale—your approach makes sense given the context.


🧠 Learnings used
Learnt from: bmr-cymru
Repo: snapshotmanager/snapm PR: 541
File: snapm/manager/_mounts.py:174-197
Timestamp: 2025-10-22T20:06:54.993Z
Learning: For snapshotmanager/snapm follow-up requests from bmr-cymru, create a GitHub issue with label "enhancement", set/ensure a "type: Task" label, and assign it to bmr-cymru. Include backlinks to the PR and the discussion comment in the issue body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment