Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Unscoped enums access for PyQt6 and other missing PyQt6 compatibility changes #271

Merged
merged 9 commits into from
Nov 22, 2021
7 changes: 4 additions & 3 deletions qtpy/QtCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@
"""
Provides QtCore classes and functions.
"""

from . import PYQT6, PYQT5, PYSIDE2, PYSIDE6, PythonQtError


if PYQT6:
from PyQt6 import QtCore
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
from PyQt6.QtCore import *
from PyQt6.QtCore import pyqtSignal as Signal
from PyQt6.QtCore import QT_VERSION_STR as __version__

QCoreApplication.exec_ = QCoreApplication.exec
QEventLoop.exec_ = QEventLoop.exec
QThread.exec_ = QThread.exec

from .enums_compat import promote_enums
promote_enums(QtCore)
elif PYQT5:
from PyQt5.QtCore import *
from PyQt5.QtCore import pyqtSignal as Signal
Expand Down
5 changes: 3 additions & 2 deletions qtpy/QtGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
"""
Provides QtGui classes and functions.
"""
import warnings

from . import PYQT6, PYQT5, PYSIDE2, PYSIDE6, PythonQtError


if PYQT6:
from PyQt6 import QtGui
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
from PyQt6.QtGui import *
QDrag.exec_ = QDrag.exec
QGuiApplication.exec_ = QGuiApplication.exec
QTextDocument.print_ = QTextDocument.print
from .enums_compat import promote_enums
promote_enums(QtGui)
elif PYQT5:
from PyQt5.QtGui import *
elif PYSIDE2:
Expand Down
10 changes: 6 additions & 4 deletions qtpy/QtWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@
"""
Provides widget classes and functions.
"""

from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, PythonQtError
from ._patch.qheaderview import introduce_renamed_methods_qheaderview


if PYQT6:
from PyQt6 import QtWidgets
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
from PyQt6.QtWidgets import *
from PyQt6.QtGui import QAction, QActionGroup, QShortcut
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
QTextEdit.setTabStopWidth = QTextEdit.setTabStopDistance
QTextEdit.tabStopWidth = QTextEdit.tabStopDistance
QTextEdit.print_ = QTextEdit.print
QPlainTextEdit.setTabStopWidth = QPlainTextEdit.setTabStopDistance
QPlainTextEdit.tabStopWidth = QPlainTextEdit.tabStopDistance
QPlainTextEdit.print_ = QPlainTextEdit.print
QApplication.exec_ = QApplication.exec
QDialog.exec_ = QDialog.exec
QMenu.exec_ = QMenu.exec
QTextEdit.print_ = QTextEdit.print
QPlainTextEdit.print_ = QPlainTextEdit.print
from .enums_compat import promote_enums
promote_enums(QtWidgets)
elif PYQT5:
from PyQt5.QtWidgets import *
elif PYSIDE6:
Expand Down
4 changes: 1 addition & 3 deletions qtpy/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
"""
Compatibility functions
"""

from collections.abc import Callable
import sys

from .QtWidgets import QFileDialog
Expand Down Expand Up @@ -53,7 +51,7 @@ def from_qvariant(qobj=None, pytype=None): # analysis:ignore
# Wrappers around QFileDialog static methods
# =============================================================================
def getexistingdirectory(parent=None, caption='', basedir='',
options=QFileDialog.ShowDirsOnly):
options=QFileDialog.Option.ShowDirsOnly):
"""Wrapper around QtGui.QFileDialog.getExistingDirectory static method
Compatible with PyQt >=v4.4 (API #1 and #2) and PySide >=v1.0"""
# Calling QFileDialog static method
Expand Down
50 changes: 50 additions & 0 deletions qtpy/enums_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
# Copyright © 2009- The Spyder Development Team
# Licensed under the terms of the MIT License
"""
Compatibility functions for scoped and unscoped enums access
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
"""
from . import PYQT6

if PYQT6:
import enum

from . import sip

dalthviz marked this conversation as resolved.
Show resolved Hide resolved

def promote_specific_enums(base_class, enum_classes_list, inclusion_criteria):
CAM-Gerlach marked this conversation as resolved.
Show resolved Hide resolved
"""
Allow access for the given enumeration classes values at base class level.

Based on:
https://github.com/pyqtgraph/pyqtgraph/blob/pyqtgraph-0.12.1/pyqtgraph/Qt.py#L331-L377
"""
for enum_class_name in enum_classes_list:
klass = getattr(base_class, enum_class_name)
attrib_names = [x for x in dir(klass) if inclusion_criteria(x)]
CAM-Gerlach marked this conversation as resolved.
Show resolved Hide resolved
for attrib_name in attrib_names:
attrib = getattr(klass, attrib_name)
if not isinstance(attrib, (enum.Enum)):
continue
setattr(base_class, attrib.name, attrib)
CAM-Gerlach marked this conversation as resolved.
Show resolved Hide resolved


