diff --git a/IPython/external/qt_for_kernel.py b/IPython/external/qt_for_kernel.py index 33cf622..1a94e7e 100644 --- a/IPython/external/qt_for_kernel.py +++ b/IPython/external/qt_for_kernel.py @@ -33,10 +33,10 @@ import sys from IPython.utils.version import check_version from IPython.external.qt_loaders import (load_qt, loaded_api, QT_API_PYSIDE, - QT_API_PYQT, QT_API_PYQT5, + QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQTv1, QT_API_PYQT_DEFAULT) -_qt_apis = (QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQTv1, +_qt_apis = (QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQTv1, QT_API_PYQT_DEFAULT) #Constraints placed on an imported matplotlib @@ -83,7 +83,8 @@ def get_options(): qt_api = os.environ.get('QT_API', None) if qt_api is None: #no ETS variable. Ask mpl, then use default fallback path - return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYQT5] + return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, + QT_API_PYQT5, QT_API_PYSIDE2] elif qt_api not in _qt_apis: raise RuntimeError("Invalid Qt API %r, valid values are: %r" % (qt_api, ', '.join(_qt_apis))) diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index e16cb2a..0fa95f4 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -64,14 +64,21 @@ def commit_api(api): """Commit to a particular API, and trigger ImportErrors on subsequent dangerous imports""" + if api == QT_API_PYSIDE2: + ID.forbid('PySide') + ID.forbid('PyQt4') + ID.forbid('PyQt5') if api == QT_API_PYSIDE: + ID.forbid('PySide2') ID.forbid('PyQt4') ID.forbid('PyQt5') elif api == QT_API_PYQT5: + ID.forbid('PySide2') ID.forbid('PySide') ID.forbid('PyQt4') else: # There are three other possibilities, all representing PyQt4 ID.forbid('PyQt5') + ID.forbid('PySide2') ID.forbid('PySide') @@ -83,7 +90,7 @@ def loaded_api(): Returns ------- - None, 'pyside', 'pyqt', 'pyqt5', or 'pyqtv1' + None, 'pyside2', 'pyside', 'pyqt', 'pyqt5', or 'pyqtv1' """ if 'PyQt4.QtCore' in sys.modules: if qtapi_version() == 2: @@ -92,19 +99,21 @@ def loaded_api(): return QT_API_PYQTv1 elif 'PySide.QtCore' in sys.modules: return QT_API_PYSIDE + elif 'PySide2.QtCore' in sys.modules: + return QT_API_PYSIDE2 elif 'PyQt5.QtCore' in sys.modules: return QT_API_PYQT5 return None def has_binding(api): - """Safely check for PyQt4/5 or PySide, without importing submodules + """Safely check for PyQt4/5, PySide or PySide2, without importing submodules Supports Python <= 3.3 Parameters ---------- - api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyqtdefault'] + api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] Which module to check for Returns @@ -124,7 +133,7 @@ def has_binding(api): imp.find_module('QtCore', mod.__path__) imp.find_module('QtGui', mod.__path__) imp.find_module('QtSvg', mod.__path__) - if api == QT_API_PYQT5: + if api in (QT_API_PYQT5, QT_API_PYSIDE2): # QT5 requires QtWidgets too imp.find_module('QtWidgets', mod.__path__) @@ -137,13 +146,13 @@ def has_binding(api): return False def has_binding_new(api): - """Safely check for PyQt4/5 or PySide, without importing submodules + """Safely check for PyQt4/5, PySide or PySide2, without importing submodules Supports Python >= 3.4 Parameters ---------- - api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyqtdefault'] + api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] Which module to check for Returns @@ -277,6 +286,22 @@ def import_pyside(): from PySide import QtGui, QtCore, QtSvg return QtCore, QtGui, QtSvg, QT_API_PYSIDE +def import_pyside2(): + """ + Import PySide2 + + ImportErrors raised within this function are non-recoverable + """ + from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType('QtGuiCompat') + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + + return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2 + def load_qt(api_options): """ @@ -288,7 +313,7 @@ def load_qt(api_options): Parameters ---------- api_options: List of strings - The order of APIs to try. Valid items are 'pyside', + The order of APIs to try. Valid items are 'pyside', 'pyside2', 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault' Returns @@ -304,12 +329,14 @@ def load_qt(api_options): bindings (either becaues they aren't installed, or because an incompatible library has already been installed) """ - loaders = {QT_API_PYSIDE: import_pyside, + loaders = { + QT_API_PYSIDE2: import_pyside2, + QT_API_PYSIDE: import_pyside, QT_API_PYQT: import_pyqt4, QT_API_PYQT5: import_pyqt5, QT_API_PYQTv1: partial(import_pyqt4, version=1), QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None) - } + } for api in api_options: @@ -329,16 +356,18 @@ def load_qt(api_options): else: raise ImportError(""" Could not load requested Qt binding. Please ensure that - PyQt4 >= 4.7, PyQt5 or PySide >= 1.0.3 is available, + PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available, and only one is imported per session. Currently-imported Qt library: %r PyQt4 available (requires QtCore, QtGui, QtSvg): %s PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s PySide >= 1.0.3 installed: %s + PySide2 installed: %s Tried to load: %r """ % (loaded_api(), has_binding(QT_API_PYQT), has_binding(QT_API_PYQT5), has_binding(QT_API_PYSIDE), + has_binding(QT_API_PYSIDE2), api_options))