diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f0665efc164..2089f60cc99 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -80,7 +80,7 @@ stages: jobs: - job: Ultraslow_PG pool: - vmImage: 'ubuntu-20.04' + vmImage: 'ubuntu-22.04' variables: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' @@ -107,7 +107,7 @@ stages: - bash: | set -e python -m pip install --progress-bar off --upgrade pip setuptools wheel - python -m pip install --progress-bar off mne-qt-browser[opengl] pyvista scikit-learn pytest-error-for-skips python-picard "PySide6!=6.3.0,!=6.4.0,!=6.4.0.1,!=6.5.0" qtpy + python -m pip install --progress-bar off mne-qt-browser[opengl] pyvista scikit-learn pytest-error-for-skips python-picard "PySide6!=6.5.1" qtpy python -m pip uninstall -yq mne python -m pip install --progress-bar off --upgrade -e .[test] displayName: 'Install dependencies with pip' @@ -149,7 +149,7 @@ stages: - job: Qt pool: - vmImage: 'ubuntu-20.04' + vmImage: 'ubuntu-22.04' variables: DISPLAY: ':99' OPENBLAS_NUM_THREADS: '1' @@ -196,6 +196,8 @@ stages: - bash: | set -e python -m pip install PyQt6 + # Uncomment if "xcb not found" Qt errors/segfaults come up again + # LD_DEBUG=libs python -c "from PyQt6.QtWidgets import QApplication, QWidget; app = QApplication([]); import matplotlib; matplotlib.use('QtAgg'); import matplotlib.pyplot as plt; plt.figure()" mne sys_info -pd mne sys_info -pd | grep "qtpy .* (PyQt6=.*)$" pytest -m "not slowtest" ${TEST_OPTIONS} diff --git a/doc/changes/1.4.inc b/doc/changes/1.4.inc index e2e727266a4..c48b0a9a2a4 100644 --- a/doc/changes/1.4.inc +++ b/doc/changes/1.4.inc @@ -1,3 +1,13 @@ +.. _changes_1_4_2: + +Version 1.4.2 (2023-06-06) +-------------------------- + +Bugs +~~~~ + +- Fix bug with PySide6 compatibility (:gh:`11721` by `Eric Larson`_) + .. _changes_1_4_1: Version 1.4.1 (2023-06-05) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index ecfc8bc3f01..05d2aa132e7 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -43,7 +43,11 @@ import pyvista from pyvista import Plotter, PolyData, Line, close_all, UnstructuredGrid from pyvistaqt import BackgroundPlotter - from pyvista.plotting.plotting import _ALL_PLOTTERS + + try: + from pyvista.plotting.plotter import _ALL_PLOTTERS + except Exception: # PV < 0.40 + from pyvista.plotting.plotting import _ALL_PLOTTERS from vtkmodules.vtkCommonCore import vtkCommand, vtkLookupTable, VTK_UNSIGNED_CHAR from vtkmodules.vtkCommonDataModel import VTK_VERTEX, vtkPiecewiseFunction diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 07b2da7aaf6..db1d8ba1d2f 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -269,7 +269,8 @@ class _Button(QPushButton, _AbstractButton, _Widget, metaclass=_BaseWidget): def __init__(self, value, callback, icon=None): _AbstractButton.__init__(value=value, callback=callback) _Widget.__init__(self) - QPushButton.__init__(self) + with _disabled_init(_AbstractButton): + QPushButton.__init__(self) self.setText(value) self.released.connect(callback) if icon: @@ -288,7 +289,8 @@ def __init__(self, value, rng, callback, horizontal=True): value=value, rng=rng, callback=callback, horizontal=horizontal ) _Widget.__init__(self) - QSlider.__init__(self, Qt.Horizontal if horizontal else Qt.Vertical) + with _disabled_init(_AbstractSlider): + QSlider.__init__(self, Qt.Horizontal if horizontal else Qt.Vertical) self.setMinimum(rng[0]) self.setMaximum(rng[1]) self.setValue(value) @@ -322,7 +324,8 @@ class _CheckBox(QCheckBox, _AbstractCheckBox, _Widget, metaclass=_BaseWidget): def __init__(self, value, callback): _AbstractCheckBox.__init__(value=value, callback=callback) _Widget.__init__(self) - QCheckBox.__init__(self) + with _disabled_init(_AbstractCheckBox): + QCheckBox.__init__(self) self.setChecked(value) self.stateChanged.connect(lambda x: callback(bool(x))) @@ -337,7 +340,8 @@ class _SpinBox(QDoubleSpinBox, _AbstractSpinBox, _Widget, metaclass=_BaseWidget) def __init__(self, value, rng, callback, step=None): _AbstractSpinBox.__init__(value=value, rng=rng, callback=callback, step=step) _Widget.__init__(self) - QDoubleSpinBox.__init__(self) + with _disabled_init(_AbstractSpinBox): + QDoubleSpinBox.__init__(self) self.setAlignment(Qt.AlignCenter) self.setMinimum(rng[0]) self.setMaximum(rng[1]) @@ -360,7 +364,8 @@ class _ComboBox(QComboBox, _AbstractComboBox, _Widget, metaclass=_BaseWidget): def __init__(self, value, items, callback): _AbstractComboBox.__init__(value=value, items=items, callback=callback) _Widget.__init__(self) - QComboBox.__init__(self) + with _disabled_init(_AbstractComboBox): + QComboBox.__init__(self) self.addItems(items) self.setCurrentText(value) self.currentTextChanged.connect(callback) @@ -377,7 +382,8 @@ class _RadioButtons(QVBoxLayout, _AbstractRadioButtons, _Widget, metaclass=_Base def __init__(self, value, items, callback): _AbstractRadioButtons.__init__(value=value, items=items, callback=callback) _Widget.__init__(self) - QVBoxLayout.__init__(self) + with _disabled_init(_AbstractRadioButtons): + QVBoxLayout.__init__(self) self._button_group = QButtonGroup() self._button_group.setExclusive(True) for val in items: @@ -455,7 +461,8 @@ class _PlayMenu(QVBoxLayout, _AbstractPlayMenu, _Widget, metaclass=_BaseWidget): def __init__(self, value, rng, callback): _AbstractPlayMenu.__init__(value=value, rng=rng, callback=callback) _Widget.__init__(self) - QVBoxLayout.__init__(self) + with _disabled_init(_AbstractPlayMenu): + QVBoxLayout.__init__(self) self._slider = QSlider(Qt.Horizontal) self._slider.setMinimum(rng[0]) self._slider.setMaximum(rng[1]) @@ -540,7 +547,8 @@ def __init__( window=window, ) _Widget.__init__(self) - QMessageBox.__init__(self, parent=window) + with _disabled_init(_AbstractPopup): + QMessageBox.__init__(self, parent=window) self.setWindowTitle(title) self.setText(text) # icon is one of _Dialog.supported_icon_names @@ -693,9 +701,22 @@ def _set_size(self, width=None, height=None): # https://github.com/mne-tools/mne-python/issues/9182 +# This is necessary to make PySide6 happy -- something weird with the +# __init__ calling causes the _AbstractXYZ class __init__ to be called twice +@contextmanager +def _disabled_init(klass): + orig = klass.__init__ + klass.__init__ = lambda *args, **kwargs: None + try: + yield + finally: + klass.__init__ = orig + + class _MNEMainWindow(MainWindow): def __init__(self, parent=None, title=None, size=None): - MainWindow.__init__(self, parent=parent, title=title, size=size) + with _disabled_init(_Widget): + MainWindow.__init__(self, parent=parent, title=title, size=size) self.setAttribute(Qt.WA_ShowWithoutActivating, True) self.setAttribute(Qt.WA_DeleteOnClose, True) diff --git a/requirements.txt b/requirements.txt index 6baf9465326..68c2553b87d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ h5io packaging pymatreader qtpy -PySide6!=6.3.0,!=6.4.0,!=6.4.0.1,!=6.5.0 # incompat with Matplotlib 3.6.1 and qtpy +PySide6!=6.5.1 pyobjc-framework-Cocoa>=5.2.0; platform_system=="Darwin" sip scikit-learn diff --git a/setup.py b/setup.py index d6cecb00012..6406a9e058c 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ if "SETUPTOOLS_SCM_PRETEND_VERSION" not in os.environ: - os.environ["SETUPTOOLS_SCM_PRETEND_VERSION"] = "1.4.1" + os.environ["SETUPTOOLS_SCM_PRETEND_VERSION"] = "1.4.2" def parse_requirements_file(fname):