Skip to content

Commit f94b755

Browse files
authored
Merge pull request #130 from abstractfactory/master
Implement #111
2 parents 86051d7 + f506ddc commit f94b755

File tree

2 files changed

+138
-133
lines changed

2 files changed

+138
-133
lines changed

Qt.py

Lines changed: 128 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22
33
This module replaces itself with the most desirable binding.
44
5-
Resolution order:
5+
Project goals:
6+
Qt.py was born in the film and visual effects industry to address
7+
the growing need for the development of software capable of running
8+
with more than one flavour of the Qt bindings for Python - PySide,
9+
PySide2, PyQt4 and PyQt5.
10+
11+
1. Build for one, run with all
12+
2. Explicit is better than implicit
13+
3. Support co-existence
14+
15+
Default resolution order:
616
- PySide2
717
- PyQt5
818
- PySide
@@ -21,28 +31,74 @@
2131
import os
2232
import sys
2333

24-
__version__ = "0.3.4"
34+
__version__ = "0.4.0"
35+
36+
# All unique members of Qt.py
37+
__added__ = list()
38+
39+
# Members copied from elsewhere, such as QtGui -> QtWidgets
40+
__remapped__ = list()
41+
42+
# Existing members modified in some way
43+
__modified__ = list()
44+
45+
46+
def remap(object, name, value, safe=True):
47+
"""Prevent accidental assignment of existing members
48+
49+
Arguments:
50+
object (object): Parent of new attribute
51+
name (str): Name of new attribute
52+
value (object): Value of new attribute
53+
safe (bool): Whether or not to guarantee that
54+
the new attribute was not overwritten.
55+
Can be set to False under condition that
56+
it is superseded by extensive testing.
57+
58+
"""
59+
60+
if safe:
61+
# Cannot alter original binding.
62+
if hasattr(object, name):
63+
raise AttributeError("Cannot override existing name: "
64+
"%s.%s" % (object.__name__, name))
2565

66+
# Cannot alter classes of functions
67+
if type(object).__name__ != "module":
68+
raise AttributeError("%s != 'module': Cannot alter "
69+
"anything but modules" % object)
2670

27-
def _pyqt5():
71+
__remapped__.append(name)
72+
setattr(object, name, value)
73+
74+
75+
def add(object, name, value):
76+
"""Identical to :func:`remap` and provided for readability only"""
77+
__added__.append(name)
78+
remap(object, name, value)
79+
80+
81+
def pyqt5():
2882
import PyQt5.Qt
83+
from PyQt5 import QtCore, uic
2984

30-
# Remap
31-
PyQt5.QtCore.Signal = PyQt5.QtCore.pyqtSignal
32-
PyQt5.QtCore.Slot = PyQt5.QtCore.pyqtSlot
33-
PyQt5.QtCore.Property = PyQt5.QtCore.pyqtProperty
85+
remap(QtCore, "Signal", QtCore.pyqtSignal)
86+
remap(QtCore, "Slot", QtCore.pyqtSlot)
87+
remap(QtCore, "Property", QtCore.pyqtProperty)
3488

35-
# Add
36-
PyQt5.__wrapper_version__ = __version__
37-
PyQt5.__binding__ = "PyQt5"
38-
PyQt5.__binding_version__ = PyQt5.QtCore.PYQT_VERSION_STR
39-
PyQt5.__qt_version__ = PyQt5.QtCore.QT_VERSION_STR
40-
PyQt5.load_ui = pyqt5_load_ui
89+
add(PyQt5, "__wrapper_version__", __version__)
90+
add(PyQt5, "__binding__", "PyQt5")
91+
add(PyQt5, "__binding_version__", QtCore.PYQT_VERSION_STR)
92+
add(PyQt5, "__qt_version__", QtCore.QT_VERSION_STR)
93+
add(PyQt5, "__added__", __added__)
94+
add(PyQt5, "__remapped__", __remapped__)
95+
add(PyQt5, "__modified__", __modified__)
96+
add(PyQt5, "load_ui", lambda fname: uic.loadUi(fname))
4197

