Skip to content

Commit 0d1f4c6

Browse files
Merge pull request #11914 - Activate flake8-bugbear and flake8-pyi
2 parents e28f35c + 3101c02 commit 0d1f4c6

18 files changed

+64
-52
lines changed

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,24 @@ ignore = "W009"
128128
src = ["src"]
129129
line-length = 88
130130
select = [
131+
"B", # bugbear
131132
"D", # pydocstyle
132133
"E", # pycodestyle
133134
"F", # pyflakes
134135
"I", # isort
136+
"PYI", # flake8-pyi
135137
"UP", # pyupgrade
136138
"RUF", # ruff
137139
"W", # pycodestyle
138140
]
139141
ignore = [
142+
# bugbear ignore
143+
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
144+
"B007", # Loop control variable `i` not used within loop body
145+
"B009", # Do not call `getattr` with a constant attribute value
146+
"B010", # [*] Do not call `setattr` with a constant attribute value.
147+
"B011", # Do not `assert False` (`python -O` removes these calls)
148+
"B028", # No explicit `stacklevel` keyword argument found
140149
# pycodestyle ignore
141150
# pytest can do weird low-level things, and we usually know
142151
# what we're doing when we use type(..) is ...
@@ -180,4 +189,6 @@ known-local-folder = ["pytest", "_pytest"]
180189
lines-after-imports = 2
181190

182191
[tool.ruff.lint.per-file-ignores]
192+
"src/_pytest/_py/**/*.py" = ["B", "PYI"]
183193
"src/_pytest/_version.py" = ["I001"]
194+
"testing/python/approx.py" = ["B015"]

scripts/prepare-release-pr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def prepare_release_pr(
7979
)
8080
except InvalidFeatureRelease as e:
8181
print(f"{Fore.RED}{e}")
82-
raise SystemExit(1)
82+
raise SystemExit(1) from None
8383

8484
print(f"Version: {Fore.CYAN}{version}")
8585

scripts/update-plugin-list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def main() -> None:
208208
f.write(f"This list contains {len(plugins)} plugins.\n\n")
209209
f.write(".. only:: not latex\n\n")
210210

211-
wcwidth # reference library that must exist for tabulate to work
211+
_ = wcwidth # reference library that must exist for tabulate to work
212212
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
213213
f.write(indent(plugin_table, " "))
214214
f.write("\n\n")

src/_pytest/_io/terminalwriter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,17 @@ def _highlight(
232232
# which may lead to the previous color being propagated to the
233233
# start of the expression, so reset first.
234234
return "\x1b[0m" + highlighted
235-
except pygments.util.ClassNotFound:
235+
except pygments.util.ClassNotFound as e:
236236
raise UsageError(
237237
"PYTEST_THEME environment variable had an invalid value: '{}'. "
238238
"Only valid pygment styles are allowed.".format(
239239
os.getenv("PYTEST_THEME")
240240
)
241-
)
242-
except pygments.util.OptionError:
241+
) from e
242+
except pygments.util.OptionError as e:
243243
raise UsageError(
244244
"PYTEST_THEME_MODE environment variable had an invalid value: '{}'. "
245245
"The only allowed values are 'dark' and 'light'.".format(
246246
os.getenv("PYTEST_THEME_MODE")
247247
)
248-
)
248+
) from e

src/_pytest/capture.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,8 @@ class CaptureResult(NamedTuple, Generic[AnyStr]):
598598
else:
599599

600600
class CaptureResult(
601-
collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr]
601+
collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024
602+
Generic[AnyStr],
602603
):
603604
"""The result of :method:`caplog.readouterr() <pytest.CaptureFixture.readouterr>`."""
604605

src/_pytest/compat.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515
from typing import Callable
1616
from typing import Final
1717
from typing import NoReturn
18-
from typing import TypeVar
19-
20-
21-
_T = TypeVar("_T")
22-
_S = TypeVar("_S")
2318

2419

2520
# fmt: off

