diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S113.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S113.py index 75cb5a7ff4f6c..0a13833982b61 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S113.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S113.py @@ -1,23 +1,71 @@ +import httpx import requests +# OK +requests.get('https://gmail.com', timeout=5) +requests.post('https://gmail.com', timeout=5) +requests.put('https://gmail.com', timeout=5) +requests.delete('https://gmail.com', timeout=5) +requests.patch('https://gmail.com', timeout=5) +requests.options('https://gmail.com', timeout=5) +requests.head('https://gmail.com', timeout=5) + +httpx.get('https://gmail.com', timeout=5) +httpx.post('https://gmail.com', timeout=5) +httpx.put('https://gmail.com', timeout=5) +httpx.delete('https://gmail.com', timeout=5) +httpx.patch('https://gmail.com', timeout=5) +httpx.options('https://gmail.com', timeout=5) +httpx.head('https://gmail.com', timeout=5) +httpx.Client(timeout=5) +httpx.AsyncClient(timeout=5) +with httpx.Client(timeout=5) as client: + client.get('https://gmail.com') +async def foo(): + async with httpx.AsyncClient(timeout=5) as client: + await client.get('https://gmail.com') + +# Errors requests.get('https://gmail.com') requests.get('https://gmail.com', timeout=None) -requests.get('https://gmail.com', timeout=5) requests.post('https://gmail.com') requests.post('https://gmail.com', timeout=None) -requests.post('https://gmail.com', timeout=5) requests.put('https://gmail.com') requests.put('https://gmail.com', timeout=None) -requests.put('https://gmail.com', timeout=5) requests.delete('https://gmail.com') requests.delete('https://gmail.com', timeout=None) -requests.delete('https://gmail.com', timeout=5) requests.patch('https://gmail.com') requests.patch('https://gmail.com', timeout=None) -requests.patch('https://gmail.com', timeout=5) requests.options('https://gmail.com') requests.options('https://gmail.com', timeout=None) -requests.options('https://gmail.com', timeout=5) requests.head('https://gmail.com') requests.head('https://gmail.com', timeout=None) -requests.head('https://gmail.com', timeout=5) + +httpx.get('https://gmail.com') +httpx.get('https://gmail.com', timeout=None) +httpx.post('https://gmail.com') +httpx.post('https://gmail.com', timeout=None) +httpx.put('https://gmail.com') +httpx.put('https://gmail.com', timeout=None) +httpx.delete('https://gmail.com') +httpx.delete('https://gmail.com', timeout=None) +httpx.patch('https://gmail.com') +httpx.patch('https://gmail.com', timeout=None) +httpx.options('https://gmail.com') +httpx.options('https://gmail.com', timeout=None) +httpx.head('https://gmail.com') +httpx.head('https://gmail.com', timeout=None) +httpx.Client() +httpx.Client(timeout=None) +httpx.AsyncClient() +httpx.AsyncClient(timeout=None) +with httpx.Client() as client: + client.get('https://gmail.com') +with httpx.Client(timeout=None) as client: + client.get('https://gmail.com') +async def bar(): + async with httpx.AsyncClient() as client: + await client.get('https://gmail.com') +async def baz(): + async with httpx.AsyncClient(timeout=None) as client: + await client.get('https://gmail.com') diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs index 3497e681b6087..94df25cec8ecb 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/request_without_timeout.rs @@ -7,8 +7,8 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for uses of the Python `requests` module that omit the `timeout` -/// parameter. +/// Checks for uses of the Python `requests` or `httpx` module that omit the +/// `timeout` parameter. /// /// ## Why is this bad? /// The `timeout` parameter is used to set the maximum time to wait for a @@ -31,48 +31,50 @@ use crate::checkers::ast::Checker; /// /// ## References /// - [Requests documentation: Timeouts](https://requests.readthedocs.io/en/latest/user/advanced/#timeouts) +/// - [httpx documentation: Timeouts](https://www.python-httpx.org/advanced/timeouts/) #[violation] pub struct RequestWithoutTimeout { implicit: bool, + module: String, } impl Violation for RequestWithoutTimeout { #[derive_message_formats] fn message(&self) -> String { - let RequestWithoutTimeout { implicit } = self; + let RequestWithoutTimeout { implicit, module } = self; if *implicit { - format!("Probable use of requests call without timeout") + format!("Probable use of `{module}` call without timeout") } else { - format!("Probable use of requests call with timeout set to `None`") + format!("Probable use of `{module}` call with timeout set to `None`") } } } /// S113 pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCall) { - if checker + if let Some(module) = checker .semantic() .resolve_qualified_name(&call.func) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - [ - "requests", - "get" | "options" | "head" | "post" | "put" | "patch" | "delete" | "request" - ] - ) + .and_then(|qualified_name| match qualified_name.segments() { + ["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete" | "request"] => { + Some("requests") + } + ["httpx", "get" | "options" | "head" | "post" | "put" | "patch" | "delete" | "request" | "stream" | "Client" | "AsyncClient"] => { + Some("httpx") + } + _ => None, }) { if let Some(keyword) = call.arguments.find_keyword("timeout") { if keyword.value.is_none_literal_expr() { checker.diagnostics.push(Diagnostic::new( - RequestWithoutTimeout { implicit: false }, + RequestWithoutTimeout { implicit: false, module: module.to_string() }, keyword.range(), )); } } else { checker.diagnostics.push(Diagnostic::new( - RequestWithoutTimeout { implicit: true }, + RequestWithoutTimeout { implicit: true, module: module.to_string() }, call.func.range(), )); } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S113_S113.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S113_S113.py.snap index 472679eee9244..da0c8c13d147a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S113_S113.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S113_S113.py.snap @@ -1,142 +1,358 @@ --- source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs --- -S113.py:3:1: S113 Probable use of requests call without timeout - | -1 | import requests -2 | -3 | requests.get('https://gmail.com') - | ^^^^^^^^^^^^ S113 -4 | requests.get('https://gmail.com', timeout=None) -5 | requests.get('https://gmail.com', timeout=5) - | - -S113.py:4:35: S113 Probable use of requests call with timeout set to `None` - | -3 | requests.get('https://gmail.com') -4 | requests.get('https://gmail.com', timeout=None) - | ^^^^^^^^^^^^ S113 -5 | requests.get('https://gmail.com', timeout=5) -6 | requests.post('https://gmail.com') - | - -S113.py:6:1: S113 Probable use of requests call without timeout - | -4 | requests.get('https://gmail.com', timeout=None) -5 | requests.get('https://gmail.com', timeout=5) -6 | requests.post('https://gmail.com') - | ^^^^^^^^^^^^^ S113 -7 | requests.post('https://gmail.com', timeout=None) -8 | requests.post('https://gmail.com', timeout=5) - | - -S113.py:7:36: S113 Probable use of requests call with timeout set to `None` - | -5 | requests.get('https://gmail.com', timeout=5) -6 | requests.post('https://gmail.com') -7 | requests.post('https://gmail.com', timeout=None) - | ^^^^^^^^^^^^ S113 -8 | requests.post('https://gmail.com', timeout=5) -9 | requests.put('https://gmail.com') - | - -S113.py:9:1: S113 Probable use of requests call without timeout - | - 7 | requests.post('https://gmail.com', timeout=None) - 8 | requests.post('https://gmail.com', timeout=5) - 9 | requests.put('https://gmail.com') +S113.py:29:1: S113 Probable use of `requests` call without timeout + | +28 | # Errors +29 | requests.get('https://gmail.com') + | ^^^^^^^^^^^^ S113 +30 | requests.get('https://gmail.com', timeout=None) +31 | requests.post('https://gmail.com') + | + +S113.py:30:35: S113 Probable use of `requests` call with timeout set to `None` + | +28 | # Errors +29 | requests.get('https://gmail.com') +30 | requests.get('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +31 | requests.post('https://gmail.com') +32 | requests.post('https://gmail.com', timeout=None) + | + +S113.py:31:1: S113 Probable use of `requests` call without timeout + | +29 | requests.get('https://gmail.com') +30 | requests.get('https://gmail.com', timeout=None) +31 | requests.post('https://gmail.com') + | ^^^^^^^^^^^^^ S113 +32 | requests.post('https://gmail.com', timeout=None) +33 | requests.put('https://gmail.com') + | + +S113.py:32:36: S113 Probable use of `requests` call with timeout set to `None` + | +30 | requests.get('https://gmail.com', timeout=None) +31 | requests.post('https://gmail.com') +32 | requests.post('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +33 | requests.put('https://gmail.com') +34 | requests.put('https://gmail.com', timeout=None) + | + +S113.py:33:1: S113 Probable use of `requests` call without timeout + | +31 | requests.post('https://gmail.com') +32 | requests.post('https://gmail.com', timeout=None) +33 | requests.put('https://gmail.com') | ^^^^^^^^^^^^ S113 -10 | requests.put('https://gmail.com', timeout=None) -11 | requests.put('https://gmail.com', timeout=5) +34 | requests.put('https://gmail.com', timeout=None) +35 | requests.delete('https://gmail.com') | -S113.py:10:35: S113 Probable use of requests call with timeout set to `None` +S113.py:34:35: S113 Probable use of `requests` call with timeout set to `None` | - 8 | requests.post('https://gmail.com', timeout=5) - 9 | requests.put('https://gmail.com') -10 | requests.put('https://gmail.com', timeout=None) +32 | requests.post('https://gmail.com', timeout=None) +33 | requests.put('https://gmail.com') +34 | requests.put('https://gmail.com', timeout=None) | ^^^^^^^^^^^^ S113 -11 | requests.put('https://gmail.com', timeout=5) -12 | requests.delete('https://gmail.com') +35 | requests.delete('https://gmail.com') +36 | requests.delete('https://gmail.com', timeout=None) | -S113.py:12:1: S113 Probable use of requests call without timeout +S113.py:35:1: S113 Probable use of `requests` call without timeout | -10 | requests.put('https://gmail.com', timeout=None) -11 | requests.put('https://gmail.com', timeout=5) -12 | requests.delete('https://gmail.com') +33 | requests.put('https://gmail.com') +34 | requests.put('https://gmail.com', timeout=None) +35 | requests.delete('https://gmail.com') | ^^^^^^^^^^^^^^^ S113 -13 | requests.delete('https://gmail.com', timeout=None) -14 | requests.delete('https://gmail.com', timeout=5) +36 | requests.delete('https://gmail.com', timeout=None) +37 | requests.patch('https://gmail.com') | -S113.py:13:38: S113 Probable use of requests call with timeout set to `None` +S113.py:36:38: S113 Probable use of `requests` call with timeout set to `None` | -11 | requests.put('https://gmail.com', timeout=5) -12 | requests.delete('https://gmail.com') -13 | requests.delete('https://gmail.com', timeout=None) +34 | requests.put('https://gmail.com', timeout=None) +35 | requests.delete('https://gmail.com') +36 | requests.delete('https://gmail.com', timeout=None) | ^^^^^^^^^^^^ S113 -14 | requests.delete('https://gmail.com', timeout=5) -15 | requests.patch('https://gmail.com') +37 | requests.patch('https://gmail.com') +38 | requests.patch('https://gmail.com', timeout=None) | -S113.py:15:1: S113 Probable use of requests call without timeout +S113.py:37:1: S113 Probable use of `requests` call without timeout | -13 | requests.delete('https://gmail.com', timeout=None) -14 | requests.delete('https://gmail.com', timeout=5) -15 | requests.patch('https://gmail.com') +35 | requests.delete('https://gmail.com') +36 | requests.delete('https://gmail.com', timeout=None) +37 | requests.patch('https://gmail.com') | ^^^^^^^^^^^^^^ S113 -16 | requests.patch('https://gmail.com', timeout=None) -17 | requests.patch('https://gmail.com', timeout=5) +38 | requests.patch('https://gmail.com', timeout=None) +39 | requests.options('https://gmail.com') | -S113.py:16:37: S113 Probable use of requests call with timeout set to `None` +S113.py:38:37: S113 Probable use of `requests` call with timeout set to `None` | -14 | requests.delete('https://gmail.com', timeout=5) -15 | requests.patch('https://gmail.com') -16 | requests.patch('https://gmail.com', timeout=None) +36 | requests.delete('https://gmail.com', timeout=None) +37 | requests.patch('https://gmail.com') +38 | requests.patch('https://gmail.com', timeout=None) | ^^^^^^^^^^^^ S113 -17 | requests.patch('https://gmail.com', timeout=5) -18 | requests.options('https://gmail.com') +39 | requests.options('https://gmail.com') +40 | requests.options('https://gmail.com', timeout=None) | -S113.py:18:1: S113 Probable use of requests call without timeout +S113.py:39:1: S113 Probable use of `requests` call without timeout | -16 | requests.patch('https://gmail.com', timeout=None) -17 | requests.patch('https://gmail.com', timeout=5) -18 | requests.options('https://gmail.com') +37 | requests.patch('https://gmail.com') +38 | requests.patch('https://gmail.com', timeout=None) +39 | requests.options('https://gmail.com') | ^^^^^^^^^^^^^^^^ S113 -19 | requests.options('https://gmail.com', timeout=None) -20 | requests.options('https://gmail.com', timeout=5) +40 | requests.options('https://gmail.com', timeout=None) +41 | requests.head('https://gmail.com') | -S113.py:19:39: S113 Probable use of requests call with timeout set to `None` +S113.py:40:39: S113 Probable use of `requests` call with timeout set to `None` | -17 | requests.patch('https://gmail.com', timeout=5) -18 | requests.options('https://gmail.com') -19 | requests.options('https://gmail.com', timeout=None) +38 | requests.patch('https://gmail.com', timeout=None) +39 | requests.options('https://gmail.com') +40 | requests.options('https://gmail.com', timeout=None) | ^^^^^^^^^^^^ S113 -20 | requests.options('https://gmail.com', timeout=5) -21 | requests.head('https://gmail.com') +41 | requests.head('https://gmail.com') +42 | requests.head('https://gmail.com', timeout=None) + | + +S113.py:41:1: S113 Probable use of `requests` call without timeout + | +39 | requests.options('https://gmail.com') +40 | requests.options('https://gmail.com', timeout=None) +41 | requests.head('https://gmail.com') + | ^^^^^^^^^^^^^ S113 +42 | requests.head('https://gmail.com', timeout=None) + | + +S113.py:42:36: S113 Probable use of `requests` call with timeout set to `None` + | +40 | requests.options('https://gmail.com', timeout=None) +41 | requests.head('https://gmail.com') +42 | requests.head('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +43 | +44 | httpx.get('https://gmail.com') + | + +S113.py:44:1: S113 Probable use of `httpx` call without timeout + | +42 | requests.head('https://gmail.com', timeout=None) +43 | +44 | httpx.get('https://gmail.com') + | ^^^^^^^^^ S113 +45 | httpx.get('https://gmail.com', timeout=None) +46 | httpx.post('https://gmail.com') + | + +S113.py:45:32: S113 Probable use of `httpx` call with timeout set to `None` + | +44 | httpx.get('https://gmail.com') +45 | httpx.get('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +46 | httpx.post('https://gmail.com') +47 | httpx.post('https://gmail.com', timeout=None) + | + +S113.py:46:1: S113 Probable use of `httpx` call without timeout + | +44 | httpx.get('https://gmail.com') +45 | httpx.get('https://gmail.com', timeout=None) +46 | httpx.post('https://gmail.com') + | ^^^^^^^^^^ S113 +47 | httpx.post('https://gmail.com', timeout=None) +48 | httpx.put('https://gmail.com') + | + +S113.py:47:33: S113 Probable use of `httpx` call with timeout set to `None` + | +45 | httpx.get('https://gmail.com', timeout=None) +46 | httpx.post('https://gmail.com') +47 | httpx.post('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +48 | httpx.put('https://gmail.com') +49 | httpx.put('https://gmail.com', timeout=None) + | + +S113.py:48:1: S113 Probable use of `httpx` call without timeout + | +46 | httpx.post('https://gmail.com') +47 | httpx.post('https://gmail.com', timeout=None) +48 | httpx.put('https://gmail.com') + | ^^^^^^^^^ S113 +49 | httpx.put('https://gmail.com', timeout=None) +50 | httpx.delete('https://gmail.com') + | + +S113.py:49:32: S113 Probable use of `httpx` call with timeout set to `None` + | +47 | httpx.post('https://gmail.com', timeout=None) +48 | httpx.put('https://gmail.com') +49 | httpx.put('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +50 | httpx.delete('https://gmail.com') +51 | httpx.delete('https://gmail.com', timeout=None) | -S113.py:21:1: S113 Probable use of requests call without timeout +S113.py:50:1: S113 Probable use of `httpx` call without timeout | -19 | requests.options('https://gmail.com', timeout=None) -20 | requests.options('https://gmail.com', timeout=5) -21 | requests.head('https://gmail.com') +48 | httpx.put('https://gmail.com') +49 | httpx.put('https://gmail.com', timeout=None) +50 | httpx.delete('https://gmail.com') + | ^^^^^^^^^^^^ S113 +51 | httpx.delete('https://gmail.com', timeout=None) +52 | httpx.patch('https://gmail.com') + | + +S113.py:51:35: S113 Probable use of `httpx` call with timeout set to `None` + | +49 | httpx.put('https://gmail.com', timeout=None) +50 | httpx.delete('https://gmail.com') +51 | httpx.delete('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +52 | httpx.patch('https://gmail.com') +53 | httpx.patch('https://gmail.com', timeout=None) + | + +S113.py:52:1: S113 Probable use of `httpx` call without timeout + | +50 | httpx.delete('https://gmail.com') +51 | httpx.delete('https://gmail.com', timeout=None) +52 | httpx.patch('https://gmail.com') + | ^^^^^^^^^^^ S113 +53 | httpx.patch('https://gmail.com', timeout=None) +54 | httpx.options('https://gmail.com') + | + +S113.py:53:34: S113 Probable use of `httpx` call with timeout set to `None` + | +51 | httpx.delete('https://gmail.com', timeout=None) +52 | httpx.patch('https://gmail.com') +53 | httpx.patch('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +54 | httpx.options('https://gmail.com') +55 | httpx.options('https://gmail.com', timeout=None) + | + +S113.py:54:1: S113 Probable use of `httpx` call without timeout + | +52 | httpx.patch('https://gmail.com') +53 | httpx.patch('https://gmail.com', timeout=None) +54 | httpx.options('https://gmail.com') | ^^^^^^^^^^^^^ S113 -22 | requests.head('https://gmail.com', timeout=None) -23 | requests.head('https://gmail.com', timeout=5) +55 | httpx.options('https://gmail.com', timeout=None) +56 | httpx.head('https://gmail.com') | -S113.py:22:36: S113 Probable use of requests call with timeout set to `None` +S113.py:55:36: S113 Probable use of `httpx` call with timeout set to `None` | -20 | requests.options('https://gmail.com', timeout=5) -21 | requests.head('https://gmail.com') -22 | requests.head('https://gmail.com', timeout=None) +53 | httpx.patch('https://gmail.com', timeout=None) +54 | httpx.options('https://gmail.com') +55 | httpx.options('https://gmail.com', timeout=None) | ^^^^^^^^^^^^ S113 -23 | requests.head('https://gmail.com', timeout=5) +56 | httpx.head('https://gmail.com') +57 | httpx.head('https://gmail.com', timeout=None) | +S113.py:56:1: S113 Probable use of `httpx` call without timeout + | +54 | httpx.options('https://gmail.com') +55 | httpx.options('https://gmail.com', timeout=None) +56 | httpx.head('https://gmail.com') + | ^^^^^^^^^^ S113 +57 | httpx.head('https://gmail.com', timeout=None) +58 | httpx.Client() + | +S113.py:57:33: S113 Probable use of `httpx` call with timeout set to `None` + | +55 | httpx.options('https://gmail.com', timeout=None) +56 | httpx.head('https://gmail.com') +57 | httpx.head('https://gmail.com', timeout=None) + | ^^^^^^^^^^^^ S113 +58 | httpx.Client() +59 | httpx.Client(timeout=None) + | + +S113.py:58:1: S113 Probable use of `httpx` call without timeout + | +56 | httpx.head('https://gmail.com') +57 | httpx.head('https://gmail.com', timeout=None) +58 | httpx.Client() + | ^^^^^^^^^^^^ S113 +59 | httpx.Client(timeout=None) +60 | httpx.AsyncClient() + | + +S113.py:59:14: S113 Probable use of `httpx` call with timeout set to `None` + | +57 | httpx.head('https://gmail.com', timeout=None) +58 | httpx.Client() +59 | httpx.Client(timeout=None) + | ^^^^^^^^^^^^ S113 +60 | httpx.AsyncClient() +61 | httpx.AsyncClient(timeout=None) + | + +S113.py:60:1: S113 Probable use of `httpx` call without timeout + | +58 | httpx.Client() +59 | httpx.Client(timeout=None) +60 | httpx.AsyncClient() + | ^^^^^^^^^^^^^^^^^ S113 +61 | httpx.AsyncClient(timeout=None) +62 | with httpx.Client() as client: + | + +S113.py:61:19: S113 Probable use of `httpx` call with timeout set to `None` + | +59 | httpx.Client(timeout=None) +60 | httpx.AsyncClient() +61 | httpx.AsyncClient(timeout=None) + | ^^^^^^^^^^^^ S113 +62 | with httpx.Client() as client: +63 | client.get('https://gmail.com') + | + +S113.py:62:6: S113 Probable use of `httpx` call without timeout + | +60 | httpx.AsyncClient() +61 | httpx.AsyncClient(timeout=None) +62 | with httpx.Client() as client: + | ^^^^^^^^^^^^ S113 +63 | client.get('https://gmail.com') +64 | with httpx.Client(timeout=None) as client: + | + +S113.py:64:19: S113 Probable use of `httpx` call with timeout set to `None` + | +62 | with httpx.Client() as client: +63 | client.get('https://gmail.com') +64 | with httpx.Client(timeout=None) as client: + | ^^^^^^^^^^^^ S113 +65 | client.get('https://gmail.com') +66 | async def bar(): + | + +S113.py:67:16: S113 Probable use of `httpx` call without timeout + | +65 | client.get('https://gmail.com') +66 | async def bar(): +67 | async with httpx.AsyncClient() as client: + | ^^^^^^^^^^^^^^^^^ S113 +68 | await client.get('https://gmail.com') +69 | async def baz(): + | + +S113.py:70:34: S113 Probable use of `httpx` call with timeout set to `None` + | +68 | await client.get('https://gmail.com') +69 | async def baz(): +70 | async with httpx.AsyncClient(timeout=None) as client: + | ^^^^^^^^^^^^ S113 +71 | await client.get('https://gmail.com') + |