mypy: event-handler signatures accept None (Qt stub overrides) #17

Merged
clanker merged 1 commit from pr/mypy-event-signatures into master 2026-08-14 20:04:22 +00:00
Member

mypy: fix the Qt stub override-signature bucket.

PyQt6 stubs declare event-handler parameters as X | None; every override in the codebase used the non-Optional form, producing ~35 Liskov override errors. This PR widens the signatures and guards None at the top of each handler (None events are no-ops):

  • keyPressEvent — commandbar, compose, editor, helpwindow, panel
  • eventFilter (compose), focusInEvent (panel)
  • mousePressEvent / showEvent — SearchOverlay, _BarBox
  • resizeEvent / closeEvent — MainWindow
  • requestStarted — MessageHandler, EmbeddedImageHandler
  • javaScriptConsoleMessage — MessagePage
  • thread_model.parent — type: ignore[override] (the no-arg QObject.parent() overload isn't meaningful for a model)

Rebased onto master after #15 merged: 152 → 122, zero new. #18 and #19 stack on this branch.

**mypy: fix the Qt stub override-signature bucket.** PyQt6 stubs declare event-handler parameters as `X | None`; every override in the codebase used the non-Optional form, producing ~35 Liskov override errors. This PR widens the signatures and guards `None` at the top of each handler (None events are no-ops): - `keyPressEvent` — commandbar, compose, editor, helpwindow, panel - `eventFilter` (compose), `focusInEvent` (panel) - `mousePressEvent` / `showEvent` — SearchOverlay, _BarBox - `resizeEvent` / `closeEvent` — MainWindow - `requestStarted` — MessageHandler, EmbeddedImageHandler - `javaScriptConsoleMessage` — MessagePage - `thread_model.parent` — `type: ignore[override]` (the no-arg `QObject.parent()` overload isn't meaningful for a model) Rebased onto master after #15 merged: **152 → 122**, zero new. #18 and #19 stack on this branch.
- close_panel(0): 'if not to_close:' treated index 0 as falsy and
  closed the *current* tab instead of tab 0. Now 'to_close is None'
  distinguishes 'no argument' from an explicit 0.
- headerData: section <= len(columns) was off-by-one; now <.
- keymap: 'D' was a duplicate of 'I' (both show inbox); removed D,
  updated the roadmap docs.
- setup.py: python_requires >=3.7 -> >=3.10 to match the code (X | None,
  typing.Literal, modern PyQt6) and the README/.agent requirement.
Every single-message tag change (u/f/s in the preview) used to trigger
beginResetModel + a notmuch search in *every* open search panel: the
tree was torn down, selection/scroll lost, and the view re-rendered.
refresh_thread now replaces the row and emits dataChanged when the
thread still matches the query; only falls back to a full reset when
the thread drops out of the results (row removal) or notmuch errors.

Verified: no modelReset on in-place update, no signals when nothing
changed, full reset when the thread leaves the query.
Panels and keymaps now dispatch through structural protocols instead of
'object' + hasattr chains that failed silently on typos.

- protocols.py (new): PanelApp (satisfied by Dodo and AppController),
  runtime_checkable ThreadList / ThreadView, and the method-name
  allowlists used by the string-based delegators.
- controller: navigate_list / mark_and_advance / toggle_tag_hotkey /
  delegate_to_list / delegate_to_thread narrow via isinstance() and log
  unknown method names instead of no-opping; typed calls replace the
  hasattr() checks.
- Panel / panels / mixin: 'app: object' -> PanelApp; MessagePage and
  CommandBar widened likewise.
- keymap: all keymap dicts annotated (fixes the lambda inference error).

mypy: 182 -> 152 errors (30 fixed: the app:object bucket + mixin app
conflict), zero new.
Nothing panel-side uses them; the controller mirrors are being removed
in a separate PR, so requiring them would break AppController's
structural compatibility.
PyQt6 stubs declare handler params as 'X | None'; every override used
the non-Optional form, producing ~35 Liskov override errors. Widen the
signatures and guard None at the top of each handler:

- keyPressEvent (commandbar, compose, editor, helpwindow, panel)
- eventFilter (compose), focusInEvent (panel)
- mousePressEvent/showEvent (SearchOverlay, _BarBox)
- resizeEvent/closeEvent (MainWindow)
- requestStarted (MessageHandler/EmbeddedImageHandler)
- javaScriptConsoleMessage (MessagePage)
- thread_model.parent: type: ignore[override] (the no-arg
  QObject.parent() overload isn't meaningful for a model)

mypy: 182 -> 122 (60 fixed), zero new. Runtime behavior unchanged
(None events are no-ops).
clanker force-pushed pr/mypy-event-signatures from 26642c124c to 814bc00235 2026-08-14 19:45:31 +00:00 Compare
clanker force-pushed pr/mypy-event-signatures from 814bc00235 to 68e2db50b2 2026-08-14 20:02:58 +00:00 Compare
clanker merged commit 62c860bb68 into master 2026-08-14 20:04:22 +00:00
clanker deleted branch pr/mypy-event-signatures 2026-08-14 20:04:22 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Home/lazarus!17
No description provided.