Source code for pyfrc.tests.docstring_test

import inspect
import os
import re
import sys

#: If you want to be really pedantic, enforce sphinx docstrings. Ha.
pedantic_docstrings = True

# regex to use to detect the sphinx docstrings
param_re = re.compile(r"^:param (\S+?):\s*(.+)$")
type_re = re.compile(r"^:type (\S+?):\s*(.+)$")
both_re = re.compile(r"^:param (\S+?) (\S+?):\s*(.+)$")


[docs] def ignore_object(o, robot_path): """Returns true if the object can be ignored""" if inspect.isbuiltin(o): return True try: src = inspect.getsourcefile(o) except TypeError: return True return src is None or not os.path.abspath(src).startswith(robot_path)
def print_fn_err(msg, parent, fn, errors): if inspect.isclass(parent): name = "%s.%s" % (parent.__name__, fn.__name__) else: name = "%s" % fn.__name__ name = "%s.%s" % (parent.__name__, fn.__name__) err = "ERROR: %s '%s()'\n-> See %s:%s" % ( msg, name, inspect.getsourcefile(fn), inspect.getsourcelines(fn)[1], ) if err not in errors: print(err, file=sys.stderr) errors.append(err) def check_function(parent, fn, errors): doc = inspect.getdoc(fn) if doc is None: print_fn_err("No docstring for", parent, fn, errors) elif pedantic_docstrings: # find the list of parameters args, varargs, keywords, defaults, _, _, annotations = inspect.getfullargspec( fn ) if len(args) > 0 and args[0] == "self": del args[0] if varargs is not None: args.append(varargs) if keywords is not None: args.append(keywords) params = [] for line in doc.splitlines(): # :param arg: stuff match = param_re.match(line) if match: arg = match.group(1) if arg not in args: print_fn_err( "Param '%s' is documented but isn't a parameter for" % arg, parent, fn, errors, ) if arg in annotations.keys() and isinstance(annotations.get(arg), str): print_fn_err( "Do not document %s in both the docstring and annotations" % arg, parent, fn, errors, ) params.append(arg) # :type arg: type match = type_re.match(line) if match: arg = match.group(1) if arg in annotations.keys() and isinstance(annotations.get(arg), type): print_fn_err( "Do not document %s in both the docstring and annotations" % arg, parent, fn, errors, ) # :param type arg: stuff match = both_re.match(line) if match: arg = match.group(2) if arg not in args: print_fn_err( "Param '%s' is documented but isn't a parameter for" % arg, parent, fn, errors, ) if arg in annotations.keys() and isinstance( annotations.get(arg), (str, type) ): print_fn_err( "Do not document %s in both the docstring and annotations" % arg, parent, fn, errors, ) params.append(arg) for param, annotation in annotations.items(): if param not in params and isinstance(annotation, str): params.insert(args.index(param), param) if len(params) != len(args): diff = set(args).difference(params) if len(diff) == 1: print_fn_err( "Param '%s' is not documented in docstring for" % diff.pop(), parent, fn, errors, ) elif len(diff) > 1: print_fn_err( "Params '%s' are not documented in docstring for" % "','".join(diff), parent, fn, errors, ) else: for param, arg in zip(params, args): if param != arg: print_fn_err( "Param '%s' is out of order, does not match param '%s' in docstring for" % (param, arg), parent, fn, errors, ) def check_object(o, robot_path, errors): if inspect.isclass(o) and inspect.getdoc(o) is None: err = "ERROR: Class '%s' has no docstring!\n-> See %s:%s" % ( o.__name__, inspect.getsourcefile(o), inspect.getsourcelines(o)[1], ) print(err) errors.append(err) for name, value in inspect.getmembers(o): if ignore_object(value, robot_path): continue check_thing(o, value, robot_path, errors) def check_thing(parent, thing, robot_path, errors): if inspect.isclass(thing): check_object(thing, robot_path, errors) elif inspect.isfunction(thing): check_function(parent, thing, errors)
[docs] def test_docstrings(robot, robot_path): """ The purpose of this test is to ensure that all of your robot code has docstrings. Properly using docstrings will make your code more maintainable and look more professional. """ # this allows abspath() to work correctly os.chdir(robot_path) errors = [] for module in sys.modules.values(): if ignore_object(module, robot_path): continue check_object(module, robot_path, errors) # if you get an error here, look at stdout for the error message assert len(errors) == 0