modern_decode: read abstract sequence interfaces as several values - #36
Merged
Merged
Conversation
`argh` recognises `list` and nothing else, so a parameter annotated
`names: Sequence[str]` became ONE positional taking ONE token. The failure is
quiet in the way that costs time: the parser builds, `--help` looks right, the
command runs, and the second argument comes back as an "unrecognized argument"
pointing at the value rather than at the annotation.
That penalises exactly the annotation style that prefers `collections.abc`
interfaces over concrete containers — a signature written `Sequence[str]` means
the same thing to a reader as `list[str]` and meant something else to the
parser.
`modern_decode` now covers `Sequence` / `MutableSequence` / `Iterable` /
`Collection` / `Set` / `MutableSet`, plus `tuple` / `set` / `frozenset`,
subscripted or bare. `argh_decode` is **untouched**: it is argh's if-chain
if-branch for if-branch, and widening it would break the compatibility contract
that is its whole purpose. A test asserts it still returns `{}` for every new
spelling, and still returns what it always did for `list[int]`.
Three decisions in the implementation:
- **`str` and `bytes` must never be read as sequences**, and matching on
`typing.get_origin` avoids it BY CONSTRUCTION rather than by a special case:
`get_origin(str)` is None. Both are registered `Sequence`s, so an `issubclass`
test would have read every string parameter as variadic and broken every
existing CLI. Tested directly, because it is the trap this change exists
inside.
- **A homogeneous fixed-length tuple gets a fixed `nargs`** — `tuple[int, int]`
-> `nargs=2, type=int`. It is the one case where the annotation carries a
count, and it makes `--size W H` fall out of the signature.
- **A heterogeneous tuple gets no inference at all.** `add_argument` has a
single `type` and there is no honest one to choose, so `tuple[int, str]`
falls through to `{}` rather than guessing one and converting the other
wrongly.
Found while adopting `cw` as the CLI for `looks` (t/looks), whose house style
annotates with `collections.abc` interfaces throughout.
Claude-Session: https://claude.ai/code/session_01CnXLihXKG9jfvygHvgKVpX
Pure line-wrapping and quote normalisation, no semantics. They were already unformatted before this branch; `ruff format` picked them up while formatting the grammar change, and they are split out here so the functional commit contains only the functional change. Claude-Session: https://claude.ai/code/session_01CnXLihXKG9jfvygHvgKVpX
thorwhalen
added a commit
to thorwhalen/looks
that referenced
this pull request
Sep 2, 2026
Both owner questions ratified 2026-09-02. RULE G: `burns` keeps authored geometry and gains a `looks`-backed ffmpeg backend, so `looks` owns the compile side and the dependency points burns -> looks. RULE N: `looks` owns normalisation, as already shipped in `Look.target`. `looks.motion` is RULE G's compile half — keyframes in, an ffmpeg fragment out, no easing and no `burns` import. It picks the filter from what the path does, which is not a matter of taste: a recorded fleet fact said to use `crop` and avoid `zoompan`, and measurement overturned it. Both of that advice's premises are true (`t` is undefined; the default `d` duplicates frames) and its conclusion is wrong (`in_time` works; `d=1` is exactly 1:1) — while `crop` cannot express a zoom at all, refusing to configure rather than freezing. Three further traps are silent wrong answers and are now refusals: x/y are in original input pixels (60.5 dB vs 6.3), `fps` retimes without changing the frame count (2.0s -> 0.8s), and `zoom` is clamped at 10 (54.4 dB vs 13.2). The tests compare pictures with ffmpeg's own psnr, because string equality passes for a fragment ffmpeg refuses and equally for one that renders the wrong thing. The CLI dispatches through `cw` (MIT, no dependencies, stdlib-only on import) rather than the LGPL package or hand-rolled argparse, with the Sequence-reading fix contributed as i2mint/cw#36. Two of the package's own guards caught this work: the extras ledger refused the new `[cli]` extra until `cw` had a row, and the pin over the decisions table refused a silently-grown ledger. A third had been reporting "skipped" on half of CI since it was written, for want of `tomllib` on 3.10 — now read through a small reader that is checked against `tomllib` on the leg that has one. 628 tests on 3.12, 545 on 3.10. Follow-ups filed: thorwhalen/muvid#68 (the docstring at its source), thorwhalen/burns#12 (the adapter).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
arghrecogniseslistand nothing else, so a parameter annotatednames: Sequence[str]became one positional taking one token:The failure is quiet in the way that costs time: the parser builds,
--helplooks right, the command runs, and the error points at the value rather than at the annotation. And it penalises exactly the annotation style that preferscollections.abcinterfaces over concrete containers —Sequence[str]means the same thing to a reader aslist[str], and meant something else to the parser.Measured before the change:
list[str],List[str],listSequence[str],Iterable[str],tuple[str, ...],SequenceThe change
modern_decodenow coversSequence/MutableSequence/Iterable/Collection/Set/MutableSet, plustuple/set/frozenset, subscripted or bare.argh_decodeis untouched. It is argh's if-chain if-branch for if-branch, and widening it would break the compatibility contract that is its whole purpose —modern_decodeis the seam for exactly this, and its fall-through composition made the change three lines. A test assertsargh_decodestill returns{}for every new spelling and still returns what it always did forlist[int].Three decisions
strandbytesmust never be read as sequences, and this avoids it by construction rather than by a special case: matching is ontyping.get_origin, andget_origin(str)isNone. Both are registeredSequences, so anissubclasstest would have read every string parameter as variadic and broken every existing CLI. Tested directly, because it is the trap this change lives inside.nargs—tuple[int, int]→{'nargs': 2, 'type': int}. It is the one case where the annotation carries a count, and it makes--size W Hfall out of the signature.add_argumenthas a singletypeand there is no honest one to choose, sotuple[int, str]→{}rather than guessing one and converting the other wrongly.Tests
952 passing (was 932). 20 new: the abstract interfaces subscripted and bare, the
str/bytestrap, both tuple cases,Optional[Sequence[int]], and theargh_decode-is-unchanged guard.Second commit is pure formatting of two test files
ruff formathad drifted from — split out so the functional commit contains only the functional change.Where it came from
Adopting
cwas the CLI forlooks, whose house style annotates withcollections.abcinterfaces throughout.cwwas the right package — MIT,dependencies = [], andimport cwverified to pull in nothing beyond stdlib — it just could not read the annotations the calling code was already written with.https://claude.ai/code/session_01CnXLihXKG9jfvygHvgKVpX