testsAndMisc/docs/superpowers/evidence/wsg-grabber-downloader-split-2026-08-16.json
Krzysztof kuhy Rudnicki 324ec0c9b5 Split wsg_grabber/downloader.py under the 250-line cap
Worker did two separable jobs. Both become mixins, because the tests drive
their methods as bound attributes (worker._download_one(), worker._fetch_thread)
and free functions would have broken that:

  _scan.py    ScanMixin      -- threads.json, the archive, one changed thread
  _fetch.py   DownloadMixin  -- claim a file, fetch it, record the outcome

_worker_base.py holds WorkerBase(ABC) with the state both halves touch and the
three methods Worker supplies. Two earlier shapes were abandoned rather than
suppressed: NotImplementedError stubs are uncovered lines under the 100% gate,
and `if TYPE_CHECKING` stubs satisfy mypy but leave pylint reporting no-member.
The ABC satisfies both with no disable comment anywhere.

_downloaded needs a real class-level default: _fetch does `self._downloaded += 1`
and pylint only accepts an attribute as assignable when there is something to
assign to. Worker.__init__ still owns the instance value.

downloader.py 420 -> 238, _scan.py 134, _fetch.py 98, _worker_base.py 52.
277 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 16:01:11 +02:00

44 lines
3.5 KiB
JSON

{
"intent": "Bring python_pkg/wsg_grabber/downloader.py under the 250-line file cap by splitting the Worker class's board-walking and file-fetching halves into mixins, keeping every method reachable as a bound attribute of Worker.",
"scope": [
"python_pkg/wsg_grabber/downloader.py",
"python_pkg/wsg_grabber/_scan.py (new)",
"python_pkg/wsg_grabber/_fetch.py (new)",
"python_pkg/wsg_grabber/_worker_base.py (new)",
"Non-goal: no behaviour change, no test changes, no pylint config change"
],
"changes": [
"Moved the board-walking methods -- _scan_step, _refresh_thread_list, _archive_refs, _conditional_get, _fetch_thread, _write_off_deleted -- into ScanMixin in _scan.py.",
"Moved _download_one and _apply_result into DownloadMixin in _fetch.py.",
"Added _worker_base.py holding WorkerBase(ABC): the state both halves touch, plus abstract _get_json / _publish / _wait_for_slot which Worker supplies. Both mixins inherit from it, so neither redeclares the other's bookkeeping.",
"Worker is now `class Worker(ScanMixin, DownloadMixin)`; __init__ is unchanged and remains the only place instance state is created.",
"downloader.py 420 -> 238 lines; _scan.py 134; _fetch.py 98; _worker_base.py 52."
],
"verification": [
{
"command": "python3 -m pytest python_pkg/wsg_grabber/tests/ -q",
"result": "pass",
"evidence": "277 passed in 15.20s, unchanged from before the split. This is the load-bearing check: test_downloader_part2.py calls worker._download_one() and worker._fetch_thread(ThreadRef(7, 100)) as bound methods and sets worker._catalog_fresh directly, so the mixin composition is exercised rather than assumed."
},
{
"command": "pre-commit run --files python_pkg/wsg_grabber/downloader.py python_pkg/wsg_grabber/_scan.py python_pkg/wsg_grabber/_fetch.py python_pkg/wsg_grabber/_worker_base.py",
"result": "pass",
"evidence": "ruff, ruff format, mypy, pylint, bandit and the coverage-gated pytest all pass. The only remaining hook failures were 'no staged evidence artifact' and 'no workflow contract', which this file and the paired contract satisfy. No suppression was added."
},
{
"command": "bash ~/utils/scripts/check_file_length.sh --all | grep -c 'over by'",
"result": "pass",
"evidence": "All four files are under the cap: 238 / 134 / 98 / 52."
}
],
"risks": [
"Two intermediate designs failed the gate and were abandoned rather than suppressed. Bare `raise NotImplementedError` stubs on the mixins would have been uncovered lines under the 100% gate; `if TYPE_CHECKING` method stubs satisfied mypy but left pylint reporting no-member. The ABC with real abstract methods satisfies both.",
"_downloaded carries a class-level default of 0 rather than a bare annotation, because _fetch does `self._downloaded += 1` and pylint only accepts an attribute as assignable when there is something to assign to. Worker.__init__ still sets the instance value, so no state is shared between instances -- ints are immutable, so the class attribute is never mutated in place.",
"Mixin method resolution order now matters: Worker(ScanMixin, DownloadMixin) means ScanMixin wins on any name collision. There are none today; adding a same-named method to both would silently pick ScanMixin's."
],
"rollback": [
"git revert the commit; downloader.py returns to its 420-line form and the three new modules are deleted.",
"After rollback, run python3 -m pytest python_pkg/wsg_grabber/tests/ -q to confirm 277 tests still pass."
]
}