Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
12 changes: 11 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
2 changes: 1 addition & 1 deletion demo/keybinding_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
w = QModelKeyBindingEdit()
w.editingFinished.connect(lambda: print(w.keyBinding()))
w.show()
sys.exit(app.exec_())
sys.exit(app.exec())
26 changes: 17 additions & 9 deletions demo/model_app.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -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("")

Expand All @@ -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:
Expand All @@ -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}."
Expand All @@ -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",
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -255,4 +263,4 @@ class CommandId:

app.injection_store.register_provider(lambda: main_win, MainWindow)
main_win.show()
qapp.exec_()
qapp.exec()
2 changes: 1 addition & 1 deletion demo/multi_file/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
qapp = QApplication.instance() or QApplication([])
app = MyApp()
app.show()
qapp.exec_()
qapp.exec()
3 changes: 2 additions & 1 deletion demo/multi_file/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ def open_file() -> None:


def close() -> None:
QApplication.activeWindow().close()
if win := QApplication.activeWindow():
win.close()
print("close")


Expand Down
9 changes: 5 additions & 4 deletions demo/qapplication.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()

Expand All @@ -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}."
Expand Down Expand Up @@ -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()
2 changes: 1 addition & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions src/app_model/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from types import MappingProxyType
from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Literal,
Optional,
Expand All @@ -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,
Expand Down Expand Up @@ -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 = ...,
Expand All @@ -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,
Expand Down
4 changes: 1 addition & 3 deletions src/app_model/backends/qt/_qaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions src/app_model/backends/qt/_qkeymap.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion src/app_model/expressions/_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 3 additions & 3 deletions src/app_model/registries/_menus_reg.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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.

Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/app_model/registries/_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ...,
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/app_model/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
KeyCode,
KeyCombo,
KeyMod,
ScanCode,
SimpleKeyBinding,
StandardKeyBinding,
)
Expand Down
6 changes: 3 additions & 3 deletions src/app_model/types/_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
)
Expand Down
Loading