diff --git a/demo/model_app.py b/demo/model_app.py index 3dffd4a..1a5eb67 100644 --- a/demo/model_app.py +++ b/demo/model_app.py @@ -9,7 +9,7 @@ class MainWindow(QModelMainWindow): - def __init__(self, app: Application): + def __init__(self, app: Application) -> None: super().__init__(app) self._cur_file: str = "" diff --git a/demo/multi_file/functions.py b/demo/multi_file/functions.py index e04692d..0e83201 100644 --- a/demo/multi_file/functions.py +++ b/demo/multi_file/functions.py @@ -1,31 +1,31 @@ from qtpy.QtWidgets import QApplication, QFileDialog -def open_file(): +def open_file() -> None: name, _ = QFileDialog.getOpenFileName() print("Open file:", name) -def close(): +def close() -> None: QApplication.activeWindow().close() print("close") -def undo(): +def undo() -> None: print("undo") -def redo(): +def redo() -> None: print("redo") -def cut(): +def cut() -> None: print("cut") -def copy(): +def copy() -> None: print("copy") -def paste(): +def paste() -> None: print("paste") diff --git a/demo/qapplication.py b/demo/qapplication.py index 6050f8c..9989a36 100644 --- a/demo/qapplication.py +++ b/demo/qapplication.py @@ -17,7 +17,7 @@ class MainWindow(QMainWindow): - def __init__(self): + def __init__(self) -> None: super().__init__() self._cur_file = "" @@ -32,28 +32,28 @@ def __init__(self): self.set_current_file("") - def new_file(self): + def new_file(self) -> None: if self.maybe_save(): self._text_edit.clear() self.set_current_file("") - def open(self): + def open(self) -> None: if self.maybe_save(): fileName, filtr = QFileDialog.getOpenFileName(self) if fileName: self.load_file(fileName) - def save(self): + def save(self) -> bool: return self.save_file(self._cur_file) if self._cur_file else self.save_as() - def save_as(self): + def save_as(self) -> bool: fileName, filtr = QFileDialog.getSaveFileName(self) if fileName: return self.save_file(fileName) return False - def about(self): + def about(self) -> None: QMessageBox.about( self, "About Application", @@ -62,7 +62,7 @@ def about(self): "toolbars, and a status bar.", ) - def create_actions(self): + def create_actions(self) -> None: self._new_act = QAction( fonticon.icon(FA6S.file_circle_plus), "&New", @@ -145,7 +145,7 @@ def create_actions(self): self._text_edit.copyAvailable.connect(self._cut_act.setEnabled) self._text_edit.copyAvailable.connect(self._copy_act.setEnabled) - def create_menus(self): + def create_menus(self) -> None: self._file_menu = self.menuBar().addMenu("&File") self._file_menu.addAction(self._new_act) self._file_menu.addAction(self._open_act) @@ -164,7 +164,7 @@ def create_menus(self): self._help_menu = self.menuBar().addMenu("&Help") self._help_menu.addAction(self._about_act) - def create_tool_bars(self): + def create_tool_bars(self) -> None: self._file_tool_bar = self.addToolBar("File") self._file_tool_bar.addAction(self._new_act) self._file_tool_bar.addAction(self._open_act) @@ -175,10 +175,10 @@ def create_tool_bars(self): self._edit_tool_bar.addAction(self._copy_act) self._edit_tool_bar.addAction(self._paste_act) - def create_status_bar(self): + def create_status_bar(self) -> None: self.statusBar().showMessage("Ready") - def maybe_save(self): + def maybe_save(self) -> bool: if self._text_edit.document().isModified(): ret = QMessageBox.warning( self, @@ -194,7 +194,7 @@ def maybe_save(self): return False return True - def load_file(self, fileName): + def load_file(self, fileName: str) -> None: file = QFile(fileName) if not file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): reason = file.errorString() @@ -211,7 +211,7 @@ def load_file(self, fileName): self.set_current_file(fileName) self.statusBar().showMessage("File loaded", 2000) - def save_file(self, fileName): + def save_file(self, fileName: str) -> bool: error = None QApplication.setOverrideCursor(Qt.WaitCursor) file = QSaveFile(fileName) @@ -234,7 +234,7 @@ def save_file(self, fileName): self.statusBar().showMessage("File saved", 2000) return True - def set_current_file(self, fileName: str): + def set_current_file(self, fileName: str) -> None: self._cur_file = fileName self._text_edit.document().setModified(False) self.setWindowModified(False) @@ -246,7 +246,7 @@ def set_current_file(self, fileName: str): self.setWindowTitle(f"{shown_name}[*] - Application") - def stripped_name(self, fullFileName: str): + def stripped_name(self, fullFileName: str) -> str: return QFileInfo(fullFileName).fileName() diff --git a/pyproject.toml b/pyproject.toml index 9ea6754..94b609c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,13 +117,14 @@ select = [ "C4", # flake8-comprehensions "B", # flake8-bugbear "A001", # flake8-builtins - # "ANN", # flake8-annotations - "RUF", # ruff-specific rules - "TC", # flake8-type-checking - "TID", # flake8-tidy-imports + "ANN", # flake8-annotations + "RUF", # ruff-specific rules + "TC", # flake8-type-checking + "TID", # flake8-tidy-imports ] ignore = [ - "D401", # First line should be in imperative mood (remove to opt in) + "D401", # First line should be in imperative mood (remove to opt in) + "ANN401", # Disallow typing.Any ] [tool.ruff.lint.pyupgrade] @@ -131,7 +132,7 @@ ignore = [ keep-runtime-typing = true [tool.ruff.lint.per-file-ignores] -"tests/*.py" = ["D", "E501"] +"tests/*.py" = ["D", "E501", "ANN"] "demo/*" = ["D"] "docs/*" = ["D"] "src/app_model/_registries.py" = ["D10"] diff --git a/src/app_model/backends/qt/_qaction.py b/src/app_model/backends/qt/_qaction.py index 862ac34..fc4f37d 100644 --- a/src/app_model/backends/qt/_qaction.py +++ b/src/app_model/backends/qt/_qaction.py @@ -41,7 +41,7 @@ def __init__( command_id: str, app: Application | str, parent: QObject | None = None, - ): + ) -> None: super().__init__(parent) self._app = Application.get_or_create(app) if isinstance(app, str) else app self._command_id = command_id @@ -81,7 +81,7 @@ def __init__( parent: QObject | None = None, *, use_short_title: bool = False, - ): + ) -> None: super().__init__(command_rule.id, app, parent) self._cmd_rule = command_rule if use_short_title and command_rule.short_title: @@ -146,7 +146,7 @@ def __init__( menu_item: MenuItem, app: Application | str, parent: QObject | None = None, - ): + ) -> None: super().__init__(menu_item.command, app, parent) self._menu_item = menu_item with contextlib.suppress(NameError): diff --git a/src/app_model/backends/qt/_qmenu.py b/src/app_model/backends/qt/_qmenu.py index c36ddce..bfa8a64 100644 --- a/src/app_model/backends/qt/_qmenu.py +++ b/src/app_model/backends/qt/_qmenu.py @@ -42,7 +42,7 @@ def __init__( app: Application | str, title: str | None = None, parent: QWidget | None = None, - ): + ) -> None: QMenu.__init__(self, parent) # NOTE: code duplication with QModelToolBar, but Qt mixins and multiple @@ -132,7 +132,7 @@ def __init__( submenu: SubmenuItem, app: Application | str, parent: QWidget | None = None, - ): + ) -> None: assert isinstance(submenu, SubmenuItem), f"Expected str, got {type(submenu)!r}" self._submenu = submenu super().__init__( diff --git a/src/app_model/expressions/_expressions.py b/src/app_model/expressions/_expressions.py index 7de3316..8ec825e 100644 --- a/src/app_model/expressions/_expressions.py +++ b/src/app_model/expressions/_expressions.py @@ -476,7 +476,7 @@ def __init__( op: ast.boolop, values: Sequence[ConstType | Expr], **kwargs: Unpack[_Attributes], - ): + ) -> None: super().__init__(op, [Expr._cast(v) for v in values], **kwargs) diff --git a/src/app_model/types/_keys/_keybindings.py b/src/app_model/types/_keys/_keybindings.py index c958046..bcd6fb3 100644 --- a/src/app_model/types/_keys/_keybindings.py +++ b/src/app_model/types/_keys/_keybindings.py @@ -188,7 +188,7 @@ class KeyBinding: parts: list[SimpleKeyBinding] = Field(..., **MIN1) # type: ignore - def __init__(self, *, parts: list[SimpleKeyBinding]): + def __init__(self, *, parts: list[SimpleKeyBinding]) -> None: self.parts = parts def __str__(self) -> str: diff --git a/tests/test_actions.py b/tests/test_actions.py index 356136d..a992e42 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -41,14 +41,14 @@ def test_register_action_decorator( if mode == "decorator": @register_action(app=app, id_or_action=cmd_id, **kwargs) - def f1(): + def f1() -> str: return "hi" assert f1() == "hi" # decorator returns the function else: - def f2(): + def f2() -> str: return "hi" if mode == "str": @@ -99,14 +99,14 @@ def f2(): assert not list(app.menus) -def test_errors(simple_app: Application): +def test_errors(simple_app: Application) -> None: with pytest.raises(ValueError, match="'title' is required"): simple_app.register_action("cmd_id") # type: ignore with pytest.raises(TypeError, match="must be a string or an Action"): simple_app.register_action(None) # type: ignore -def test_register_multiple_actions(simple_app: Application): +def test_register_multiple_actions(simple_app: Application) -> None: actions: list[Action] = [ Action(id="cmd_id1", title="title1", callback=lambda: None), Action(id="cmd_id2", title="title2", callback=lambda: None), diff --git a/tests/test_context/test_context.py b/tests/test_context/test_context.py index 2a40fe3..c20f798 100644 --- a/tests/test_context/test_context.py +++ b/tests/test_context/test_context.py @@ -7,7 +7,7 @@ from app_model.expressions._context import _OBJ_TO_CONTEXT -def test_create_context(): +def test_create_context() -> None: """You can create a context for any object""" class T: ... @@ -30,7 +30,7 @@ class T: ... create_context(T(), root={}) # type: ignore -def test_create_and_get_scoped_contexts(): +def test_create_and_get_scoped_contexts() -> None: """Test that objects created in the stack of another contexted object. likely the most common way that this API will be used: @@ -61,7 +61,7 @@ def __init__(self) -> None: assert len(_OBJ_TO_CONTEXT) == before -def test_context_events(): +def test_context_events() -> None: """Changing context keys emits an event""" mock = Mock() root = Context() diff --git a/tests/test_context/test_expressions.py b/tests/test_context/test_expressions.py index 408eea1..8669e2a 100644 --- a/tests/test_context/test_expressions.py +++ b/tests/test_context/test_expressions.py @@ -7,7 +7,7 @@ from app_model.expressions._expressions import _OPS, _iter_names -def test_names(): +def test_names() -> None: expr = Name("n", bound=int) assert expr.eval({"n": 5}) == 5 @@ -18,7 +18,7 @@ def test_names(): assert repr(Name("n")) == "Expr.parse('n')" -def test_constants(): +def test_constants() -> None: assert Constant(1).eval() == 1 assert Constant(3.14).eval() == 3.14 @@ -40,7 +40,7 @@ def test_constants(): Constant((1, 2)) # type: ignore -def test_bool_ops(): +def test_bool_ops() -> None: n1 = Name[bool]("n1") true = Constant(True) false = Constant(False) @@ -68,7 +68,7 @@ def test_bool_ops(): assert not isinstance(Constant(1) and 1, Expr) -def test_bin_ops(): +def test_bin_ops() -> None: one = Constant(1) assert (one + 1).eval() == 2 assert (one - 1).eval() == 0 @@ -86,7 +86,7 @@ def test_bin_ops(): assert (Constant(16).bitor(4)).eval() == 20 -def test_unary_ops(): +def test_unary_ops() -> None: assert Constant(1).eval() == 1 assert (+Constant(1)).eval() == 1 assert (-Constant(1)).eval() == -1 @@ -94,7 +94,7 @@ def test_unary_ops(): assert (~Constant(True)).eval() is False -def test_comparison(): +def test_comparison() -> None: n = Name[int]("n") n2 = Name[int]("n2") one = Constant(1) @@ -140,7 +140,7 @@ def test_comparison(): assert repr(n > n2) == "Expr.parse('n > n2')" -def test_iter_names(): +def test_iter_names() -> None: expr = "a if b in c else d > e" a = parse_expression(expr) assert a is parse_expression(a) @@ -201,18 +201,18 @@ def test_iter_names(): @pytest.mark.parametrize("expr", GOOD_EXPRESSIONS) -def test_serdes(expr): +def test_serdes(expr) -> None: assert str(parse_expression(expr)) == expr assert repr(parse_expression(expr)) # smoke test @pytest.mark.parametrize("expr", BAD_EXPRESSIONS) -def test_bad_serdes(expr): +def test_bad_serdes(expr) -> None: with pytest.raises(SyntaxError): parse_expression(expr) -def test_deepcopy_expression(): +def test_deepcopy_expression() -> None: deepcopy(parse_expression("1")) deepcopy(parse_expression("1 > 2")) deepcopy(parse_expression("1 & 2")) @@ -222,7 +222,7 @@ def test_deepcopy_expression(): deepcopy(parse_expression("2 if x else 3")) -def test_safe_eval(): +def test_safe_eval() -> None: expr = "7 > x if x > 2 else 3" assert safe_eval(expr, {"x": 3}) is True assert safe_eval(expr, {"x": 10}) is False @@ -237,12 +237,12 @@ def test_safe_eval(): safe_eval("func(x)") -def test_eval_kwargs(): +def test_eval_kwargs() -> None: expr = parse_expression("a + b") assert expr.eval(a=1, b=2) == 3 assert expr.eval({"a": 2}, b=2) == 4 @pytest.mark.parametrize("expr", GOOD_EXPRESSIONS) -def test_hash(expr): +def test_hash(expr) -> None: assert isinstance(hash(parse_expression(expr)), int) diff --git a/tests/test_key_codes.py b/tests/test_key_codes.py index dcfa4ff..0677519 100644 --- a/tests/test_key_codes.py +++ b/tests/test_key_codes.py @@ -7,7 +7,7 @@ from app_model.types._keys._key_codes import keycode_to_os_name, keycode_to_os_symbol -def test_key_codes(): +def test_key_codes() -> None: for key in KeyCode: assert key == KeyCode.from_string(str(key)) @@ -42,12 +42,12 @@ def test_key_codes_to_os( assert getattr(key, os_method)(os) == key_map_func(key, os) -def test_scan_codes(): +def test_scan_codes() -> None: for scan in ScanCode: assert scan == ScanCode.from_string(str(scan)), scan -def test_key_combo(): +def test_key_combo() -> None: """KeyCombo is an integer combination of one or more KeyMod and KeyCode.""" combo = KeyMod.Shift | KeyMod.Alt | KeyCode.KeyK assert repr(combo) == "" @@ -55,7 +55,7 @@ def test_key_combo(): assert kb == SimpleKeyBinding(shift=True, alt=True, key=KeyCode.KeyK) -def test_key_chord(): +def test_key_chord() -> None: """KeyChord is an integer combination of two KeyCombos, KeyCodes, or integers.""" chord = KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyM) assert int(chord) == 1968156 diff --git a/tests/test_qt/test_demos.py b/tests/test_qt/test_demos.py index 0c1960d..45a3fa0 100644 --- a/tests/test_qt/test_demos.py +++ b/tests/test_qt/test_demos.py @@ -8,6 +8,6 @@ @pytest.mark.parametrize("fname", ["qapplication.py", "model_app.py", "multi_file"]) -def test_qapp(qapp, fname, monkeypatch): +def test_qapp(qapp, fname, monkeypatch) -> 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_qkeybindingedit.py b/tests/test_qt/test_qkeybindingedit.py index 749d3c0..e6163c6 100644 --- a/tests/test_qt/test_qkeybindingedit.py +++ b/tests/test_qt/test_qkeybindingedit.py @@ -4,7 +4,7 @@ from app_model.types import KeyBinding, KeyCode, KeyMod -def test_qkeysequenceedit(qtbot): +def test_qkeysequenceedit(qtbot) -> None: edit = QModelKeyBindingEdit() qtbot.addWidget(edit) assert edit.keyBinding() is None diff --git a/tests/test_qt/test_qmainwindow.py b/tests/test_qt/test_qmainwindow.py index 6ee2db7..0868595 100644 --- a/tests/test_qt/test_qmainwindow.py +++ b/tests/test_qt/test_qmainwindow.py @@ -8,7 +8,7 @@ from ..conftest import FullApp # noqa: TID252 -def test_qmodel_main_window(qtbot, full_app: "FullApp"): +def test_qmodel_main_window(qtbot, full_app: "FullApp") -> None: win = QModelMainWindow(full_app) qtbot.addWidget(win) diff --git a/tests/test_registries.py b/tests/test_registries.py index 10a2ea9..98c39ab 100644 --- a/tests/test_registries.py +++ b/tests/test_registries.py @@ -244,7 +244,7 @@ def test_register_action_keybindings_priorization(kb1, kb2, kb3) -> None: ), ], ) -def test_registered_keybinding_comparison(kb1, kb2, gt, lt, eq): +def test_registered_keybinding_comparison(kb1, kb2, gt, lt, eq) -> None: rkb1 = _RegisteredKeyBinding( keybinding=kb1["primary"], command_id=kb1["command_id"], diff --git a/tests/test_types.py b/tests/test_types.py index b8ed9d7..64fdd41 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -4,13 +4,13 @@ from app_model.types import Action, Icon -def test_icon_validate(): +def test_icon_validate() -> None: assert Icon._validate('"fa6s.arrow_down"') == Icon( dark='"fa6s.arrow_down"', light='"fa6s.arrow_down"' ) -def test_action_validation(): +def test_action_validation() -> None: with pytest.raises(ValidationError, match="'s!adf' is not a valid python_name"): Action(id="test", title="test", callback="s!adf")