• Stars
    star
    915
  • Rank 49,917 (Top 1.0 %)
  • Language
    Python
  • License
    MIT License
  • Created over 8 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

Downloads Build Status 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
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 Travis are operating on the same assumptions which means that when the tests pass on your machine, they pass on Travis. 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\*

More Repositories

1

cmdx

Fast and persistent subset of maya.cmds
Python
191
star
2

allzpark

Package-based application launcher for VFX and games production
Python
184
star
3

Maya-Environment-Variables

All available (but undocumented) environment variables in Autodesk Maya
160
star
4

cmdc

Maya Python API 3.0
C
120
star
5

docker-maya

Dockerfile for Maya
Dockerfile
119
star
6

maya-sniffer

Treemap visualisation of Maya scene files
Python
90
star
7

apiundo

Undo/Redo support for Maya Python API 2.0
Python
67
star
8

qargparse.py

Build settings-style graphical user interfaces for Python with argparse syntax
Python
48
star
9

be

minimal directory and environment management system for collaborative creative projects
Python
47
star
10

rez-for-projects

An example and exploration of how and whether to use Rez for project configuration management
Python
46
star
11

docker-deadline

Docker images for Thinkbox Software Deadline
Mathematica
40
star
12

Qt5.py

Minimal Python 2 & 3 shim around PySide2 and PyQt5
Python
33
star
13

rez-pipz

Pip for Rez - Install any Python package from PyPI as a Rez package
Python
29
star
14

rez-scoopz

Scoop for Rez - Install Windows software from a selection of 500+ packages and counting
Python
25
star
15

ncloth-reference

Joel Stutz's excellent nCloth reference, revived! https://mottosso.github.io/ncloth-reference
HTML
24
star
16

maya-test

Run your Python script, in every version of Maya
Python
20
star
17

learn-cinder

Notes and examples for getting started with libcinder - https://mottosso.gitbooks.io/cinder/content/
C++
19
star
18

aTools

Alan Camilo's animation toolkit
Python
17
star
19

docker-cgwire

Docker container for https://cg-wire.com
15
star
20

pymel-mock

Accelerate Maya start-up time and prevent accidental use
Python
10
star
21

rez-localz

Package localisation for Rez
Python
10
star
22

docker-usd

Unofficial Dockerfile for Pixar's USD
9
star
23

maya-multicapture

Capture multiple cameras at once
Python
9
star
24

mayabase-centos

Maya base on CentOS 6
8
star
25

maya_python_c_extension

Fork from sonictk
Shell
8
star
26

cs50x

Introduction to Computer Science
C
7
star
27

iscompatible

Python versioning with PyPI requirements.txt syntax
Python
7
star
28

maya-attribute-explorer

Explore available attributes of any node in Maya
Python
6
star
29

deplish

Depends + Pyblish
Python
5
star
30

rez-bundles

Companion repository to rez-for-projects
Python
4
star
31

vfxplatform-docker

Unofficial docker image with apps according to VFX Reference Platform
4
star
32

djv

Mirror of http://djv.sourceforge.net/
C++
3
star
33

pyblish-cpp

C++ implementation of Pyblish QML
C++
3
star
34

avalon-launcher2

Experimental Launcher for the Avalon pipeline
CSS
3
star
35

feather

3d polygon modeller based off of Wings3d
C++
3
star
36

yappi

GitHub clone of https://bitbucket.org/sumerc/yappi
Python
3
star
37

maya-memorycache

Produce caches that remember the state in which they were produced
2
star
38

feather-plugins

Collection of Feather plugins
C++
2
star
39

docker-maya2016

Dockerfile for Maya 2016
2
star
40

rez-installz

Native Rez package manager
2
star
41

PersistentPython

Persistent Python interpreter for Sublime Text 3
Python
2
star
42

mayaGeometryShader

Non-working example of a geometry shader in Maya
Python
2
star
43

rez-internal-example

An example project for rez-for-projects
Python
2
star
44

postal

Source for Postal (1)
C++
2
star
45

from-nand-to-tetris-I

From NAND to Tetris I
Assembly
2
star
46

badshell

Python
2
star
47

add

minimal, cross-platform additive environments for your shell.
Shell
2
star
48

maya-corollary

Client + Server model of a Maya deformer via ZeroMQ
C++
2
star
49

sublime-python-exec

Run Python in Sublime Text without saving
Python
1
star
50

bleed

An Avalon demo profile for Allzpark
Python
1
star
51

be-presets

be presets
1
star
52

be-ad

be project preset for ads
1
star
53

mayabase-ubuntu

Maya base on Ubuntu 14.04
1
star
54

mygame

An OpenGL game
C++
1
star
55

be-helloworld

Test preset for be
1
star
56

ftrack

ftrack evaluation
1
star
57

allzparkdemo

Demo content for Allzpark
Python
1
star
58

docker-dance

Dockerfile for DANCE
1
star
59

maya-procedural-rigging

A Wiki and Issue tracker for a rigging technique.
1
star
60

arsenalsuite

Clone of https://code.google.com/archive/p/arsenalsuite/
HTML
1
star
61

kabaret

Kabaret, https://pythonhosted.org/kabaret/
Python
1
star
62

blur

CS50x Final Project
C
1
star