From ca0023f5b747bd56b984311fca019f0d53c37abf Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sun, 6 Sep 2026 20:17:12 -0400 Subject: [PATCH] The import blocks hold only imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `E402` read as seventeen deliberate late imports and was in fact two accidents. A module constant had settled into the middle of `packet_viewer`'s import block (`_BODY_CACHE_MAX`, now below it), and ten test modules put their `_FIXTURE` path or a small helper above the last import — the `tests.conftest` screen reader, which now joins the block it belonged to. `B008` is the opposite case: all six are Typer declaring a CLI parameter by calling `Argument`/`Option` in the default, which is the framework's API and cannot be written another way in this style. The objects it returns are inert sentinels no command mutates — exactly the thing `extend-immutable-calls` is for, so the rule now knows about them instead of being silenced per line. `.claude` leaves the lint's reach: those are helper scripts for the skills, not package code, and CI has no reason to gate on them. Co-Authored-By: Claude Opus 5 (1M context) --- meshterm/ui/packet_viewer.py | 6 +++--- pyproject.toml | 7 +++++++ tests/test_config_editor.py | 2 +- tests/test_dashboard.py | 2 +- tests/test_live_screens.py | 2 +- tests/test_livefeed.py | 2 +- tests/test_map.py | 2 +- tests/test_message_paths_screen.py | 2 +- tests/test_node_detail.py | 2 +- tests/test_packet_viewer.py | 2 +- tests/test_records_screen.py | 2 +- tests/test_walk.py | 2 +- 12 files changed, 20 insertions(+), 13 deletions(-) diff --git a/meshterm/ui/packet_viewer.py b/meshterm/ui/packet_viewer.py index f333222..9137959 100644 --- a/meshterm/ui/packet_viewer.py +++ b/meshterm/ui/packet_viewer.py @@ -53,9 +53,6 @@ from ..core.models import NODE_TYPE_LABELS, Observation, utcnow from ..services.trace_runner import NodeResolver from .map_render import _SELF, _UNKNOWN from .menus import SEP_COMPACT, SEP_ROOMY - -#: Composed packet cards the viewer memoizes (a few pages either side of the view). -_BODY_CACHE_MAX = 8 from .pathgraph import PathLayer, render_path_graph, revisited_hops from .pathline import PathLine, path_line from .theme import glyph, name_style, snr_style @@ -75,6 +72,9 @@ from .widgets import ( route_graph_style, ) +#: Composed packet cards the viewer memoizes (a few pages either side of the view). +_BODY_CACHE_MAX = 8 + #: Friendly gloss for a raw packet's parsed payload class (mirrors the meshcore #: library's own ``PAYLOAD_TYPENAMES``), shown on a ``packet`` entry's "class" row. _PAYLOAD_GLOSS = { diff --git a/pyproject.toml b/pyproject.toml index a73ba27..e29ecff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ packages = ["meshterm"] [tool.ruff] line-length = 100 target-version = "py310" +# Helper scripts for the Claude skills; they are not part of the package. +extend-exclude = [".claude"] [tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "D"] @@ -51,6 +53,11 @@ ignore = ["D203", "D213"] # pick one blank-line-before-class / multi-line-summa [tool.ruff.lint.pydocstyle] convention = "google" +[tool.ruff.lint.flake8-bugbear] +# Typer declares CLI parameters by calling these in the default; the objects they +# return are inert sentinels the command never mutates, which is what B008 guards. +extend-immutable-calls = ["typer.Argument", "typer.Option"] + [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] diff --git a/tests/test_config_editor.py b/tests/test_config_editor.py index 07aa0f3..04e62d8 100644 --- a/tests/test_config_editor.py +++ b/tests/test_config_editor.py @@ -41,11 +41,11 @@ from meshterm.ui.marks import MASK_MARK from meshterm.ui.theme import active_theme from meshterm.ui.tui.prompt import TypedConfirmDialog from meshterm.ui.tui.screen import CANCEL +from tests.conftest import plain as _plain # THE strip-and-join screen reader _ANSI = re.compile(r"\x1b\[[0-9;]*m") -from tests.conftest import plain as _plain # THE strip-and-join screen reader class _Fut: diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 8268c76..bf69d68 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -16,6 +16,7 @@ from meshterm.core.models import Observation, utcnow from meshterm.persistence.repository import Repository from meshterm.services.monitor_service import ACTIVITY_BUCKETS from meshterm.ui.dashboard_screen import DashboardScreen +from tests.conftest import plain as _plain # THE strip-and-join screen reader class _FakeSession: @@ -46,7 +47,6 @@ def _obs(node="a1b2", kind="advert", snr=5.0, rssi=-90.0, age_s=0, **extra) -> O ) -from tests.conftest import plain as _plain # THE strip-and-join screen reader def _stripped(lines: list[str]) -> list[str]: diff --git a/tests/test_live_screens.py b/tests/test_live_screens.py index 008b2f7..a9e0caf 100644 --- a/tests/test_live_screens.py +++ b/tests/test_live_screens.py @@ -34,6 +34,7 @@ from meshterm.ui.trace_screen import ( snr_bar, ) from meshterm.ui.tx_screen import TxSweepScreen +from tests.conftest import plain as _plain # THE strip-and-join screen reader # The slim meter's fill glyphs (the SNR bar draws through the shared braille meter). _BAR_FULL, _BAR_HALF = _METER_SLIM @@ -79,7 +80,6 @@ def _trace(*snrs: float, success: bool = True, target: str = "Alice") -> TraceRe ) -from tests.conftest import plain as _plain # THE strip-and-join screen reader # --- snr_bar ------------------------------------------------------------------- diff --git a/tests/test_livefeed.py b/tests/test_livefeed.py index 8ae30fa..68244d1 100644 --- a/tests/test_livefeed.py +++ b/tests/test_livefeed.py @@ -14,6 +14,7 @@ from rich.cells import cell_len from meshterm.core.events import MeshEvent from meshterm.core.models import Ack, Message, Observation, utcnow from meshterm.ui.livefeed_screen import _ICON_LANE, LiveFeedScreen +from tests.conftest import plain as _plain # THE strip-and-join screen reader class _FakeSession: @@ -71,7 +72,6 @@ def _obs(node="a1b2", kind="advert", snr=5.0, rssi=-90.0, age_s=0, **extra) -> O ) -from tests.conftest import plain as _plain # THE strip-and-join screen reader def _stripped(lines: list[str]) -> list[str]: diff --git a/tests/test_map.py b/tests/test_map.py index 32cddf7..ec614a6 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -31,12 +31,12 @@ from meshterm.tools.map import MapTool from meshterm.ui.map_screen import _PAN_DIRS, _PAN_STEP from meshterm.ui.map_screen import _TILE_RETRY_SECONDS as _TILE_RETRY from meshterm.ui.mapcanvas import MapCanvas, parse_hex +from tests.conftest import plain as _plain # THE strip-and-join screen reader if TYPE_CHECKING: from meshterm.ui.map_screen import MapScreen _FIXTURE = Path(__file__).parent / "fixtures" / "tile_14_4843_5861.mvt" -from tests.conftest import plain as _plain # THE strip-and-join screen reader # -- MVT decoding ------------------------------------------------------------- diff --git a/tests/test_message_paths_screen.py b/tests/test_message_paths_screen.py index 783746a..9552077 100644 --- a/tests/test_message_paths_screen.py +++ b/tests/test_message_paths_screen.py @@ -14,13 +14,13 @@ from meshterm.core.models import ChatMessage, utcnow from meshterm.services.message_paths import Arrival from meshterm.ui.message_paths_screen import MessagePathsScreen from meshterm.ui.pathline import CRACK_HEAD, CRACK_TAIL +from tests.conftest import plain as _plain # THE strip-and-join screen reader def _resolve(hop: str) -> str: return {"3d63": "Hilltop-Repeater", "a1b2": "Waymarker"}.get(hop, hop) -from tests.conftest import plain as _plain # THE strip-and-join screen reader def _screen(arrivals: list[Arrival], **kwargs) -> MessagePathsScreen: diff --git a/tests/test_node_detail.py b/tests/test_node_detail.py index 5383a9a..b7122f5 100644 --- a/tests/test_node_detail.py +++ b/tests/test_node_detail.py @@ -39,6 +39,7 @@ from meshterm.ui.pathgraph import DST_NODE, SRC_NODE from meshterm.ui.pathline import CRACK_HEAD, CRACK_TAIL, SELF_GLYPH, PathHop, PathLine from meshterm.ui.tui.screen import CANCEL, ListWindow from meshterm.ui.widgets import highlighted_hash, route_graph_style, tab_strip +from tests.conftest import plain as _plain # THE strip-and-join screen reader US = "aaaaaaaaaaaa" HUB = Contact(name="Hub", public_key="3d63c6429436" + "0" * 52, key_prefix="3d63c6429436", @@ -46,7 +47,6 @@ HUB = Contact(name="Hub", public_key="3d63c6429436" + "0" * 52, key_prefix="3d63 FAR = Contact(name="Far", public_key="f2c24f54551e" + "0" * 52, key_prefix="f2c24f54551e") -from tests.conftest import plain as _plain # THE strip-and-join screen reader class _FakeSession: diff --git a/tests/test_packet_viewer.py b/tests/test_packet_viewer.py index ee5f8f7..4d5ad12 100644 --- a/tests/test_packet_viewer.py +++ b/tests/test_packet_viewer.py @@ -15,13 +15,13 @@ from meshterm.core.channels import channel_hash, derive_secret from meshterm.core.models import utcnow from meshterm.platforms import set_platform from meshterm.ui.packet_viewer import PacketEntry, PacketViewer +from tests.conftest import plain as _plain # THE strip-and-join screen reader def _stripped(lines: list[str]) -> list[str]: return [re.sub(r"\x1b\[[0-9;]*m", "", line) for line in lines] -from tests.conftest import plain as _plain # THE strip-and-join screen reader def _grp_txt_entry(raw_extra: dict) -> PacketEntry: diff --git a/tests/test_records_screen.py b/tests/test_records_screen.py index ef89b0e..d232029 100644 --- a/tests/test_records_screen.py +++ b/tests/test_records_screen.py @@ -30,6 +30,7 @@ from meshterm.ui.tui.screen import CANCEL from meshterm.ui.tui.select import SelectScreen from meshterm.ui.tui.session import TuiSession from meshterm.ui.widgets import node_marker, self_marker +from tests.conftest import plain as _plain # THE strip-and-join screen reader HUB_ID, FAR_ID = "3d63c6429436", "f2c24f54551e" @@ -46,7 +47,6 @@ def _loop_shape() -> list[WalkVertex]: ] -from tests.conftest import plain as _plain # THE strip-and-join screen reader def _resolve(hop: str) -> str: diff --git a/tests/test_walk.py b/tests/test_walk.py index 68801ab..64d4c4f 100644 --- a/tests/test_walk.py +++ b/tests/test_walk.py @@ -20,6 +20,7 @@ from meshterm.ui.pathline import ( ) from meshterm.ui.tui.render import render_to_ansi from meshterm.ui.walk_screen import _HSCROLL_STEP, WalkScreen +from tests.conftest import plain as _plain # THE strip-and-join screen reader US = "aa" * 6 Hub = Contact(name="Hilltop-Repeater", public_key="3d" * 32, node_type=2) @@ -61,7 +62,6 @@ def _screen(topo: MeshTopology, cell_h: int = 24) -> WalkScreen: return screen -from tests.conftest import plain as _plain # THE strip-and-join screen reader # --- rendering ----------------------------------------------------------------------------