Skip to content

PR: Add missing kwargs to add_action wrapper#531

Open
beatreichenbach wants to merge 3 commits intospyder-ide:masterfrom
beatreichenbach:add_action-kwargs
Open

PR: Add missing kwargs to add_action wrapper#531
beatreichenbach wants to merge 3 commits intospyder-ide:masterfrom
beatreichenbach:add_action-kwargs

Conversation

@beatreichenbach
Copy link
Copy Markdown
Contributor

This adds the kwargs back to the add_action function and should hopefully fix #472.

However, before proceeding, I would like to add tests for all cases. I'm a bit unsure what cases should be supported. Does this cover everything?

# Case 1 (arg__2 is a callable and arg__3 is kwarg 'shortcut')
arg__1: str
arg__2: object
arg__3: QKeySequence = 0

# Case 2 (arg__3 is a callable and arg__4 is kwarg 'shortcut')
arg__1: QIcon
arg__2: str
arg__3: object
arg__4: QKeySequence = 0 

# Case 3
text: str

# Case 4
icon: QIcon
text: str

# Case 5 (shortcut can be arg or kwarg)
text: str
receiver: QObject
member: str
shortcut: QKeySequence = 0

# Case 6 (shortcut can be arg or kwarg)
icon: QIcon
text: str
receiver: QObject
member: str
shortcut: QKeySequence = 0

# Case 7 (PySide6>=6.3, PyQt6>=6.3)
text: str
shortcut: QKeySequence

# Case 8 (PySide6>=6.3, PyQt6>=6.3)
icon: QIcon
text: str
shortcut: QKeySequence

# Case 9 (PySide6>=6.3, PyQt6>=6.3)
text: str
shortcut: QKeySequence
receiver: QObject
member: str
type: ConnectionType = ConnectionType.AutoConnection

# Case 10 (PySide6>=6.3, PyQt6>=6.3)
icon: QIcon
text: str
shortcut: QKeySequence
receiver: QObject
member: str
type: ConnectionType = ConnectionType.AutoConnection

@beatreichenbach beatreichenbach force-pushed the add_action-kwargs branch 2 times, most recently from 508b392 to 8920bed Compare February 15, 2026 10:07
@coveralls
Copy link
Copy Markdown

Coverage Status

coverage: 90.757% (+0.9%) from 89.834%
when pulling 8920bed on beatreichenbach:add_action-kwargs
into cc853c3 on spyder-ide:master.

@ccordoba12 ccordoba12 requested a review from dalthviz February 15, 2026 19:42
@dalthviz
Copy link
Copy Markdown
Member

Hi @beatreichenbach thank you for giving that issue a check! The summary of possible signatures that you mention makes sense to me 👍 For completness, checking to the different bindings and got the following signatures when checking help(QMenu.addAction) from an interpreter:

  • PyQt5:
addAction(...)
    addAction(self, action: Optional[QAction])
    addAction(self, text: Optional[str]) -> Optional[QAction]
    addAction(self, icon: QIcon, text: Optional[str]) -> Optional[QAction]
    addAction(self, text: Optional[str], slot: PYQT_SLOT, shortcut: Union[QKeySequence, QKeySequence.StandardKey, Optional[str], int] = 0) -> Optional[QAction]
    addAction(self, icon: QIcon, text: Optional[str], slot: PYQT_SLOT, shortcut: Union[QKeySequence, QKeySequence.StandardKey, Optional[str], int] = 0) -> Optional[QAction]
  • PySide2:
addAction(...)
    addAction(self, arg__1: PySide2.QtGui.QIcon, arg__2: str, arg__3: object, arg__4: typing.Optional[PySide2.QtGui.QKeySequence] = None) -> None
    addAction(self, arg__1: PySide2.QtWidgets.QAction) -> None
    addAction(self, arg__1: str, arg__2: object, arg__3: typing.Optional[PySide2.QtGui.QKeySequence] = None) -> None
    addAction(self, icon: PySide2.QtGui.QIcon, text: str) -> PySide2.QtWidgets.QAction
    addAction(self, icon: PySide2.QtGui.QIcon, text: str, receiver: PySide2.QtCore.QObject, member: bytes, shortcut: typing.Optional[PySide2.QtGui.QKeySequence] = None) -> PySide2.QtWidgets.QAction
    addAction(self, text: str) -> PySide2.QtWidgets.QAction
    addAction(self, text: str, receiver: PySide2.QtCore.QObject, member: bytes, shortcut: typing.Optional[PySide2.QtGui.QKeySequence] = None) -> PySide2.QtWidgets.QAction
  • PyQt6:
