-
-
Notifications
You must be signed in to change notification settings - Fork 70
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
migrate to qtpy #478
Open
nrbnlulu
wants to merge
3
commits into
pytest-dev:master
Choose a base branch
from
nrbnlulu:migrate_to_qtpy
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
migrate to qtpy #478
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,5 @@ src/pytest_qt.egg-info | |
|
||
# auto-generated by setuptools_scm | ||
/src/pytestqt/_version.py | ||
# PyCharm | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,200 +1,24 @@ | ||
""" | ||
Provide a common way to import Qt classes used by pytest-qt in a unique manner, | ||
abstracting API differences between PyQt5/6 and PySide2/6. | ||
import qtpy | ||
|
||
.. note:: This module is not part of pytest-qt public API, hence its interface | ||
may change between releases and users should not rely on it. | ||
|
||
Based on from https://github.com/epage/PythonUtils. | ||
""" | ||
from qtpy import QtTest, QtCore, QtWidgets, QtQuick, QtQml, QtGui | ||
|
||
|
||
from collections import namedtuple, OrderedDict | ||
import os | ||
import sys | ||
class qt_api: | ||
QT_API = qtpy.API | ||
QtCore = QtCore | ||
QtTest = QtTest | ||
QtGui = QtGui | ||
QtWidgets = QtWidgets | ||
QtQuick = QtQuick | ||
QtQml = QtQml | ||
pytest_qt_api = qtpy.API | ||
Signal = QtCore.Signal | ||
|
||
import pytest | ||
qDebug = QtCore.qDebug | ||
qCritical = QtCore.qCritical | ||
qInfo = QtCore.qInfo | ||
qWarning = QtCore.qWarning | ||
is_pyside = qtpy.API in (qtpy.PYSIDE2, qtpy.PYSIDE6) | ||
|
||
|
||
VersionTuple = namedtuple("VersionTuple", "qt_api, qt_api_version, runtime, compiled") | ||
|
||
QT_APIS = OrderedDict() | ||
QT_APIS["pyside6"] = "PySide6" | ||
QT_APIS["pyside2"] = "PySide2" | ||
QT_APIS["pyqt6"] = "PyQt6" | ||
QT_APIS["pyqt5"] = "PyQt5" | ||
|
||
|
||
def _import(name): | ||
"""Think call so we can mock it during testing""" | ||
return __import__(name) | ||
|
||
|
||
def _is_library_loaded(name): | ||
return name in sys.modules | ||
|
||
|
||
class _QtApi: | ||
""" | ||
Interface to the underlying Qt API currently configured for pytest-qt. | ||
|
||
This object lazily loads all class references and other objects when the ``set_qt_api`` method | ||
gets called, providing a uniform way to access the Qt classes. | ||
""" | ||
|
||
def __init__(self): | ||
self._import_errors = {} | ||
|
||
def _get_qt_api_from_env(self): | ||
api = os.environ.get("PYTEST_QT_API") | ||
supported_apis = QT_APIS.keys() | ||
|
||
if api is not None: | ||
api = api.lower() | ||
if api not in supported_apis: # pragma: no cover | ||
msg = f"Invalid value for $PYTEST_QT_API: {api}, expected one of {supported_apis}" | ||
raise pytest.UsageError(msg) | ||
return api | ||
|
||
def _get_already_loaded_backend(self): | ||
for api, backend in QT_APIS.items(): | ||
if _is_library_loaded(backend): | ||
return api | ||
return None | ||
|
||
def _guess_qt_api(self): # pragma: no cover | ||
def _can_import(name): | ||
try: | ||
_import(name) | ||
return True | ||
except ModuleNotFoundError as e: | ||
self._import_errors[name] = str(e) | ||
return False | ||
|
||
# Note, not importing only the root namespace because when uninstalling from conda, | ||
# the namespace can still be there. | ||
for api, backend in QT_APIS.items(): | ||
if _can_import(f"{backend}.QtCore"): | ||
return api | ||
return None | ||
|
||
def set_qt_api(self, api): | ||
self.pytest_qt_api = ( | ||
self._get_qt_api_from_env() | ||
or api | ||
or self._get_already_loaded_backend() | ||
or self._guess_qt_api() | ||
) | ||
|
||
self.is_pyside = self.pytest_qt_api in ["pyside2", "pyside6"] | ||
self.is_pyqt = self.pytest_qt_api in ["pyqt5", "pyqt6"] | ||
|
||
if not self.pytest_qt_api: # pragma: no cover | ||
errors = "\n".join( | ||
f" {module}: {reason}" | ||
for module, reason in sorted(self._import_errors.items()) | ||
) | ||
msg = ( | ||
"pytest-qt requires either PySide2, PySide6, PyQt5 or PyQt6 installed.\n" | ||
+ errors | ||
) | ||
raise pytest.UsageError(msg) | ||
|
||
_root_module = QT_APIS[self.pytest_qt_api] | ||
|
||
def _import_module(module_name): | ||
m = __import__(_root_module, globals(), locals(), [module_name], 0) | ||
return getattr(m, module_name) | ||
|
||
self.QtCore = QtCore = _import_module("QtCore") | ||
self.QtGui = _import_module("QtGui") | ||
self.QtTest = _import_module("QtTest") | ||
self.QtWidgets = _import_module("QtWidgets") | ||
|
||
self._check_qt_api_version() | ||
|
||
# qInfo is not exposed in PySide2/6 (#232) | ||
if hasattr(QtCore, "QMessageLogger"): | ||
self.qInfo = lambda msg: QtCore.QMessageLogger().info(msg) | ||
elif hasattr(QtCore, "qInfo"): | ||
self.qInfo = QtCore.qInfo | ||
else: | ||
self.qInfo = None | ||
|
||
self.qDebug = QtCore.qDebug | ||
self.qWarning = QtCore.qWarning | ||
self.qCritical = QtCore.qCritical | ||
self.qFatal = QtCore.qFatal | ||
|
||
if self.is_pyside: | ||
self.Signal = QtCore.Signal | ||
self.Slot = QtCore.Slot | ||
self.Property = QtCore.Property | ||
elif self.is_pyqt: | ||
self.Signal = QtCore.pyqtSignal | ||
self.Slot = QtCore.pyqtSlot | ||
self.Property = QtCore.pyqtProperty | ||
else: | ||
assert False, "Expected either is_pyqt or is_pyside" | ||
|
||
def _check_qt_api_version(self): | ||
if not self.is_pyqt: | ||
# We support all PySide versions | ||
return | ||
|
||
if self.QtCore.PYQT_VERSION == 0x060000: # 6.0.0 | ||
raise pytest.UsageError( | ||
"PyQt 6.0 is not supported by pytest-qt, use 6.1+ instead." | ||
) | ||
elif self.QtCore.PYQT_VERSION < 0x050B00: # 5.11.0 | ||
raise pytest.UsageError( | ||
"PyQt < 5.11 is not supported by pytest-qt, use 5.11+ instead." | ||
) | ||
|
||
def exec(self, obj, *args, **kwargs): | ||
# exec was a keyword in Python 2, so PySide2 (and also PySide6 6.0) | ||
# name the corresponding method "exec_" instead. | ||
# | ||
# The old _exec() alias is removed in PyQt6 and also deprecated as of | ||
# PySide 6.1: | ||
# https://codereview.qt-project.org/c/pyside/pyside-setup/+/342095 | ||
if hasattr(obj, "exec"): | ||
return obj.exec(*args, **kwargs) | ||
return obj.exec_(*args, **kwargs) | ||
|
||
def get_versions(self): | ||
if self.pytest_qt_api == "pyside6": | ||
import PySide6 | ||
|
||
version = PySide6.__version__ | ||
|
||
return VersionTuple( | ||
"PySide6", version, self.QtCore.qVersion(), self.QtCore.__version__ | ||
) | ||
elif self.pytest_qt_api == "pyside2": | ||
import PySide2 | ||
|
||
version = PySide2.__version__ | ||
|
||
return VersionTuple( | ||
"PySide2", version, self.QtCore.qVersion(), self.QtCore.__version__ | ||
) | ||
elif self.pytest_qt_api == "pyqt6": | ||
return VersionTuple( | ||
"PyQt6", | ||
self.QtCore.PYQT_VERSION_STR, | ||
self.QtCore.qVersion(), | ||
self.QtCore.QT_VERSION_STR, | ||
) | ||
elif self.pytest_qt_api == "pyqt5": | ||
return VersionTuple( | ||
"PyQt5", | ||
self.QtCore.PYQT_VERSION_STR, | ||
self.QtCore.qVersion(), | ||
self.QtCore.QT_VERSION_STR, | ||
) | ||
|
||
assert False, f"Internal error, unknown pytest_qt_api: {self.pytest_qt_api}" | ||
|
||
|
||
qt_api = _QtApi() | ||
is_pyqt = not is_pyside |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -446,7 +446,7 @@ def test_qt_api_ini_config(testdir, monkeypatch, option_api): | |
""" | ||
from pytestqt.qt_compat import qt_api | ||
|
||
monkeypatch.delenv("PYTEST_QT_API", raising=False) | ||
monkeypatch.delenv("QT_API", raising=False) | ||
|
||
testdir.makeini( | ||
""" | ||
|
@@ -467,7 +467,7 @@ def test_foo(qtbot): | |
) | ||
|
||
result = testdir.runpytest_subprocess() | ||
if qt_api.pytest_qt_api == option_api: | ||
if qt_api.QT_API == option_api: | ||
result.stdout.fnmatch_lines(["* 1 passed in *"]) | ||
else: | ||
try: | ||
|
@@ -492,7 +492,7 @@ def test_qt_api_ini_config_with_envvar(testdir, monkeypatch, envvar): | |
) | ||
) | ||
|
||
monkeypatch.setenv("PYTEST_QT_API", envvar) | ||
monkeypatch.setenv("QT_API", envvar) | ||
|
||
testdir.makepyfile( | ||
""" | ||
|
@@ -504,7 +504,7 @@ def test_foo(qtbot): | |
) | ||
|
||
result = testdir.runpytest_subprocess() | ||
if qt_api.pytest_qt_api == envvar: | ||
if qt_api.QT_API == envvar: | ||
result.stdout.fnmatch_lines(["* 1 passed in *"]) | ||
else: | ||
try: | ||
|
@@ -529,10 +529,10 @@ def test_foo(qtbot): | |
pass | ||
""" | ||
) | ||
monkeypatch.setenv("PYTEST_QT_API", "piecute") | ||
monkeypatch.setenv("QT_API", "piecute") | ||
result = testdir.runpytest_subprocess() | ||
result.stderr.fnmatch_lines( | ||
["* Invalid value for $PYTEST_QT_API: piecute, expected one of *"] | ||
["* Invalid value for $QT_API: piecute, expected one of *"] | ||
) | ||
|
||
|
||
|
@@ -566,7 +566,7 @@ def _fake_import(name, *args): | |
def _fake_is_library_loaded(name, *args): | ||
return False | ||
|
||
monkeypatch.delenv("PYTEST_QT_API", raising=False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that we need to keep supporting |
||
monkeypatch.delenv("QT_API", raising=False) | ||
monkeypatch.setattr(qt_compat, "_import", _fake_import) | ||
monkeypatch.setattr(qt_compat, "_is_library_loaded", _fake_is_library_loaded) | ||
|
||
|
@@ -640,13 +640,13 @@ def _fake_import(name, *args, **kwargs): | |
def _fake_is_library_loaded(name, *args): | ||
return name == backend | ||
|
||
monkeypatch.delenv("PYTEST_QT_API", raising=False) | ||
monkeypatch.delenv("QT_API", raising=False) | ||
monkeypatch.setattr(qt_compat, "_is_library_loaded", _fake_is_library_loaded) | ||
monkeypatch.setattr(builtins, "__import__", _fake_import) | ||
|
||
qt_api.set_qt_api(api=None) | ||
|
||
assert qt_api.pytest_qt_api == option_api | ||
assert qt_api.QT_API == option_api | ||
|
||
|
||
def test_before_close_func(testdir): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should definitely stay - it's very useful information for test runs, and it missing also currently breaks a lot of self tests.