diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 3053625721560..58f33f8a6f445 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -201,6 +201,7 @@ Other Enhancements - :meth:`Index.to_frame` now supports overriding column name(s) (:issue:`22580`). - New attribute :attr:`__git_version__` will return git commit sha of current build (:issue:`21295`). - Compatibility with Matplotlib 3.0 (:issue:`22790`). +- Validate docstring examples are PEP-8 compliant (:issue:`23154`) .. _whatsnew_0240.api_breaking: diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py index 6588522331433..2b746b1d0a8fe 100755 --- a/scripts/validate_docstrings.py +++ b/scripts/validate_docstrings.py @@ -23,6 +23,7 @@ import pydoc import inspect import importlib +import contextlib import doctest try: from io import StringIO @@ -40,6 +41,8 @@ from numpydoc.docscrape import NumpyDocString from pandas.io.formats.printing import pprint_thing +from pycodestyle import Checker + PRIVATE_CLASSES = ['NDFrame', 'IndexOpsMixin'] DIRECTIVES = ['versionadded', 'versionchanged', 'deprecated'] @@ -112,6 +115,11 @@ def get_api_items(api_doc_fd): previous_line = line +class DocstringExampleChecker(Checker): + def set_lines(self, lines): + self.lines = lines + + class Docstring(object): def __init__(self, name): self.name = name @@ -512,6 +520,25 @@ def validate_one(func_name): examples_errs = doc.examples_errors if examples_errs: errs.append('Examples do not pass tests') + example_checker = DocstringExampleChecker() + example_list = doctest.DocTestParser().get_examples(doc.raw_doc) + example_checker.set_lines([ex.source for ex in example_list]) + + # check_all will print the error to stdout, so we suppress this + with open(os.devnull, "w") as f, contextlib.redirect_stdout(f): + num_errs = example_checker.check_all() + if num_errs > 0: + # returns a tuple of error information + err_report = example_checker.report._deferred_print + for line_number, offset, code, text, doc in err_report: + # module level import not at top of file + if code == 'E402': + continue + # multi-line assignments report E501 incorrectly + elif code == 'E501': + continue + errs.append('{}: {} {}:{}:{}'.format( + code, text, func_name, line_number, offset)) return {'type': doc.type, 'docstring': doc.clean_doc,