[flake8-pytest-style] Avoid false positive for legacy form of pytest.raises (PT011) (#17231)

This fix closes #17026 

## Summary

The check for the `PytestRaisesTooBroad` rule is now skipped if there is
a second positional argument present, which means `pytest.raises` is
used as a function.

## Test Plan

Tested on the example from the issue, which now passes the check.
```Python3
pytest.raises(Exception, func, *func_args, **func_kwargs).match("error message")
```

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Denys Kyslytsyn 2025-04-08 16:24:47 +09:00 committed by GitHub
parent 34e06f2d17
commit 97dd6d120c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 131 additions and 111 deletions

View file

@ -14,6 +14,13 @@ def test_ok_different_error_from_config():
raise ZeroDivisionError("Can't divide by 0")
def test_ok_legacy_form():
def func():
raise ValueError("Can't divide by 0")
pytest.raises(ValueError, func).match("Can't divide by 0")
def test_error_no_argument_given():
with pytest.raises(ValueError):
raise ValueError("Can't divide 1 by 0")

View file

@ -173,7 +173,11 @@ const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
pub(crate) fn raises_call(checker: &Checker, call: &ast::ExprCall) {
if is_pytest_raises(&call.func, checker.semantic()) {
if checker.enabled(Rule::PytestRaisesWithoutException) {
if call.arguments.is_empty() {
if call
.arguments
.find_argument("expected_exception", 0)
.is_none()
{
checker.report_diagnostic(Diagnostic::new(
PytestRaisesWithoutException,
call.func.range(),
@ -182,13 +186,22 @@ pub(crate) fn raises_call(checker: &Checker, call: &ast::ExprCall) {
}
if checker.enabled(Rule::PytestRaisesTooBroad) {
if let Some(exception) = call.arguments.find_argument_value("expected_exception", 0) {
if call
.arguments
.find_keyword("match")
.is_none_or(|k| is_empty_or_null_string(&k.value))
// Pytest.raises has two overloads
// ```py
// with raises(expected_exception: type[E] | tuple[type[E], ...], *, match: str | Pattern[str] | None = ...) → RaisesContext[E] as excinfo
// with raises(expected_exception: type[E] | tuple[type[E], ...], func: Callable[[...], Any], *args: Any, **kwargs: Any) → ExceptionInfo[E] as excinfo
// ```
// Don't raise this diagnostic if the call matches the second overload (has a second positional argument or an argument named `func`)
if call.arguments.find_argument("func", 1).is_none() {
if let Some(exception) = call.arguments.find_argument_value("expected_exception", 0)
{
exception_needs_match(checker, exception);
if call
.arguments
.find_keyword("match")
.is_none_or(|k| is_empty_or_null_string(&k.value))
{
exception_needs_match(checker, exception);
}
}
}
}

View file

@ -1,54 +1,54 @@
---
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
---
PT011.py:18:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:25:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
17 | def test_error_no_argument_given():
18 | with pytest.raises(ValueError):
24 | def test_error_no_argument_given():
25 | with pytest.raises(ValueError):
| ^^^^^^^^^^ PT011
19 | raise ValueError("Can't divide 1 by 0")
26 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:21:43: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:28:43: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
19 | raise ValueError("Can't divide 1 by 0")
20 |
21 | with pytest.raises(expected_exception=ValueError):
26 | raise ValueError("Can't divide 1 by 0")
27 |
28 | with pytest.raises(expected_exception=ValueError):
| ^^^^^^^^^^ PT011
22 | raise ValueError("Can't divide 1 by 0")
29 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:24:24: PT011 `pytest.raises(socket.error)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:31:24: PT011 `pytest.raises(socket.error)` is too broad, set the `match` parameter or use a more specific exception
|
22 | raise ValueError("Can't divide 1 by 0")
23 |
24 | with pytest.raises(socket.error):
29 | raise ValueError("Can't divide 1 by 0")
30 |
31 | with pytest.raises(socket.error):
| ^^^^^^^^^^^^ PT011
25 | raise ValueError("Can't divide 1 by 0")
32 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:35:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:42:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
34 | def test_error_match_is_empty():
35 | with pytest.raises(ValueError, match=None):
41 | def test_error_match_is_empty():
42 | with pytest.raises(ValueError, match=None):
| ^^^^^^^^^^ PT011
36 | raise ValueError("Can't divide 1 by 0")
43 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:38:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:45:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
36 | raise ValueError("Can't divide 1 by 0")
37 |
38 | with pytest.raises(ValueError, match=""):
43 | raise ValueError("Can't divide 1 by 0")
44 |
45 | with pytest.raises(ValueError, match=""):
| ^^^^^^^^^^ PT011
39 | raise ValueError("Can't divide 1 by 0")
46 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:41:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:48:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
39 | raise ValueError("Can't divide 1 by 0")
40 |
41 | with pytest.raises(ValueError, match=f""):
46 | raise ValueError("Can't divide 1 by 0")
47 |
48 | with pytest.raises(ValueError, match=f""):
| ^^^^^^^^^^ PT011
42 | raise ValueError("Can't divide 1 by 0")
49 | raise ValueError("Can't divide 1 by 0")
|

View file

@ -9,54 +9,54 @@ PT011.py:13:24: PT011 `pytest.raises(ZeroDivisionError)` is too broad, set the `
14 | raise ZeroDivisionError("Can't divide by 0")
|
PT011.py:18:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:25:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
17 | def test_error_no_argument_given():
18 | with pytest.raises(ValueError):
24 | def test_error_no_argument_given():
25 | with pytest.raises(ValueError):
| ^^^^^^^^^^ PT011
19 | raise ValueError("Can't divide 1 by 0")
26 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:21:43: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:28:43: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
19 | raise ValueError("Can't divide 1 by 0")
20 |
21 | with pytest.raises(expected_exception=ValueError):
26 | raise ValueError("Can't divide 1 by 0")
27 |
28 | with pytest.raises(expected_exception=ValueError):
| ^^^^^^^^^^ PT011
22 | raise ValueError("Can't divide 1 by 0")
29 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:24:24: PT011 `pytest.raises(socket.error)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:31:24: PT011 `pytest.raises(socket.error)` is too broad, set the `match` parameter or use a more specific exception
|
22 | raise ValueError("Can't divide 1 by 0")
23 |
24 | with pytest.raises(socket.error):
29 | raise ValueError("Can't divide 1 by 0")
30 |
31 | with pytest.raises(socket.error):
| ^^^^^^^^^^^^ PT011
25 | raise ValueError("Can't divide 1 by 0")
32 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:35:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:42:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
34 | def test_error_match_is_empty():
35 | with pytest.raises(ValueError, match=None):
41 | def test_error_match_is_empty():
42 | with pytest.raises(ValueError, match=None):
| ^^^^^^^^^^ PT011
36 | raise ValueError("Can't divide 1 by 0")
43 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:38:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:45:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
36 | raise ValueError("Can't divide 1 by 0")
37 |
38 | with pytest.raises(ValueError, match=""):
43 | raise ValueError("Can't divide 1 by 0")
44 |
45 | with pytest.raises(ValueError, match=""):
| ^^^^^^^^^^ PT011
39 | raise ValueError("Can't divide 1 by 0")
46 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:41:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:48:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
39 | raise ValueError("Can't divide 1 by 0")
40 |
41 | with pytest.raises(ValueError, match=f""):
46 | raise ValueError("Can't divide 1 by 0")
47 |
48 | with pytest.raises(ValueError, match=f""):
| ^^^^^^^^^^ PT011
42 | raise ValueError("Can't divide 1 by 0")
49 | raise ValueError("Can't divide 1 by 0")
|

View file

@ -9,72 +9,72 @@ PT011.py:13:24: PT011 `pytest.raises(ZeroDivisionError)` is too broad, set the `
14 | raise ZeroDivisionError("Can't divide by 0")
|
PT011.py:18:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:25:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
17 | def test_error_no_argument_given():
18 | with pytest.raises(ValueError):
24 | def test_error_no_argument_given():
25 | with pytest.raises(ValueError):
| ^^^^^^^^^^ PT011
19 | raise ValueError("Can't divide 1 by 0")
26 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:21:43: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:28:43: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
19 | raise ValueError("Can't divide 1 by 0")
20 |
21 | with pytest.raises(expected_exception=ValueError):
26 | raise ValueError("Can't divide 1 by 0")
27 |
28 | with pytest.raises(expected_exception=ValueError):
| ^^^^^^^^^^ PT011
22 | raise ValueError("Can't divide 1 by 0")
29 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:24:24: PT011 `pytest.raises(socket.error)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:31:24: PT011 `pytest.raises(socket.error)` is too broad, set the `match` parameter or use a more specific exception
|
22 | raise ValueError("Can't divide 1 by 0")
23 |
24 | with pytest.raises(socket.error):
29 | raise ValueError("Can't divide 1 by 0")
30 |
31 | with pytest.raises(socket.error):
| ^^^^^^^^^^^^ PT011
25 | raise ValueError("Can't divide 1 by 0")
32 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:27:24: PT011 `pytest.raises(pickle.PicklingError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:34:24: PT011 `pytest.raises(pickle.PicklingError)` is too broad, set the `match` parameter or use a more specific exception
|
25 | raise ValueError("Can't divide 1 by 0")
26 |
27 | with pytest.raises(PicklingError):
32 | raise ValueError("Can't divide 1 by 0")
33 |
34 | with pytest.raises(PicklingError):
| ^^^^^^^^^^^^^ PT011
28 | raise PicklingError("Can't pickle")
35 | raise PicklingError("Can't pickle")
|
PT011.py:30:24: PT011 `pytest.raises(pickle.UnpicklingError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:37:24: PT011 `pytest.raises(pickle.UnpicklingError)` is too broad, set the `match` parameter or use a more specific exception
|
28 | raise PicklingError("Can't pickle")
29 |
30 | with pytest.raises(UnpicklingError):
35 | raise PicklingError("Can't pickle")
36 |
37 | with pytest.raises(UnpicklingError):
| ^^^^^^^^^^^^^^^ PT011
31 | raise UnpicklingError("Can't unpickle")
38 | raise UnpicklingError("Can't unpickle")
|
PT011.py:35:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:42:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
34 | def test_error_match_is_empty():
35 | with pytest.raises(ValueError, match=None):
41 | def test_error_match_is_empty():
42 | with pytest.raises(ValueError, match=None):
| ^^^^^^^^^^ PT011
36 | raise ValueError("Can't divide 1 by 0")
43 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:38:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:45:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
36 | raise ValueError("Can't divide 1 by 0")
37 |
38 | with pytest.raises(ValueError, match=""):
43 | raise ValueError("Can't divide 1 by 0")
44 |
45 | with pytest.raises(ValueError, match=""):
| ^^^^^^^^^^ PT011
39 | raise ValueError("Can't divide 1 by 0")
46 | raise ValueError("Can't divide 1 by 0")
|
PT011.py:41:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:48:24: PT011 `pytest.raises(ValueError)` is too broad, set the `match` parameter or use a more specific exception
|
39 | raise ValueError("Can't divide 1 by 0")
40 |
41 | with pytest.raises(ValueError, match=f""):
46 | raise ValueError("Can't divide 1 by 0")
47 |
48 | with pytest.raises(ValueError, match=f""):
| ^^^^^^^^^^ PT011
42 | raise ValueError("Can't divide 1 by 0")
49 | raise ValueError("Can't divide 1 by 0")
|

View file

@ -1,20 +1,20 @@
---
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
---
PT011.py:27:24: PT011 `pytest.raises(pickle.PicklingError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:34:24: PT011 `pytest.raises(pickle.PicklingError)` is too broad, set the `match` parameter or use a more specific exception
|
25 | raise ValueError("Can't divide 1 by 0")
26 |
27 | with pytest.raises(PicklingError):
32 | raise ValueError("Can't divide 1 by 0")
33 |
34 | with pytest.raises(PicklingError):
| ^^^^^^^^^^^^^ PT011
28 | raise PicklingError("Can't pickle")
35 | raise PicklingError("Can't pickle")
|
PT011.py:30:24: PT011 `pytest.raises(pickle.UnpicklingError)` is too broad, set the `match` parameter or use a more specific exception
PT011.py:37:24: PT011 `pytest.raises(pickle.UnpicklingError)` is too broad, set the `match` parameter or use a more specific exception
|
28 | raise PicklingError("Can't pickle")
29 |
30 | with pytest.raises(UnpicklingError):
35 | raise PicklingError("Can't pickle")
36 |
37 | with pytest.raises(UnpicklingError):
| ^^^^^^^^^^^^^^^ PT011
31 | raise UnpicklingError("Can't unpickle")
38 | raise UnpicklingError("Can't unpickle")
|