def check_docstring(self, f): """ Ensure the docstring of `f` is in accordance with the numpy standard Currently, only the Parameters section of the docstring is checked. Parameters ---------- f : function, method, class Returns ------- repr : string A string represntation """ doc = getdoc(f) if doc is None: if error_on_none: raise ValueError('no docstring for %s' % format(f)) else: with warnings.catch_warnings(): warnings.simplefilter('error') parsed = NumpyDocString(doc) param_names = set([e[0] for e in parsed['Parameters']]) if isbuiltin(f): # You can't get the arglist from a builtin, which # is how cython functions turn up # but you can, hackily, get the number of arguments it wants # by parseing the error hen you supply too many import re try: f(*list(range(100))) except TypeError as e: m = re.search('takes at most (\d+) positional arguments', str(e)) if not m: return n_args = int(m.group(1)) if len(param_names) != n_args: raise ValueError( "In %s, number of arguments, %d, doesn't " " match the length of the Parameters in the " "docstring, %d" % (format(f), n_args, len(param_names))) return args = set(getargs(get_function_code(f)).args) if 'self' in args: args.remove('self') if args != param_names: raise ValueError("In %s, arguments %s don't " "match Parameters list %s" % (format(f), list(args), list(param_names)))
def check_docstring(f): """ Ensure the docstring of `f` is in accordance with the numpy standard Currently, only the Parameters section of the docstring is checked. Parameters ---------- f : function, method, class Returns ------- repr : string A string represntation """ doc = getdoc(f) if doc is None: if error_on_none: raise ValueError('no docstring for %s' % format(f)) else: with warnings.catch_warnings(): warnings.simplefilter('error') parsed = NumpyDocString(doc) param_names = set([e[0] for e in parsed['Parameters']]) if isbuiltin(f): # You can't get the arglist from a builtin, which # is how cython functions turn up # but you can, hackily, get the number of arguments it wants # by parseing the error hen you supply too many import re try: f(*list(range(100))) except TypeError as e: m = re.search('takes at most (\d+) positional arguments', str(e)) if not m: return n_args = int(m.group(1)) if len(param_names) != n_args: raise ValueError("In %s, number of arguments, %d, doesn't " " match the length of the Parameters in the " "docstring, %d" % (format(f), n_args, len(param_names))) return args = set(getargs(get_function_code(f)).args) if 'self' in args: args.remove('self') if 'cls' in args: args.remove('cls') if args != param_names: raise ValueError("In %s, arguments %s don't " "match Parameters list %s" % (format(f), list(args), list(param_names)))
def DocStringFormatTester(module, error_on_none=False): """ Create a class that tests the docstrings of a python module for adherance to the numpy docstring format, for use with Nose. Parameters ---------- module : module The module to test error_on_none : bool Throw an error if no docstring is defined Notes ----- For example, test_trajectory.py in mdtraj contains the line: TestDocstrings = DocStringFormatTester(mdtraj.trajectory) TestDocstrings is now a class with one method defined per class/function in mdtraj.trajectory. Each method, when called, validates a single docstring. When nosetests runs that file (in verbose mode), you see: NumpyDoc: mdtraj.trajectory.load_xtc ... ok NumpyDoc: mdtraj.trajectory.load_pdb ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_binpos ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save ... ok NumpyDoc: mdtraj.trajectory.load ... ok NumpyDoc: mdtraj.trajectory.load_hdf ... ok NumpyDoc: mdtraj.trajectory.load_dcd ... ok NumpyDoc: mdtraj.trajectory.load_binpos ... ok NumpyDoc: mdtraj.trajectory.load ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_xtc ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_pdb ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_hdf ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_dcd ... ok """ # These are the types that we want to check # currently, the docstring on classes are not being checked, since # isclass() is not in the list acceptors = [isfunction, ismethod, isbuiltin] accept = lambda f: any([acc(f) for acc in acceptors]) functions = [f for f in walk(module) if accept(f)] def format(f): """ Format a method/function/class as a string Parameters ---------- f : function, method, class Returns ------- repr : string A string represntation """ if ismethod(f): if PY2: return '.'.join([getmodule(f).__name__, f.im_class.__name__, f.__name__]) else: return '.'.join([getmodule(f).__name__, f.__self__.__class__.__name__, f.__name__]) if isfunction(f) or isbuiltin(f): return '.'.join([getmodule(f).__name__, f.__name__]) if isclass(f): return f.__name__ return 'Error' def check_docstring(self, f): """ Ensure the docstring of `f` is in accordance with the numpy standard Currently, only the Parameters section of the docstring is checked. Parameters ---------- f : function, method, class Returns ------- repr : string A string represntation """ doc = getdoc(f) if doc is None: if error_on_none: raise ValueError('no docstring for %s' % format(f)) else: with warnings.catch_warnings(): warnings.simplefilter('error') parsed = NumpyDocString(doc) param_names = set([e[0] for e in parsed['Parameters']]) if isbuiltin(f): # You can't get the arglist from a builtin, which # is how cython functions turn up # but you can, hackily, get the number of arguments it wants # by parseing the error hen you supply too many import re try: f(*list(range(100))) except TypeError as e: m = re.search('takes at most (\d+) positional arguments', str(e)) if not m: return n_args = int(m.group(1)) if len(param_names) != n_args: raise ValueError("In %s, number of arguments, %d, doesn't " " match the length of the Parameters in the " "docstring, %d" % (format(f), n_args, len(param_names))) return args = set(getargs(get_function_code(f)).args) if 'self' in args: args.remove('self') if args != param_names: raise ValueError("In %s, arguments %s don't " "match Parameters list %s" % (format(f), list(args), list(param_names))) funcdict = {} # populate the func dict before calling type() for i, f in enumerate(functions): name = 'test_%s' % i # this is the name we give the method # create the method. this is a little complicated, but the basic # idea is that NoseTests checks the func_name, so we need to create # the method correctly. Just giving it a pointer to a closure we define # won't set func_name correctly. Instead, we make a function where # func_code is set to the func_code of check_docstring, but we add # set second argument of that function (after self) to be a default # arg -- the f that we're iterating over. method = types.FunctionType(get_function_code(check_docstring), globals(), name, (f,), get_function_closure(check_docstring)) # give the method a short docstring if PY2: method.func_doc = 'NumpyDoc: ' + format(f) else: method.__doc__ = 'NumpyDoc: ' + format(f) funcdict[name] = method return type('TestDoc', (), funcdict)
def DocStringFormatTester(module, error_on_none=False): """ Create a class that tests the docstrings of a python module for adherance to the numpy docstring format, for use with Nose. Parameters ---------- module : module The module to test error_on_none : bool Throw an error if no docstring is defined Notes ----- For example, test_trajectory.py in mdtraj contains the line: TestDocstrings = DocStringFormatTester(mdtraj.trajectory) TestDocstrings is now a class with one method defined per class/function in mdtraj.trajectory. Each method, when called, validates a single docstring. When nosetests runs that file (in verbose mode), you see: NumpyDoc: mdtraj.trajectory.load_xtc ... ok NumpyDoc: mdtraj.trajectory.load_pdb ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_binpos ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save ... ok NumpyDoc: mdtraj.trajectory.load ... ok NumpyDoc: mdtraj.trajectory.load_hdf ... ok NumpyDoc: mdtraj.trajectory.load_dcd ... ok NumpyDoc: mdtraj.trajectory.load_binpos ... ok NumpyDoc: mdtraj.trajectory.load ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_xtc ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_pdb ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_hdf ... ok NumpyDoc: mdtraj.trajectory.Trajectory.save_dcd ... ok """ # These are the types that we want to check # currently, the docstring on classes are not being checked, since # isclass() is not in the list acceptors = [isfunction, ismethod, isbuiltin] accept = lambda f: any([acc(f) for acc in acceptors]) functions = [f for f in walk(module) if accept(f)] def format(f): """ Format a method/function/class as a string Parameters ---------- f : function, method, class Returns ------- repr : string A string represntation """ if ismethod(f): if PY2: return '.'.join( [getmodule(f).__name__, f.im_class.__name__, f.__name__]) else: return '.'.join([ getmodule(f).__name__, f.__self__.__class__.__name__, f.__name__ ]) if isfunction(f) or isbuiltin(f): return '.'.join([getmodule(f).__name__, f.__name__]) if isclass(f): return f.__name__ return 'Error' def check_docstring(self, f): """ Ensure the docstring of `f` is in accordance with the numpy standard Currently, only the Parameters section of the docstring is checked. Parameters ---------- f : function, method, class Returns ------- repr : string A string represntation """ doc = getdoc(f) if doc is None: if error_on_none: raise ValueError('no docstring for %s' % format(f)) else: with warnings.catch_warnings(): warnings.simplefilter('error') parsed = NumpyDocString(doc) param_names = set([e[0] for e in parsed['Parameters']]) if isbuiltin(f): # You can't get the arglist from a builtin, which # is how cython functions turn up # but you can, hackily, get the number of arguments it wants # by parseing the error hen you supply too many import re try: f(*list(range(100))) except TypeError as e: m = re.search('takes at most (\d+) positional arguments', str(e)) if not m: return n_args = int(m.group(1)) if len(param_names) != n_args: raise ValueError( "In %s, number of arguments, %d, doesn't " " match the length of the Parameters in the " "docstring, %d" % (format(f), n_args, len(param_names))) return args = set(getargs(get_function_code(f)).args) if 'self' in args: args.remove('self') if args != param_names: raise ValueError("In %s, arguments %s don't " "match Parameters list %s" % (format(f), list(args), list(param_names))) funcdict = {} # populate the func dict before calling type() for i, f in enumerate(functions): name = 'test_%s' % i # this is the name we give the method # create the method. this is a little complicated, but the basic # idea is that NoseTests checks the func_name, so we need to create # the method correctly. Just giving it a pointer to a closure we define # won't set func_name correctly. Instead, we make a function where # func_code is set to the func_code of check_docstring, but we add # set second argument of that function (after self) to be a default # arg -- the f that we're iterating over. method = types.FunctionType(get_function_code(check_docstring), globals(), name, (f, ), get_function_closure(check_docstring)) # give the method a short docstring if PY2: method.func_doc = 'NumpyDoc: ' + format(f) else: method.__doc__ = 'NumpyDoc: ' + format(f) funcdict[name] = method return type('TestDoc', (), funcdict)