From e00b4cd5a0d1b0b6431058a45ce6d6a6d2765e7e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Dec 2024 10:36:06 -0300 Subject: [PATCH] Drop support for PySide2 PySide2 is no longer maintained, with the last release being made in 2022. --- .github/workflows/test.yml | 14 +------------- CHANGELOG.rst | 1 + README.rst | 12 +++++------- docs/intro.rst | 8 +++----- src/pytestqt/plugin.py | 4 +--- src/pytestqt/qt_compat.py | 21 ++++++--------------- tests/test_basics.py | 8 +++----- tests/test_logging.py | 2 +- tests/test_modeltest.py | 29 +++-------------------------- tests/test_wait_signal.py | 12 ++++++------ tox.ini | 4 +--- 11 files changed, 31 insertions(+), 84 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b32f9df6..c411967c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,20 +30,8 @@ jobs: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - qt-lib: [pyqt5, pyqt6, pyside2, pyside6] + qt-lib: [pyqt5, pyqt6, pyside6] os: [ubuntu-latest, windows-latest, macos-latest] - exclude: - # Not installable: - # ERROR: Could not find a version that satisfies the requirement pyside2 (from versions: none) - - python-version: "3.11" - qt-lib: pyside2 - os: windows-latest - - python-version: "3.12" - qt-lib: pyside2 - - python-version: "3.13" - qt-lib: pyside2 - - qt-lib: pyside2 - os: macos-latest steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7fbe4b2d..5f354b42 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,7 @@ UNRELEASED * Added official support for Python 3.13. * Dropped support for EOL Python 3.8. +* Dropped support for EOL PySide 2. 4.4.0 (2024-02-07) ------------------ diff --git a/README.rst b/README.rst index ca9c6f97..a11e700d 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ pytest-qt ========= pytest-qt is a `pytest`_ plugin that allows programmers to write tests -for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications. +for `PyQt5`_, `PyQt6`_, and `PySide6`_ applications. The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp`` creation as needed and provides methods to simulate user interaction, @@ -22,7 +22,6 @@ like key presses and mouse clicks: assert widget.greet_label.text() == "Hello!" -.. _PySide2: https://pypi.org/project/PySide2/ .. _PySide6: https://pypi.org/project/PySide6/ .. _PyQt5: https://pypi.org/project/PyQt5/ .. _PyQt6: https://pypi.org/project/PyQt6/ @@ -74,24 +73,23 @@ Features Requirements ============ -Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_. +Works with either PySide6_, PyQt6_ or PyQt5_. If any of the above libraries is already imported by the time the tests execute, that library will be used. If not, pytest-qt will try to import and use the Qt APIs, in this order: - ``PySide6`` -- ``PySide2`` - ``PyQt6`` - ``PyQt5`` To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to -``pyside6``, ``pyside2``, ``pyqt6`` or ``pyqt5``: +``pyside6``, ``pyqt6`` or ``pyqt5``: .. code-block:: ini [pytest] - qt_api=pyqt5 + qt_api=pyqt6 Alternatively, you can set the ``PYTEST_QT_API`` environment @@ -144,7 +142,7 @@ Running tests Tests are run using `tox`_:: - $ tox -e py37-pyside2,py37-pyqt5 + $ tox -e py-pyside6,py-pyqt5 ``pytest-qt`` is formatted using `black `_ and uses `pre-commit `_ for linting checks before commits. You diff --git a/docs/intro.rst b/docs/intro.rst index 3075d322..1500a543 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -3,7 +3,7 @@ pytest-qt ========= pytest-qt is a `pytest`_ plugin that allows programmers to write tests -for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications. +for `PyQt5`_, `PyQt6`_, and `PySide6`_ applications. The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp`` creation as needed, and registering widgets for testing: @@ -21,7 +21,6 @@ creation as needed, and registering widgets for testing: assert widget.greet_label.text() == "Hello!" -.. _PySide2: https://pypi.org/project/PySide2/ .. _PySide6: https://pypi.org/project/PySide6/ .. _PyQt5: https://pypi.org/project/PyQt5/ .. _PyQt6: https://pypi.org/project/PyQt6/ @@ -75,17 +74,16 @@ Requirements ``pytest-qt`` requires Python 3.7+. -Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever +Works with either PySide6_, PyQt6_ or PyQt5_, picking whichever is available on the system, giving preference to the first one installed in this order: - ``PySide6`` -- ``PySide2`` - ``PyQt6`` - ``PyQt5`` To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to -``pyside6``, ``pyside2``, ``pyqt6`` or ``pyqt5``: +``pyside6``, ``pyqt6`` or ``pyqt5``: .. code-block:: ini diff --git a/src/pytestqt/plugin.py b/src/pytestqt/plugin.py index b90390c4..3b4a3d6c 100644 --- a/src/pytestqt/plugin.py +++ b/src/pytestqt/plugin.py @@ -125,9 +125,7 @@ def qtmodeltester(request): def pytest_addoption(parser): - parser.addini( - "qt_api", 'Qt api version to use: "pyside6" , "pyside2", "pyqt6", "pyqt5"' - ) + parser.addini("qt_api", 'Qt api version to use: "pyside6" , "pyqt6", "pyqt5"') parser.addini("qt_no_exception_capture", "disable automatic exception capture") parser.addini( "qt_default_raising", diff --git a/src/pytestqt/qt_compat.py b/src/pytestqt/qt_compat.py index e4373e41..d3aafded 100644 --- a/src/pytestqt/qt_compat.py +++ b/src/pytestqt/qt_compat.py @@ -1,6 +1,6 @@ """ 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. +abstracting API differences between PyQt5/6 and PySide6. .. 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. @@ -19,7 +19,6 @@ QT_APIS = OrderedDict() QT_APIS["pyside6"] = "PySide6" -QT_APIS["pyside2"] = "PySide2" QT_APIS["pyqt6"] = "PyQt6" QT_APIS["pyqt5"] = "PyQt5" @@ -85,7 +84,7 @@ def set_qt_api(self, api): or self._guess_qt_api() ) - self.is_pyside = self.pytest_qt_api in ["pyside2", "pyside6"] + self.is_pyside = self.pytest_qt_api in ["pyside6"] self.is_pyqt = self.pytest_qt_api in ["pyqt5", "pyqt6"] if not self.pytest_qt_api: # pragma: no cover @@ -94,7 +93,7 @@ def set_qt_api(self, api): for module, reason in sorted(self._import_errors.items()) ) msg = ( - "pytest-qt requires either PySide2, PySide6, PyQt5 or PyQt6 installed.\n" + "pytest-qt requires either PySide6, PyQt5 or PyQt6 installed.\n" + errors ) raise pytest.UsageError(msg) @@ -112,7 +111,7 @@ def _import_module(module_name): self._check_qt_api_version() - # qInfo is not exposed in PySide2/6 (#232) + # qInfo is not exposed in PySide6 (#232) if hasattr(QtCore, "QMessageLogger"): self.qInfo = lambda msg: QtCore.QMessageLogger().info(msg) elif hasattr(QtCore, "qInfo"): @@ -151,8 +150,8 @@ def _check_qt_api_version(self): ) 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. + # exec was a keyword in Python 2, so PySide6 6.0 + # names the corresponding method "exec_" instead. # # The old _exec() alias is removed in PyQt6 and also deprecated as of # PySide 6.1: @@ -170,14 +169,6 @@ def get_versions(self): 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", diff --git a/tests/test_basics.py b/tests/test_basics.py index 60b03c34..85e24f1b 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -438,7 +438,7 @@ def test_parse_ini_boolean_invalid(): pytestqt.qtbot._parse_ini_boolean("foo") -@pytest.mark.parametrize("option_api", ["pyqt5", "pyqt6", "pyside2", "pyside6"]) +@pytest.mark.parametrize("option_api", ["pyqt5", "pyqt6", "pyside6"]) def test_qt_api_ini_config(testdir, monkeypatch, option_api): """ Test qt_api ini option handling. @@ -479,7 +479,7 @@ def test_foo(qtbot): result.stderr.fnmatch_lines(["*ModuleNotFoundError:*"]) -@pytest.mark.parametrize("envvar", ["pyqt5", "pyqt6", "pyside2", "pyside6"]) +@pytest.mark.parametrize("envvar", ["pyqt5", "pyqt6", "pyside6"]) def test_qt_api_ini_config_with_envvar(testdir, monkeypatch, envvar): """ensure environment variable wins over config value if both are present""" testdir.makeini( @@ -586,10 +586,9 @@ def _fake_is_library_loaded(name, *args): monkeypatch.setattr(qt_compat, "_is_library_loaded", _fake_is_library_loaded) expected = ( - "pytest-qt requires either PySide2, PySide6, PyQt5 or PyQt6 installed.\n" + "pytest-qt requires either PySide6, PyQt5 or PyQt6 installed.\n" " PyQt5.QtCore: Failed to import PyQt5.QtCore\n" " PyQt6.QtCore: Failed to import PyQt6.QtCore\n" - " PySide2.QtCore: Failed to import PySide2.QtCore\n" " PySide6.QtCore: Failed to import PySide6.QtCore" ) @@ -602,7 +601,6 @@ def _fake_is_library_loaded(name, *args): [ ("pyqt5", "PyQt5"), ("pyqt6", "PyQt6"), - ("pyside2", "PySide2"), ("pyside6", "PySide6"), ], ) diff --git a/tests/test_logging.py b/tests/test_logging.py index b13f1814..80b694ca 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -75,7 +75,7 @@ def test_qinfo(qtlog): if qt_api.is_pyside: assert ( qt_api.qInfo is None - ), "pyside2/6 does not expose qInfo. If it does, update this test." + ), "pyside6 does not expose qInfo. If it does, update this test." return qt_api.qInfo("this is an INFO message") diff --git a/tests/test_modeltest.py b/tests/test_modeltest.py index 665f1250..c427a263 100644 --- a/tests/test_modeltest.py +++ b/tests/test_modeltest.py @@ -1,5 +1,3 @@ -import sys - import pytest from pytestqt.qt_compat import qt_api @@ -113,33 +111,12 @@ def data( check_model(BrokenTypeModel(), should_pass=False) -def check_broken_flag_or(): - flag = qt_api.QtCore.Qt.AlignmentFlag - try: - int(flag.AlignHorizontal_Mask | flag.AlignVertical_Mask) - except SystemError: - # Should not be happening anywhere else - assert sys.version_info[:2] == (3, 11) and qt_api.pytest_qt_api == "pyside2" - return True - return False - - -xfail_py311_pyside2 = pytest.mark.xfail( - check_broken_flag_or(), - reason="Fails to OR mask flags", -) - - @pytest.mark.parametrize( "role_value, should_pass", [ - pytest.param( - qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True, marks=xfail_py311_pyside2 - ), - pytest.param( - qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True, marks=xfail_py311_pyside2 - ), - pytest.param(0xFFFFFF, False, marks=xfail_py311_pyside2), + pytest.param(qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True), + pytest.param(qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True), + pytest.param(0xFFFFFF, False), ("foo", False), (object(), False), ], diff --git a/tests/test_wait_signal.py b/tests/test_wait_signal.py index e7f6013f..87ca324e 100644 --- a/tests/test_wait_signal.py +++ b/tests/test_wait_signal.py @@ -881,7 +881,7 @@ def cb(str_param, int_param): def get_mixed_signals_with_guaranteed_name(signaller): """ Returns a list of signals with the guarantee that the signals have names (i.e. the names are - manually provided in case of using PySide2, where the signal names cannot be determined at run-time). + manually provided in case of using PySide6, where the signal names cannot be determined at run-time). """ if qt_api.is_pyside: signals = [ @@ -918,9 +918,9 @@ def test_empty_when_no_signal_name_available(self, qtbot, signaller): Tests that all_signals_and_args is empty even though expected signals are emitted, but signal names aren't available. """ - if qt_api.pytest_qt_api != "pyside2": + if qt_api.pytest_qt_api != "pyside6": pytest.skip( - "test only makes sense for PySide2, whose signals don't contain a name!" + "test only makes sense for PySide6, whose signals don't contain a name" ) with qtbot.waitSignals( @@ -1198,13 +1198,13 @@ def test_strict_order_violation(self, qtbot, signaller): def test_degenerate_error_msg(self, qtbot, signaller): """ - Tests that the TimeoutError message is degenerate when using PySide2 signals for which no name is provided + Tests that the TimeoutError message is degenerate when using PySide6 signals for which no name is provided by the user. This degenerate messages doesn't contain the signals' names, and includes a hint to the user how to fix the situation. """ - if qt_api.pytest_qt_api != "pyside2": + if qt_api.pytest_qt_api != "pyside6": pytest.skip( - "test only makes sense for PySide, whose signals don't contain a name!" + "test only makes sense for PySide, whose signals don't contain a name" ) with pytest.raises(TimeoutError) as excinfo: diff --git a/tox.ini b/tox.ini index e1203e3a..09abcb76 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,16 @@ [tox] -envlist = py{39,310,311,312,313}-{pyqt5,pyside2,pyside6,pyqt6} +envlist = py{39,310,311,312,313}-{pyqt5,pyside6,pyqt6} [testenv] deps= pytest pyside6: pyside6 - pyside2: pyside2 pyqt5: pyqt5 pyqt6: pyqt6 commands= pytest --color=yes {posargs} setenv= pyside6: PYTEST_QT_API=pyside6 - pyside2: PYTEST_QT_API=pyside2 pyqt5: PYTEST_QT_API=pyqt5 pyqt6: PYTEST_QT_API=pyqt6 QT_QPA_PLATFORM=offscreen