Skip to content

Qt6/PySide6 compatibility + cleanup (re-apply of #439)#461

Merged
astafan8 merged 4 commits into
masterfrom
pyside6-and-cleanup-reapply
Jul 1, 2026
Merged

Qt6/PySide6 compatibility + cleanup (re-apply of #439)#461
astafan8 merged 4 commits into
masterfrom
pyside6-and-cleanup-reapply

Conversation

@astafan8

@astafan8 astafan8 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

What this is

A faithful re-application of #439 ("Qt6 compatibility + some cleanup") onto the current
master. The original branch (pyside6_and_cleanup) is unmergeable — master diverged heavily
on autoplot.py, inspectr.py, datadict.py, pyqtgraph/autoplot.py, monitr.py, etc. since the
original merge-base (460fbbb). Rather than fight cherry-pick conflicts, the changes were
re-implemented on top of current master so the intent is preserved while the result is clean and
passes checks.

Opened as draft for review of the differences and implementation. Original PR by @wpfff.

Re-implemented by an automated assistant (GitHub Copilot CLI) on behalf of @astafan8.

Validation

Run with uv (Python 3.12) using the repo's CI checks, with the PySide6 backend (PySide6 6.11.1):

  • mypy plottrSuccess: no issues found in 63 source files
  • pytest test/pytest411 passed (388 original + 22 new GUI tests + the previously
    Windows-flaky concurrent test, now fixed). The only console noise is pre-existing
    libpyside: Failed to disconnect ... sigLevelsChanged RuntimeWarnings (already present on master).

Buckets of change

1. Cleanup — file removals (clean, no conflicts)

  • Entire doc/ directory (old Sphinx docs; the plan in Qt6 compatibility + some cleanup #439 is to replace with mkdocs).
  • readthedocs.yml (no longer hosting on RTD).
  • Legacy test/gui/*, test/apps/*, test/prototyping/*, test/scripts/*, test/run_gui_test.py
    (manual/interactive prototyping scripts). The GUI scripts under test/gui/ were not just deleted —
    they were rewritten as real pytest-qt tests; see section 5.

2. Config / CI

  • pyproject.toml: added pyside6 = ["PySide6>=6.0"] optional dependency; added a mypy override
    disabling attr-defined for plottr.* (Qt enum access patterns differ between qtpy/PyQt5 and
    PySide6 stubs at type-check time, but work at runtime).
  • test_requirements.txt: PyQt5-stubs==5.15.6.0PySide6-stubs.
  • test/pytest/pytest.ini: qt_api=pyqt5qt_api=pyside6.
  • .github/actions/install-dependencies-and-plottr/action.yml: pip install .[pyqt5].[pyside6].
  • .github/workflows/python-app.yml: replaced the manual apt install ... xvfb step with
    tlambert03/setup-qt-libs@v1 + a dedicated Xvfb "display" step (needed for headless PySide6).
    The old step was removed entirely (not left commented out) per review feedback.

3. Code — PySide6 compatibility

  • plottr/__init__.py (foundational): under TYPE_CHECKING import from PySide6; expose
    Signal/Slot, API_NAME, and PYSIDE6/PYQT6 flags; export QAction/QActionGroup from the
    correct module (they moved QtWidgetsQtGui in Qt6); loop.exec_()loop.exec().
  • Mechanical (applied across apps/, plot/): *.exec_()*.exec();
    QtWidgets.QAction(Group)QAction/QActionGroup (imported from plottr).
  • appmanager.py: import PYSIDE6; QProcess state check uses the Qt6 nested enum
    (QProcess.ProcessState.NotRunning) vs Qt5 flat enum; qtsleep(0.01)0.05; fixed the
    onProcessEneded typo → onProcessEnded.
  • inspectr.py: super().__init__(strings)super().__init__(list(strings))
    (QTreeWidgetItem needs a real list under PySide6).
  • json_viewer.py: import Qt symbols via plottr and expose API_NAME as __binding__
    (the old __binding__ reference was undefined); ("PySide","PyQt4")("PySide","PyQt5");
    Qt.ItemFlagsQt.ItemFlag.
  • monitr.py: QAction/QActionGroup migration; bug fixes — leaveEvent calling
    super().enterEventsuper().leaveEvent; null-guards in VerticalScrollArea.eventFilter and
    on_data_window_timer; on_adjust_column_width(None).
  • data_display.py: setSelectionMode uses the Qt6 nested enum under PySide6.
  • datadict.py: _DataAccess.__getattribute__ now tolerates a missing _parent
    (try/except AttributeError) to avoid pickling/unpickling errors.
  • fitter.py: List[QtCore.pyqtBoundSignal]List[Any] (PyQt-specific type).
  • # type: ignore annotations: arg-type ignores on object/typed @Slots and override ignores
    on Qt model/view method overrides, as required by mypy 2.1.0 + PySide6 stubs.

4. Test changes (existing suite)

  • test_app_manager.py: added the FIXME note on test_getting_values (flaky on the author's Mac,
    fine on CI).
  • test_ddh5.py::test_loader_node: waits for the loader's queued onThreadComplete() to set the
    flowchart output via qtbot.waitUntil(...) (was a fixed qtsleep, see review fixes below).
  • test_ddh5.py::test_concurrent_write_and_read (Windows robustness): the test spawns a writer
    Process and used to read after a fixed time.sleep(2). On Windows (spawn) the child needs
    ~3.8 s just to import plottr/qcodes/Qt and create the file, so the first read raced against process
    startup and failed (it passed on Linux fork). It now waits for the file to be created before
    reading
    — robust on both platforms, no lost coverage. Concurrent access itself is coordinated by
    plottr's FileOpener lock file, so cross-platform concurrent read/write is fine once the file exists.

5. New: rewrote the deleted test/gui/ scripts as pytest-qt tests

The old test/gui/* files were interactive demo scripts (each ended in app.exec_()), not part of the
maintained suite. They are now proper qtbot-based smoke tests that run in CI and verify the GUI
builds and accepts data under PySide6. Added 22 tests across three files:

  • test/pytest/test_gui_widgets.pyDataSelectionWidget, DimensionSelector/AxisSelector/
    DependentSelector/MultiDimensionSelector, ShapeSpecificationWidget, GridOptionWidget,
    XYSelectionWidget.
  • test/pytest/test_gui_nodes.py — node UIs in a flowchart (DataSelector, DDH5Loader,
    DimensionReducer, XYSelector, DataGridder) plus plot windows (SubtractAverage +
    PlotWindow, MPL AutoPlot).
  • test/pytest/test_gui_figuremaker.py — MPL and pyqtgraph FigureMaker (line/image/scatter,
    including complex data).

Two latent bugs surfaced by these tests, and fixed:

  • gui/widgets.py MultiDimensionSelector used the Qt5-style unscoped enum self.MultiSelection,
    which doesn't exist under PySide6 — guarded it the same way DataSelectionWidget already is.
  • plot/pyqtgraph/plots.py scatter coloring used colorbar.cmap, removed in pyqtgraph 0.14 — switched
    to the public colorbar.colorMap() accessor (still tracks interactive colormap changes).

test_gui_nodes.py uses a small fixture that forces node UI creation and restores useUi/uiClass,
so it is robust against other suite modules that set those to False/None without restoring.

6. README

Rewrote the Installation section to explain that plottr talks to Qt through qtpy and that exactly
one Qt binding must be installed by the user, with a table of pip extras
(pyside6 recommended / pyqt5 / pyqt6 / pyside2), QT_API selection guidance for multi-binding
setups, and the conda note. Bumped the documented Python requirement to 3.12 to match pyproject.toml.

Review feedback addressed

  • Removed the commented-out xvfb workflow step entirely.
  • monitr.VerticalScrollArea.on_range_changed: rangeChanged(int, int) passes two ints, so the slot is
    now @Slot(int, int) accepting (min_value, max_value) (was @Slot(int) on a no-arg method); the
    masking # type: ignore[arg-type] was dropped and mypy stays green.
  • test_loader_node: replaced the fixed qtsleep(0.1) with qtbot.waitUntil(... output is not None).

Deliberate differences from #439

  • Skipped removing the numpy.typing.mypy_plugin mypy line — already removed on master.
  • Kept master's newer tooling: mypy==2.1.0 (not 1.13.0) + hypothesis;
    actions/checkout@v7 and actions/setup-python@v6.3.0.
  • # type: ignore set recomputed for mypy 2.1.0 + PySide6-stubs rather than copied verbatim from
    Qt6 compatibility + some cleanup #439 (which targeted mypy 1.13.0); warn_unused_ignores=true means stale ignores fail.
  • Fixed Qt6 compatibility + some cleanup #439's bad import: it contained from build.lib.plottr import qtsleep; corrected (and the
    import is now gone entirely after switching test_loader_node to qtbot.waitUntil).
  • plottr/apps/watchdog_classes.py (not touched by Qt6 compatibility + some cleanup #439): removed five now-redundant
    # type: ignore[attr-defined] comments that became "unused" once attr-defined is globally disabled
    for plottr.*.

Known / left items (for reviewer decision)

  • plottr/apps/ui/Monitr_UI.py (auto-generated, mypy-ignored, not imported anywhere) still uses
    QtWidgets.QAction, which doesn't exist at runtime under Qt6. Left untouched (matches Qt6 compatibility + some cleanup #439); the
    proper fix is to regenerate the .ui. Flagged here since it's dead-but-latent under PySide6.
  • A second leaveEventsuper().enterEvent occurrence exists near floating_button; Qt6 compatibility + some cleanup #439 only fixed
    the TextInput/save_button one, so that's all this PR changes.

Re-implements PR #439 (pyside6_and_cleanup) on top of current master, which had
diverged enough to make the original branch unmergeable.

- Default type checking and pytest qt_api switched to PySide6; runtime keeps
  qtpy backend abstraction (PyQt5/PyQt6/PySide2/PySide6 all supported).
- plottr/__init__.py exports QAction/QActionGroup (moved to QtGui in Qt6) and
  PYSIDE6/PYQT6/API_NAME flags; QAbstractItemModel exec_()->exec().
- Per-file PySide6 fixes across apps/plot/data/gui/node modules.
- Removed old Sphinx docs, readthedocs config, and legacy prototyping/gui test
  scripts.
- CI: install .[pyside6]; use tlambert03/setup-qt-libs for headless Qt libs.

Kept newer master tooling (mypy 2.1.0, checkout@v7, setup-python@v6.3.0) and
skipped changes already on master (numpy mypy plugin removal).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-applies the Qt6/PySide6 compatibility work (originally in #439) onto current master, updating runtime Qt API usage, CI defaults, and removing legacy docs/prototyping assets.

Changes:

  • Switched default CI/testing Qt backend to PySide6 (deps, pytest-qt config, CI setup) and added a pyside6 extra.
  • Updated core/app code for Qt6 API differences (e.g., exec() usage, QAction/QActionGroup module move, nested enums, PySide6 stub compatibility).
  • Removed legacy docs and manual/prototyping scripts not covered by the maintained pytest suite.

Reviewed changes

Copilot reviewed 66 out of 68 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/scripts/h5py_concurrent_rw_swmr.py Removed legacy manual concurrency script.
test/scripts/h5py_concurrent_rw_lock.py Removed legacy manual concurrency script.
test/run_gui_test.py Removed legacy GUI test runner script.
test/pytest/test_ddh5.py Adjusted loader-node test timing/Qt handling.
test/pytest/test_app_manager.py Added note about a locally flaky test.
test/pytest/pytest.ini Default pytest-qt backend set to PySide6.
test/prototyping/test_data.py Removed prototyping helper script.
test/prototyping/plottrcfg_main.py Removed prototyping config snippet.
test/prototyping/new_data_methods.ipynb Removed prototyping notebook.
test/prototyping/autoplot testing.ipynb Removed prototyping notebook.
test/gui/simple_2d_plot.py Removed manual GUI demo script.
test/gui/pyqtgraph_testing.py Removed manual GUI demo script.
test/gui/pyqtgraph_figuremaker.py Removed manual GUI demo script.
test/gui/mpl_figuremaker.py Removed manual GUI demo script.
test/gui/grid_options.py Removed manual GUI demo script.
test/gui/dimension_selection_widgets.py Removed manual GUI demo script.
test/gui/dimension_assignment.py Removed manual GUI demo script.
test/gui/ddh5_loader.py Removed manual GUI demo script.
test/gui/data_selector.py Removed manual GUI demo script.
test/gui/data_display_widgets.py Removed manual GUI demo script.
test/gui/correct_offset.py Removed manual GUI demo script.
test/apps/test_histograms.py Removed manual app/prototyping script.
test/apps/fitter_test.py Removed manual app/prototyping script.
test/apps/custom_app.py Removed manual app/prototyping script.
test/apps/autoplot_app.py Removed manual app/prototyping script.
test_requirements.txt Switched Qt stub package to PySide6-stubs.
readthedocs.yml Removed RTD config (docs cleanup).
pyproject.toml Added pyside6 extra + mypy override for Qt enum typing differences.
plottr/plot/pyqtgraph/autoplot.py Migrated QAction/QActionGroup usage for Qt6.
plottr/node/fitter.py Relaxed PyQt-specific signal typing for PySide6.
plottr/gui/data_display.py Fixed selection-mode enum access for PySide6/Qt6.
plottr/data/datadict.py Made _DataAccess.__getattribute__ robust to missing _parent (pickle/unpickle).
plottr/data/datadict_storage.py Added slot typing ignore for PySide6 stubs.
plottr/apps/watchdog_classes.py Removed redundant mypy ignores after config change.
plottr/apps/ui/monitr.py Added slot typing ignore for PySide6 stubs.
plottr/apps/monitr.py Qt6 compatibility changes + UI bugfixes/guards (exec/QAction/etc).
plottr/apps/json_viewer.py Routed Qt imports through plottr binding + Qt6 enum/signal adjustments.
plottr/apps/inspectr.py Qt6 QAction + exec() updates + PySide6 QTreeWidgetItem init fix.
plottr/apps/autoplot.py Qt6 QAction + exec() updates.
plottr/apps/apprunner.py Qt6 exec() usage.
plottr/apps/appmanager.py PySide6 QProcess state enum handling + slot typing ignores + typo fix.
plottr/init.py Centralized Qt binding exports (Signal/Slot/QAction/QActionGroup) + Qt6 exec().
doc/requirements.txt Removed Sphinx doc requirements (docs cleanup).
doc/plotnode.rst Removed legacy Sphinx docs content.
doc/nodes/index.rst Removed legacy Sphinx docs content.
doc/Makefile Removed legacy Sphinx build file.
doc/make.bat Removed legacy Sphinx build file.
doc/intro.rst Removed legacy Sphinx docs content.
doc/index.rst Removed legacy Sphinx docs content.
doc/examples/Simple Live plotting example with DDH5.ipynb Removed legacy docs example notebook.
doc/examples/node_with_dimension_selector_widget.py Removed legacy docs example script.
doc/examples/Live plotting qcodes data.ipynb Removed legacy docs example notebook.
doc/examples/Inferring grids.ipynb Removed legacy docs example notebook.
doc/examples/autonode_app.py Removed legacy docs example script.
doc/examples.rst Removed legacy Sphinx docs content.
doc/conf.py Removed legacy Sphinx configuration.
doc/concepts/nodes.rst Removed legacy Sphinx docs content.
doc/concepts/nodes.bak.rst Removed legacy Sphinx docs content.
doc/concepts/index.rst Removed legacy Sphinx docs content.
doc/concepts/data.rst Removed legacy Sphinx docs content.
doc/api/plot.rst Removed legacy Sphinx docs content.
doc/api/node.rst Removed legacy Sphinx docs content.
doc/api/index.rst Removed legacy Sphinx docs content.
doc/api/data.rst Removed legacy Sphinx docs content.
.github/workflows/python-app.yml Updated CI to install Qt libs via action + Xvfb setup for headless PySide6.
.github/actions/install-dependencies-and-plottr/action.yml CI installs Plottr with .[pyside6] extra.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plottr/apps/monitr.py Outdated
Comment thread test/pytest/test_ddh5.py Outdated
Mikhail Astafev and others added 3 commits June 30, 2026 18:27
- Remove the commented-out 'setup ubuntu-latest xvfb' step from the workflow
  entirely (superseded by tlambert03/setup-qt-libs).
- Recreate the deleted test/gui/* interactive scripts as proper pytest-qt tests
  under test/pytest (test_gui_widgets.py, test_gui_nodes.py,
  test_gui_figuremaker.py): 22 qtbot-based smoke tests for widgets, node UIs and
  both FigureMaker backends.
- Fix two latent bugs surfaced by those tests:
  * MultiDimensionSelector used the Qt5-style unscoped enum
    self.MultiSelection, which doesn't exist under PySide6; guard it the same
    way DataSelectionWidget already does.
  * PlotWithColorbar scatter coloring used colorbar.cmap, removed in pyqtgraph
    0.14; use the public colorbar.colorMap() accessor instead.
- Make test_concurrent_write_and_read robust on Windows: wait for the writer
  process to create the file before reading, instead of assuming it exists
  after a fixed 2s sleep (Windows 'spawn' startup is slower than that).
- test_gui_nodes uses a fixture that forces node UI creation and restores
  useUi/uiClass, so it is robust against other tests that set those to
  False/None without restoring.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite the README Installation section to explain that plottr uses qtpy and
that exactly one Qt binding must be installed by the user, with a table of pip
extras (pyside6/pyqt5/pyqt6/pyside2), QT_API selection guidance, and the conda
note. Bump the documented Python requirement to 3.12 to match pyproject.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- monitr.VerticalScrollArea.on_range_changed: the rangeChanged(int, int) signal
  passes two ints; accept them in the slot signature/decorator (@slot(int, int))
  instead of declaring @slot(int) on a no-arg method.
- test_ddh5.test_loader_node: replace the fixed qtsleep(0.1) with
  qtbot.waitUntil(... output is not None), so the test waits for the queued
  onThreadComplete() slot to set the output rather than depending on timing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@astafan8 astafan8 marked this pull request as draft June 30, 2026 16:34
@astafan8 astafan8 marked this pull request as ready for review July 1, 2026 09:59
@astafan8 astafan8 merged commit 42cd393 into master Jul 1, 2026
2 checks passed
@astafan8 astafan8 deleted the pyside6-and-cleanup-reapply branch July 1, 2026 09:59
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.

3 participants