From 43b4e0f39bbb159bd89181b975c2b06fe36b9839 Mon Sep 17 00:00:00 2001 From: Cooper Ry Lees Date: Sun, 9 Nov 2025 14:12:59 -0600 Subject: [PATCH] Move B042 to B913 to not be enabled by deefault due to false positives - Some false positives we need to iron out - Until someone wants to do that, let's not enable this check by default by moving to a B9XX - I've reserved B042 incase we want to ever move it back Test: - Move tests and see them stay passing as is ``` python3 -m venv --upgrade-dep /tmp/tf /tmp/tf/bin/pip install tox /tmp/tf/bin/tox -e py314 ... ============================================================================================= test session starts ============================================================================================== platform darwin -- Python 3.14.0, pytest-9.0.0, pluggy-1.6.0 cachedir: .tox/py314/.pytest_cache rootdir: /Users/cooper/repos/flake8-bugbear configfile: pyproject.toml plugins: hypothesis-6.147.0 collected 76 items tests/test_bugbear.py ............................................................................ [100%] ============================================================================================= 76 passed in 10.49s ============================================================================================== py314: commands[1]> coverage report -m Name Stmts Miss Cover Missing ------------------------------------------ bugbear.py 1116 20 98% 141-142, 152-163, 294, 308, 416, 758, 1275, 1454, 1471, 1605, 1713, 1792, 1910-1911, 2047-2048 ------------------------------------------ TOTAL 1116 20 98% py314: OK (18.98=setup[7.08]+cmd[11.76,0.15] seconds) congratulations :) (19.01 seconds) ``` --- README.rst | 14 ++++++++++++-- bugbear.py | 27 +++++++++++++------------- tests/eval_files/{b042.py => b913.py} | 28 +++++++++++++-------------- 3 files changed, 40 insertions(+), 29 deletions(-) rename tests/eval_files/{b042.py => b913.py} (69%) diff --git a/README.rst b/README.rst index ba1b1a8..c8c98b1 100644 --- a/README.rst +++ b/README.rst @@ -288,7 +288,7 @@ second usage. Save the result to a list if the result is needed multiple times. **B041**: Repeated key-value pair in dictionary literal. Only emits errors when the key's value is *also* the same, being the opposite of the pyflakes like check. -**B042**: Remember to call super().__init__() in custom exceptions initalizer. +**B042**: Reserved for future use. This check has been moved to B913. Opinionated warnings ~~~~~~~~~~~~~~~~~~~~ @@ -372,6 +372,11 @@ The ``strict=`` argument was added in Python 3.13, so don't enable this flag for **B912**: ``map()`` without an explicit `strict=` parameter set. ``strict=True`` causes the resulting iterator to raise a ``ValueError`` if the arguments are exhausted at differing lengths. +.. _B913: + +**B913**: Exception class with ``__init__`` should pass all args to ``super().__init__()`` in order to work +with ``copy.copy()``. It should also not take any kwargs. + .. _B950: **B950**: Line too long. This is a pragmatic equivalent of @@ -479,10 +484,15 @@ MIT Change Log ---------- +UNRELEASED +~~~~~~~~~~ + +* B913: Move B042 to be optional by default. It checks for reminding to call super().__init__ in custom exceptions + 25.10.21 ~~~~~~~~~~ -* B042: New check for reminding to call super().__init__ in custom exceptions +* B042: New check for reminding to call super().__init__ in custom exceptions (moved to B913, B042 reserved for future use) * B028: Skip if skip_file_prefixes is used (#503) * B912: New check for `map()` without an explicit `strict=` parameter. (#516) * Add python3.14 Support / CI diff --git a/bugbear.py b/bugbear.py index 1a3af60..f110e3f 100644 --- a/bugbear.py +++ b/bugbear.py @@ -614,7 +614,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: self.check_for_b903(node) self.check_for_b021(node) self.check_for_b024_and_b027(node) - self.check_for_b042(node) + self.check_for_b913(node) self.generic_visit(node) def visit_Try(self, node) -> None: @@ -1733,7 +1733,7 @@ def check(num_args: int, param_name: str) -> None: elif func.attr == "split": check(2, "maxsplit") - def check_for_b042(self, node: ast.ClassDef) -> None: # noqa: C901 # too-complex + def check_for_b913(self, node: ast.ClassDef) -> None: # noqa: C901 # too-complex def is_exception(s: str): for ending in "Exception", "Error", "Warning", "ExceptionGroup": if s.endswith(ending): @@ -1755,7 +1755,7 @@ def is_exception(s: str): continue if fun.args.kwonlyargs or fun.args.kwarg: # kwargs cannot be passed to super().__init__() - self.add_error("B042", fun) + self.add_error("B913", fun) return # -1 to exclude the `self` argument expected_arg_count = ( @@ -1782,18 +1782,18 @@ def is_exception(s: str): and b.value.func.attr == "__init__" ): if len(b.value.args) != expected_arg_count: - self.add_error("B042", fun) + self.add_error("B913", fun) elif fun.args.vararg: for arg in b.value.args: if isinstance(arg, ast.Starred): return else: # no Starred argument despite vararg - self.add_error("B042", fun) + self.add_error("B913", fun) return else: # no super().__init__() found - self.add_error("B042", fun) + self.add_error("B913", fun) return # no `def __init__` found, which is fine @@ -2408,13 +2408,6 @@ def __call__(self, lineno: int, col: int, vars: tuple[object, ...] = ()) -> erro message="B040 Exception with added note not used. Did you forget to raise it?" ), "B041": Error(message=("B041 Repeated key-value pair in dictionary literal.")), - "B042": Error( - message=( - "B042 Exception class with `__init__` should pass all args to " - "`super().__init__()` in order to work with `copy.copy()`. " - "It should also not take any kwargs." - ) - ), # Warnings disabled by default. "B901": Error( message=( @@ -2477,6 +2470,13 @@ def __call__(self, lineno: int, col: int, vars: tuple[object, ...] = ()) -> erro message="B911 `itertools.batched()` without an explicit `strict=` parameter." ), "B912": Error(message="B912 `map()` without an explicit `strict=` parameter."), + "B913": Error( + message=( + "B913 Exception class with `__init__` should pass all args to " + "`super().__init__()` in order to work with `copy.copy()`. " + "It should also not take any kwargs." + ) + ), "B950": Error(message="B950 line too long ({} > {} characters)"), } @@ -2493,5 +2493,6 @@ def __call__(self, lineno: int, col: int, vars: tuple[object, ...] = ()) -> erro "B910", "B911", "B912", + "B913", "B950", ] diff --git a/tests/eval_files/b042.py b/tests/eval_files/b913.py similarity index 69% rename from tests/eval_files/b042.py rename to tests/eval_files/b913.py index d1efcc3..d93eb1b 100644 --- a/tests/eval_files/b042.py +++ b/tests/eval_files/b913.py @@ -9,17 +9,17 @@ def __init__(self, foo, bar=3): class MyError_args_bad(Exception): - def __init__(self, foo, bar=3): # B042: 4 + def __init__(self, foo, bar=3): # B913: 4 super().__init__(foo) class MyError_kwonlyargs(Exception): - def __init__(self, *, foo): # B042: 4 + def __init__(self, *, foo): # B913: 4 super().__init__(foo=foo) class MyError_kwargs(Exception): - def __init__(self, **kwargs): # B042: 4 + def __init__(self, **kwargs): # B913: 4 super().__init__(**kwargs) @@ -29,16 +29,16 @@ def __init__(self, *args): # safe class MyError_vararg_bad(Exception): - def __init__(self, *args): # B042: 4 + def __init__(self, *args): # B913: 4 super().__init__() class MyError_args_nothing(Exception): - def __init__(self, *args): ... # B042: 4 + def __init__(self, *args): ... # B913: 4 class MyError_nested_init(Exception): - def __init__(self, x): # B042: 4 + def __init__(self, x): # B913: 4 if True: super().__init__(x) @@ -50,22 +50,22 @@ def __init__(self, x, /, y): # if it inherits from a class whose name ends with, any of # 'Error', 'Exception', 'ExceptionGroup', 'Warning', 'ExceptionGroup' class Anything(ValueError): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class Anything2(BaseException): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class Anything3(ExceptionGroup): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class Anything4(UserWarning): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class MyError(Anything): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class MyException(Anything): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class MyExceptionGroup(Anything): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class MyWarning(Anything): - def __init__(self, x): ... # B042: 4 + def __init__(self, x): ... # B913: 4 class ExceptionHandler(Anything): def __init__(self, x): ... # safe