1""" 2This module contains factory functions that attempt 3to return Qt submodules from the various python Qt bindings. 4 5It also protects against double-importing Qt with different 6bindings, which is unstable and likely to crash 7 8This is used primarily by qt and qt_for_kernel, and shouldn't 9be accessed directly from the outside 10""" 11import sys 12from functools import partial 13 14from pydev_ipython.version import check_version 15 16# Available APIs. 17QT_API_PYQT = 'pyqt' 18QT_API_PYQTv1 = 'pyqtv1' 19QT_API_PYQT_DEFAULT = 'pyqtdefault' # don't set SIP explicitly 20QT_API_PYSIDE = 'pyside' 21QT_API_PYQT5 = 'pyqt5' 22 23 24class ImportDenier(object): 25 """Import Hook that will guard against bad Qt imports 26 once IPython commits to a specific binding 27 """ 28 29 def __init__(self): 30 self.__forbidden = None 31 32 def forbid(self, module_name): 33 sys.modules.pop(module_name, None) 34 self.__forbidden = module_name 35 36 def find_module(self, mod_name, pth): 37 if pth: 38 return 39 if mod_name == self.__forbidden: 40 return self 41 42 def load_module(self, mod_name): 43 raise ImportError(""" 44 Importing %s disabled by IPython, which has 45 already imported an Incompatible QT Binding: %s 46 """ % (mod_name, loaded_api())) 47 48ID = ImportDenier() 49sys.meta_path.append(ID) 50 51 52def commit_api(api): 53 """Commit to a particular API, and trigger ImportErrors on subsequent 54 dangerous imports""" 55 56 if api == QT_API_PYSIDE: 57 ID.forbid('PyQt4') 58 ID.forbid('PyQt5') 59 else: 60 ID.forbid('PySide') 61 62 63def loaded_api(): 64 """Return which API is loaded, if any 65 66 If this returns anything besides None, 67 importing any other Qt binding is unsafe. 68 69 Returns 70 ------- 71 None, 'pyside', 'pyqt', or 'pyqtv1' 72 """ 73 if 'PyQt4.QtCore' in sys.modules: 74 if qtapi_version() == 2: 75 return QT_API_PYQT 76 else: 77 return QT_API_PYQTv1 78 elif 'PySide.QtCore' in sys.modules: 79 return QT_API_PYSIDE 80 elif 'PyQt5.QtCore' in sys.modules: 81 return QT_API_PYQT5 82 return None 83 84 85def has_binding(api): 86 """Safely check for PyQt4 or PySide, without importing 87 submodules 88 89 Parameters 90 ---------- 91 api : str [ 'pyqtv1' | 'pyqt' | 'pyside' | 'pyqtdefault'] 92 Which module to check for 93 94 Returns 95 ------- 96 True if the relevant module appears to be importable 97 """ 98 # we can't import an incomplete pyside and pyqt4 99 # this will cause a crash in sip (#1431) 100 # check for complete presence before importing 101 module_name = {QT_API_PYSIDE: 'PySide', 102 QT_API_PYQT: 'PyQt4', 103 QT_API_PYQTv1: 'PyQt4', 104 QT_API_PYQT_DEFAULT: 'PyQt4', 105 QT_API_PYQT5: 'PyQt5', 106 } 107 module_name = module_name[api] 108 109 import imp 110 try: 111 #importing top level PyQt4/PySide module is ok... 112 mod = __import__(module_name) 113 #...importing submodules is not 114 imp.find_module('QtCore', mod.__path__) 115 imp.find_module('QtGui', mod.__path__) 116 imp.find_module('QtSvg', mod.__path__) 117 118 #we can also safely check PySide version 119 if api == QT_API_PYSIDE: 120 return check_version(mod.__version__, '1.0.3') 121 else: 122 return True 123 except ImportError: 124 return False 125 126 127def qtapi_version(): 128 """Return which QString API has been set, if any 129 130 Returns 131 ------- 132 The QString API version (1 or 2), or None if not set 133 """ 134 try: 135 import sip 136 except ImportError: 137 return 138 try: 139 return sip.getapi('QString') 140 except ValueError: 141 return 142 143 144def can_import(api): 145 """Safely query whether an API is importable, without importing it""" 146 if not has_binding(api): 147 return False 148 149 current = loaded_api() 150 if api == QT_API_PYQT_DEFAULT: 151 return current in [QT_API_PYQT, QT_API_PYQTv1, QT_API_PYQT5, None] 152 else: 153 return current in [api, None] 154 155 156def import_pyqt4(version=2): 157 """ 158 Import PyQt4 159 160 Parameters 161 ---------- 162 version : 1, 2, or None 163 Which QString/QVariant API to use. Set to None to use the system 164 default 165 166 ImportErrors raised within this function are non-recoverable 167 """ 168 # The new-style string API (version=2) automatically 169 # converts QStrings to Unicode Python strings. Also, automatically unpacks 170 # QVariants to their underlying objects. 171 import sip 172 173 if version is not None: 174 sip.setapi('QString', version) 175 sip.setapi('QVariant', version) 176 177 from PyQt4 import QtGui, QtCore, QtSvg 178 179 if not check_version(QtCore.PYQT_VERSION_STR, '4.7'): 180 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % 181 QtCore.PYQT_VERSION_STR) 182 183 # Alias PyQt-specific functions for PySide compatibility. 184 QtCore.Signal = QtCore.pyqtSignal 185 QtCore.Slot = QtCore.pyqtSlot 186 187 # query for the API version (in case version == None) 188 version = sip.getapi('QString') 189 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT 190 return QtCore, QtGui, QtSvg, api 191 192def import_pyqt5(): 193 """ 194 Import PyQt5 195 196 ImportErrors raised within this function are non-recoverable 197 """ 198 from PyQt5 import QtGui, QtCore, QtSvg 199 200 # Alias PyQt-specific functions for PySide compatibility. 201 QtCore.Signal = QtCore.pyqtSignal 202 QtCore.Slot = QtCore.pyqtSlot 203 204 return QtCore, QtGui, QtSvg, QT_API_PYQT5 205 206 207def import_pyside(): 208 """ 209 Import PySide 210 211 ImportErrors raised within this function are non-recoverable 212 """ 213 from PySide import QtGui, QtCore, QtSvg # @UnresolvedImport 214 return QtCore, QtGui, QtSvg, QT_API_PYSIDE 215 216 217def load_qt(api_options): 218 """ 219 Attempt to import Qt, given a preference list 220 of permissible bindings 221 222 It is safe to call this function multiple times. 223 224 Parameters 225 ---------- 226 api_options: List of strings 227 The order of APIs to try. Valid items are 'pyside', 228 'pyqt', and 'pyqtv1' 229 230 Returns 231 ------- 232 233 A tuple of QtCore, QtGui, QtSvg, QT_API 234 The first three are the Qt modules. The last is the 235 string indicating which module was loaded. 236 237 Raises 238 ------ 239 ImportError, if it isn't possible to import any requested 240 bindings (either becaues they aren't installed, or because 241 an incompatible library has already been installed) 242 """ 243 loaders = {QT_API_PYSIDE: import_pyside, 244 QT_API_PYQT: import_pyqt4, 245 QT_API_PYQTv1: partial(import_pyqt4, version=1), 246 QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None), 247 QT_API_PYQT5: import_pyqt5, 248 } 249 250 for api in api_options: 251 252 if api not in loaders: 253 raise RuntimeError( 254 "Invalid Qt API %r, valid values are: %r, %r, %r, %r" % 255 (api, QT_API_PYSIDE, QT_API_PYQT, 256 QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_API_PYQT5)) 257 258 if not can_import(api): 259 continue 260 261 #cannot safely recover from an ImportError during this 262 result = loaders[api]() 263 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT 264 commit_api(api) 265 return result 266 else: 267 raise ImportError(""" 268 Could not load requested Qt binding. Please ensure that 269 PyQt4 >= 4.7 or PySide >= 1.0.3 is available, 270 and only one is imported per session. 271 272 Currently-imported Qt library: %r 273 PyQt4 installed: %s 274 PyQt5 installed: %s 275 PySide >= 1.0.3 installed: %s 276 Tried to load: %r 277 """ % (loaded_api(), 278 has_binding(QT_API_PYQT), 279 has_binding(QT_API_PYQT5), 280 has_binding(QT_API_PYSIDE), 281 api_options)) 282