14
14
$ ./validate_docstrings.py pandas.DataFrame.head
15
15
"""
16
16
import os
17
+ import subprocess
17
18
import sys
18
19
import json
19
20
import re
24
25
import inspect
25
26
import importlib
26
27
import doctest
28
+
27
29
try :
28
30
from io import StringIO
29
31
except ImportError :
40
42
from numpydoc .docscrape import NumpyDocString
41
43
from pandas .io .formats .printing import pprint_thing
42
44
43
-
44
45
PRIVATE_CLASSES = ['NDFrame' , 'IndexOpsMixin' ]
45
46
DIRECTIVES = ['versionadded' , 'versionchanged' , 'deprecated' ]
46
47
@@ -446,7 +447,7 @@ def validate_one(func_name):
446
447
if doc .summary != doc .summary .lstrip ():
447
448
errs .append ('Summary contains heading whitespaces.' )
448
449
elif (doc .is_function_or_method
449
- and doc .summary .split (' ' )[0 ][- 1 ] == 's' ):
450
+ and doc .summary .split (' ' )[0 ][- 1 ] == 's' ):
450
451
errs .append ('Summary must start with infinitive verb, '
451
452
'not third person (e.g. use "Generate" instead of '
452
453
'"Generates")' )
@@ -529,15 +530,16 @@ def validate_one(func_name):
529
530
if examples_errs :
530
531
errs .append ('Examples do not pass tests' )
531
532
532
- return {'type' : doc .type ,
533
- 'docstring' : doc .clean_doc ,
534
- 'deprecated' : doc .deprecated ,
535
- 'file' : doc .source_file_name ,
536
- 'file_line' : doc .source_file_def_line ,
537
- 'github_link' : doc .github_url ,
538
- 'errors' : errs ,
539
- 'warnings' : wrns ,
540
- 'examples_errors' : examples_errs }
533
+ return ({'type' : doc .type ,
534
+ 'docstring' : doc .clean_doc ,
535
+ 'deprecated' : doc .deprecated ,
536
+ 'file' : doc .source_file_name ,
537
+ 'file_line' : doc .source_file_def_line ,
538
+ 'github_link' : doc .github_url ,
539
+ 'errors' : errs ,
540
+ 'warnings' : wrns ,
541
+ 'examples_errors' : examples_errs },
542
+ doc )
541
543
542
544
543
545
def validate_all ():
@@ -598,10 +600,15 @@ def header(title, width=80, char='#'):
598
600
full_line = full_line , title_line = title_line )
599
601
600
602
if func_name is None :
603
+ flake8 = _call_flake8_plugin ()
604
+ if flake8 :
605
+ fd .write ('Flake8 reported issues:\n ' )
606
+ fd .write (flake8 )
607
+
601
608
json_doc = validate_all ()
602
609
fd .write (json .dumps (json_doc ))
603
610
else :
604
- doc_info = validate_one (func_name )
611
+ doc_info , doc = validate_one (func_name )
605
612
606
613
fd .write (header ('Docstring ({})' .format (func_name )))
607
614
fd .write ('{}\n ' .format (doc_info ['docstring' ]))
@@ -615,14 +622,40 @@ def header(title, width=80, char='#'):
615
622
for wrn in doc_info ['warnings' ]:
616
623
fd .write ('\t {}\n ' .format (wrn ))
617
624
618
- if not doc_info ['errors' ]:
625
+ flake8 = _call_flake8_plugin (doc .source_file_name )
626
+ if flake8 :
627
+ fd .write ('Flake8 reported issues:\n ' )
628
+ fd .write (flake8 )
629
+
630
+ if not doc_info ['errors' ] and not flake8 :
619
631
fd .write ('Docstring for "{}" correct. :)\n ' .format (func_name ))
620
632
621
633
if doc_info ['examples_errors' ]:
622
634
fd .write (header ('Doctests' ))
623
635
fd .write (doc_info ['examples_errors' ])
624
636
625
637
638
+ def _call_flake8_plugin (* source_file_name ):
639
+ sub_cmds = (['flake8' , '--doctest' , * source_file_name ],
640
+ ['flake8-rst' , * source_file_name ])
641
+
642
+ check_output = [_check_output_safe (sub_cmd ) for sub_cmd in sub_cmds ]
643
+ output = [output for output in check_output if output ]
644
+ return '\n \n ' .join (output ) if output else None
645
+
646
+
647
+ def _check_output_safe (sub_cmd ):
648
+ try :
649
+ output = subprocess .check_output (sub_cmd )
650
+ except subprocess .CalledProcessError as e :
651
+ output = e .output
652
+ if output :
653
+ return '\n ' .join (['Invoking command "{}" returned:'
654
+ .format (' ' .join (sub_cmd )), output .decode ("utf-8" )])
655
+ else :
656
+ return None
657
+
658
+
626
659
if __name__ == '__main__' :
627
660
func_help = ('function or method to validate (e.g. pandas.DataFrame.head) '
628
661
'if not provided, all docstrings are validated and returned '
0 commit comments