Skip to content

Commit d379c3f

Browse files
committed
Merge branch 'master' into develop
2 parents 36e3585 + a6fe5c5 commit d379c3f

File tree

21 files changed

+330
-98
lines changed

21 files changed

+330
-98
lines changed

.github/workflows/build_deploy.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ jobs:
1111
runs-on: ${{ matrix.os }}
1212
strategy:
1313
matrix:
14-
# macos-13 is an intel runner, macos-14 is apple silicon
15-
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
14+
os: [ubuntu-latest, windows-latest, macos-14, macos-15]
1615

1716
steps:
1817
- uses: actions/checkout@v4
@@ -23,11 +22,11 @@ jobs:
2322
pip install babel
2423
2524
- name: Build wheels
26-
uses: pypa/cibuildwheel@v2.21.2
25+
uses: pypa/cibuildwheel@v3.3.1
2726
env:
2827
CIBW_BEFORE_BUILD: pip install babel && pybabel compile -d plotpy/locale -D plotpy
2928

30-
- uses: actions/upload-artifact@v4
29+
- uses: actions/upload-artifact@v6
3130
with:
3231
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
3332
path: ./wheelhouse/*.whl
@@ -49,7 +48,7 @@ jobs:
4948
- name: Build sdist
5049
run: pipx run build --sdist
5150

52-
- uses: actions/upload-artifact@v4
51+
- uses: actions/upload-artifact@v6
5352
with:
5453
name: cibw-sdist
5554
path: dist/*.tar.gz
@@ -62,7 +61,7 @@ jobs:
6261
id-token: write
6362
if: github.event_name == 'release' && github.event.action == 'published'
6463
steps:
65-
- uses: actions/download-artifact@v4
64+
- uses: actions/download-artifact@v7
6665
with:
6766
# unpacks all CIBW artifacts into dist/
6867
pattern: cibw-*
@@ -71,4 +70,5 @@ jobs:
7170

7271
- uses: pypa/gh-action-pypi-publish@release/v1
7372
with:
73+
attestations: false
7474
password: ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/test_pyqt5.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
strategy:
2222
fail-fast: false
2323
matrix:
24-
python-version: ["3.9", "3.13"]
24+
python-version: ["3.9", "3.13", "3.14"]
2525

2626
steps:
2727
- uses: actions/checkout@v4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ __pycache__/
66
# Visual Studio Code
77
.env
88
.venv
9+
.venv*
910

1011
# C extensions
1112
*.so

.vscode/tasks.json

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,15 +251,12 @@
251251
{
252252
"label": "🔁 Reinstall guidata dev",
253253
"type": "shell",
254-
"windows": {
255-
"command": "pip uninstall -y guidata; Remove-Item -Recurse -Force .venv/Lib/site-packages/guidata -ErrorAction SilentlyContinue; pip install -e ../guidata",
256-
},
257-
"linux": {
258-
"command": "pip uninstall -y guidata && rm -rf .venv/lib/python*/site-packages/guidata && pip install -e ../guidata",
259-
},
260-
"osx": {
261-
"command": "pip uninstall -y guidata && rm -rf .venv/lib/python*/site-packages/guidata && pip install -e ../guidata",
262-
},
254+
"command": "${config:python.defaultInterpreterPath}",
255+
"args": [
256+
"scripts/run_with_env.py",
257+
"${config:python.defaultInterpreterPath}",
258+
"${workspaceFolder}/scripts/reinstall_dev.py",
259+
],
263260
"options": {
264261
"cwd": "${workspaceFolder}",
265262
"statusbar": {
@@ -521,7 +518,7 @@
521518
"label": "📊 Coverage full",
522519
"type": "shell",
523520
"windows": {
524-
"command": "${command:python.interpreterPath} -m coverage combine && ${command:python.interpreterPath} -m coverage html && start htmlcov\\index.html",
521+
"command": "${command:python.interpreterPath} -m coverage combine; if ($?) { ${command:python.interpreterPath} -m coverage html; if ($?) { start htmlcov\\index.html } }",
525522
},
526523
"linux": {
527524
"command": "${command:python.interpreterPath} -m coverage combine && ${command:python.interpreterPath} -m coverage html && xdg-open htmlcov/index.html",

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ Compatibility table:
6767

6868
| PlotPy version | PyQt5 | PyQt6 | PySide2 | PySide6 |
6969
|----------------|-------|-------|---------|---------|
70-
| 2.0-2.5 || ⚠️ || ⚠️ |
71-
| Latest |||||
70+
| 2.0-2.5 || ⚠️ | | ⚠️ |
71+
| Latest ||| ||
7272

7373
### Other dependencies and installation
7474

doc/release_notes/release_2.08.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
# Version 2.8 #
22

3-
## PlotPy Version 2.8.3 ##
3+
## PlotPy Version 2.8.4 (2026-02-14) ##
4+
5+
💥 New features:
6+
7+
* Added official support for Python 3.14
8+
9+
🛠️ Bug fixes:
10+
11+
* Fixed PySide6 compatibility issues causing segfaults in the test suite:
12+
* Use `object` instead of C++ type strings (e.g., `"QMouseEvent"`, `"QEvent"`, `"QPointF"`) in `Signal` declarations — PySide6 segfaults with C++ type name strings
13+
* Check QObject validity via `is_qobject_valid()` before accessing widgets in `__del__`, `closeEvent`, and panel close operations — PySide6 segfaults on deleted C++ objects instead of raising `RuntimeError`
14+
* Restructure `BaseSyncPlot.__init__` to defer widget operations until after Qt `__init__` completes — PySide6 requires `__init__` to have fully completed before the widget can be used as a parent
15+
* Replace deprecated `exec_()` calls with `exec()`
16+
* Fixed `test_multiline_tool` failing with PyQt6 due to smaller canvas size:
17+
* The spiral test data started at `t=0`, placing the first two points so close together that they mapped to the same pixel on PyQt6's smaller default canvas
18+
* Start the spiral at `t=2π` to ensure sufficient pixel spacing between consecutive points
19+
* Fixed pytest running all tests when selecting a single test from VS Code:
20+
* Move `plotpy` from `addopts` to `testpaths` in `[tool.pytest.ini_options]`
21+
22+
🔧 Other changes:
23+
24+
* Add missing `setuptools` to `requirements.txt` (dev)
25+
* Update GitHub Actions to use latest artifact upload and download versions
26+
* Update `cibuildwheel` version to v3.3.1 for improved wheel building
27+
* Add `.venv*` to `.gitignore` to exclude virtual environment files
28+
29+
## PlotPy Version 2.8.3 (2025-12-18) ##
430

531
💥 New features:
632

plotpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
.. _GitHub: https://github.com/PierreRaybaut/plotpy
2121
"""
2222

23-
__version__ = "2.8.2"
23+
__version__ = "2.8.3"
2424
__VERSION__ = tuple([int(number) for number in __version__.split(".")])
2525

2626
# --- Important note: DATAPATH and LOCALEPATH are used by guidata.configtools

plotpy/events.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ class ClickHandler(QC.QObject):
727727
"""
728728

729729
#: Signal emitted by ClickHandler on mouse click
730-
SIG_CLICK_EVENT = QC.Signal(object, "QEvent")
730+
SIG_CLICK_EVENT = QC.Signal(object, object)
731731

732732
def __init__(
733733
self,
@@ -1134,16 +1134,16 @@ class QtDragHandler(DragHandler):
11341134
"""Class to handle drag events using Qt signals."""
11351135

11361136
#: Signal emitted by QtDragHandler when starting tracking
1137-
SIG_START_TRACKING = QC.Signal(object, "QMouseEvent")
1137+
SIG_START_TRACKING = QC.Signal(object, object)
11381138

11391139
#: Signal emitted by QtDragHandler when stopping tracking and not moving
1140-
SIG_STOP_NOT_MOVING = QC.Signal(object, "QMouseEvent")
1140+
SIG_STOP_NOT_MOVING = QC.Signal(object, object)
11411141

11421142
#: Signal emitted by QtDragHandler when stopping tracking and moving
1143-
SIG_STOP_MOVING = QC.Signal(object, "QMouseEvent")
1143+
SIG_STOP_MOVING = QC.Signal(object, object)
11441144

11451145
#: Signal emitted by QtDragHandler when moving
1146-
SIG_MOVE = QC.Signal(object, "QMouseEvent")
1146+
SIG_MOVE = QC.Signal(object, object)
11471147

11481148
def start_tracking(self, filter: StatefulEventFilter, event: QMouseEvent) -> None:
11491149
"""Starts tracking the drag event.
@@ -1636,7 +1636,7 @@ class RectangularSelectionHandler(DragHandler):
16361636
"""
16371637

16381638
#: Signal emitted by RectangularSelectionHandler when ending selection
1639-
SIG_END_RECT = QC.Signal(object, "QPointF", "QPointF")
1639+
SIG_END_RECT = QC.Signal(object, object, object)
16401640

16411641
def __init__(
16421642
self,

plotpy/plot/base.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import numpy as np
2828
import qwt
2929
from guidata.configtools import get_font
30+
from guidata.qthelpers import is_qobject_valid
3031
from qtpy import QtCore as QC
3132
from qtpy import QtGui as QG
3233
from qtpy import QtWidgets as QW
@@ -447,18 +448,16 @@ def __del__(self):
447448
# Sometimes, an obscure exception happens when we quit an application
448449
# because if we don't remove the eventFilter it can still be called
449450
# after the filter object has been destroyed by Python.
451+
# Note: PySide6 segfaults instead of raising RuntimeError when accessing
452+
# a deleted C++ object, so we must check validity before calling methods.
453+
if not is_qobject_valid(self):
454+
return
450455
canvas: qwt.QwtPlotCanvas = self.canvas()
451-
if canvas:
456+
if canvas and is_qobject_valid(canvas) and is_qobject_valid(self.filter):
452457
try:
453458
canvas.removeEventFilter(self.filter)
454-
except RuntimeError as exc:
455-
# Depending on which widget owns the plot,
456-
# Qt may have already deleted the canvas when
457-
# the plot is deleted.
458-
if "C++ object" not in str(exc):
459-
raise
460-
except ValueError:
461-
# This happens when object has already been deleted
459+
except (RuntimeError, ValueError):
460+
# Widget/filter may have already been deleted
462461
pass
463462

464463
def update_color_mode(self) -> None:

plotpy/plot/interactive.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import numpy as np
2020
from guidata.configtools import get_icon
2121
from guidata.env import execenv
22-
from guidata.qthelpers import win32_fix_title_bar_background
22+
from guidata.qthelpers import is_qobject_valid, win32_fix_title_bar_background
2323
from qtpy import QtCore as QC
2424
from qtpy import QtGui as QG
2525
from qtpy import QtWidgets as QW
@@ -94,10 +94,9 @@ def closeEvent(self, event):
9494
if _figures.pop(figure_title) == _current_fig:
9595
_current_fig = None
9696
_current_axes = None
97-
self.itemlist.close()
98-
self.contrast.close()
99-
self.xcsw.close()
100-
self.ycsw.close()
97+
for panel in (self.itemlist, self.contrast, self.xcsw, self.ycsw):
98+
if is_qobject_valid(panel):
99+
panel.close()
101100
event.accept()
102101

103102
def add_plot(self, i, j, plot):

0 commit comments

Comments
 (0)