Source code for docstringtest


import os
import inspect

[docs]class DocstringTestError(RuntimeError): """ Special Error for Docstring test failures where an appropriate docstring is not found for a function/method """ def __init__(self, message, filename, funcName): """ Constructor for the DocstringTestError class :param message: Message for the specific error :param filename: Filename for the Python code that contains the function/method :param funcName: Name of the function which has an docstring error :type message: str :type filename: str :type funcName: str """ super(DocstringTestError, self).__init__(message) self.message = message self.funcName = funcName self.filename = os.path.abspath(filename) def __str__(self): """ Get a string representation of the error :return: String representation of this error including the message, function name and filename :rtype: str """ return "%s for function %s in file %s" % (self.message,self.funcName,self.filename)
def codeReturnsSomething(func): """ Checks whether a function/method appears to have a return statement that returns data. It simply examines the code for the function/method looking for a return call. :param func: Function to check :type func: function/method :return: Whether the function/method :rtype: bool """ sourceCode = inspect.getsource(func).split('\n') for line in sourceCode: tokens = line.strip().split() if len(tokens) >= 2 and (tokens[0] == 'return' or tokens[0] == 'yield'): return True return False
[docs]def generateAllDocstrings(c): """ Generate skeleton docstrings for all functions/methods in a module or class :param c: Module or class to generate docstrings for :type c: module/class :return: All docstrings for functions/methods :rtype: str """ assert inspect.isclass(c) or inspect.ismodule(c) output = [] for name,obj in inspect.getmembers(c): if inspect.ismethod(obj) or inspect.isfunction(obj): output.append("-"*30) output.append(name) output.append('"""') output.append(generateDocstring(obj)) output.append('"""') return "\n".join(output)
[docs]def generateDocstring(func): """ Generates a skeleton docstring (to be filled in) for a function/method :param func: Function/method that docstring should be generated for :type func: function/method :return: Skeleton docstring for function/method :rtype: str """ assert inspect.isfunction(func) or inspect.ismethod(func) methodArgs = func.__code__.co_varnames[:func.__code__.co_argcount] params = [ ":param %s: description" % methodArg for methodArg in methodArgs ] types = [ ":type %s: type description" % methodArg for methodArg in methodArgs ] # Remove first parameter if name is self (like a method of a class) if len(methodArgs) >= 1 and methodArgs[0] == 'self': params = params[1:] types = types[1:] returns = [] if codeReturnsSomething(func): returns = [":return: return description",":rtype: the return type description"] txt = "\n".join(params + types + returns) return txt
[docs]def testFunction(func): """ Test a function/method to see if the necessary docstring is included. Will check for line describing each parameter and its type. Will also expected at least one line with description of function. :param func: Function/method to test :type func: function/method """ funcName = func.__code__.co_name # Skip methods that start with '_' except for constructors if funcName.startswith('_') and not funcName == '__init__': return funcFilename = func.__code__.co_filename methodArgs = func.__code__.co_varnames[:func.__code__.co_argcount] params = [ ":param %s:" % methodArg for methodArg in methodArgs ] types = [ ":type %s:" % methodArg for methodArg in methodArgs ] # Remove first parameter if name is self (like a method of a class) if len(methodArgs) >= 1 and methodArgs[0] == 'self': params = params[1:] types = types[1:] returns = [] if codeReturnsSomething(func): returns = [":return:",":rtype:"] expected = params + types + returns if func.__doc__ is None: raise DocstringTestError("Missing docstring",funcFilename,funcName) docstring = func.__doc__.split('\n') docstring = [ line.strip() for line in docstring ] docstring = [ line for line in docstring if line ] docstringWithParams = [ line for line in docstring if line.startswith(':param') or line.startswith(':type') or line.startswith(':return: ') or line.startswith(':rtype: ') ] for i in range(max(len(expected),len(docstringWithParams))): if i >= len(docstringWithParams): raise DocstringTestError("Missing '%s' in docstring" % expected[i],funcFilename,funcName) elif i >= len(expected): raise DocstringTestError("Unexpected '%s' in docstring" % docstringWithParams[i],funcFilename,funcName) elif not docstringWithParams[i].startswith(expected[i]): raise DocstringTestError("Missing '%s' in docstring" % expected[i],funcFilename,funcName) # If we have exactly the parameter info and nothing else, then we're missing a description (of some length) about what the function does if len(expected) == len(docstring): raise DocstringTestError("Missing description of function in docstring" ,funcFilename,funcName)
[docs]def testClass(c): """ Test all the docstrings for methods in a class :param c: Class to test :type c: class """ for name,obj in inspect.getmembers(c): if inspect.ismethod(obj) or inspect.isfunction(obj): testFunction(obj)
[docs]def testModule(m): """ Test all the docstrings for classes/functions/methods in a module :param m: Module to test :type m: module """ for name,obj in inspect.getmembers(m): if inspect.isclass(obj): testClass(obj) elif inspect.isfunction(obj): testFunction(obj)