Skip to content

Commit 76bcfda

Browse files
authored
[ADD] missing-timeout: Used when a method call an external request (#6780)
Calling external request needs to use timeout in order to avoid waiting for a long time You can even reproduce the case using deelay.me e.g. ```python import requests response = requests.get("https://deelay.me/5000/http://localhost:80") # It will spend 5s response = requests.get("https://deelay.me/5000/http://localhost:80", timeout=2) # timeout response = requests.get("https://deelay.me/1000/http://localhost:80", timeout=2) # fine ``` After 2s if the request doesn't have response it raises the following exception requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='deelay.me', port=443): Read timed out. (read timeout=2) But if it responses <=1s it is fine Now you can test the same but using a bigger delay
1 parent 02e9023 commit 76bcfda

File tree

9 files changed

+217
-0
lines changed

9 files changed

+217
-0
lines changed

CONTRIBUTORS.txt

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ contributors:
8888
* Add consider-merging-isinstance, superfluous-else-return
8989
* Fix consider-using-ternary for 'True and True and True or True' case
9090
* Add bad-docstring-quotes and docstring-first-line-empty
91+
* Add missing-timeout
9192
- Ville Skyttä <[email protected]>
9293
- Pierre-Yves David <[email protected]>
9394
- David Shea <[email protected]>: invalid sequence and slice index
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import requests
2+
3+
requests.post("http://localhost") # [missing-timeout]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
You can add new methods that should have a defined ```timeout`` argument as qualified names
2+
in the ``timeout-methods`` option, for example:
3+
4+
* ``requests.api.get``
5+
* ``requests.api.head``
6+
* ``requests.api.options``
7+
* ``requests.api.patch``
8+
* ``requests.api.post``
9+
* ``requests.api.put``
10+
* ``requests.api.request``
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import requests
2+
3+
requests.post("http://localhost", timeout=10)

doc/whatsnew/2/2.15/index.rst

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Summary -- Release highlights
1515
New checkers
1616
============
1717

18+
Added new checker ``missing-timeout`` to warn of default timeout values that could cause
19+
a program to be hanging indefinitely.
20+
1821

1922
Removed checkers
2023
================

pylint/checkers/method_args.py

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""Variables checkers for Python code."""
6+
7+
from __future__ import annotations
8+
9+
from typing import TYPE_CHECKING
10+
11+
from astroid import arguments, nodes
12+
13+
from pylint.checkers import BaseChecker, utils
14+
from pylint.interfaces import INFERENCE
15+
16+
if TYPE_CHECKING:
17+
from pylint.lint import PyLinter
18+
19+
20+
class MethodArgsChecker(BaseChecker):
21+
"""BaseChecker for method_args.
22+
23+
Checks for
24+
* missing-timeout
25+
"""
26+
27+
name = "method_args"
28+
msgs = {
29+
"W3101": (
30+
"Missing timeout argument for method '%s' can cause your program to hang indefinitely",
31+
"missing-timeout",
32+
"Used when a method needs a 'timeout' parameter in order to avoid waiting "
33+
"for a long time. If no timeout is specified explicitly the default value "
34+
"is used. For example for 'requests' the program will never time out "
35+
"(i.e. hang indefinitely).",
36+
),
37+
}
38+
options = (
39+
(
40+
"timeout-methods",
41+
{
42+
"default": (
43+
"requests.api.delete",
44+
"requests.api.get",
45+
"requests.api.head",
46+
"requests.api.options",
47+
"requests.api.patch",
48+
"requests.api.post",
49+
"requests.api.put",
50+
"requests.api.request",
51+
),
52+
"type": "csv",
53+
"metavar": "<comma separated list>",
54+
"help": "List of qualified names (i.e., library.method) which require a timeout parameter "
55+
"e.g. 'requests.api.get,requests.api.post'",
56+
},
57+
),
58+
)
59+
60+
@utils.only_required_for_messages("missing-timeout")
61+
def visit_call(self, node: nodes.Call) -> None:
62+
"""Check if the call needs a timeout parameter based on package.func_name
63+
configured in config.timeout_methods.
64+
65+
Package uses inferred node in order to know the package imported.
66+
"""
67+
inferred = utils.safe_infer(node.func)
68+
call_site = arguments.CallSite.from_call(node)
69+
if (
70+
inferred
71+
and not call_site.has_invalid_keywords()
72+
and inferred.qname() in self.config.timeout_methods
73+
):
74+
keyword_arguments = [keyword.arg for keyword in node.keywords]
75+
keyword_arguments.extend(call_site.keyword_arguments)
76+
if "timeout" not in keyword_arguments:
77+
self.add_message(
78+
"missing-timeout",
79+
node=node,
80+
args=(node.func.as_string(),),
81+
confidence=INFERENCE,
82+
)
83+
84+
85+
def register(linter: PyLinter) -> None:
86+
linter.register_checker(MethodArgsChecker(linter))

requirements_test_min.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ typing-extensions~=4.2
55
pytest~=7.1
66
pytest-benchmark~=3.4
77
pytest-timeout~=2.1
8+
requests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Tests for missing-timeout."""
2+
3+
# pylint: disable=consider-using-with,import-error,no-member,no-name-in-module,reimported
4+
5+
import requests
6+
from requests import (
7+
delete,
8+
delete as delete_r,
9+
get,
10+
get as get_r,
11+
head,
12+
head as head_r,
13+
options,
14+
options as options_r,
15+
patch,
16+
patch as patch_r,
17+
post,
18+
post as post_r,
19+
put,
20+
put as put_r,
21+
request,
22+
request as request_r,
23+
)
24+
25+
# requests without timeout
26+
requests.delete("http://localhost") # [missing-timeout]
27+
requests.get("http://localhost") # [missing-timeout]
28+
requests.head("http://localhost") # [missing-timeout]
29+
requests.options("http://localhost") # [missing-timeout]
30+
requests.patch("http://localhost") # [missing-timeout]
31+
requests.post("http://localhost") # [missing-timeout]
32+
requests.put("http://localhost") # [missing-timeout]
33+
requests.request("call", "http://localhost") # [missing-timeout]
34+
35+
delete_r("http://localhost") # [missing-timeout]
36+
get_r("http://localhost") # [missing-timeout]
37+
head_r("http://localhost") # [missing-timeout]
38+
options_r("http://localhost") # [missing-timeout]
39+
patch_r("http://localhost") # [missing-timeout]
40+
post_r("http://localhost") # [missing-timeout]
41+
put_r("http://localhost") # [missing-timeout]
42+
request_r("call", "http://localhost") # [missing-timeout]
43+
44+
delete("http://localhost") # [missing-timeout]
45+
get("http://localhost") # [missing-timeout]
46+
head("http://localhost") # [missing-timeout]
47+
options("http://localhost") # [missing-timeout]
48+
patch("http://localhost") # [missing-timeout]
49+
post("http://localhost") # [missing-timeout]
50+
put("http://localhost") # [missing-timeout]
51+
request("call", "http://localhost") # [missing-timeout]
52+
53+
kwargs_wo_timeout = {}
54+
post("http://localhost", **kwargs_wo_timeout) # [missing-timeout]
55+
56+
# requests valid cases
57+
requests.delete("http://localhost", timeout=10)
58+
requests.get("http://localhost", timeout=10)
59+
requests.head("http://localhost", timeout=10)
60+
requests.options("http://localhost", timeout=10)
61+
requests.patch("http://localhost", timeout=10)
62+
requests.post("http://localhost", timeout=10)
63+
requests.put("http://localhost", timeout=10)
64+
requests.request("call", "http://localhost", timeout=10)
65+
66+
delete_r("http://localhost", timeout=10)
67+
get_r("http://localhost", timeout=10)
68+
head_r("http://localhost", timeout=10)
69+
options_r("http://localhost", timeout=10)
70+
patch_r("http://localhost", timeout=10)
71+
post_r("http://localhost", timeout=10)
72+
put_r("http://localhost", timeout=10)
73+
request_r("call", "http://localhost", timeout=10)
74+
75+
delete("http://localhost", timeout=10)
76+
get("http://localhost", timeout=10)
77+
head("http://localhost", timeout=10)
78+
options("http://localhost", timeout=10)
79+
patch("http://localhost", timeout=10)
80+
post("http://localhost", timeout=10)
81+
put("http://localhost", timeout=10)
82+
request("call", "http://localhost", timeout=10)
83+
84+
kwargs_timeout = {'timeout': 10}
85+
post("http://localhost", **kwargs_timeout)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
missing-timeout:26:0:26:35::Missing timeout argument for method 'requests.delete' can cause your program to hang indefinitely:INFERENCE
2+
missing-timeout:27:0:27:32::Missing timeout argument for method 'requests.get' can cause your program to hang indefinitely:INFERENCE
3+
missing-timeout:28:0:28:33::Missing timeout argument for method 'requests.head' can cause your program to hang indefinitely:INFERENCE
4+
missing-timeout:29:0:29:36::Missing timeout argument for method 'requests.options' can cause your program to hang indefinitely:INFERENCE
5+
missing-timeout:30:0:30:34::Missing timeout argument for method 'requests.patch' can cause your program to hang indefinitely:INFERENCE
6+
missing-timeout:31:0:31:33::Missing timeout argument for method 'requests.post' can cause your program to hang indefinitely:INFERENCE
7+
missing-timeout:32:0:32:32::Missing timeout argument for method 'requests.put' can cause your program to hang indefinitely:INFERENCE
8+
missing-timeout:33:0:33:44::Missing timeout argument for method 'requests.request' can cause your program to hang indefinitely:INFERENCE
9+
missing-timeout:35:0:35:28::Missing timeout argument for method 'delete_r' can cause your program to hang indefinitely:INFERENCE
10+
missing-timeout:36:0:36:25::Missing timeout argument for method 'get_r' can cause your program to hang indefinitely:INFERENCE
11+
missing-timeout:37:0:37:26::Missing timeout argument for method 'head_r' can cause your program to hang indefinitely:INFERENCE
12+
missing-timeout:38:0:38:29::Missing timeout argument for method 'options_r' can cause your program to hang indefinitely:INFERENCE
13+
missing-timeout:39:0:39:27::Missing timeout argument for method 'patch_r' can cause your program to hang indefinitely:INFERENCE
14+
missing-timeout:40:0:40:26::Missing timeout argument for method 'post_r' can cause your program to hang indefinitely:INFERENCE
15+
missing-timeout:41:0:41:25::Missing timeout argument for method 'put_r' can cause your program to hang indefinitely:INFERENCE
16+
missing-timeout:42:0:42:37::Missing timeout argument for method 'request_r' can cause your program to hang indefinitely:INFERENCE
17+
missing-timeout:44:0:44:26::Missing timeout argument for method 'delete' can cause your program to hang indefinitely:INFERENCE
18+
missing-timeout:45:0:45:23::Missing timeout argument for method 'get' can cause your program to hang indefinitely:INFERENCE
19+
missing-timeout:46:0:46:24::Missing timeout argument for method 'head' can cause your program to hang indefinitely:INFERENCE
20+
missing-timeout:47:0:47:27::Missing timeout argument for method 'options' can cause your program to hang indefinitely:INFERENCE
21+
missing-timeout:48:0:48:25::Missing timeout argument for method 'patch' can cause your program to hang indefinitely:INFERENCE
22+
missing-timeout:49:0:49:24::Missing timeout argument for method 'post' can cause your program to hang indefinitely:INFERENCE
23+
missing-timeout:50:0:50:23::Missing timeout argument for method 'put' can cause your program to hang indefinitely:INFERENCE
24+
missing-timeout:51:0:51:35::Missing timeout argument for method 'request' can cause your program to hang indefinitely:INFERENCE
25+
missing-timeout:54:0:54:45::Missing timeout argument for method 'post' can cause your program to hang indefinitely:INFERENCE

0 commit comments

Comments
 (0)