From 65e7e88232f6074344c4372b584e69c80f3ec4ea Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sun, 16 Aug 2026 16:18:58 +0200 Subject: [PATCH] Record the TC001 re-export trap and the test-patch pinning rule Both cost a full revert cycle in the wsg_grabber work: - ruff TC001 rewrites a re-exported annotation-only class into the TYPE_CHECKING block. __all__ does not stop it. Lints clean, AttributeError at import. Always finish a re-export split by resolving the name in a real interpreter. - Whatever the tests patch (patch.object(ui, "tk", ...)) or call directly (mpv._note_errors) is pinned to its module and cannot move. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY --- refactor_claude_todo_resume.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/refactor_claude_todo_resume.md b/refactor_claude_todo_resume.md index 5f443a66..1059fdc9 100644 --- a/refactor_claude_todo_resume.md +++ b/refactor_claude_todo_resume.md @@ -93,10 +93,31 @@ need no shim at all — check with grep first, as in `catalog.py`. `__all__` only needs the **re-exported** names, not every public name in the module — names defined locally stay reachable either way. Verified against the -full gate on `store.py`. Worth ~5–8 lines on every file with this shape, which -matters: three splits so far landed 2–6 lines over the cap and needed a second -seam. **Budget the new import block and `__all__` before deciding a two-way -split is enough.** +full gate on `store.py`. + +**`__all__` does not save you from ruff's `TC001`.** It suppresses `F401`, but +a re-exported class used only in annotations gets rewritten into the +`TYPE_CHECKING` block, which lints perfectly and raises `AttributeError` at +import time. This nearly shipped a broken `ui.Callbacks` that `cli.py` +constructs. A class that must be reachable at runtime from module X stays +_defined_ in module X. **Always finish a re-export split with** + +```bash +PYTHONPATH=. python3 -c "from python_pkg.pkg import mod; mod.TheName" +``` + +Two other things the gate will not catch for you: + +- **Check what the tests patch before choosing a seam.** `test_ui.py` does + `patch.object(ui, "tk", fake)`, so every line touching `tkinter` had to stay + in `ui.py`; moving widget construction out errored 22 tests. Grep the test + file for `patch.object(` first and treat those names as pinned. +- **Tests that call private methods pin them too** — `test_player.py` calls + `mpv._note_errors(...)`, so that method stayed as a one-line delegate to the + moved implementation. Worth ~5–8 lines on every file with this shape, which + matters: three splits so far landed 2–6 lines over the cap and needed a second + seam. **Budget the new import block and `__all__` before deciding a two-way + split is enough.** Splitting a **class**: if tests call private methods as bound attributes (`worker._fetch_thread(...)` — grep the tests first), module-level functions