diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 937f01e..dfadbd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,13 @@ jobs: - uses: actions/checkout@v4 - run: pipx run check-manifest + pyright: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v6 + - run: uv run pyright + test: name: ${{ matrix.platform }} py${{ matrix.python-version }} runs-on: ${{ matrix.platform }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8051bf3..c95a2b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,6 @@ repos: args: ["--fix", "--unsafe-fixes"] - id: ruff-format - - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.16.1 hooks: @@ -32,3 +31,14 @@ repos: - pydantic >2 - pydantic-compat - in-n-out + + - repo: local + hooks: + - id: pyright + stages: [manual] + name: pyright + language: system + exclude: "^tests/.*|^demo/.*|^docs/.*" + types_or: [python, pyi] + require_serial: true + entry: uv run pyright diff --git a/demo/keybinding_helper.py b/demo/keybinding_helper.py index da2afd0..5bc0cdc 100644 --- a/demo/keybinding_helper.py +++ b/demo/keybinding_helper.py @@ -8,4 +8,4 @@ w = QModelKeyBindingEdit() w.editingFinished.connect(lambda: print(w.keyBinding())) w.show() -sys.exit(app.exec_()) +sys.exit(app.exec()) diff --git a/demo/model_app.py b/demo/model_app.py index 1a5eb67..f28d4ad 100644 --- a/demo/model_app.py +++ b/demo/model_app.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import TYPE_CHECKING, cast from qtpy.QtCore import QFile, QFileInfo, QSaveFile, Qt, QTextStream from qtpy.QtWidgets import QApplication, QFileDialog, QMessageBox, QTextEdit @@ -7,6 +8,9 @@ from app_model.backends.qt import QModelMainWindow from app_model.expressions import create_context +if TYPE_CHECKING: + from app_model.backends.qt._qmenu import QModelMenuBar + class MainWindow(QModelMainWindow): def __init__(self, app: Application) -> None: @@ -21,7 +25,8 @@ def __init__(self, app: Application) -> None: self.addModelToolBar(MenuId.FILE, exclude={CommandId.SAVE_AS, CommandId.EXIT}) self.addModelToolBar(MenuId.EDIT) self.addModelToolBar(MenuId.HELP) - self.statusBar().showMessage("Ready") + if sb := self.statusBar(): + sb.showMessage("Ready") self.set_current_file("") @@ -33,11 +38,13 @@ def _update_context(self, available: bool) -> None: self._ctx["copyAvailable"] = available def _on_context_changed(self) -> None: - self.menuBar().update_from_context(self._ctx) + mb = cast("QModelMenuBar", self.menuBar()) + mb.update_from_context(self._ctx) def set_current_file(self, fileName: str) -> None: self._cur_file = fileName - self._text_edit.document().setModified(False) + if doc := self._text_edit.document(): + doc.setModified(False) self.setWindowModified(False) if self._cur_file: @@ -58,11 +65,11 @@ def save_as(self) -> bool: def save_file(self, fileName: str) -> bool: error = None - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) file = QSaveFile(fileName) if file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text): # type: ignore outf = QTextStream(file) - outf << self._text_edit.toPlainText() + outf << self._text_edit.toPlainText() # pyright: ignore if not file.commit(): reason = file.errorString() error = f"Cannot write file {fileName}:\n{reason}." @@ -77,7 +84,7 @@ def save_file(self, fileName: str) -> bool: return True def maybe_save(self) -> bool: - if self._text_edit.document().isModified(): + if (doc := self._text_edit.document()) and doc.isModified(): ret = QMessageBox.warning( self, "Application", @@ -113,12 +120,13 @@ def load_file(self, fileName: str) -> None: return inf = QTextStream(file) - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) self._text_edit.setPlainText(inf.readAll()) QApplication.restoreOverrideCursor() self.set_current_file(fileName) - self.statusBar().showMessage("File loaded", 2000) + if sb := self.statusBar(): + sb.showMessage("File loaded", 2000) def about(self) -> None: QMessageBox.about( @@ -255,4 +263,4 @@ class CommandId: app.injection_store.register_provider(lambda: main_win, MainWindow) main_win.show() - qapp.exec_() + qapp.exec() diff --git a/demo/multi_file/__main__.py b/demo/multi_file/__main__.py index 91e6466..b5bd22c 100644 --- a/demo/multi_file/__main__.py +++ b/demo/multi_file/__main__.py @@ -10,4 +10,4 @@ qapp = QApplication.instance() or QApplication([]) app = MyApp() app.show() -qapp.exec_() +qapp.exec() diff --git a/demo/multi_file/functions.py b/demo/multi_file/functions.py index 0e83201..03af6f3 100644 --- a/demo/multi_file/functions.py +++ b/demo/multi_file/functions.py @@ -7,7 +7,8 @@ def open_file() -> None: def close() -> None: - QApplication.activeWindow().close() + if win := QApplication.activeWindow(): + win.close() print("close") diff --git a/demo/qapplication.py b/demo/qapplication.py index 9989a36..236c575 100644 --- a/demo/qapplication.py +++ b/demo/qapplication.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# pyright: reportOptionalMemberAccess=false from fonticon_fa6 import FA6S from qtpy.QtCore import QFile, QFileInfo, QSaveFile, Qt, QTextStream @@ -204,7 +205,7 @@ def load_file(self, fileName: str) -> None: return inf = QTextStream(file) - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) self._text_edit.setPlainText(inf.readAll()) QApplication.restoreOverrideCursor() @@ -213,11 +214,11 @@ def load_file(self, fileName: str) -> None: def save_file(self, fileName: str) -> bool: error = None - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) file = QSaveFile(fileName) if file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text): outf = QTextStream(file) - outf << self._text_edit.toPlainText() + outf << self._text_edit.toPlainText() # pyright: ignore if not file.commit(): reason = file.errorString() error = f"Cannot write file {fileName}:\n{reason}." @@ -255,4 +256,4 @@ def stripped_name(self, fullFileName: str) -> str: qapp.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus) main_win = MainWindow() main_win.show() - qapp.exec_() + qapp.exec() diff --git a/docs/getting_started.md b/docs/getting_started.md index d1adee2..6c71708 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -223,7 +223,7 @@ main.setModelMenuBar(['File']) main.addModelToolBar('File') main.show() -app.exec_() +app.exec() ``` You should now have a QMainWindow with a menu bar and toolbar populated with diff --git a/pyproject.toml b/pyproject.toml index 94b609c..3376232 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ dev = [ "pre-commit-uv>=4", "pyqt6>=6.8.0", "rich>=13.9.4", + "pyright>=1.1.402", ] docs = [ "griffe-fieldz>=0.1.0", @@ -174,6 +175,11 @@ disallow_untyped_defs = false module = ["qtpy.*"] implicit_reexport = true +[tool.pyright] +include = ["src", "demo"] +reportArgumentType = "none" # hard with pydantic casting +venvPath = "." + # https://coverage.readthedocs.io/ [tool.coverage.report] show_missing = true diff --git a/src/app_model/_app.py b/src/app_model/_app.py index 507ff3f..ded4504 100644 --- a/src/app_model/_app.py +++ b/src/app_model/_app.py @@ -7,6 +7,7 @@ from types import MappingProxyType from typing import ( TYPE_CHECKING, + Any, ClassVar, Literal, Optional, @@ -28,8 +29,10 @@ ) if TYPE_CHECKING: + from typing import Callable + from .expressions import Expr - from .registries._register import CommandCallable, CommandDecorator + from .registries._register import CommandDecorator from .types import ( DisposeCallable, IconOrDict, @@ -231,7 +234,7 @@ def register_action( action: str, title: str, *, - callback: CommandCallable, + callback: Callable[..., Any], category: str | None = ..., tooltip: str | None = ..., icon: IconOrDict | None = ..., @@ -246,7 +249,7 @@ def register_action( action: str | Action, title: str | None = None, *, - callback: CommandCallable | None = None, + callback: Callable[..., Any] | None = None, category: str | None = None, tooltip: str | None = None, icon: IconOrDict | None = None, diff --git a/src/app_model/backends/qt/_qaction.py b/src/app_model/backends/qt/_qaction.py index fc4f37d..9dd5a65 100644 --- a/src/app_model/backends/qt/_qaction.py +++ b/src/app_model/backends/qt/_qaction.py @@ -137,9 +137,7 @@ class QMenuItemAction(QCommandRuleAction): Optional parent widget, by default None """ - _cache: ClassVar[WeakValueDictionary[tuple[int, int], QMenuItemAction]] = ( - WeakValueDictionary() - ) + _cache: ClassVar[WeakValueDictionary[tuple[int, int], Self]] = WeakValueDictionary() def __init__( self, diff --git a/src/app_model/backends/qt/_qkeymap.py b/src/app_model/backends/qt/_qkeymap.py index 5d8638c..bcaf0db 100644 --- a/src/app_model/backends/qt/_qkeymap.py +++ b/src/app_model/backends/qt/_qkeymap.py @@ -1,11 +1,13 @@ # mypy: disable-error-code="operator" +# pyright: reportOperatorIssue=false + from __future__ import annotations import operator from functools import reduce from typing import TYPE_CHECKING -from qtpy import API, QT_VERSION +from qtpy import API, QT_VERSION # pyright: ignore[reportAttributeAccessIssue] from qtpy.QtCore import QCoreApplication, Qt from qtpy.QtGui import QKeySequence @@ -239,7 +241,7 @@ def __init__(self, kb: KeyBinding) -> None: KEY_FROM_QT: MutableMapping[Qt.Key, KeyCode | KeyCombo] = { - v.toCombined() if hasattr(v, "toCombined") else int(v): k + v.toCombined() if hasattr(v, "toCombined") else int(v): k # pyright: ignore for k, v in KEY_TO_QT.items() if k } diff --git a/src/app_model/expressions/_expressions.py b/src/app_model/expressions/_expressions.py index 8ec825e..a285659 100644 --- a/src/app_model/expressions/_expressions.py +++ b/src/app_model/expressions/_expressions.py @@ -414,7 +414,7 @@ class Constant(Expr[V], ast.Constant): The kind of constant. This is used to provide type hints when """ - value: V + value: V # pyright: ignore[reportIncompatibleVariableOverride] def __init__( self, value: V, kind: str | None = None, **kwargs: Unpack[_Attributes] diff --git a/src/app_model/registries/_menus_reg.py b/src/app_model/registries/_menus_reg.py index b092933..79c6205 100644 --- a/src/app_model/registries/_menus_reg.py +++ b/src/app_model/registries/_menus_reg.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Final +from typing import TYPE_CHECKING, Any, Callable, Final, cast from psygnal import Signal @@ -64,7 +64,7 @@ def _dispose() -> None: return _dispose def append_menu_items( - self, items: Iterable[tuple[MenuId, MenuOrSubmenu]] + self, items: Iterable[tuple[MenuId, MenuOrSubmenu | dict]] ) -> DisposeCallable: """Append menu items to the registry. @@ -81,7 +81,7 @@ def append_menu_items( changed_ids: set[str] = set() disposers: list[Callable[[], None]] = [] for menu_id, item in items: - item = MenuItem._validate(item) # type: ignore + item = cast("MenuOrSubmenu", MenuItem._validate(item)) menu_dict = self._menu_items.setdefault(menu_id, {}) menu_dict[item] = None changed_ids.add(menu_id) diff --git a/src/app_model/registries/_register.py b/src/app_model/registries/_register.py index 6186495..2c92648 100644 --- a/src/app_model/registries/_register.py +++ b/src/app_model/registries/_register.py @@ -49,7 +49,7 @@ def register_action( id_or_action: str, title: str, *, - callback: CommandCallable, + callback: Callable[..., Any], category: str | None = ..., tooltip: str | None = ..., icon: IconOrDict | None = ..., @@ -65,7 +65,7 @@ def register_action( id_or_action: str | Action, title: str | None = None, *, - callback: CommandCallable | None = None, + callback: Callable[..., Any] | None = None, category: str | None = None, tooltip: str | None = None, icon: IconOrDict | None = None, diff --git a/src/app_model/types/__init__.py b/src/app_model/types/__init__.py index 72acf38..56a1a57 100644 --- a/src/app_model/types/__init__.py +++ b/src/app_model/types/__init__.py @@ -13,6 +13,7 @@ KeyCode, KeyCombo, KeyMod, + ScanCode, SimpleKeyBinding, StandardKeyBinding, ) diff --git a/src/app_model/types/_action.py b/src/app_model/types/_action.py index ce5ee6d..669e099 100644 --- a/src/app_model/types/_action.py +++ b/src/app_model/types/_action.py @@ -40,17 +40,17 @@ class Action(CommandRule, Generic[P, R]): "(e.g. `my_package.a_module:some_function`)", ) menus: Optional[list[MenuRule]] = Field( - None, + default=None, description="(Optional) Menus to which this action should be added. Note that " "menu items in the sequence may be supplied as a plain string, which will " "be converted to a `MenuRule` with the string as the `id` field.", ) keybindings: Optional[list[KeyBindingRule]] = Field( - None, + default=None, description="(Optional) Default keybinding(s) that will trigger this command.", ) palette: bool = Field( - True, + default=True, description="Whether to add this command to the global Command Palette " "during registration.", ) diff --git a/src/app_model/types/_command_rule.py b/src/app_model/types/_command_rule.py index 0bd465f..df06d3e 100644 --- a/src/app_model/types/_command_rule.py +++ b/src/app_model/types/_command_rule.py @@ -12,12 +12,12 @@ class ToggleRule(_BaseModel): """More detailed description of a toggle rule.""" condition: Optional[expressions.Expr] = Field( - None, + default=None, description="(Optional) Condition under which the command should appear " "checked/toggled in any GUI representation (like a menu or button).", ) get_current: Optional[Callable[[], bool]] = Field( - None, + default=None, description="Function that returns the current state of the toggle.", ) @@ -38,20 +38,20 @@ class CommandRule(_BaseModel): description="Title by which the command is represented in the UI.", ) category: Optional[str] = Field( - None, + default=None, description="(Optional) Category string by which the command may be grouped " "in the UI", ) tooltip: Optional[str] = Field( - None, description="(Optional) Tooltip to show when hovered." + default=None, description="(Optional) Tooltip to show when hovered." ) status_tip: Optional[str] = Field( - None, + default=None, description="(Optional) Help message to show in the status bar when a " "button representing this command is hovered (for backends that support it).", ) icon: Optional[Icon] = Field( - None, + default=None, description="(Optional) Icon used to represent this command, e.g. on buttons " "or in menus. These may be [iconify keys](https://icon-sets.iconify.design), " "such as `fa6-solid:arrow-down`, or " @@ -62,24 +62,24 @@ class CommandRule(_BaseModel): "`file:///` (three slashes)", ) icon_visible_in_menu: bool = Field( - True, + default=True, description="Whether to show the icon in menus (for backends that support it). " "If `False`, only the title will be shown. By default, `True`.", ) enablement: Optional[expressions.Expr] = Field( - None, + default=None, description="(Optional) Condition which must be true to enable the command in " "the UI (menu and keybindings). Does not prevent executing the command by " "other means, like the `execute_command` API.", ) short_title: Optional[str] = Field( - None, + default=None, description="(Optional) Short title by which the command is represented in " "the UI. Menus pick either `title` or `short_title` depending on the context " "in which they show commands.", ) toggled: Union[ToggleRule, expressions.Expr, None] = Field( - None, + default=None, description="(Optional) Condition under which the command should appear " "checked/toggled in any GUI representation (like a menu or button).", ) diff --git a/src/app_model/types/_icon.py b/src/app_model/types/_icon.py index 1dc578f..a416728 100644 --- a/src/app_model/types/_icon.py +++ b/src/app_model/types/_icon.py @@ -14,7 +14,7 @@ class Icon(_BaseModel): """ dark: Optional[str] = Field( - None, + default=None, description="Icon path when a dark theme is used. These may be " "[iconify keys](https://icon-sets.iconify.design), such as " "`fa6-solid:arrow-down`, or " @@ -22,7 +22,7 @@ class Icon(_BaseModel): " keys, such as `fa6s.arrow_down`", ) light: Optional[str] = Field( - None, + default=None, description="Icon path when a light theme is used. These may be " "[iconify keys](https://icon-sets.iconify.design), such as " "`fa6-solid:arrow-down`, or " diff --git a/src/app_model/types/_keybinding_rule.py b/src/app_model/types/_keybinding_rule.py index a184f79..f19d3de 100644 --- a/src/app_model/types/_keybinding_rule.py +++ b/src/app_model/types/_keybinding_rule.py @@ -29,28 +29,28 @@ class KeyBindingRule(_BaseModel): """ primary: Optional[KeyEncoding] = Field( - None, description="(Optional) Key combo, (e.g. Ctrl+O)." + default=None, description="(Optional) Key combo, (e.g. Ctrl+O)." ) win: Optional[KeyEncoding] = Field( - None, description="(Optional) Windows specific key combo." + default=None, description="(Optional) Windows specific key combo." ) mac: Optional[KeyEncoding] = Field( - None, description="(Optional) MacOS specific key combo." + default=None, description="(Optional) MacOS specific key combo." ) linux: Optional[KeyEncoding] = Field( - None, description="(Optional) Linux specific key combo." + default=None, description="(Optional) Linux specific key combo." ) when: Optional[expressions.Expr] = Field( - None, + default=None, description="(Optional) Condition when the keybingding is active.", ) weight: int = Field( - 0, + default=0, description="Internal weight used to sort keybindings. " "This is not part of the plugin schema", ) source: KeyBindingSource = Field( - KeyBindingSource.APP, + default=KeyBindingSource.APP, description="Who registered the keybinding. Used to sort keybindings.", ) diff --git a/src/app_model/types/_keys/_key_codes.py b/src/app_model/types/_keys/_key_codes.py index d0f6ecc..7e6c3cf 100644 --- a/src/app_model/types/_keys/_key_codes.py +++ b/src/app_model/types/_keys/_key_codes.py @@ -750,7 +750,7 @@ def __or__(self, other: KeyCode) -> "KeyCombo": ... @overload def __or__(self, other: int) -> int: ... - def __or__( + def __or__( # pyright: ignore[reportIncompatibleMethodOverride] self, other: Union["KeyMod", KeyCode, int] ) -> Union["KeyMod", "KeyCombo", int]: if isinstance(other, self.__class__): diff --git a/src/app_model/types/_menu_rule.py b/src/app_model/types/_menu_rule.py index 535deb4..5ec9f88 100644 --- a/src/app_model/types/_menu_rule.py +++ b/src/app_model/types/_menu_rule.py @@ -20,18 +20,18 @@ class MenuItemBase(_BaseModel): """Data representing where and when a menu item should be shown.""" when: Optional[expressions.Expr] = Field( - None, + default=None, description="(Optional) Condition which must be true to show the item.", ) group: Optional[str] = Field( - None, + default=None, description="(Optional) Menu group to which this item should be added. Menu " "groups are sortable strings (like `'1_cutandpaste'`). 'navigation' is a " "special group that always appears at the top of a menu. If not provided, " "the item is added in the last group of the menu.", ) order: Optional[float] = Field( - None, + default=None, description="(Optional) Order of the item *within* its group. Note, order is " "not part of the plugin schema, plugins may provide it using the group key " "and the syntax 'group@order'. If not provided, items are sorted by title.", @@ -90,7 +90,7 @@ class MenuItem(MenuItemBase): description="CommandRule to execute when this menu item is selected.", ) alt: Optional[CommandRule] = Field( - None, + default=None, description="(Optional) Alternate command to execute when this menu item is " "selected, (e.g. when the Alt-key is held when opening the menu)", ) @@ -108,7 +108,7 @@ class SubmenuItem(MenuItemBase): submenu: str = Field(..., description="Menu to insert as a submenu.") title: str = Field(..., description="Title of this submenu, shown in the UI.") icon: Optional[Icon] = Field( - None, + default=None, description="(Optional) Icon used to represent this submenu. " "These may be [iconify keys](https://icon-sets.iconify.design), " "such as `fa6-solid:arrow-down`, or " @@ -116,7 +116,7 @@ class SubmenuItem(MenuItemBase): " keys, such as `fa6s.arrow_down`", ) enablement: Optional[expressions.Expr] = Field( - None, + default=None, description="(Optional) Condition which must be true to enable the submenu. " "Disabled submenus appear grayed out in the UI, and cannot be selected. By " "default, submenus are enabled.", diff --git a/tests/test_qt/test_demos.py b/tests/test_qt/test_demos.py index 45a3fa0..7fa13ae 100644 --- a/tests/test_qt/test_demos.py +++ b/tests/test_qt/test_demos.py @@ -9,5 +9,5 @@ @pytest.mark.parametrize("fname", ["qapplication.py", "model_app.py", "multi_file"]) def test_qapp(qapp, fname, monkeypatch) -> None: - monkeypatch.setattr(QApplication, "exec_", lambda *a, **k: None) + monkeypatch.setattr(QApplication, "exec", lambda *a, **k: None) runpy.run_path(str(DEMO / fname), run_name="__main__") diff --git a/tests/test_qt/test_qkeymap.py b/tests/test_qt/test_qkeymap.py index e088be1..2afaa2d 100644 --- a/tests/test_qt/test_qkeymap.py +++ b/tests/test_qt/test_qkeymap.py @@ -1,3 +1,5 @@ +# mypy: disable-error-code="operator" +# pyright: reportOperatorIssue=false from unittest.mock import patch from qtpy.QtCore import Qt diff --git a/tests/test_registries.py b/tests/test_registries.py index 98c39ab..a011fa3 100644 --- a/tests/test_registries.py +++ b/tests/test_registries.py @@ -1,3 +1,4 @@ +# mypy: disable-error-code="var-annotated" import pytest from app_model.registries import KeyBindingsRegistry, MenusRegistry @@ -13,6 +14,10 @@ ) +def _noop() -> None: + pass + + def test_menus_registry() -> None: reg = MenusRegistry() reg.append_menu_items([("file", {"command": {"id": "file.new", "title": "File"}})]) @@ -33,7 +38,7 @@ def test_register_keybinding_rule_filter_type() -> None: """Check `_filter_keybinding` type checking when setting.""" reg = KeyBindingsRegistry() with pytest.raises(TypeError, match="'filter_keybinding' must be a callable"): - reg.filter_keybinding = "string" + reg.filter_keybinding = "string" # type: ignore def _filter_fun(kb: KeyBinding) -> str: @@ -79,7 +84,7 @@ def test_register_keybinding_rule_filter() -> None: ), ], ) -def test_register_action_keybindings_filter(kb, msg) -> None: +def test_register_action_keybindings_filter(kb: list[dict], msg: str) -> None: """Check `filter_keybinding` in `register_action_keybindings`.""" reg = KeyBindingsRegistry() reg.filter_keybinding = _filter_fun @@ -87,7 +92,7 @@ def test_register_action_keybindings_filter(kb, msg) -> None: action = Action( id="cmd_id1", title="title1", - callback=lambda: None, + callback=_noop, keybindings=kb, ) if msg: @@ -119,14 +124,14 @@ def test_register_action_keybindings_filter(kb, msg) -> None: ), ], ) -def test_register_action_keybindings_priorization(kb1, kb2, kb3) -> None: +def test_register_action_keybindings_priorization(kb1: str, kb2: str, kb3: str) -> None: """Check `get_context_prioritized_keybinding`.""" reg = KeyBindingsRegistry() action1 = Action( id="cmd_id1", title="title1", - callback=lambda: None, + callback=_noop, keybindings=kb1, ) reg.register_action_keybindings(action1) @@ -134,7 +139,7 @@ def test_register_action_keybindings_priorization(kb1, kb2, kb3) -> None: action2 = Action( id="cmd_id2", title="title2", - callback=lambda: None, + callback=_noop, keybindings=kb2, ) reg.register_action_keybindings(action2) @@ -142,7 +147,7 @@ def test_register_action_keybindings_priorization(kb1, kb2, kb3) -> None: action3 = Action( id="cmd_id3", title="title3", - callback=lambda: None, + callback=_noop, keybindings=kb3, ) reg.register_action_keybindings(action3) @@ -244,7 +249,9 @@ def test_register_action_keybindings_priorization(kb1, kb2, kb3) -> None: ), ], ) -def test_registered_keybinding_comparison(kb1, kb2, gt, lt, eq) -> None: +def test_registered_keybinding_comparison( + kb1: dict, kb2: dict, gt: bool, lt: bool, eq: bool +) -> None: rkb1 = _RegisteredKeyBinding( keybinding=kb1["primary"], command_id=kb1["command_id"],