addAction(...) method of PyQt6.sip.wrappertype instance
    addAction(self, icon: QIcon, text: Optional[str]) -> Optional[QAction]
    addAction(self, icon: QIcon, text: Optional[str], slot: PYQT_SLOT, type: Qt.ConnectionType = Qt.AutoConnection) -> Optional[QAction]
    addAction(self, icon: QIcon, text: Optional[str], shortcut: Union[QKeySequence, QKeySequence.StandardKey, Optional[str], int]) -> Optional[QAction]
    addAction(self, icon: QIcon, text: Optional[str], shortcut: Union[QKeySequence, QKeySequence.StandardKey, Optional[str], int], slot: PYQT_SLOT, type: Qt.ConnectionType = Qt.AutoConnection) -> Optional[QAction]
    addAction(self, text: Optional[str]) -> Optional[QAction]
    addAction(self, text: Optional[str], shortcut: Union[QKeySequence, QKeySequence.StandardKey, Optional[str], int]) -> Optional[QAction]
    addAction(self, text: Optional[str], slot: PYQT_SLOT, type: Qt.ConnectionType = Qt.AutoConnection) -> Optional[QAction]
    addAction(self, text: Optional[str], shortcut: Union[QKeySequence, QKeySequence.StandardKey, Optional[str], int], slot: PYQT_SLOT, type: Qt.ConnectionType = Qt.AutoConnection) -> Optional[QAction]
    addAction(self, action: Optional[QAction])
  • PySide6
addAction(...)
    addAction(self, action: PySide6.QtGui.QAction, /) -> None
    addAction(self, text: str, /) -> PySide6.QtGui.QAction
    addAction(self, text: str, receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview], /, type: PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection)) -> PySide6.QtGui.QAction
    addAction(self, text: str, receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview], shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], /) -> PySide6.QtGui.QAction
    addAction(self, text: str, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], /) -> PySide6.QtGui.QAction
    addAction(self, text: str, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview], /, type: PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection)) -> PySide6.QtGui.QAction
    addAction(self, text: str, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], callable: object, /) -> PySide6.QtGui.QAction
    addAction(self, text: str, callable: object, /) -> PySide6.QtGui.QAction
    addAction(self, text: str, arg__2: object, /, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int, NoneType] = None) -> None
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, /) -> PySide6.QtGui.QAction
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview], /, type: PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection)) -> PySide6.QtGui.QAction
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview], shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], /) -> PySide6.QtGui.QAction
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], /) -> PySide6.QtGui.QAction
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], receiver: PySide6.QtCore.QObject, member: Union[bytes, bytearray, memoryview], /, type: PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection)) -> PySide6.QtGui.QAction
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int], callable: object, /) -> PySide6.QtGui.QAction
    addAction(self, icon: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, callable: object, /) -> PySide6.QtGui.QAction
    addAction(self, arg__1: Union[PySide6.QtGui.QIcon, PySide6.QtGui.QPixmap], text: str, arg__3: object, /, shortcut: Union[PySide6.QtGui.QKeySequence, PySide6.QtCore.QKeyCombination, PySide6.QtGui.QKeySequence.StandardKey, str, int, NoneType] = None) -> None

So seems like one missing signature from the ones listed in the OP is passing a QAction instance as arg

@dalthviz dalthviz added this to the v2.5.0 milestone Feb 17, 2026
@beatreichenbach
Copy link
Copy Markdown
Contributor Author

Okay thanks, I'll add a check for all of them and try to refactor that add_action wrapper.

menu.addAction(icon, text, func, shortcut=shortcut)
menu.addAction(icon, text, receiver, member, shortcut)

# Qt 5
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These signatures are valid in Qt5 but not Qt6, do we still need to support them?
They won't error with Qt5 but will error with Qt6.

Copy link
Copy Markdown
Member

@dalthviz dalthviz Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they should still be available to ensure things with Qt5 bindings work. If no Qt6 bindings signature is usable with the passed arguments we should detect that case and probably at the very least show a warning explaining the situation (something like no Qt6 compatible signature is available please use one of the following to keep compatibility between Qt versions...).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we'd have to also change this section here:

if PYQT5 or PYSIDE2 or _parse_version(_qt_version) < _parse_version("6.4"):

I feel like the error is pretty clear already:
AttributeError: PySide6.QtWidgets.QMenu.addAction(): unsupported keyword 'shortcut'

This way we're also ensuring that we're not adding unnecessary code for when the user uses a modern Qt6 binding.

Are you okay, if we just leave it as is?

shortcut = kwargs.pop("shortcut", None)
connection_type = kwargs.pop("type", None)
if connection_type:
warnings.warn("type argument is not supported in Qt<6.3")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These signatures got added in 6.3 and won't error in Qt5 but type is not supported. How should this be handled?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showing a warning if someone uses the argument with a binding that doesn't support it makes sense to me

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we leave the AttributeError, should we then also change this warning to an exception?

AttributeError: QtWidgets.QMenu.addAction(): unsupported keyword 'type'

"QtPy with an icon and a QKeySequence shortcut",
QtGui.QKeySequence.UnknownKey,

# Actions
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests have only been added to QMenu, in theory qtpy supports these for QToolBar as well.
But in Qt6 these also are valid for QWidget. But in Qt5 they don't exist at all.
Does QtPy provide backwards compatibility for everything? I'm a bit confused with that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a little bit of context the original PR from where this patch approach comes is #437 The idea shown there only takes care of the more simple cases of compatibility for QMenu and QToolbar so I will say we currently only support backwards compatibility for QMenu and QToolbar addAction

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added tests for QToolbar.

@dalthviz dalthviz changed the title fix: Add kwargs to add_action wrapper PR: Add missing kwargs to add_action wrapper Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QMenu.add_action() got an unexpected keyword argument 'shortcut'

3 participants