Skip to content

Commit c6e55d6

Browse files
committed
Drop support for PySide2
PySide2 is no longer maintained, with the last release being made in 2022.
1 parent 89178ed commit c6e55d6

File tree

11 files changed

+26
-131
lines changed

11 files changed

+26
-131
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,8 @@ jobs:
3030

3131
matrix:
3232
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
33-
qt-lib: [pyqt5, pyqt6, pyside2, pyside6]
33+
qt-lib: [pyqt5, pyqt6, pyside6]
3434
os: [ubuntu-latest, windows-latest, macos-latest]
35-
exclude:
36-
# Not installable:
37-
# ERROR: Could not find a version that satisfies the requirement pyside2 (from versions: none)
38-
- python-version: "3.11"
39-
qt-lib: pyside2
40-
os: windows-latest
41-
- python-version: "3.12"
42-
qt-lib: pyside2
43-
- python-version: "3.13"
44-
qt-lib: pyside2
45-
- qt-lib: pyside2
46-
os: macos-latest
4735

4836
steps:
4937
- uses: actions/checkout@v4

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ UNRELEASED
33

44
* Added official support for Python 3.13.
55
* Dropped support for EOL Python 3.8.
6+
* Dropped support for EOL PySide 2.
67

78
4.4.0 (2024-02-07)
89
------------------

README.rst

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pytest-qt
33
=========
44

55
pytest-qt is a `pytest`_ plugin that allows programmers to write tests
6-
for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications.
6+
for `PyQt5`_, `PyQt6`_, and `PySide6`_ applications.
77

88
The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp``
99
creation as needed and provides methods to simulate user interaction,
@@ -22,7 +22,6 @@ like key presses and mouse clicks:
2222
assert widget.greet_label.text() == "Hello!"
2323
2424
25-
.. _PySide2: https://pypi.org/project/PySide2/
2625
.. _PySide6: https://pypi.org/project/PySide6/
2726
.. _PyQt5: https://pypi.org/project/PyQt5/
2827
.. _PyQt6: https://pypi.org/project/PyQt6/
@@ -74,24 +73,23 @@ Features
7473
Requirements
7574
============
7675

77-
Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_.
76+
Works with either PySide6_, PyQt6_ or PyQt5_.
7877

7978
If any of the above libraries is already imported by the time the tests execute, that library will be used.
8079

8180
If not, pytest-qt will try to import and use the Qt APIs, in this order:
8281

8382
- ``PySide6``
84-
- ``PySide2``
8583
- ``PyQt6``
8684
- ``PyQt5``
8785

8886
To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to
89-
``pyside6``, ``pyside2``, ``pyqt6`` or ``pyqt5``:
87+
``pyside6``, ``pyqt6`` or ``pyqt5``:
9088

9189
.. code-block:: ini
9290
9391
[pytest]
94-
qt_api=pyqt5
92+
qt_api=pyqt6
9593
9694
9795
Alternatively, you can set the ``PYTEST_QT_API`` environment
@@ -144,7 +142,7 @@ Running tests
144142

145143
Tests are run using `tox`_::
146144

147-
$ tox -e py37-pyside2,py37-pyqt5
145+
$ tox -e py-pyside6,py-pyqt5
148146

149147
``pytest-qt`` is formatted using `black <https://github.com/ambv/black>`_ and uses
150148
`pre-commit <https://github.com/pre-commit/pre-commit>`_ for linting checks before commits. You

docs/intro.rst

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pytest-qt
33
=========
44

55
pytest-qt is a `pytest`_ plugin that allows programmers to write tests
6-
for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications.
6+
for `PyQt5`_, `PyQt6`_, and `PySide6`_ applications.
77

88
The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp``
99
creation as needed, and registering widgets for testing:
@@ -21,7 +21,6 @@ creation as needed, and registering widgets for testing:
2121
assert widget.greet_label.text() == "Hello!"
2222
2323
24-
.. _PySide2: https://pypi.org/project/PySide2/
2524
.. _PySide6: https://pypi.org/project/PySide6/
2625
.. _PyQt5: https://pypi.org/project/PyQt5/
2726
.. _PyQt6: https://pypi.org/project/PyQt6/
@@ -75,17 +74,16 @@ Requirements
7574

