Skip to content

Commit 7c98f1b

Browse files
authored
Do not hardcode boolean connection verify v2 (#9118)
* Version 2 Checker Duplicated * Function Positiona lArguments Added * positional arg check removed
1 parent 6852ce7 commit 7c98f1b

5 files changed

Lines changed: 315 additions & 5 deletions

File tree

tools/pylint-extensions/azure-pylint-guidelines-checker/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ In the case of a false positive, use the disable command to remove the pylint er
9696
| do-not-log-raised-errors | Do not log errors at `error` or `warning` level when error is raised in an exception block. | pylint:disable=do-not-log-raised-errors | No Link. |
9797
| do-not-use-legacy-typing | Do not use legacy (<Python 3.8) type hinting comments | pylint:disable=do-not-use-legacy-typing | No Link. |
9898
| do-not-import-asyncio | Do not import asyncio directly. | pylint:disable=do-not-import-asyncio | No Link. |
99+
| do-not-hardcode-connection-verify | Do not hardcode a boolean value to connection_verify | pylint:disable=do-not-hardcode-connection-verify | No LInk. |
99100
| TODO | custom linter check for invalid use of @overload #3229 | | |
100101
| do-not-log-exceptions | Do not log exceptions in levels other than debug, otherwise it can reveal sensitive information | pylint:disable=do-not-log-exceptions | [link](https://azure.github.io/azure-sdk/python_implementation.html#python-logging-sensitive-info) |
101102
| unapproved-client-method-name-prefix | Clients should use preferred verbs for method names | pylint:disable=unapproved-client-method-name-prefix | [link](https://azure.github.io/azure-sdk/python_design.html#naming) |
102-
| TODO | Add a check for connection_verify hardcoded settings #35355 | | |
103-
| TODO | Address Commented out Pylint Custom Plugin Checkers #3228 | | |
103+
| TODO | Address Commented out Pylint Custom Plugin Checkers #3228 | | |

tools/pylint-extensions/azure-pylint-guidelines-checker/pylint_guidelines_checker.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,7 +2959,80 @@ def check_for_logging(self, node, exception_name):
29592959

29602960

29612961
# [Pylint] Address Commented out Pylint Custom Plugin Checkers #3228
2962-
# [Pylint] Add a check for connection_verify hardcoded settings #35355
2962+
2963+
2964+
class DoNotHardcodeConnectionVerify(BaseChecker):
2965+
2966+
"""Rule to check that developers do not hardcode a boolean to connection_verify."""
2967+
2968+
name = "do-not-hardcode-connection-verify"
2969+
priority = -1
2970+
msgs = {
2971+
"C4767": (
2972+
"Do not hardcode a boolean value to connection_verify",
2973+
"do-not-hardcode-connection-verify",
2974+
"Do not hardcode a boolean value to connection_verify. It's up to customers who use the code to be able to set it",
2975+
),
2976+
}
2977+
2978+
def visit_call(self, node):
2979+
"""Visit function calls to ensure it isn't used as a keyword parameter"""
2980+
if len(node.keywords) > 0:
2981+
for keyword in node.keywords:
2982+
if keyword.arg == "connection_verify":
2983+
if type(keyword.value.value) == bool:
2984+
self.add_message(
2985+
msgid=f"do-not-hardcode-connection-verify",
2986+
node=keyword,
2987+
confidence=None,
2988+
)
2989+
2990+
def visit_assign(self, node):
2991+
"""Visiting variable Assignments"""
2992+
try: # self.connection_verify = True
2993+
if node.targets[0].attrname == "connection_verify":
2994+
if type(node.value.value) == bool:
2995+
self.add_message(
2996+
msgid=f"do-not-hardcode-connection-verify",
2997+
node=node,
2998+
confidence=None,
2999+
)
3000+
except:
3001+
try: # connection_verify = True
3002+
if node.targets[0].name == "connection_verify":
3003+
if type(node.value.value) == bool:
3004+
self.add_message(
3005+
msgid=f"do-not-hardcode-connection-verify",
3006+
node=node,
3007+
confidence=None,
3008+
)
3009+
except:
3010+
pass # typically lists
3011+
3012+
def visit_annassign(self, node):
3013+
"""Visiting variable annotated assignments"""
3014+
try: # self.connection_verify: bool = True
3015+
if node.target.attrname == "connection_verify":
3016+
if type(node.value.value) == bool:
3017+
self.add_message(
3018+
msgid=f"do-not-hardcode-connection-verify",
3019+
node=node,
3020+
confidence=None,
3021+
)
3022+
except: # Picks up connection_verify: bool = True
3023+
try:
3024+
if node.target.name == "connection_verify":
3025+
if type(node.value.value) == bool:
3026+
self.add_message(
3027+
msgid=f"do-not-hardcode-connection-verify",
3028+
node=node,
3029+
confidence=None,
3030+
)
3031+
except:
3032+
pass
3033+
3034+
3035+
29633036
# [Pylint] Investigate pylint rule around missing dependency #3231
29643037

29653038

@@ -3002,7 +3075,7 @@ def register(linter):
30023075

30033076
# [Pylint] custom linter check for invalid use of @overload #3229
30043077
# [Pylint] Address Commented out Pylint Custom Plugin Checkers #3228
3005-
# [Pylint] Add a check for connection_verify hardcoded settings #35355
3078+
linter.register_checker(DoNotHardcodeConnectionVerify(linter))
30063079
# [Pylint] Investigate pylint rule around missing dependency #3231
30073080

30083081
# disabled by default, use pylint --enable=check-docstrings if you want to use it
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
class InstanceVariable:
2+
def __init__(self):
3+
self.connection_verify = None
4+
self.self = self
5+
6+
7+
class Variable:
8+
connection_verify = None
9+
10+
11+
class FunctionKeywordArgumentsErrors:
12+
def create(connection_verify):
13+
pass
14+
15+
client = create(connection_verify=None)
16+
17+
18+
class FunctionArgumentsInstanceErrors:
19+
def __init__(self):
20+
client = self.create_client_from_credential(connection_verify=None)
21+
22+
23+
class ReturnErrorFunctionArgument:
24+
def send(connection_verify):
25+
pass
26+
27+
def sampleFunction(self):
28+
return self.send(connection_verify=None)
29+
30+
31+
class ReturnErrorDict:
32+
def returnDict(self):
33+
34+
return dict(
35+
connection_verify=None,
36+
)
37+
38+
39+
class AnnotatedAssignment:
40+
connection_verify: bool = None
41+
42+
43+
class AnnotatedSelfAssignment:
44+
def __init__(self):
45+
self.connection_verify: bool = None
46+
47+
48+
class VisitAssignPass:
49+
connection_verify = ["apple", "banana", "cherry"]
50+
51+
52+
class VisitAnnassignPass:
53+
connection_verify = [0]
54+
connection_verify[0]: int = 0
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
class InstanceVariableError:
2+
def __init__(self):
3+
self.connection_verify = True
4+
self.self = self
5+
6+
7+
class VariableError:
8+
connection_verify = True
9+
10+
11+
class FunctionKeywordArgumentsErrors:
12+
def create(x, connection_verify):
13+
pass
14+
15+
client = create(connection_verify=False, x=0)
16+
17+
18+
class FunctionArgumentsInstanceErrors:
19+
def __init__(self):
20+
client = self.create_client_from_credential(connection_verify=False)
21+
22+
23+
class ReturnErrorFunctionArgument:
24+
def send(connection_verify):
25+
pass
26+
27+
def sample_function(self):
28+
return self.send(connection_verify=True)
29+
30+
31+
class ReturnErrorDict:
32+
def return_dict(self):
33+
34+
return dict(
35+
connection_verify=False,
36+
)
37+
38+
class AnnotatedAssignment:
39+
connection_verify: bool = True
40+
41+
42+
class AnnotatedSelfAssignment:
43+
def __init__(self):
44+
self.connection_verify: bool = True
45+
46+

tools/pylint-extensions/azure-pylint-guidelines-checker/tests/test_pylint_custom_plugins.py

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3724,7 +3724,144 @@ def test_guidelines_link_active(self):
37243724

37253725

37263726
# [Pylint] Address Commented out Pylint Custom Plugin Checkers #3228
3727-
# [Pylint] Add a check for connection_verify hardcoded settings #35355
3727+
3728+
3729+
class TestDoNotHardcodeConnectionVerify(pylint.testutils.CheckerTestCase):
3730+
"""Test that we are not hard-coding a True or False to connection_verify"""
3731+
3732+
CHECKER_CLASS = checker.DoNotHardcodeConnectionVerify
3733+
3734+
def test_valid_connection_verify(self):
3735+
"""Check that valid connection_verify hard coding does not raise warnings"""
3736+
file = open(
3737+
os.path.join(
3738+
TEST_FOLDER, "test_files", "do_not_hardcode_connection_verify_acceptable.py"
3739+
)
3740+
)
3741+
node = astroid.parse(file.read())
3742+
file.close()
3743+
3744+
nodes = node.body
3745+
instance_variable_error = nodes[0].body[0].body[0]
3746+
variable_error = nodes[1].body[0]
3747+
function_arguments_errors = nodes[2].body[1].value
3748+
function_arguments_instance_errors = nodes[3].body[0].body[0].value
3749+
return_error_function_argument = nodes[4].body[1].body[0].value
3750+
return_error_dict = nodes[5].body[0].body[0].value
3751+
annotated_assignment = nodes[6].body[0]
3752+
annotated_self_assignment = nodes[7].body[0].body[0]
3753+
visit_assign_pass = nodes[8].body[0]
3754+
visit_annassign_pass = nodes[9].body[1]
3755+
3756+
with self.assertNoMessages():
3757+
self.checker.visit_assign(instance_variable_error)
3758+
self.checker.visit_assign(variable_error)
3759+
self.checker.visit_call(function_arguments_errors)
3760+
self.checker.visit_call(function_arguments_instance_errors)
3761+
self.checker.visit_call(return_error_function_argument)
3762+
self.checker.visit_call(return_error_dict)
3763+
self.checker.visit_annassign(annotated_assignment)
3764+
self.checker.visit_annassign(annotated_self_assignment)
3765+
self.checker.visit_assign(visit_assign_pass)
3766+
self.checker.visit_annassign(visit_annassign_pass)
3767+
3768+
def test_invalid_connection_verify(self):
3769+
"""Check that hard-coding connection_verify to a bool raise warnings"""
3770+
file = open(
3771+
os.path.join(
3772+
TEST_FOLDER, "test_files", "do_not_hardcode_connection_verify_violation.py"
3773+
)
3774+
)
3775+
node = astroid.parse(file.read())
3776+
file.close()
3777+
3778+
nodes = node.body
3779+
instance_variable_error = nodes[0].body[0].body[0]
3780+
variable_error = nodes[1].body[0]
3781+
function_keyword_arguments = nodes[2].body[1].value
3782+
function_arguments_instance = nodes[3].body[0].body[0].value
3783+
return_error_function_argument = nodes[4].body[1].body[0].value
3784+
return_error_dict = nodes[5].body[0].body[0].value
3785+
annotated_assignment = nodes[6].body[0]
3786+
annotated_self_assignment = nodes[7].body[0].body[0]
3787+
3788+
with self.assertAddsMessages(
3789+
pylint.testutils.MessageTest(
3790+
msg_id="do-not-hardcode-connection-verify",
3791+
line=3,
3792+
node=instance_variable_error,
3793+
col_offset=8,
3794+
end_line=3,
3795+
end_col_offset=37,
3796+
),
3797+
pylint.testutils.MessageTest(
3798+
msg_id="do-not-hardcode-connection-verify",
3799+
line=8,
3800+
node=variable_error,
3801+
col_offset=4,
3802+
end_line=8,
3803+
end_col_offset=28,
3804+
),
3805+
pylint.testutils.MessageTest(
3806+
msg_id="do-not-hardcode-connection-verify",
3807+
line=15,
3808+
node=function_keyword_arguments.keywords[0],
3809+
col_offset=20,
3810+
end_line=15,
3811+
end_col_offset=43,
3812+
),
3813+
pylint.testutils.MessageTest(
3814+
msg_id="do-not-hardcode-connection-verify",
3815+
line=20,
3816+
node=function_arguments_instance.keywords[0],
3817+
col_offset=52,
3818+
end_line=20,
3819+
end_col_offset=75,
3820+
),
3821+
pylint.testutils.MessageTest(
3822+
msg_id="do-not-hardcode-connection-verify",
3823+
line=28,
3824+
node=return_error_function_argument.keywords[0],
3825+
col_offset=25,
3826+
end_line=28,
3827+
end_col_offset=47,
3828+
),
3829+
pylint.testutils.MessageTest(
3830+
msg_id="do-not-hardcode-connection-verify",
3831+
line=35,
3832+
node=return_error_dict.keywords[0],
3833+
col_offset=12,
3834+
end_line=35,
3835+
end_col_offset=35,
3836+
),
3837+
pylint.testutils.MessageTest(
3838+
msg_id="do-not-hardcode-connection-verify",
3839+
line=39,
3840+
node=annotated_assignment,
3841+
col_offset=4,
3842+
end_line=39,
3843+
end_col_offset=34,
3844+
),
3845+
pylint.testutils.MessageTest(
3846+
msg_id="do-not-hardcode-connection-verify",
3847+
line=44,
3848+
node=annotated_self_assignment,
3849+
col_offset=8,
3850+
end_line=44,
3851+
end_col_offset=43,
3852+
),
3853+
):
3854+
self.checker.visit_assign(instance_variable_error)
3855+
self.checker.visit_assign(variable_error)
3856+
self.checker.visit_call(function_keyword_arguments)
3857+
self.checker.visit_call(function_arguments_instance)
3858+
self.checker.visit_call(return_error_function_argument)
3859+
self.checker.visit_call(return_error_dict)
3860+
self.checker.visit_annassign(annotated_assignment)
3861+
self.checker.visit_annassign(annotated_self_assignment)
3862+
3863+
3864+
37283865
# [Pylint] Refactor test suite for custom pylint checkers to use files instead of docstrings #3233
37293866
# [Pylint] Investigate pylint rule around missing dependency #3231
37303867

0 commit comments

Comments
 (0)