From 33194a4dfc44ff8846fa72b7c6e008e2c5d73a40 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Mon, 25 Aug 2025 13:08:55 +0200 Subject: [PATCH 1/8] Switch from pyqt5 to qtpy --- pyproject.toml | 3 +- src/legacy/mapping_gui_test.ipy | 2 +- src/qumada/instrument/mapping/mapping_gui.py | 32 ++++++++++++-------- src/qumada/utils/device_GUI.py | 4 +-- src/tests/mapping_test.py | 4 +-- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2c3464ec..6d3843d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "matplotlib", "jsonschema", "zhinst-toolkit >= 0.3.3", - "pyqt5", + "qtpy", "versioningit ~= 2.2.0", ] @@ -59,7 +59,6 @@ build-file = "qumada/_version.py" addopts = [ "--import-mode=importlib", ] -qt_api = "pyqt5" [tool.coverage.run] source = [ diff --git a/src/legacy/mapping_gui_test.ipy b/src/legacy/mapping_gui_test.ipy index f7395fec..7fc38efa 100644 --- a/src/legacy/mapping_gui_test.ipy +++ b/src/legacy/mapping_gui_test.ipy @@ -84,7 +84,7 @@ script.setup( # %% -from PyQt5.QtWidgets import QApplication +from qtpy.QtWidgets import QApplication # %% print(QApplication.instance()) diff --git a/src/qumada/instrument/mapping/mapping_gui.py b/src/qumada/instrument/mapping/mapping_gui.py index 21a0741b..f403c3cb 100644 --- a/src/qumada/instrument/mapping/mapping_gui.py +++ b/src/qumada/instrument/mapping/mapping_gui.py @@ -23,8 +23,8 @@ from collections.abc import Iterable, Mapping from typing import Any -from PyQt5.QtCore import QItemSelectionModel, Qt, QTimer, pyqtSignal, pyqtSlot -from PyQt5.QtGui import ( +from qtpy.QtCore import QItemSelectionModel, Qt, QTimer, Signal as pyqtSignal, Slot as pyqtSlot +from qtpy.QtGui import ( QBrush, QColor, QDropEvent, @@ -34,10 +34,9 @@ QStandardItem, QStandardItemModel, ) -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QAction, QApplication, - QDesktopWidget, QHBoxLayout, QInputDialog, QLabel, @@ -203,7 +202,8 @@ def update_tree(self): def import_data(self, terminal_parameters: TerminalParameters) -> None: """Build up tree with provided terminal parameters.""" - root = self.model().invisibleRootItem() + model = self.model() + root = model.invisibleRootItem() self.terminal_parameters = terminal_parameters for terminal_name, terminal_params in terminal_parameters.items(): item = QStandardItem(terminal_name) @@ -221,13 +221,18 @@ def import_data(self, terminal_parameters: TerminalParameters) -> None: item.appendRow(subitem) qidx = item.index() - self.model().setData(qidx.siblingAtColumn(1), QBrush(RED), Qt.BackgroundRole) - self.model().insertColumn(1, qidx) - self.model().insertColumn(2, qidx) + model.setData(qidx.siblingAtColumn(1), QBrush(RED), Qt.BackgroundRole) + model.insertColumn(1, qidx) + model.insertColumn(2, qidx) + for i in range(len(terminal_params.keys())): - self.model().setData(qidx.child(i, 1), "") - self.model().setData(qidx.child(i, 1), QBrush(RED), Qt.BackgroundRole) - self.model().setData(qidx.child(i, 2), "") + # this is written with questionable LLM support + idx1 = model.index(i, 1, qidx) + idx2 = model.index(i, 2, qidx) + + self.model().setData(idx1, "") + self.model().setData(idx1, QBrush(RED), Qt.BackgroundRole) + self.model().setData(idx2, "") self.setColumnHidden(2, not self.monitoring_enable) @@ -590,7 +595,10 @@ def __init__( idx = self.terminal_tree.model().invisibleRootItem().child(0, 0).index() self.terminal_tree.selectionModel().select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect) self.terminal_tree.setCurrentIndex(idx) - self.resize(QDesktopWidget().availableGeometry(self).size() * 0.45) + + screen = self.screen() or QApplication.primaryScreen() + new_size = screen.availableGeometry().size() * 0.45 + self.resize(new_size) self.terminal_parameters = terminal_parameters diff --git a/src/qumada/utils/device_GUI.py b/src/qumada/utils/device_GUI.py index 3df8e951..cd90fc8d 100644 --- a/src/qumada/utils/device_GUI.py +++ b/src/qumada/utils/device_GUI.py @@ -3,7 +3,7 @@ import sys import threading -from PyQt5.QtCore import ( +from qtpy.QtCore import ( Q_ARG, QMetaObject, QObject, @@ -13,7 +13,7 @@ pyqtSignal, pyqtSlot, ) -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QApplication, QLabel, QPushButton, diff --git a/src/tests/mapping_test.py b/src/tests/mapping_test.py index 2db714d5..2d6dd12e 100644 --- a/src/tests/mapping_test.py +++ b/src/tests/mapping_test.py @@ -28,8 +28,8 @@ import pytest from jsonschema import ValidationError -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QApplication, QMessageBox +from qtpy.QtCore import Qt +from qtpy.QtWidgets import QApplication, QMessageBox from pytest_cases import fixture_ref, parametrize from pytest_mock import MockerFixture from qcodes.instrument_drivers.mock_instruments import ( From 7a5112dd0c840170349dbd9cfbe10df134e8346f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:10:29 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qumada/instrument/mapping/mapping_gui.py | 12 +++++++----- src/tests/mapping_test.py | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/qumada/instrument/mapping/mapping_gui.py b/src/qumada/instrument/mapping/mapping_gui.py index f403c3cb..127bf586 100644 --- a/src/qumada/instrument/mapping/mapping_gui.py +++ b/src/qumada/instrument/mapping/mapping_gui.py @@ -23,7 +23,13 @@ from collections.abc import Iterable, Mapping from typing import Any -from qtpy.QtCore import QItemSelectionModel, Qt, QTimer, Signal as pyqtSignal, Slot as pyqtSlot +from qcodes.instrument.channel import InstrumentModule +from qcodes.instrument.instrument import Instrument +from qcodes.instrument.parameter import Parameter +from qcodes.utils.metadata import Metadatable +from qtpy.QtCore import QItemSelectionModel, Qt, QTimer +from qtpy.QtCore import Signal as pyqtSignal +from qtpy.QtCore import Slot as pyqtSlot from qtpy.QtGui import ( QBrush, QColor, @@ -50,10 +56,6 @@ QVBoxLayout, QWidget, ) -from qcodes.instrument.channel import InstrumentModule -from qcodes.instrument.instrument import Instrument -from qcodes.instrument.parameter import Parameter -from qcodes.utils.metadata import Metadatable from qumada.instrument.mapping.base import TerminalParameters, filter_flatten_parameters from qumada.metadata import Metadata diff --git a/src/tests/mapping_test.py b/src/tests/mapping_test.py index 2d6dd12e..8340f8eb 100644 --- a/src/tests/mapping_test.py +++ b/src/tests/mapping_test.py @@ -28,8 +28,6 @@ import pytest from jsonschema import ValidationError -from qtpy.QtCore import Qt -from qtpy.QtWidgets import QApplication, QMessageBox from pytest_cases import fixture_ref, parametrize from pytest_mock import MockerFixture from qcodes.instrument_drivers.mock_instruments import ( @@ -37,6 +35,8 @@ DummyInstrument, ) from qcodes.station import Station +from qtpy.QtCore import Qt +from qtpy.QtWidgets import QApplication, QMessageBox import qumada.instrument.mapping as mapping from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac From f56faee9cb40e8c987a6b46fa5b60f999c41360c Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Mon, 25 Aug 2025 13:53:45 +0200 Subject: [PATCH 3/8] Add pyside6 dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 6d3843d0..5d10a117 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ dependencies = [ "jsonschema", "zhinst-toolkit >= 0.3.3", "qtpy", + "pyside6", "versioningit ~= 2.2.0", ] From 4238c2d1998f062cf7ce883da89003a789214cbb Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Mon, 25 Aug 2025 14:06:59 +0200 Subject: [PATCH 4/8] Fix signal and slot imports --- src/qumada/utils/device_GUI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qumada/utils/device_GUI.py b/src/qumada/utils/device_GUI.py index cd90fc8d..377561df 100644 --- a/src/qumada/utils/device_GUI.py +++ b/src/qumada/utils/device_GUI.py @@ -10,8 +10,8 @@ Qt, QThread, QTimer, - pyqtSignal, - pyqtSlot, + Signal as pyqtSignal, + Slot as pyqtSlot, ) from qtpy.QtWidgets import ( QApplication, From 837ba3a280014f6e4c9afe9b854960e186e648cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:07:12 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qumada/utils/device_GUI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qumada/utils/device_GUI.py b/src/qumada/utils/device_GUI.py index 377561df..973b3af9 100644 --- a/src/qumada/utils/device_GUI.py +++ b/src/qumada/utils/device_GUI.py @@ -10,9 +10,9 @@ Qt, QThread, QTimer, - Signal as pyqtSignal, - Slot as pyqtSlot, ) +from qtpy.QtCore import Signal as pyqtSignal +from qtpy.QtCore import Slot as pyqtSlot from qtpy.QtWidgets import ( QApplication, QLabel, From a4c0e3b55a1c8e16b0b7843f62b8ec599103c2f4 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Mon, 25 Aug 2025 14:10:43 +0200 Subject: [PATCH 6/8] Add minimal useless device_GUI test --- src/tests/device_gui_test.py | 89 ++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/tests/device_gui_test.py diff --git a/src/tests/device_gui_test.py b/src/tests/device_gui_test.py new file mode 100644 index 00000000..1747daba --- /dev/null +++ b/src/tests/device_gui_test.py @@ -0,0 +1,89 @@ +""" +This test was generated by an LLM. + +It does not test actual functionality. It just tests that the module device_GUI.py can be imported and that some code +paths don't raise an exception. +""" + + +import multiprocessing as mp +import types +import pytest +from qtpy.QtCore import Qt, QObject, Signal as pyqtSignal +import qumada.utils.device_GUI as device_GUI + + +class DummyWorker(QObject): + """Deterministic replacement for device_GUI.Worker used in most tests.""" + data_ready = pyqtSignal(list) + + def __init__(self, interval, parameters): + super().__init__() + self.interval = interval + self.parameters = parameters + self.running = True + + def stop(self): + self.running = False + + +@pytest.fixture +def parameters(): + p1 = device_GUI.Parent("P1") + p2 = device_GUI.Parent("P2") + return [ + device_GUI.Parameter("A", p1), + device_GUI.Parameter("B", p1), + device_GUI.Parameter("C", p2), + ] + + +@pytest.fixture +def gui(qtbot, parameters, monkeypatch): + # Replace Worker with a deterministic stub + monkeypatch.setattr(device_GUI, "Worker", DummyWorker) + q = mp.Queue() + w = device_GUI.MeasurementGUI(parameters, q) + qtbot.addWidget(w) + # keep intervals small if anything uses it + w.interval_spinbox.setValue(120) + return w + + +def _col_texts(table, col): + return [ + (table.item(r, col).text() if table.item(r, col) is not None else "") + for r in range(table.rowCount()) + ] + + +def test_initial_table_population(gui, parameters): + assert gui.table.rowCount() == len(parameters) + # Column 0: names "Param: " + names = _col_texts(gui.table, 0) + assert names == [f"Param: {p._parent.name} {p.name}" for p in parameters] + # Column 1: initial values from __call__() -> "42" + values = _col_texts(gui.table, 1) + assert values == ["42"] * len(parameters) + + +def test_update_interval_changes_worker_field(gui, qtbot): + # Initial value is 120 from fixture + assert gui.worker.interval == 120 + gui.interval_spinbox.setValue(333) + # Signal is connected in initUI; the slot sets worker.interval + assert gui.worker.interval == 333 + + +def test_close_event_stops_worker_and_puts_quit(gui, qtbot): + q = gui.data_queue + # Thread should be running before close + assert gui.worker_thread.isRunning() + gui.close() + + # After close, thread should be stopped + assert not gui.worker_thread.isRunning() + # Worker stop() should have been called + assert gui.worker.running is False + # Queue should receive "QUIT" + assert q.get(timeout=1) == "QUIT" From e5616aeb4c669f75797df0a243af8cc0b688cc1b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:11:37 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/tests/device_gui_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tests/device_gui_test.py b/src/tests/device_gui_test.py index 1747daba..bb970d06 100644 --- a/src/tests/device_gui_test.py +++ b/src/tests/device_gui_test.py @@ -5,16 +5,19 @@ paths don't raise an exception. """ - import multiprocessing as mp import types + import pytest -from qtpy.QtCore import Qt, QObject, Signal as pyqtSignal +from qtpy.QtCore import QObject, Qt +from qtpy.QtCore import Signal as pyqtSignal + import qumada.utils.device_GUI as device_GUI class DummyWorker(QObject): """Deterministic replacement for device_GUI.Worker used in most tests.""" + data_ready = pyqtSignal(list) def __init__(self, interval, parameters): @@ -51,10 +54,7 @@ def gui(qtbot, parameters, monkeypatch): def _col_texts(table, col): - return [ - (table.item(r, col).text() if table.item(r, col) is not None else "") - for r in range(table.rowCount()) - ] + return [(table.item(r, col).text() if table.item(r, col) is not None else "") for r in range(table.rowCount())] def test_initial_table_population(gui, parameters): From 0269bc0d4f4f294ce03dfaf26c322ac8493d9c21 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Mon, 25 Aug 2025 14:25:52 +0200 Subject: [PATCH 8/8] Remove unised imports --- src/tests/device_gui_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tests/device_gui_test.py b/src/tests/device_gui_test.py index bb970d06..7425cfb4 100644 --- a/src/tests/device_gui_test.py +++ b/src/tests/device_gui_test.py @@ -6,10 +6,9 @@ """ import multiprocessing as mp -import types import pytest -from qtpy.QtCore import QObject, Qt +from qtpy.QtCore import QObject from qtpy.QtCore import Signal as pyqtSignal import qumada.utils.device_GUI as device_GUI