Skip to content

Make --no-status and --quiet suppress the progress bar - #135

Merged
ESultanik merged 2 commits into
masterfrom
133-suppress-progress-bars
Sep 8, 2026
Merged

ESultanik merged 2 commits into
masterfrom
133-suppress-progress-bars

Conversation

@ESultanik

Copy link
Copy Markdown
Collaborator

Closes #133

Problem

--no-status and --quiet do not suppress the "Diffing" progress bar, contrary to what README.md documents.

Root cause

main() builds a printer from the command line options and replaces the module attribute:

printermodule.DEFAULT_PRINTER = printer

Three modules bind the name at import time instead of reading it through the module:

  • graphtage/tree.pyfrom .printer import DEFAULT_PRINTER, Printer
  • graphtage/json.pyfrom .printer import DEFAULT_PRINTER, Fore, Printer
  • graphtage/levenshtein.pyfrom .printer import DEFAULT_PRINTER

Rebinding printermodule.DEFAULT_PRINTER leaves those names pointing at the printer that was current when each module was first imported, whose quiet is False. Their six tqdm call sites therefore draw progress bars whatever the flags say. The bar in the issue's reproducer comes from tree.py.

Approach

printer.py gains get_default_printer() and set_default_printer(), and the three modules call the accessor at the point of use rather than binding the name. main() installs its printer through set_default_printer().

The accessor resolves the printer when the progress bar is created, so the result no longer depends on which module Python imported first. That is what makes this different from a fix that only works today: any future module can call get_default_printer() without having to reason about import order, and adding another from .printer import DEFAULT_PRINTER is now a visible departure from the convention rather than an invisible one.

DEFAULT_PRINTER stays as the module attribute and remains the single source of truth, so printer.DEFAULT_PRINTER (used in docs/library.rst) keeps working. set_default_printer() gives the mutation a documented home instead of leaving it as a bare assignment in main().

I considered plumbing the printer through explicitly. BuildOptions already carries a printer attribute, which would cover the two build-time bars in json.py, but the diff-time bars in tree.py and levenshtein.py sit on TreeNode.diff() and inside EditDistance.tighten_bounds(). Threading a printer to those would change the TreeNode and Edit signatures across the package, which is out of proportion to the bug.

levenshtein.py reads the printer once into a local and uses it for both the quiet guard and the tqdm call, so the guard and the bar can no longer disagree.

Verification

The reproducer from the issue, on merged master (06e0417) and on this branch, with stderr redirected to a file:

command master this branch
graphtage big1.json big2.json 414 bytes 414 bytes
graphtage --no-status big1.json big2.json 101 bytes 0 bytes
graphtage --quiet big1.json big2.json 101 bytes 0 bytes

Default output is byte-for-byte unchanged.

Because tqdm is created with disable=False rather than disable=None, the bars are drawn whether or not stderr is a terminal, so a redirected run is not enough on its own to show that the default still works. I also ran the three cases with stdout and stderr each attached to a real pty sized 100x40:

command master stderr this branch stderr
default 402 bytes, bar present 402 bytes, bar present
--no-status 201 bytes, bar present 0 bytes
--quiet 201 bytes, bar present 0 bytes

stdout was identical (23302 bytes) in all six runs.

Tests

test/test_progress.py adds four tests. Three run the command line in a subprocess, which is what the bug requires: the stale binding is established while the module is imported, so an in-process test would reuse whichever printer an earlier test had installed. The fourth asserts that all three modules observe set_default_printer(), which fails if anyone reintroduces an import-time binding.

Against merged master with the source changes reverted, three of the four fail:

FAILED test/test_progress.py::TestProgress::test_no_status_suppresses_progress
FAILED test/test_progress.py::TestProgress::test_quiet_suppresses_progress
FAILED test/test_progress.py::TestProgress::test_replacement_printer_reaches_every_progress_bar
3 failed, 1 passed

The one that passes is test_progress_is_shown_by_default, which is correct: this change does not alter default behavior.

With the fix applied, all four pass. Full suite: 123 passed on Python 3.14 and on Python 3.8. ruff check reports the same 27 pre-existing findings on the touched files before and after, and none on the new test file. flake8 --select=E9,F63,F7,F82 is clean and cd docs && make html succeeds with the same five pre-existing warnings.

Interaction with #130

This branch is rebased on master after #130, #129, and #131 merged. #130's changes are orthogonal: it moved colorama.init() into enable_ansi_support() and changed NullWriter.isatty() to return False, neither of which touches how DEFAULT_PRINTER is looked up. I re-confirmed the reproducer against merged master before finishing the fix; #130 did not incidentally address it. The rebase produced one conflict, in json.py, where #131 restructured the same ListNode/UnorderedListNode branch that holds one of the tqdm calls; it is resolved to keep both changes.

🤖 Generated with Claude Code

ESultanik and others added 2 commits September 7, 2026 17:18
`main()` replaced `printer.DEFAULT_PRINTER` with a printer configured from
`--no-status` and `--quiet`, but `tree.py`, `json.py`, and `levenshtein.py`
bound the name at import time and never saw the replacement. Those modules kept
drawing progress bars from the original printer, whose `quiet` is `False`.

Add `get_default_printer()` and `set_default_printer()` to `printer.py`, and
have the three modules resolve the printer at call time so the result no longer
depends on import order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	graphtage/json.py
The bug lives in an import-time name binding, so an in-process test would not
reach it. Run the command line in a subprocess and assert that `--no-status`
and `--quiet` leave stderr empty while the default run still draws its bar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ESultanik
ESultanik merged commit f672b2b into master Sep 8, 2026
12 checks passed
@ESultanik
ESultanik deleted the 133-suppress-progress-bars branch September 8, 2026 13:43
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.

--no-status and --quiet do not suppress the progress bar

1 participant