PySide2 typing stubs are available
-
Hi,
If you are into using type annotations for your python programs, you have certainly been disappointed by the typing stubs delivered by Qt. The official version fails to detect many errors and reports many errors for perfectly valid code.
But things are changing: I have started a PySide2-stubs package. The first version is already available at pypi (https://pypi.org/project/PySide2-stubs/ ).
The package provides updated typing information for all of PySide2. It fixes some very basic typing issues:
- fix Signal with method emit()
- fix qVersion() returning string, not bytes
- fix QMessageBox.warning, information, critical, question, about, aboutQt to accept None as parent argument
- fix QAction.setShortcut() to accept string as argument
- fix QTreeWidgetItem comparison with <
- fix QTimer.timeout undeclared signal
- fix QLineEdit.setText() to accept None
The current version is already useful but still has many wrong types declared (all QFlags are declared with a wrong type for example). I'll continue working on it until I get something reasonably usable.
Don't hesitate to join the effort if you like typed python code.
Have a nice day,
Philippe
-
I have just uploaded a new version of the stubs. They are now quite good : they will help any project to correctly use the PySide2/Qt5 API and provide full static typing capabilities with mypy.
Don't hesitate to try them at https://pypi.org/project/PySide2-stubs/
Have a nice day,
Philippe
-
Let's see an example in practice.
See what happens if I run mypy on one of my PySide2 project without extra package:
(.env-pyside) c:\work\Multigit\Dev>mypy . src\mg_const.py:88: error: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior src\mg_config.py:108: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_config.py:108: note: Possible overload variants: src\mg_config.py:108: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_config.py:108: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_config.py:117: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_config.py:117: note: Possible overload variants: src\mg_config.py:117: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_config.py:117: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_config.py:129: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_config.py:129: note: Possible overload variants: src\mg_config.py:129: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_config.py:129: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_button_history.py:59: error: "Signal" has no attribute "emit" idemia\gui\ui_gitflow_release_tag.py:12: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtGui.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_release_tag.py:13: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtWidgets.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_merge.py:12: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtGui.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_merge.py:13: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtWidgets.Object]", local name has type "Type[PySide2.QtCore.Object]") [...] idemia\gui\ui_gitflow_init.py:12: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtGui.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_init.py:13: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtWidgets.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_init.py:114: error: Argument 1 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes" [...] idemia\gui\ui_gitflow_init.py:124: error: Argument 1 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes" idemia\gui\ui_gitflow_init.py:124: error: Argument 2 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes" [...] idemia\gui\ui_gitflow_create_branch.py:12: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtGui.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_create_branch.py:13: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtWidgets.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_create_branch.py:236: error: Argument 1 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes" idemia\gui\ui_gitflow_create_branch.py:236: error: Argument 2 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes"[...] idemia\gui\ui_gitflow_advance_int_branch.py:12: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtGui.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_advance_int_branch.py:13: error: Incompatible import of "Object" (imported name has type "Type[PySide2.QtWidgets.Object]", local name has type "Type[PySide2.QtCore.Object]") idemia\gui\ui_gitflow_advance_int_branch.py:169: error: Argument 1 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes" idemia\gui\ui_gitflow_advance_int_branch.py:169: error: Argument 2 to "translate" of "QCoreApplication" has incompatible type "str"; expected "bytes" [...] src\mg_tools.py:408: error: "Signal" has no attribute "emit" src\mg_tools.py:502: error: No overload variant of "int" matches argument type "ExitStatus" src\mg_tools.py:502: note: Possible overload variants: src\mg_tools.py:502: note: def int(cls, Union[str, bytes, SupportsInt, SupportsIndex, SupportsTrunc] = ...) -> int src\mg_tools.py:502: note: def int(cls, Union[str, bytes], base: SupportsIndex) -> int src\mg_tools.py:541: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_tools.py:541: note: Possible overload variants: src\mg_tools.py:541: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_tools.py:541: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:144: error: "Signal" has no attribute "connect" src\mg_repo_info.py:327: error: "Signal" has no attribute "emit" src\mg_repo_info.py:374: error: "Signal" has no attribute "emit" src\mg_repo_info.py:507: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:507: note: Possible overload variants: src\mg_repo_info.py:507: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:507: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:535: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:535: note: Possible overload variants: src\mg_repo_info.py:535: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:535: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:629: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:629: note: Possible overload variants: src\mg_repo_info.py:629: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:629: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:666: error: "Signal" has no attribute "emit" src\mg_repo_info.py:680: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:680: note: Possible overload variants: src\mg_repo_info.py:680: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:680: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:869: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:869: note: Possible overload variants: src\mg_repo_info.py:869: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:869: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:985: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:985: note: Possible overload variants: src\mg_repo_info.py:985: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:985: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_repo_info.py:1033: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_repo_info.py:1033: note: Possible overload variants: src\mg_repo_info.py:1033: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_repo_info.py:1033: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_git_exec_window.py:116: error: "Signal" has no attribute "emit" src\mg_git_exec_window.py:188: error: "Signal" has no attribute "connect" src\mg_git_exec_window.py:392: error: "Signal" has no attribute "connect" src\mg_ensure_info_available.py:30: error: Argument 1 to "setCancelButton" of "QProgressDialog" has incompatible type "None"; expected "QPushButton" src\mg_ensure_info_available.py:68: error: "Signal" has no attribute "emit" src\mg_ensure_info_available.py:137: error: "Signal" has no attribute "emit" src\mg_repo_tree_item.py:39: error: "Signal" has no attribute "connect" src\mg_repo_tree_item.py:40: error: "Signal" has no attribute "connect" src\mg_repo_tree_item.py:41: error: "Signal" has no attribute "connect" src\mg_dialog_utils.py:149: error: "Signal" has no attribute "emit" src\mg_dialog_select_repo.py:160: error: Unused "type: ignore" comment src\mg_dialog_export_mgit.py:40: error: "Signal" has no attribute "connect" src\mg_dialog_git_switch_delete_branch.py:276: error: "Signal" has no attribute "connect" src\mg_dialog_git_revert.py:23: error: "Signal" has no attribute "connect" src\mg_dialog_clone_from_mgit.py:88: error: "Signal" has no attribute "connect" src\mg_dialog_clone_from_mgit.py:89: error: "Signal" has no attribute "connect" src\mg_dialog_clone_from_mgit.py:90: error: "Signal" has no attribute "connect" src\mg_dialog_clone_from_mgit.py:215: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_clone_from_mgit.py:215: note: Possible overload variants: src\mg_dialog_clone_from_mgit.py:215: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_clone_from_mgit.py:215: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_clone_from_mgit.py:221: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_clone_from_mgit.py:221: note: Possible overload variants: src\mg_dialog_clone_from_mgit.py:221: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_clone_from_mgit.py:221: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_clone_from_mgit.py:230: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_clone_from_mgit.py:230: note: Possible overload variants: src\mg_dialog_clone_from_mgit.py:230: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_clone_from_mgit.py:230: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_clone_from_mgit.py:275: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_clone_from_mgit.py:275: note: Possible overload variants: src\mg_dialog_clone_from_mgit.py:275: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_clone_from_mgit.py:275: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_clone_from_mgit.py:280: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_clone_from_mgit.py:280: note: Possible overload variants: src\mg_dialog_clone_from_mgit.py:280: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_clone_from_mgit.py:280: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_apply_mgit_file.py:82: error: "Signal" has no attribute "connect" src\mg_dialog_apply_mgit_file.py:83: error: "Signal" has no attribute "connect" src\mg_dialog_apply_mgit_file.py:197: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_apply_mgit_file.py:197: note: Possible overload variants: src\mg_dialog_apply_mgit_file.py:197: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_apply_mgit_file.py:197: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_apply_mgit_file.py:203: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_apply_mgit_file.py:203: note: Possible overload variants: src\mg_dialog_apply_mgit_file.py:203: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_apply_mgit_file.py:203: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton src\mg_dialog_apply_mgit_file.py:212: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_dialog_apply_mgit_file.py:212: note: Possible overload variants: src\mg_dialog_apply_mgit_file.py:212: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_dialog_apply_mgit_file.py:212: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton idemia\mg_dialog_gitflow_init.py:27: error: "Signal" has no attribute "connect" idemia\mg_dialog_gitflow_advance_int_branch.py:74: error: "Signal" has no attribute "connect" src\mg_repo_tree.py:140: error: Argument 1 to "setShortcut" of "QAction" has incompatible type "str"; expected "QKeySequence" [...] src\mg_repo_tree.py:321: error: Argument 1 to "setShortcut" of "QAction" has incompatible type "str"; expected "QKeySequence" src\mg_repo_tree.py:477: error: Unused "type: ignore" comment src\mg_repo_tree.py:665: error: "Signal" has no attribute "connect" src\mg_dialog_git_tag.py:19: error: Unused "type: ignore" comment src\mg_dialog_git_tag.py:23: error: Unused "type: ignore" comment src\mg_dialog_git_push_tag.py:19: error: Unused "type: ignore" comment src\mg_dialog_git_push_tag.py:22: error: Unused "type: ignore" comment src\mg_dialog_git_push_tag.py:23: error: "Signal" has no attribute "connect" src\mg_dialog_git_commit.py:17: error: Unused "type: ignore" comment src\mg_dialog_git_commit.py:21: error: Unused "type: ignore" comment src\mg_dialog_git_commit.py:24: error: "Signal" has no attribute "connect" src\mg_window.py:148: error: "Signal" has no attribute "connect" src\mg_window.py:558: error: No overload variant of "warning" of "QMessageBox" matches argument types "None", "str", "str" src\mg_window.py:558: note: Possible overload variants: src\mg_window.py:558: note: def warning(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int src\mg_window.py:558: note: def warning(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton multigit.py:34: error: No overload variant of "critical" of "QMessageBox" matches argument types "None", "str", "str" multigit.py:34: note: Possible overload variants: multigit.py:34: note: def critical(parent: QWidget, title: str, text: str, button0: StandardButton, button1: StandardButton) -> int multigit.py:34: note: def critical(parent: QWidget, title: str, text: str, buttons: StandardButtons = ..., defaultButton: StandardButton = ...) -> StandardButton multigit.py:180: error: Unsupported operand types for + ("str" and "bytes") multigit.py:196: error: On Python 3 formatting "b'abc'" with "%s" produces "b'abc'", not "abc"; use "%r" if this is desired behavior Found 310 errors in 30 files (checked 72 source files)
Tons of errors, eventhough my project is working fine. The default stubs are useless for mypy. Let's fix that.
(.env-pyside) c:\work\Multigit\Dev>pip install pyside2-stubs Collecting pyside2-stubs Downloading PySide2_stubs-5.15.2.1.2-py3-none-any.whl (542 kB) |████████████████████████████████| 542 kB 819 kB/s Requirement already satisfied: mypy>=0.940 in c:\work\multigit\dev\.env-pyside\lib\site-packages (from pyside2-stubs) (0.940) Requirement already satisfied: PySide2>=5.11.0 in c:\work\multigit\dev\.env-pyside\lib\site-packages (from pyside2-stubs) (5.15.2.1) Requirement already satisfied: tomli>=1.1.0 in c:\work\multigit\dev\.env-pyside\lib\site-packages (from mypy>=0.940->pyside2-stubs) (2.0.1) Requirement already satisfied: mypy-extensions>=0.4.3 in c:\work\multigit\dev\.env-pyside\lib\site-packages (from mypy>=0.940->pyside2-stubs) (0.4.3) Requirement already satisfied: typing-extensions>=3.10 in c:\work\multigit\dev\.env-pyside\lib\site-packages (from mypy>=0.940->pyside2-stubs) (4.2.0) Requirement already satisfied: shiboken2==5.15.2.1 in c:\work\multigit\dev\.env-pyside\lib\site-packages (from PySide2>=5.11.0->pyside2-stubs) (5.15.2.1) Installing collected packages: pyside2-stubs Successfully installed pyside2-stubs-5.15.2.1.2 WARNING: You are using pip version 21.1.1; however, version 22.2 is available. You should consider upgrading via the 'c:\work\multigit\dev\.env-pyside\scripts\python.exe -m pip install --upgrade pip' command.
Installation is pretty straightforward.
And now:
(.env-pyside) c:\work\Multigit\Dev>mypy . src\mg_repo_tree.py:665: error: Argument 1 to "append" of "list" has incompatible type "bool"; expected "Connection" Found 1 error in 1 file (checked 72 source files) (.env-pyside) c:\work\Multigit\Dev>
Only one error reported. Let's check that! The code looke like :
self.menuCopyConnections = [] # type: List[QMetaObject.Connection] [...] self.menuCopyConnections.append( repoInfo.repo_info_available.connect(local_set_head) )
First I am declaring the type of
menuCopyConnections
to be a list ofQMetaObject.Connection
items. Then I am filling the list with the result of a signal connection call. And mypy tell me that I am filling the list incorrectly withbool
.After verification, this is a pyside2 incompabilitity with Qt5. The
connect()
call returnsbool
instead ofConnection
objects. So my code in this context is buggy, I will get an exception when I try to use the content of the list, assuming it is aConnection
object while it is abool.
Mypy and the correct stubs have helped me to find a bug!
Note: the bug has been reported and the Qt folks were already aware of it because in PySide6, connect() returns a Connection object like in Qt.