##// END OF EJS Templates
Deprecate `IPython.utils.version`...
Nikita Kniazev -
Show More
@@ -1,129 +1,128 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 `matplotlib=qt` initialization.
3 This is the import used for the `gui=qt` or `matplotlib=qt` initialization.
4
4
5 Import Priority:
5 Import Priority:
6
6
7 if Qt has been imported anywhere else:
7 if Qt 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 QT_API env variable
13 Next, ask QT_API env variable
14
14
15 if QT_API not set:
15 if QT_API not set:
16 ask matplotlib what it's using. If Qt4Agg or Qt5Agg, then use the
16 ask matplotlib what it's using. If Qt4Agg or Qt5Agg, then use the
17 version matplotlib is configured with
17 version matplotlib is configured with
18
18
19 else: (matplotlib said nothing)
19 else: (matplotlib said nothing)
20 # this is the default path - nobody told us anything
20 # this is the default path - nobody told us anything
21 try in this order:
21 try in this order:
22 PyQt default version, PySide, PyQt5
22 PyQt default version, PySide, PyQt5
23 else:
23 else:
24 use what QT_API says
24 use what QT_API says
25
25
26 """
26 """
27 # NOTE: This is no longer an external, third-party module, and should be
27 # NOTE: This is no longer an external, third-party module, and should be
28 # considered part of IPython. For compatibility however, it is being kept in
28 # considered part of IPython. For compatibility however, it is being kept in
29 # IPython/external.
29 # IPython/external.
30
30
31 import os
31 import os
32 import sys
32 import sys
33
33
34 from IPython.utils.version import check_version
35 from IPython.external.qt_loaders import (
34 from IPython.external.qt_loaders import (
36 load_qt,
35 load_qt,
37 loaded_api,
36 loaded_api,
38 enum_factory,
37 enum_factory,
39 # QT6
38 # QT6
40 QT_API_PYQT6,
39 QT_API_PYQT6,
41 QT_API_PYSIDE6,
40 QT_API_PYSIDE6,
42 # QT5
41 # QT5
43 QT_API_PYQT5,
42 QT_API_PYQT5,
44 QT_API_PYSIDE2,
43 QT_API_PYSIDE2,
45 # QT4
44 # QT4
46 QT_API_PYQTv1,
45 QT_API_PYQTv1,
47 QT_API_PYQT,
46 QT_API_PYQT,
48 QT_API_PYSIDE,
47 QT_API_PYSIDE,
49 # default
48 # default
50 QT_API_PYQT_DEFAULT,
49 QT_API_PYQT_DEFAULT,
51 )
50 )
52
51
53 _qt_apis = (
52 _qt_apis = (
54 # QT6
53 # QT6
55 QT_API_PYQT6,
54 QT_API_PYQT6,
56 QT_API_PYSIDE6,
55 QT_API_PYSIDE6,
57 # QT5
56 # QT5
58 QT_API_PYQT5,
57 QT_API_PYQT5,
59 QT_API_PYSIDE2,
58 QT_API_PYSIDE2,
60 # QT4
59 # QT4
61 QT_API_PYQTv1,
60 QT_API_PYQTv1,
62 QT_API_PYQT,
61 QT_API_PYQT,
63 QT_API_PYSIDE,
62 QT_API_PYSIDE,
64 # default
63 # default
65 QT_API_PYQT_DEFAULT,
64 QT_API_PYQT_DEFAULT,
66 )
65 )
67
66
68
67
69 def matplotlib_options(mpl):
68 def matplotlib_options(mpl):
70 """Constraints placed on an imported matplotlib."""
69 """Constraints placed on an imported matplotlib."""
71 if mpl is None:
70 if mpl is None:
72 return
71 return
73 backend = mpl.rcParams.get('backend', None)
72 backend = mpl.rcParams.get('backend', None)
74 if backend == 'Qt4Agg':
73 if backend == 'Qt4Agg':
75 mpqt = mpl.rcParams.get('backend.qt4', None)
74 mpqt = mpl.rcParams.get('backend.qt4', None)
76 if mpqt is None:
75 if mpqt is None:
77 return None
76 return None
78 if mpqt.lower() == 'pyside':
77 if mpqt.lower() == 'pyside':
79 return [QT_API_PYSIDE]
78 return [QT_API_PYSIDE]
80 elif mpqt.lower() == 'pyqt4':
79 elif mpqt.lower() == 'pyqt4':
81 return [QT_API_PYQT_DEFAULT]
80 return [QT_API_PYQT_DEFAULT]
82 elif mpqt.lower() == 'pyqt4v2':
81 elif mpqt.lower() == 'pyqt4v2':
83 return [QT_API_PYQT]
82 return [QT_API_PYQT]
84 raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
83 raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
85 mpqt)
84 mpqt)
86 elif backend == 'Qt5Agg':
85 elif backend == 'Qt5Agg':
87 mpqt = mpl.rcParams.get('backend.qt5', None)
86 mpqt = mpl.rcParams.get('backend.qt5', None)
88 if mpqt is None:
87 if mpqt is None:
89 return None
88 return None
90 if mpqt.lower() == 'pyqt5':
89 if mpqt.lower() == 'pyqt5':
91 return [QT_API_PYQT5]
90 return [QT_API_PYQT5]
92 raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
91 raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
93 mpqt)
92 mpqt)
94
93
95 def get_options():
94 def get_options():
96 """Return a list of acceptable QT APIs, in decreasing order of preference."""
95 """Return a list of acceptable QT APIs, in decreasing order of preference."""
97 #already imported Qt somewhere. Use that
96 #already imported Qt somewhere. Use that
98 loaded = loaded_api()
97 loaded = loaded_api()
99 if loaded is not None:
98 if loaded is not None:
100 return [loaded]
99 return [loaded]
101
100
102 mpl = sys.modules.get('matplotlib', None)
101 mpl = sys.modules.get('matplotlib', None)
103
102
104 if mpl is not None and not check_version(mpl.__version__, '1.0.2'):
103 if mpl is not None and tuple(mpl.__version__.split(".")) < ("1", "0", "2"):
105 #1.0.1 only supports PyQt4 v1
104 # 1.0.1 only supports PyQt4 v1
106 return [QT_API_PYQT_DEFAULT]
105 return [QT_API_PYQT_DEFAULT]
107
106
108 qt_api = os.environ.get('QT_API', None)
107 qt_api = os.environ.get('QT_API', None)
109 if qt_api is None:
108 if qt_api is None:
110 #no ETS variable. Ask mpl, then use default fallback path
109 #no ETS variable. Ask mpl, then use default fallback path
111 return matplotlib_options(mpl) or [
110 return matplotlib_options(mpl) or [
112 QT_API_PYQT_DEFAULT,
111 QT_API_PYQT_DEFAULT,
113 QT_API_PYQT6,
112 QT_API_PYQT6,
114 QT_API_PYSIDE6,
113 QT_API_PYSIDE6,
115 QT_API_PYQT5,
114 QT_API_PYQT5,
116 QT_API_PYSIDE2,
115 QT_API_PYSIDE2,
117 QT_API_PYQT,
116 QT_API_PYQT,
118 QT_API_PYSIDE,
117 QT_API_PYSIDE,
119 ]
118 ]
120 elif qt_api not in _qt_apis:
119 elif qt_api not in _qt_apis:
121 raise RuntimeError("Invalid Qt API %r, valid values are: %r" %
120 raise RuntimeError("Invalid Qt API %r, valid values are: %r" %
122 (qt_api, ', '.join(_qt_apis)))
121 (qt_api, ', '.join(_qt_apis)))
123 else:
122 else:
124 return [qt_api]
123 return [qt_api]
125
124
126
125
127 api_opts = get_options()
126 api_opts = get_options()
128 QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
127 QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
129 enum_helper = enum_factory(QT_API, QtCore)
128 enum_helper = enum_factory(QT_API, QtCore)
@@ -1,401 +1,400 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 import types
12 import types
13 from functools import partial, lru_cache
13 from functools import partial, lru_cache
14 import operator
14 import operator
15
15
16 from IPython.utils.version import check_version
17
18 # ### Available APIs.
16 # ### Available APIs.
19 # Qt6
17 # Qt6
20 QT_API_PYQT6 = "pyqt6"
18 QT_API_PYQT6 = "pyqt6"
21 QT_API_PYSIDE6 = "pyside6"
19 QT_API_PYSIDE6 = "pyside6"
22
20
23 # Qt5
21 # Qt5
24 QT_API_PYQT5 = 'pyqt5'
22 QT_API_PYQT5 = 'pyqt5'
25 QT_API_PYSIDE2 = 'pyside2'
23 QT_API_PYSIDE2 = 'pyside2'
26
24
27 # Qt4
25 # Qt4
28 QT_API_PYQT = "pyqt" # Force version 2
26 QT_API_PYQT = "pyqt" # Force version 2
29 QT_API_PYQTv1 = "pyqtv1" # Force version 2
27 QT_API_PYQTv1 = "pyqtv1" # Force version 2
30 QT_API_PYSIDE = "pyside"
28 QT_API_PYSIDE = "pyside"
31
29
32 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
30 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
33
31
34 api_to_module = {
32 api_to_module = {
35 # Qt6
33 # Qt6
36 QT_API_PYQT6: "PyQt6",
34 QT_API_PYQT6: "PyQt6",
37 QT_API_PYSIDE6: "PySide6",
35 QT_API_PYSIDE6: "PySide6",
38 # Qt5
36 # Qt5
39 QT_API_PYQT5: "PyQt5",
37 QT_API_PYQT5: "PyQt5",
40 QT_API_PYSIDE2: "PySide2",
38 QT_API_PYSIDE2: "PySide2",
41 # Qt4
39 # Qt4
42 QT_API_PYSIDE: "PySide",
40 QT_API_PYSIDE: "PySide",
43 QT_API_PYQT: "PyQt4",
41 QT_API_PYQT: "PyQt4",
44 QT_API_PYQTv1: "PyQt4",
42 QT_API_PYQTv1: "PyQt4",
45 # default
43 # default
46 QT_API_PYQT_DEFAULT: "PyQt6",
44 QT_API_PYQT_DEFAULT: "PyQt6",
47 }
45 }
48
46
49
47
50 class ImportDenier(object):
48 class ImportDenier(object):
51 """Import Hook that will guard against bad Qt imports
49 """Import Hook that will guard against bad Qt imports
52 once IPython commits to a specific binding
50 once IPython commits to a specific binding
53 """
51 """
54
52
55 def __init__(self):
53 def __init__(self):
56 self.__forbidden = set()
54 self.__forbidden = set()
57
55
58 def forbid(self, module_name):
56 def forbid(self, module_name):
59 sys.modules.pop(module_name, None)
57 sys.modules.pop(module_name, None)
60 self.__forbidden.add(module_name)
58 self.__forbidden.add(module_name)
61
59
62 def find_module(self, fullname, path=None):
60 def find_module(self, fullname, path=None):
63 if path:
61 if path:
64 return
62 return
65 if fullname in self.__forbidden:
63 if fullname in self.__forbidden:
66 return self
64 return self
67
65
68 def load_module(self, fullname):
66 def load_module(self, fullname):
69 raise ImportError("""
67 raise ImportError("""
70 Importing %s disabled by IPython, which has
68 Importing %s disabled by IPython, which has
71 already imported an Incompatible QT Binding: %s
69 already imported an Incompatible QT Binding: %s
72 """ % (fullname, loaded_api()))
70 """ % (fullname, loaded_api()))
73
71
74
72
75 ID = ImportDenier()
73 ID = ImportDenier()
76 sys.meta_path.insert(0, ID)
74 sys.meta_path.insert(0, ID)
77
75
78
76
79 def commit_api(api):
77 def commit_api(api):
80 """Commit to a particular API, and trigger ImportErrors on subsequent
78 """Commit to a particular API, and trigger ImportErrors on subsequent
81 dangerous imports"""
79 dangerous imports"""
82 modules = set(api_to_module.values())
80 modules = set(api_to_module.values())
83
81
84 modules.remove(api_to_module[api])
82 modules.remove(api_to_module[api])
85 for mod in modules:
83 for mod in modules:
86 ID.forbid(mod)
84 ID.forbid(mod)
87
85
88
86
89 def loaded_api():
87 def loaded_api():
90 """Return which API is loaded, if any
88 """Return which API is loaded, if any
91
89
92 If this returns anything besides None,
90 If this returns anything besides None,
93 importing any other Qt binding is unsafe.
91 importing any other Qt binding is unsafe.
94
92
95 Returns
93 Returns
96 -------
94 -------
97 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
95 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
98 """
96 """
99 if sys.modules.get("PyQt6.QtCore"):
97 if sys.modules.get("PyQt6.QtCore"):
100 return QT_API_PYQT6
98 return QT_API_PYQT6
101 elif sys.modules.get("PySide6.QtCore"):
99 elif sys.modules.get("PySide6.QtCore"):
102 return QT_API_PYSIDE6
100 return QT_API_PYSIDE6
103 elif sys.modules.get("PyQt5.QtCore"):
101 elif sys.modules.get("PyQt5.QtCore"):
104 return QT_API_PYQT5
102 return QT_API_PYQT5
105 elif sys.modules.get("PySide2.QtCore"):
103 elif sys.modules.get("PySide2.QtCore"):
106 return QT_API_PYSIDE2
104 return QT_API_PYSIDE2
107 elif sys.modules.get("PyQt4.QtCore"):
105 elif sys.modules.get("PyQt4.QtCore"):
108 if qtapi_version() == 2:
106 if qtapi_version() == 2:
109 return QT_API_PYQT
107 return QT_API_PYQT
110 else:
108 else:
111 return QT_API_PYQTv1
109 return QT_API_PYQTv1
112 elif sys.modules.get("PySide.QtCore"):
110 elif sys.modules.get("PySide.QtCore"):
113 return QT_API_PYSIDE
111 return QT_API_PYSIDE
114
112
115 return None
113 return None
116
114
117
115
118 def has_binding(api):
116 def has_binding(api):
119 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
117 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
120
118
121 Parameters
119 Parameters
122 ----------
120 ----------
123 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
121 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
124 Which module to check for
122 Which module to check for
125
123
126 Returns
124 Returns
127 -------
125 -------
128 True if the relevant module appears to be importable
126 True if the relevant module appears to be importable
129 """
127 """
130 module_name = api_to_module[api]
128 module_name = api_to_module[api]
131 from importlib.util import find_spec
129 from importlib.util import find_spec
132
130
133 required = ['QtCore', 'QtGui', 'QtSvg']
131 required = ['QtCore', 'QtGui', 'QtSvg']
134 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
132 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
135 # QT5 requires QtWidgets too
133 # QT5 requires QtWidgets too
136 required.append('QtWidgets')
134 required.append('QtWidgets')
137
135
138 for submod in required:
136 for submod in required:
139 try:
137 try:
140 spec = find_spec('%s.%s' % (module_name, submod))
138 spec = find_spec('%s.%s' % (module_name, submod))
141 except ImportError:
139 except ImportError:
142 # Package (e.g. PyQt5) not found
140 # Package (e.g. PyQt5) not found
143 return False
141 return False
144 else:
142 else:
145 if spec is None:
143 if spec is None:
146 # Submodule (e.g. PyQt5.QtCore) not found
144 # Submodule (e.g. PyQt5.QtCore) not found
147 return False
145 return False
148
146
149 if api == QT_API_PYSIDE:
147 if api == QT_API_PYSIDE:
150 # We can also safely check PySide version
148 # We can also safely check PySide version
151 import PySide
149 import PySide
152 return check_version(PySide.__version__, '1.0.3')
150
151 return PySide.__version_info__ >= (1, 0, 3)
153
152
154 return True
153 return True
155
154
156
155
157 def qtapi_version():
156 def qtapi_version():
158 """Return which QString API has been set, if any
157 """Return which QString API has been set, if any
159
158
160 Returns
159 Returns
161 -------
160 -------
162 The QString API version (1 or 2), or None if not set
161 The QString API version (1 or 2), or None if not set
163 """
162 """
164 try:
163 try:
165 import sip
164 import sip
166 except ImportError:
165 except ImportError:
167 # as of PyQt5 5.11, sip is no longer available as a top-level
166 # as of PyQt5 5.11, sip is no longer available as a top-level
168 # module and needs to be imported from the PyQt5 namespace
167 # module and needs to be imported from the PyQt5 namespace
169 try:
168 try:
170 from PyQt5 import sip
169 from PyQt5 import sip
171 except ImportError:
170 except ImportError:
172 return
171 return
173 try:
172 try:
174 return sip.getapi('QString')
173 return sip.getapi('QString')
175 except ValueError:
174 except ValueError:
176 return
175 return
177
176
178
177
179 def can_import(api):
178 def can_import(api):
180 """Safely query whether an API is importable, without importing it"""
179 """Safely query whether an API is importable, without importing it"""
181 if not has_binding(api):
180 if not has_binding(api):
182 return False
181 return False
183
182
184 current = loaded_api()
183 current = loaded_api()
185 if api == QT_API_PYQT_DEFAULT:
184 if api == QT_API_PYQT_DEFAULT:
186 return current in [QT_API_PYQT6, None]
185 return current in [QT_API_PYQT6, None]
187 else:
186 else:
188 return current in [api, None]
187 return current in [api, None]
189
188
190
189
191 def import_pyqt4(version=2):
190 def import_pyqt4(version=2):
192 """
191 """
193 Import PyQt4
192 Import PyQt4
194
193
195 Parameters
194 Parameters
196 ----------
195 ----------
197 version : 1, 2, or None
196 version : 1, 2, or None
198 Which QString/QVariant API to use. Set to None to use the system
197 Which QString/QVariant API to use. Set to None to use the system
199 default
198 default
200
199
201 ImportErrors raised within this function are non-recoverable
200 ImportErrors raised within this function are non-recoverable
202 """
201 """
203 # The new-style string API (version=2) automatically
202 # The new-style string API (version=2) automatically
204 # converts QStrings to Unicode Python strings. Also, automatically unpacks
203 # converts QStrings to Unicode Python strings. Also, automatically unpacks
205 # QVariants to their underlying objects.
204 # QVariants to their underlying objects.
206 import sip
205 import sip
207
206
208 if version is not None:
207 if version is not None:
209 sip.setapi('QString', version)
208 sip.setapi('QString', version)
210 sip.setapi('QVariant', version)
209 sip.setapi('QVariant', version)
211
210
212 from PyQt4 import QtGui, QtCore, QtSvg
211 from PyQt4 import QtGui, QtCore, QtSvg
213
212
214 if not check_version(QtCore.PYQT_VERSION_STR, '4.7'):
213 if QtCore.PYQT_VERSION < 0x040700:
215 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
214 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
216 QtCore.PYQT_VERSION_STR)
215 QtCore.PYQT_VERSION_STR)
217
216
218 # Alias PyQt-specific functions for PySide compatibility.
217 # Alias PyQt-specific functions for PySide compatibility.
219 QtCore.Signal = QtCore.pyqtSignal
218 QtCore.Signal = QtCore.pyqtSignal
220 QtCore.Slot = QtCore.pyqtSlot
219 QtCore.Slot = QtCore.pyqtSlot
221
220
222 # query for the API version (in case version == None)
221 # query for the API version (in case version == None)
223 version = sip.getapi('QString')
222 version = sip.getapi('QString')
224 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
223 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
225 return QtCore, QtGui, QtSvg, api
224 return QtCore, QtGui, QtSvg, api
226
225
227
226
228 def import_pyqt5():
227 def import_pyqt5():
229 """
228 """
230 Import PyQt5
229 Import PyQt5
231
230
232 ImportErrors raised within this function are non-recoverable
231 ImportErrors raised within this function are non-recoverable
233 """
232 """
234
233
235 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
234 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
236
235
237 # Alias PyQt-specific functions for PySide compatibility.
236 # Alias PyQt-specific functions for PySide compatibility.
238 QtCore.Signal = QtCore.pyqtSignal
237 QtCore.Signal = QtCore.pyqtSignal
239 QtCore.Slot = QtCore.pyqtSlot
238 QtCore.Slot = QtCore.pyqtSlot
240
239
241 # Join QtGui and QtWidgets for Qt4 compatibility.
240 # Join QtGui and QtWidgets for Qt4 compatibility.
242 QtGuiCompat = types.ModuleType('QtGuiCompat')
241 QtGuiCompat = types.ModuleType('QtGuiCompat')
243 QtGuiCompat.__dict__.update(QtGui.__dict__)
242 QtGuiCompat.__dict__.update(QtGui.__dict__)
244 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
243 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
245
244
246 api = QT_API_PYQT5
245 api = QT_API_PYQT5
247 return QtCore, QtGuiCompat, QtSvg, api
246 return QtCore, QtGuiCompat, QtSvg, api
248
247
249
248
250 def import_pyqt6():
249 def import_pyqt6():
251 """
250 """
252 Import PyQt6
251 Import PyQt6
253
252
254 ImportErrors raised within this function are non-recoverable
253 ImportErrors raised within this function are non-recoverable
255 """
254 """
256
255
257 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
256 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
258
257
259 # Alias PyQt-specific functions for PySide compatibility.
258 # Alias PyQt-specific functions for PySide compatibility.
260 QtCore.Signal = QtCore.pyqtSignal
259 QtCore.Signal = QtCore.pyqtSignal
261 QtCore.Slot = QtCore.pyqtSlot
260 QtCore.Slot = QtCore.pyqtSlot
262
261
263 # Join QtGui and QtWidgets for Qt4 compatibility.
262 # Join QtGui and QtWidgets for Qt4 compatibility.
264 QtGuiCompat = types.ModuleType("QtGuiCompat")
263 QtGuiCompat = types.ModuleType("QtGuiCompat")
265 QtGuiCompat.__dict__.update(QtGui.__dict__)
264 QtGuiCompat.__dict__.update(QtGui.__dict__)
266 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
265 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
267
266
268 api = QT_API_PYQT6
267 api = QT_API_PYQT6
269 return QtCore, QtGuiCompat, QtSvg, api
268 return QtCore, QtGuiCompat, QtSvg, api
270
269
271
270
272 def import_pyside():
271 def import_pyside():
273 """
272 """
274 Import PySide
273 Import PySide
275
274
276 ImportErrors raised within this function are non-recoverable
275 ImportErrors raised within this function are non-recoverable
277 """
276 """
278 from PySide import QtGui, QtCore, QtSvg
277 from PySide import QtGui, QtCore, QtSvg
279 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
278 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
280
279
281 def import_pyside2():
280 def import_pyside2():
282 """
281 """
283 Import PySide2
282 Import PySide2
284
283
285 ImportErrors raised within this function are non-recoverable
284 ImportErrors raised within this function are non-recoverable
286 """
285 """
287 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
286 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
288
287
289 # Join QtGui and QtWidgets for Qt4 compatibility.
288 # Join QtGui and QtWidgets for Qt4 compatibility.
290 QtGuiCompat = types.ModuleType('QtGuiCompat')
289 QtGuiCompat = types.ModuleType('QtGuiCompat')
291 QtGuiCompat.__dict__.update(QtGui.__dict__)
290 QtGuiCompat.__dict__.update(QtGui.__dict__)
292 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
291 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
293 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
292 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
294
293
295 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
294 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
296
295
297
296
298 def import_pyside6():
297 def import_pyside6():
299 """
298 """
300 Import PySide6
299 Import PySide6
301
300
302 ImportErrors raised within this function are non-recoverable
301 ImportErrors raised within this function are non-recoverable
303 """
302 """
304 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
303 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
305
304
306 # Join QtGui and QtWidgets for Qt4 compatibility.
305 # Join QtGui and QtWidgets for Qt4 compatibility.
307 QtGuiCompat = types.ModuleType("QtGuiCompat")
306 QtGuiCompat = types.ModuleType("QtGuiCompat")
308 QtGuiCompat.__dict__.update(QtGui.__dict__)
307 QtGuiCompat.__dict__.update(QtGui.__dict__)
309 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
308 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
310 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
309 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
311
310
312 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
311 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
313
312
314
313
315 def load_qt(api_options):
314 def load_qt(api_options):
316 """
315 """
317 Attempt to import Qt, given a preference list
316 Attempt to import Qt, given a preference list
318 of permissible bindings
317 of permissible bindings
319
318
320 It is safe to call this function multiple times.
319 It is safe to call this function multiple times.
321
320
322 Parameters
321 Parameters
323 ----------
322 ----------
324 api_options: List of strings
323 api_options: List of strings
325 The order of APIs to try. Valid items are 'pyside', 'pyside2',
324 The order of APIs to try. Valid items are 'pyside', 'pyside2',
326 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
325 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
327
326
328 Returns
327 Returns
329 -------
328 -------
330
329
331 A tuple of QtCore, QtGui, QtSvg, QT_API
330 A tuple of QtCore, QtGui, QtSvg, QT_API
332 The first three are the Qt modules. The last is the
331 The first three are the Qt modules. The last is the
333 string indicating which module was loaded.
332 string indicating which module was loaded.
334
333
335 Raises
334 Raises
336 ------
335 ------
337 ImportError, if it isn't possible to import any requested
336 ImportError, if it isn't possible to import any requested
338 bindings (either because they aren't installed, or because
337 bindings (either because they aren't installed, or because
339 an incompatible library has already been installed)
338 an incompatible library has already been installed)
340 """
339 """
341 loaders = {
340 loaders = {
342 # Qt6
341 # Qt6
343 QT_API_PYQT6: import_pyqt6,
342 QT_API_PYQT6: import_pyqt6,
344 QT_API_PYSIDE6: import_pyside6,
343 QT_API_PYSIDE6: import_pyside6,
345 # Qt5
344 # Qt5
346 QT_API_PYQT5: import_pyqt5,
345 QT_API_PYQT5: import_pyqt5,
347 QT_API_PYSIDE2: import_pyside2,
346 QT_API_PYSIDE2: import_pyside2,
348 # Qt4
347 # Qt4
349 QT_API_PYSIDE: import_pyside,
348 QT_API_PYSIDE: import_pyside,
350 QT_API_PYQT: import_pyqt4,
349 QT_API_PYQT: import_pyqt4,
351 QT_API_PYQTv1: partial(import_pyqt4, version=1),
350 QT_API_PYQTv1: partial(import_pyqt4, version=1),
352 # default
351 # default
353 QT_API_PYQT_DEFAULT: import_pyqt6,
352 QT_API_PYQT_DEFAULT: import_pyqt6,
354 }
353 }
355
354
356 for api in api_options:
355 for api in api_options:
357
356
358 if api not in loaders:
357 if api not in loaders:
359 raise RuntimeError(
358 raise RuntimeError(
360 "Invalid Qt API %r, valid values are: %s" %
359 "Invalid Qt API %r, valid values are: %s" %
361 (api, ", ".join(["%r" % k for k in loaders.keys()])))
360 (api, ", ".join(["%r" % k for k in loaders.keys()])))
362
361
363 if not can_import(api):
362 if not can_import(api):
364 continue
363 continue
365
364
366 #cannot safely recover from an ImportError during this
365 #cannot safely recover from an ImportError during this
367 result = loaders[api]()
366 result = loaders[api]()
368 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
367 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
369 commit_api(api)
368 commit_api(api)
370 return result
369 return result
371 else:
370 else:
372 raise ImportError("""
371 raise ImportError("""
373 Could not load requested Qt binding. Please ensure that
372 Could not load requested Qt binding. Please ensure that
374 PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available,
373 PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available,
375 and only one is imported per session.
374 and only one is imported per session.
376
375
377 Currently-imported Qt library: %r
376 Currently-imported Qt library: %r
378 PyQt4 available (requires QtCore, QtGui, QtSvg): %s
377 PyQt4 available (requires QtCore, QtGui, QtSvg): %s
379 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
378 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
380 PySide >= 1.0.3 installed: %s
379 PySide >= 1.0.3 installed: %s
381 PySide2 installed: %s
380 PySide2 installed: %s
382 Tried to load: %r
381 Tried to load: %r
383 """ % (loaded_api(),
382 """ % (loaded_api(),
384 has_binding(QT_API_PYQT),
383 has_binding(QT_API_PYQT),
385 has_binding(QT_API_PYQT5),
384 has_binding(QT_API_PYQT5),
386 has_binding(QT_API_PYSIDE),
385 has_binding(QT_API_PYSIDE),
387 has_binding(QT_API_PYSIDE2),
386 has_binding(QT_API_PYSIDE2),
388 api_options))
387 api_options))
389
388
390
389
391 def enum_factory(QT_API, QtCore):
390 def enum_factory(QT_API, QtCore):
392 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
391 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
393
392
394 @lru_cache(None)
393 @lru_cache(None)
395 def _enum(name):
394 def _enum(name):
396 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
395 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
397 return operator.attrgetter(
396 return operator.attrgetter(
398 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
397 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
399 )(sys.modules[QtCore.__package__])
398 )(sys.modules[QtCore.__package__])
400
399
401 return _enum
400 return _enum
@@ -1,36 +1,40 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for version comparison
3 Utilities for version comparison
4
4
5 It is a bit ridiculous that we need these.
5 It is a bit ridiculous that we need these.
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 from warnings import warn
16 # Imports
17 #-----------------------------------------------------------------------------
18
16
19 from distutils.version import LooseVersion
17 warn("The `IPython.utils.version` module has been deprecated since IPython 8.0.")
20
18
21 #-----------------------------------------------------------------------------
22 # Code
23 #-----------------------------------------------------------------------------
24
19
25 def check_version(v, check):
20 def check_version(v, check):
26 """check version string v >= check
21 """check version string v >= check
27
22
28 If dev/prerelease tags result in TypeError for string-number comparison,
23 If dev/prerelease tags result in TypeError for string-number comparison,
29 it is assumed that the dependency is satisfied.
24 it is assumed that the dependency is satisfied.
30 Users on dev branches are responsible for keeping their own packages up to date.
25 Users on dev branches are responsible for keeping their own packages up to date.
31 """
26 """
27 warn(
28 "`check_version` function is deprecated as of IPython 8.0"
29 "and will be removed in future versions.",
30 DeprecationWarning,
31 stacklevel=2,
32 )
33
34 from distutils.version import LooseVersion
35
32 try:
36 try:
33 return LooseVersion(v) >= LooseVersion(check)
37 return LooseVersion(v) >= LooseVersion(check)
34 except TypeError:
38 except TypeError:
35 return True
39 return True
36
40
@@ -1,47 +1,48 b''
1 [pytest]
1 [pytest]
2 addopts = --durations=10
2 addopts = --durations=10
3 -p IPython.testing.plugin.pytest_ipdoctest --ipdoctest-modules
3 -p IPython.testing.plugin.pytest_ipdoctest --ipdoctest-modules
4 --ignore=docs
4 --ignore=docs
5 --ignore=examples
5 --ignore=examples
6 --ignore=htmlcov
6 --ignore=htmlcov
7 --ignore=ipython_kernel
7 --ignore=ipython_kernel
8 --ignore=ipython_parallel
8 --ignore=ipython_parallel
9 --ignore=results
9 --ignore=results
10 --ignore=tmp
10 --ignore=tmp
11 --ignore=tools
11 --ignore=tools
12 --ignore=traitlets
12 --ignore=traitlets
13 --ignore=IPython/core/tests/daft_extension
13 --ignore=IPython/core/tests/daft_extension
14 --ignore=IPython/sphinxext
14 --ignore=IPython/sphinxext
15 --ignore=IPython/terminal/pt_inputhooks
15 --ignore=IPython/terminal/pt_inputhooks
16 --ignore=IPython/__main__.py
16 --ignore=IPython/__main__.py
17 --ignore=IPython/config.py
17 --ignore=IPython/config.py
18 --ignore=IPython/frontend.py
18 --ignore=IPython/frontend.py
19 --ignore=IPython/html.py
19 --ignore=IPython/html.py
20 --ignore=IPython/nbconvert.py
20 --ignore=IPython/nbconvert.py
21 --ignore=IPython/nbformat.py
21 --ignore=IPython/nbformat.py
22 --ignore=IPython/parallel.py
22 --ignore=IPython/parallel.py
23 --ignore=IPython/qt.py
23 --ignore=IPython/qt.py
24 --ignore=IPython/external/qt_for_kernel.py
24 --ignore=IPython/external/qt_for_kernel.py
25 --ignore=IPython/html/widgets/widget_link.py
25 --ignore=IPython/html/widgets/widget_link.py
26 --ignore=IPython/html/widgets/widget_output.py
26 --ignore=IPython/html/widgets/widget_output.py
27 --ignore=IPython/terminal/console.py
27 --ignore=IPython/terminal/console.py
28 --ignore=IPython/terminal/ptshell.py
28 --ignore=IPython/terminal/ptshell.py
29 --ignore=IPython/utils/_process_cli.py
29 --ignore=IPython/utils/_process_cli.py
30 --ignore=IPython/utils/_process_posix.py
30 --ignore=IPython/utils/_process_posix.py
31 --ignore=IPython/utils/_process_win32.py
31 --ignore=IPython/utils/_process_win32.py
32 --ignore=IPython/utils/_process_win32_controller.py
32 --ignore=IPython/utils/_process_win32_controller.py
33 --ignore=IPython/utils/daemonize.py
33 --ignore=IPython/utils/daemonize.py
34 --ignore=IPython/utils/eventful.py
34 --ignore=IPython/utils/eventful.py
35
35
36 --ignore=IPython/kernel
36 --ignore=IPython/kernel
37 --ignore=IPython/consoleapp.py
37 --ignore=IPython/consoleapp.py
38 --ignore=IPython/core/inputsplitter.py
38 --ignore=IPython/core/inputsplitter.py
39 --ignore-glob=IPython/lib/inputhook*.py
39 --ignore-glob=IPython/lib/inputhook*.py
40 --ignore=IPython/lib/kernel.py
40 --ignore=IPython/lib/kernel.py
41 --ignore=IPython/utils/jsonutil.py
41 --ignore=IPython/utils/jsonutil.py
42 --ignore=IPython/utils/localinterfaces.py
42 --ignore=IPython/utils/localinterfaces.py
43 --ignore=IPython/utils/log.py
43 --ignore=IPython/utils/log.py
44 --ignore=IPython/utils/signatures.py
44 --ignore=IPython/utils/signatures.py
45 --ignore=IPython/utils/traitlets.py
45 --ignore=IPython/utils/traitlets.py
46 --ignore=IPython/utils/version.py
46 doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
47 doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
47 ipdoctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
48 ipdoctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
General Comments 0
You need to be logged in to leave comments. Login now