Skip to content

Commit cab0ebe

Browse files
authored
Merge pull request #153 from abstractfactory/cosmetics
Cosmetics
2 parents 770484d + 31e33ef commit cab0ebe

File tree

3 files changed

+101
-58
lines changed

3 files changed

+101
-58
lines changed

CONTRIBUTING.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Qt.py was born to address the growing needs in these industries for the developm
1111
- [Development goals](#development-goals)
1212
- [Support co-existence](#support-co-existence)
1313
- [Keep it simple](#keep-it-simple)
14-
- [No bugs](#no-bugs)
14+
- [No wrappers](#no-wrappers)
1515
- [How can I contribute?](#how-can-i-contribute)
1616
- [Reporting Bugs](#reporting-bugs)
1717
- [Suggesting Enhancements](#suggesting-enhancements)
@@ -28,11 +28,11 @@ Qt.py was born to address the growing needs in these industries for the developm
2828

2929
Qt.py was born in the film and visual effects industry to address the growing needs for the development of software capable of running with more than one flavor of the Qt bindings for Python - PySide, PySide2, PyQt4 and PyQt5.
3030

31-
| Goal | Description
32-
|:--------------------------|:---------------
33-
| *Support co-existence* | Qt.py should not affect other bindings running in same interpreter session.
34-
| *Keep it simple* | One file, copy/paste installation, PEP08.
35-
| *No bugs* | No implementations = No bugs.
31+
| Goal | Description
32+
|:---------------------------|:---------------
33+
| [*Support co-existence*](#support-coexistence) | Qt.py should not affect other bindings running in same interpreter session.
34+
| [*Keep it simple*](#keep-it-simple) | One file, copy/paste installation, PEP08.
35+
| [*No wrappers*](#no-wrappers) | Don't attempt to fill in for missing functionality in a binding.
3636

3737
Each of these deserve some explanation and rationale.
3838

@@ -55,8 +55,7 @@ QtWidgets.QApplication.translate = staticmethod(translate)
5555

5656
```python
5757
# Right
58-
...
59-
QtWidgets.QApplication.translate_ = staticmethod(translate)
58+
QtCompat.translate = translate
6059
```
6160

6261
<br>
@@ -67,6 +66,20 @@ At the end of the day, Qt.py is a middle-man. It delegates requests you make to
6766

6867
<br>
6968

69+
#### No wrappers
70+
71+
One approach at bridging two different implementations is by implementing missing functionality yourself.
72+
73+
A [common example](https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8) of this is the differing argument signature in `loadUi` from PyQt4 versus PySide.
74+
75+
One problem with this approach is that bindings are already wrapping an original implementation and carries a large surface area for bugs with it. By wrapping it once more, we multiply this surface area, resulting in potential for even more obscure bugs that may take years to experience and filter out.
76+
77+
By instead limiting the argument signature to ones they both share, we both (1) reduce the surface area (2) avoid introducing additional bugs.
78+
79+
We believe neither approach is right or wrong - this is simply the approach taken here that turns out to be the easier and more robust rule to follow consistently as a team.
80+
81+
<br>
82+
7083
##### No bugs
7184

7285
This may seem like an impossible requirement, but hear me out. Bugs stem from implementations. Therefore, if there are no implementations, there can be no bugs.

Qt.py

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,32 @@
3232
import sys
3333
import shutil
3434

35-
self = sys.modules[__name__]
35+
# Flags from environment variables
36+
QT_VERBOSE = bool(os.getenv("QT_VERBOSE")) # Extra output
37+
QT_TESTING = bool(os.getenv("QT_TESTING")) # Extra constraints
38+
QT_PREFERRED_BINDING = os.getenv("QT_PREFERRED_BINDING") # Override default
3639

37-
self.__version__ = "0.6.1"
40+
self = sys.modules[__name__]
3841

39-
self.__added__ = list() # All unique members of Qt.py
42+
# Internal members, may be used externally for debugging
43+
self.__added__ = list() # All members added to QtCompat
4044
self.__remapped__ = list() # Members copied from elsewhere
4145
self.__modified__ = list() # Existing members modified in some way
4246

4347
# Below members are set dynamically on import relative the original binding.
48+
self.__version__ = "0.6.2"
4449
self.__qt_version__ = "0.0.0"
4550
self.__binding__ = "None"
4651
self.__binding_version__ = "0.0.0"
52+
4753
self.load_ui = lambda fname: None
4854
self.translate = lambda context, sourceText, disambiguation, n: None
49-
self.setSectionResizeMode = lambda *args, **kwargs: None
55+
self.setSectionResizeMode = lambda logicalIndex, hide: None
56+
57+
# All members of this module is directly accessible via QtCompat
58+
# Take care not to access any "private" members; i.e. those with
59+
# a leading underscore.
60+
QtCompat = self
5061

5162

5263
def convert(lines):
@@ -89,7 +100,7 @@ def _remap(object, name, value, safe=True):
89100
90101
"""
91102

92-
if os.getenv("QT_TESTING") is not None and safe:
103+
if QT_TESTING is not None and safe:
93104
# Cannot alter original binding.
94105
if hasattr(object, name):
95106
raise AttributeError("Cannot override existing name: "
@@ -112,7 +123,7 @@ def _remap(object, name, value, safe=True):
112123
def _add(object, name, value):
113124
"""Append to self, accessible via Qt.QtCompat"""
114125
self.__added__.append(name)
115-
setattr(self, name, value)
126+
setattr(object, name, value)
116127

117128

118129
def _pyqt5():
@@ -123,13 +134,10 @@ def _pyqt5():
123134
_remap(QtCore, "Slot", QtCore.pyqtSlot)
124135
_remap(QtCore, "Property", QtCore.pyqtProperty)
125136

126-
_add(PyQt5, "__binding__", PyQt5.__name__)
127-
_add(PyQt5, "load_ui", lambda fname: uic.loadUi(fname))
128-
_add(PyQt5, "translate", lambda context, sourceText, disambiguation, n: (
129-
QtCore.QCoreApplication(context, sourceText,
130-
disambiguation, n)))
131-
_add(PyQt5,
132-
"setSectionResizeMode",
137+
_add(QtCompat, "__binding__", PyQt5.__name__)
138+
_add(QtCompat, "load_ui", lambda fname: uic.loadUi(fname))
139+
_add(QtCompat, "translate", QtCore.QCoreApplication.translate)
140+
_add(QtCompat, "setSectionResizeMode",
133141
QtWidgets.QHeaderView.setSectionResizeMode)
134142

135143
_maintain_backwards_compatibility(PyQt5)
@@ -172,16 +180,27 @@ def _pyqt4():
172180
from PyQt4 import QtWebKit
173181
_remap(PyQt4, "QtWebKitWidgets", QtWebKit)
174182
except ImportError:
175-
# QtWebkit is optional in Qt , therefore might not be available
176-
pass
177-
178-
_add(PyQt4, "QtCompat", self)
179-
_add(PyQt4, "__binding__", PyQt4.__name__)
180-
_add(PyQt4, "load_ui", lambda fname: uic.loadUi(fname))
181-
_add(PyQt4, "translate", lambda context, sourceText, disambiguation, n: (
182-
QtCore.QCoreApplication(context, sourceText,
183-
disambiguation, None, n)))
184-
_add(PyQt4, "setSectionResizeMode", QtGui.QHeaderView.setResizeMode)
183+
"QtWebkit is optional in Qt , therefore might not be available"
184+
185+
_add(QtCompat, "__binding__", PyQt4.__name__)
186+
_add(QtCompat, "load_ui", lambda fname: uic.loadUi(fname))
187+
188+
# The second argument - hide - does not apply to Qt4
189+
_add(QtCompat, "setSectionResizeMode",
190+
lambda logicalIndex, hide:
191+
QtGui.QHeaderView.setResizeMode(logicalIndex))
192+
193+
# PySide2 differs from Qt4 in that Qt4 has one extra argument
194+
# which is always `None`. The lambda arguments represents the PySide2
195+
# interface, whereas the arguments passed to `.translate` represent
196+
# those expected of a Qt4 binding.
197+
_add(QtCompat, "translate",
198+
lambda context, sourceText, disambiguation, n:
199+
QtCore.QCoreApplication.translate(context,
200+
sourceText,
201+
disambiguation,
202+
None,
203+
n))
185204

186205
_maintain_backwards_compatibility(PyQt4)
187206

@@ -194,15 +213,20 @@ def _pyside2():
194213

195214
_remap(QtCore, "QStringListModel", QtGui.QStringListModel)
196215

197-
_add(PySide2, "__binding__", PySide2.__name__)
198-
_add(PySide2, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
199-
_add(PySide2, "translate", lambda context, sourceText, disambiguation, n: (
200-
QtCore.QCoreApplication(context, sourceText,
201-
disambiguation, None, n)))
202-
_add(PySide2,
203-
"setSectionResizeMode",
216+
_add(QtCompat, "__binding__", PySide2.__name__)
217+
_add(QtCompat, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
218+
219+
_add(QtCompat, "setSectionResizeMode",
204220
QtWidgets.QHeaderView.setSectionResizeMode)
205221

222+
_add(QtCompat, "translate",
223+
lambda context, sourceText, disambiguation, n:
224+
QtCore.QCoreApplication.translate(context,
225+
sourceText,
226+
disambiguation,
227+
None,
228+
n))
229+
206230
_maintain_backwards_compatibility(PySide2)
207231

208232
return PySide2
@@ -223,15 +247,22 @@ def _pyside():
223247
from PySide import QtWebKit
224248
_remap(PySide, "QtWebKitWidgets", QtWebKit)
225249
except ImportError:
226-
# QtWebkit is optional in Qt, therefore might not be available
227-
pass
250+
"QtWebkit is optional in Qt, therefore might not be available"
251+
252+
_add(QtCompat, "__binding__", PySide.__name__)
253+
_add(QtCompat, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
254+
255+
_add(QtCompat, "setSectionResizeMode",
256+
lambda logicalIndex, hide:
257+
QtGui.QHeaderView.setResizeMode(logicalIndex))
228258

229-
_add(PySide, "__binding__", PySide.__name__)
230-
_add(PySide, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
231-
_add(PySide, "translate", lambda context, sourceText, disambiguation, n: (
232-
QtCore.QCoreApplication(context, sourceText,
233-
disambiguation, None, n)))
234-
_add(PySide, "setSectionResizeMode", QtGui.QHeaderView.setResizeMode)
259+
_add(QtCompat, "translate",
260+
lambda context, sourceText, disambiguation, n:
261+
QtCore.QCoreApplication.translate(context,
262+
sourceText,
263+
disambiguation,
264+
None,
265+
n))
235266

236267
_maintain_backwards_compatibility(PySide)
237268

@@ -311,17 +342,15 @@ def init():
311342
312343
"""
313344

314-
preferred = os.getenv("QT_PREFERRED_BINDING")
315-
verbose = os.getenv("QT_VERBOSE") is not None
316345
bindings = (_pyside2, _pyqt5, _pyside, _pyqt4)
317346

318-
if preferred:
347+
if QT_PREFERRED_BINDING:
319348
# Internal flag (used in installer)
320-
if preferred == "None":
349+
if QT_PREFERRED_BINDING == "None":
321350
self.__wrapper_version__ = self.__version__
322351
return
323352

324-
preferred = preferred.split(os.pathsep)
353+
preferred = QT_PREFERRED_BINDING.split(os.pathsep)
325354
available = {
326355
"PySide2": _pyside2,
327356
"PyQt5": _pyqt5,
@@ -338,19 +367,19 @@ def init():
338367
)
339368

340369
for binding in bindings:
341-
_log("Trying %s" % binding.__name__, verbose)
370+
_log("Trying %s" % binding.__name__, QT_VERBOSE)
342371

343372
try:
344373
binding = binding()
345374

346375
except ImportError as e:
347-
_log(" - ImportError(\"%s\")" % e, verbose)
376+
_log(" - ImportError(\"%s\")" % e, QT_VERBOSE)
348377
continue
349378

350379
else:
351380
# Reference to this module
352-
binding.__shim__ = self
353381
binding.QtCompat = self
382+
binding.__shim__ = self # DEPRECATED
354383

355384
sys.modules.update({
356385
__name__: binding,

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ Qt.py enables you to write software that dynamically chooses the most desireable
2828

2929
### Project goals
3030

31+
Write for PySide2, run in any binding.
3132

32-
Qt.py was born in the film and visual effects industry to address the growing need for the development of software capable of running with more than one flavour of the Qt bindings for Python - PySide, PySide2, PyQt4 and PyQt5.
33+
Qt.py was born in the film and visual effects industry to address the growing need for software capable of running with more than one flavor of the Qt bindings for Python - PySide, PySide2, PyQt4 and PyQt5.
3334

3435
| Goal | Description
3536
|:-------------------------------------|:---------------
36-
| *Build for one, run with all* | You code written with Qt.py should run on any binding.
37-
| *Explicit is better than implicit* | Differences between bindings should be visible to you.
3837
| *Support co-existence* | Qt.py should not affect other bindings running in same interpreter session.
38+
| *Build for one, run with all* | Code written with Qt.py should run on any binding.
39+
| *Explicit is better than implicit* | Differences between bindings should be visible to you.
3940

4041
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for more details.
4142

@@ -45,7 +46,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for more details.
4546

4647
### Install
4748

48-
Qt.py is a single file and can either be [copy/pasted](https://raw.githubusercontent.com/mottosso/Qt.py/master/Qt.py) into your project, [downloaded](https://github.com/mottosso/Qt.py/archive/master.zip) as-is or installed via PyPI.
49+
Qt.py is a single file and can either be [copy/pasted](https://raw.githubusercontent.com/mottosso/Qt.py/master/Qt.py) into your project, [downloaded](https://github.com/mottosso/Qt.py/archive/master.zip) as-is, cloned as-is or installed via PyPI.
4950

5051
```bash
5152
$ pip install Qt.py

0 commit comments

Comments
 (0)