Giter VIP home page Giter VIP logo

qt.py's Introduction

Downloads Run Tests PyPI version Anaconda-Server Badge Gitter Reviewed by Hound

Qt.py enables you to write software that runs on any of the 4 supported bindings - PySide2, PyQt5, PySide and PyQt4.


News
Date Version Event
Jan 2024 1.3.9 Run CI on Github Actions, instead of Travis CI.
Sep 2020 1.3.0 Stability improvements and greater ability for QtCompat.wrapInstance to do its job
Jun 2019 1.2.1 Bugfixes and additional members
Jan 2018 1.1.0 Adds new test suite, new members
Mar 2017 1.0.0 Increased safety, backwards incompatible
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
Guides
Table of contents



Project goals

Write once, run in any binding.

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.

Goal Description
Support co-existence Qt.py should not affect other bindings running in same interpreter session.
Build for one, run with all Code written with Qt.py should run on any binding.
Explicit is better than implicit Differences between bindings should be visible to you.

See CONTRIBUTING.md for more details.




Install

Qt.py is a single file and can either be copy/pasted into your project, downloaded as-is, cloned as-is or installed via pip or conda.

# From PyPI
$ pip install Qt.py
# From Anaconda
$ conda config --add channels conda-forge
$ conda install qt.py
  • Pro tip: Never use the latest commit for production. Instead, use the latest release. That way, when you read bug reports or make one for yourself you will be able to match a version with the problem without which you will not know which fixes apply to you nor would we be able to help you. Installing via pip or conda as above ensures you are provided the latest stable release. Unstable releases are suffixed with a .b, e.g. 1.1.0.b3.
  • Pro tip: Supports vendoring



Usage

Use Qt.py as you would use PySide2.

image

import sys
from Qt import QtWidgets

app = QtWidgets.QApplication(sys.argv)
button = QtWidgets.QPushButton("Hello World")
button.show()
app.exec_()



Documentation

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

Attribute Returns Description
__version__ str Version of this project
__binding__ str A string reference to binding currently in use
__qt_version__ str Reference to version of Qt, such as Qt 5.6.1
__binding_version__ str Reference to version of binding, such as PySide 1.2.6

Example

>>> from Qt import __binding__
>>> __binding__
'PyQt5'

Compatibility

Qt.py also provides compatibility wrappers for critical functionality that differs across bindings, these can be found in the added QtCompat submodule.

Attribute Returns Description
loadUi(uifile=str, baseinstance=QWidget) QObject Minimal wrapper of PyQt4.loadUi and PySide equivalent
translate(...) function Compatibility wrapper around QCoreApplication.translate
wrapInstance(addr=long, type=QObject) QObject Wrapper around shiboken2.wrapInstance and PyQt equivalent
getCppPointer(object=QObject) long Wrapper around shiboken2.getCppPointer and PyQt equivalent
isValid(object=QObject) bool Wrapper around shiboken2.isValid and PyQt equivalent
dataChanged(topLeft=QModelIndex, bottomRight=QModelIndex, roles=[]) None Wrapper around QtCore.QAbstractItemModel.dataChanged.emit

Example

>>> from Qt import QtCompat
>>> QtCompat.loadUi

Class specific compatibility objects

Between Qt4 and Qt5 there have been many classes and class members that are obsolete. Under Qt.QtCompat there are many classes with names matching the classes they provide compatibility functions. These will match the PySide2 naming convention.

from Qt import QtCore, QtWidgets, QtCompat
header = QtWidgets.QHeaderView(QtCore.Qt.Horizontal)
QtCompat.QHeaderView.setSectionsMovable(header, False)
movable = QtCompat.QHeaderView.sectionsMovable(header)

This also covers inconsistencies between bindings. For example PyQt4's QFileDialog matches Qt4's return value of the selected. While all other bindings return the selected filename and the file filter the user used to select the file. Qt.QtCompat.QFileDialog ensures that getOpenFileName(s) and getSaveFileName always return the tuple.


Environment Variables

These are the publicly facing environment variables that in one way or another affect the way Qt.py is run.

Variable Type Description
QT_PREFERRED_BINDING_JSON str Override order and content of binding to try. This can apply per Qt.py namespace.
QT_PREFERRED_BINDING str Override order and content of binding to try. Used if QT_PREFERRED_BINDING_JSON does not apply.
QT_VERBOSE bool Be a little more chatty about what's going on with Qt.py
QT_SIP_API_HINT int Sets the preferred SIP api version that will be attempted to set.

Subset (or "common members")

Members of Qt.py is a subset of PySide2. Which means for a member to be made accessible via Qt.py, it will need to (1) be accessible via PySide2 and (2) each of the other supported bindings. This excludes large portions of the Qt framework, including the newly added QtQml and QtQuick modules but guarantees that anything you develop with Qt.py will work identically on any binding - PySide, PySide2, PyQt4 and PyQt5. If you need to use such excluded modules with Qt.py, please see QtSiteConfig.py.

We call this subset "common members" and these can be generated by running the build_membership.sh script. The script will output all modules and members of each binding into individual JSON files. These JSON files are then compared and a common_members.json file is generated. The contents of this file is copy-pasted into the _common_members dictionary of Qt.py. Please note that the script will only use the very latest version of our Docker test suite to generate the common members subset, using the most up-to-date set of VFX Platform-stipulated software versions.

⚠️ The version of PySide2 used as reference is the one specified on VFX Platform, currently version is 2.0.x. But unfortunately, the version string of PySide2 is not yet properly maintained and the VFX Platform does not specifiy a explicit commit SHA for PySide2. Therefore, it could be difficult to know exactly which PySide2 is running on your system (unless you built it from source). In layman's terms; as PySide2 is in development and is continuously adding new support for modules, you may see differences between PySide2 built early in the year vs PySide2 built later in the year. The exact commit SHAs of PySide2 used by the Qt.py test suite can be reviewed in DOCKER.md. QtC implemented an alternative way to identify which version of PySide2 you are running. You can read more about that here.


Branch binding-specific code

Some bindings offer features not available in others, you can use __binding__ to capture those.

if "PySide" in __binding__:
  do_pyside_stuff()

Override preferred choice

If your system has multiple choices where one or more is preferred, you can override the preference and order in which they are tried with this environment variable.

$ set QT_PREFERRED_BINDING=PyQt5  # Windows
$ export QT_PREFERRED_BINDING=PyQt5  # Unix/OSX
$ python -c "import Qt;print(Qt.__binding__)"
PyQt5

Constrain available choices and order of discovery by supplying multiple values.

# Try PyQt4 first and then PySide, but nothing else.
$ export QT_PREFERRED_BINDING=PyQt4:PySide

Using the OS path separator (os.pathsep) which is : on Unix systems and ; on Windows.

If you need to control the preferred choice of a specific vendored Qt.py you can use the QT_PREFERRED_BINDING_JSON environment variable instead.

{
    "Qt":["PyQt5"],
    "myproject.vendor.Qt":["PyQt5"],
    "default":["PySide2"]
}

This json data forces any code that uses import Qt or import myproject.vendor.Qt to use PyQt5(from x import Qt etc works too, this is based on __name__ of the Qt.py being imported). Any other imports of a Qt module will use the "default" PySide2 only. If "default" is not provided or a Qt.py being used does not support QT_PREFERRED_BINDING_JSON, QT_PREFERRED_BINDING will be respected.

# Try PyQt5 first and then PyQt4 for the Qt module name space.
$ export QT_PREFERRED_BINDING_JSON="{"Qt":["PyQt5","PyQt4"]}"
# Use PyQt4 for any other Qt module name spaces.
$ export QT_PREFERRED_BINDING=PySide2

QtSiteConfig.py

Add or remove members from Qt.py at run-time.


If you need to expose a module that isn't included in Qt.py by default or wish to remove something from being exposed in Qt.py you can do so by creating a QtSiteConfig.py module and making it available to Python.

  1. Create a new file QtSiteConfig.py
  2. Implement update_members
  3. Expose to Python
# QtSiteConfig.py
def update_members(members):
    """Called by Qt.py at run-time to modify the modules it makes available.

    Arguments:
        members (dict): The members considered by Qt.py
    """
    members.pop("QtCore")

Finally, expose the module to Python.

$ set PYTHONPATH=/path/to
$ python -c "import Qt.QtCore"
ImportError: No module named Qt.QtCore

Linux and MacOS users, replace set with export


Compile Qt Designer files

WARNING - ALPHA FUNCTIONALITY
See #132 for details.

.ui files compiled via pyside2-uic inherently contain traces of PySide2 - e.g. the line from PySide2 import QtGui.

In order to use these with Qt.py, or any other binding, one must first erase such traces and replace them with cross-compatible code.

$ pyside2-uic my_ui.ui -o my_ui.py
$ python -m Qt --convert my_ui.py
# Creating "my_ui_backup.py"..
# Successfully converted "my_ui.py"

Now you may use the file as you normally would, with Qt.py


Loading Qt Designer files

The uic.loadUi function of PyQt4 and PyQt5 as well as the QtUiTools.QUiLoader().load function of PySide/PySide2 are mapped to a convenience function loadUi.

import sys
from Qt import QtCompat

app = QtWidgets.QApplication(sys.argv)
ui = QtCompat.loadUi(uifile="my.ui")
ui.show()
app.exec_()

For PyQt bindings it uses their native implementation, whereas for PySide bindings it uses our custom implementation borrowed from the qtpy project.

loadUi has two arguments as opposed to the multiple that PyQt ships with. See here for details - in a nutshell, those arguments differ between PyQt and PySide in incompatible ways. The second argument is baseinstance which allows a ui to be dynamically loaded onto an existing QWidget instance.

QtCompat.loadUi(uifile="my.ui", baseinstance=QtWidgets.QWidget)

uifile is the string path to the ui file to load.

If baseinstance is None, the a new instance of the top-level widget will be created. Otherwise, the user interface is created within the given baseinstance. In this case baseinstance must be an instance of the top-level widget class in the UI file to load, or a subclass thereof. In other words, if you've created a QMainWindow interface in the designer, baseinstance must be a QMainWindow or a subclass thereof, too. You cannot load a QMainWindow UI file with a plain QWidget as baseinstance.

loadUi returns baseinstance, if baseinstance is provided. Otherwise it will return the newly created instance of the user interface.


sip API v2

If you're using PyQt4, sip attempts to set its API to version 2 for the following:

  • QString
  • QVariant
  • QDate
  • QDateTime
  • QTextStream
  • QTime
  • QUrl



Rules

The PyQt and PySide bindings are similar, but not identical. Where there is ambiguity, there must to be a clear direction on which path to take.

Governing API

The official Qt 5 documentation is always right. Where the documentation lacks answers, PySide2 is right.

For example.

# PyQt5 adheres to PySide2 signals and slots
PyQt5.Signal = PyQt5.pyqtSignal
PyQt5.Slot = PyQt5.pyqtSlot

# PySide2 adheres to the official documentation
PySide2.QtCore.QStringListModel = PySide2.QtGui.QStringListModel

Caveats

There are cases where Qt.py is not handling incompatibility issues. Please see CAVEATS.md for more information.




Known Problems

Send us a pull-request with known problems here!




Who's using Qt.py?

Send us a pull-request with your studio here.

Presented at Siggraph 2016, BOF!

image




Projects using Qt.py

Send us a pull-request with your project here.




Projects similar to Qt.py

Comparison matrix.

Project Audience Reference binding License PEP8 Standalone PyPI Co-existence
Qt.py Film PySide2 MIT X X X X
jupyter Scientific N/A N/A X
QtPy Scientific N/A MIT X X
pyqode.qt Scientific PyQt5 MIT X X
QtExt Film N/A N/A X
python_qt_binding Robotics N/A BSD X X X X

Also worth mentioning, pyqt4topyqt5; a good starting point for transitioning to Qt.py.

Send us a pull-request with your project here.




Developer Guide

Tests are performed on each aspect of the shim.

Each of these are run under..

  • Python 2.7
  • Python 3.4
  • Python 3.5
  • Python 3.6

..once for each binding or under a specific binding only.

Each test is run within it's own isolated process, so as to allow an import to occur independently from other tests. Process isolation is handled via nosepipe.

Tests that are written at module level are run four times - once per binding - whereas tests written under a specific if-statement are run only for this particular binding.

if binding("PyQt4"):
    def test_something_related_to_pyqt4():
        pass

Code convention

Below are some of the conventions that used throughout the Qt.py module and tests.

  • Etiquette: PEP8
    • All code is written in PEP8. It is recommended you use a linter as you work, flake8 and pylinter are both good options. Anaconda if using Sublime is another good option.
  • Etiquette: Double quotes
    • " = yes, ' = no.
  • Etiquette: Napoleon docstrings
    • Any docstrings are made in Google Napoleon format. See Napoleon for details.
  • Etiquette: Semantic Versioning
  • Etiquette: Underscore means private
    • Anything prefixed with an underscore means that it is internal to Qt.py and not for public consumption.

Running tests

Due to the nature of multiple bindings and multiple interpreter support, setting up a development environment in which to properly test your contraptions can be challenging. So here is a guide for how to do just that using Docker.

With Docker setup, here's what you do. Please note this will pull down a ~1 GB image.

cd Qt.py

# Run nosetests (Linux/OSX)
docker run --rm -v $(pwd):/Qt.py -e PYTHON=2.7 fredrikaverpil/qt.py:2018
docker run --rm -v $(pwd):/Qt.py -e PYTHON=3.4 fredrikaverpil/qt.py:2018
docker run --rm -v $(pwd):/Qt.py -e PYTHON=3.5 fredrikaverpil/qt.py:2018
docker run --rm -v $(pwd):/Qt.py -e PYTHON=3.6 fredrikaverpil/qt.py:2018

# Run nosetests (Windows)
docker run --rm -v %CD%:/Qt.py -e PYTHON=2.7 fredrikaverpil/qt.py:2018
docker run --rm -v %CD%:/Qt.py -e PYTHON=3.4 fredrikaverpil/qt.py:2018
docker run --rm -v %CD%:/Qt.py -e PYTHON=3.5 fredrikaverpil/qt.py:2018
docker run --rm -v %CD%:/Qt.py -e PYTHON=3.6 fredrikaverpil/qt.py:2018

# Doctest: test_caveats.test_1_qtgui_qabstractitemmodel_createindex ... ok
# Doctest: test_caveats.test_2_qtgui_qabstractitemmodel_createindex ... ok
# Doctest: test_caveats.test_3_qtcore_qitemselection ... ok
# ...
#
# ----------------------------------------------------------------------
# Ran 21 tests in 7.799s
#
# OK

Now both you and Github Actions are operating on the same assumptions which means that when the tests pass on your machine, they pass on Github Actions. And everybody wins!

For details on the Docker image for testing, see DOCKER.md.

See CONTRIBUTING.md for more of the good stuff.

Upload to PyPI

To make a new release onto PyPI, you'll need to be mottosso and type this.

cd Qt.py
python .\setup.py sdist bdist_wheel
python -m twine upload .\dist\*

qt.py's People

Contributors

ahuge avatar andryjong avatar aoblet avatar chadrik avatar darkvertex avatar dgovil avatar fredrikaverpil avatar friedererdmann avatar fxtd-odyssey avatar greatestape avatar instinct-vfx avatar jandorr avatar jasonbrackman avatar jdorr avatar justinfx avatar kgermain avatar martin-chatterjee avatar maxnbk avatar mhendricks avatar mitchellsimmons avatar mottosso avatar oep avatar salbertson avatar simonouel avatar sol-ansano-kim avatar sporty avatar tbttfox avatar timurhai avatar tobkum avatar tzaumiaan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qt.py's Issues

PyQt4 import should try and set sip api to v2

It would be good, for compatibility, for the PyQt4 import logic to also attempt to set the sip api version to 2 for at least QString and QVariant (setapi()). This will ensure that PyQt4 bindings behave the same way as PySide, when the helper library is hiding away the import details.

Caveat: It is possible for the setting of sip api v2 to fail, if another library was imported first, which uses PyQt4 and already locked sip into v1.

Conversion guide

Converting a project initially developed wih PyQt4, PyQt5 or PySide should be relatively straightforward and similar across projects. Let's collect our thoughts on some of the things to consider when heading down this route.


#### From PySide2

Qt.py conforms to the interface of PySide2, so if your code is already written for that you should only ever need to:

  1. Search-and-replace PySide2 with Qt and you're done.

From PySide

This should be relatively straightforward as well.

  1. Replace references to QtGui with QtWidgets apart from a few exceptions.

From PyQt5

Here I'd imagine:

  1. Look into the different names for signals and properties, e.g. pyqtSignal

From PyQt4

Prepend this to the steps outlined for PyQt5

  1. Convert code via pyqt4topyqt5

Finally, look towards CAVEATS.md for details and workarounds.


### What else?

What's missing? Let's fill in the blanks.

List of PyQt4 vs PySide incompatibilities

I have a very large PySide application that has been developed for over 3 years. I've just started to convert it from PySide to PyQt4 and am using Qt.py to see how well it handles it. Surprisingly, I am hitting quite a decent size list of incompatibilities, so I thought I would list them here.

This list it not yet complete, asI have not finished fully testing every area of the app. I am a bit scared though, as Python is dynamic, that I could easily hit more of these during runtime if I don't manage to exercise every branch of the GUI code


QtCore.Slot - PySide allows for a result=None keyword param to set the return type. PyQt4 crashes

    QtCore.Slot(QtGui.QWidget, result=None)
TypeError: string or ASCII unicode expected not 'NoneType'

QAction.triggered signal requires a bool arg in PyQt4, while PySide cannot accept any arguments

# PySide
>>> a.triggered.emit()
>>> a.triggered.emit(True)
TypeError: triggered() only accept 0 arguments, 2 given!

# PyQt4
>>> a.triggered.emit()
TypeError: triggered(bool) has 1 argument(s) but 0 provided

>>> a.triggered.emit(True)  # is checked

QAbstractModel.createIndex(row, col, quint32)

In PySide, somehow the last argument (the id) is allowed to be negative and is maintained. While in PyQt4 it gets coerced into an undefined unsigned value.

# PySide
>>> idx = model.createIndex(0, 0, -1)
>>> print idx.internalId()
# -1

# PyQt4
>>> idx = model.createIndex(0, 0, -1)
>>> print idx.internalId()
#18446744073709551615
  • Note - I had been using the id as an index into a list. But the unexpected return value from PyQt4 broke it by being invalid. The workaround was to always check that the returned id was between 0 and the max size I expect.

# PySide
>>> QtGui.QItemSelection.empty()

# PyQt4
>>> QtGui.QItemSelection.isEmpty()

However, they both do support the len(selection) operation


Closures

# valid in PySide
def someMethod(self):
    def _wrapper():
        self.runSomething("foo")

    someObject._callback = _wrapper
    someObject.someSignal.connect(_wrapper)

# In PyQt4 an exception is generated when the signal fires
    def _wrapper():
        self.runSomething("foo")
NameError: free variable 'self' referenced before assignment in enclosing scope

In PySide, the constructor for QtGui.QRegExpValidator() can just take a QRegExp instance, and that is all.

In PyQt4 you are required to pass some form of a parent argument, otherwise you get a TypeError:

QtGui.QRegExpValidator(regex, None)

Add links to binding repos

I was just informed that the PySide2 development by the Qt Company is not being done over at the Github PySide2 repository. Instead it is being carried out here: http://wiki.qt.io/PySide2

I think it would be useful to include such info somewhere, probably in the README.

Basically, for each binding make sure it's easy to find the github repo ... or:

  • source / binary distributions
  • bug tracker
  • mailing list (or equivalent)

Override preferred binding

Goal

The available Qt bindings - PySide, PySide2, PyQt4 and PyQt5 - are automatically detected. In case two or more bindings are available and one if preferred, currently there is no way of specifying which to pick.

Enable some mechanism with which to specify up-front which bindings to use.

Motivation

Primarily intended for use in testing, where tests must be able to specify bindings with which to tests against, but could potentially be useful in an enterprise environment where multiple bindings must be present on the PYTHONPATH.

Implementation

Query the value of QT_PREFERRED_BINDING and throw an error is this binding doesn't exist.

compiling resources

Hi , is there any wrapped command for things like : pyside-rcc and pyside-uic , so does pick up the right one depending on the framework in use ?

sys.modules["Qt"] breaks sys

For some reason, when you set e.g. sys.modules['Qt'] = PyQt5, the sys module cannot be used after this:

load_pyqt5()
print sys
>>> None

I was expecting:

load_pyqt5()
print sys
>>> <module 'sys' (built-in)>

The growing try/except block; flat is better

Agreed.

This is what we have now:

"""Support Qt 4 and 5, PyQt and PySide"""
try:
    load_pyside2()
except ImportError:
    try:
        load_pyqt5()
    except ImportError:
        try:
            load_pyside()
        except ImportError:
            try:
                load_pyqt4()
            except:
                sys.stderr.write("Qt: Could not find "
                                 "appropriate bindings for Qt\n")

pip search doesn't show Qt.py

For some reason, this doesn't return anything:

pip search Qt.py

Sorry, I wish I could be of more help. I'm unfamiliar with how to set up pip (although I'd like to learn).

Repo structure

Right now, the repo acts as a Python module:

~/code/github
❯ git clone https://github.com/mottosso/Qt.git
Cloning into 'Qt'...
remote: Counting objects: 39, done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 39 (delta 14), reused 28 (delta 3), pack-reused 0
Unpacking objects: 100% (39/39), done.
Checking connectivity... done.

~/code/github
❯ python
Python 2.7.11 (default, Jan 22 2016, 08:29:18)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from Qt import QtGui
Loaded PyQt5
>>>

Perhaps there should be a "Qt" folder in the github repo, which will be the Python module. Not sure how this is usually done and how it'll work with PyPi.

QWebKit remapping

It seems in Qt5 webkit has been reshuffled as well as QtGui.
This means that QWebPage is no longer available under QtWebKit , but is now under QtWebKitWidgets.

PySide

From Qt.QtWebkit import QWebPage

PySide2

From Qt.QtWebKitWidgets import QWebPage

I'll createa branch with this fix and open a pull request.

L.

load_ui example in README could be better

The guide here has a spelling error and more importantly provides an example that could be improved.

import sys
from Qt import QtWidgets
from Qt import load_ui


class Hello(QtWidgets.QWidget):
    def __init__(self):
        super(Hello, self).__init__()
        self.ui = load_ui('my_ui.ui')

app = QtWidgets.QApplication(sys.argv)
window = Hello()
window.ui.show()

sys.exit(app.exec_())

This could have been:

import sys
import Qt

app = QtWidgets.QApplication(sys.argv)
ui = Qt.load_ui("my.ui")
ui.show()
app.exec_()

Loading a standalone widget to the inside of another standalone widget doesn't make much sense to me.

Qt.py

What do you think of making the name more Python-specific?

I figure this would be the GitHub and PyPI name.

$ git clone https://github.com/mottosso/Qt.py
$ pip install Qt.py

But that we'd still import it as we do now.

import Qt

Performance hit on import Qt

Importing the .Qt submodule of PyQt means "import all" which might give a performance hit, when one might only end up using QtWidgets (?)

Not sure what you meant here @mottosso

"Import qt" or "import Qt"?

PEP08 mandates lower-case module names. But Qt bindings are traditionally mixedcase.

from qt import QtWidgets

# Or

from Qt import QtWidgets

Additional PySide2 caveats

Found some more cross-development tips here:

Pasting them here in case they disappear. Will need formatting.

QHBoxLayout QVBoxLayout

Background:
Qt4 (PySide): QLayout::setAlignment(Qt::Alignment alignment)
http://doc.qt.io/qt-4.8/qlayout.html#setAlignment-2
Qt5 (PySide2): removed the above method
http://doc.qt.io/qt-5/qlayout.html#setAlignment
To upgrade to PySIde2, we have to make the following changes for
QHBoxLayout::setAlignment(Qt::Alignment alignment)
QVBoxLayout::setAlignment(Qt::Alignment alignment)

Qt4 (PySide):

layout = QHBoxLayout()
layout.setAlignment(Qt.AlignLeft)

Qt5(PySide2) : --solution

layout = QHBoxLayout()
QLayoutItem.setAlignment(layout, Qt.AlignLeft)

QHeaderView

Qt4 (PySide): http://doc.qt.io/qt-4.8/qheaderview.html

void    setResizeMode(ResizeMode mode);
void    setResizeMode(int logicalIndex, ResizeMode mode);

Qt5(PySide2) : http://doc.qt.io/qt-5/qheaderview.html

void    setSectionResizeMode(ResizeMode mode);
void    setSectionResizeMode(int logicalIndex, ResizeMode mode);

Solution:

pysideVersion = '-1'
try:
    import PySide
    pysideVersion = PySide.__version__
except ImportError:
    import PySide2
    pysideVersion = PySide2.__version__
if pysideVersion == '1.2.0':
    treeUI.header().setResizeMode(QHeaderView.Fixed)
    treeUI.header().setResizeMode(TREE_COLUMN_NAME, QHeaderView.Interactive)
    treeUI.header().setResizeMode(TREE_COLUMN_WEIGHT, QHeaderView.Stretch)
else:
    treeUI.header().setSectionResizeMode(QHeaderView.Fixed)
    treeUI.header().setSectionResizeMode(TREE_COLUMN_NAME, QHeaderView.Interactive)
    treeUI.header().setSectionResizeMode(TREE_COLUMN_WEIGHT, QHeaderView.Stretch)

QApplication.palette()

Background:
Qt4:
QPalette QApplication::palette()
http://doc.qt.io/qt-4.8/qapplication.html#palette

Qt5:
QPalette QGuiApplication::palette()
http://doc.qt.io/qt-5/qguiapplication.html#palette

Solution:
PySide(Qt4): palette = QApplication.palette()
PySide2 (Qt5): palette = QGuiApplication.palette()

Another workaround:

palette = self.palette()  # self is widget
# as you could also find palette() within QWidget class http://doc.qt.io/qt-5/qwidget.html   
# it works for both Qt4 and Qt5.

Remove init()?

init() is being declared and immediately called, might as well not be a function?

Qt py in maya 2017

Hi, I've been reading this article :
https://fredrikaverpil.github.io/2016/07/25/dealing-with-maya-2017-and-pyside2/

but when I try to import Qt into maya 2017 I get straight a :

File "....../site-packages/Qt.py", line 234, in _init
    raise ImportError("No Qt binding were found.")

do I have to set manually to PySide2 ?
should be able to automatically try them all and import ?
Am I missing something ? :)

Cheers.
L.

ps.
Note, PySide2 is available:

<module 'PySide2' from '/usr/autodesk/maya2017/lib/python2.7/site-packages/PySide2/__init__.py'>

Test availability paridy with PySide2

Goal

Check that all bindings provide identical members as PySide2; the reference binding.

Motivation

It has happened that one binding was missing access to certain classes that should have been available due to a typo or forgetfulness. A "one test to rule them all" for availability should iron these out.

Implementation

I'd imagine running a simple dir(PySide2.QtWidgets) and checking that against what's available elsewhere. It would be nice to also include argument signatures for these. Possibly that we can achieve this via the Python inspect module.

The number one goal is for each binding to provide the same members and argument signatures (differences break cross-binding compatibility). If we can also reach a state where they all provide everything that PySide2 has in an easy manner, then that would be great too, but it isn't as important and is unlikely to ever happen. Simply because some of the features provided are ad-hoc (due to trying to convert C++ methodologies to Python).

Wrapper for compiling ui files

Do we also wish to provide a facility to support differences between PySide/PyQt?

from Qt import QtCore
from Qt import QtWidgets
from Qt import __binding__
if __binding__.startswith('PySide'):
    from Qt import QtUiTools
    import pysideuic
elif __binding__.startswith('PyQt'):
    from Qt import uic
    import sip

I'm using the above frequently now, but it could have looked like this with the help of the Qt module:

from Qt import QtCore
from Qt import QtWidgets
from Qt import uic
from Qt import generator

In this case, perhaps uic and generator should be prefixed to stress they are custom wrappers.

context manager for PySide2 and PyQt5 in tests

Hi , is there any reason why there isn't a context manager for these two binding ?
Is anyone already looking on adding them ?
Asking as it would allow a more thoughtful testing.
Cheers.
L.

PyQt5 __version__ error

I just noticed this when importing PyQt5.

  File "/usr/local/lib/python2.7/site-packages/Qt.py", line 127, in <module>
    _init()
  File "/usr/local/lib/python2.7/site-packages/Qt.py", line 110, in _init
    sys.modules["Qt"] = available[preferred]()
  File "/usr/local/lib/python2.7/site-packages/Qt.py", line 35, in _pyqt5
    PyQt5.__binding_version__ = PyQt5.__version__
AttributeError: 'module' object has no attribute '__version__'

Why the <br> in README?

I noticed the README has a bunch of <br> tags which makes it really long (vertically). Just wondering if you intended to do that or if it was in error. The page becomes really long on a laptop but perhaps you felt readability was improved...?

Caveats tests

Goal

Ensure caveats are actually caveats, from first writing through future changes.

Implementation

Automatically test documented caveats. We could have a look at running the examples we type into CAVEATS.md, and seeing whether they produce the expected output or throws the expected errors.

We'd have to be more careful in how we format it, but in general should be able to conform to the doc-test format.

Before

# PySide
>>> QtGui.QItemSelection.empty()

# PyQt4
>>> QtGui.QItemSelection.isEmpty()

After

# PySide
>>> from Qt import QtCore
>>> assert not hasattr(QtCore.QItemSelection, "isEmpty")
>>> assert hasattr(QtCore.QItemSelection, "empty")
# PyQt4
>>> from Qt import QtCore
>>> assert hasattr(QtCore.QItemSelection, "isEmpty")
>>> assert not hasattr(QtCore.QItemSelection, "empty")

Where each block is run, like other tests, in an independent process, using the binding specified on the first line.

tests: clean function

In tests.py, I find the clean function a bit confusing...

Some points:

  • Python does not support unloading modules
  • Nose has this --with-isolation option, which does its best to isolate each test function.
  • Currently, sys.modules.pop() is being used to "clean out" modules, but it doesn't actually clean out modules imported by the module you're popping.

If we just disregard that Python can't "unimport" modules and see how far we can push it... and skip --with-isolation since we might not want to isolate all functions –– shouldn't this be a more reliable cleaning function?

import os
import sys
import imp
import contextlib

from nose.tools import (
    with_setup,
    assert_raises,
)

_modules = sys.modules.copy()

def clean():
    """Provide clean working environment"""
    os.environ.pop("QT_PREFERRED_BINDING", None)
    modules_to_delete = list(set(sys.modules) - set(_modules))
    for module in modules_to_delete:
        sys.modules.pop(module)  # or "del sys.modules[module]"

Right now, even if you pop Qt, the modules imported by Qt aren't popped.

Also, with nose, we can create a tests folder (rather than the tests.py file), and inside it put several python scripts prefixed with test_ and nose will run all of them.

@mottosso what do you think about this?

Enforce portability?

Should Qt.py enforce/encourage portability? That is, should Qt.py try and prevent a user from using functionality found only in a specific binding, so that any code written with it is guaranteed to run on any other binding?

It's more or less a road in the fork between being a helper library or a complete framework.

Scenario A - Yes portability

>>> from Qt import QtCore
>>> signal = QtCore.pyqtSignal()
AttributeError: 'module' object has no attribute 'pyqtSignal'

Scenario B - No portability

>>> from Qt import QtCore
>>> signal = QtCore.pyqtSignal()
>>> signal2 = QtCore.Signal()
>>> from PyQt4 import QtCore
>>> signal3 = QtCore.pyqtSignal()

Implementation

We could achieve this by simply removing the redirected members.

PyQt4.QtCore.Signal = PyQt4.QtCore.pyqtSignal
del(PyQt4.QtCore.pyqtSignal)

However the problem is that this affects the PyQt4 module globally; meaning that if the user is running another application using PyQt4, it would break.

We could work around this by, rather than redirecting Qt.py to its nearest binding..

>>> import Qt
>>> print(Qt)
<module 'PyQt4' from '/usr/lib/python2.7/dist-packages/PyQt4/__init__.pyc'>

..we re-define each member..

>>> from Qt import QtCore
>>> print(QtCore)
<module 'Qt.QtCore' from '/usr/lib/python2.7/dist-packages/Qt.pyc'>

But the problem with that is (1) we would have to redefine every possible member of Qt which might be difficult and (2) because of (1) we would lose the current simplicity of Qt.py.

Discussion

If we choose to enforce portability, we'll have to consider the consequences and find an elegant way in which to implement it. If we chose not to, we should consistently ensure that Qt.py can be used alongside any particular binding.

An alternative approach might be to instead have a separate tool check for used features only found in any one binding?

# psuedo
# validate_portability.py
import PySide
import PyQt4

unique = set(all_pyside).intersect(set(all_pyqt4))

with open("mycode.py") as f:
  used_members = parse(f)
  for member in used_members:
    if member in unique:
      warn("%s is not portable!" % member)

Support alternate install locations / import paths of Qt module

Qt.py wants to replace itself with the resolved Qt bindings, by updating sys.modules. But it uses a hard-coded "Qt" string instead of __name__. This causes it not to work in my case where I am currently vendoring the Qt.py dependency into my project.

My import path looks something like this:

from project.packages import Qt

I had to update the locations that did this:

sys.modules["Qt"] = available[preferred]()

With

sys.modules["Qt"] = available[preferred]()
sys.modules[__name__] = sys.modules["Qt"]

Travis-CI: register new versions with PyPI

pip install -U Qt.py returns version 0.2.2 for me (although 0.2.3 is available).

I'm very unfamiliar with how pip works and I've never distributed anything using it.

Do you have to perform any manual labour in order to get the newer 0.2.3 out onto PyPi?
I was under the impression that the setup.py was supposed to handle that?

I had to curl down the newest Qt.py from the github repo today since I wasn't able to fetch it via pip.

code structure reorganisation proposal

After having spent some time trying to get around the various differences in the framework, has become pretty clear to me, that all these in place replacements (still many to come I fear), might bring soon the code to a point where is going to be hard to maintain, especially if many other contributors are coming in.

Here an idea on how the code could be reorganised :

├── docs
│   └── source
├── setup.py
├── source
│   └── Qt
│       ├── __init__.py
│       └── bindings
│           ├── __init__.py
│           ├── _pyqt4_.py
│           ├── _pyqt5_.py
│           ├── _pyside2_.py
│           └── _pyside_.py
└── tests
    ├── test_pyside2.py
    ├── test_pyside.py
    ├── test_qt4.py
    └── test_qt5.py

Any thought on this ?
Cheers.

_init() docstring

I believe we should create a docstring for _init() and explain what happens at sys.modules["Qt"] = binding().

For example:

def _init():
    """Try loading each binding in turn

    Please note: the entire Qt module is replaced with this code:
        sys.modules["Qt"] = binding()
    This means no functions or variables can be called after this has
    executed.
    """

What do you think @mottosso?

Remove commented out code?

I didn't want to remove code which I thought was redundant before checking with you (@mottosso) first. What do you think?

When you import Qt you don't want stuff printed, I think.

Exclude bindings from lookup

Goal

Enable the end-user to exclude specific bindings from being made available, even though they are on PYTHONPATH.

Motivation

In some circumstances, only some bindings can reasonable be expected to exist. For example, in Autodesk Maya, only bindings for Qt 4 are possible and attempting to use Qt 5 may cause a crash.

Implementation

Alongside QT_PREFERRED_BINDING we'll make a QT_EXCLUDE_BINDING.

$ export QT_EXCLUDE_BINDING=PySide2:PyQt5

Question is whether to make it plural, as it's mean for multiple values, or singular to align with the other environment variable. Which is easier to remember, and which is easier understood?

How to test docstrings of Qt.py?

As mentioned here.

Example:

>>> from Qt import load_ui
>>> class MyWindow(QtWidgets.QWidget):
...    def __init__(self, parent=None):
...        fname = 'my_ui.ui'
...        load_ui(fname, self)
>>> window = MyWindow()

Missing attributes

These attributes currently do not exist on the Qt object, which are mentioned in the README:

# Reference to version of Qt, such as Qt 5.6.1
print Qt.__qtVersion__
# Reference to version of binding, such as PySide 1.2.6
print Qt.__bindingVersion__
# Version of this project
print Qt.__version__

QSortFilterProxyModel can't be imported

Hi Marcus, trying now to port some complex stuff on Qt.py, but started hitting some issues:

from Qt.QtGui import QSortFilterProxyModel
ImportError: cannot import name QSortFilterProxyModel

any idea ?
Cheers.

p.s

from PySide.QtGui import QSortFilterProxyModel
print QSortFilterProxyModel
>> <type 'PySide.QtGui.QSortFilterProxyModel'>
print Qt
<module 'PyQt5' from '/usr/lib64/python2.7/site-packages/PyQt5/__init__.pyc'>

I'll try forcing on PySide

Qt 5 and continuous integration

Goal

Enable testing of Qt.py with Pyside2 and PyQt5.

Motivation

At the moment, testing only happens with PySide and PyQt4, but the codebase and tests remain open for expansion towards bindings for Qt 5. The reason they haven't yet seen the day of light has been due to difficulties in installing each binding in an efficient manner.

PyQt4 and PySide both have pre-compiled counterparts that take a matter of seconds to install, whereas Qt 5 bindings are more complex, especially PySide2, requiring manual compilation. (On Python < 3.5).

Docker has always been an option, but the problem has been losing out on being able to concurrently run tests, one build per version of Python. But this technique should enable greater concurrency, and greater distribution of tasks with one build per version of Python and binding.

Implementation

The idea is simple. Utilise the build matrix functionality to provide many identical environments with varying environment variables. The environment variables can then be used to programatically select an appropriate Docker image.

Remaps: QItemSelection, QItemSelectionModel

I'm finishing up some stuff and just quickly jotting this down so I can adress this later on...

QItemSelection

QItemSelectionModel

So it seems we should be able to do:

def _pyqt4():
    # Remap
    PyQt4.QtCore.QItemSelection = PyQt4.QtGui.QItemSelection
    PyQt4.QtCore.QItemSelectionModel = PyQt4.QtGui.QItemSelectionModel

def _pyside():
    # Remap
    PySide.QtCore.QItemSelection = PySide.QtGui.QItemSelection
    PySide.QtCore.QItemSelectionModel = PySide.QtGui.QItemSelectionModel

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.