def promote_enums(module):
"""
Search enums in the given module and allow unscoped access.

Taken from:
https://github.com/pyqtgraph/pyqtgraph/blob/pyqtgraph-0.12.1/pyqtgraph/Qt.py#L331-L377
"""
class_names = [x for x in dir(module) if x.startswith('Q')]
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
for class_name in class_names:
klass = getattr(module, class_name)
if not isinstance(klass, sip.wrappertype):
continue
attrib_names = [x for x in dir(klass) if x[0].isupper()]
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
for attrib_name in attrib_names:
attrib = getattr(klass, attrib_name)
if not isinstance(attrib, enum.EnumMeta):
continue
for e in attrib:
setattr(klass, e.name, e)
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 15 additions & 0 deletions qtpy/sip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

#
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
# Copyright © 2009- The Spyder Development Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)

from . import PYQT6, PYQT5,PythonQtError
dalthviz marked this conversation as resolved.
Show resolved Hide resolved

if PYQT6:
from PyQt6.sip import *
elif PYQT5:
from PyQt5.sip import *
else:
raise PythonQtError('No Qt bindings could be found')
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 16 additions & 1 deletion qtpy/tests/test_qtcore.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from qtpy import PYQT5, PYQT6, PYSIDE2, QtCore
from qtpy import PYQT5, PYQT6, PYSIDE2, PYQT_VERSION, QtCore
dalthviz marked this conversation as resolved.
Show resolved Hide resolved

"""Test QtCore."""

Expand All @@ -25,3 +25,18 @@ class ClassWithSignal(QtCore.QObject):
instance = ClassWithSignal()

assert isinstance(instance.signal, QtCore.SignalInstance)


@pytest.mark.skipif(PYQT5 and PYQT_VERSION.startswith('5.9'),
reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*"
"to work with scoped enum access")
def test_enum_access():
"""
Test scoped and unscoped enum access for qtpy.QtCore.*.
"""
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
assert QtCore.QAbstractAnimation.Stopped == QtCore.QAbstractAnimation.State.Stopped
assert QtCore.QEvent.ActionAdded == QtCore.QEvent.Type.ActionAdded
assert QtCore.Qt.AlignLeft == QtCore.Qt.AlignmentFlag.AlignLeft
assert QtCore.Qt.Key_Return == QtCore.Qt.Key.Key_Return
assert QtCore.Qt.transparent == QtCore.Qt.GlobalColor.transparent
assert QtCore.Qt.Widget == QtCore.Qt.WindowType.Widget
33 changes: 33 additions & 0 deletions qtpy/tests/test_qtgui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Test QtGui."""
import pytest

from qtpy import PYQT5, PYQT_VERSION, QtGui


def test_qdrag_functions():
"""Test functions mapping for QtGui.QDrag."""
assert QtGui.QDrag.exec_


def test_qguiapplication_functions():
"""Test functions mapping for QtGui.QGuiApplication."""
assert QtGui.QGuiApplication.exec_


def test_qtextdocument_functions():
"""Test functions mapping for QtGui.QTextDocument."""
assert QtGui.QTextDocument.print_


@pytest.mark.skipif(PYQT5 and PYQT_VERSION.startswith('5.9'),
reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*"
"to work with scoped enum access")
def test_enum_access():
"""
Test scoped and unscoped enum access for qtpy.QtWidgets.*.
"""
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
assert QtGui.QColor.Rgb == QtGui.QColor.Spec.Rgb
assert QtGui.QFont.AllUppercase == QtGui.QFont.Capitalization.AllUppercase
assert QtGui.QIcon.Normal == QtGui.QIcon.Mode.Normal
assert QtGui.QImage.Format_Invalid == QtGui.QImage.Format.Format_Invalid

dalthviz marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 45 additions & 0 deletions qtpy/tests/test_qtwidgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Test QtWidgets."""
import pytest

from qtpy import PYQT5, PYQT_VERSION, QtWidgets


def test_qtextedit_functions():
"""Test functions mapping for QtWidgets.QTextEdit."""
assert QtWidgets.QTextEdit.setTabStopWidth
assert QtWidgets.QTextEdit.tabStopWidth
assert QtWidgets.QTextEdit.print_


def test_qplaintextedit_functions():
"""Test functions mapping for QtWidgets.QPlainTextEdit."""
assert QtWidgets.QPlainTextEdit.setTabStopWidth
assert QtWidgets.QPlainTextEdit.tabStopWidth
assert QtWidgets.QPlainTextEdit.print_


def test_qapplication_functions():
"""Test functions mapping for QtWidgets.QApplication."""
assert QtWidgets.QApplication.exec_


def test_qdialog_functions():
"""Test functions mapping for QtWidgets.QDialog."""
assert QtWidgets.QDialog.exec_


def test_qmenu_functions():
"""Test functions mapping for QtWidgets.QDialog."""
assert QtWidgets.QMenu.exec_


@pytest.mark.skipif(PYQT5 and PYQT_VERSION.startswith('5.9'),
reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*"
"to work with scoped enum access")
def test_enum_access():
"""
Test scoped and unscoped enum access for qtpy.QtWidgets.*.
"""
dalthviz marked this conversation as resolved.
Show resolved Hide resolved
assert QtWidgets.QFileDialog.AcceptOpen == QtWidgets.QFileDialog.AcceptMode.AcceptOpen
assert QtWidgets.QMessageBox.InvalidRole == QtWidgets.QMessageBox.ButtonRole.InvalidRole
assert QtWidgets.QStyle.State_None == QtWidgets.QStyle.StateFlag.State_None
dalthviz marked this conversation as resolved.
Show resolved Hide resolved