7675
``pytest-qt`` requires Python 3.7+.
7776

78-
Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever
77+
Works with either PySide6_, PyQt6_ or PyQt5_, picking whichever
7978
is available on the system, giving preference to the first one installed in
8079
this order:
8180

8281
- ``PySide6``
83-
- ``PySide2``
8482
- ``PyQt6``
8583
- ``PyQt5``
8684

8785
To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to
88-
``pyside6``, ``pyside2``, ``pyqt6`` or ``pyqt5``:
86+
``pyside6``, ``pyqt6`` or ``pyqt5``:
8987

9088
.. code-block:: ini
9189

src/pytestqt/plugin.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,7 @@ def qtmodeltester(request):
125125

126126

127127
def pytest_addoption(parser):
128-
parser.addini(
129-
"qt_api", 'Qt api version to use: "pyside6" , "pyside2", "pyqt6", "pyqt5"'
130-
)
128+
parser.addini("qt_api", 'Qt api version to use: "pyside6" , "pyqt6", "pyqt5"')
131129
parser.addini("qt_no_exception_capture", "disable automatic exception capture")
132130
parser.addini(
133131
"qt_default_raising",

src/pytestqt/qt_compat.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Provide a common way to import Qt classes used by pytest-qt in a unique manner,
3-
abstracting API differences between PyQt5/6 and PySide2/6.
3+
abstracting API differences between PyQt5/6 and PySide6.
44
55
.. note:: This module is not part of pytest-qt public API, hence its interface
66
may change between releases and users should not rely on it.
@@ -19,7 +19,6 @@
1919

2020
QT_APIS = OrderedDict()
2121
QT_APIS["pyside6"] = "PySide6"
22-
QT_APIS["pyside2"] = "PySide2"
2322
QT_APIS["pyqt6"] = "PyQt6"
2423
QT_APIS["pyqt5"] = "PyQt5"
2524

@@ -85,7 +84,7 @@ def set_qt_api(self, api):
8584
or self._guess_qt_api()
8685
)
8786

88-
self.is_pyside = self.pytest_qt_api in ["pyside2", "pyside6"]
87+
self.is_pyside = self.pytest_qt_api in ["pyside6"]
8988
self.is_pyqt = self.pytest_qt_api in ["pyqt5", "pyqt6"]
9089

9190
if not self.pytest_qt_api: # pragma: no cover
@@ -94,7 +93,7 @@ def set_qt_api(self, api):
9493
for module, reason in sorted(self._import_errors.items())
9594
)
9695
msg = (
97-
"pytest-qt requires either PySide2, PySide6, PyQt5 or PyQt6 installed.\n"
96+
"pytest-qt requires either PySide6, PyQt5 or PyQt6 installed.\n"
9897
+ errors
9998
)
10099
raise pytest.UsageError(msg)
@@ -112,7 +111,7 @@ def _import_module(module_name):
112111

113112
self._check_qt_api_version()
114113

115-
# qInfo is not exposed in PySide2/6 (#232)
114+
# qInfo is not exposed in PySide6 (#232)
116115
if hasattr(QtCore, "QMessageLogger"):
117116
self.qInfo = lambda msg: QtCore.QMessageLogger().info(msg)
118117
elif hasattr(QtCore, "qInfo"):
@@ -151,8 +150,8 @@ def _check_qt_api_version(self):
151150
)
152151

