Skip to content

Commit a893a5a

Browse files
Merge pull request #87 from PlotPyStack/ci/69-qt6-workflows
Add PyQt6 and PySide6 CI workflows (and fix the hanging SnapshotTool test)
2 parents b62c67d + e1f4791 commit a893a5a

6 files changed

Lines changed: 203 additions & 4 deletions

File tree

‎.github/workflows/build_deploy.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ jobs:
99
test:
1010
uses: ./.github/workflows/test_pyqt5.yml
1111

12+
test_pyqt6:
13+
uses: ./.github/workflows/test_pyqt6.yml
14+
1215
build_wheels:
1316
name: Build wheels on ${{ matrix.os }}
1417
runs-on: ${{ matrix.os }}
@@ -57,7 +60,7 @@ jobs:
5760
path: dist/*.tar.gz
5861

5962
upload_pypi:
60-
needs: [test, build_wheels, build_sdist]
63+
needs: [test, test_pyqt6, build_wheels, build_sdist]
6164
runs-on: ubuntu-latest
6265
environment: pypi
6366
permissions:

‎.github/workflows/test_pyqt5.yml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
strategy:
2323
fail-fast: false
2424
matrix:
25-
python-version: ["3.10", "3.13", "3.14"]
25+
python-version: ["3.10", "3.14"]
2626

2727
steps:
2828
- uses: actions/checkout@v7
@@ -31,14 +31,17 @@ jobs:
3131
with:
3232
python-version: ${{ matrix.python-version }}
3333
- name: Install dependencies
34+
env:
35+
# Pull requests are routed by their target branch, pushes by their own branch
36+
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
3437
run: |
3538
sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils
3639
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
3740
python -m pip install --upgrade pip
3841
python -m pip install ruff pytest
3942
pip install PyQt5 setuptools numpy Cython
4043
python setup.py build_ext --inplace
41-
if [ "${{ github.ref_name }}" = "develop" ]; then
44+
if [ "$TARGET_BRANCH" = "develop" ]; then
4245
pip uninstall -y guidata PythonQwt
4346
cd ..
4447
git clone --depth 1 --branch develop https://github.com/PlotPyStack/guidata.git
@@ -53,7 +56,7 @@ jobs:
5356
pip install -r deps.txt
5457
# Install PlotPy without dependencies
5558
pip install --no-deps .
56-
elif [ "${{ github.ref_name }}" = "release" ]; then
59+
elif [ "$TARGET_BRANCH" = "release" ]; then
5760
pip uninstall -y guidata PythonQwt
5861
cd ..
5962
# Try cloning guidata from release, fallback to main or master

‎.github/workflows/test_pyqt6.yml‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3+
4+
# Inspired from https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html#github-actions
5+
6+
name: Build, install and test with PyQt6
7+
8+
on:
9+
push:
10+
branches: [ "master", "develop", "release" ]
11+
pull_request:
12+
branches: [ "master", "develop", "release" ]
13+
workflow_call:
14+
15+
jobs:
16+
build:
17+
18+
env:
19+
DISPLAY: ':99.0'
20+
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 20
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
python-version: ["3.10", "3.14"]
27+
28+
steps:
29+
- uses: actions/checkout@v7
30+
- name: Set up Python ${{ matrix.python-version }}
31+
uses: actions/setup-python@v7
32+
with:
33+
python-version: ${{ matrix.python-version }}
34+
- name: Install dependencies
35+
env:
36+
# Pull requests are routed by their target branch, pushes by their own branch
37+
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
38+
run: |
39+
sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils libegl1 libgl1 libopengl0 libxcb-cursor0
40+
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
41+
python -m pip install --upgrade pip
42+
python -m pip install ruff pytest
43+
pip install PyQt6 setuptools numpy Cython
44+
python setup.py build_ext --inplace
45+
if [ "$TARGET_BRANCH" = "develop" ]; then
46+
pip uninstall -y guidata PythonQwt
47+
cd ..
48+
git clone --depth 1 --branch develop https://github.com/PlotPyStack/guidata.git
49+
git clone --depth 1 https://github.com/PlotPyStack/PythonQwt.git
50+
cd PlotPy
51+
pip install -e ../guidata
52+
pip install -e ../PythonQwt
53+
# Install tomli for TOML parsing (safe if already present)
54+
pip install tomli
55+
# Extract dependencies and save to file, then install
56+
python -c "import tomli; f=open('pyproject.toml','rb'); data=tomli.load(f); deps=[d for d in data['project']['dependencies'] if not any(p in d for p in ['guidata', 'PythonQwt'])]; open('deps.txt','w').write('\n'.join(deps))"
57+
pip install -r deps.txt
58+
# Install PlotPy without dependencies
59+
pip install --no-deps .
60+
elif [ "$TARGET_BRANCH" = "release" ]; then
61+
pip uninstall -y guidata PythonQwt
62+
cd ..
63+
# Try cloning guidata from release, fallback to main or master
64+
git clone --depth 1 --branch release https://github.com/PlotPyStack/guidata.git || git clone --depth 1 https://github.com/PlotPyStack/guidata.git || git clone --depth 1 --branch master https://github.com/PlotPyStack/guidata.git
65+
# Try cloning PythonQwt from main or master
66+
git clone --depth 1 https://github.com/PlotPyStack/PythonQwt.git || git clone --depth 1 --branch master https://github.com/PlotPyStack/PythonQwt.git
67+
cd PlotPy
68+
pip install -e ../guidata
69+
pip install -e ../PythonQwt
70+
# Install tomli for TOML parsing (safe if already present)
71+
pip install tomli
72+
# Extract dependencies and save to file, then install
73+
python -c "import tomli; f=open('pyproject.toml','rb'); data=tomli.load(f); deps=[d for d in data['project']['dependencies'] if not any(p in d for p in ['guidata', 'PythonQwt'])]; open('deps.txt','w').write('\n'.join(deps))"
74+
pip install -r deps.txt
75+
# Install PlotPy without dependencies
76+
pip install --no-deps .
77+
else
78+
# Install from PyPI normally for master branch
79+
pip install .[test]
80+
fi
81+
- name: Lint with Ruff
82+
run: ruff check --output-format=github plotpy
83+
- name: Test with pytest
84+
run: |
85+
pytest

‎.github/workflows/test_pyside6.yml‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3+
4+
# Inspired from https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html#github-actions
5+
6+
name: Build, install and test with PySide6
7+
8+
on:
9+
push:
10+
branches: [ "master", "develop", "release" ]
11+
pull_request:
12+
branches: [ "master", "develop", "release" ]
13+
workflow_call:
14+
15+
jobs:
16+
build:
17+
18+
env:
19+
DISPLAY: ':99.0'
20+
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 20
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
python-version: ["3.10", "3.14"]
27+
28+
steps:
29+
- uses: actions/checkout@v7
30+
- name: Set up Python ${{ matrix.python-version }}
31+
uses: actions/setup-python@v7
32+
with:
33+
python-version: ${{ matrix.python-version }}
34+
- name: Install dependencies
35+
env:
36+
# Pull requests are routed by their target branch, pushes by their own branch
37+
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
38+
run: |
39+
sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils libegl1 libgl1 libopengl0 libxcb-cursor0
40+
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
41+
python -m pip install --upgrade pip
42+
python -m pip install ruff pytest
43+
# PySide6 6.12.0 drops a reference to None on each void call: fatal before Python 3.12
44+
pip install "PySide6!=6.12.0; python_version < '3.12'" "PySide6; python_version >= '3.12'" setuptools numpy Cython
45+
python setup.py build_ext --inplace
46+
if [ "$TARGET_BRANCH" = "develop" ]; then
47+
pip uninstall -y guidata PythonQwt
48+
cd ..
49+
git clone --depth 1 --branch develop https://github.com/PlotPyStack/guidata.git
50+
git clone --depth 1 https://github.com/PlotPyStack/PythonQwt.git
51+
cd PlotPy
52+
pip install -e ../guidata
53+
pip install -e ../PythonQwt
54+
# Install tomli for TOML parsing (safe if already present)
55+
pip install tomli
56+
# Extract dependencies and save to file, then install
57+
python -c "import tomli; f=open('pyproject.toml','rb'); data=tomli.load(f); deps=[d for d in data['project']['dependencies'] if not any(p in d for p in ['guidata', 'PythonQwt'])]; open('deps.txt','w').write('\n'.join(deps))"
58+
pip install -r deps.txt
59+
# Install PlotPy without dependencies
60+
pip install --no-deps .
61+
elif [ "$TARGET_BRANCH" = "release" ]; then
62+
pip uninstall -y guidata PythonQwt
63+
cd ..
64+
# Try cloning guidata from release, fallback to main or master
65+
git clone --depth 1 --branch release https://github.com/PlotPyStack/guidata.git || git clone --depth 1 https://github.com/PlotPyStack/guidata.git || git clone --depth 1 --branch master https://github.com/PlotPyStack/guidata.git
66+
# Try cloning PythonQwt from main or master
67+
git clone --depth 1 https://github.com/PlotPyStack/PythonQwt.git || git clone --depth 1 --branch master https://github.com/PlotPyStack/PythonQwt.git
68+
cd PlotPy
69+
pip install -e ../guidata
70+
pip install -e ../PythonQwt
71+
# Install tomli for TOML parsing (safe if already present)
72+
pip install tomli
73+
# Extract dependencies and save to file, then install
74+
python -c "import tomli; f=open('pyproject.toml','rb'); data=tomli.load(f); deps=[d for d in data['project']['dependencies'] if not any(p in d for p in ['guidata', 'PythonQwt'])]; open('deps.txt','w').write('\n'.join(deps))"
75+
pip install -r deps.txt
76+
# Install PlotPy without dependencies
77+
pip install --no-deps .
78+
else
79+
# Install from PyPI normally for master branch
80+
pip install .[test]
81+
fi
82+
- name: Lint with Ruff
83+
run: ruff check --output-format=github plotpy
84+
- name: Test with pytest
85+
run: |
86+
pytest

‎doc/release_notes/release_2.12.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@
2424
⚙️ Dependencies:
2525

2626
* Python 3.10 or later is now required, and wheels are no longer built for Python 3.9. Python 3.9 users must keep an earlier compatible release (PlotPy 2.11.x) (closes [Issue #74](https://github.com/PlotPyStack/PlotPy/issues/74))
27+
28+
🔧 Other changes:
29+
30+
* **Continuous integration** — PlotPy is now also tested with PyQt6 and PySide6 (Python 3.10 and 3.14) on each push and pull request, and with PyQt6 before each PyPI release. Pull requests targeting the `develop` or `release` branch are now tested against the development versions of guidata and PythonQwt, as pushes to these branches already were (closes [Issue #69](https://github.com/PlotPyStack/PlotPy/issues/69))
31+
* **Test suite** — The `SnapshotTool` annotation test no longer hangs when run on its own, on Windows or with Qt 6: it waited indefinitely for the snapshot dialog to be closed

‎plotpy/tests/unit/test_annotation_tools.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66
import pytest
77
from guidata.qthelpers import exec_dialog, execenv, qt_app_context
8+
from qtpy import QtWidgets as QW
89

910
from plotpy.interfaces.items import IBasePlotItem, IShapeItemType
1011
from plotpy.tests import vistools as ptv
@@ -97,13 +98,29 @@ def _test_annotation_tools(tool_classes: tuple[type[InteractiveTool], ...]):
9798
for tool_class in tool_classes:
9899
tool = win.manager.get_tool(tool_class)
99100
assert tool is not None
101+
snapshot_calls: list[tuple] | None = None
102+
if execenv.unattended and isinstance(tool, SnapshotTool):
103+
# The snapshot action opens modal dialogs (destination size, options,
104+
# file name) which are never closed in unattended mode
105+
snapshot_calls = []
106+
tool.action_func = lambda *args, calls=snapshot_calls: calls.append(
107+
args
108+
)
100109
tool.activate()
101110
x_path = np.linspace(0, 0.5, 100)
102111
y_path = np.linspace(0, 0.5, 100)
103112
with execenv.context(accept_dialogs=True):
104113
drag_mouse(win, x_path, y_path)
114+
# Run the actions deferred by a zero-delay timer (e.g. the snapshot
115+
# action) within this test rather than in a later event loop
116+
QW.QApplication.processEvents()
105117
if hasattr(tool_class, "SWITCH_TO_DEFAULT_TOOL"):
106118
assert win.manager.get_default_tool() == default_tool
119+
if snapshot_calls is not None:
120+
assert len(snapshot_calls) == 1
121+
snap_plot, p0, p1 = snapshot_calls[0]
122+
assert snap_plot is plot
123+
assert p0.x() < p1.x() and p0.y() < p1.y()
107124
plot.unselect_all()
108125
shape_items: list[IBasePlotItem] = plot.get_items(item_type=IShapeItemType)
109126
plot.select_some_items(shape_items)

0 commit comments

Comments
 (0)