From 0c98e4f4f12caae64d3ae8d233d4390a53b43ba8 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:46:42 -0800 Subject: [PATCH] Fixing up the tests and redirecting the progress bar to stderr --- src/documents/management/commands/base.py | 9 ++++- .../commands/document_fuzzy_match.py | 40 +++++++++++-------- .../commands/document_thumbnails.py | 8 +++- src/documents/tests/test_management_fuzzy.py | 2 +- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/documents/management/commands/base.py b/src/documents/management/commands/base.py index dc9f7e98d..61e92f953 100644 --- a/src/documents/management/commands/base.py +++ b/src/documents/management/commands/base.py @@ -18,6 +18,7 @@ from typing import TypeVar from django import db from django.core.management import CommandError from django_rich.management import RichCommand +from rich.console import Console from rich.progress import BarColumn from rich.progress import MofNCompleteColumn from rich.progress import Progress @@ -142,6 +143,12 @@ class PaperlessCommand(RichCommand): """ Create a configured Progress instance. + Progress output is directed to stderr to match the convention that + progress bars are transient UI feedback, not command output. This + mirrors tqdm's default behavior and prevents progress bar rendering + from interfering with stdout-based assertions in tests or piped + command output. + Args: description: Text to display alongside the progress bar. @@ -155,7 +162,7 @@ class PaperlessCommand(RichCommand): MofNCompleteColumn(), TimeElapsedColumn(), TimeRemainingColumn(), - console=self.console, + console=Console(stderr=True), transient=False, ) diff --git a/src/documents/management/commands/document_fuzzy_match.py b/src/documents/management/commands/document_fuzzy_match.py index 98709b540..5d10d8008 100644 --- a/src/documents/management/commands/document_fuzzy_match.py +++ b/src/documents/management/commands/document_fuzzy_match.py @@ -62,9 +62,10 @@ class Command(PaperlessCommand): RATIO_MAX: Final[float] = 100.0 if options["delete"]: - self.console.print( - "[yellow]The command is configured to delete documents. " - "Use with caution.[/yellow]", + self.stdout.write( + self.style.WARNING( + "The command is configured to delete documents. Use with caution", + ), ) opt_ratio = options["ratio"] @@ -90,17 +91,21 @@ class Command(PaperlessCommand): work_pkgs.append(_WorkPackage(first_doc, second_doc)) results: list[_WorkResult] = [] - for result in self.process_parallel( - _process_and_match, - work_pkgs, - description="Matching...", - ): - if result.error: - self.console.print( - f"[red]Failed: {result.error}[/red]", - ) - elif result.result is not None: - results.append(result.result) + if self.process_count == 1: + for work in self.track(work_pkgs, description="Matching..."): + results.append(_process_and_match(work)) + else: # pragma: no cover + for proc_result in self.process_parallel( + _process_and_match, + work_pkgs, + description="Matching...", + ): + if proc_result.error: + self.console.print( + f"[red]Failed: {proc_result.error}[/red]", + ) + elif proc_result.result is not None: + results.append(proc_result.result) messages: list[str] = [] maybe_delete_ids: list[int] = [] @@ -120,8 +125,9 @@ class Command(PaperlessCommand): self.stdout.writelines(messages) if options["delete"]: - self.console.print( - f"[yellow]Deleting {len(maybe_delete_ids)} documents " - f"based on ratio matches[/yellow]", + self.stdout.write( + self.style.NOTICE( + f"Deleting {len(maybe_delete_ids)} documents based on ratio matches", + ), ) Document.objects.filter(pk__in=maybe_delete_ids).delete() diff --git a/src/documents/management/commands/document_thumbnails.py b/src/documents/management/commands/document_thumbnails.py index 03824a63e..275d8d5c4 100644 --- a/src/documents/management/commands/document_thumbnails.py +++ b/src/documents/management/commands/document_thumbnails.py @@ -5,13 +5,19 @@ from documents.management.commands.base import PaperlessCommand from documents.models import Document from documents.parsers import get_parser_class_for_mime_type +logger = logging.getLogger("paperless.management.thumbnails") + def _process_document(doc_id: int) -> None: document: Document = Document.objects.get(id=doc_id) parser_class = get_parser_class_for_mime_type(document.mime_type) if parser_class is None: - print(f"{document} No parser for mime type {document.mime_type}") # noqa: T201 + logger.warning( + "%s: No parser for mime type %s", + document, + document.mime_type, + ) return parser = parser_class(logging_group=None) diff --git a/src/documents/tests/test_management_fuzzy.py b/src/documents/tests/test_management_fuzzy.py index e097a015f..b3d03ecea 100644 --- a/src/documents/tests/test_management_fuzzy.py +++ b/src/documents/tests/test_management_fuzzy.py @@ -140,7 +140,7 @@ class TestFuzzyMatchCommand(TestCase): mime_type="application/pdf", filename="final_test.pdf", ) - stdout, _ = self.call_command() + stdout, _ = self.call_command("--no-progress-bar") lines = [x.strip() for x in stdout.splitlines() if x.strip()] self.assertEqual(len(lines), 3) for line in lines: