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"
]*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>")
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()