Skip to content

Three more annotations that described the wrong thing, one of them a live bug - #367

Merged
alexarje merged 2 commits into
masterfrom
mypy-tail
Aug 23, 2026
Merged

alexarje merged 2 commits into
masterfrom
mypy-tail

Conversation

@alexarje

Copy link
Copy Markdown
Contributor

Working the tail of #350. mypy goes from 90 errors in 16 files to 76, and as in the earlier passes the errors turned out to be failures rather than typing debt.

A weak reference dereferenced unguarded

Flow holds its MgVideo weakly, so that the video holding the flow and the flow holding the video do not make a cycle. Nothing therefore keeps the video alive in this shape, which does not look wrong:

flow = mg.MgVideo("clip.avi").flow   # the video is collected here
flow.dense()

Every later self.parent() resolves to None, and the call raised AttributeError: 'NoneType' object has no attribute 'flow_dense_video' — naming whichever attribute was reached for and saying nothing about the cause. Reproduced on the parent commit before anything was changed.

The fifteen dereferences now go through a _parent() resolver that raises a sentence showing the pattern that works. That file goes from seventeen errors to seven.

A frame rate is not an integer

frame2ms declared fps: int while every caller passes self.fps read from the file. Commit fa0f719 made that rate a true float on the grounds that truncating it is wrong; this signature never followed.

mg_motiondata said it returned a list

It returns the path it built: a str for one data format and a list for several, confirmed by running it. An unsupported data_format now says so rather than returning None after doing all the work.

Tests

Five, for the weak reference. One pins that the reference stays weak — a well-meaning change to a strong one would make every video that touched .flow immortal.

680 tests pass, 4 skipped, locally on Linux/Python 3.12. This PR exists to get the full matrix, since CI does not run on branch pushes.

Left deliberately

self.fps is float | None and accounts for about eight of the remaining errors. get_video() always replaces it with the real rate, so the attribute really is a float after construction — but saying so means changing a guard in __init__, which changes behaviour when fps=0 is passed explicitly. That is a call for @alexarje rather than one to make unattended.

🤖 Generated with Claude Code

alexarje and others added 2 commits August 23, 2026 12:32
…live bug

Working #350's tail. mypy goes from 90 errors in 16 files to 76, and as with the
earlier passes the errors were failures rather than typing debt.

*A weak reference dereferenced unguarded.* `Flow` holds its `MgVideo` weakly, so
that the video holding the flow and the flow holding the video do not make a
cycle. Nothing therefore keeps the video alive in `flow = mg.MgVideo(p).flow`,
and every later `self.parent()` resolves to None. Calling `flow.dense()` after
that raised `AttributeError: 'NoneType' object has no attribute
'flow_dense_video'`, which names the attribute reached for and says nothing
about the cause. Verified on the parent commit before changing anything. The
fifteen dereferences go through a `_parent()` resolver now, which raises a
sentence showing the pattern that works. Seventeen errors in that file become
seven.

*A frame rate is not an integer.* `frame2ms` declared `fps: int` while every
caller passes `self.fps` read from the file. Commit fa0f719 made that rate a
true float on the grounds that truncating it is wrong; this signature never
followed.

*`mg_motiondata` said it returned a list.* It returns the path it built: a `str`
for one data format and a `list` for several, which I confirmed by running it.
An unsupported `data_format` also says so now instead of returning None after
doing all the work.

Five tests for the weak reference, one of them pinning that it stays WEAK --- a
well-meaning change to a strong reference would make every video that touched
`.flow` immortal.

DELIBERATELY LEFT for ARJ: `self.fps` is `float | None` and accounts for about
eight of the remaining errors. `get_video()` always replaces it with the real
rate, so the attribute is genuinely a float after construction, but saying so
means changing a guard in `__init__` and that changes behaviour when `fps=0` is
passed explicitly. Not a call to make unattended.

680 tests pass, 4 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ARJ's call. The ARGUMENT stays optional, because a file carries its own rate and
only an array needs one supplied. The ATTRIBUTE is not: `get_video()` reads the
true rate from the file and overwrites whatever the constructor stored, so by
the end of `__init__` it is always a number. Saying so closes twelve errors
across the modules that divide by it --- mypy goes from 76 to 64 --- and it was
also simply true, which is the better reason.

The edge case I had flagged as needing a decision turned out not to exist. I
built the parent commit in a scratch worktree and ran it: `MgVideo(array=arr,
fps=0)` raised `FileNotFoundError: z.avi` there, exactly as it does now. The
from-array branch tests the rate for truth rather than for None, which rejects
an explicit zero, and zero was never a frame rate anything could encode at.

Both spellings blamed the output file for a missing argument, though, because
the encode never ran and the read after it found nothing. So the case raises a
ValueError naming the real problem instead.

682 tests pass, 4 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alexarje
alexarje merged commit 2073592 into master Aug 23, 2026
12 checks passed
@alexarje
alexarje deleted the mypy-tail branch August 23, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant