agrant3d / Qt.py

Minimal Python 2 & 3 shim around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Build Status

Qt.py

Qt.py enables you to write software that dynamically chooses the most desireable bindings based on what's available, including PySide, PySide2, PyQt4 and PyQt5.




Install

Qt.py is a single file and can either be downloaded as-is or installed via PyPI.

$ pip install Qt.py



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_()

Guides




How it works

Once you import Qt.py, Qt.py replaces itself with the most desirable binding on your platform, or throws an ImportError if none are available.

>>> import Qt
>>> print(Qt)
<module 'PyQt5' from 'C:\Python27\lib\site-packages\PyQt5\__init__.pyc'>

Here's an example of how this works.

Qt.py

import sys
import PyQt5

# Replace myself PyQt5
sys.modules["Qt"] = PyQt5

Once imported, it is as though your application was importing whichever binding was chosen and Qt.py never existed.




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

Portability

Qt.py does not hide members from the original binding. This can be problematic if, for example, you accidentally use a member that only exists PyQt5 and later try running your software with a different binding.

from Qt import QtCore

# Incompatible with PySide
signal = QtCore.pyqtSignal()

But it enables use of Qt.py as a helper library, in conjunction with an existing binding, simplifying the transition of an existing project from a particular binding.

# This is ok
from Qt import QtCore
from PyQt4 import QtGui

Caveats

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




Documentation

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

import Qt

# A string reference to binding currently in use
Qt.__binding__ == 'PyQt5'

# Reference to version of Qt, such as Qt 5.6.1
Qt.__qt_version__ == '5.6.1'

# Reference to version of binding, such as PySide 1.2.6
Qt.__binding_version__ == '1.2.6'

# Version of this project
Qt.__wrapper_version__ == '1.0.0'
Branch binding-specific code

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

if "PySide" in Qt.__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.

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

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

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

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

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

Load Qt Designer .ui 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 load_ui.

import sys
import Qt

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

Please note, for maximum compatibility, only pass the argument of the filename to the load_ui function.

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



Known Problems

None yet.




Who's using Qt.py?

Send us a pull-request with your studio here.

  • Framestore
  • Weta Digital
  • Disney Animation
  • Industriromantik

Projects using Qt.py

Send us a pull-request with your project here.




Projects similar to Qt.py

Send us a pull-request with your project here.




Developer Guide

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.

This project uses Travis for continuous integration and Travis uses Ubuntu 14.04. For an ideal development environment, we'd better stick with it. There is one more advantage to using the same environment, which I will show you.

Assuming you have Docker already setup.

# Build image (see Dockerfile for specifics)
# Re-run this command to pull latest version of image
docker build -t mottosso/qt.py https://github.com/mottosso/Qt.py.git

# Run nosetests
# Explanation of flags:
# --rm 	delete the container on exit
# -v 	mount local path to container path
cd Qt.py
docker run --rm -v $(pwd):/Qt.py mottosso/qt.py

# Tests require PySide and PyQt4 bindings to be installed ... ok
# Setting QT_PREFERRED_BINDING properly forces a particular binding ... ok
# Preferring None shouldn't import anything ... ok
# 
# ----------------------------------------------------------------------
# Ran 3 tests in 0.530s
# 
# OK

The dependencies, and OS, can and should be identical to those found in .travis.yml. That way, both you and Travis are operating on the same assumptions which means that when the tests pass on your machine, they pass on Travis. And everybody wins!

Commits

Commits should be well contained, as small as possible (but no smaller) and its messages should be in present-tense, imperative-style.

E.g.

# No
Changed this and did that

# No
Changes this and does that

# Yes
Change this and do that

The reason is that, each commit is like an action. An event. And it is perfectly possible to "cherry-pick" a commit onto any given branch. In this style, it makes more sense what exactly the commit will do to your code.

  • Cherry pick "Add this and remove that"
  • Cherry pick "Remove X and replace with Y"

Version bumping

This project uses semantic versioning and is updated after a new release has been made.

For example, if the project had 100 commits at the time of the latest release and has 103 commits now, then it's time to increment. If however you modify the project and it has not yet been released, then your changes are included in the overall next release.

The goal is to make a new release per increment.

Making a Release

Once the project has gained features, had bugs sorted out and is in a relatively stable state, it's time to make a new release.

Each release should come with:

  • An short summary of what has changed.
  • A full changelog, including links to resolved issues.

The release is then automatically uploaded to PyPI.

$ pip install Qt.py

About

Minimal Python 2 & 3 shim around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5.

License:MIT License


Languages

Language:Python 100.0%