src/_pytest/config/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,13 +1848,13 @@ def parse_warning_filter(
18481848
try:
18491849
action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
18501850
except warnings._OptionError as e:
1851-
raise UsageError(error_template.format(error=str(e)))
1851+
raise UsageError(error_template.format(error=str(e))) from None
18521852
try:
18531853
category: Type[Warning] = _resolve_warning_category(category_)
18541854
except Exception:
18551855
exc_info = ExceptionInfo.from_current()
18561856
exception_text = exc_info.getrepr(style="native")
1857-
raise UsageError(error_template.format(error=exception_text))
1857+
raise UsageError(error_template.format(error=exception_text)) from None
18581858
if message and escape:
18591859
message = re.escape(message)
18601860
if module and escape:
@@ -1867,7 +1867,7 @@ def parse_warning_filter(
18671867
except ValueError as e:
18681868
raise UsageError(
18691869
error_template.format(error=f"invalid lineno {lineno_!r}: {e}")
1870-
)
1870+
) from None
18711871
else:
18721872
lineno = 0
18731873
return action, message, category, module, lineno

src/_pytest/unittest.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
209209
)
210210
# Invoke the attributes to trigger storing the traceback
211211
# trial causes some issue there.
212-
excinfo.value
213-
excinfo.traceback
212+
_ = excinfo.value
213+
_ = excinfo.traceback
214214
except TypeError:
215215
try:
216216
try:
@@ -361,14 +361,21 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
361361

362362

363363
# Twisted trial support.
364+
classImplements_has_run = False
364365

365366

366367
@hookimpl(wrapper=True)
367368
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
368369
if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
369370
ut: Any = sys.modules["twisted.python.failure"]
371+
global classImplements_has_run
370372
Failure__init__ = ut.Failure.__init__
371-
check_testcase_implements_trial_reporter()
373+
if not classImplements_has_run:
374+
from twisted.trial.itrial import IReporter
375+
from zope.interface import classImplements
376+
377+
classImplements(TestCaseFunction, IReporter)
378+
classImplements_has_run = True
372379

373380
def excstore(
374381
self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
@@ -396,16 +403,6 @@ def excstore(
396403
return res
397404

398405

399-
def check_testcase_implements_trial_reporter(done: List[int] = []) -> None:
400-
if done:
401-
return
402-
from twisted.trial.itrial import IReporter
403-
from zope.interface import classImplements
404-
405-
classImplements(TestCaseFunction, IReporter)
406-
done.append(1)
407-
408-
409406
def _is_skipped(obj) -> bool:
410407
"""Return True if the given object has been marked with @unittest.skip."""
411408
return bool(getattr(obj, "__unittest_skip__", False))

testing/_py/test_local.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,9 +1241,9 @@ class TestWINLocalPath:
12411241

12421242
def test_owner_group_not_implemented(self, path1):
12431243
with pytest.raises(NotImplementedError):
1244-
path1.stat().owner
1244+
_ = path1.stat().owner
12451245
with pytest.raises(NotImplementedError):
1246-
path1.stat().group
1246+
_ = path1.stat().group
12471247

12481248
def test_chmod_simple_int(self, path1):
12491249
mode = path1.stat().mode

testing/code/test_excinfo.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
387387
excinfo = pytest.raises(ValueError, template.render, h=h)
388388
for item in excinfo.traceback:
389389
print(item) # XXX: for some reason jinja.Template.render is printed in full
390-
item.source # shouldn't fail
390+
_ = item.source # shouldn't fail
391391
if isinstance(item.path, Path) and item.path.name == "test.txt":
392392
assert str(item.source) == "{{ h()}}:"
393393

@@ -418,7 +418,7 @@ def test_codepath_Queue_example() -> None:
418418

419419
def test_match_succeeds():
420420
with pytest.raises(ZeroDivisionError) as excinfo:
421-
0 // 0
421+
_ = 0 // 0
422422
excinfo.match(r".*zero.*")
423423

424424

@@ -584,7 +584,7 @@ def test_repr_source_excinfo(self) -> None:
584584
try:
585585

586586
def f():
587-
1 / 0
587+
_ = 1 / 0
588588

589589
f()
590590

@@ -601,7 +601,7 @@ def f():
601601
print(line)
602602
assert lines == [
603603
" def f():",
604-
"> 1 / 0",
604+
"> _ = 1 / 0",
605605
"E ZeroDivisionError: division by zero",
606606
]
607607

@@ -638,7 +638,7 @@ def test_repr_source_failing_fullsource(self, monkeypatch) -> None:
638638
pr = FormattedExcinfo()
639639

640640
try:
641-
1 / 0
641+
_ = 1 / 0
642642
except ZeroDivisionError:
643643
excinfo = ExceptionInfo.from_current()
644644

@@ -1582,7 +1582,7 @@ def __getattr__(self, attr):
15821582
return getattr(self, "_" + attr)
15831583

15841584
with pytest.raises(RuntimeError) as excinfo:
1585-
RecursionDepthError().trigger
1585+
_ = RecursionDepthError().trigger
15861586
assert "maximum recursion" in str(excinfo.getrepr())
15871587

15881588

testing/python/raises.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def __class__(self):
280280

281281
def test_raises_context_manager_with_kwargs(self):
282282
with pytest.raises(TypeError) as excinfo:
283-
with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload]
283+
with pytest.raises(OSError, foo="bar"): # type: ignore[call-overload]
284284
pass
285285
assert "Unexpected keyword arguments" in str(excinfo.value)
286286

testing/test_assertion.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# mypy: allow-untyped-defs
2-
import collections
32
import sys
43
import textwrap
54
from typing import Any
65
from typing import List
76
from typing import MutableSequence
7+
from typing import NamedTuple
88
from typing import Optional
99

1010
import attr
@@ -1179,7 +1179,9 @@ def __eq__(self, other): # pragma: no cover
11791179

11801180
class TestAssert_reprcompare_namedtuple:
11811181
def test_namedtuple(self) -> None:
1182-
NT = collections.namedtuple("NT", ["a", "b"])
1182+
class NT(NamedTuple):
1183+
a: Any
1184+
b: Any
11831185

11841186
left = NT(1, "b")
11851187
right = NT(1, "c")
@@ -1200,8 +1202,13 @@ def test_namedtuple(self) -> None:
12001202
]
12011203

