Skip to content

Commit fbe6677

Browse files
committed
[ADD] missing-timeout: Used when a method call an external request
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 You can define your custom methods using the configuration parameter: --timeout-methods The default methods are: - http.client.HTTPConnection - http.client.HTTPSConnection - requests.delete - requests.get - requests.head - requests.options - requests.patch - requests.post - requests.put - requests.request - serial.Serial - smtplib.SMTP - suds.client.Client
1 parent bd33f79 commit fbe6677

File tree

6 files changed

+289
-5
lines changed

6 files changed

+289
-5
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

doc/whatsnew/2/2.14/full.rst

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ What's New in Pylint 2.14.0?
55
----------------------------
66
Release date: TBA
77

8+
* Added new checker ``missing-timeout``.
89

910
* The refactoring checker now also raises 'consider-using-generator' messages for
1011
``max()``, ``min()`` and ``sum()``.

doc/whatsnew/2/2.14/summary.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Summary -- Release highlights
77
With 2.14 ``pylint`` only supports Python version 3.7.2 and above.
88

99
We introduced several new checks among which ``duplicate-value`` for sets,
10-
``comparison-of-constants``, and checks related to lambdas. We removed ``no-init`` and
10+
``comparison-of-constants``, ``missing-timeout``and checks related to lambdas. We removed ``no-init`` and
1111
made ``no-self-use`` optional as they were too opinionated. We also added an option
1212
to generate a toml configuration: ``--generate-toml-config``.
1313

@@ -30,6 +30,8 @@ to contribute to ``pylint`` or open source without any experience with our code!
3030
New checkers
3131
============
3232

33+
* Added new checker ``missing-timeout``.
34+
3335
* Added new checker ``comparison-of-constants``.
3436

3537
Closes #6076

pylint/checkers/variables.py

+86-4
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,12 @@ def _has_locals_call_after_node(stmt, scope):
463463
"Used when there is an unbalanced tuple unpacking in assignment",
464464
{"old_names": [("E0632", "old-unbalanced-tuple-unpacking")]},
465465
),
466+
"W0635": (
467+
"Missing timeout for method %s",
468+
"missing-timeout",
469+
'Used when a method needs a "timeout" parameter '
470+
"in order to avoid waiting for a long time.",
471+
),
466472
"E0633": (
467473
"Attempting to unpack a non-sequence%s",
468474
"unpacking-non-sequence",
@@ -1051,6 +1057,32 @@ class VariablesChecker(BaseChecker):
10511057
"help": "List of names allowed to shadow builtins",
10521058
},
10531059
),
1060+
(
1061+
"timeout-methods",
1062+
{
1063+
"default": (
1064+
"http.client.HTTPConnection",
1065+
"http.client.HTTPSConnection",
1066+
"odoo.addons.iap.models.iap.jsonrpc",
1067+
"requests.delete",
1068+
"requests.get",
1069+
"requests.head",
1070+
"requests.options",
1071+
"requests.patch",
1072+
"requests.post",
1073+
"requests.put",
1074+
"requests.request",
1075+
"serial.Serial",
1076+
"smtplib.SMTP",
1077+
"suds.client.Client",
1078+
"urllib.request.urlopen",
1079+
),
1080+
"type": "csv",
1081+
"metavar": "<comma separated list>",
1082+
"help": "List of library.method which requires timeout parameter "
1083+
"e.g. 'requests.get,requests.post'",
1084+
},
1085+
),
10541086
)
10551087

10561088
def __init__(self, linter=None):
@@ -1080,6 +1112,7 @@ def visit_module(self, node: nodes.Module) -> None:
10801112
"""Visit module : update consumption analysis variable
10811113
checks globals doesn't overrides builtins.
10821114
"""
1115+
self._imports: dict = {}
10831116
self._to_consume = [NamesConsumer(node, "module")]
10841117
self._postponed_evaluation_enabled = is_postponed_evaluation_enabled(node)
10851118

