Skip to content

Commit 366fd72

Browse files
[raise-missing-from] Clearer message and example in the documentation (#6576)
* [raise-missing-from] Clearer message and example in the documentation Co-authored-by: cool-RR <[email protected]> Co-authored-by: Daniël van Noord <[email protected]> Refs #5953 Closes #3707
1 parent b986bcb commit 366fd72

File tree

6 files changed

+77
-31
lines changed

6 files changed

+77
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
try:
2+
1 / 0
3+
except ZeroDivisionError as e:
4+
raise ValueError("Rectangle Area cannot be zero") # [raise-missing-from]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
try:
2+
1 / 0
3+
except ZeroDivisionError as e:
4+
raise ValueError("Rectangle Area cannot be zero") from e
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `PEP 3132 <https://peps.python.org/pep-3132/>`_

pylint/checkers/exceptions.py

+31-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from pylint import checkers
1717
from pylint.checkers import utils
18+
from pylint.interfaces import HIGH
1819
from pylint.typing import MessageDefinitionTuple
1920

2021
if TYPE_CHECKING:
@@ -131,13 +132,13 @@ def _is_raising(body: list) -> bool:
131132
"try-except-raise block!",
132133
),
133134
"W0707": (
134-
"Consider explicitly re-raising using the 'from' keyword",
135+
"Consider explicitly re-raising using %s'%s from %s'",
135136
"raise-missing-from",
136-
"Python 3's exception chaining means it shows the traceback of the "
137-
"current exception, but also the original exception. Not using `raise "
138-
"from` makes the traceback inaccurate, because the message implies "
139-
"there is a bug in the exception-handling code itself, which is a "
140-
"separate situation than wrapping an exception.",
137+
"Python's exception chaining shows the traceback of the current exception, "
138+
"but also of the original exception. When you raise a new exception after "
139+
"another exception was caught it's likely that the second exception is a "
140+
"friendly re-wrapping of the first exception. In such cases `raise from` "
141+
"provides a better link between the two tracebacks in the final error.",
141142
),
142143
"W0711": (
143144
'Exception to catch is the result of a binary "%s" operation',
@@ -334,16 +335,33 @@ def _check_raise_missing_from(self, node: nodes.Raise) -> None:
334335
if containing_except_node.name is None:
335336
# The `except` doesn't have an `as exception:` part, meaning there's no way that
336337
# the `raise` is raising the same exception.
337-
self.add_message("raise-missing-from", node=node)
338-
elif isinstance(node.exc, nodes.Call) and isinstance(node.exc.func, nodes.Name):
339-
# We have a `raise SomeException(whatever)`.
340-
self.add_message("raise-missing-from", node=node)
338+
class_of_old_error = "Exception"
339+
if isinstance(containing_except_node.type, (nodes.Name, nodes.Tuple)):
340+
# 'except ZeroDivisionError' or 'except (ZeroDivisionError, ValueError)'
341+
class_of_old_error = containing_except_node.type.as_string()
342+
self.add_message(
343+
"raise-missing-from",
344+
node=node,
345+
args=(
346+
f"'except {class_of_old_error} as exc' and ",
347+
node.as_string(),
348+
"exc",
349+
),
350+
confidence=HIGH,
351+
)
341352
elif (
342-
isinstance(node.exc, nodes.Name)
353+
isinstance(node.exc, nodes.Call)
354+
and isinstance(node.exc.func, nodes.Name)
355+
or isinstance(node.exc, nodes.Name)
343356
and node.exc.name != containing_except_node.name.name
344357
):
345-
# We have a `raise SomeException`.
346-
self.add_message("raise-missing-from", node=node)
358+
# We have a `raise SomeException(whatever)` or a `raise SomeException`
359+
self.add_message(
360+
"raise-missing-from",
361+
node=node,
362+
args=("", node.as_string(), containing_except_node.name.name),
363+
confidence=HIGH,
364+
)
347365

348366
def _check_catching_non_exception(self, handler, exc, part):
349367
if isinstance(exc, nodes.Tuple):

tests/functional/r/raise_missing_from.py

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# pylint:disable=missing-docstring, unreachable, using-constant-test, invalid-name, bare-except
22
# pylint:disable=try-except-raise, undefined-variable, too-few-public-methods, superfluous-parens
33

4-
################################################################################
5-
# Positives:
4+
try:
5+
1 / 0
6+
except:
7+
raise ValueError('Invalid integer') # [raise-missing-from]
68

79
try:
810
1 / 0
@@ -13,9 +15,8 @@
1315
try:
1416
1 / 0
1517
except ZeroDivisionError:
16-
# Our algorithm doesn't have to be careful about the complicated expression below, because
17-
# the exception above wasn't bound to a name.
18-
# +1: [raise-missing-from]
18+
# Our algorithm doesn't have to be careful about the complicated expression below,
19+
# because the exception above wasn't bound to a name. # +1: [raise-missing-from]
1920
raise (foo + bar).baz
2021

2122
try:
@@ -59,8 +60,26 @@
5960
raise KeyError(whatever, whatever=whatever)
6061

6162

62-
################################################################################
63-
# Negatives (Same cases as above, except with `from`):
63+
try:
64+
1 / 0
65+
except (ZeroDivisionError, ValueError, KeyError):
66+
if 1:
67+
if 2:
68+
pass
69+
else:
70+
with whatever:
71+
# +1: [raise-missing-from]
72+
raise KeyError(whatever, whatever=whatever)
73+
else:
74+
# +1: [raise-missing-from]
75+
raise KeyError(whatever, overever=12)
76+
77+
try:
78+
# Taken from https://github.com/python/cpython/blob/3.10/Lib/plistlib.py#L459
79+
pass
80+
except (OSError, IndexError, struct.error, OverflowError,
81+
ValueError):
82+
raise InvalidFileException() # [raise-missing-from]
6483

6584
try:
6685
1 / 0
@@ -107,10 +126,6 @@
107126
except ZeroDivisionError as e:
108127
raise KeyError(whatever, whatever=whatever) from foo
109128

110-
111-
################################################################################
112-
# Other negatives:
113-
114129
try:
115130
1 / 0
116131
except ZeroDivisionError:
+11-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
raise-missing-from:11:4:11:18::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
2-
raise-missing-from:19:4:19:25::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
3-
raise-missing-from:25:4:25:18::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
4-
raise-missing-from:31:4:31:18::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
5-
raise-missing-from:45:20:45:34::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
6-
raise-missing-from:53:4:53:20::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
7-
raise-missing-from:59:4:59:47::Consider explicitly re-raising using the 'from' keyword:UNDEFINED
1+
raise-missing-from:7:4:7:39::Consider explicitly re-raising using 'except Exception as exc' and 'raise ValueError('Invalid integer') from exc':HIGH
2+
raise-missing-from:13:4:13:18::Consider explicitly re-raising using 'except ZeroDivisionError as exc' and 'raise KeyError from exc':HIGH
3+
raise-missing-from:20:4:20:25::Consider explicitly re-raising using 'except ZeroDivisionError as exc' and 'raise (foo + bar).baz from exc':HIGH
4+
raise-missing-from:26:4:26:18::Consider explicitly re-raising using 'raise KeyError from e':HIGH
5+
raise-missing-from:32:4:32:18::Consider explicitly re-raising using 'raise KeyError from e':HIGH
6+
raise-missing-from:46:20:46:34::Consider explicitly re-raising using 'raise KeyError from e':HIGH
7+
raise-missing-from:54:4:54:20::Consider explicitly re-raising using 'raise KeyError() from e':HIGH
8+
raise-missing-from:60:4:60:47::Consider explicitly re-raising using 'raise KeyError(whatever, whatever=whatever) from e':HIGH
9+
raise-missing-from:72:16:72:59::Consider explicitly re-raising using 'except (ZeroDivisionError, ValueError, KeyError) as exc' and 'raise KeyError(whatever, whatever=whatever) from exc':HIGH
10+
raise-missing-from:75:8:75:45::Consider explicitly re-raising using 'except (ZeroDivisionError, ValueError, KeyError) as exc' and 'raise KeyError(whatever, overever=12) from exc':HIGH
11+
raise-missing-from:82:4:82:32::Consider explicitly re-raising using 'except (OSError, IndexError, struct.error, OverflowError, ValueError) as exc' and 'raise InvalidFileException() from exc':HIGH

0 commit comments

Comments
 (0)