Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qt 6 support #255

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
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
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ jobs:
matrix:
python: ['3.6', '3.7', '3.8', '3.9']
arch: ['x86', 'x64']
qt_library: ['PyQt5', 'PySide2']
qt_library: ['PySide2', 'PyQt5', 'PySide6', 'PyQt6']
exclude:
- arch: x86
qt_library: PySide6
- arch: x86
qt_library: PyQt6
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down Expand Up @@ -135,7 +140,7 @@ jobs:
fail-fast: false
matrix:
python: ['3.6', '3.7', '3.8', '3.9']
qt_library: ['PyQt5', 'PySide2']
qt_library: ['PySide2', 'PyQt5', 'PySide6', 'PyQt6']
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down Expand Up @@ -163,7 +168,7 @@ jobs:
fail-fast: false
matrix:
python: ['3.6', '3.7', '3.8', '3.9']
qt_library: ['PyQt5', 'PySide2']
qt_library: ['PySide2', 'PyQt5', 'PySide6', 'PyQt6']
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"py:class",
"Union[<class 'PySide2.QtWidgets.QMessageBox.StandardButton'>, PySide2.QtWidgets.QMessageBox.StandardButtons]",
),
("py:class", "<class 'PySide2.QtWidgets.QMessageBox.StandardButton'>"),
]

# -- General configuration ------------------------------------------------
Expand Down
43 changes: 14 additions & 29 deletions qtrio/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import attr
import outcome
import qts
import qts.util
import trio
import trio.abc

Expand Down Expand Up @@ -62,16 +63,12 @@ def register_event_type() -> None:

# assign to the global
# TODO: https://bugreports.qt.io/browse/PYSIDE-1347
if qts.is_pyqt_5_wrapper:
_reenter_event_type = QtCore.QEvent.Type(event_hint)
elif qts.is_pyside_5_wrapper:
if qts.is_pyside_5_wrapper:
_reenter_event_type = typing.cast(
typing.Callable[[int], QtCore.QEvent.Type], QtCore.QEvent.Type
)(event_hint)
else: # pragma: no cover
raise qtrio.InternalError(
"You should not be here but you are running neither PyQt5 nor PySide2.",
)
else:
_reenter_event_type = QtCore.QEvent.Type(event_hint)


