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