153152
def exec(self, obj, *args, **kwargs):
154-
# exec was a keyword in Python 2, so PySide2 (and also PySide6 6.0)
155-
# name the corresponding method "exec_" instead.
153+
# exec was a keyword in Python 2, so PySide6 6.0
154+
# names the corresponding method "exec_" instead.
156155
#
157156
# The old _exec() alias is removed in PyQt6 and also deprecated as of
158157
# PySide 6.1:
@@ -170,14 +169,6 @@ def get_versions(self):
170169
return VersionTuple(
171170
"PySide6", version, self.QtCore.qVersion(), self.QtCore.__version__
172171
)
173-
elif self.pytest_qt_api == "pyside2":
174-
import PySide2
175-
176-
version = PySide2.__version__
177-
178-
return VersionTuple(
179-
"PySide2", version, self.QtCore.qVersion(), self.QtCore.__version__
180-
)
181172
elif self.pytest_qt_api == "pyqt6":
182173
return VersionTuple(
183174
"PyQt6",

tests/test_basics.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ def test_parse_ini_boolean_invalid():
438438
pytestqt.qtbot._parse_ini_boolean("foo")
439439

440440

441-
@pytest.mark.parametrize("option_api", ["pyqt5", "pyqt6", "pyside2", "pyside6"])
441+
@pytest.mark.parametrize("option_api", ["pyqt5", "pyqt6", "pyside6"])
442442
def test_qt_api_ini_config(testdir, monkeypatch, option_api):
443443
"""
444444
Test qt_api ini option handling.
@@ -479,7 +479,7 @@ def test_foo(qtbot):
479479
result.stderr.fnmatch_lines(["*ModuleNotFoundError:*"])
480480

481481

482-
@pytest.mark.parametrize("envvar", ["pyqt5", "pyqt6", "pyside2", "pyside6"])
482+
@pytest.mark.parametrize("envvar", ["pyqt5", "pyqt6", "pyside6"])
483483
def test_qt_api_ini_config_with_envvar(testdir, monkeypatch, envvar):
484484
"""ensure environment variable wins over config value if both are present"""
485485
testdir.makeini(
@@ -586,10 +586,9 @@ def _fake_is_library_loaded(name, *args):
586586
monkeypatch.setattr(qt_compat, "_is_library_loaded", _fake_is_library_loaded)
587587

588588
expected = (
589-
"pytest-qt requires either PySide2, PySide6, PyQt5 or PyQt6 installed.\n"
589+
"pytest-qt requires either PySide6, PyQt5 or PyQt6 installed.\n"
590590
" PyQt5.QtCore: Failed to import PyQt5.QtCore\n"
591591
" PyQt6.QtCore: Failed to import PyQt6.QtCore\n"
592-
" PySide2.QtCore: Failed to import PySide2.QtCore\n"
593592
" PySide6.QtCore: Failed to import PySide6.QtCore"
594593
)
595594

@@ -602,7 +601,6 @@ def _fake_is_library_loaded(name, *args):
602601
[
603602
("pyqt5", "PyQt5"),
604603
("pyqt6", "PyQt6"),
605-
("pyside2", "PySide2"),
606604
("pyside6", "PySide6"),
607605
],
608606
)

tests/test_logging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def test_qinfo(qtlog):
7575
if qt_api.is_pyside:
7676
assert (
7777
qt_api.qInfo is None
78-
), "pyside2/6 does not expose qInfo. If it does, update this test."
78+
), "pyside6 does not expose qInfo. If it does, update this test."
7979
return
8080

8181
qt_api.qInfo("this is an INFO message")

tests/test_modeltest.py

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import sys
2-
31
import pytest
42

53
from pytestqt.qt_compat import qt_api
@@ -113,33 +111,12 @@ def data(
113111
check_model(BrokenTypeModel(), should_pass=False)
114112

115113

116-
def check_broken_flag_or():
117-
flag = qt_api.QtCore.Qt.AlignmentFlag
118-
try:
119-
int(flag.AlignHorizontal_Mask | flag.AlignVertical_Mask)
120-
except SystemError:
121-
# Should not be happening anywhere else
122-
assert sys.version_info[:2] == (3, 11) and qt_api.pytest_qt_api == "pyside2"
123-
return True
124-
return False
125-
126-
127-
xfail_py311_pyside2 = pytest.mark.xfail(
128-
check_broken_flag_or(),
129-
reason="Fails to OR mask flags",
130-
)
131-
132-
133114
@pytest.mark.parametrize(
134115
"role_value, should_pass",
135116
[
136-
pytest.param(
137-
qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True, marks=xfail_py311_pyside2
138-
),
139-
pytest.param(
140-
qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True, marks=xfail_py311_pyside2
141-
),
142-
pytest.param(0xFFFFFF, False, marks=xfail_py311_pyside2),
117+
pytest.param(qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True),
118+
pytest.param(qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True),
119+
pytest.param(0xFFFFFF, False),
143120
("foo", False),
144121
(object(), False),
145122
],

tests/test_wait_signal.py

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ def cb(str_param, int_param):
881881
def get_mixed_signals_with_guaranteed_name(signaller):
882882
"""
883883
Returns a list of signals with the guarantee that the signals have names (i.e. the names are
884-
manually provided in case of using PySide2, where the signal names cannot be determined at run-time).
884+
manually provided in case of using PySide6, where the signal names cannot be determined at run-time).
885885
"""
886886
if qt_api.is_pyside:
887887
signals = [
@@ -913,27 +913,6 @@ def test_empty_when_no_signal(self, qtbot, signaller):
913913
pass
914914
assert blocker.all_signals_and_args == []
915915

916-
def test_empty_when_no_signal_name_available(self, qtbot, signaller):
917-
"""
918-
Tests that all_signals_and_args is empty even though expected signals are emitted, but signal names aren't
919-
available.
920-
"""
921-
if qt_api.pytest_qt_api != "pyside2":
922-
pytest.skip(
923-
"test only makes sense for PySide2, whose signals don't contain a name!"
924-
)
925-
926-
with qtbot.waitSignals(
927-
signals=[signaller.signal, signaller.signal_args, signaller.signal_args],
928-
timeout=200,
929-
check_params_cbs=None,
930-
order="none",
931-
raising=False,
932-
) as blocker:
933-
signaller.signal.emit()
934-
signaller.signal_args.emit("1", 1)
935-
assert blocker.all_signals_and_args == []
936-
937916
def test_non_empty_on_timeout_no_cb(self, qtbot, signaller):
938917
"""
939918
Tests that all_signals_and_args contains the emitted signals. No callbacks for arg-evaluation are provided. The
@@ -1196,37 +1175,6 @@ def test_strict_order_violation(self, qtbot, signaller):
11961175
"Missing: [signal(), signal_args(QString,int), signal_args(QString,int)]"
11971176
).format(signal_args, signal_args)
11981177

