From e81276e8c4f985199cb43cc7eaa7616fbf6a553c Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 13:46:08 +0000 Subject: [PATCH 01/14] Add shortcuts to running and testing with Docker --- run_docker.sh | 1 + test_docker.sh | 1 + 2 files changed, 2 insertions(+) create mode 100644 run_docker.sh create mode 100644 test_docker.sh diff --git a/run_docker.sh b/run_docker.sh new file mode 100644 index 00000000..91265bb4 --- /dev/null +++ b/run_docker.sh @@ -0,0 +1 @@ +docker run -ti --rm -v /c/Users/marcus/Dropbox/AF/development/marcus/pyblish/Qt:/Qt.py --entrypoint bash mottosso/qt.py27 diff --git a/test_docker.sh b/test_docker.sh new file mode 100644 index 00000000..474545f4 --- /dev/null +++ b/test_docker.sh @@ -0,0 +1 @@ +docker run --rm -v $(pwd):/Qt.py mottosso/qt.py27 \ No newline at end of file From 34fc4dfc54127d383a21b4457766989b3e328c42 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 13:46:31 +0000 Subject: [PATCH 02/14] Isolate os.environ in tests --- tests.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests.py b/tests.py index dd57a3e3..ffde9415 100644 --- a/tests.py +++ b/tests.py @@ -296,20 +296,19 @@ def test_binding_and_qt_version(): assert Qt.__qt_version__ != "0.0.0", ("Qt version was not populated") -def test_strict(): - """QT_STRICT exposes only a subset of PySide2""" - os.environ["QT_STRICT"] = "1" - from Qt import QtGui - assert not hasattr(QtGui, "QWidget") - - def test_cli(): """Qt.py is available from the command-line""" - os.environ.pop("QT_VERBOSE") - popen = subprocess.Popen([sys.executable, "Qt.py", "--help"], - stdout=subprocess.PIPE) + env = os.environ.copy() + env.pop("QT_VERBOSE") # Do not include debug messages + + popen = subprocess.Popen( + [sys.executable, "Qt.py", "--help"], + stdout=subprocess.PIPE, + env=env + ) + out, err = popen.communicate() - assert out.startswith(b"usage: Qt.py") + assert out.startswith(b"usage: Qt.py"), "\n%s" % out if binding("PyQt4"): From 169683f424d720368dfb7e0be57a4afcd6f6b215 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 13:46:41 +0000 Subject: [PATCH 03/14] Implement #185 --- Qt.py | 731 ++++++++++++++++++++++++++++------------------------------ 1 file changed, 359 insertions(+), 372 deletions(-) diff --git a/Qt.py b/Qt.py index 30d66639..caafc507 100644 --- a/Qt.py +++ b/Qt.py @@ -61,305 +61,22 @@ import sys import types import shutil +import importlib __version__ = "1.0.0.b1" # Enable support for `from Qt import *` -__all__ = [ - "QtGui", - "QtCore", - "QtWidgets", - "QtNetwork", - "QtXml", - "QtHelp", - "QtCompat" -] +__all__ = [] # Flags from environment variables QT_VERBOSE = bool(os.getenv("QT_VERBOSE")) -QT_PREFERRED_BINDING = os.getenv("QT_PREFERRED_BINDING") -QT_STRICT = bool(os.getenv("QT_STRICT")) +QT_PREFERRED_BINDING = os.getenv("QT_PREFERRED_BINDING", "") -# Supported submodules -QtGui = types.ModuleType("QtGui") -QtCore = types.ModuleType("QtCore") -QtWidgets = types.ModuleType("QtWidgets") -QtWidgets = types.ModuleType("QtWidgets") -QtNetwork = types.ModuleType("QtNetwork") -QtXml = types.ModuleType("QtXml") -QtHelp = types.ModuleType("QtHelp") -QtCompat = types.ModuleType("QtCompat") -Qt = sys.modules[__name__] # Reference to this module +# Reference to Qt.py +Qt = sys.modules[__name__] +Qt.QtCompat = types.ModuleType("QtCompat") -# To use other modules, such as QtTest and QtScript, -# use conditional branching and import these explicitly. - - -def _pyside2(): - from PySide2 import ( - QtWidgets, - QtGui, - QtCore, - QtNetwork, - QtXml, - QtHelp, - QtUiTools, - __version__ - ) - - Qt.__binding__ = "PySide2" - Qt.__qt_version__ = QtCore.qVersion() - Qt.__binding_version__ = __version__ - QtCompat.load_ui = lambda fname: QtUiTools.QUiLoader().load(fname) - QtCompat.setSectionResizeMode = QtWidgets.QHeaderView.setSectionResizeMode - QtCompat.translate = QtCore.QCoreApplication.translate - - return QtCore, QtGui, QtWidgets, QtNetwork, QtXml, QtHelp - - -def _pyside(): - from PySide import ( - QtGui, - QtCore, - QtNetwork, - QtXml, - QtHelp, - QtUiTools, - __version__ - ) - - QtWidgets = QtGui - - Qt.__binding__ = "PySide" - Qt.__qt_version__ = QtCore.qVersion() - Qt.__binding_version__ = __version__ - QtCompat.load_ui = lambda fname: QtUiTools.QUiLoader().load(fname) - QtCompat.setSectionResizeMode = QtGui.QHeaderView.setResizeMode - QtCompat.translate = ( - lambda context, sourceText, disambiguation, n: - QtCore.QCoreApplication.translate(context, - sourceText, - disambiguation, - QtCore.QCoreApplication.CodecForTr, - n)) - return QtCore, QtGui, QtWidgets, QtNetwork, QtXml, QtHelp - - -def _pyqt5(): - from PyQt5 import ( - QtWidgets, - QtGui, - QtCore, - QtNetwork, - QtXml, - QtHelp, - uic - ) - - Qt.__binding__ = "PyQt5" - Qt.__qt_version__ = QtCore.QT_VERSION_STR - Qt.__binding_version__ = QtCore.PYQT_VERSION_STR - QtCompat.load_ui = lambda fname: uic.loadUi(fname) - QtCompat.translate = QtCore.QCoreApplication.translate - QtCompat.setSectionResizeMode = QtWidgets.QHeaderView.setSectionResizeMode - - return QtCore, QtGui, QtWidgets, QtNetwork, QtXml, QtHelp - - -def _pyqt4(): - import sip - try: - sip.setapi("QString", 2) - sip.setapi("QVariant", 2) - sip.setapi("QDate", 2) - sip.setapi("QDateTime", 2) - sip.setapi("QTextStream", 2) - sip.setapi("QTime", 2) - sip.setapi("QUrl", 2) - except AttributeError as e: - raise ImportError(str(e)) - # PyQt4 < v4.6 - except ValueError as e: - # API version already set to v1 - raise ImportError(str(e)) - - from PyQt4 import ( - QtGui, - QtCore, - QtNetwork, - QtXml, - QtHelp, - uic - ) - - QtWidgets = QtGui - - Qt.__binding__ = "PyQt4" - Qt.__qt_version__ = QtCore.QT_VERSION_STR - Qt.__binding_version__ = QtCore.PYQT_VERSION_STR - QtCompat.load_ui = lambda fname: uic.loadUi(fname) - QtCompat.setSectionResizeMode = QtGui.QHeaderView.setResizeMode - - # PySide2 differs from Qt4 in that Qt4 has one extra argument - # which is always `None`. The lambda arguments represents the PySide2 - # interface, whereas the arguments passed to `.translate` represent - # those expected of a Qt4 binding. - QtCompat.translate = ( - lambda context, sourceText, disambiguation, n: - QtCore.QCoreApplication.translate(context, - sourceText, - disambiguation, - QtCore.QCoreApplication.CodecForTr, - n)) - - return QtCore, QtGui, QtWidgets, QtNetwork, QtXml, QtHelp - - -def _none(): - """Internal option (used in installer)""" - - Mock = type("Mock", (), {"__getattr__": lambda Qt, attr: None}) - - Qt.__binding__ = "None" - Qt.__qt_version__ = "0.0.0" - Qt.__binding_version__ = "0.0.0" - QtCompat.load_ui = lambda fname: None - QtCompat.setSectionResizeMode = lambda *args, **kwargs: None - - return Mock(), Mock(), Mock(), Mock(), Mock(), Mock() - - -def _log(text): - if QT_VERBOSE: - sys.stdout.write(text + "\n") - - -def _convert(lines): - """Convert compiled .ui file from PySide2 to Qt.py - - Arguments: - lines (list): Each line of of .ui file - - Usage: - >> with open("myui.py") as f: - .. lines = _convert(f.readlines()) - - """ - - def parse(line): - line = line.replace("from PySide2 import", "from Qt import") - line = line.replace("QtWidgets.QApplication.translate", - "Qt.QtCompat.translate") - return line - - parsed = list() - for line in lines: - line = parse(line) - parsed.append(line) - - return parsed - - -def _cli(args): - """Qt.py command-line interface""" - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument("--convert", - help="Path to compiled Python module, e.g. my_ui.py") - parser.add_argument("--compile", - help="Accept raw .ui file and compile with native " - "PySide2 compiler.") - parser.add_argument("--stdout", - help="Write to stdout instead of file", - action="store_true") - parser.add_argument("--stdin", - help="Read from stdin instead of file", - action="store_true") - - args = parser.parse_args(args) - - if args.stdout: - raise NotImplementedError("--stdout") - - if args.stdin: - raise NotImplementedError("--stdin") - - if args.compile: - raise NotImplementedError("--compile") - - if args.convert: - sys.stdout.write("#\n" - "# WARNING: --convert is an ALPHA feature.\n#\n" - "# See https://github.com/mottosso/Qt.py/pull/132\n" - "# for details.\n" - "#\n") - - # - # ------> Read - # - with open(args.convert) as f: - lines = _convert(f.readlines()) - - backup = "%s_backup%s" % os.path.splitext(args.convert) - sys.stdout.write("Creating \"%s\"..\n" % backup) - shutil.copy(args.convert, backup) - - # - # <------ Write - # - with open(args.convert, "w") as f: - f.write("".join(lines)) - - sys.stdout.write("Successfully converted \"%s\"\n" % args.convert) - - -# Default order (customise order and content via QT_PREFERRED_BINDING) -_bindings = (_pyside2, _pyqt5, _pyside, _pyqt4) - -if QT_PREFERRED_BINDING: - _preferred = QT_PREFERRED_BINDING.split(os.pathsep) - _available = { - "PySide2": _pyside2, - "PyQt5": _pyqt5, - "PySide": _pyside, - "PyQt4": _pyqt4, - "None": _none - } - - try: - _bindings = [_available[binding] for binding in _preferred] - except KeyError: - raise ImportError( - ("Requested %s, available: " % _preferred) + - "\n".join(_available.keys()) - ) - - del(_preferred) - del(_available) - -_log("Preferred bindings: %s" % list(_b.__name__ for _b in _bindings)) - - -_found_binding = False -for _binding in _bindings: - _log("Trying %s" % _binding.__name__) - - try: - _QtCore, _QtGui, _QtWidgets, _QtNetwork, _QtXml, _QtHelp = _binding() - _found_binding = True - break - - except ImportError as e: - _log("ImportError: %s" % e) - continue - -if not _found_binding: - # If not binding were found, throw this error - raise ImportError("No Qt binding were found.") - - -"""Members of Qt.py +"""Common members of all bindings This is where each member of Qt.py is explicitly defined. It is based on a "lowest commond denominator" of all bindings; @@ -369,7 +86,7 @@ def _cli(args): """ -_strict_members = { +_common_members = { "QtGui": [ "QAbstractTextDocumentLayout", "QActionEvent", @@ -687,7 +404,6 @@ def _cli(args): "QWizard", "QWizardPage", ], - "QtCore": [ "QAbstractAnimation", "QAbstractEventDispatcher", @@ -891,107 +607,378 @@ def _cli(args): "QTcpServer", "QTcpSocket", "QUdpSocket" + ], + "QtOpenGL": [ + "QGL", + "QGLBuffer", + "QGLColormap", + "QGLContext", + "QGLFormat", + "QGLFramebufferObject", + "QGLFramebufferObjectFormat", + "QGLPixelBuffer", + "QGLShader", + "QGLShaderProgram", + "QGLWidget" ] } -"""Augment QtCompat -QtCompat contains wrappers and added functionality -to the original bindings, such as the CLI interface -and otherwise incompatible members between bindings, -such as `QHeaderView.setSectionResizeMode`. +def _new_module(name): + return types.ModuleType(__name__ + "." + name) -""" -QtCompat._cli = _cli -QtCompat._convert = _convert +def _setup(module, extras): + """Install common submodules""" + Qt.__binding__ = module -"""Apply strict mode + for name in _common_members.keys() + extras: + try: + submodule = importlib.import_module(module + "." + name) + except ImportError: + continue -This make Qt.py into a subset of PySide2 members that exist -across all other bindings. + # Store reference to original binding + setattr(Qt, name, _new_module(name)) + setattr(Qt, "_" + name, submodule) -""" -for module, members in _strict_members.items(): - for member in members: - orig = getattr(sys.modules[__name__], "_%s" % module) - repl = getattr(sys.modules[__name__], module) - setattr(repl, member, getattr(orig, member)) +def _pyside2(): + """Initialise PySide2 + These functions serve to test the existence of a binding + along with set it up in such a way that it aligns with + the final step; adding members from the original binding + to Qt.py -# Enable direct import of submodules -# E.g. import Qt.QtCore -sys.modules.update({ - __name__ + ".QtGui": QtGui, - __name__ + ".QtCore": QtCore, - __name__ + ".QtWidgets": QtWidgets, - __name__ + ".QtXml": QtXml, - __name__ + ".QtNetwork": QtNetwork, - __name__ + ".QtHelp": QtHelp, - __name__ + ".QtCompat": QtCompat, -}) + """ + _setup("PySide2", ["QtUiTools"]) -""" + Qt.__binding_version__ = __import__("PySide2").__version__ + + if hasattr(Qt, "QtUiTools"): + Qt.QtCompat.load_ui = lambda fname: \ + Qt._QtUiTools.QUiLoader().load(fname) + + if hasattr(Qt, "QtGui") and hasattr(Qt, "QtCore"): + Qt.QtCore.QStringListModel = Qt._QtGui.QStringListModel -Special case + if hasattr(Qt, "QtWidgets"): + Qt.QtCompat.setSectionResizeMode = \ + Qt._QtWidgets.QHeaderView.setSectionResizeMode -In some bindings, members are either misplaced or renamed. + if hasattr(Qt, "QtCore"): + Qt.__qt_version__ = Qt._QtCore.qVersion() + Qt.QtCompat.translate = Qt._QtCore.QCoreApplication.translate -TODO: This is difficult to read, compared to the above dictionary. - Find a better way of implementing this, that also simplifies - adding or removing members. + Qt.QtCore.Property = Qt._QtCore.Property + Qt.QtCore.Signal = Qt._QtCore.Signal + Qt.QtCore.Slot = Qt._QtCore.Slot + + Qt.QtCore.QAbstractProxyModel = Qt._QtCore.QAbstractProxyModel + Qt.QtCore.QSortFilterProxyModel = Qt._QtCore.QSortFilterProxyModel + Qt.QtCore.QItemSelection = Qt._QtCore.QItemSelection + Qt.QtCore.QItemSelectionModel = Qt._QtCore.QItemSelectionModel + + +def _pyside(): + """Initialise PySide""" + + _setup("PySide", ["QtUiTools"]) + + Qt.__binding_version__ = __import__("PySide2").__version__ + + if hasattr(Qt, "QtUiTools"): + Qt.QtCompat.load_ui = lambda fname: \ + Qt._QtUiTools.QUiLoader().load(fname) + + if hasattr(Qt, "QtGui"): + setattr(Qt, "QtWidgets", _new_module("QtWidgets")) + setattr(Qt, "_QtWidgets", Qt._QtGui) + + Qt.QtCompat.setSectionResizeMode = Qt._QtGui.QHeaderView.setResizeMode + + if hasattr(Qt, "QtCore"): + Qt.QtCore.QAbstractProxyModel = Qt._QtGui.QAbstractProxyModel + Qt.QtCore.QSortFilterProxyModel = Qt._QtGui.QSortFilterProxyModel + Qt.QtCore.QStringListModel = Qt._QtGui.QStringListModel + Qt.QtCore.QItemSelection = Qt._QtGui.QItemSelection + Qt.QtCore.QItemSelectionModel = Qt._QtGui.QItemSelectionModel + + if hasattr(Qt, "QtCore"): + Qt.__qt_version__ = Qt._QtCore.qVersion() + + Qt.QtCore.Property = Qt._QtCore.Property + Qt.QtCore.Signal = Qt._QtCore.Signal + Qt.QtCore.Slot = Qt._QtCore.Slot + + QCoreApplication = Qt._QtCore.QCoreApplication + Qt.QtCompat.translate = ( + lambda context, sourceText, disambiguation, n: + QCoreApplication.translate( + context, + sourceText, + disambiguation, + QCoreApplication.CodecForTr, + n + ) + ) + + +def _pyqt5(): + """Initialise PyQt5""" + + _setup("PyQt5", ["uic"]) + + if hasattr(Qt, "uic"): + Qt.QtCompat.load_ui = lambda fname: Qt._uic.loadUi(fname) + + if hasattr(Qt, "QtWidgets"): + Qt.QtCompat.setSectionResizeMode = \ + Qt._QtWidgets.QHeaderView.setSectionResizeMode + + if hasattr(Qt, "QtCore"): + Qt.QtCompat.translate = Qt._QtCore.QCoreApplication.translate + + Qt.QtCore.Property = Qt._QtCore.pyqtProperty + Qt.QtCore.Signal = Qt._QtCore.pyqtSignal + Qt.QtCore.Slot = Qt._QtCore.pyqtSlot + + Qt.QtCore.QAbstractProxyModel = Qt._QtCore.QAbstractProxyModel + Qt.QtCore.QSortFilterProxyModel = Qt._QtCore.QSortFilterProxyModel + Qt.QtCore.QStringListModel = Qt._QtCore.QStringListModel + Qt.QtCore.QItemSelection = Qt._QtCore.QItemSelection + Qt.QtCore.QItemSelectionModel = Qt._QtCore.QItemSelectionModel + + Qt.__qt_version__ = Qt._QtCore.QT_VERSION_STR + Qt.__binding_version__ = Qt._QtCore.PYQT_VERSION_STR + + +def _pyqt4(): + """Initialise PyQt4""" + + import sip + try: + sip.setapi("QString", 2) + sip.setapi("QVariant", 2) + sip.setapi("QDate", 2) + sip.setapi("QDateTime", 2) + sip.setapi("QTextStream", 2) + sip.setapi("QTime", 2) + sip.setapi("QUrl", 2) + except AttributeError as e: + raise ImportError(str(e)) + # PyQt4 < v4.6 + except ValueError as e: + # API version already set to v1 + raise ImportError(str(e)) + + _setup("PyQt4", ["uic"]) + + if hasattr(Qt, "uic"): + Qt.QtCompat.load_ui = lambda fname: Qt._uic.loadUi(fname) + + if hasattr(Qt, "QtGui"): + setattr(Qt, "QtWidgets", _new_module("QtWidgets")) + setattr(Qt, "_QtWidgets", Qt._QtGui) + + Qt.QtCompat.setSectionResizeMode = \ + Qt._QtGui.QHeaderView.setResizeMode + + if hasattr(Qt, "QtCore"): + Qt.QtCore.QAbstractProxyModel = Qt._QtGui.QAbstractProxyModel + Qt.QtCore.QSortFilterProxyModel = Qt._QtGui.QSortFilterProxyModel + Qt.QtCore.QItemSelection = Qt._QtGui.QItemSelection + Qt.QtCore.QStringListModel = Qt._QtGui.QStringListModel + Qt.QtCore.QItemSelectionModel = Qt._QtGui.QItemSelectionModel + + if hasattr(Qt, "QtCore"): + Qt.__qt_version__ = Qt._QtCore.QT_VERSION_STR + Qt.__binding_version__ = Qt._QtCore.PYQT_VERSION_STR + + Qt.QtCore.Property = Qt._QtCore.pyqtProperty + Qt.QtCore.Signal = Qt._QtCore.pyqtSignal + Qt.QtCore.Slot = Qt._QtCore.pyqtSlot + + QCoreApplication = Qt._QtCore.QCoreApplication + Qt.QtCompat.translate = ( + lambda context, sourceText, disambiguation, n: + QCoreApplication.translate( + context, + sourceText, + disambiguation, + QCoreApplication.CodecForTr, + n) + ) + + +def _none(): + """Internal option (used in installer)""" + + Mock = type("Mock", (), {"__getattr__": lambda Qt, attr: None}) + + Qt.__binding__ = "None" + Qt.__qt_version__ = "0.0.0" + Qt.__binding_version__ = "0.0.0" + Qt.QtCompat.load_ui = lambda fname: None + Qt.QtCompat.setSectionResizeMode = lambda *args, **kwargs: None + + for submodule in _common_members.keys(): + setattr(Qt, submodule, Mock()) + setattr(Qt, "_" + submodule, Mock()) + + +def _log(text): + if QT_VERBOSE: + sys.stdout.write(text + "\n") + + +def _convert(lines): + """Convert compiled .ui file from PySide2 to Qt.py + + Arguments: + lines (list): Each line of of .ui file + + Usage: + >> with open("myui.py") as f: + .. lines = _convert(f.readlines()) + + """ + + def parse(line): + line = line.replace("from PySide2 import", "from Qt import") + line = line.replace("QtWidgets.QApplication.translate", + "Qt.QtCompat.translate") + return line + + parsed = list() + for line in lines: + line = parse(line) + parsed.append(line) + + return parsed + + +def _cli(args): + """Qt.py command-line interface""" + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("--convert", + help="Path to compiled Python module, e.g. my_ui.py") + parser.add_argument("--compile", + help="Accept raw .ui file and compile with native " + "PySide2 compiler.") + parser.add_argument("--stdout", + help="Write to stdout instead of file", + action="store_true") + parser.add_argument("--stdin", + help="Read from stdin instead of file", + action="store_true") + + args = parser.parse_args(args) + + if args.stdout: + raise NotImplementedError("--stdout") + + if args.stdin: + raise NotImplementedError("--stdin") + + if args.compile: + raise NotImplementedError("--compile") + + if args.convert: + sys.stdout.write("#\n" + "# WARNING: --convert is an ALPHA feature.\n#\n" + "# See https://github.com/mottosso/Qt.py/pull/132\n" + "# for details.\n" + "#\n") + + # + # ------> Read + # + with open(args.convert) as f: + lines = _convert(f.readlines()) + + backup = "%s_backup%s" % os.path.splitext(args.convert) + sys.stdout.write("Creating \"%s\"..\n" % backup) + shutil.copy(args.convert, backup) + + # + # <------ Write + # + with open(args.convert, "w") as f: + f.write("".join(lines)) + + sys.stdout.write("Successfully converted \"%s\"\n" % args.convert) + + +def _install(): + # Default order (customise order and content via QT_PREFERRED_BINDING) + default_order = ("PySide2", "PyQt5", "PySide", "PyQt4", "None") + preferred_order = QT_PREFERRED_BINDING.split(os.pathsep) + order = preferred_order or default_order + available = { + "PySide2": _pyside2, + "PyQt5": _pyqt5, + "PySide": _pyside, + "PyQt4": _pyqt4, + "None": _none + } + + _log("Order: '%s'" % "', '".join(order)) + + found_binding = False + for name in order: + _log("Trying %s" % name) + + try: + available[name]() + found_binding = True + break + + except ImportError as e: + _log("ImportError: %s" % e) + + except KeyError: + _log("ImportError: Preferred binding '%s' not found." % name) + + if not found_binding: + # If not binding were found, throw this error + raise ImportError("No Qt binding were found.") + + # Install individual members + for name, members in _common_members.items(): + try: + their_submodule = getattr(Qt, "_%s" % name) + except AttributeError: + continue + + our_submodule = getattr(Qt, name) + + __all__.append(name) + sys.modules[__name__ + "." + name] = our_submodule + + for member in members: + setattr(our_submodule, member, getattr(their_submodule, member)) + + +_install() + + +"""Augment QtCompat + +QtCompat contains wrappers and added functionality +to the original bindings, such as the CLI interface +and otherwise incompatible members between bindings, +such as `QHeaderView.setSectionResizeMode`. """ -if "PySide2" == Qt.__binding__: - QtCore.QAbstractProxyModel = _QtCore.QAbstractProxyModel - QtCore.QSortFilterProxyModel = _QtCore.QSortFilterProxyModel - QtCore.QStringListModel = _QtGui.QStringListModel - QtCore.QItemSelection = _QtCore.QItemSelection - QtCore.QItemSelectionModel = _QtCore.QItemSelectionModel - -if "PyQt5" == Qt.__binding__: - QtCore.QAbstractProxyModel = _QtCore.QAbstractProxyModel - QtCore.QSortFilterProxyModel = _QtCore.QSortFilterProxyModel - QtCore.QStringListModel = _QtCore.QStringListModel - QtCore.QItemSelection = _QtCore.QItemSelection - QtCore.QItemSelectionModel = _QtCore.QItemSelectionModel - -if "PySide" == Qt.__binding__: - QtCore.QAbstractProxyModel = _QtGui.QAbstractProxyModel - QtCore.QSortFilterProxyModel = _QtGui.QSortFilterProxyModel - QtCore.QStringListModel = _QtGui.QStringListModel - QtCore.QItemSelection = _QtGui.QItemSelection - QtCore.QItemSelectionModel = _QtGui.QItemSelectionModel - -if "PyQt4" == Qt.__binding__: - QtCore.QAbstractProxyModel = _QtGui.QAbstractProxyModel - QtCore.QSortFilterProxyModel = _QtGui.QSortFilterProxyModel - QtCore.QItemSelection = _QtGui.QItemSelection - QtCore.QStringListModel = _QtGui.QStringListModel - QtCore.QItemSelectionModel = _QtGui.QItemSelectionModel - -if "PyQt" in Qt.__binding__: - QtCore.Property = _QtCore.pyqtProperty - QtCore.Signal = _QtCore.pyqtSignal - QtCore.Slot = _QtCore.pyqtSlot - -else: - QtCore.Property = _QtCore.Property - QtCore.Signal = _QtCore.Signal - QtCore.Slot = _QtCore.Slot - - -# Hide internal members from external use. -del(_QtCore) -del(_QtGui) -del(_QtWidgets) -del(_bindings) -del(_binding) -del(_found_binding) +Qt.QtCompat._cli = _cli +Qt.QtCompat._convert = _convert # Enable command-line interface if __name__ == "__main__": From 9f8a78ddf2ebf959a47cd6048098746773e3523d Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 14:08:47 +0000 Subject: [PATCH 04/14] Fix preferred bindings Allow individual missing members from submodule --- Qt.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Qt.py b/Qt.py index caafc507..5aad6646 100644 --- a/Qt.py +++ b/Qt.py @@ -610,15 +610,8 @@ ], "QtOpenGL": [ "QGL", - "QGLBuffer", - "QGLColormap", "QGLContext", "QGLFormat", - "QGLFramebufferObject", - "QGLFramebufferObjectFormat", - "QGLPixelBuffer", - "QGLShader", - "QGLShaderProgram", "QGLWidget" ] } @@ -633,7 +626,7 @@ def _setup(module, extras): Qt.__binding__ = module - for name in _common_members.keys() + extras: + for name in list(_common_members) + extras: try: submodule = importlib.import_module(module + "." + name) except ImportError: @@ -918,8 +911,12 @@ def _cli(args): def _install(): # Default order (customise order and content via QT_PREFERRED_BINDING) default_order = ("PySide2", "PyQt5", "PySide", "PyQt4", "None") - preferred_order = QT_PREFERRED_BINDING.split(os.pathsep) + preferred_order = list( + b for b in QT_PREFERRED_BINDING.split(os.pathsep) if b + ) + order = preferred_order or default_order + available = { "PySide2": _pyside2, "PyQt5": _pyqt5, @@ -958,11 +955,22 @@ def _install(): our_submodule = getattr(Qt, name) + # Enable import * __all__.append(name) + + # Enable direct import of submodule, + # e.g. import Qt.QtCore sys.modules[__name__ + "." + name] = our_submodule for member in members: - setattr(our_submodule, member, getattr(their_submodule, member)) + # Accept that a submodule may miss certain members. + try: + their_member = getattr(their_submodule, member) + except AttributeError: + _log("'%s.%s' was missing." % (name, member)) + continue + + setattr(our_submodule, member, their_member) _install() From 3343230d26c1ebbf1218c0c42ddebeeccaa04ef4 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 14:13:55 +0000 Subject: [PATCH 05/14] Version bump --- Qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Qt.py b/Qt.py index 5aad6646..c2970e8e 100644 --- a/Qt.py +++ b/Qt.py @@ -63,7 +63,7 @@ import shutil import importlib -__version__ = "1.0.0.b1" +__version__ = "1.0.0.b2" # Enable support for `from Qt import *` __all__ = [] From 85f3115eb50a89ceb614a0b174b51504a967a5d0 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 15:03:08 +0000 Subject: [PATCH 06/14] Update member exclusion list Sometimes, packages are updated natively on the OS, in this case Ubuntu. Now this member was discarded. --- build_docker.sh | 1 + build_membership.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 build_docker.sh diff --git a/build_docker.sh b/build_docker.sh new file mode 100644 index 00000000..3d35543c --- /dev/null +++ b/build_docker.sh @@ -0,0 +1 @@ +docker build -t mottosso/qt.py27 -f Dockerfile-py2.7 . diff --git a/build_membership.py b/build_membership.py index 773029e8..e792d427 100644 --- a/build_membership.py +++ b/build_membership.py @@ -238,6 +238,7 @@ def test_{binding}_members(): "QOpenGLContext", "QOpenGLFramebufferObject", "QOpenGLShader", + "QOpenGLBuffer", "QScreen", ], From 91e0f4d3bd74291721ef5415a9698d736fcaa50d Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 15:03:22 +0000 Subject: [PATCH 07/14] Add News to README --- README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c7b576f0..bb10bdfa 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,34 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings - PySide2, PyQt5, PySide and PyQt4. -**Guides** +
+ +##### News + +| Date | Version | Event +|:---------|:----------|:---------- +| Mar 2017 | [1.0.0][] | Increased safety, **backwards incompatible** +| Sep 2016 | [0.6.0][] | Introducing `QtCompat` +| Sep 2016 | [0.5.0][] | Alpha release of `--convert` +| Jun 2016 | [0.2.6][] | First release of Qt.py + +- [More details](https://github.com/mottosso/Qt.py/releases). + +[0.2.6]: https://github.com/mottosso/Qt.py/releases/tag/0.2.6 +[0.5.0]: https://github.com/mottosso/Qt.py/releases/tag/0.5.0 +[0.6.0]: https://github.com/mottosso/Qt.py/releases/tag/0.6.0 +[1.0.0]: https://github.com/mottosso/Qt.py/releases/tag/1.0.0 + +##### Guides - [Developing with Qt.py](https://fredrikaverpil.github.io/2016/07/25/developing-with-qt-py/) - [Dealing with Maya 2017 and PySide2](https://fredrikaverpil.github.io/2016/07/25/dealing-with-maya-2017-and-pyside2/) -**Mentions** +##### Mentions + - [Qt.py: A portable wrapper for Qt](https://www.udemy.com/python-for-maya/learn/v4/t/lecture/6027394) (Udemy course, registration required) -**Table of contents** +##### Table of contents - [Install](#install) - [Usage](#usage) From 70626d32dc4aa04462f1a2d4707b744f90aef50b Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sat, 11 Mar 2017 17:48:20 +0000 Subject: [PATCH 08/14] Update README --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index bb10bdfa..176aaab0 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,7 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings - [Developing with Qt.py](https://fredrikaverpil.github.io/2016/07/25/developing-with-qt-py/) - [Dealing with Maya 2017 and PySide2](https://fredrikaverpil.github.io/2016/07/25/dealing-with-maya-2017-and-pyside2/) - -##### Mentions - -- [Qt.py: A portable wrapper for Qt](https://www.udemy.com/python-for-maya/learn/v4/t/lecture/6027394) (Udemy course, registration required) +- [Udemy Course](https://www.udemy.com/python-for-maya/learn/v4/t/lecture/6027394) ##### Table of contents From abd8b944e78a119e923629d241e109df4796461e Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 13 Mar 2017 17:31:25 +0000 Subject: [PATCH 09/14] Throw ImportError on no available binding and don't expose extra modules --- Qt.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Qt.py b/Qt.py index c2970e8e..7f44d639 100644 --- a/Qt.py +++ b/Qt.py @@ -624,17 +624,23 @@ def _new_module(name): def _setup(module, extras): """Install common submodules""" - Qt.__binding__ = module + Qt.__binding__ = module.__name__ for name in list(_common_members) + extras: try: - submodule = importlib.import_module(module + "." + name) + # print("Trying %s" % name) + submodule = importlib.import_module( + module.__name__ + "." + name) except ImportError: + # print("Failed %s" % name) continue - # Store reference to original binding - setattr(Qt, name, _new_module(name)) - setattr(Qt, "_" + name, submodule) + if name not in extras: + # Store reference to original binding, + # but don't store speciality modules + # such as uic or QtUiTools + setattr(Qt, name, _new_module(name)) + setattr(Qt, "_" + name, submodule) def _pyside2(): @@ -647,7 +653,8 @@ def _pyside2(): """ - _setup("PySide2", ["QtUiTools"]) + import PySide2 as module + _setup(module, ["QtUiTools"]) Qt.__binding_version__ = __import__("PySide2").__version__ @@ -679,7 +686,8 @@ def _pyside2(): def _pyside(): """Initialise PySide""" - _setup("PySide", ["QtUiTools"]) + import PySide as module + _setup(module, ["QtUiTools"]) Qt.__binding_version__ = __import__("PySide2").__version__ @@ -723,7 +731,8 @@ def _pyside(): def _pyqt5(): """Initialise PyQt5""" - _setup("PyQt5", ["uic"]) + import PyQt5 as module + _setup(module, ["uic"]) if hasattr(Qt, "uic"): Qt.QtCompat.load_ui = lambda fname: Qt._uic.loadUi(fname) @@ -768,7 +777,8 @@ def _pyqt4(): # API version already set to v1 raise ImportError(str(e)) - _setup("PyQt4", ["uic"]) + import PyQt4 as module + _setup(module, ["uic"]) if hasattr(Qt, "uic"): Qt.QtCompat.load_ui = lambda fname: Qt._uic.loadUi(fname) @@ -910,7 +920,7 @@ def _cli(args): def _install(): # Default order (customise order and content via QT_PREFERRED_BINDING) - default_order = ("PySide2", "PyQt5", "PySide", "PyQt4", "None") + default_order = ("PySide2", "PyQt5", "PySide", "PyQt4") preferred_order = list( b for b in QT_PREFERRED_BINDING.split(os.pathsep) if b ) From b71222503cf8fcdeee1d7129444376350735eab2 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 13 Mar 2017 17:44:25 +0000 Subject: [PATCH 10/14] Restore load_ui --- Qt.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Qt.py b/Qt.py index 7f44d639..f0631418 100644 --- a/Qt.py +++ b/Qt.py @@ -635,12 +635,13 @@ def _setup(module, extras): # print("Failed %s" % name) continue + setattr(Qt, "_" + name, submodule) + if name not in extras: # Store reference to original binding, # but don't store speciality modules # such as uic or QtUiTools setattr(Qt, name, _new_module(name)) - setattr(Qt, "_" + name, submodule) def _pyside2(): @@ -658,18 +659,18 @@ def _pyside2(): Qt.__binding_version__ = __import__("PySide2").__version__ - if hasattr(Qt, "QtUiTools"): + if hasattr(Qt, "_QtUiTools"): Qt.QtCompat.load_ui = lambda fname: \ Qt._QtUiTools.QUiLoader().load(fname) - if hasattr(Qt, "QtGui") and hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtGui") and hasattr(Qt, "_QtCore"): Qt.QtCore.QStringListModel = Qt._QtGui.QStringListModel - if hasattr(Qt, "QtWidgets"): + if hasattr(Qt, "_QtWidgets"): Qt.QtCompat.setSectionResizeMode = \ Qt._QtWidgets.QHeaderView.setSectionResizeMode - if hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtCore"): Qt.__qt_version__ = Qt._QtCore.qVersion() Qt.QtCompat.translate = Qt._QtCore.QCoreApplication.translate @@ -691,24 +692,24 @@ def _pyside(): Qt.__binding_version__ = __import__("PySide2").__version__ - if hasattr(Qt, "QtUiTools"): + if hasattr(Qt, "_QtUiTools"): Qt.QtCompat.load_ui = lambda fname: \ Qt._QtUiTools.QUiLoader().load(fname) - if hasattr(Qt, "QtGui"): + if hasattr(Qt, "_QtGui"): setattr(Qt, "QtWidgets", _new_module("QtWidgets")) setattr(Qt, "_QtWidgets", Qt._QtGui) Qt.QtCompat.setSectionResizeMode = Qt._QtGui.QHeaderView.setResizeMode - if hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtCore"): Qt.QtCore.QAbstractProxyModel = Qt._QtGui.QAbstractProxyModel Qt.QtCore.QSortFilterProxyModel = Qt._QtGui.QSortFilterProxyModel Qt.QtCore.QStringListModel = Qt._QtGui.QStringListModel Qt.QtCore.QItemSelection = Qt._QtGui.QItemSelection Qt.QtCore.QItemSelectionModel = Qt._QtGui.QItemSelectionModel - if hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtCore"): Qt.__qt_version__ = Qt._QtCore.qVersion() Qt.QtCore.Property = Qt._QtCore.Property @@ -734,14 +735,14 @@ def _pyqt5(): import PyQt5 as module _setup(module, ["uic"]) - if hasattr(Qt, "uic"): + if hasattr(Qt, "_uic"): Qt.QtCompat.load_ui = lambda fname: Qt._uic.loadUi(fname) - if hasattr(Qt, "QtWidgets"): + if hasattr(Qt, "_QtWidgets"): Qt.QtCompat.setSectionResizeMode = \ Qt._QtWidgets.QHeaderView.setSectionResizeMode - if hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtCore"): Qt.QtCompat.translate = Qt._QtCore.QCoreApplication.translate Qt.QtCore.Property = Qt._QtCore.pyqtProperty @@ -780,24 +781,24 @@ def _pyqt4(): import PyQt4 as module _setup(module, ["uic"]) - if hasattr(Qt, "uic"): + if hasattr(Qt, "_uic"): Qt.QtCompat.load_ui = lambda fname: Qt._uic.loadUi(fname) - if hasattr(Qt, "QtGui"): + if hasattr(Qt, "_QtGui"): setattr(Qt, "QtWidgets", _new_module("QtWidgets")) setattr(Qt, "_QtWidgets", Qt._QtGui) Qt.QtCompat.setSectionResizeMode = \ Qt._QtGui.QHeaderView.setResizeMode - if hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtCore"): Qt.QtCore.QAbstractProxyModel = Qt._QtGui.QAbstractProxyModel Qt.QtCore.QSortFilterProxyModel = Qt._QtGui.QSortFilterProxyModel Qt.QtCore.QItemSelection = Qt._QtGui.QItemSelection Qt.QtCore.QStringListModel = Qt._QtGui.QStringListModel Qt.QtCore.QItemSelectionModel = Qt._QtGui.QItemSelectionModel - if hasattr(Qt, "QtCore"): + if hasattr(Qt, "_QtCore"): Qt.__qt_version__ = Qt._QtCore.QT_VERSION_STR Qt.__binding_version__ = Qt._QtCore.PYQT_VERSION_STR From bfcecc7e247b094c7d43aef5f594c21fc7a8b95b Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 13 Mar 2017 17:45:30 +0000 Subject: [PATCH 11/14] Fix typo on __version__ --- Qt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Qt.py b/Qt.py index f0631418..dbfe10f3 100644 --- a/Qt.py +++ b/Qt.py @@ -657,7 +657,7 @@ def _pyside2(): import PySide2 as module _setup(module, ["QtUiTools"]) - Qt.__binding_version__ = __import__("PySide2").__version__ + Qt.__binding_version__ = module.__version__ if hasattr(Qt, "_QtUiTools"): Qt.QtCompat.load_ui = lambda fname: \ @@ -690,7 +690,7 @@ def _pyside(): import PySide as module _setup(module, ["QtUiTools"]) - Qt.__binding_version__ = __import__("PySide2").__version__ + Qt.__binding_version__ = module.__version__ if hasattr(Qt, "_QtUiTools"): Qt.QtCompat.load_ui = lambda fname: \ From 8d15bf6c0151f7813dee3857f25bfedc60722ed5 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 13 Mar 2017 17:51:15 +0000 Subject: [PATCH 12/14] Fix typo for PyQt4 --- Qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Qt.py b/Qt.py index dbfe10f3..e33e16d4 100644 --- a/Qt.py +++ b/Qt.py @@ -778,7 +778,7 @@ def _pyqt4(): # API version already set to v1 raise ImportError(str(e)) - import PyQt4 as module + import PyQt4 as module _setup(module, ["uic"]) if hasattr(Qt, "_uic"): From 852b04bceba659da9181496709ee8c175fc070cc Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 13 Mar 2017 17:58:05 +0000 Subject: [PATCH 13/14] Update run_docker.sh --- run_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_docker.sh b/run_docker.sh index 91265bb4..f7fdf1d9 100644 --- a/run_docker.sh +++ b/run_docker.sh @@ -1 +1 @@ -docker run -ti --rm -v /c/Users/marcus/Dropbox/AF/development/marcus/pyblish/Qt:/Qt.py --entrypoint bash mottosso/qt.py27 +docker run -ti --rm -v $(pwd):/Qt.py --entrypoint bash mottosso/qt.py27 From e08d88bf33e909923a04445dfc79f7def7510566 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Wed, 22 Mar 2017 14:49:32 +0000 Subject: [PATCH 14/14] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 176aaab0..9322e60b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings | Date | Version | Event |:---------|:----------|:---------- | Mar 2017 | [1.0.0][] | Increased safety, **backwards incompatible** -| Sep 2016 | [0.6.0][] | Introducing `QtCompat` +| Sep 2016 | [0.6.9][] | Stable release | Sep 2016 | [0.5.0][] | Alpha release of `--convert` | Jun 2016 | [0.2.6][] | First release of Qt.py @@ -30,6 +30,7 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings ##### Table of contents +- [Project goals](#project-goals) - [Install](#install) - [Usage](#usage) - [Documentation](#documentation)