4298
return PyQt5
4399

44100

45-
def _pyqt4():
101+
def pyqt4():
46102
# Attempt to set sip API v2 (must be done prior to importing PyQt4)
47103
import sip
48104
try:
@@ -61,144 +117,91 @@ def _pyqt4():
61117
raise ImportError
62118

63119
import PyQt4.Qt
64-
65-
# Remap
66-
PyQt4.QtWidgets = PyQt4.QtGui
67-
PyQt4.QtCore.Signal = PyQt4.QtCore.pyqtSignal
68-
PyQt4.QtCore.Slot = PyQt4.QtCore.pyqtSlot
69-
PyQt4.QtCore.Property = PyQt4.QtCore.pyqtProperty
70-
PyQt4.QtCore.QItemSelection = PyQt4.QtGui.QItemSelection
71-
PyQt4.QtCore.QStringListModel = PyQt4.QtGui.QStringListModel
72-
PyQt4.QtCore.QItemSelectionModel = PyQt4.QtGui.QItemSelectionModel
73-
PyQt4.QtCore.QSortFilterProxyModel = PyQt4.QtGui.QSortFilterProxyModel
74-
PyQt4.QtCore.QAbstractProxyModel = PyQt4.QtGui.QAbstractProxyModel
120+
from PyQt4 import QtCore, QtGui, uic
121+
122+
remap(PyQt4, "QtWidgets", QtGui)
123+
remap(QtCore, "Signal", QtCore.pyqtSignal)
124+
remap(QtCore, "Slot", QtCore.pyqtSlot)
125+
remap(QtCore, "Property", QtCore.pyqtProperty)
126+
remap(QtCore, "QItemSelection", QtGui.QItemSelection)
127+
remap(QtCore, "QStringListModel", QtGui.QStringListModel)
128+
remap(QtCore, "QItemSelectionModel", QtGui.QItemSelectionModel)
129+
remap(QtCore, "QSortFilterProxyModel", QtGui.QSortFilterProxyModel)
130+
remap(QtCore, "QAbstractProxyModel", QtGui.QAbstractProxyModel)
75131

76132
try:
77133
from PyQt4 import QtWebKit
78-
PyQt4.QtWebKitWidgets = QtWebKit
134+
remap(PyQt4, "QtWebKitWidgets", QtWebKit)
79135
except ImportError:
80136
# QtWebkit is optional in Qt , therefore might not be available
81137
pass
82138

83-
# Add
84-
PyQt4.__wrapper_version__ = __version__
85-
PyQt4.__binding__ = "PyQt4"
86-
PyQt4.__binding_version__ = PyQt4.QtCore.PYQT_VERSION_STR
87-
PyQt4.__qt_version__ = PyQt4.QtCore.QT_VERSION_STR
88-
PyQt4.load_ui = pyqt4_load_ui
139+
add(PyQt4, "__wrapper_version__", __version__)
140+
add(PyQt4, "__binding__", "PyQt4")
141+
add(PyQt4, "__binding_version__", QtCore.PYQT_VERSION_STR)
142+
add(PyQt4, "__qt_version__", QtCore.QT_VERSION_STR)
143+
add(PyQt4, "__added__", __added__)
144+
add(PyQt4, "__remapped__", __remapped__)
145+
add(PyQt4, "__modified__", __modified__)
146+
add(PyQt4, "load_ui", lambda fname: uic.loadUi(fname))
89147

90148
return PyQt4
91149

92150

93-
def _pyside2():
151+
def pyside2():
94152
import PySide2
95-
from PySide2 import QtGui, QtCore
153+
from PySide2 import QtGui, QtCore, QtUiTools
96154

97-
# Remap
98-
QtCore.QStringListModel = QtGui.QStringListModel
155+
remap(QtCore, "QStringListModel", QtGui.QStringListModel)
99156

