|
1 | 1 | # Technical Debt |
2 | 2 |
|
| 3 | +## [HIGH PRIORITY] mypy is not run anywhere in CI or dev tooling |
| 4 | + |
| 5 | +Found 2026-07-29 while fixing the `_ImageBase` Protocol gaps in |
| 6 | +`ImageBlobs.py`/`ImageRegionFeatures.py`/`ImagePointFeatures.py` |
| 7 | +(see git history — three mixins were missing the |
| 8 | +`class XMixin(_ImageBase if TYPE_CHECKING else object)` pattern every |
| 9 | +other mixin uses). That fix prompted the question: if a new method is |
| 10 | +added to a mixin and it's accessed via `self.` from a *different* |
| 11 | +mixin, and it's not yet declared in `_image_typing.py`'s `_ImageBase` |
| 12 | +Protocol, what actually catches that? Answer: **nothing, currently**. |
| 13 | +`mypy` is not in `pyproject.toml`'s `dev` extra and does not run in any |
| 14 | +`.github/workflows/*.yml` — confirmed by grep, zero hits. So a Protocol |
| 15 | +gap like this doesn't fail a build or even show a warning; it just |
| 16 | +silently produces incomplete/wrong type information for anyone using an |
| 17 | +editor with type-checking (Pylance, mypy in an IDE), with no automated |
| 18 | +signal anywhere. This is the quietest version of a pattern that's |
| 19 | +already bitten this project twice today in more visible forms (the |
| 20 | +`image_class.rst` autosummary list silently going stale for the whole |
| 21 | +`Image` sidebar, and `ci.yml`'s conda `create-args` list silently |
| 22 | +missing `pgraph-python`/drifting on `opencv`) — a hand-maintained |
| 23 | +shadow list with no automated check that it stays in sync with reality. |
| 24 | + |
| 25 | +**Ran `mypy src/machinevisiontoolbox --ignore-missing-imports` fresh, |
| 26 | +2026-07-29** (superseding the stale, less-categorized April audit in |
| 27 | +`NOTES`): **524 errors in 31 files** (checked 49 source files). By |
| 28 | +category: |
| 29 | + |
| 30 | +| Code | Count | |
| 31 | +|---|---| |
| 32 | +| `attr-defined` | 84 | |
| 33 | +| `assignment` | 82 | |
| 34 | +| `union-attr` | 76 | |
| 35 | +| `index` | 75 | |
| 36 | +| `arg-type` | 53 | |
| 37 | +| `misc` | 26 | |
| 38 | +| `name-defined` | 24 | |
| 39 | +| `var-annotated` | 18 | |
| 40 | +| `valid-type` | 15 | |
| 41 | +| `operator` | 15 | |
| 42 | +| `call-overload` | 14 | |
| 43 | +| `return-value` | 13 | |
| 44 | +| `has-type` | 11 | |
| 45 | +| `return` | 6 | |
| 46 | +| `no-redef` | 5 | |
| 47 | +| `override` | 4 | |
| 48 | +| `method-assign` | 2 | |
| 49 | +| `call-arg` | 1 | |
| 50 | + |
| 51 | +**Correcting an initial hypothesis**: expected most `attr-defined` |
| 52 | +errors to trace to incomplete `_ImageBase` coverage (only ~96 of |
| 53 | +`Image`'s ~290 public members are declared — by design, since the |
| 54 | +Protocol only needs to cover attributes actually cross-referenced |
| 55 | +between mixins, not the full public API). Checked the real breakdown |
| 56 | +instead of assuming: **none** of the current `attr-defined` errors are |
| 57 | +actually `_ImageBase` gaps. The two real dominant causes are unrelated: |
| 58 | +- 23 of 84: `machinevisiontoolbox/base/__init__.py` re-exports every |
| 59 | + submodule via wildcard `from X import *` (9 submodules) with no |
| 60 | + explicit `__all__`/direct re-export list; mypy can't reliably resolve |
| 61 | + names through that chain, so every file that does |
| 62 | + `from machinevisiontoolbox.base import (draw_circle, plot_labelbox, |
| 63 | + findpeaks2d, ...)` gets a false "module has no attribute" even though |
| 64 | + these work fine at runtime. |
| 65 | +- 8 of 84: `cv2.<X>_create` dynamic dispatch (`getattr(cv2, ...)` / |
| 66 | + `getattr(cv2.xfeatures2d, ...)` patterns in `ImagePointFeatures.py`'s |
| 67 | + feature-detector dict) — mypy can't type-check dynamic attribute |
| 68 | + access, expected and low-value to fix. |
| 69 | +- The remaining ~53 are scattered; `VisualServo.py` alone accounts for |
| 70 | + 44 of the 84 `attr-defined` errors (a mix of real typos like |
| 71 | + `"plotpose"; maybe "plot_pose"?` and missing `machinevisiontoolbox.base` |
| 72 | + attributes via the same wildcard-import issue) and is worth its own |
| 73 | + look independent of the mixin-Protocol question that prompted this |
| 74 | + audit. |
| 75 | + |
| 76 | +The `_ImageBase`-completeness risk described above is still real, just |
| 77 | +currently *latent* rather than demonstrated by a live error — worth |
| 78 | +re-running this same `mypy` audit after any future mixin refactor to |
| 79 | +catch it if it does start manifesting. |
| 80 | + |
| 81 | +### Fix |
| 82 | + |
| 83 | +Two independent pieces, roughly in priority order: |
| 84 | +1. Fix `machinevisiontoolbox/base/__init__.py`'s wildcard re-exports |
| 85 | + (add explicit `__all__` composed from each submodule's own `__all__`, |
| 86 | + or switch to explicit `from X import (name1, name2, ...)` — either |
| 87 | + should immediately clear ~23+ of the `attr-defined` count and is a |
| 88 | + mechanical, low-risk change). |
| 89 | +2. Wire `mypy` into CI (even just as a non-blocking/advisory job at |
| 90 | + first, given 524 existing errors) so future drift is visible instead |
| 91 | + of silent. Add `mypy` to `pyproject.toml`'s `dev` extra either way. |
| 92 | +Do not attempt to fix all 524 errors in one pass — triage by category |
| 93 | +(the `[call-arg]`/`[override]`/`[return]` categories are more likely to |
| 94 | +be real bugs; `assignment`/`arg-type`/`index` are more likely the |
| 95 | +`ArrayLike`-union-too-broad pattern the April `NOTES` audit already |
| 96 | +identified). |
| 97 | + |
3 | 98 | ## `Image.ncdf` is documented as deprecated but never warns |
4 | 99 |
|
5 | 100 | `ncdf` (`src/machinevisiontoolbox/ImageWholeFeatures.py:511-523`) has a |
@@ -147,6 +242,33 @@ new extra or dependency. Patched directly (added `ipython`/`pygments` to |
147 | 242 | `create-args`) and merged 2026-07-29 — the underlying architectural gap |
148 | 243 | below is still open. |
149 | 244 |
|
| 245 | +**Third example, found 2026-07-29 (pre-existing, not a new incident)**: |
| 246 | +`pgraph-python` is a real, declared dependency in `pyproject.toml`'s |
| 247 | +`dependencies` list, but is completely absent from `ci.yml`'s |
| 248 | +`create-args` — not merely unpinned, never installed at all. The only |
| 249 | +file that imports it, `BundleAdjust.py`, does so inside a bare |
| 250 | +`except:` (see the bare-except finding elsewhere in this file) that |
| 251 | +silently falls back when the import fails. So every CI run has been |
| 252 | +exercising `BundleAdjust`'s pgraph-dependent code in "not installed" |
| 253 | +fallback mode this whole time, with nothing surfacing it. Not fixed |
| 254 | +here — decided (2026-07-29) to leave the conda→pip migration itself as |
| 255 | +deferred tech debt rather than keep patching individual missing |
| 256 | +packages one at a time; noting this one so it's not lost. |
| 257 | + |
| 258 | +**Decision, 2026-07-29**: known who introduced the conda/micromamba |
| 259 | +setup and why (a well-intentioned contributor's preference for conda, |
| 260 | +not a technical requirement) — confirmed not urgent enough to fix now. |
| 261 | +Branch protection on `main` (`All tests passed` + `build` required, |
| 262 | +strict/up-to-date-with-base required) now contains the *blast radius* |
| 263 | +of a future incident like this — it'll block just the one PR that hits |
| 264 | +it, rather than silently landing on `main` and confusing every other |
| 265 | +open PR at once, which was the actually painful part of the opencv5 |
| 266 | +incident. The root architectural gap remains real and will very likely |
| 267 | +surface again the same way (a fourth missing/drifted package), but |
| 268 | +that's an acceptable trade for now given the fix requires careful |
| 269 | +cross-OS testing (libegl/Xvfb handling, `matplotlib-base` vs |
| 270 | +`matplotlib` naming) rather than a quick patch. |
| 271 | + |
150 | 272 | ### Fix |
151 | 273 |
|
152 | 274 | Normalize to the same `actions/setup-python` + `pip install .[dev]` |
|
0 commit comments