Merge pull request #1618 from pijyoi/pyqt6_abort

PyQt6 install sys.excepthook
This commit is contained in:
Ogi Moore 2021-03-02 22:22:18 -08:00 committed by GitHub
commit 829f07b24a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -11,7 +11,6 @@ This module exists to smooth out some of the differences between PySide and PyQt
""" """
import os, sys, re, time, subprocess, warnings import os, sys, re, time, subprocess, warnings
import importlib
import enum import enum
from .python2_3 import asUnicode from .python2_3 import asUnicode
@ -28,9 +27,10 @@ QT_LIB = os.getenv('PYQTGRAPH_QT_LIB')
## Automatically determine which Qt package to use (unless specified by ## Automatically determine which Qt package to use (unless specified by
## environment variable). ## environment variable).
## This is done by first checking to see whether one of the libraries ## This is done by first checking to see whether one of the libraries
## is already imported. If not, then attempt to import PyQt4, then PySide. ## is already imported. If not, then attempt to import in the order
## specified in libOrder.
if QT_LIB is None: if QT_LIB is None:
libOrder = [PYQT4, PYSIDE, PYQT5, PYSIDE2, PYSIDE6, PYQT6] libOrder = [PYQT5, PYSIDE2, PYSIDE6, PYQT6]
for lib in libOrder: for lib in libOrder:
if lib in sys.modules: if lib in sys.modules:
@ -47,7 +47,7 @@ if QT_LIB is None:
pass pass
if QT_LIB is None: if QT_LIB is None:
raise Exception("PyQtGraph requires one of PyQt4, PyQt5, PyQt6, PySide, PySide2 or PySide6; none of these packages could be imported.") raise Exception("PyQtGraph requires one of PyQt5, PyQt6, PySide2 or PySide6; none of these packages could be imported.")
class FailedImport(object): class FailedImport(object):
@ -60,22 +60,6 @@ class FailedImport(object):
raise self.err raise self.err
def _isQObjectAlive(obj):
"""An approximation of PyQt's isQObjectAlive().
"""
try:
if hasattr(obj, 'parent'):
obj.parent()
elif hasattr(obj, 'parentItem'):
obj.parentItem()
else:
raise Exception("Cannot determine whether Qt object %s is still alive." % obj)
except RuntimeError:
return False
else:
return True
# Make a loadUiType function like PyQt has # Make a loadUiType function like PyQt has
# Credit: # Credit:
@ -106,14 +90,18 @@ def _loadUiType(uiFile):
http://stackoverflow.com/a/8717832 http://stackoverflow.com/a/8717832
""" """
if QT_LIB == "PYSIDE": pyside2uic = None
import pysideuic if QT_LIB == PYSIDE2:
else:
try: try:
import pyside2uic as pysideuic import pyside2uic
except ImportError: except ImportError:
# later vserions of pyside2 have dropped pysideuic; use the uic binary instead. # later versions of pyside2 have dropped pyside2uic; use the uic binary instead.
pysideuic = None pyside2uic = None
if pyside2uic is None:
pyside2version = tuple(map(int, PySide2.__version__.split(".")))
if (5, 14) <= pyside2version < (5, 14, 2, 2):
warnings.warn('For UI compilation, it is recommended to upgrade to PySide >= 5.15')
# get class names from ui file # get class names from ui file
import xml.etree.ElementTree as xml import xml.etree.ElementTree as xml
@ -122,20 +110,16 @@ def _loadUiType(uiFile):
form_class = parsed.find('class').text form_class = parsed.find('class').text
# convert ui file to python code # convert ui file to python code
if pysideuic is None: if pyside2uic is None:
if QT_LIB == PYSIDE2:
pyside2version = tuple(map(int, PySide2.__version__.split(".")))
if pyside2version >= (5, 14) and pyside2version < (5, 14, 2, 2):
warnings.warn('For UI compilation, it is recommended to upgrade to PySide >= 5.15')
uic_executable = QT_LIB.lower() + '-uic' uic_executable = QT_LIB.lower() + '-uic'
uipy = subprocess.check_output([uic_executable, uiFile]) uipy = subprocess.check_output([uic_executable, uiFile])
else: else:
o = _StringIO() o = _StringIO()
with open(uiFile, 'r') as f: with open(uiFile, 'r') as f:
pysideuic.compileUi(f, o, indent=0) pyside2uic.compileUi(f, o, indent=0)
uipy = o.getvalue() uipy = o.getvalue()
# exceute python code # execute python code
pyc = compile(uipy, '<string>', 'exec') pyc = compile(uipy, '<string>', 'exec')
frame = {} frame = {}
exec(pyc, frame) exec(pyc, frame)
@ -147,65 +131,10 @@ def _loadUiType(uiFile):
return form_class, base_class return form_class, base_class
if QT_LIB == PYSIDE: if QT_LIB == PYQT5:
from PySide import QtGui, QtCore
try:
from PySide import QtOpenGL
except ImportError as err:
QtOpenGL = FailedImport(err)
try:
from PySide import QtSvg
except ImportError as err:
QtSvg = FailedImport(err)
try:
from PySide import QtTest
except ImportError as err:
QtTest = FailedImport(err)
try:
from PySide import shiboken
isQObjectAlive = shiboken.isValid
except ImportError:
# use approximate version
isQObjectAlive = _isQObjectAlive
import PySide
VERSION_INFO = 'PySide ' + PySide.__version__ + ' Qt ' + QtCore.__version__
elif QT_LIB == PYQT4:
from PyQt4 import QtGui, QtCore, uic
try:
from PyQt4 import QtSvg
except ImportError as err:
QtSvg = FailedImport(err)
try:
from PyQt4 import QtOpenGL
except ImportError as err:
QtOpenGL = FailedImport(err)
try:
from PyQt4 import QtTest
except ImportError as err:
QtTest = FailedImport(err)
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
elif QT_LIB == PYQT5:
# We're using PyQt5 which has a different structure so we're going to use a shim to # We're using PyQt5 which has a different structure so we're going to use a shim to
# recreate the Qt4 structure for Qt5 # recreate the Qt4 structure for Qt5
from PyQt5 import QtGui, QtCore, QtWidgets, uic from PyQt5 import QtGui, QtCore, QtWidgets, sip, uic
# PyQt5, starting in v5.5, calls qAbort when an exception is raised inside
# a slot. To maintain backward compatibility (and sanity for interactive
# users), we install a global exception hook to override this behavior.
ver = QtCore.PYQT_VERSION_STR.split('.')
if int(ver[1]) >= 5:
if sys.excepthook == sys.__excepthook__:
sys_excepthook = sys.excepthook
def pyqt5_qabort_override(*args, **kwds):
return sys_excepthook(*args, **kwds)
sys.excepthook = pyqt5_qabort_override
try: try:
from PyQt5 import QtSvg from PyQt5 import QtSvg
@ -219,7 +148,7 @@ elif QT_LIB == PYQT5:
VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
elif QT_LIB == PYQT6: elif QT_LIB == PYQT6:
from PyQt6 import QtGui, QtCore, QtWidgets, uic from PyQt6 import QtGui, QtCore, QtWidgets, sip, uic
try: try:
from PyQt6 import QtSvg from PyQt6 import QtSvg
@ -248,12 +177,8 @@ elif QT_LIB == PYSIDE2:
except ImportError as err: except ImportError as err:
QtTest = FailedImport(err) QtTest = FailedImport(err)
try: import shiboken2
import shiboken2 isQObjectAlive = shiboken2.isValid
isQObjectAlive = shiboken2.isValid
except ImportError:
# use approximate version
isQObjectAlive = _isQObjectAlive
import PySide2 import PySide2
VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__ VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__
@ -273,12 +198,8 @@ elif QT_LIB == PYSIDE6:
except ImportError as err: except ImportError as err:
QtTest = FailedImport(err) QtTest = FailedImport(err)
try: import shiboken6
import shiboken6 isQObjectAlive = shiboken6.isValid
isQObjectAlive = shiboken6.isValid
except ImportError:
# use approximate version
isQObjectAlive = _isQObjectAlive
import PySide6 import PySide6
VERSION_INFO = 'PySide6 ' + PySide6.__version__ + ' Qt ' + QtCore.__version__ VERSION_INFO = 'PySide6 ' + PySide6.__version__ + ' Qt ' + QtCore.__version__
@ -360,8 +281,8 @@ if QT_LIB in [PYQT6, PYSIDE6]:
QtWidgets.QOpenGLWidget = QtOpenGLWidgets.QOpenGLWidget QtWidgets.QOpenGLWidget = QtOpenGLWidgets.QOpenGLWidget
# Common to PySide, PySide2 and PySide6 # Common to PySide2 and PySide6
if QT_LIB in [PYSIDE, PYSIDE2, PYSIDE6]: if QT_LIB in [PYSIDE2, PYSIDE6]:
QtVersion = QtCore.__version__ QtVersion = QtCore.__version__
loadUiType = _loadUiType loadUiType = _loadUiType
@ -377,14 +298,19 @@ if QT_LIB in [PYSIDE, PYSIDE2, PYSIDE6]:
QtTest.QTest.qWait = qWait QtTest.QTest.qWait = qWait
# Common to PyQt4, PyQt5 and PyQt6 # Common to PyQt5 and PyQt6
if QT_LIB in [PYQT4, PYQT5, PYQT6]: if QT_LIB in [PYQT5, PYQT6]:
QtVersion = QtCore.QT_VERSION_STR QtVersion = QtCore.QT_VERSION_STR
try: # PyQt, starting in v5.5, calls qAbort when an exception is raised inside
sip = importlib.import_module(QT_LIB + '.sip') # a slot. To maintain backward compatibility (and sanity for interactive
except ModuleNotFoundError: # users), we install a global exception hook to override this behavior.
import sip if sys.excepthook == sys.__excepthook__:
sys_excepthook = sys.excepthook
def pyqt_qabort_override(*args, **kwds):
return sys_excepthook(*args, **kwds)
sys.excepthook = pyqt_qabort_override
def isQObjectAlive(obj): def isQObjectAlive(obj):
return not sip.isdeleted(obj) return not sip.isdeleted(obj)