Skip to content

Commit 39a2e0a

Browse files
FHaaseJustinZhengBC
authored andcommitted
DOC: Use flake8 to check for PEP8 violations in doctests (pandas-dev#23399)
1 parent e938ed4 commit 39a2e0a

File tree

2 files changed

+86
-7
lines changed

2 files changed

+86
-7
lines changed

scripts/tests/test_validate_docstrings.py

+54-6
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,9 @@ def good_imports(self):
225225
Examples
226226
--------
227227
This example does not import pandas or import numpy.
228-
>>> import time
229228
>>> import datetime
229+
>>> datetime.MAXYEAR
230+
9999
230231
"""
231232
pass
232233

@@ -596,6 +597,44 @@ def prefix_pandas(self):
596597
pass
597598

598599

600+
class BadExamples(object):
601+
602+
def unused_import(self):
603+
"""
604+
Examples
605+
--------
606+
>>> import pandas as pdf
607+
>>> df = pd.DataFrame(np.ones((3, 3)), columns=('a', 'b', 'c'))
608+
"""
609+
pass
610+
611+
def missing_whitespace_around_arithmetic_operator(self):
612+
"""
613+
Examples
614+
--------
615+
>>> 2+5
616+
7
617+
"""
618+
pass
619+
620+
def indentation_is_not_a_multiple_of_four(self):
621+
"""
622+
Examples
623+
--------
624+
>>> if 2 + 5:
625+
... pass
626+
"""
627+
pass
628+
629+
def missing_whitespace_after_comma(self):
630+
"""
631+
Examples
632+
--------
633+
>>> df = pd.DataFrame(np.ones((3,3)),columns=('a','b', 'c'))
634+
"""
635+
pass
636+
637+
599638
class TestValidator(object):
600639

601640
def _import_path(self, klass=None, func=None):
@@ -634,7 +673,7 @@ def test_good_class(self):
634673
@capture_stderr
635674
@pytest.mark.parametrize("func", [
636675
'plot', 'sample', 'random_letters', 'sample_values', 'head', 'head1',
637-
'contains', 'mode'])
676+
'contains', 'mode', 'good_imports'])
638677
def test_good_functions(self, func):
639678
errors = validate_one(self._import_path(
640679
klass='GoodDocStrings', func=func))['errors']
@@ -714,16 +753,25 @@ def test_bad_generic_functions(self, func):
714753
marks=pytest.mark.xfail),
715754
# Examples tests
716755
('BadGenericDocStrings', 'method',
717-
('numpy does not need to be imported in the examples,')),
756+
('numpy does not need to be imported in the examples',)),
718757
('BadGenericDocStrings', 'method',
719-
('pandas does not need to be imported in the examples,')),
758+
('pandas does not need to be imported in the examples',)),
720759
# See Also tests
721760
('BadSeeAlso', 'prefix_pandas',
722761
('pandas.Series.rename in `See Also` section '
723-
'does not need `pandas` prefix',))
762+
'does not need `pandas` prefix',)),
763+
# Examples tests
764+
('BadExamples', 'unused_import',
765+
('1 F401 \'pandas as pdf\' imported but unused',)),
766+
('BadExamples', 'indentation_is_not_a_multiple_of_four',
767+
('1 E111 indentation is not a multiple of four',)),
768+
('BadExamples', 'missing_whitespace_around_arithmetic_operator',
769+
('1 E226 missing whitespace around arithmetic operator',)),
770+
('BadExamples', 'missing_whitespace_after_comma',
771+
('3 E231 missing whitespace after \',\'',)),
724772
])
725773
def test_bad_examples(self, capsys, klass, func, msgs):
726-
result = validate_one(self._import_path(klass=klass, func=func)) # noqa:F821
774+
result = validate_one(self._import_path(klass=klass, func=func))
727775
for msg in msgs:
728776
assert msg in ' '.join(result['errors'])
729777

scripts/validate_docstrings.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
import inspect
2525
import importlib
2626
import doctest
27+
import tempfile
28+
29+
import flake8.main.application
30+
2731
try:
2832
from io import StringIO
2933
except ImportError:
@@ -168,7 +172,7 @@ def _load_obj(name):
168172
@staticmethod
169173
def _to_original_callable(obj):
170174
"""
171-
Find the Python object that contains the source code ot the object.
175+
Find the Python object that contains the source code of the object.
172176
173177
This is useful to find the place in the source code (file and line
174178
number) where a docstring is defined. It does not currently work for
@@ -407,6 +411,26 @@ def examples_source_code(self):
407411
lines = doctest.DocTestParser().get_examples(self.raw_doc)
408412
return [line.source for line in lines]
409413

414+
def validate_pep8(self):
415+
if not self.examples:
416+
return
417+
418+
content = ''.join(('import numpy as np # noqa: F401\n',
419+
'import pandas as pd # noqa: F401\n',
420+
*self.examples_source_code))
421+
422+
application = flake8.main.application.Application()
423+
application.initialize(["--quiet"])
424+
425+
with tempfile.NamedTemporaryFile(mode='w') as file:
426+
file.write(content)
427+
file.flush()
428+
application.run_checks([file.name])
429+
430+
application.report()
431+
432+
yield from application.guide.stats.statistics_for('')
433+
410434

411435
def validate_one(func_name):
412436
"""
@@ -495,6 +519,13 @@ def validate_one(func_name):
495519
for param_err in param_errs:
496520
errs.append('\t{}'.format(param_err))
497521

522+
pep8_errs = list(doc.validate_pep8())
523+
if pep8_errs:
524+
errs.append('Linting issues in doctests:')
525+
for err in pep8_errs:
526+
errs.append('\t{} {} {}'.format(err.count, err.error_code,
527+
err.message))
528+
498529
if doc.is_function_or_method:
499530
if not doc.returns and "return" in doc.method_source:
500531
errs.append('No Returns section found')

0 commit comments

Comments
 (0)