From 84aa9f77e61df45682dd7867d35191184414c7b0 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 5 Oct 2015 21:40:20 -0400 Subject: [PATCH 1/5] TST: properly fence-post qt{4,5} backend tests This issue is that the new 'default' qt version is 5 if it is available. In the tests the backend is Agg so we are hitting the fall through behavior. If both pyqt4 and pyqt5 are installed, then `QtCore` will be from PyQt5, but the backend_qt4Agg code assumes that it is seeing the PyQt4 version of the classes. This results in error on `__init__` due to the change between PyQt4 and PyQt5. Added test_backend_qt5.py to white listed tests. closes #5194 --- lib/matplotlib/__init__.py | 1 + lib/matplotlib/tests/test_backend_qt4.py | 9 +++++++-- lib/matplotlib/tests/test_backend_qt5.py | 20 +++++++++++--------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 88842ae0c0cf..8f7378e9ef78 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1429,6 +1429,7 @@ def tk_window_focus(): 'matplotlib.tests.test_backend_pgf', 'matplotlib.tests.test_backend_ps', 'matplotlib.tests.test_backend_qt4', + 'matplotlib.tests.test_backend_qt5', 'matplotlib.tests.test_backend_svg', 'matplotlib.tests.test_basic', 'matplotlib.tests.test_bbox_tight', diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index b59eda33e60f..f32cf7459b5a 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -17,6 +17,7 @@ try: from matplotlib.backends.qt_compat import QtCore + from matplotlib.backends.backend_qt4 import (MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) @@ -24,7 +25,11 @@ _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] _, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT] - HAS_QT = True + py_qt_ver = int(QtCore.PYQT_VERSION_STR.split('.')[0]) + if py_qt_ver != 4: + HAS_QT = False + else: + HAS_QT = True except ImportError: HAS_QT = False @@ -33,7 +38,7 @@ @knownfailureif(not HAS_QT) @switch_backend('Qt4Agg') def test_fig_close(): - #save the state of Gcf.figs + # save the state of Gcf.figs init_figs = copy.copy(Gcf.figs) # make a figure using pyplot interface diff --git a/lib/matplotlib/tests/test_backend_qt5.py b/lib/matplotlib/tests/test_backend_qt5.py index 9fa51602cc76..b5ebf2fe1279 100644 --- a/lib/matplotlib/tests/test_backend_qt5.py +++ b/lib/matplotlib/tests/test_backend_qt5.py @@ -4,7 +4,7 @@ from matplotlib.externals import six from matplotlib import pyplot as plt -from matplotlib.testing.decorators import cleanup +from matplotlib.testing.decorators import cleanup, switch_backend from matplotlib.testing.decorators import knownfailureif from matplotlib._pylab_helpers import Gcf import copy @@ -24,17 +24,19 @@ _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] _, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT] - HAS_QT = True + py_qt_ver = int(QtCore.PYQT_VERSION_STR.split('.')[0]) + if py_qt_ver != 5: + HAS_QT = False + else: + HAS_QT = True except ImportError: HAS_QT = False @cleanup @knownfailureif(not HAS_QT) +@switch_backend('Qt5Agg') def test_fig_close(): - # force switch to the Qt5 backend - plt.switch_backend('Qt5Agg') - # save the state of Gcf.figs init_figs = copy.copy(Gcf.figs) @@ -50,6 +52,7 @@ def test_fig_close(): assert(init_figs == Gcf.figs) +@switch_backend('Qt5Agg') def assert_correct_key(qt_key, qt_mods, answer): """ Make a figure @@ -57,7 +60,6 @@ def assert_correct_key(qt_key, qt_mods, answer): Catch the event Assert sent and caught keys are the same """ - plt.switch_backend('Qt5Agg') qt_canvas = plt.figure().canvas event = mock.Mock() @@ -101,7 +103,7 @@ def test_control(): def test_unicode_upper(): assert_correct_key(QtCore.Qt.Key_Aacute, ShiftModifier, - unichr(193)) + chr(193)) @cleanup @@ -109,7 +111,7 @@ def test_unicode_upper(): def test_unicode_lower(): assert_correct_key(QtCore.Qt.Key_Aacute, QtCore.Qt.NoModifier, - unichr(225)) + chr(225)) @cleanup @@ -133,7 +135,7 @@ def test_control_alt(): def test_modifier_order(): assert_correct_key(QtCore.Qt.Key_Aacute, (ControlModifier | AltModifier | SuperModifier), - 'ctrl+alt+super+' + unichr(225)) + 'ctrl+alt+super+' + chr(225)) @cleanup From f67ea3c62f33f0e675b3e091044c4983ae01b085 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 6 Oct 2015 00:16:50 -0400 Subject: [PATCH 2/5] FIX: mro issue with pyside/pyqt4 Do not let a `super` be called or the the c++ base-classes will be initialized twice which causes havoc. - pyside failed on figure creation - pyqt4 may segfault on exit closes #5196 --- lib/matplotlib/backends/backend_qt4agg.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index 392d5d5cd073..d36991c84110 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -15,7 +15,7 @@ from matplotlib.figure import Figure -from .backend_qt5agg import FigureCanvasQTAggBase +from .backend_qt5agg import FigureCanvasQTAggBase as _FigureCanvasQTAggBase from .backend_agg import FigureCanvasAgg from .backend_qt4 import QtCore @@ -54,6 +54,11 @@ def new_figure_manager_given_figure(num, figure): return FigureManagerQT(canvas, num) +class FigureCanvasQTAggBase(_FigureCanvasQTAggBase): + def __init__(self, figure): + self._agg_draw_pending = False + + class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT, FigureCanvasAgg): """ From cdc52bfecb95477972d5345ccff9719c02372f0f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 6 Oct 2015 01:00:36 -0400 Subject: [PATCH 3/5] FIX: be more careful about pyside If: 1. backend is not Qt{4,5}Agg 2. PyQt4 and PySide are installed, PyQt5 is not 3. backend.qt4 == 'PySide' The fall through will start trying to import PyQt5 (so sip will be imported as it is installed for PyQt4). It will then fall back to Qt4 using the binding specified in the rcparam ('PySide') which will then miss the qt4 import conditionals which means QtCore is never imported which means we get name errors from the pyqt / pyside patch up code. This catches those exceptions and gives pyside a chance to be imported. --- lib/matplotlib/backends/qt_compat.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 326f0f639383..67d0687a7ca7 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -139,19 +139,22 @@ def _getSaveFileName(*args, **kwargs): # call to getapi() can fail in older versions of sip def _getSaveFileName(*args, **kwargs): return QtGui.QFileDialog.getSaveFileName(*args, **kwargs), None - - # Alias PyQt-specific functions for PySide compatibility. - QtCore.Signal = QtCore.pyqtSignal try: - QtCore.Slot = QtCore.pyqtSlot - except AttributeError: - # Not a perfect match but works in simple cases - QtCore.Slot = QtCore.pyqtSignature - - QtCore.Property = QtCore.pyqtProperty - __version__ = QtCore.PYQT_VERSION_STR + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + try: + QtCore.Slot = QtCore.pyqtSlot + except AttributeError: + # Not a perfect match but works in simple cases + QtCore.Slot = QtCore.pyqtSignature + + QtCore.Property = QtCore.pyqtProperty + __version__ = QtCore.PYQT_VERSION_STR + except NameError: + # QtCore did not get imported, fall back to pyside + QT_API = QT_API_PYSIDE -else: # try importing pyside +if QT_API == QT_API_PYSIDE: # try importing pyside try: from PySide import QtCore, QtGui, __version__, __version_info__ except ImportError: From d0153b7c86e44a7a8acfbc593da03c7e8c9d125b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 6 Oct 2015 01:08:03 -0400 Subject: [PATCH 4/5] TST: be a bit more defensive about imports/HAS_QT --- lib/matplotlib/tests/test_backend_qt4.py | 17 +++++++++++------ lib/matplotlib/tests/test_backend_qt5.py | 14 +++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index f32cf7459b5a..e2fe57a8246b 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -7,6 +7,7 @@ from matplotlib.testing.decorators import cleanup, switch_backend from matplotlib.testing.decorators import knownfailureif from matplotlib._pylab_helpers import Gcf +import matplotlib.style as mstyle import copy try: @@ -16,7 +17,8 @@ import mock try: - from matplotlib.backends.qt_compat import QtCore + with mstyle.context({'backend': 'Qt4Agg'}): + from matplotlib.backends.qt_compat import QtCore from matplotlib.backends.backend_qt4 import (MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) @@ -25,11 +27,14 @@ _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] _, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT] - py_qt_ver = int(QtCore.PYQT_VERSION_STR.split('.')[0]) - if py_qt_ver != 4: - HAS_QT = False - else: - HAS_QT = True + + try: + py_qt_ver = int(QtCore.PYQT_VERSION_STR.split('.')[0]) + except AttributeError: + py_qt_ver = QtCore.__version_info__[0] + print(py_qt_ver) + HAS_QT = py_qt_ver == 4 + except ImportError: HAS_QT = False diff --git a/lib/matplotlib/tests/test_backend_qt5.py b/lib/matplotlib/tests/test_backend_qt5.py index b5ebf2fe1279..8f56bbf6471c 100644 --- a/lib/matplotlib/tests/test_backend_qt5.py +++ b/lib/matplotlib/tests/test_backend_qt5.py @@ -1,12 +1,12 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) - from matplotlib.externals import six from matplotlib import pyplot as plt from matplotlib.testing.decorators import cleanup, switch_backend from matplotlib.testing.decorators import knownfailureif from matplotlib._pylab_helpers import Gcf +import matplotlib.style as mstyle import copy try: @@ -16,7 +16,8 @@ import mock try: - from matplotlib.backends.qt_compat import QtCore + with mstyle.context({'backend': 'Qt5Agg'}): + from matplotlib.backends.qt_compat import QtCore, __version__ from matplotlib.backends.backend_qt5 import (MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT) @@ -24,11 +25,10 @@ _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] _, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT] - py_qt_ver = int(QtCore.PYQT_VERSION_STR.split('.')[0]) - if py_qt_ver != 5: - HAS_QT = False - else: - HAS_QT = True + + py_qt_ver = int(__version__.split('.')[0]) + HAS_QT = py_qt_ver == 5 + except ImportError: HAS_QT = False From ada2f99e13da03c9352e0c528f1b6feaa2863dc3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 6 Oct 2015 08:50:40 -0400 Subject: [PATCH 5/5] FIX: restore lpy compat --- lib/matplotlib/tests/test_backend_qt5.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt5.py b/lib/matplotlib/tests/test_backend_qt5.py index 8f56bbf6471c..1e6967122fa4 100644 --- a/lib/matplotlib/tests/test_backend_qt5.py +++ b/lib/matplotlib/tests/test_backend_qt5.py @@ -103,7 +103,7 @@ def test_control(): def test_unicode_upper(): assert_correct_key(QtCore.Qt.Key_Aacute, ShiftModifier, - chr(193)) + six.unichr(193)) @cleanup @@ -111,7 +111,7 @@ def test_unicode_upper(): def test_unicode_lower(): assert_correct_key(QtCore.Qt.Key_Aacute, QtCore.Qt.NoModifier, - chr(225)) + six.unichr(225)) @cleanup @@ -135,7 +135,7 @@ def test_control_alt(): def test_modifier_order(): assert_correct_key(QtCore.Qt.Key_Aacute, (ControlModifier | AltModifier | SuperModifier), - 'ctrl+alt+super+' + chr(225)) + 'ctrl+alt+super+' + six.unichr(225)) @cleanup