@@ -1098,9 +1131,11 @@ def visit_module(self, node: nodes.Module) -> None:
10981131
"invalid-all-format",
10991132
"unused-variable",
11001133
"undefined-variable",
1134+
"missing-timeout",
11011135
)
11021136
def leave_module(self, node: nodes.Module) -> None:
11031137
"""Leave module: check globals."""
1138+
self._imports = {}
11041139
assert len(self._to_consume) == 1
11051140

11061141
self._check_metaclasses(node)
@@ -1163,6 +1198,43 @@ def leave_setcomp(self, _: nodes.SetComp) -> None:
11631198
# do not check for not used locals here
11641199
self._to_consume.pop()
11651200

1201+
@utils.only_required_for_messages(
1202+
"missing-timeout",
1203+
)
1204+
def visit_call(self, node: nodes.Call) -> None:
1205+
"""Check if the call needs timeout parameter based on package.func_name
1206+
configured in config.timeout_methods.
1207+
1208+
Package uses the self._imports dict variable
1209+
in order to know the original package name imported
1210+
"""
1211+
lib_name = ""
1212+
if isinstance(node.func, nodes.Attribute) and isinstance(
1213+
node.func.expr, nodes.Name
1214+
):
1215+
lib_alias = node.func.expr.name
1216+
# Use dict "self._imports" to know the source library of the method
1217+
lib_name = self._imports.get(lib_alias) or lib_alias
1218+
func_name = (
1219+
isinstance(node.func, astroid.Name)
1220+
and node.func.name
1221+
or isinstance(node.func, astroid.Attribute)
1222+
and node.func.attrname
1223+
)
1224+
lib_func_name = (
1225+
# If it using "requests.request()"
1226+
f"{lib_name}.{func_name}"
1227+
if lib_name
1228+
# If it using "from requests import request;request()"
1229+
else self._imports.get(func_name)
1230+
)
1231+
if lib_func_name in self.config.timeout_methods:
1232+
for keyword in node.keywords:
1233+
if keyword.arg == "timeout":
1234+
break
1235+
else:
1236+
self.add_message("missing-timeout", node=node, args=(lib_func_name,))
1237+
11661238
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
11671239
"""Visit function: update consumption analysis variable and check locals."""
11681240
self._to_consume.append(NamesConsumer(node, "function"))
@@ -1665,9 +1737,13 @@ def _check_consumer(
16651737

16661738
return (VariableVisitConsumerAction.RETURN, found_nodes)
16671739

1668-
@utils.only_required_for_messages("no-name-in-module")
1740+
@utils.only_required_for_messages("no-name-in-module", "missing-timeout")
16691741
def visit_import(self, node: nodes.Import) -> None:
1670-
"""Check modules attribute accesses."""
1742+
"""Check modules attribute accesses and
1743+
fill self._imports dict variable with {alias: package} to be used from
1744+
visit_call to check if it needs timeout parameter.
1745+
"""
1746+
self._imports.update({alias or name: "%s" % name for name, alias in node.names})
16711747
if not self._analyse_fallback_blocks and utils.is_from_fallback_block(node):
16721748
# No need to verify this, since ImportError is already
16731749
# handled by the client code.
@@ -1687,9 +1763,15 @@ def visit_import(self, node: nodes.Import) -> None:
16871763
continue
16881764
self._check_module_attrs(node, module, parts[1:])
16891765

1690-
@utils.only_required_for_messages("no-name-in-module")
1766+
@utils.only_required_for_messages("no-name-in-module", "missing-timeout")
16911767
def visit_importfrom(self, node: nodes.ImportFrom) -> None:
1692-
"""Check modules attribute accesses."""
1768+
"""Check modules attribute accesses and
1769+
fill self._imports dict variable with {alias: package} to be used from
1770+
visit_call to check if it needs timeout parameter.
1771+
"""
1772+
self._imports.update(
1773+
{alias or name: f"{node.modname}.{name}" for name, alias in node.names}
1774+
)
16931775
if not self._analyse_fallback_blocks and utils.is_from_fallback_block(node):
16941776
# No need to verify this, since ImportError is already
16951777
# handled by the client code.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# pylint: disable=consider-using-with,import-errormissing-module-docstring,reimported
2+
import http.client
3+
import smtplib
4+
import smtplib as smtplib_r
5+
import urllib
6+
from http.client import (
7+
HTTPConnection,
8+
HTTPConnection as HTTPConnection_r,
9+
HTTPSConnection,
10+
HTTPSConnection as HTTPSConnection_r,
11+
)
12+
from smtplib import SMTP, SMTP as SMTP_r
13+
from urllib.request import urlopen, urlopen as urlopen_r
14+
15+
import requests
16+
import serial
17+
import serial as serial_r
18+
import suds.client
19+
from requests import (
20+
delete,
21+
delete as delete_r,
22+
get,
23+
get as get_r,
24+
head,
25+
head as head_r,
26+
options,
27+
options as options_r,
28+
patch,
29+
patch as patch_r,
30+
post,
31+
post as post_r,
32+
put,
33+
put as put_r,
34+
request,
35+
request as request_r,
36+
)
37+
from serial import Serial, Serial as Serial_r
38+
from suds.client import Client, Client as Client_r
39+
40+
# requests without timeout
41+
requests.delete("http://localhost") # [missing-timeout]
42+
requests.get("http://localhost") # [missing-timeout]
43+
requests.head("http://localhost") # [missing-timeout]
44+
requests.options("http://localhost") # [missing-timeout]
45+
requests.patch("http://localhost") # [missing-timeout]
46+
requests.post("http://localhost") # [missing-timeout]
47+
requests.put("http://localhost") # [missing-timeout]
48+
requests.request("call", "http://localhost") # [missing-timeout]
49+
50+
delete_r("http://localhost") # [missing-timeout]
51+
get_r("http://localhost") # [missing-timeout]
52+
head_r("http://localhost") # [missing-timeout]
53+
options_r("http://localhost") # [missing-timeout]
54+
patch_r("http://localhost") # [missing-timeout]
55+
post_r("http://localhost") # [missing-timeout]
56+
put_r("http://localhost") # [missing-timeout]
57+
request_r("call", "http://localhost") # [missing-timeout]
58+
59+
delete("http://localhost") # [missing-timeout]
60+
get("http://localhost") # [missing-timeout]
61+
head("http://localhost") # [missing-timeout]
62+
options("http://localhost") # [missing-timeout]
63+
patch("http://localhost") # [missing-timeout]
64+
post("http://localhost") # [missing-timeout]
65+
put("http://localhost") # [missing-timeout]
66+
request("call", "http://localhost") # [missing-timeout]
67+
68+
# requests valid cases
69+
requests.delete("http://localhost", timeout=10)
70+
requests.get("http://localhost", timeout=10)
71+
requests.head("http://localhost", timeout=10)
72+
requests.options("http://localhost", timeout=10)
73+
requests.patch("http://localhost", timeout=10)
74+
requests.post("http://localhost", timeout=10)
75+
requests.put("http://localhost", timeout=10)
76+
requests.request("call", "http://localhost", timeout=10)
77+
78+
delete_r("http://localhost", timeout=10)
79+
get_r("http://localhost", timeout=10)
80+
head_r("http://localhost", timeout=10)
81+
options_r("http://localhost", timeout=10)
82+
patch_r("http://localhost", timeout=10)
83+
post_r("http://localhost", timeout=10)
84+
put_r("http://localhost", timeout=10)
85+
request_r("call", "http://localhost", timeout=10)
86+
87+
delete("http://localhost", timeout=10)
88+
get("http://localhost", timeout=10)
89+
head("http://localhost", timeout=10)
90+
options("http://localhost", timeout=10)
91+
patch("http://localhost", timeout=10)
92+
post("http://localhost", timeout=10)
93+
put("http://localhost", timeout=10)
94+
request("call", "http://localhost", timeout=10)
95+
96+
# urllib without timeout
97+
urllib.request.urlopen("http://localhost") # [missing-timeout]
98+
urlopen("http://localhost") # [missing-timeout]
99+
urlopen_r("http://localhost") # [missing-timeout]
100+
101+
# urllib valid cases
102+
urllib.request.urlopen("http://localhost", timeout=10)
103+
urlopen("http://localhost", timeout=10)
104+
urlopen_r("http://localhost", timeout=10)
105+
106+
# suds without timeout
107+
suds.client.Client("http://localhost") # [missing-timeout]
108+
Client("http://localhost") # [missing-timeout]
109+
Client_r("http://localhost") # [missing-timeout]
110+
111+
# suds valid cases
112+
suds.client.Client("http://localhost", timeout=10)
113+
Client("http://localhost", timeout=10)
114+
Client_r("http://localhost", timeout=10)
115+
116+
# http.client without timeout
117+
http.client.HTTPConnection("http://localhost") # [missing-timeout]
118+
http.client.HTTPSConnection("http://localhost") # [missing-timeout]
119+
HTTPConnection("http://localhost") # [missing-timeout]
120+
HTTPSConnection("http://localhost") # [missing-timeout]
121+
HTTPConnection_r("http://localhost") # [missing-timeout]
122+
HTTPSConnection_r("http://localhost") # [missing-timeout]
123+
124+
# http.client valid cases
125+
http.client.HTTPConnection("http://localhost", timeout=10)
126+
http.client.HTTPSConnection("http://localhost", timeout=10)
127+
HTTPConnection("http://localhost", timeout=10)
128+
HTTPSConnection("http://localhost", timeout=10)
129+
HTTPConnection_r("http://localhost", timeout=10)
130+
HTTPSConnection_r("http://localhost", timeout=10)
131+
132+
# smtplib without timeout
133+
smtplib.SMTP("http://localhost") # [missing-timeout]
134+
smtplib_r.SMTP("http://localhost") # [missing-timeout]
135+
SMTP("http://localhost") # [missing-timeout]
136+
SMTP_r("http://localhost") # [missing-timeout]
137+
138+
# smtplib valid cases
139+
smtplib.SMTP("http://localhost", timeout=10)
140+
smtplib_r.SMTP("http://localhost", timeout=10)
141+
SMTP("http://localhost", timeout=10)
142+
SMTP_r("http://localhost", timeout=10)
143+
144+
# Serial without timeout
145+
serial.Serial("/dev/ttyS1") # [missing-timeout]
146+
serial_r.Serial("/dev/ttyS1") # [missing-timeout]
147+
Serial("/dev/ttyS1") # [missing-timeout]
148+
Serial_r("/dev/ttyS1") # [missing-timeout]
149+
150+
# serial valid cases
151+
serial.Serial("/dev/ttyS1", timeout=10)
152+
serial_r.Serial("/dev/ttyS1", timeout=10)
153+
Serial("/dev/ttyS1", timeout=10)
154+
Serial_r("/dev/ttyS1", timeout=10)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
missing-timeout:41:0:41:35::Missing timeout for method requests.delete:UNDEFINED
2+
missing-timeout:42:0:42:32::Missing timeout for method requests.get:UNDEFINED
3+
missing-timeout:43:0:43:33::Missing timeout for method requests.head:UNDEFINED
4+
missing-timeout:44:0:44:36::Missing timeout for method requests.options:UNDEFINED
5+
missing-timeout:45:0:45:34::Missing timeout for method requests.patch:UNDEFINED
6+
missing-timeout:46:0:46:33::Missing timeout for method requests.post:UNDEFINED
7+
missing-timeout:47:0:47:32::Missing timeout for method requests.put:UNDEFINED
8+
missing-timeout:48:0:48:44::Missing timeout for method requests.request:UNDEFINED
9+
missing-timeout:50:0:50:28::Missing timeout for method requests.delete:UNDEFINED
10+
missing-timeout:51:0:51:25::Missing timeout for method requests.get:UNDEFINED
11+
missing-timeout:52:0:52:26::Missing timeout for method requests.head:UNDEFINED
12+
missing-timeout:53:0:53:29::Missing timeout for method requests.options:UNDEFINED
13+
missing-timeout:54:0:54:27::Missing timeout for method requests.patch:UNDEFINED
14+
missing-timeout:55:0:55:26::Missing timeout for method requests.post:UNDEFINED
15+
missing-timeout:56:0:56:25::Missing timeout for method requests.put:UNDEFINED
16+
missing-timeout:57:0:57:37::Missing timeout for method requests.request:UNDEFINED
17+
missing-timeout:59:0:59:26::Missing timeout for method requests.delete:UNDEFINED
18+
missing-timeout:60:0:60:23::Missing timeout for method requests.get:UNDEFINED
19+
missing-timeout:61:0:61:24::Missing timeout for method requests.head:UNDEFINED
20+
missing-timeout:62:0:62:27::Missing timeout for method requests.options:UNDEFINED
21+
missing-timeout:63:0:63:25::Missing timeout for method requests.patch:UNDEFINED
22+
missing-timeout:64:0:64:24::Missing timeout for method requests.post:UNDEFINED
23+
missing-timeout:65:0:65:23::Missing timeout for method requests.put:UNDEFINED
24+
missing-timeout:66:0:66:35::Missing timeout for method requests.request:UNDEFINED
25+
missing-timeout:97:0:97:42::Missing timeout for method urllib.request.urlopen:UNDEFINED
26+
missing-timeout:98:0:98:27::Missing timeout for method urllib.request.urlopen:UNDEFINED
27+
missing-timeout:99:0:99:29::Missing timeout for method urllib.request.urlopen:UNDEFINED
28+
missing-timeout:107:0:107:38::Missing timeout for method suds.client.Client:UNDEFINED
29+
missing-timeout:108:0:108:26::Missing timeout for method suds.client.Client:UNDEFINED
30+
missing-timeout:109:0:109:28::Missing timeout for method suds.client.Client:UNDEFINED
31+
missing-timeout:117:0:117:46::Missing timeout for method http.client.HTTPConnection:UNDEFINED
32+
missing-timeout:118:0:118:47::Missing timeout for method http.client.HTTPSConnection:UNDEFINED
33+
missing-timeout:119:0:119:34::Missing timeout for method http.client.HTTPConnection:UNDEFINED
34+
missing-timeout:120:0:120:35::Missing timeout for method http.client.HTTPSConnection:UNDEFINED
35+
missing-timeout:121:0:121:36::Missing timeout for method http.client.HTTPConnection:UNDEFINED
36+
missing-timeout:122:0:122:37::Missing timeout for method http.client.HTTPSConnection:UNDEFINED
37+
missing-timeout:133:0:133:32::Missing timeout for method smtplib.SMTP:UNDEFINED
38+
missing-timeout:134:0:134:34::Missing timeout for method smtplib.SMTP:UNDEFINED
39+
missing-timeout:135:0:135:24::Missing timeout for method smtplib.SMTP:UNDEFINED
40+
missing-timeout:136:0:136:26::Missing timeout for method smtplib.SMTP:UNDEFINED
41+
missing-timeout:145:0:145:27::Missing timeout for method serial.Serial:UNDEFINED
42+
missing-timeout:146:0:146:29::Missing timeout for method serial.Serial:UNDEFINED
43+
missing-timeout:147:0:147:20::Missing timeout for method serial.Serial:UNDEFINED
44+
missing-timeout:148:0:148:22::Missing timeout for method serial.Serial:UNDEFINED

0 commit comments

Comments
 (0)