1199-
def test_degenerate_error_msg(self, qtbot, signaller):
1200-
"""
1201-
Tests that the TimeoutError message is degenerate when using PySide2 signals for which no name is provided
1202-
by the user. This degenerate messages doesn't contain the signals' names, and includes a hint to the user how
1203-
to fix the situation.
1204-
"""
1205-
if qt_api.pytest_qt_api != "pyside2":
1206-
pytest.skip(
1207-
"test only makes sense for PySide, whose signals don't contain a name!"
1208-
)
1209-
1210-
with pytest.raises(TimeoutError) as excinfo:
1211-
with qtbot.waitSignals(
1212-
signals=[
1213-
signaller.signal,
1214-
signaller.signal_args,
1215-
signaller.signal_args,
1216-
],
1217-
timeout=200,
1218-
check_params_cbs=None,
1219-
order="none",
1220-
raising=True,
1221-
):
1222-
signaller.signal.emit()
1223-
ex_msg = TestWaitSignalsTimeoutErrorMessage.get_exception_message(excinfo)
1224-
assert ex_msg == (
1225-
"Received 1 of the 3 expected signals. "
1226-
"To improve this error message, provide the names of the signals "
1227-
"in the waitSignals() call."
1228-
)
1229-
12301178
def test_self_defined_signal_name(self, qtbot, signaller):
12311179
"""
12321180
Tests that the waitSignals implementation prefers the user-provided signal names over the names that can

0 commit comments

Comments
 (0)