def register_requested_event_type(
Expand All @@ -98,17 +95,13 @@ def register_requested_event_type(
raise qtrio.EventTypeAlreadyRegisteredError()

# TODO: https://bugreports.qt.io/browse/PYSIDE-1468
if qts.is_pyqt_5_wrapper:
event_hint = QtCore.QEvent.registerEventType(requested_value)
elif qts.is_pyside_5_wrapper:
if qts.is_pyside_5_wrapper:
event_hint = typing.cast(
typing.Callable[[typing.Union[int, QtCore.QEvent.Type]], int],
QtCore.QEvent.registerEventType,
)(requested_value)
else: # pragma: no cover
raise qtrio.InternalError(
"You should not be here but you are running neither PyQt5 nor PySide2.",
)
else:
event_hint = QtCore.QEvent.registerEventType(requested_value)

if event_hint == -1:
raise qtrio.EventTypeRegistrationFailedError()
Expand All @@ -119,16 +112,12 @@ def register_requested_event_type(

# assign to the global
# TODO: https://bugreports.qt.io/browse/PYSIDE-1347
if qts.is_pyqt_5_wrapper:
_reenter_event_type = QtCore.QEvent.Type(event_hint)
elif qts.is_pyside_5_wrapper:
if qts.is_pyside_5_wrapper:
_reenter_event_type = typing.cast(
typing.Callable[[int], QtCore.QEvent.Type], QtCore.QEvent.Type
)(event_hint)
else: # pragma: no cover
raise qtrio.InternalError(
"You should not be here but you are running neither PyQt5 nor PySide2.",
)
else:
_reenter_event_type = QtCore.QEvent.Type(event_hint)


async def wait_signal(signal: "QtCore.SignalInstance") -> typing.Tuple[object, ...]:
Expand Down Expand Up @@ -540,17 +529,13 @@ def maybe_build_application() -> "QtGui.QGuiApplication":
application: QtCore.QCoreApplication

# TODO: https://bugreports.qt.io/browse/PYSIDE-1467
if qts.is_pyqt_5_wrapper:
maybe_application = QtWidgets.QApplication.instance()
elif qts.is_pyside_5_wrapper:
if qts.is_pyside_5_wrapper:
maybe_application = typing.cast(
typing.Optional["QtCore.QCoreApplication"],
QtWidgets.QApplication.instance(),
)
else: # pragma: no cover
raise qtrio.InternalError(
"You should not be here but you are running neither PyQt5 nor PySide2.",
)
else:
maybe_application = QtWidgets.QApplication.instance()

if maybe_application is None:
application = QtWidgets.QApplication(sys.argv[1:])
Expand Down Expand Up @@ -649,7 +634,7 @@ def run(
)

if execute_application:
return_code = self.application.exec_()
return_code = qts.util.exec(self.application)

self.outcomes = attr.evolve(
self.outcomes,
Expand Down
6 changes: 5 additions & 1 deletion qtrio/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ def connection(
# if you get segfault or sigsegv here, especially from pyside2<5.15.2, make
# sure the slot isn't on a non-hashable (frozen will make it hashable) attrs
# class. https://bugreports.qt.io/browse/PYSIDE-1422
this_connection = signal.connect(slot)
try:
this_connection = signal.connect(slot)
except TypeError as e:
print(e)
raise

import qts

Expand Down
14 changes: 6 additions & 8 deletions qtrio/_tests/examples/readme/test_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ def test_main(qtbot: pytestqt.qtbot.QtBot, qapp: QtWidgets.QApplication) -> None
output_dialog=output_dialog,
)

main_object.setup()

qtbot.wait_for_window_shown(input_dialog)
with qtbot.wait_exposed(widget=input_dialog):
main_object.setup()

[line_edit] = input_dialog.findChildren(QtWidgets.QLineEdit)
line_edit.setText(text_to_enter)
input_dialog.accept()

qtbot.wait_for_window_shown(output_dialog)
with qtbot.wait_exposed(widget=output_dialog):
input_dialog.accept()

output_text = output_dialog.text()

Expand All @@ -53,9 +52,8 @@ def test_main_cancelled(
output_dialog=output_dialog,
)

main_object.setup()

qtbot.wait_for_window_shown(input_dialog)
with qtbot.wait_exposed(widget=input_dialog):
main_object.setup()

[line_edit] = input_dialog.findChildren(QtWidgets.QLineEdit)
line_edit.setText(text_to_enter)
Expand Down
2 changes: 1 addition & 1 deletion qtrio/_tests/examples/test_crossingpaths.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def test_main(
hold_event=optional_hold_event,
)
widget: qtrio.examples.crossingpaths.Widget = await nursery.start(start)
qtbot.addWidget(widget)
qtbot.addWidget(widget.label)

async with qtrio.enter_emissions_channel(
signals=[widget.text_changed],
Expand Down
4 changes: 2 additions & 2 deletions qtrio/_tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
@pytest.fixture(name="qtrio_preshow_workaround", scope="session", autouse=True)
def qtrio_preshow_workaround_fixture(qapp):
dialog = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Information,
QtWidgets.QMessageBox.Icon.Information,
"",
"",
QtWidgets.QMessageBox.Ok,
QtWidgets.QMessageBox.StandardButton.Ok,
)

dialog.show()
Expand Down
44 changes: 40 additions & 4 deletions qtrio/_tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import outcome
import pytest
from qts import QtCore
from qts import QtWidgets
import qtrio
import qtrio._core
import trio
Expand Down Expand Up @@ -393,7 +394,7 @@ def test():
pass

with pytest.raises(qtrio.EventTypeRegistrationFailedError):
qtrio.register_requested_event_type(QtCore.QEvent.User)
qtrio.register_requested_event_type(QtCore.QEvent.Type.User)
"""
testdir.makepyfile(test_file)

Expand Down Expand Up @@ -431,9 +432,9 @@ def test_requesting_available_event_type_succeeds(testdir):


def test():
qtrio.register_requested_event_type(QtCore.QEvent.User)
qtrio.register_requested_event_type(QtCore.QEvent.Type.User)

assert qtrio.registered_event_type() == QtCore.QEvent.User
assert qtrio.registered_event_type() == QtCore.QEvent.Type.User
"""
testdir.makepyfile(test_file)

Expand Down Expand Up @@ -671,6 +672,27 @@ async def test():
)


def test_qobject_destroyed_signal_equality(qapp):
"""Verify that the underlying signal objects can be compared by equality.

https://bugreports.qt.io/browse/PYSIDE-1431
"""
q_object = QtCore.QObject()

assert q_object.destroyed == q_object.destroyed


def test_qpushbutton_clicked_signal_equality(qapp):
"""Verify that the underlying signal objects can be compared by equality even when
the signal is inherited.

https://bugreports.qt.io/browse/PYSIDE-1431
"""
button = QtWidgets.QPushButton()

assert button.clicked == button.clicked


def test_emissions_equal():
""":class:`Emission` objects created from the same :class:`QtCore.Signal` instance
and args are equal even if the attributes are different instances.
Expand All @@ -686,6 +708,18 @@ class C(QtCore.QObject):
) == qtrio._core.Emission(signal=instance.signal, args=(13,))


def test_emissions_for_buttons():
""":class:`Emission` objects created from the same :class:`QtWidgets.QPushButton`
instance and args are equal even if the attributes are different instances.
"""

instance = QtWidgets.QPushButton()

assert qtrio._core.Emission(
signal=instance.clicked, args=(13,)
) == qtrio._core.Emission(signal=instance.clicked, args=(13,))


def test_emissions_unequal_by_signal():
""":class:`Emission` objects with the same arguments but different signals are
unequal.
Expand Down Expand Up @@ -1154,6 +1188,8 @@ def test_execute_manually(testdir):
"""Executing manually works."""

test_file = r"""
import qts.util

import qtrio


Expand All @@ -1169,7 +1205,7 @@ async def async_fn():

assert not ran

runner.application.exec_()
qts.util.exec(runner.application)

assert ran
"""
Expand Down
15 changes: 9 additions & 6 deletions qtrio/_tests/test_dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def user(task_status):
task_status.started()

qtbot.keyClicks(dialog.edit_widget, str(test_value))
qtbot.mouseClick(dialog.accept_button, QtCore.Qt.LeftButton)
qtbot.mouseClick(dialog.accept_button, QtCore.Qt.MouseButton.LeftButton)

test_value = 928

Expand All @@ -73,7 +73,7 @@ async def user(task_status):
task_status.started()

qtbot.keyClicks(dialog.edit_widget, "abc")
qtbot.mouseClick(dialog.reject_button, QtCore.Qt.LeftButton)
qtbot.mouseClick(dialog.reject_button, QtCore.Qt.MouseButton.LeftButton)

async with trio.open_nursery() as nursery:
await nursery.start(user)
Expand All @@ -92,7 +92,7 @@ async def user(task_status):
task_status.started()

qtbot.keyClicks(dialog.edit_widget, "abc")
qtbot.mouseClick(dialog.accept_button, QtCore.Qt.LeftButton)
qtbot.mouseClick(dialog.accept_button, QtCore.Qt.MouseButton.LeftButton)

async with trio.open_nursery() as nursery:
await nursery.start(user)
Expand Down Expand Up @@ -229,7 +229,7 @@ async def test_information_message_box(qtbot: pytestqt.qtbot.QtBot) -> None:
dialog = qtrio.dialogs.create_message_box(
title="Information",
text=text,
icon=QtWidgets.QMessageBox.Information,
icon=QtWidgets.QMessageBox.Icon.Information,
)

async def user(task_status):
Expand All @@ -255,8 +255,11 @@ async def test_information_message_box_cancel(qtbot: pytestqt.qtbot.QtBot) -> No
dialog = qtrio.dialogs.create_message_box(
title="",
text="",
icon=QtWidgets.QMessageBox.Information,
buttons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
icon=QtWidgets.QMessageBox.Icon.Information,
buttons=(
QtWidgets.QMessageBox.StandardButton.Ok
| QtWidgets.QMessageBox.StandardButton.Cancel
),
)

async def user(task_status):
Expand Down
4 changes: 2 additions & 2 deletions qtrio/_tests/test_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class NotQObject:

instance = NotQObject()

with qtbot.wait_signal(instance.signal, 100):
with qtbot.wait_signal(signal=instance.signal, timeout=100):
instance.signal.emit()


Expand All @@ -52,7 +52,7 @@ def collect_result(value):
instance = NotQObject()
instance.signal.connect(collect_result)

with qtbot.wait_signal(instance.signal, 100):
with qtbot.wait_signal(signal=instance.signal, timeout=100):
instance.signal.emit(13)

assert result == 13
Expand Down
Loading