From 535a431fdace3820487774eb2d2ff1764e50e2d7 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 17 Aug 2026 12:09:06 +0200 Subject: [PATCH 1/5] [ruff] Enable RET501 (unnecessary explicit 'return None') Both functions return nothing anywhere else, so a bare 'return' says it. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 2 -- src/_pytest/unittest.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1ad6e5e796a..a4140fef46b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -218,8 +218,6 @@ lint.ignore = [ # flake8-use-pathlib ignore "PTH124", # `py.path` is in maintenance mode, use `pathlib` instead "PTH210", # Invalid suffix passed to `.with_suffix()` - # flake8-return ignore - "RET501", # Do not explicitly `return None` in function if it is the only possible return value # ruff ignore "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` "RUF061", # Use context-manager form of `pytest.raises()` diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index b30926a12b9..2c9e0dcb80b 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -200,7 +200,7 @@ def _register_unittest_setup_method_fixture(self, cls: type) -> None: setup = getattr(cls, "setup_method", None) teardown = getattr(cls, "teardown_method", None) if setup is None and teardown is None: - return None + return def unittest_setup_method_fixture( request: FixtureRequest, From 04f8f0b117bf719056978b345a2600e83f611bc9 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 17 Aug 2026 12:09:12 +0200 Subject: [PATCH 2/5] [ruff] Enable TC005 (empty type-checking block) Two modules kept an 'if TYPE_CHECKING: pass' block and the import that went with it. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 2 -- src/_pytest/threadexception.py | 4 ---- src/_pytest/unraisableexception.py | 4 ---- 3 files changed, 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a4140fef46b..ad8a9b918ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -239,8 +239,6 @@ lint.ignore = [ "SIM222", # Use the simplified expression instead of `... or True` "SIM223", # Use the simplified expression instead of `... and False` "SIM905", # Consider using a list literal instead of `str.split()` - # flake8-type-checking ignore - "TC005", # Found empty type-checking block # tryceratops ignore "TRY002", # Create your own exception "TRY004", # Prefer `TypeError` exception for invalid type diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index eb57783be26..74640873259 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -7,7 +7,6 @@ import threading import traceback from typing import NamedTuple -from typing import TYPE_CHECKING import warnings from _pytest.config import Config @@ -17,9 +16,6 @@ import pytest -if TYPE_CHECKING: - pass - if sys.version_info < (3, 11): from exceptiongroup import ExceptionGroup diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py index 6c092fb6bd3..32f1258038b 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -7,7 +7,6 @@ import sys import traceback from typing import NamedTuple -from typing import TYPE_CHECKING import warnings from _pytest.config import Config @@ -17,9 +16,6 @@ import pytest -if TYPE_CHECKING: - pass - if sys.version_info < (3, 11): from exceptiongroup import ExceptionGroup From 83b504cc4051628c9121758540107d5fd843f9d4 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 17 Aug 2026 12:10:33 +0200 Subject: [PATCH 3/5] [ruff] Enable FURB157 (verbose 'Decimal' constructor) 'Decimal("1")' and 'Decimal(1)' build the same value, the int form skips the string parsing. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 1 - testing/python/approx.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ad8a9b918ea..96cae468fcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,7 +179,6 @@ lint.ignore = [ # flynt ignore "FLY002", # Consider an f-string instead of string join # refurb ignore - "FURB157", # Verbose expression in `Decimal` constructor "FURB188", # Prefer `str.removeprefix()` over conditionally replacing with slice # flake8-implicit-str-concat ignore "ISC004", # Unparenthesized implicit string concatenation in collection diff --git a/testing/python/approx.py b/testing/python/approx.py index ed11a1c4ab5..8cac6f45489 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -672,7 +672,7 @@ def test_list(self): def test_list_decimal(self): actual = [Decimal("1.000001"), Decimal("2.000001")] - expected = [Decimal("1"), Decimal("2")] + expected = [Decimal(1), Decimal(2)] assert actual == approx(expected) @@ -713,7 +713,7 @@ def test_dict_decimal(self): actual = {"a": Decimal("1.000001"), "b": Decimal("2.000001")} # Dictionaries became ordered in python3.6, so switch up the order here # to make sure it doesn't matter. - expected = {"b": Decimal("2"), "a": Decimal("1")} + expected = {"b": Decimal(2), "a": Decimal(1)} assert actual == approx(expected) From 5882c162f85daceb49fabd673b53510d71fb72ea Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 17 Aug 2026 12:13:08 +0200 Subject: [PATCH 4/5] [ruff] Enable FURB188 (use 'str.removeprefix()'/'removesuffix()') Eight hand-rolled 'startswith' + slice pairs become the dedicated string methods. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 2 -- src/_pytest/_code/code.py | 3 +-- src/_pytest/junitxml.py | 6 ++---- src/_pytest/pathlib.py | 3 +-- src/_pytest/terminal.py | 12 ++++-------- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 96cae468fcb..6b4732993f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,8 +178,6 @@ lint.ignore = [ "FA102", # Missing `from __future__ import annotations`, but uses PEP 585/604 syntax # flynt ignore "FLY002", # Consider an f-string instead of string join - # refurb ignore - "FURB188", # Prefer `str.removeprefix()` over conditionally replacing with slice # flake8-implicit-str-concat ignore "ISC004", # Unparenthesized implicit string concatenation in collection # flake8-logging ignore diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 3da3984770b..fee4468cf0f 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -676,8 +676,7 @@ def _get_single_subexc( text = "".join(lines) text = text.rstrip() if tryshort: - if text.startswith(self._striptext): - text = text[len(self._striptext) :] + text = text.removeprefix(self._striptext) return text def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 4dab8f0fb03..dfa9cad7726 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -234,16 +234,14 @@ def append_error(self, report: TestReport) -> None: def append_skipped(self, report: TestReport) -> None: if hasattr(report, "wasxfail"): xfailreason = report.wasxfail - if xfailreason.startswith("reason: "): - xfailreason = xfailreason[8:] + xfailreason = xfailreason.removeprefix("reason: ") xfailreason = bin_xml_escape(xfailreason) skipped = ET.Element("skipped", type="pytest.xfail", message=xfailreason) self.append(skipped) else: assert isinstance(report.longrepr, tuple) filename, lineno, skipreason = report.longrepr - if skipreason.startswith("Skipped: "): - skipreason = skipreason[9:] + skipreason = skipreason.removeprefix("Skipped: ") details = f"{filename}:{lineno}: {skipreason}" skipped = ET.Element( diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index c7bbc939401..8eb593d194d 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -651,8 +651,7 @@ def import_path( if module_file.endswith((".pyc", ".pyo")): module_file = module_file[:-1] - if module_file.endswith(os.sep + "__init__.py"): - module_file = module_file[: -(len(os.sep + "__init__.py"))] + module_file = module_file.removesuffix(os.sep + "__init__.py") try: is_same = _is_same(str(path), module_file) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index e9aa36e9421..825435225b3 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1294,8 +1294,7 @@ def summary_stats(self) -> None: if display_sep: markup_for_end_sep = self._tw.markup("", **main_markup) - if markup_for_end_sep.endswith("\x1b[0m"): - markup_for_end_sep = markup_for_end_sep[:-4] + markup_for_end_sep = markup_for_end_sep.removesuffix("\x1b[0m") fullwidth += len(markup_for_end_sep) msg += markup_for_end_sep @@ -1365,8 +1364,7 @@ def show_skipped_folded(lines: list[str]) -> None: markup_word = self._tw.markup(verbose_word, **verbose_markup) prefix = "Skipped: " for num, fspath, lineno, reason in fskips: - if reason.startswith(prefix): - reason = reason[len(prefix) :] + reason = reason.removeprefix(prefix) if lineno is not None: lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}") else: @@ -1659,8 +1657,7 @@ def _plugin_nameversions(plugininfo) -> list[str]: # Gets us name and version! name = f"{dist.project_name}-{dist.version}" # Questionable convenience, but it keeps things short. - if name.startswith("pytest-"): - name = name[7:] + name = name.removeprefix("pytest-") # We decided to print python package names they can have more than one plugin. if name not in values: values.append(name) @@ -1706,8 +1703,7 @@ def _get_raw_skip_reason(report: TestReport) -> str: """ if hasattr(report, "wasxfail"): reason = report.wasxfail - if reason.startswith("reason: "): - reason = reason[len("reason: ") :] + reason = reason.removeprefix("reason: ") return reason else: assert report.skipped From f0b1c4e2e24a971909387f877329b335b184d3dc Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 17 Aug 2026 12:13:22 +0200 Subject: [PATCH 5/5] [ruff] Enable SIM905 (split a static string) Nine '"a b c".split()' calls become plain list literals. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 1 - testing/python/fixtures.py | 10 +++++----- testing/test_argcomplete.py | 2 +- testing/test_config.py | 2 +- testing/test_conftest.py | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6b4732993f4..6902ef07714 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -235,7 +235,6 @@ lint.ignore = [ "SIM211", # Use `not ...` instead of `False if ... else True` "SIM222", # Use the simplified expression instead of `... or True` "SIM223", # Use the simplified expression instead of `... and False` - "SIM905", # Consider using a list literal instead of `str.split()` # tryceratops ignore "TRY002", # Create your own exception "TRY004", # Prefer `TypeError` exception for invalid type diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 95fe5c389b8..f49c41ec9df 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -4628,7 +4628,7 @@ def test_func(m1): items, _ = pytester.inline_genitems() assert isinstance(items[0], Function) request = TopRequest(items[0], _ispytest=True) - assert request.fixturenames == "m1 f1".split() + assert request.fixturenames == ["m1", "f1"] def test_func_closure_with_native_fixtures(self, pytester: Pytester) -> None: """Sanity check that verifies the order returned by the closures and the @@ -4717,7 +4717,7 @@ def test_func(f1, m1): items, _ = pytester.inline_genitems() assert isinstance(items[0], Function) request = TopRequest(items[0], _ispytest=True) - assert request.fixturenames == "m1 f1".split() + assert request.fixturenames == ["m1", "f1"] def test_func_closure_scopes_reordered(self, pytester: Pytester) -> None: """Test ensures that fixtures are ordered by scope regardless of the order of the parameters, although @@ -4751,7 +4751,7 @@ def test_func(self, f2, f1, c1, m1, s1): items, _ = pytester.inline_genitems() assert isinstance(items[0], Function) request = TopRequest(items[0], _ispytest=True) - assert request.fixturenames == "s1 m1 c1 f2 f1".split() + assert request.fixturenames == ["s1", "m1", "c1", "f2", "f1"] def test_func_closure_same_scope_closer_root_first( self, pytester: Pytester @@ -4794,7 +4794,7 @@ def test_func(m_test, f1): items, _ = pytester.inline_genitems() assert isinstance(items[0], Function) request = TopRequest(items[0], _ispytest=True) - assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split() + assert request.fixturenames == ["p_sub", "m_conf", "m_sub", "m_test", "f1"] def test_func_closure_all_scopes_complex(self, pytester: Pytester) -> None: """Complex test involving all scopes and mixing autouse with normal fixtures""" @@ -4839,7 +4839,7 @@ def test_func(self, f2, f1, m2): items, _ = pytester.inline_genitems() assert isinstance(items[0], Function) request = TopRequest(items[0], _ispytest=True) - assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() + assert request.fixturenames == ["s1", "p1", "m1", "m2", "c1", "f2", "f1"] def test_parametrized_package_scope_reordering(self, pytester: Pytester) -> None: """A parameterized package-scoped fixture correctly reorders items to diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index 5d1513b6206..c2c80f272a6 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -95,5 +95,5 @@ def test_remove_dir_prefix(self): ffc = FastFilesCompleter() fc = FilesCompleter() - for x in "/usr/".split(): + for x in ["/usr/"]: assert not equal_with_bash(x, ffc, fc, out=sys.stdout) diff --git a/testing/test_config.py b/testing/test_config.py index 4a290f98ec5..1d2aceb0d99 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2467,7 +2467,7 @@ def test_with_config_also_in_parent_directory( class TestOverrideIniArgs: - @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) + @pytest.mark.parametrize("name", ["setup.cfg", "tox.ini", "pytest.ini"]) def test_override_ini_names(self, pytester: Pytester, name: str) -> None: section = "[pytest]" if name != "setup.cfg" else "[tool:pytest]" pytester.path.joinpath(name).write_text( diff --git a/testing/test_conftest.py b/testing/test_conftest.py index d02baf7b29a..92e1392d5ac 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -152,7 +152,7 @@ def test_doubledash_considered(pytester: Pytester) -> None: def test_issue151_load_all_conftests(pytester: Pytester) -> None: - names = "code proj src".split() + names = ["code", "proj", "src"] for name in names: p = pytester.mkdir(name) p.joinpath("conftest.py").touch() @@ -248,7 +248,7 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None: assert values[0].__file__.startswith(str(conf)) -@pytest.mark.parametrize("name", "test tests whatever .dotdir".split()) +@pytest.mark.parametrize("name", ["test", "tests", "whatever", ".dotdir"]) def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None: sub = pytester.mkdir(name) subconftest = sub.joinpath("conftest.py")