12021204
def test_comparing_two_different_namedtuple(self) -> None:
1203-
NT1 = collections.namedtuple("NT1", ["a", "b"])
1204-
NT2 = collections.namedtuple("NT2", ["a", "b"])
1205+
class NT1(NamedTuple):
1206+
a: Any
1207+
b: Any
1208+
1209+
class NT2(NamedTuple):
1210+
a: Any
1211+
b: Any
12051212

12061213
left = NT1(1, "b")
12071214
right = NT2(2, "b")

testing/test_compat.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,17 @@ def raise_fail_outcome(self):
169169

170170
def test_helper_failures() -> None:
171171
helper = ErrorsHelper()
172-
with pytest.raises(Exception):
173-
helper.raise_exception
172+
with pytest.raises(Exception): # noqa: B017
173+
_ = helper.raise_exception
174174
with pytest.raises(OutcomeException):
175-
helper.raise_fail_outcome
175+
_ = helper.raise_fail_outcome
176176

177177

178178
def test_safe_getattr() -> None:
179179
helper = ErrorsHelper()
180180
assert safe_getattr(helper, "raise_exception", "default") == "default"
181181
assert safe_getattr(helper, "raise_fail_outcome", "default") == "default"
182-
with pytest.raises(BaseException):
182+
with pytest.raises(BaseException): # noqa: B017
183183
assert safe_getattr(helper, "raise_baseexception", "default")
184184

185185

testing/test_legacypath.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def test_session_scoped_unavailable_attributes(self, session_request):
108108
AttributeError,
109109
match="path not available in session-scoped context",
110110
):
111-
session_request.fspath
111+
_ = session_request.fspath
112112

113113

114114
@pytest.mark.parametrize("config_type", ["ini", "pyproject"])

testing/test_mark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class SomeClass:
4242
def test_pytest_mark_name_starts_with_underscore(self) -> None:
4343
mark = MarkGenerator(_ispytest=True)
4444
with pytest.raises(AttributeError):
45-
mark._some_name
45+
_ = mark._some_name
4646

4747

4848
def test_marked_class_run_twice(pytester: Pytester) -> None:

testing/test_recwarn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def test_deprecated_call_specificity(self) -> None:
228228
for warning in other_warnings:
229229

230230
def f():
231-
warnings.warn(warning("hi"))
231+
warnings.warn(warning("hi")) # noqa: B023
232232

233233
with pytest.warns(warning):
234234
with pytest.raises(pytest.fail.Exception):

testing/test_terminal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# mypy: allow-untyped-defs
22
"""Terminal reporting of the full testing process."""
3-
import collections
43
from io import StringIO
54
import os
65
from pathlib import Path
@@ -10,6 +9,7 @@
109
from typing import cast
1110
from typing import Dict
1211
from typing import List
12+
from typing import NamedTuple
1313
from typing import Tuple
1414

1515
import pluggy
@@ -34,7 +34,9 @@
3434
import pytest
3535

3636

37-
DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"])
37+
class DistInfo(NamedTuple):
38+
project_name: str
39+
version: int
3840

3941

4042
TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"})

testing/test_warnings.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
def pyfile_with_warnings(pytester: Pytester, request: FixtureRequest) -> str:
1919
"""Create a test file which calls a function in a module which generates warnings."""
2020
pytester.syspathinsert()
21-
test_name = request.function.__name__
22-
module_name = test_name.lstrip("test_") + "_module"
21+
module_name = request.function.__name__[len("test_") :] + "_module"
2322
test_file = pytester.makepyfile(
2423
f"""
2524
import {module_name}

0 commit comments

Comments
 (0)