Every zip says whether its sides are the same length
`B905` over 56 sites, and the answer differs by idiom rather than by file. 21 were the sliding window `zip(xs, xs[1:])` — ragged by exactly one, on purpose, so `strict=` is the wrong question entirely. Those become `itertools.pairwise`, which is what they always meant and needs no flag: the walk's SNR stops, the heat ladder, the route chains in `topology`/`records`, every polyline in `mapcanvas`, and the graph's edge sets in `pathgraph`. The other 35 pair sequences that must line up, so they now say `strict=True` and a mismatch raises instead of silently truncating the longer side. Running them under that assertion is what sorted the two groups: the first pass put `strict=True` everywhere and 208 tests failed, all of them a sliding window. One goes the other way. `mvt._decode_feature` reads tags off a network tile, where an odd-length list means the tile is malformed — and this decoder answers that with a shrug everywhere else (`if not geom_ints: return None`, bounds- checked key and value indices). It keeps `strict=False`, now with a comment saying why, so a bad basemap tile still can't crash the map. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
71b16ec7b7
commit
61b6387269
31 files changed
+80
-67
No files matched your search
@@ -1,4 +1,4 @@
|
||||
"""Diagnose one emoji for the MeshTerm terminal-aligned width machinery.
|
||||
r"""Diagnose one emoji for the MeshTerm terminal-aligned width machinery.
|
||||
|
||||
Given an emoji on the command line, report its codepoints and how the three width
|
||||
authorities measure it (wcwidth, Rich, prompt_toolkit), then classify it and print the
|
||||
|
||||
@@ -131,9 +131,9 @@ def measure(name, cols, rows, reps):
|
||||
|
||||
# How different are the two frames, in rows?
|
||||
rows_a, rows_b = f_a.split("\n"), f_b.split("\n")
|
||||
changed = sum(1 for x, y in zip(rows_a, rows_b) if x != y)
|
||||
changed = sum(1 for x, y in zip(rows_a, rows_b, strict=True) if x != y)
|
||||
rows_far = f_far.split("\n")
|
||||
changed_far = sum(1 for x, y in zip(rows_a, rows_far) if x != y)
|
||||
changed_far = sum(1 for x, y in zip(rows_a, rows_far, strict=True) if x != y)
|
||||
return {
|
||||
"compose": compose_ms,
|
||||
"parse": parse_ms,
|
||||
|
||||
@@ -576,7 +576,7 @@ def rank_contacts(
|
||||
reasons=_reasons(sig, terms, weights),
|
||||
)
|
||||
for contact, sig, score, terms in zip(
|
||||
contacts, gathered, scores, per_contact_terms
|
||||
contacts, gathered, scores, per_contact_terms, strict=True
|
||||
)
|
||||
]
|
||||
ranked.sort(key=lambda scored: (-scored.score, scored.contact.name.casefold()))
|
||||
|
||||
@@ -234,7 +234,9 @@ def _decode_feature(buf: bytes, keys: list[str], values: list[Any]) -> Feature |
|
||||
if not geom_ints:
|
||||
return None
|
||||
tags: dict[str, Any] = {}
|
||||
for k, v in zip(tag_ints[0::2], tag_ints[1::2]):
|
||||
# A malformed tile may carry an odd tag list; drop the dangling key rather than
|
||||
# raise — the rest of this decoder is deliberately lenient about bad tiles.
|
||||
for k, v in zip(tag_ints[0::2], tag_ints[1::2], strict=False):
|
||||
if 0 <= k < len(keys) and 0 <= v < len(values):
|
||||
tags[keys[k]] = values[v]
|
||||
return Feature(geom_type=geom_type, rings=_decode_geometry(geom_ints), tags=tags)
|
||||
|
||||
@@ -34,6 +34,7 @@ from __future__ import annotations
|
||||
import math
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass
|
||||
from itertools import pairwise
|
||||
|
||||
from ..core.geo import haversine_km
|
||||
from ..core.models import Hop, TraceResult
|
||||
@@ -69,7 +70,7 @@ def first_repeated_edge(nodes: Sequence[str]) -> tuple[str, str] | None:
|
||||
"""
|
||||
walked = [n.strip().lower() for n in nodes if n and n.strip()]
|
||||
seen: set[tuple[str, str]] = set()
|
||||
for pair in zip(walked, walked[1:]):
|
||||
for pair in pairwise(walked):
|
||||
if pair in seen:
|
||||
return pair
|
||||
seen.add(pair)
|
||||
@@ -306,7 +307,7 @@ def compute_walk_stats(
|
||||
complete = True
|
||||
leg_km: float | None = None
|
||||
leg_link: tuple[str | None, str | None] | None = None
|
||||
for i, (a, b) in enumerate(zip(points, points[1:])):
|
||||
for i, (a, b) in enumerate(pairwise(points)):
|
||||
if a is None or b is None:
|
||||
complete = False
|
||||
continue
|
||||
|
||||
@@ -35,7 +35,7 @@ import math
|
||||
import statistics
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
from itertools import chain, pairwise
|
||||
|
||||
from ..core.models import Contact, utcnow
|
||||
from ..persistence.repository import NeighbourLink, PacketPath, TracedPath
|
||||
@@ -823,7 +823,7 @@ class MeshTopology:
|
||||
weakest_strength: float | None = None
|
||||
weakest_snr: float | None = None
|
||||
samples = 0
|
||||
for a, b in zip(chain, chain[1:]):
|
||||
for a, b in pairwise(chain):
|
||||
link = self.link(a, b)
|
||||
if link is None:
|
||||
return 0.0, None, samples
|
||||
@@ -912,7 +912,7 @@ class MeshTopology:
|
||||
|
||||
def path_cost(path: list[str]) -> float:
|
||||
total = 0.0
|
||||
for a, b in zip(path, path[1:]):
|
||||
for a, b in pairwise(path):
|
||||
total += min((c for v, c in neighbors.get(a, ()) if v == b), default=math.inf)
|
||||
return total
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ def axis_caption(
|
||||
taken: list[tuple[int, int]] = [] # placed [start, end) spans, in order
|
||||
ticks: list[int] = [] # the chart column each label points at
|
||||
ok = True
|
||||
for frac, label in zip(fractions, labels):
|
||||
for frac, label in zip(fractions, labels, strict=True):
|
||||
if frac == 0.0:
|
||||
start = 0
|
||||
ref = 0
|
||||
@@ -626,7 +626,7 @@ def axis_chart(
|
||||
marks = y_axis_labels(peak, len(chart_rows), lo=floor)
|
||||
label_w = label_w or max([1, *(len(mark) for mark in marks)])
|
||||
out: list[Text] = []
|
||||
for mark, row in zip(marks, chart_rows):
|
||||
for mark, row in zip(marks, chart_rows, strict=True):
|
||||
line = Text(f"{mark:>{label_w}} " + ("┤" if mark else "│"), style=style)
|
||||
line.append_text(row)
|
||||
line.append("├" if mark else "│", style=style)
|
||||
|
||||
@@ -141,7 +141,7 @@ class MapScreen(Screen):
|
||||
|
||||
@property
|
||||
def fkey_lane(self):
|
||||
"""The PicoCalc lane: three ways to frame the view, then the zoom rocker.
|
||||
r"""The PicoCalc lane: three ways to frame the view, then the zoom rocker.
|
||||
|
||||
The map is the one screen that repurposes the nav actions wholesale: PgUp/PgDn
|
||||
zoom, Home reframes, and ``end`` is bound to nothing at all — so the shared lane's
|
||||
|
||||
@@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
from dataclasses import dataclass
|
||||
from itertools import pairwise
|
||||
|
||||
from ..platforms import Platform, on_platform
|
||||
from .marks import RGB, parse_hex # noqa: F401 - canonical home; re-exported for importers
|
||||
@@ -177,7 +178,7 @@ class MapCanvas:
|
||||
self, points: list[tuple[float, float]], color: RGB, priority: int
|
||||
) -> None:
|
||||
"""Rasterize a polyline through ``points`` (dot coordinates) as braille dots."""
|
||||
for (x0, y0), (x1, y1) in zip(points, points[1:]):
|
||||
for (x0, y0), (x1, y1) in pairwise(points):
|
||||
self._segment(x0, y0, x1, y1, color, priority)
|
||||
|
||||
def _segment(
|
||||
@@ -232,7 +233,7 @@ class MapCanvas:
|
||||
edges: list[tuple[float, float, float, float]] = []
|
||||
ys: list[float] = []
|
||||
for ring in rings:
|
||||
for (x0, y0), (x1, y1) in zip(ring, ring[1:]):
|
||||
for (x0, y0), (x1, y1) in pairwise(ring):
|
||||
if y0 != y1:
|
||||
edges.append((x0, y0, x1, y1))
|
||||
ys.extend((y0, y1))
|
||||
|
||||
+1
-1
@@ -522,7 +522,7 @@ async def _menu_loop(ctx: AppContext, session: TuiSession) -> None:
|
||||
name_w = max((label.cell_len for label in labels), default=0)
|
||||
items: list = []
|
||||
current_category: str | None = None
|
||||
for tool, row in zip(tools, labels):
|
||||
for tool, row in zip(tools, labels, strict=True):
|
||||
if tool.category != current_category:
|
||||
current_category = tool.category
|
||||
items.append(section_heading(current_category))
|
||||
|
||||
@@ -333,7 +333,7 @@ def column_header(lanes: Sequence[Lane], width: int, *, indent: int = 2) -> str:
|
||||
|
||||
def line() -> str:
|
||||
out = " " * indent
|
||||
for lane, form in zip(lanes, picked):
|
||||
for lane, form in zip(lanes, picked, strict=True):
|
||||
label = lane.forms[form]
|
||||
out += label + " " * max(0, lane.width - cell_len(label))
|
||||
return out
|
||||
|
||||
@@ -78,6 +78,7 @@ import re
|
||||
import time
|
||||
from dataclasses import dataclass, field, replace
|
||||
from datetime import datetime
|
||||
from itertools import pairwise
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rich.cells import cell_len
|
||||
@@ -2013,7 +2014,7 @@ def _route_is_fresh(topo, hops: tuple[str, ...], target: str, now: datetime) ->
|
||||
is treated as fresh — we have no age to hold against it.
|
||||
"""
|
||||
chain = [topo.self_id, *hops, target]
|
||||
for a, b in zip(chain, chain[1:]):
|
||||
for a, b in pairwise(chain):
|
||||
link = topo.link(a, b)
|
||||
if link is None:
|
||||
return False
|
||||
|
||||
@@ -104,7 +104,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass
|
||||
from itertools import permutations, product
|
||||
from itertools import pairwise, permutations, product
|
||||
from math import ceil
|
||||
|
||||
from .mapcanvas import MapCanvas
|
||||
@@ -375,7 +375,7 @@ def revisited_hops(hops: Sequence[str]) -> tuple[str, ...]:
|
||||
|
||||
|
||||
def _split_revisits(layers: Sequence[PathLayer]) -> list[PathLayer]:
|
||||
"""Give every revisit *within* a path its own node id, so no walk folds into a cycle.
|
||||
r"""Give every revisit *within* a path its own node id, so no walk folds into a cycle.
|
||||
|
||||
The k-th occurrence of a hop in one layer becomes ``hop\\x00#k`` (the first keeps the bare
|
||||
id), which leaves the flow acyclic and lets the balanced rank spread the walk evenly again.
|
||||
@@ -555,7 +555,7 @@ def render_path_graph(
|
||||
if node not in seen:
|
||||
seen.add(node)
|
||||
ordered_nodes.append(node)
|
||||
edges.update(zip(seq, seq[1:]))
|
||||
edges.update(pairwise(seq))
|
||||
|
||||
xfrac = _balanced_x(ordered_nodes, edges)
|
||||
|
||||
@@ -667,9 +667,9 @@ def render_path_graph(
|
||||
# dropping out where it overlaps another. A two-way pair (``bidir``, above) draws as the
|
||||
# one honest vertical rather than a lane change.
|
||||
edge_style: dict[frozenset[str], tuple[int, RGB]] = {}
|
||||
for layer, seq in sorted(zip(drawn, seqs), key=lambda ls: _draw_rank(ls[0])):
|
||||
for layer, seq in sorted(zip(drawn, seqs, strict=True), key=lambda ls: _draw_rank(ls[0])):
|
||||
rank = _draw_rank(layer)
|
||||
for u, v in zip(seq, seq[1:]):
|
||||
for u, v in pairwise(seq):
|
||||
key = frozenset((u, v))
|
||||
prev = edge_style.get(key)
|
||||
if prev is None or rank > prev[0]:
|
||||
@@ -731,7 +731,7 @@ def _route(
|
||||
(xu, yu), (xv, yv) = (xv, yv), (xu, yu)
|
||||
anchors: list[tuple[float, float]] = [(xu, yu), *sorted(vias), (xv, yv)]
|
||||
pts: list[tuple[float, float]] = [anchors[0]]
|
||||
for (xa, ya), (xb, yb) in zip(anchors, anchors[1:]):
|
||||
for (xa, ya), (xb, yb) in pairwise(anchors):
|
||||
if ya == yb:
|
||||
pts.append((xb, yb))
|
||||
continue
|
||||
@@ -845,7 +845,7 @@ def bidir_clusters(sequences: Sequence[tuple[str, ...]]) -> list[tuple[str, ...]
|
||||
Returns each cluster's member ids in first-appearance order (endpoints excluded); a lone
|
||||
node or the tidy two-node pair is not a cluster and is not returned.
|
||||
"""
|
||||
edges = {pair for seq in sequences for pair in zip(seq, seq[1:])}
|
||||
edges = {pair for seq in sequences for pair in pairwise(seq)}
|
||||
ordered = list(dict.fromkeys(node for seq in sequences for node in seq))
|
||||
rep = _merge_bidir_pairs(ordered, edges)
|
||||
groups: dict[str, list[str]] = {}
|
||||
@@ -1047,7 +1047,7 @@ def _bypass_vias(
|
||||
level: list[tuple[frozenset[str], float, int, int]] = []
|
||||
seen: set[frozenset[str]] = set()
|
||||
for seq in seqs:
|
||||
for u, v in zip(seq, seq[1:]):
|
||||
for u, v in pairwise(seq):
|
||||
key = frozenset((u, v))
|
||||
if key in seen or key in bidir:
|
||||
continue
|
||||
@@ -1328,7 +1328,7 @@ def _balance_sides(
|
||||
best_assign: dict[int, int] | None = None
|
||||
best_key: tuple[int, int, int] | None = None
|
||||
for combo in product((-1, 1), repeat=len(routes)):
|
||||
assign = dict(zip(routes, combo))
|
||||
assign = dict(zip(routes, combo, strict=True))
|
||||
if any(assign[d] != assign[s] for d, s in tie.items()): # a nest split off its sibling
|
||||
continue
|
||||
deep_above, deep_below = flanks(assign)
|
||||
|
||||
@@ -211,7 +211,7 @@ async def _rank(ctx: AppContext, contacts: list[Contact]) -> list[ScoredContact]
|
||||
|
||||
watch = getattr(ctx, "watch_store", None)
|
||||
admin = getattr(ctx, "admin_store", None)
|
||||
for contact, node in zip(contacts, nodes):
|
||||
for contact, node in zip(contacts, nodes, strict=True):
|
||||
found = signals.get(node)
|
||||
if found is None:
|
||||
continue
|
||||
|
||||
@@ -446,10 +446,10 @@ class RecordDialog(Screen):
|
||||
canvas.draw_line(closed, _AREA_TONE, priority=0)
|
||||
# Hops first, then our own node last, so the star always wins its cell — a hop that
|
||||
# projects onto the same cell can never hide us.
|
||||
for v, (dx, dy) in zip(verts, ring):
|
||||
for v, (dx, dy) in zip(verts, ring, strict=True):
|
||||
if not v.is_self:
|
||||
canvas.marker(int(round(dx)), int(round(dy)), v.glyph, v.color)
|
||||
for v, (dx, dy) in zip(verts, ring):
|
||||
for v, (dx, dy) in zip(verts, ring, strict=True):
|
||||
if v.is_self:
|
||||
canvas.marker(int(round(dx)), int(round(dy)), v.glyph, v.color)
|
||||
return canvas.to_ansi_lines()
|
||||
|
||||
@@ -668,7 +668,7 @@ def _ticks_fit(picks: list[int], labels: list[str], centers: list[int], chars: i
|
||||
label would drop ticks a variable-width axis has ample room for.
|
||||
"""
|
||||
last_end = -_TICK_GAP
|
||||
for i, label in zip(picks, labels):
|
||||
for i, label in zip(picks, labels, strict=True):
|
||||
cell = max(0, min(chars - 1, centers[i]))
|
||||
start = max(0, min(chars - len(label), cell - len(label) // 2))
|
||||
if start < last_end + _TICK_GAP:
|
||||
@@ -707,9 +707,9 @@ def _fit_ticks(
|
||||
continue # rounding fused two picks — this many will not fit cleanly
|
||||
labels = label_of(picks)
|
||||
if _ticks_fit(picks, labels, centers, chars):
|
||||
return list(zip((centers[i] for i in picks), labels))
|
||||
return list(zip((centers[i] for i in picks), labels, strict=True))
|
||||
picks = [0] if n == 1 else [0, n - 1]
|
||||
return list(zip((centers[i] for i in picks), label_of(picks)))
|
||||
return list(zip((centers[i] for i in picks), label_of(picks), strict=True))
|
||||
|
||||
|
||||
def _day_ticks(shown: list, chars: int) -> list[tuple[int, str]]:
|
||||
@@ -912,7 +912,7 @@ def _day_columns(values: list[int], chars: int) -> list:
|
||||
:data:`~meshterm.ui.braillechart.GAP` marks a day-boundary notch).
|
||||
"""
|
||||
out: list = [GAP] * (chars * 2)
|
||||
for value, (start, width) in zip(values, _day_spans(len(values), chars)):
|
||||
for value, (start, width) in zip(values, _day_spans(len(values), chars), strict=True):
|
||||
out[start : start + width] = [value] * width
|
||||
return out
|
||||
|
||||
|
||||
@@ -1177,7 +1177,7 @@ class TraceScreen(Screen):
|
||||
rows.append(spin)
|
||||
elif self._status:
|
||||
rows.append(Text(self._status, style="err"))
|
||||
for number, trace in zip(range(len(self._traces), 0, -1), reversed(self._traces)):
|
||||
for number, trace in zip(range(len(self._traces), 0, -1), reversed(self._traces), strict=True):
|
||||
rows.append(self._trace_row(number, trace))
|
||||
return Group(*rows)
|
||||
|
||||
|
||||
@@ -372,7 +372,7 @@ def _banner_lines(banner: Sequence[str], cols: int) -> list[str]:
|
||||
return []
|
||||
widths = [cell_len(Text.from_ansi(row).plain) for row in banner]
|
||||
width = max(widths)
|
||||
padded = [fold_text(row) + " " * (width - w) for row, w in zip(banner, widths)]
|
||||
padded = [fold_text(row) + " " * (width - w) for row, w in zip(banner, widths, strict=True)]
|
||||
return _center(padded, cols)
|
||||
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ def _changed_rows(before: str, after: str) -> list[int] | None:
|
||||
old, new = before.split("\n"), after.split("\n")
|
||||
if len(old) != len(new):
|
||||
return None
|
||||
return [i for i, (a, b) in enumerate(zip(old, new)) if a != b]
|
||||
return [i for i, (a, b) in enumerate(zip(old, new, strict=True)) if a != b]
|
||||
|
||||
|
||||
def _has_wide_glyph(text: str) -> bool:
|
||||
@@ -680,7 +680,7 @@ class TuiSession:
|
||||
self._scrub_columns(cols - count, cols)
|
||||
|
||||
def _scrub_rows(self, rows: Sequence[int]) -> bool:
|
||||
"""Force prompt_toolkit to rewrite these whole terminal rows on the next diff.
|
||||
r"""Force prompt_toolkit to rewrite these whole terminal rows on the next diff.
|
||||
|
||||
The row-wise twin of :meth:`_scrub_columns`, and the cheap form of the wide-glyph
|
||||
repaint (:meth:`_emit`): every column of each listed row is sentinelled, so pt finds
|
||||
@@ -1539,7 +1539,7 @@ class TuiSession:
|
||||
return bool(self._float_layers())
|
||||
|
||||
def _emit(self, text: str, layer: str = "base") -> ANSI:
|
||||
"""Wrap a composed frame as prompt_toolkit :class:`ANSI`, repainting whole *rows*
|
||||
r"""Wrap a composed frame as prompt_toolkit :class:`ANSI`, repainting whole *rows*
|
||||
when it holds a glyph the terminal may draw narrower than pt reserves for it.
|
||||
|
||||
prompt_toolkit paints differentially: it rewrites only the cells that changed since
|
||||
|
||||
@@ -57,6 +57,7 @@ from __future__ import annotations
|
||||
import math
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from itertools import pairwise
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rich.cells import cell_len
|
||||
@@ -201,10 +202,10 @@ def _snr_rgb(snr: float | None) -> RGB:
|
||||
if snr >= _SNR_STOPS[-1][0]:
|
||||
return _SNR_STOPS[-1][1]
|
||||
(x0, c0), (x1, c1) = next(
|
||||
(lo, hi) for lo, hi in zip(_SNR_STOPS, _SNR_STOPS[1:]) if lo[0] <= snr <= hi[0]
|
||||
(lo, hi) for lo, hi in pairwise(_SNR_STOPS) if lo[0] <= snr <= hi[0]
|
||||
)
|
||||
f = (snr - x0) / (x1 - x0)
|
||||
return tuple(round(a + (b - a) * f) for a, b in zip(c0, c1)) # type: ignore[return-value]
|
||||
return tuple(round(a + (b - a) * f) for a, b in zip(c0, c1, strict=True)) # type: ignore[return-value]
|
||||
|
||||
|
||||
def _scaled(rgb: RGB, factor: float) -> RGB:
|
||||
@@ -1039,7 +1040,7 @@ class WalkScreen(Screen):
|
||||
# marker, in cells, doubled into dot space.
|
||||
rx = max(floor, 2 * (width - 2 - _FAN_BASE_LABEL_W)) - ax
|
||||
phi = _FAN_HALF_ANGLE * min(1.0, (len(keys) - 1) / 5.0)
|
||||
for i, (node, row, label) in enumerate(zip(keys, rows, labels)):
|
||||
for i, (node, row, label) in enumerate(zip(keys, rows, labels, strict=True)):
|
||||
angle = 0.0 if len(keys) == 1 else -phi + (2 * phi) * i / (len(keys) - 1)
|
||||
x = ax + rx * math.cos(angle * _FAN_X_FLATTEN)
|
||||
room = 2 * (width - 2 - min(cell_len(label), _FAN_LABEL_W))
|
||||
|
||||
@@ -6,6 +6,7 @@ import math
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from itertools import pairwise
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rich import box
|
||||
@@ -935,10 +936,10 @@ def _recency_gradient(secs: float | None) -> str:
|
||||
r, g, b = _HEAT_STOPS[-1][1]
|
||||
else:
|
||||
(x0, c0), (x1, c1) = next(
|
||||
(lo, hi) for lo, hi in zip(_HEAT_STOPS, _HEAT_STOPS[1:]) if lo[0] <= x <= hi[0]
|
||||
(lo, hi) for lo, hi in pairwise(_HEAT_STOPS) if lo[0] <= x <= hi[0]
|
||||
)
|
||||
f = (x - x0) / (x1 - x0)
|
||||
r, g, b = (round(a + (bb - a) * f) for a, bb in zip(c0, c1))
|
||||
r, g, b = (round(a + (bb - a) * f) for a, bb in zip(c0, c1, strict=True))
|
||||
return f"#{r:02x}{g:02x}{b:02x}"
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -495,7 +495,8 @@ def test_title_preview_column_aligns_regardless_of_label_length(repo: Repository
|
||||
def test_title_leads_with_openness_glyph(repo: Repository) -> None:
|
||||
"""Channel rows lead with an openness glyph: # name-derived, 🌐 fixed-key public, 🔒 private."""
|
||||
ctx = _RowCtx(repo)
|
||||
head = lambda conv: _title(ctx, conv, {}, _NO_KEYS).plain.split(" ", 1)[0]
|
||||
def head(conv):
|
||||
return _title(ctx, conv, {}, _NO_KEYS).plain.split(" ", 1)[0]
|
||||
named = Conversation(
|
||||
label="#general", is_channel=True, channel_id="c0", secret=derive_secret("#general")
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ def _rank(*specs) -> list: # noqa: ANN001
|
||||
"""Rank ``(name, signal-overrides)`` pairs, returning the scored contacts strongest first."""
|
||||
contacts = [_contact(name) for name, _ in specs]
|
||||
signals = {}
|
||||
for contact, (_, overrides) in zip(contacts, specs):
|
||||
for contact, (_, overrides) in zip(contacts, specs, strict=True):
|
||||
node = contact.public_key[:12]
|
||||
signals[node] = ContactSignals(node=node, **{**_BASE, **overrides})
|
||||
return rank_contacts(contacts, signals)
|
||||
|
||||
+3
-3
@@ -615,7 +615,7 @@ def test_render_map_brightens_piled_markers() -> None:
|
||||
crowd = [MapMarker(f"n{i}", 45.50, -73.57) for i in range(8)]
|
||||
piled = _glyph_color(render_map(vp, {}, crowd), "●")
|
||||
# Washed toward white: every channel is brighter than the base cyan.
|
||||
assert all(p > b for p, b in zip(piled, base))
|
||||
assert all(p > b for p, b in zip(piled, base, strict=True))
|
||||
assert piled != base
|
||||
|
||||
|
||||
@@ -684,7 +684,7 @@ def test_the_ghost_ground_lands_where_the_pan_put_it() -> None:
|
||||
assert len(before) == len(after)
|
||||
matched = sum(
|
||||
1
|
||||
for old, new in zip(before, after)
|
||||
for old, new in zip(before, after, strict=True)
|
||||
if old[shift:].rstrip() and new.rstrip() == old[shift:].rstrip()
|
||||
)
|
||||
assert matched > len(before) // 2, "the ground did not slide with the view"
|
||||
@@ -906,7 +906,7 @@ def test_decoded_layers_survive_a_round_trip() -> None:
|
||||
|
||||
assert restored is not None
|
||||
assert [(l.name, l.extent) for l in restored] == [(l.name, l.extent) for l in original]
|
||||
for before, after in zip(original, restored):
|
||||
for before, after in zip(original, restored, strict=True):
|
||||
assert after.features == before.features
|
||||
|
||||
|
||||
|
||||
+4
-4
@@ -56,7 +56,7 @@ def test_menu_rows_align_descriptions_in_display_cells() -> None:
|
||||
from rich.cells import cell_len
|
||||
|
||||
starts = {cell_len(r.title.plain[: r.title.plain.index(d)]) for r, d in
|
||||
zip(rows, ["Restart it", "Second"])}
|
||||
zip(rows, ["Restart it", "Second"], strict=True)}
|
||||
assert len(starts) == 1 # one shared description column
|
||||
|
||||
|
||||
@@ -219,14 +219,14 @@ def test_marked_label_lines_up_a_mixed_list_when_told_its_lane() -> None:
|
||||
marked_label(_WIDE, "View archived contacts", "", lane=lane),
|
||||
]
|
||||
starts = {cell_len(row.plain[: row.plain.index(word)])
|
||||
for row, word in zip(rows, ("Purge", "View"))}
|
||||
for row, word in zip(rows, ("Purge", "View"), strict=True)}
|
||||
assert starts == {lane + 1}
|
||||
|
||||
# Without the lane each mark measures itself, which is right for a list whose rows all
|
||||
# lead with the same icon — and is exactly what misaligns a mixed one.
|
||||
solo = [marked_label(_NARROW, "A", "err"), marked_label(_WIDE, "B", "")]
|
||||
assert len({cell_len(row.plain[: row.plain.index(letter)])
|
||||
for row, letter in zip(solo, ("A", "B"))}) == 2
|
||||
for row, letter in zip(solo, ("A", "B"), strict=True)}) == 2
|
||||
|
||||
|
||||
def test_the_main_menu_starts_every_title_in_the_same_cell() -> None:
|
||||
@@ -252,7 +252,7 @@ def test_the_main_menu_starts_every_title_in_the_same_cell() -> None:
|
||||
assert widths == {1, 2}, "a menu of one icon width would prove nothing"
|
||||
starts = {
|
||||
label.cell_len - cell_len(tool.title or tool.name)
|
||||
for tool, label in zip(tools, labels)
|
||||
for tool, label in zip(tools, labels, strict=True)
|
||||
}
|
||||
assert len(starts) == 1, "a title starts a column early"
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from itertools import pairwise
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -197,7 +198,7 @@ def test_revisited_path_folds_into_a_cycle_that_piles_its_relays() -> None:
|
||||
hops = ("7f", "c1", "8e", "da", "ee", "c1", "27")
|
||||
seq = (SRC_NODE, *hops, DST_NODE)
|
||||
ordered = list(dict.fromkeys(seq))
|
||||
x = _balanced_x(ordered, set(zip(seq, seq[1:])))
|
||||
x = _balanced_x(ordered, set(pairwise(seq)))
|
||||
# Evenly spread, these eight nodes would sit 1/7 apart and the four loop members would
|
||||
# span three of those gaps; folded, the whole loop fits inside a single one.
|
||||
loop = [x[node] for node in ("c1", "8e", "da", "ee")]
|
||||
@@ -217,12 +218,12 @@ def test_allow_duplicate_nodes_keeps_x_strictly_rising_along_the_walk() -> None:
|
||||
[layer] = _split_revisits([PathLayer(hops, WHITE, 3)])
|
||||
seq = (SRC_NODE, *layer.hops, DST_NODE)
|
||||
ordered = list(dict.fromkeys(seq))
|
||||
x = _balanced_x(ordered, set(zip(seq, seq[1:])))
|
||||
x = _balanced_x(ordered, set(pairwise(seq)))
|
||||
walked = [x[node] for node in seq]
|
||||
assert walked == sorted(walked) and len(set(walked)) == len(walked)
|
||||
assert x[SRC_NODE] == 0.0 and x[DST_NODE] == 1.0
|
||||
# A single walk with nothing else to balance against spreads dead evenly end to end.
|
||||
gaps = [b - a for a, b in zip(walked, walked[1:])]
|
||||
gaps = [b - a for a, b in pairwise(walked)]
|
||||
assert max(gaps) - min(gaps) < 1e-9
|
||||
|
||||
|
||||
@@ -390,7 +391,7 @@ def _packed_lanes(layers, width=60, max_rows=15): # noqa: ANN001
|
||||
if node not in seen:
|
||||
seen.add(node)
|
||||
ordered.append(node)
|
||||
edges.update(zip(seq, seq[1:]))
|
||||
edges.update(pairwise(seq))
|
||||
xfrac = _balanced_x(ordered, edges)
|
||||
best = max(range(len(drawn)), key=lambda j: drawn[j].priority)
|
||||
owner: dict[str, int] = {}
|
||||
@@ -632,7 +633,7 @@ def _vias_for(layers, width=60, max_rows=15): # noqa: ANN001
|
||||
if node not in seen:
|
||||
seen.add(node)
|
||||
ordered.append(node)
|
||||
edges.update(zip(seq, seq[1:]))
|
||||
edges.update(pairwise(seq))
|
||||
xfrac = _balanced_x(ordered, edges)
|
||||
best = max(range(len(drawn)), key=lambda j: drawn[j].priority)
|
||||
owner: dict[str, int] = {}
|
||||
@@ -774,7 +775,7 @@ def test_bidir_pair_does_not_jam_a_sibling_relay_against_the_origin() -> None:
|
||||
]
|
||||
seqs = [(SRC_NODE, *layer.hops, DST_NODE) for layer in layers]
|
||||
ordered = list(dict.fromkeys(n for seq in seqs for n in seq))
|
||||
edges = {pair for seq in seqs for pair in zip(seq, seq[1:])}
|
||||
edges = {pair for seq in seqs for pair in pairwise(seq)}
|
||||
x = _balanced_x(ordered, edges)
|
||||
assert x[SRC_NODE] == 0.0 and x[DST_NODE] == 1.0
|
||||
assert x["cc"] == pytest.approx(1 / 3) # its natural spot, not jammed toward 0
|
||||
|
||||
@@ -57,7 +57,7 @@ def _char_styles(text) -> list[tuple[str, str]]: # noqa: ANN001
|
||||
for span in text.spans:
|
||||
for i in range(span.start, span.end):
|
||||
styles[i] = str(span.style)
|
||||
return list(zip(text.plain, styles))
|
||||
return list(zip(text.plain, styles, strict=True))
|
||||
|
||||
|
||||
def test_plain_mode_matches_the_app_wide_arrow_presentation() -> None:
|
||||
|
||||
@@ -8,6 +8,7 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from itertools import pairwise
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -344,7 +345,7 @@ def test_day_columns_widths_differ_by_at_most_one_dot_and_interleave() -> None:
|
||||
notches = [i for i, c in enumerate(cols) if c is GAP] # one per day boundary
|
||||
assert len(notches) == 12
|
||||
edges = [-1, *notches, len(cols)] # each bar runs between two notches
|
||||
widths = [b - a - 1 for a, b in zip(edges, edges[1:])]
|
||||
widths = [b - a - 1 for a, b in pairwise(edges)]
|
||||
assert len(widths) == 13 and set(widths) == {3, 4}
|
||||
first_wide = widths.index(4)
|
||||
last_wide = len(widths) - 1 - widths[::-1].index(4)
|
||||
|
||||
+2
-2
@@ -1241,7 +1241,7 @@ def test_device_picker_builds_aligned_columns(tmp_path) -> None:
|
||||
assert rows[-2].strip().endswith("Add a network device…") # no caveat tag trailing it
|
||||
assert rows[-1].strip().endswith("Quit")
|
||||
device_rows = rows[:2]
|
||||
assert all(port in row for port, row in zip(("COM5", "/dev/ttyUSB0"), device_rows))
|
||||
assert all(port in row for port, row in zip(("COM5", "/dev/ttyUSB0"), device_rows, strict=True))
|
||||
assert device_rows[0].index("COM5") == device_rows[1].index("/dev/ttyUSB0")
|
||||
|
||||
|
||||
@@ -2765,7 +2765,7 @@ def test_the_rows_a_dialog_does_not_reach_come_back_unchanged() -> None:
|
||||
session.push(ButtonDialog("Sure?", [("Cancel", 0), ("Yes", 1)]))
|
||||
second = session._plain_frame().split("\n")
|
||||
|
||||
unchanged = sum(1 for a, b in zip(first, second) if a == b)
|
||||
unchanged = sum(1 for a, b in zip(first, second, strict=True) if a == b)
|
||||
assert unchanged >= 10, "the box should only rewrite the rows it covers"
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ def test_walk_graph_labels_names_in_full_when_the_canvas_has_room() -> None:
|
||||
]
|
||||
topo = MeshTopology(US, contacts=long_names)
|
||||
when = utcnow()
|
||||
for c, snr in zip(long_names, (8.0, 1.0)):
|
||||
for c, snr in zip(long_names, (8.0, 1.0), strict=True):
|
||||
topo.add_walk([topo.self_id, topo.canonical(c.public_key)], snrs=[snr], when=when,
|
||||
source="trace")
|
||||
screen = WalkScreen(
|
||||
|
||||
@@ -238,7 +238,8 @@ def test_alert_row_hues_the_label_by_resolved_key(tmp_path: Path) -> None:
|
||||
from meshterm.ui.theme import name_style
|
||||
from meshterm.ui.watchtower_screen import _alert_lanes
|
||||
|
||||
key_of = lambda label: "d4" * 6 if label == "Roof" else None
|
||||
def key_of(label):
|
||||
return "d4" * 6 if label == "Roof" else None
|
||||
alert = Alert(ident=1, when=utcnow(), kind="silence", label="Roof", message="quiet")
|
||||
row = _alert_lanes(alert, key_of)
|
||||
at = row.plain.index("Roof")
|
||||
@@ -261,8 +262,10 @@ def test_alert_row_leads_node_name_with_type_glyph(tmp_path: Path) -> None:
|
||||
from meshterm.ui.watchtower_screen import _alert_lanes
|
||||
from meshterm.ui.widgets import _DEFAULT_GLYPH, _NODE_GLYPHS
|
||||
|
||||
key_of = lambda label: None
|
||||
type_of = lambda label: 2 if label == "Roof" else None # Roof advertises as a repeater
|
||||
def key_of(label):
|
||||
return None
|
||||
def type_of(label):
|
||||
return 2 if label == "Roof" else None # Roof advertises as a repeater
|
||||
glyph, glyph_style = _NODE_GLYPHS[2]
|
||||
|
||||
alert = Alert(ident=1, when=utcnow(), kind="silence", label="Roof", message="quiet")
|
||||
|
||||
Reference in new issue
Block a user