-
Notifications
You must be signed in to change notification settings - Fork 171
/
validate_docstrings.py
147 lines (129 loc) · 4.82 KB
/
validate_docstrings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import inspect
import os
import re
import sys
import requests
sys.path.append(os.path.join(sys.path[0], ".."))
import canvasapi # noqa
def validate_method(themethod, quiet=False):
# No docstring means no erroneous docstrings
if not inspect.getdoc(themethod):
return True
# Docstrings without API calls can't be checked this way
if not re.search(r":calls:", inspect.getdoc(themethod)):
return True
if not re.search(r"<\S*>", inspect.getdoc(themethod)):
return True
method_string = inspect.getfile(themethod) + " " + themethod.__name__
call_lines = re.findall(
"`(POST|GET|PUT|PATCH|DELETE)([^<]*)<([^>]*)>`_", inspect.getdoc(themethod)
)
if not call_lines:
if not quiet:
# Docstring exists, has a :calls: line, contains a URL, but could
# not be parsed;
print("{} Failed to parse :calls: line.".format(method_string))
return False
for call_line in call_lines:
if not validate_docstring(method_string, call_line, quiet):
return False
return True
def validate_docstring(method_string, call_line, quiet):
docstring_verb, api_URL, doc_URL = call_line
api_URL = "".join(api_URL.split())
if api_URL[-1] == "/":
api_URL = api_URL[0:-1]
docfile_URL, endpoint_name = re.search("([^#]*)#?(.*)", doc_URL).groups()
html_doc_response = requests.get(docfile_URL)
if html_doc_response.status_code != requests.codes.ok:
if not quiet:
print(
"{} docstring URL request returned {}".format(
method_string, html_doc_response.status_code
)
)
return False
endpoint_h2 = re.search(
r"<h2[^>]*name=[\'\"]{}[\'\"]".format(endpoint_name), html_doc_response.text
)
if not endpoint_name:
if not quiet:
print(
(
"{} docstring URL does not contain an endpoint name in link"
" to API documentation"
).format(method_string)
)
return False
if not endpoint_h2:
if not quiet:
print(
"{} docstring refers to {} in {}, not found".format(
method_string, endpoint_name, docfile_URL
)
)
return False
endpoint_element_re = re.compile(r"<h3 class=[\"\']endpoint[\"\']>[^<]*<\/h3>")
endpoint_search_start_match = endpoint_element_re.search(
html_doc_response.text, endpoint_h2.end()
)
if not endpoint_search_start_match:
if not quiet:
print("Found no endpoint after {} in {}".format(endpoint_name, docfile_URL))
return False
endpoint_search_start_pos = endpoint_search_start_match.start()
after_endpoint_re = re.compile(r"<[^h\/]")
endpoint_search_end = after_endpoint_re.search(
html_doc_response.text, endpoint_search_start_pos
)
if not endpoint_search_end:
endpoint_search_stop_pos = len(html_doc_response.text)
else:
endpoint_search_stop_pos = endpoint_search_end.start()
endpoint_element_match = endpoint_element_re.search(
html_doc_response.text, endpoint_search_start_pos, endpoint_search_stop_pos
)
endpoint_element_list = []
while endpoint_element_match:
endpoint_element_list.append(endpoint_element_match.group())
endpoint_element_match = endpoint_element_re.search(
html_doc_response.text,
endpoint_element_match.end(),
endpoint_search_stop_pos,
)
if not endpoint_element_list:
if not quiet:
print("Found no endpoint after {} in {}".format(endpoint_name, docfile_URL))
return False
docfile_lines = []
for endpoint_element_str in endpoint_element_list:
docfile_match = re.search(
"(POST|GET|PUT|PATCH|DELETE) (.*)", endpoint_element_str
)
docfile_lines.append(docfile_match.group())
docfile_verb, docfile_API_URL = docfile_match.groups()
if docfile_verb != docstring_verb:
continue
if docfile_API_URL != api_URL:
continue
return True
if not quiet:
print(
"{} docstring {} not found in {} (found {})".format(
method_string,
docstring_verb + " " + api_URL,
doc_URL,
str(docfile_lines),
)
)
return False
def test_methods():
methods = set()
for _, module in inspect.getmembers(canvasapi, inspect.ismodule):
for _, theclass in inspect.getmembers(module, inspect.isclass):
for _, method in inspect.getmembers(theclass, inspect.isroutine):
methods.add(method)
for method_to_test in methods:
validate_method(method_to_test)
if __name__ == "__main__":
test_methods()