100-
# Add
101-
PySide2.__wrapper_version__ = __version__
102-
PySide2.__binding__ = "PySide2"
103-
PySide2.__binding_version__ = PySide2.__version__
104-
PySide2.__qt_version__ = PySide2.QtCore.qVersion()
105-
PySide2.load_ui = pyside2_load_ui
157+
add(PySide2, "__wrapper_version__", __version__)
158+
add(PySide2, "__binding__", "PySide2")
159+
add(PySide2, "__binding_version__", PySide2.__version__)
160+
add(PySide2, "__qt_version__", PySide2.QtCore.qVersion())
161+
add(PySide2, "__added__", __added__)
162+
add(PySide2, "__remapped__", __remapped__)
163+
add(PySide2, "__modified__", __modified__)
164+
add(PySide2, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
106165

107166
return PySide2
108167

109168

110-
def _pyside():
169+
def pyside():
111170
import PySide
112-
from PySide import QtGui, QtCore
113-
QtCore, QtGui # bypass linter warnings
171+
from PySide import QtGui, QtCore, QtUiTools
114172

115-
# Remap
116-
PySide.QtWidgets = PySide.QtGui
117-
PySide.QtCore.QSortFilterProxyModel = PySide.QtGui.QSortFilterProxyModel
118-
PySide.QtCore.QStringListModel = PySide.QtGui.QStringListModel
119-
PySide.QtCore.QItemSelection = PySide.QtGui.QItemSelection
120-
PySide.QtCore.QItemSelectionModel = PySide.QtGui.QItemSelectionModel
121-
PySide.QtCore.QAbstractProxyModel = PySide.QtGui.QAbstractProxyModel
173+
remap(PySide, "QtWidgets", QtGui)
174+
remap(QtCore, "QSortFilterProxyModel", QtGui.QSortFilterProxyModel)
175+
remap(QtCore, "QStringListModel", QtGui.QStringListModel)
176+
remap(QtCore, "QItemSelection", QtGui.QItemSelection)
177+
remap(QtCore, "QItemSelectionModel", QtGui.QItemSelectionModel)
178+
remap(QtCore, "QAbstractProxyModel", QtGui.QAbstractProxyModel)
122179

123180
try:
124181
from PySide import QtWebKit
125-
PySide.QtWebKitWidgets = QtWebKit
182+
remap(PySide, "QtWebKitWidgets", QtWebKit)
126183
except ImportError:
127184
# QtWebkit is optional in Qt , therefore might not be available
128185
pass
129186

130-
# Add
131-
PySide.__wrapper_version__ = __version__
132-
PySide.__binding__ = "PySide"
133-
PySide.__binding_version__ = PySide.__version__
134-
PySide.__qt_version__ = PySide.QtCore.qVersion()
135-
PySide.load_ui = pyside_load_ui
187+
add(PySide, "__wrapper_version__", __version__)
188+
add(PySide, "__binding__", "PySide")
189+
add(PySide, "__binding_version__", PySide.__version__)
190+
add(PySide, "__qt_version__", PySide.QtCore.qVersion())
191+
add(PySide, "__added__", __added__)
192+
add(PySide, "__remapped__", __remapped__)
193+
add(PySide, "__modified__", __modified__)
194+
add(PySide, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
136195

137196
return PySide
138197

139198

140-
def pyside_load_ui(fname):
141-
"""Read Qt Designer .ui `fname`
142-
143-
Args:
144-
fname (str): Absolute path to .ui file
145-
146-
Usage:
147-
>> from Qt import load_ui
148-
>> class MyWindow(QtWidgets.QWidget):
149-
.. fname = 'my_ui.ui'
150-
.. self.ui = load_ui(fname)
151-
..
152-
>> window = MyWindow()
153-
154-
"""
155-
156-
from PySide import QtUiTools
157-
return QtUiTools.QUiLoader().load(fname)
158-
159-
160-
def pyside2_load_ui(fname):
161-
"""Read Qt Designer .ui `fname`
162-
163-
Args:
164-
fname (str): Absolute path to .ui file
165-
166-
"""
167-
168-
from PySide2 import QtUiTools
169-
return QtUiTools.QUiLoader().load(fname)
170-
171-
172-
def pyqt4_load_ui(fname):
173-
"""Read Qt Designer .ui `fname`
174-
175-
Args:
176-
fname (str): Absolute path to .ui file
177-
178-
"""
179-
180-
from PyQt4 import uic
181-
return uic.loadUi(fname)
182-
183-
184-
def pyqt5_load_ui(fname):
185-
"""Read Qt Designer .ui `fname`
186-
187-
Args:
188-
fname (str): Absolute path to .ui file
189-
190-
"""
191-
192-
from PyQt5 import uic
193-
return uic.loadUi(fname)
194-
195-
196-
def _log(text, verbose):
199+
def log(text, verbose):
197200
if verbose:
198201
sys.stdout.write(text)
199202

200203

201-
def _init():
204+
def init():
202205
"""Try loading each binding in turn
203206
204207
Please note: the entire Qt module is replaced with this code:
@@ -211,21 +214,20 @@ def _init():
211214

212215
preferred = os.getenv("QT_PREFERRED_BINDING")
213216
verbose = os.getenv("QT_VERBOSE") is not None
214-
bindings = (_pyside2, _pyqt5, _pyside, _pyqt4)
217+
bindings = (pyside2, pyqt5, pyside, pyqt4)
215218

216219
if preferred:
217-
218220
# Internal flag (used in installer)
219221
if preferred == "None":
220222
sys.modules[__name__].__wrapper_version__ = __version__
221223
return
222224

223225
preferred = preferred.split(os.pathsep)
224226
available = {
225-
"PySide2": _pyside2,
226-
"PyQt5": _pyqt5,
227-
"PySide": _pyside,
228-
"PyQt4": _pyqt4
227+
"PySide2": pyside2,
228+
"PyQt5": pyqt5,
229+
"PySide": pyside,
230+
"PyQt4": pyqt4
229231
}
230232

231233
try:
@@ -237,19 +239,19 @@ def _init():
237239
)
238240

239241
for binding in bindings:
240-
_log("Trying %s" % binding.__name__[1:], verbose)
242+
log("Trying %s" % binding.__name__[1:], verbose)
241243

242244
try:
243245
sys.modules[__name__] = binding()
244246
return
245247

246248
except ImportError as e:
247-
_log(" - ImportError(\"%s\")\n" % e, verbose)
249+
log(" - ImportError(\"%s\")\n" % e, verbose)
248250

249251
continue
250252

251253
# If not binding were found, throw this error
252254
raise ImportError("No Qt binding were found.")
253255

254256

255-
_init()
257+
init()

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,16 @@ app.exec_()
7979

8080
All members of `Qt` stem directly from those available via PySide2, along with these additional members.
8181

82-
| Attribute | Returns | Description
83-
|:------------------------|:----------|:------------
84-
| `__binding__` | `str` | A string reference to binding currently in use
85-
| `__qt_version__` | `str` | Reference to version of Qt, such as Qt 5.6.1
86-
| `__binding_version__` | `str` | Reference to version of binding, such as PySide 1.2.6
87-
| `__wrapper_version__` | `str` | Version of this project
88-
| `load_ui(fname=str)` | `QObject` | Minimal wrapper of PyQt4.loadUi and PySide equivalent
82+
| Attribute | Returns | Description
83+
|:------------------------|:------------|:------------
84+
| `__binding__` | `str` | A string reference to binding currently in use
85+
| `__qt_version__` | `str` | Reference to version of Qt, such as Qt 5.6.1
86+
| `__binding_version__` | `str` | Reference to version of binding, such as PySide 1.2.6
87+
| `__wrapper_version__` | `str` | Version of this project
88+
| `__added__` | `list(str)` | All unique members of Qt.py
89+
| `__remapped__` | `list(str)` | Members copied from elsewhere, such as QtGui -> QtWidgets
90+
| `__modified__` | `list(str)` | Existing members modified in some way
91+
| `load_ui(fname=str)` | `QObject` | Minimal wrapper of PyQt4.loadUi and PySide equivalent
8992

9093
<br>
9194

0 commit comments

Comments
 (0)