From c99f39381d67bc059b8cd39d6fc7fdbe870ff866 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Wed, 15 Nov 2017 18:33:32 +1100 Subject: [PATCH 1/7] Command-line tool to help debug NumpyDocString --- numpydoc/docscrape_sphinx.py | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index ff5be26c..d4722e19 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -421,3 +421,42 @@ def get_doc_object(obj, what=None, doc=None, config={}, builder=None): if doc is None: doc = pydoc.getdoc(obj) return SphinxObjDoc(obj, doc, config=config) + + +if __name__ == '__main__': + import argparse + import importlib + import ast + + descr = 'Test numpydoc docstring generation for a given object' + ap = argparse.ArgumentParser(description=descr) + ap.add_argument('import_path', help='e.g. numpy.ndarray') + + def _parse_config(s): + key, _, value = s.partition('=') + value = ast.literal_eval(value) + return key, value + + ap.add_argument('-c', '--config', type=_parse_config, + action='append', + help='key=val where val will be parsed by literal_eval, ' + 'e.g. -c use_plots=True. Multiple -c can be used.') + args = ap.parse_args() + + parts = args.import_path.split('.') + + for split_point in range(len(parts), 0, -1)[::-1]: + try: + path = '.'.join(parts[:split_point]) + obj = importlib.import_module(path) + except ImportError: + continue + break + else: + raise ImportError('Could not resolve {!r} to an importable object' + ''.format(args.import_path)) + + for part in parts[split_point:]: + obj = getattr(obj, part) + + print(get_doc_object(obj, config=dict(args.config))) From a30eb9348c0acaf75d875f06fd13f6d8fc3fe5fd Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Wed, 15 Nov 2017 19:26:01 +1100 Subject: [PATCH 2/7] handle config is None --- numpydoc/docscrape_sphinx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index d4722e19..48dd826e 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -459,4 +459,4 @@ def _parse_config(s): for part in parts[split_point:]: obj = getattr(obj, part) - print(get_doc_object(obj, config=dict(args.config))) + print(get_doc_object(obj, config=dict(args.config or []))) From 9469f593bc3e51fb95bfbe580b61c3dfa3d4d032 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Wed, 28 Mar 2018 17:40:09 +1100 Subject: [PATCH 3/7] Move to numpydoc.__main__. Add test --- numpydoc/__main__.py | 44 +++++++++++++++++++++++++++++++++++++ numpydoc/tests/test_main.py | 12 ++++++++++ 2 files changed, 56 insertions(+) create mode 100644 numpydoc/__main__.py create mode 100644 numpydoc/tests/test_main.py diff --git a/numpydoc/__main__.py b/numpydoc/__main__.py new file mode 100644 index 00000000..23be9c84 --- /dev/null +++ b/numpydoc/__main__.py @@ -0,0 +1,44 @@ +import argparse +import importlib +import ast + +from .docscrape_sphinx import get_doc_object + + +def main(argv=None): + """Test numpydoc docstring generation for a given object""" + + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('import_path', help='e.g. numpy.ndarray') + + def _parse_config(s): + key, _, value = s.partition('=') + value = ast.literal_eval(value) + return key, value + + ap.add_argument('-c', '--config', type=_parse_config, + action='append', + help='key=val where val will be parsed by literal_eval, ' + 'e.g. -c use_plots=True. Multiple -c can be used.') + args = ap.parse_args(argv) + + parts = args.import_path.split('.') + + for split_point in range(len(parts), 0, -1)[::-1]: + try: + path = '.'.join(parts[:split_point]) + obj = importlib.import_module(path) + except ImportError: + continue + break + else: + raise ImportError('Could not resolve {!r} to an importable object' + ''.format(args.import_path)) + + for part in parts[split_point:]: + obj = getattr(obj, part) + + print(get_doc_object(obj, config=dict(args.config or []))) + +if __name__ == '__main__': + main() diff --git a/numpydoc/tests/test_main.py b/numpydoc/tests/test_main.py new file mode 100644 index 00000000..905dc3e7 --- /dev/null +++ b/numpydoc/tests/test_main.py @@ -0,0 +1,12 @@ +from numpydoc.__main__ import main + +import sys +import io + + +def test_main(): + f = io.StringIO() + sys.stdout, old_stdout = f, sys.stdout + main(['numpydoc.__main__.main']) + assert f.getvalue().strip() == main.__doc__ + sys.stdout = old_stdout From 65cd2866c4000f399c48d8215229930210457578 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Wed, 28 Mar 2018 17:40:32 +1100 Subject: [PATCH 4/7] Remove from docscrape_sphinx --- numpydoc/docscrape_sphinx.py | 39 ------------------------------------ 1 file changed, 39 deletions(-) diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index a3f3bb2e..640efa7b 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -423,42 +423,3 @@ def get_doc_object(obj, what=None, doc=None, config={}, builder=None): if doc is None: doc = pydoc.getdoc(obj) return SphinxObjDoc(obj, doc, config=config) - - -if __name__ == '__main__': - import argparse - import importlib - import ast - - descr = 'Test numpydoc docstring generation for a given object' - ap = argparse.ArgumentParser(description=descr) - ap.add_argument('import_path', help='e.g. numpy.ndarray') - - def _parse_config(s): - key, _, value = s.partition('=') - value = ast.literal_eval(value) - return key, value - - ap.add_argument('-c', '--config', type=_parse_config, - action='append', - help='key=val where val will be parsed by literal_eval, ' - 'e.g. -c use_plots=True. Multiple -c can be used.') - args = ap.parse_args() - - parts = args.import_path.split('.') - - for split_point in range(len(parts), 0, -1)[::-1]: - try: - path = '.'.join(parts[:split_point]) - obj = importlib.import_module(path) - except ImportError: - continue - break - else: - raise ImportError('Could not resolve {!r} to an importable object' - ''.format(args.import_path)) - - for part in parts[split_point:]: - obj = getattr(obj, part) - - print(get_doc_object(obj, config=dict(args.config or []))) From 6d5d44930358a31a538bc067129ccbe2b2387107 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Wed, 28 Mar 2018 23:45:13 +1100 Subject: [PATCH 5/7] Add documentation --- doc/index.rst | 1 + doc/validation.rst | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 doc/validation.rst diff --git a/doc/index.rst b/doc/index.rst index 39d8e5bd..b6123953 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -22,3 +22,4 @@ Documentation install format example + validation diff --git a/doc/validation.rst b/doc/validation.rst new file mode 100644 index 00000000..c8516683 --- /dev/null +++ b/doc/validation.rst @@ -0,0 +1,12 @@ +============================== +Validating NumpyDoc docstrings +============================== + +One tool for validating docstrings is to see how an object's dosctring +translates to Restructured Text. Using numpydoc as a command-line tool +facilitates this. For example to see the Restructured Text generated +for ``numpy.ndarray``, use: + +.. code-block:: bash + + $ python -m numpydoc numpy.ndarray From 91a78dfa36015f77a84073f100caa01bff097c75 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Sun, 1 Apr 2018 22:38:57 +1000 Subject: [PATCH 6/7] Use StringIO.StringIO in Py2 --- numpydoc/tests/test_main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/numpydoc/tests/test_main.py b/numpydoc/tests/test_main.py index 905dc3e7..bac73600 100644 --- a/numpydoc/tests/test_main.py +++ b/numpydoc/tests/test_main.py @@ -1,11 +1,14 @@ from numpydoc.__main__ import main import sys -import io +try: + from StringIO import StringIO +except ImportError: + from io import StringIO def test_main(): - f = io.StringIO() + f = StringIO() sys.stdout, old_stdout = f, sys.stdout main(['numpydoc.__main__.main']) assert f.getvalue().strip() == main.__doc__ From faafc79ef7f2b9efa5dc2279f29880b24be5a209 Mon Sep 17 00:00:00 2001 From: Joel Nothman Date: Mon, 2 Apr 2018 14:26:45 +1000 Subject: [PATCH 7/7] Improve importing in main --- numpydoc/__main__.py | 2 +- numpydoc/tests/test_main.py | 75 ++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/numpydoc/__main__.py b/numpydoc/__main__.py index 23be9c84..25f58cf8 100644 --- a/numpydoc/__main__.py +++ b/numpydoc/__main__.py @@ -24,7 +24,7 @@ def _parse_config(s): parts = args.import_path.split('.') - for split_point in range(len(parts), 0, -1)[::-1]: + for split_point in range(len(parts), 0, -1): try: path = '.'.join(parts[:split_point]) obj = importlib.import_module(path) diff --git a/numpydoc/tests/test_main.py b/numpydoc/tests/test_main.py index bac73600..e565bb29 100644 --- a/numpydoc/tests/test_main.py +++ b/numpydoc/tests/test_main.py @@ -1,15 +1,80 @@ -from numpydoc.__main__ import main +from __future__ import print_function +from contextlib import contextmanager +import os import sys +import tempfile try: from StringIO import StringIO except ImportError: from io import StringIO +from numpydoc.__main__ import main -def test_main(): + +PACKAGE_CODE = """ +'''This package has test stuff''' +""" + +MODULE_CODE = """ +'''This module has test stuff''' + +def foo(a, b=5): + '''Hello world + + Parameters + ---------- + something : foo + bar + something_else + bar + ''' +""" + + +@contextmanager +def _mock_module(pkg_name): + try: + tempdir = tempfile.mkdtemp() + os.mkdir(os.path.join(tempdir, pkg_name)) + with open(os.path.join(tempdir, pkg_name, '__init__.py'), 'w') as f: + print(PACKAGE_CODE, file=f) + with open(os.path.join(tempdir, pkg_name, 'module.py'), 'w') as f: + print(MODULE_CODE, file=f) + + sys.path.insert(0, tempdir) + yield tempdir + finally: + try: + os.path.rmdir(tempdir) + sys.path.remove(tempdir) + except: + pass + + +def _capture_main(*args): f = StringIO() sys.stdout, old_stdout = f, sys.stdout - main(['numpydoc.__main__.main']) - assert f.getvalue().strip() == main.__doc__ - sys.stdout = old_stdout + try: + main(args) + return f.getvalue().strip('\n\r') + finally: + sys.stdout = old_stdout + + +def test_main(): + # TODO: does not currently check that numpydoc transformations are applied + + assert (_capture_main('numpydoc.__main__.main') == + main.__doc__.strip()) + + # check it works with modules not imported from __init__ + with _mock_module('somepackage1'): + out = _capture_main('somepackage1.module.foo') + assert out.startswith('Hello world\n') + with _mock_module('somepackage2'): + out = _capture_main('somepackage2.module') + assert out.startswith('This module has test') + with _mock_module('somepackage3'): + out = _capture_main('somepackage3') + assert out.startswith('This package has test')