diff --git a/docs/superpowers/evidence/brother-printer-page-count-split-2026-08-16.json b/docs/superpowers/evidence/brother-printer-page-count-split-2026-08-16.json new file mode 100644 index 00000000..5a11d681 --- /dev/null +++ b/docs/superpowers/evidence/brother-printer-page-count-split-2026-08-16.json @@ -0,0 +1,44 @@ +{ + "intent": "Bring brother_printer/display.py under the 250-line cap by moving the page-count estimate and dropped-page warning into their own module, with no behaviour change.", + "scope": [ + "python_pkg/brother_printer/_page_count.py (new)", + "python_pkg/brother_printer/display.py", + "python_pkg/brother_printer/tests/test_display_part3.py -> tests/test_page_count.py", + "Non-goal: no assertion changed, no test deleted, skipped or weakened" + ], + "changes": [ + "Moved _display_page_delivery_warning, _display_page_count_estimate and _display_consumables_reference verbatim into python_pkg/brother_printer/_page_count.py, which imports the patched collaborators check_page_delivery and estimate_consumable_life directly.", + "display.py now imports the three functions from _page_count so display_usb_results still resolves them, and dropped the now-unused render_life_bar, TONER_RATED_PAGES and DRUM_RATED_PAGES imports; 342 -> 248 lines.", + "Renamed tests/test_display_part3.py to tests/test_page_count.py and repointed its MOD and import block to the new module (per docs/python-split-recipes.md, the module a test patches must be the one the function actually lives in)." + ], + "verification": [ + { + "command": "PYTHONPATH=. python3 -m pytest python_pkg/brother_printer/tests -q", + "result": "pass", + "evidence": "402 passed before the split and 402 passed after it - identical pass count, not merely a green run." + }, + { + "command": "PYTHONPATH=. python3 -c \"from python_pkg.brother_printer import display; display._display_page_count_estimate; display._display_consumables_reference; display._display_report_header; display._display_page_delivery_warning\"", + "result": "pass", + "evidence": "runtime probe OK - the names test_display.py patches on display are still attributes of display at import time, so ruff's TC001 rewrite trap did not fire." + }, + { + "command": "wc -l python_pkg/brother_printer/display.py", + "result": "pass", + "evidence": "248 lines, under the 250-line cap; _page_count.py is 114 lines." + }, + { + "command": "pre-commit run --files python_pkg/brother_printer/_page_count.py python_pkg/brother_printer/display.py python_pkg/brother_printer/tests/test_page_count.py", + "result": "pass", + "evidence": "ruff auto-sorted the new import block in test_page_count.py on the first run; clean on re-run." + } + ], + "risks": [ + "mock.patch pins names to a module: a test still patching estimate_consumable_life on display would now miss. Checked - test_page_count.py is the only module that patched it, and its MOD moved with the code.", + "display.py keeps _display_report_header, which both test_display.py and test_display_part2.py patch, so it was deliberately left in place." + ], + "rollback": [ + "git revert the commit; display.py returns to 342 lines and test_page_count.py back to test_display_part3.py.", + "After rollback, re-run the package suite and confirm it still reports 402 passed." + ] +} diff --git a/python_pkg/brother_printer/_page_count.py b/python_pkg/brother_printer/_page_count.py new file mode 100644 index 00000000..9b3956c2 --- /dev/null +++ b/python_pkg/brother_printer/_page_count.py @@ -0,0 +1,117 @@ +"""Page-count estimates and the dropped-page warning. + +Split out of :mod:`python_pkg.brother_printer.display` to keep it under the +250-line cap. ``estimate_consumable_life`` and ``check_page_delivery`` are +patched by ``tests/test_display_part3.py``, so they are imported here rather +than through ``display``. +""" + +from __future__ import annotations + +from python_pkg.brother_printer._supply import render_life_bar +from python_pkg.brother_printer.constants import ( + BOLD, + DIM, + DRUM_RATED_PAGES, + RED, + RESET, + TONER_RATED_PAGES, + YELLOW, + _out, +) +from python_pkg.brother_printer.consumables import ( + check_page_delivery, + estimate_consumable_life, +) + + +def _display_page_delivery_warning(printer_total: int, *, queue_idle: bool) -> None: + """Warn when CUPS claims more pages than the printer actually counted. + + Args: + printer_total: Lifetime count from the printer's own counter. + queue_idle: False while a job is queued or printing, when the two + counters legitimately disagree. + """ + check = check_page_delivery(printer_total, queue_idle=queue_idle) + if not check.suspected: + return + _out(f"{BOLD}── Dropped Pages ──{RESET}") + _out() + _out( + f" {RED}{BOLD}⚠ {check.dropped} pages did not print.{RESET}" + f" {RED}CUPS sent {check.cups_pages} pages since the last check;" + f" the printer's own counter advanced by" + f" {check.printer_pages}.{RESET}" + ) + _out() + _out( + f" {DIM}The printer discards pages whose 600 dpi raster does not fit" + f" its memory, stays READY, and reports no error - and CUPS still calls" + f" the job successful. Reprint at a lower resolution:{RESET}" + ) + _out(f" {DIM} lp -o Resolution=300dpi {RESET}") + _out() + + +def _display_page_count_estimate(printer_total: int = 0) -> None: + """Show estimated consumable life based on the printer's page count. + + Args: + printer_total: Lifetime count from the printer's own counter, or zero + when it could not be read and the CUPS page log has to stand in. + """ + estimate = estimate_consumable_life(printer_total) + if estimate.total_pages <= 0: + return + _out(f"{BOLD}── Page Count Estimate ──{RESET}") + _out() + _out( + f" {BOLD}Total pages printed:{RESET} {estimate.total_pages}" + f" (toner: {estimate.toner_pages} since replacement," + f" drum: {estimate.drum_pages} since replacement)" + ) + if estimate.approximate: + _out( + f" {YELLOW}Approximate: counted from the CUPS log, not the" + f" printer's own counter, so it misses pages CUPS never saw" + f" and reads high.{RESET}" + ) + _out() + render_life_bar( + "Toner:", + estimate.toner_pct_remaining, + exhausted=estimate.toner_exhausted, + low=estimate.toner_low, + exhausted_note=" ← REPLACE NOW", + low_note=" ← order soon", + ) + render_life_bar( + "Drum: ", + estimate.drum_pct_remaining, + low=estimate.drum_near_end, + low_note=" ← nearing end", + ) + _out( + f" {DIM}Based on pages since last replacement" + f" vs rated capacity (toner ~{TONER_RATED_PAGES}," + f" drum ~{DRUM_RATED_PAGES}).{RESET}" + ) + _out(f" {DIM}Reset after replacing: --reset-toner or --reset-drum{RESET}") + if estimate.toner_exhausted: + _out() + _out( + f" {RED}{BOLD}⚠ Toner is likely exhausted." + f" This is probably why the orange light is flashing.{RESET}" + ) + _out() + + +def _display_consumables_reference() -> None: + """Print compatible consumables reference.""" + _out(f"{BOLD}── Compatible Consumables ──{RESET}") + _out() + _out(f" {BOLD}Toner:{RESET} TN-1050 / TN-1030 (or compatible third-party)") + _out(f" {BOLD}Drum:{RESET} DR-1050 / DR-1030 (or compatible third-party)") + _out(f" {DIM} Toner rated ~1000 pages; Drum rated ~10000 pages.{RESET}") + _out() diff --git a/python_pkg/brother_printer/display.py b/python_pkg/brother_printer/display.py index 8bb3b7fc..d3f25d30 100644 --- a/python_pkg/brother_printer/display.py +++ b/python_pkg/brother_printer/display.py @@ -9,6 +9,11 @@ from typing import TYPE_CHECKING # Re-exported through __all__: tests/test_display.py imports these names from # this module. Only the un-patched supply *maths* moved to _supply -- everything # the tests reach with mock.patch has to stay resolvable here. +from python_pkg.brother_printer._page_count import ( + _display_consumables_reference, + _display_page_count_estimate, + _display_page_delivery_warning, +) from python_pkg.brother_printer._severity import ( _SEVERITY_COLORS, _SEVERITY_ICONS, @@ -21,25 +26,18 @@ from python_pkg.brother_printer._supply import ( _format_supply_bar, _parse_supply_value, _process_supply_item, - render_life_bar, ) from python_pkg.brother_printer.constants import ( BOLD, CYAN, DIM, - DRUM_RATED_PAGES, GREEN, RED, RESET, - TONER_RATED_PAGES, YELLOW, _out, get_status_info, ) -from python_pkg.brother_printer.consumables import ( - check_page_delivery, - estimate_consumable_life, -) from python_pkg.brother_printer.cups_queue import ( display_cups_queue_status, get_cups_queue_status, @@ -72,98 +70,6 @@ def _display_report_header() -> None: _out() -def _display_page_delivery_warning(printer_total: int, *, queue_idle: bool) -> None: - """Warn when CUPS claims more pages than the printer actually counted. - - Args: - printer_total: Lifetime count from the printer's own counter. - queue_idle: False while a job is queued or printing, when the two - counters legitimately disagree. - """ - check = check_page_delivery(printer_total, queue_idle=queue_idle) - if not check.suspected: - return - _out(f"{BOLD}── Dropped Pages ──{RESET}") - _out() - _out( - f" {RED}{BOLD}⚠ {check.dropped} pages did not print.{RESET}" - f" {RED}CUPS sent {check.cups_pages} pages since the last check;" - f" the printer's own counter advanced by" - f" {check.printer_pages}.{RESET}" - ) - _out() - _out( - f" {DIM}The printer discards pages whose 600 dpi raster does not fit" - f" its memory, stays READY, and reports no error - and CUPS still calls" - f" the job successful. Reprint at a lower resolution:{RESET}" - ) - _out(f" {DIM} lp -o Resolution=300dpi {RESET}") - _out() - - -def _display_page_count_estimate(printer_total: int = 0) -> None: - """Show estimated consumable life based on the printer's page count. - - Args: - printer_total: Lifetime count from the printer's own counter, or zero - when it could not be read and the CUPS page log has to stand in. - """ - estimate = estimate_consumable_life(printer_total) - if estimate.total_pages <= 0: - return - _out(f"{BOLD}── Page Count Estimate ──{RESET}") - _out() - _out( - f" {BOLD}Total pages printed:{RESET} {estimate.total_pages}" - f" (toner: {estimate.toner_pages} since replacement," - f" drum: {estimate.drum_pages} since replacement)" - ) - if estimate.approximate: - _out( - f" {YELLOW}Approximate: counted from the CUPS log, not the" - f" printer's own counter, so it misses pages CUPS never saw" - f" and reads high.{RESET}" - ) - _out() - render_life_bar( - "Toner:", - estimate.toner_pct_remaining, - exhausted=estimate.toner_exhausted, - low=estimate.toner_low, - exhausted_note=" ← REPLACE NOW", - low_note=" ← order soon", - ) - render_life_bar( - "Drum: ", - estimate.drum_pct_remaining, - low=estimate.drum_near_end, - low_note=" ← nearing end", - ) - _out( - f" {DIM}Based on pages since last replacement" - f" vs rated capacity (toner ~{TONER_RATED_PAGES}," - f" drum ~{DRUM_RATED_PAGES}).{RESET}" - ) - _out(f" {DIM}Reset after replacing: --reset-toner or --reset-drum{RESET}") - if estimate.toner_exhausted: - _out() - _out( - f" {RED}{BOLD}⚠ Toner is likely exhausted." - f" This is probably why the orange light is flashing.{RESET}" - ) - _out() - - -def _display_consumables_reference() -> None: - """Print compatible consumables reference.""" - _out(f"{BOLD}── Compatible Consumables ──{RESET}") - _out() - _out(f" {BOLD}Toner:{RESET} TN-1050 / TN-1030 (or compatible third-party)") - _out(f" {BOLD}Drum:{RESET} DR-1050 / DR-1030 (or compatible third-party)") - _out(f" {DIM} Toner rated ~1000 pages; Drum rated ~10000 pages.{RESET}") - _out() - - # ── USB display helpers ────────────────────────────────────────────── diff --git a/python_pkg/brother_printer/tests/test_display_part3.py b/python_pkg/brother_printer/tests/test_page_count.py similarity index 97% rename from python_pkg/brother_printer/tests/test_display_part3.py rename to python_pkg/brother_printer/tests/test_page_count.py index a46d5512..88c87328 100644 --- a/python_pkg/brother_printer/tests/test_display_part3.py +++ b/python_pkg/brother_printer/tests/test_page_count.py @@ -1,21 +1,21 @@ -"""Tests for brother_printer.display - page counts and dropped-page warning.""" +"""Tests for brother_printer._page_count - page counts and dropped pages.""" from __future__ import annotations from io import StringIO from unittest.mock import MagicMock, patch -from python_pkg.brother_printer.data_classes import ( - PageCountEstimate, - PageDeliveryCheck, -) -from python_pkg.brother_printer.display import ( +from python_pkg.brother_printer._page_count import ( _display_consumables_reference, _display_page_count_estimate, _display_page_delivery_warning, ) +from python_pkg.brother_printer.data_classes import ( + PageCountEstimate, + PageDeliveryCheck, +) -MOD = "python_pkg.brother_printer.display" +MOD = "python_pkg.brother_printer._page_count" class TestDisplayPageCountEstimate: