##// END OF EJS Templates
Fix use of pyside6 >= 6.7.0 (#14510)...
M Bussonnier -
r28842:e5d1a069 merge
parent child Browse files
Show More
@@ -1,410 +1,422 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 importlib.abc
11 import importlib.abc
12 import sys
12 import sys
13 import os
13 import os
14 import types
14 import types
15 from functools import partial, lru_cache
15 from functools import partial, lru_cache
16 import operator
16 import operator
17
17
18 # ### Available APIs.
18 # ### Available APIs.
19 # Qt6
19 # Qt6
20 QT_API_PYQT6 = "pyqt6"
20 QT_API_PYQT6 = "pyqt6"
21 QT_API_PYSIDE6 = "pyside6"
21 QT_API_PYSIDE6 = "pyside6"
22
22
23 # Qt5
23 # Qt5
24 QT_API_PYQT5 = 'pyqt5'
24 QT_API_PYQT5 = 'pyqt5'
25 QT_API_PYSIDE2 = 'pyside2'
25 QT_API_PYSIDE2 = 'pyside2'
26
26
27 # Qt4
27 # Qt4
28 # NOTE: Here for legacy matplotlib compatibility, but not really supported on the IPython side.
28 # NOTE: Here for legacy matplotlib compatibility, but not really supported on the IPython side.
29 QT_API_PYQT = "pyqt" # Force version 2
29 QT_API_PYQT = "pyqt" # Force version 2
30 QT_API_PYQTv1 = "pyqtv1" # Force version 2
30 QT_API_PYQTv1 = "pyqtv1" # Force version 2
31 QT_API_PYSIDE = "pyside"
31 QT_API_PYSIDE = "pyside"
32
32
33 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
33 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
34
34
35 api_to_module = {
35 api_to_module = {
36 # Qt6
36 # Qt6
37 QT_API_PYQT6: "PyQt6",
37 QT_API_PYQT6: "PyQt6",
38 QT_API_PYSIDE6: "PySide6",
38 QT_API_PYSIDE6: "PySide6",
39 # Qt5
39 # Qt5
40 QT_API_PYQT5: "PyQt5",
40 QT_API_PYQT5: "PyQt5",
41 QT_API_PYSIDE2: "PySide2",
41 QT_API_PYSIDE2: "PySide2",
42 # Qt4
42 # Qt4
43 QT_API_PYSIDE: "PySide",
43 QT_API_PYSIDE: "PySide",
44 QT_API_PYQT: "PyQt4",
44 QT_API_PYQT: "PyQt4",
45 QT_API_PYQTv1: "PyQt4",
45 QT_API_PYQTv1: "PyQt4",
46 # default
46 # default
47 QT_API_PYQT_DEFAULT: "PyQt6",
47 QT_API_PYQT_DEFAULT: "PyQt6",
48 }
48 }
49
49
50
50
51 class ImportDenier(importlib.abc.MetaPathFinder):
51 class ImportDenier(importlib.abc.MetaPathFinder):
52 """Import Hook that will guard against bad Qt imports
52 """Import Hook that will guard against bad Qt imports
53 once IPython commits to a specific binding
53 once IPython commits to a specific binding
54 """
54 """
55
55
56 def __init__(self):
56 def __init__(self):
57 self.__forbidden = set()
57 self.__forbidden = set()
58
58
59 def forbid(self, module_name):
59 def forbid(self, module_name):
60 sys.modules.pop(module_name, None)
60 sys.modules.pop(module_name, None)
61 self.__forbidden.add(module_name)
61 self.__forbidden.add(module_name)
62
62
63 def find_spec(self, fullname, path, target=None):
63 def find_spec(self, fullname, path, target=None):
64 if path:
64 if path:
65 return
65 return
66 if fullname in self.__forbidden:
66 if fullname in self.__forbidden:
67 raise ImportError(
67 raise ImportError(
68 """
68 """
69 Importing %s disabled by IPython, which has
69 Importing %s disabled by IPython, which has
70 already imported an Incompatible QT Binding: %s
70 already imported an Incompatible QT Binding: %s
71 """
71 """
72 % (fullname, loaded_api())
72 % (fullname, loaded_api())
73 )
73 )
74
74
75
75
76 ID = ImportDenier()
76 ID = ImportDenier()
77 sys.meta_path.insert(0, ID)
77 sys.meta_path.insert(0, ID)
78
78
79
79
80 def commit_api(api):
80 def commit_api(api):
81 """Commit to a particular API, and trigger ImportErrors on subsequent
81 """Commit to a particular API, and trigger ImportErrors on subsequent
82 dangerous imports"""
82 dangerous imports"""
83 modules = set(api_to_module.values())
83 modules = set(api_to_module.values())
84
84
85 modules.remove(api_to_module[api])
85 modules.remove(api_to_module[api])
86 for mod in modules:
86 for mod in modules:
87 ID.forbid(mod)
87 ID.forbid(mod)
88
88
89
89
90 def loaded_api():
90 def loaded_api():
91 """Return which API is loaded, if any
91 """Return which API is loaded, if any
92
92
93 If this returns anything besides None,
93 If this returns anything besides None,
94 importing any other Qt binding is unsafe.
94 importing any other Qt binding is unsafe.
95
95
96 Returns
96 Returns
97 -------
97 -------
98 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
98 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
99 """
99 """
100 if sys.modules.get("PyQt6.QtCore"):
100 if sys.modules.get("PyQt6.QtCore"):
101 return QT_API_PYQT6
101 return QT_API_PYQT6
102 elif sys.modules.get("PySide6.QtCore"):
102 elif sys.modules.get("PySide6.QtCore"):
103 return QT_API_PYSIDE6
103 return QT_API_PYSIDE6
104 elif sys.modules.get("PyQt5.QtCore"):
104 elif sys.modules.get("PyQt5.QtCore"):
105 return QT_API_PYQT5
105 return QT_API_PYQT5
106 elif sys.modules.get("PySide2.QtCore"):
106 elif sys.modules.get("PySide2.QtCore"):
107 return QT_API_PYSIDE2
107 return QT_API_PYSIDE2
108 elif sys.modules.get("PyQt4.QtCore"):
108 elif sys.modules.get("PyQt4.QtCore"):
109 if qtapi_version() == 2:
109 if qtapi_version() == 2:
110 return QT_API_PYQT
110 return QT_API_PYQT
111 else:
111 else:
112 return QT_API_PYQTv1
112 return QT_API_PYQTv1
113 elif sys.modules.get("PySide.QtCore"):
113 elif sys.modules.get("PySide.QtCore"):
114 return QT_API_PYSIDE
114 return QT_API_PYSIDE
115
115
116 return None
116 return None
117
117
118
118
119 def has_binding(api):
119 def has_binding(api):
120 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
120 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
121
121
122 Parameters
122 Parameters
123 ----------
123 ----------
124 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
124 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
125 Which module to check for
125 Which module to check for
126
126
127 Returns
127 Returns
128 -------
128 -------
129 True if the relevant module appears to be importable
129 True if the relevant module appears to be importable
130 """
130 """
131 module_name = api_to_module[api]
131 module_name = api_to_module[api]
132 from importlib.util import find_spec
132 from importlib.util import find_spec
133
133
134 required = ['QtCore', 'QtGui', 'QtSvg']
134 required = ['QtCore', 'QtGui', 'QtSvg']
135 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
135 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
136 # QT5 requires QtWidgets too
136 # QT5 requires QtWidgets too
137 required.append('QtWidgets')
137 required.append('QtWidgets')
138
138
139 for submod in required:
139 for submod in required:
140 try:
140 try:
141 spec = find_spec('%s.%s' % (module_name, submod))
141 spec = find_spec('%s.%s' % (module_name, submod))
142 except ImportError:
142 except ImportError:
143 # Package (e.g. PyQt5) not found
143 # Package (e.g. PyQt5) not found
144 return False
144 return False
145 else:
145 else:
146 if spec is None:
146 if spec is None:
147 # Submodule (e.g. PyQt5.QtCore) not found
147 # Submodule (e.g. PyQt5.QtCore) not found
148 return False
148 return False
149
149
150 if api == QT_API_PYSIDE:
150 if api == QT_API_PYSIDE:
151 # We can also safely check PySide version
151 # We can also safely check PySide version
152 import PySide
152 import PySide
153
153
154 return PySide.__version_info__ >= (1, 0, 3)
154 return PySide.__version_info__ >= (1, 0, 3)
155
155
156 return True
156 return True
157
157
158
158
159 def qtapi_version():
159 def qtapi_version():
160 """Return which QString API has been set, if any
160 """Return which QString API has been set, if any
161
161
162 Returns
162 Returns
163 -------
163 -------
164 The QString API version (1 or 2), or None if not set
164 The QString API version (1 or 2), or None if not set
165 """
165 """
166 try:
166 try:
167 import sip
167 import sip
168 except ImportError:
168 except ImportError:
169 # as of PyQt5 5.11, sip is no longer available as a top-level
169 # as of PyQt5 5.11, sip is no longer available as a top-level
170 # module and needs to be imported from the PyQt5 namespace
170 # module and needs to be imported from the PyQt5 namespace
171 try:
171 try:
172 from PyQt5 import sip
172 from PyQt5 import sip
173 except ImportError:
173 except ImportError:
174 return
174 return
175 try:
175 try:
176 return sip.getapi('QString')
176 return sip.getapi('QString')
177 except ValueError:
177 except ValueError:
178 return
178 return
179
179
180
180
181 def can_import(api):
181 def can_import(api):
182 """Safely query whether an API is importable, without importing it"""
182 """Safely query whether an API is importable, without importing it"""
183 if not has_binding(api):
183 if not has_binding(api):
184 return False
184 return False
185
185
186 current = loaded_api()
186 current = loaded_api()
187 if api == QT_API_PYQT_DEFAULT:
187 if api == QT_API_PYQT_DEFAULT:
188 return current in [QT_API_PYQT6, None]
188 return current in [QT_API_PYQT6, None]
189 else:
189 else:
190 return current in [api, None]
190 return current in [api, None]
191
191
192
192
193 def import_pyqt4(version=2):
193 def import_pyqt4(version=2):
194 """
194 """
195 Import PyQt4
195 Import PyQt4
196
196
197 Parameters
197 Parameters
198 ----------
198 ----------
199 version : 1, 2, or None
199 version : 1, 2, or None
200 Which QString/QVariant API to use. Set to None to use the system
200 Which QString/QVariant API to use. Set to None to use the system
201 default
201 default
202 ImportErrors raised within this function are non-recoverable
202 ImportErrors raised within this function are non-recoverable
203 """
203 """
204 # The new-style string API (version=2) automatically
204 # The new-style string API (version=2) automatically
205 # converts QStrings to Unicode Python strings. Also, automatically unpacks
205 # converts QStrings to Unicode Python strings. Also, automatically unpacks
206 # QVariants to their underlying objects.
206 # QVariants to their underlying objects.
207 import sip
207 import sip
208
208
209 if version is not None:
209 if version is not None:
210 sip.setapi('QString', version)
210 sip.setapi('QString', version)
211 sip.setapi('QVariant', version)
211 sip.setapi('QVariant', version)
212
212
213 from PyQt4 import QtGui, QtCore, QtSvg
213 from PyQt4 import QtGui, QtCore, QtSvg
214
214
215 if QtCore.PYQT_VERSION < 0x040700:
215 if QtCore.PYQT_VERSION < 0x040700:
216 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
216 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
217 QtCore.PYQT_VERSION_STR)
217 QtCore.PYQT_VERSION_STR)
218
218
219 # Alias PyQt-specific functions for PySide compatibility.
219 # Alias PyQt-specific functions for PySide compatibility.
220 QtCore.Signal = QtCore.pyqtSignal
220 QtCore.Signal = QtCore.pyqtSignal
221 QtCore.Slot = QtCore.pyqtSlot
221 QtCore.Slot = QtCore.pyqtSlot
222
222
223 # query for the API version (in case version == None)
223 # query for the API version (in case version == None)
224 version = sip.getapi('QString')
224 version = sip.getapi('QString')
225 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
225 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
226 return QtCore, QtGui, QtSvg, api
226 return QtCore, QtGui, QtSvg, api
227
227
228
228
229 def import_pyqt5():
229 def import_pyqt5():
230 """
230 """
231 Import PyQt5
231 Import PyQt5
232
232
233 ImportErrors raised within this function are non-recoverable
233 ImportErrors raised within this function are non-recoverable
234 """
234 """
235
235
236 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
236 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
237
237
238 # Alias PyQt-specific functions for PySide compatibility.
238 # Alias PyQt-specific functions for PySide compatibility.
239 QtCore.Signal = QtCore.pyqtSignal
239 QtCore.Signal = QtCore.pyqtSignal
240 QtCore.Slot = QtCore.pyqtSlot
240 QtCore.Slot = QtCore.pyqtSlot
241
241
242 # Join QtGui and QtWidgets for Qt4 compatibility.
242 # Join QtGui and QtWidgets for Qt4 compatibility.
243 QtGuiCompat = types.ModuleType('QtGuiCompat')
243 QtGuiCompat = types.ModuleType('QtGuiCompat')
244 QtGuiCompat.__dict__.update(QtGui.__dict__)
244 QtGuiCompat.__dict__.update(QtGui.__dict__)
245 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
245 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
246
246
247 api = QT_API_PYQT5
247 api = QT_API_PYQT5
248 return QtCore, QtGuiCompat, QtSvg, api
248 return QtCore, QtGuiCompat, QtSvg, api
249
249
250
250
251 def import_pyqt6():
251 def import_pyqt6():
252 """
252 """
253 Import PyQt6
253 Import PyQt6
254
254
255 ImportErrors raised within this function are non-recoverable
255 ImportErrors raised within this function are non-recoverable
256 """
256 """
257
257
258 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
258 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
259
259
260 # Alias PyQt-specific functions for PySide compatibility.
260 # Alias PyQt-specific functions for PySide compatibility.
261 QtCore.Signal = QtCore.pyqtSignal
261 QtCore.Signal = QtCore.pyqtSignal
262 QtCore.Slot = QtCore.pyqtSlot
262 QtCore.Slot = QtCore.pyqtSlot
263
263
264 # Join QtGui and QtWidgets for Qt4 compatibility.
264 # Join QtGui and QtWidgets for Qt4 compatibility.
265 QtGuiCompat = types.ModuleType("QtGuiCompat")
265 QtGuiCompat = types.ModuleType("QtGuiCompat")
266 QtGuiCompat.__dict__.update(QtGui.__dict__)
266 QtGuiCompat.__dict__.update(QtGui.__dict__)
267 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
267 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
268
268
269 api = QT_API_PYQT6
269 api = QT_API_PYQT6
270 return QtCore, QtGuiCompat, QtSvg, api
270 return QtCore, QtGuiCompat, QtSvg, api
271
271
272
272
273 def import_pyside():
273 def import_pyside():
274 """
274 """
275 Import PySide
275 Import PySide
276
276
277 ImportErrors raised within this function are non-recoverable
277 ImportErrors raised within this function are non-recoverable
278 """
278 """
279 from PySide import QtGui, QtCore, QtSvg
279 from PySide import QtGui, QtCore, QtSvg
280 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
280 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
281
281
282 def import_pyside2():
282 def import_pyside2():
283 """
283 """
284 Import PySide2
284 Import PySide2
285
285
286 ImportErrors raised within this function are non-recoverable
286 ImportErrors raised within this function are non-recoverable
287 """
287 """
288 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
288 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
289
289
290 # Join QtGui and QtWidgets for Qt4 compatibility.
290 # Join QtGui and QtWidgets for Qt4 compatibility.
291 QtGuiCompat = types.ModuleType('QtGuiCompat')
291 QtGuiCompat = types.ModuleType('QtGuiCompat')
292 QtGuiCompat.__dict__.update(QtGui.__dict__)
292 QtGuiCompat.__dict__.update(QtGui.__dict__)
293 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
293 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
294 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
294 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
295
295
296 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
296 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
297
297
298
298
299 def import_pyside6():
299 def import_pyside6():
300 """
300 """
301 Import PySide6
301 Import PySide6
302
302
303 ImportErrors raised within this function are non-recoverable
303 ImportErrors raised within this function are non-recoverable
304 """
304 """
305
306 def get_attrs(module):
307 return {
308 name: getattr(module, name)
309 for name in dir(module)
310 if not name.startswith("_")
311 }
312
305 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
313 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
306
314
307 # Join QtGui and QtWidgets for Qt4 compatibility.
315 # Join QtGui and QtWidgets for Qt4 compatibility.
308 QtGuiCompat = types.ModuleType("QtGuiCompat")
316 QtGuiCompat = types.ModuleType("QtGuiCompat")
309 QtGuiCompat.__dict__.update(QtGui.__dict__)
317 QtGuiCompat.__dict__.update(QtGui.__dict__)
310 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
318 if QtCore.__version_info__ < (6, 7):
311 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
319 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
320 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
321 else:
322 QtGuiCompat.__dict__.update(get_attrs(QtWidgets))
323 QtGuiCompat.__dict__.update(get_attrs(QtPrintSupport))
312
324
313 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
325 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
314
326
315
327
316 def load_qt(api_options):
328 def load_qt(api_options):
317 """
329 """
318 Attempt to import Qt, given a preference list
330 Attempt to import Qt, given a preference list
319 of permissible bindings
331 of permissible bindings
320
332
321 It is safe to call this function multiple times.
333 It is safe to call this function multiple times.
322
334
323 Parameters
335 Parameters
324 ----------
336 ----------
325 api_options : List of strings
337 api_options : List of strings
326 The order of APIs to try. Valid items are 'pyside', 'pyside2',
338 The order of APIs to try. Valid items are 'pyside', 'pyside2',
327 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
339 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
328
340
329 Returns
341 Returns
330 -------
342 -------
331 A tuple of QtCore, QtGui, QtSvg, QT_API
343 A tuple of QtCore, QtGui, QtSvg, QT_API
332 The first three are the Qt modules. The last is the
344 The first three are the Qt modules. The last is the
333 string indicating which module was loaded.
345 string indicating which module was loaded.
334
346
335 Raises
347 Raises
336 ------
348 ------
337 ImportError, if it isn't possible to import any requested
349 ImportError, if it isn't possible to import any requested
338 bindings (either because they aren't installed, or because
350 bindings (either because they aren't installed, or because
339 an incompatible library has already been installed)
351 an incompatible library has already been installed)
340 """
352 """
341 loaders = {
353 loaders = {
342 # Qt6
354 # Qt6
343 QT_API_PYQT6: import_pyqt6,
355 QT_API_PYQT6: import_pyqt6,
344 QT_API_PYSIDE6: import_pyside6,
356 QT_API_PYSIDE6: import_pyside6,
345 # Qt5
357 # Qt5
346 QT_API_PYQT5: import_pyqt5,
358 QT_API_PYQT5: import_pyqt5,
347 QT_API_PYSIDE2: import_pyside2,
359 QT_API_PYSIDE2: import_pyside2,
348 # Qt4
360 # Qt4
349 QT_API_PYSIDE: import_pyside,
361 QT_API_PYSIDE: import_pyside,
350 QT_API_PYQT: import_pyqt4,
362 QT_API_PYQT: import_pyqt4,
351 QT_API_PYQTv1: partial(import_pyqt4, version=1),
363 QT_API_PYQTv1: partial(import_pyqt4, version=1),
352 # default
364 # default
353 QT_API_PYQT_DEFAULT: import_pyqt6,
365 QT_API_PYQT_DEFAULT: import_pyqt6,
354 }
366 }
355
367
356 for api in api_options:
368 for api in api_options:
357
369
358 if api not in loaders:
370 if api not in loaders:
359 raise RuntimeError(
371 raise RuntimeError(
360 "Invalid Qt API %r, valid values are: %s" %
372 "Invalid Qt API %r, valid values are: %s" %
361 (api, ", ".join(["%r" % k for k in loaders.keys()])))
373 (api, ", ".join(["%r" % k for k in loaders.keys()])))
362
374
363 if not can_import(api):
375 if not can_import(api):
364 continue
376 continue
365
377
366 #cannot safely recover from an ImportError during this
378 #cannot safely recover from an ImportError during this
367 result = loaders[api]()
379 result = loaders[api]()
368 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
380 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
369 commit_api(api)
381 commit_api(api)
370 return result
382 return result
371 else:
383 else:
372 # Clear the environment variable since it doesn't work.
384 # Clear the environment variable since it doesn't work.
373 if "QT_API" in os.environ:
385 if "QT_API" in os.environ:
374 del os.environ["QT_API"]
386 del os.environ["QT_API"]
375
387
376 raise ImportError(
388 raise ImportError(
377 """
389 """
378 Could not load requested Qt binding. Please ensure that
390 Could not load requested Qt binding. Please ensure that
379 PyQt4 >= 4.7, PyQt5, PyQt6, PySide >= 1.0.3, PySide2, or
391 PyQt4 >= 4.7, PyQt5, PyQt6, PySide >= 1.0.3, PySide2, or
380 PySide6 is available, and only one is imported per session.
392 PySide6 is available, and only one is imported per session.
381
393
382 Currently-imported Qt library: %r
394 Currently-imported Qt library: %r
383 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
395 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
384 PyQt6 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
396 PyQt6 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
385 PySide2 installed: %s
397 PySide2 installed: %s
386 PySide6 installed: %s
398 PySide6 installed: %s
387 Tried to load: %r
399 Tried to load: %r
388 """
400 """
389 % (
401 % (
390 loaded_api(),
402 loaded_api(),
391 has_binding(QT_API_PYQT5),
403 has_binding(QT_API_PYQT5),
392 has_binding(QT_API_PYQT6),
404 has_binding(QT_API_PYQT6),
393 has_binding(QT_API_PYSIDE2),
405 has_binding(QT_API_PYSIDE2),
394 has_binding(QT_API_PYSIDE6),
406 has_binding(QT_API_PYSIDE6),
395 api_options,
407 api_options,
396 )
408 )
397 )
409 )
398
410
399
411
400 def enum_factory(QT_API, QtCore):
412 def enum_factory(QT_API, QtCore):
401 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
413 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
402
414
403 @lru_cache(None)
415 @lru_cache(None)
404 def _enum(name):
416 def _enum(name):
405 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
417 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
406 return operator.attrgetter(
418 return operator.attrgetter(
407 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
419 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
408 )(sys.modules[QtCore.__package__])
420 )(sys.modules[QtCore.__package__])
409
421
410 return _enum
422 return _enum
General Comments 0
You need to be logged in to leave comments. Login now