From 05963e626cd57bb110e86f3f5788359b18c6d87e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 19 Aug 2026 22:03:39 +0200 Subject: [PATCH 1/3] [refactor] Drop a duplicate key in 'LOGLEVEL_COLOROPTS' 'logging.WARN' is another name for 'logging.WARNING', not a separate level, so the dict literal listed the same integer key twice and only ever held one of the two entries. LOG009 pointed this out. Co-Authored-By: Claude Opus 5 (1M context) --- src/_pytest/logging.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 982e12cd8c2..7f1f54ae909 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -85,7 +85,6 @@ class ColoredLevelFormatter(DatetimeFormatter): logging.CRITICAL: {"red"}, logging.ERROR: {"red", "bold"}, logging.WARNING: {"yellow"}, - logging.WARN: {"yellow"}, logging.INFO: {"green"}, logging.DEBUG: {"purple"}, logging.NOTSET: set(), From b5ed56bd7183b41f22215a8d108974ec6497ddb9 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 19 Aug 2026 22:03:50 +0200 Subject: [PATCH 2/3] [refactor] Drop a redundant 'tuple()' call in the rewriter The argument is already a tuple, built by unpacking the previous scope, so wrapping it in 'tuple()' copies it for nothing. C409 pointed this out. Co-Authored-By: Claude Opus 5 (1M context) --- src/_pytest/assertion/rewrite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 362c93d7253..3f44ce72fb4 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -723,7 +723,7 @@ def run(self, mod: ast.Module) -> None: while nodes: node = nodes.pop() if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef): - self.scope = tuple((*self.scope, node)) + self.scope = (*self.scope, node) nodes.append(_SCOPE_END_MARKER) if node == _SCOPE_END_MARKER: self.scope = self.scope[:-1] From ef96e727c833ad0d43e4fcf198b1e38d0fe9c8ce Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Wed, 19 Aug 2026 22:04:01 +0200 Subject: [PATCH 3/3] [ruff] Enable ten more rules the code already satisfies FA102, N999, PGH005, PTH210, S112, LOG009 and the source half of C409 need no change now that the two problems they found are fixed. The remaining four only need a one-line exemption, because the violation is the thing under test: SIM202 on the 'approx' operator overloading check, SIM222 and SIM223 in the assertion rewriting tests, C409 where 'tuple([1, 2])' deliberately builds two equal but distinct tuples ('assert v1 is not v2' on the next line). Every exemption is a 'noqa' on the offending line rather than an entry in 'lint.ignore' or 'per-file-ignores', so the rules keep applying to the rest of those files. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 13 ------------- testing/python/approx.py | 2 +- testing/python/fixtures.py | 2 +- testing/test_assertrewrite.py | 4 ++-- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5cc86d60857..076026f7689 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,7 +147,6 @@ lint.ignore = [ "BLE001", # Do not catch blind exception # flake8-comprehensions ignore "C408", # Unnecessary `dict()`/`list()`/`tuple()` call (rewrite as a literal) - "C409", # Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal) # pydocstyle ignore "D100", # Missing docstring in public module "D101", # Missing docstring in public class @@ -170,19 +169,12 @@ lint.ignore = [ # pytest can do weird low-level things, and we usually know # what we're doing when we use type(..) is ... "E721", # Do not compare types, use `isinstance()` - # flake8-future-annotations ignore - "FA102", # Missing `from __future__ import annotations`, but uses PEP 585/604 syntax # flynt ignore "FLY002", # Consider an f-string instead of string join # flake8-implicit-str-concat ignore "ISC004", # Unparenthesized implicit string concatenation in collection # flake8-logging ignore - "LOG009", # Use of undocumented `logging.WARN` constant "LOG015", # Call on root logger - # pep8-naming ignore - "N999", # Invalid module name - # pygrep-hooks ignore - "PGH005", # Mock method should be called # pylint ignore "PLC0105", # `TypeVar` name "E" does not reflect its covariance; "PLC0414", # Import alias does not rename original package @@ -208,14 +200,12 @@ lint.ignore = [ "PT031", # `pytest.warns()` block should contain a single simple statement # flake8-use-pathlib ignore "PTH124", # `py.path` is in maintenance mode, use `pathlib` instead - "PTH210", # Invalid suffix passed to `.with_suffix()` # ruff ignore "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` "RUF061", # Use context-manager form of `pytest.raises()` # flake8-bandit ignore "S102", # Use of `exec` detected "S110", # `try`-`except`-`pass` detected, consider logging the exception - "S112", # `try`-`except`-`continue` detected, consider logging the exception # flake8-simplify ignore "SIM102", # Use a single `if` statement instead of nested `if` statements "SIM103", # Return the condition directly @@ -223,10 +213,7 @@ lint.ignore = [ "SIM115", # Use a context manager for opening files "SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements "SIM201", # Use `!=` instead of `not ... == ...` - "SIM202", # Use `==` instead of `not ... != ...` "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` # tryceratops ignore "TRY002", # Create your own exception "TRY004", # Prefer `TypeError` exception for invalid type diff --git a/testing/python/approx.py b/testing/python/approx.py index 8cac6f45489..29effdc8820 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -425,7 +425,7 @@ def test_mixed_sequence(self, assert_approx_raises_regex) -> None: def test_operator_overloading(self): assert 1 == approx(1, rel=1e-6, abs=1e-12) - assert not (1 != approx(1, rel=1e-6, abs=1e-12)) + assert not (1 != approx(1, rel=1e-6, abs=1e-12)) # noqa: SIM202 assert 10 != approx(1, rel=1e-6, abs=1e-12) assert not (10 == approx(1, rel=1e-6, abs=1e-12)) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index f49c41ec9df..ac0f9cca54b 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -4541,7 +4541,7 @@ class TestParamValueKey: def test_equal_hashable_values(self) -> None: # Build equal-but-not-identical values to exercise the ``==`` path # rather than the identity shortcut. - v1, v2 = tuple([1, 2]), tuple([1, 2]) + v1, v2 = tuple([1, 2]), tuple([1, 2]) # noqa: C409 assert v1 is not v2 k1, k2 = ParamValueKey(v1, 0), ParamValueKey(v2, 1) assert k1 == k2 diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 12e12449693..a1c431d9dd8 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -676,7 +676,7 @@ def f11() -> None: def test_short_circuit_evaluation(self) -> None: def f1() -> None: - assert True or explode # type: ignore[name-defined,unreachable] # noqa: F821 + assert True or explode # type: ignore[name-defined,unreachable] # noqa: F821,SIM222 getmsg(f1, must_pass=True) @@ -726,7 +726,7 @@ def f2() -> None: def test_boolop_percent(self) -> None: def f1() -> None: - assert 3 % 2 and False + assert 3 % 2 and False # noqa: SIM223 assert getmsg(f1) == "assert ((3 % 2) and False)"