Skip to content

[WIP] Add a script to check discrepancies between function parameter list and function docstring parameters #7793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.autosummary',
'numpy_ext.numpydoc',
'sklearn.externals.numpy_ext.numpydoc',
'sphinx.ext.linkcode', 'sphinx.ext.doctest',
'sphinx_gallery.gen_gallery',
'sphinx_issues',
Expand Down
1 change: 1 addition & 0 deletions sklearn/externals/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ def configuration(parent_package='', top_path=None):
from numpy.distutils.misc_util import Configuration
config = Configuration('externals', parent_package, top_path)
config.add_subpackage('joblib')
config.add_subpackage('numpy_ext')

return config
149 changes: 149 additions & 0 deletions sklearn/tests/test_docstring_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""Check discrepancies between docstring parameters and function signature."""

from pkgutil import walk_packages
from sklearn.externals.numpy_ext import docscrape

import inspect
import warnings

import sklearn


_docstring_ignores = [
'sklearn.externals'
]


def get_name(func):
"""
Return name of a function.

Parameters
----------
func : Python function

Returns
-------
name : Function name with complete namespace

"""
parts = []
module = inspect.getmodule(func)
if module:
parts.append(module.__name__)
if hasattr(func, 'im_class'):
parts.append(func.im_class.__name__)
parts.append(func.__name__)
return '.'.join(parts)


def get_all_modules():
"""Return all module names of sklearn as an array."""
modules = []
for importer, modname, ispkg in \
walk_packages(sklearn.__path__, prefix='sklearn.'):
if ispkg:
modules.append(modname)
return modules


def check_parameters_match(func, doc=None):
"""
Check docstring for given function.

Parameters
----------
func : Function for which docstring is to be checked
doc : (Optional) As returned from numpydoc docscrape

Returns
-------
incorrect : Array of error strings

"""
incorrect = []
name_ = get_name(func)

# skip deprecated and data descriptors
if not name_.startswith('sklearn.') or inspect.isdatadescriptor(func) or \
any(d in name_ for d in _docstring_ignores):
return incorrect

args = inspect.getargspec(func)[0]

# drop self
if len(args) > 0 and args[0] == 'self':
args = args[1:]

if doc is None:
with warnings.catch_warnings(record=True) as w:
doc = docscrape.FunctionDoc(func)
if len(w):
raise RuntimeError('Error for %s:%s' % (name_, w[0]))

param_names = [name for name, _, _ in doc['Parameters']]
param_names = [name.split(':')[0].strip('` ') for name in param_names]
param_names = [name for name in param_names if '*' not in name]

# parameters that are present in docstring but not in signature
sign_missing_params = sorted(list(set(param_names) - set(args)))
if len(sign_missing_params):
incorrect += [
name_ + ' => params present in docstring but not in signature: ' +
', '.join(sign_missing_params)]

# parameters that are present in signature but not in docstring
doc_missing_params = sorted(list(set(args) - set(param_names)))
if len(doc_missing_params):
incorrect += [
name_ + ' => params present in signature but not in docstring: ' +
', '.join(doc_missing_params)]

return incorrect


def test_docstring_parameters():
"""Test module docstring formatting."""
public_modules = get_all_modules()
incorrect = []

for name in public_modules:
if name.endswith('tests'):
continue

module = __import__(name, globals(), locals(), ['object'], 0)
# check for classes in the module
classes = inspect.getmembers(module, inspect.isclass)
for cname, cls in classes:
if cname.startswith('_'):
continue

with warnings.catch_warnings(record=True) as w:
cdoc = docscrape.ClassDoc(cls)
if len(w):
raise RuntimeError(
'Error for __init__ of %s in %s:\n%s' % (cls, name, w[0]))

# check for __init__ method of class
if hasattr(cls, '__init__'):
incorrect += check_parameters_match(cls.__init__, cdoc)

# check for all methods of class
for method_name in cdoc.methods:
method = getattr(cls, method_name)
incorrect += check_parameters_match(method)

# check for __call__ method of class
if hasattr(cls, '__call__'):
incorrect += check_parameters_match(cls.__call__)

# check for functions in module
functions = inspect.getmembers(module, inspect.isfunction)
for fname, func in functions:
if fname.startswith('_'):
continue
incorrect += check_parameters_match(func)

msg = '\n' + '\n'.join(sorted(list(set(incorrect))))
if len(incorrect) > 0:
raise AssertionError(msg)