Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -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.
Comment on lines +377 to +378
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make more sense on how to use it? Welcome all improvements here ...


.. _B950:

**B950**: Line too long. This is a pragmatic equivalent of
Expand Down Expand Up @@ -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
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing period at the end of the changelog entry. Should be 'It checks for reminding to call super().init() in custom exceptions.'

Suggested change
* B913: Move B042 to be optional by default. It checks for reminding to call super().__init__ in custom exceptions
* B913: Move B042 to be optional by default. It checks for reminding to call super().__init__ in custom exceptions.

Copilot uses AI. Check for mistakes.

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
Expand Down
27 changes: 14 additions & 13 deletions bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand All @@ -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 = (
Expand All @@ -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

Expand Down Expand Up @@ -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=(
Expand Down Expand Up @@ -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."
Comment on lines +2475 to +2477
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to improve here too

)
),
"B950": Error(message="B950 line too long ({} > {} characters)"),
}

Expand All @@ -2493,5 +2493,6 @@ def __call__(self, lineno: int, col: int, vars: tuple[object, ...] = ()) -> erro
"B910",
"B911",
"B912",
"B913",
"B950",
]
28 changes: 14 additions & 14 deletions tests/eval_files/b042.py → tests/eval_files/b913.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -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)

Expand All @@ -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
Expand Down
Loading