diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 074a7f73..45883d00 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -136,7 +136,7 @@ def __getitem__(self, key): def __setitem__(self, key, val): if key not in self._parsed_data: - warn("Unknown section %s" % key) + self._error_location("Unknown section %s" % key, error=False) else: self._parsed_data[key] = val @@ -331,19 +331,8 @@ def _parse(self): section = (s.capitalize() for s in section.split(' ')) section = ' '.join(section) if self.get(section): - if hasattr(self, '_obj'): - # we know where the docs came from: - try: - filename = inspect.getsourcefile(self._obj) - except TypeError: - filename = None - msg = ("The section %s appears twice in " - "the docstring of %s in %s." % - (section, self._obj, filename)) - raise ValueError(msg) - else: - msg = ("The section %s appears twice" % section) - raise ValueError(msg) + self._error_location("The section %s appears twice" + % section) if section in ('Parameters', 'Returns', 'Yields', 'Raises', 'Warns', 'Other Parameters', 'Attributes', @@ -356,6 +345,20 @@ def _parse(self): else: self[section] = content + def _error_location(self, msg, error=True): + if hasattr(self, '_obj'): + # we know where the docs came from: + try: + filename = inspect.getsourcefile(self._obj) + except TypeError: + filename = None + msg = msg + (" in the docstring of %s in %s." + % (self._obj, filename)) + if error: + raise ValueError(msg) + else: + warn(msg) + # string conversion routines def _str_header(self, name, symbol='-'): diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 297a0acb..2dc45e36 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -3,6 +3,7 @@ import sys import textwrap +import warnings import jinja2 @@ -151,13 +152,16 @@ def test_signature(): assert doc['Signature'].startswith('numpy.multivariate_normal(') assert doc['Signature'].endswith('spam=None)') + def test_summary(): assert doc['Summary'][0].startswith('Draw values') assert doc['Summary'][-1].endswith('covariance.') + def test_extended_summary(): assert doc['Extended Summary'][0].startswith('The multivariate normal') + def test_parameters(): assert_equal(len(doc['Parameters']), 3) assert_equal([n for n,_,_ in doc['Parameters']], ['mean','cov','shape']) @@ -167,6 +171,7 @@ def test_parameters(): assert desc[0].startswith('Covariance matrix') assert doc['Parameters'][0][-1][-2] == ' (1+2+3)/3' + def test_other_parameters(): assert_equal(len(doc['Other Parameters']), 1) assert_equal([n for n,_,_ in doc['Other Parameters']], ['spam']) @@ -174,6 +179,7 @@ def test_other_parameters(): assert_equal(arg_type, 'parrot') assert desc[0].startswith('A parrot off its mortal coil') + def test_returns(): assert_equal(len(doc['Returns']), 2) arg, arg_type, desc = doc['Returns'][0] @@ -188,6 +194,7 @@ def test_returns(): assert desc[0].startswith('This is not a real') assert desc[-1].endswith('anonymous return values.') + def test_yields(): section = doc_yields['Yields'] assert_equal(len(section), 3) @@ -200,6 +207,7 @@ def test_yields(): assert desc[0].startswith('The number of') assert desc[0].endswith(end) + def test_returnyield(): doc_text = """ Test having returns and yields. @@ -289,26 +297,31 @@ def test_notes(): assert doc['Notes'][-1].endswith('definite.') assert_equal(len(doc['Notes']), 17) + def test_references(): assert doc['References'][0].startswith('..') assert doc['References'][-1].endswith('2001.') + def test_examples(): assert doc['Examples'][0].startswith('>>>') assert doc['Examples'][-1].endswith('True]') + def test_index(): assert_equal(doc['index']['default'], 'random') assert_equal(len(doc['index']), 2) assert_equal(len(doc['index']['refguide']), 2) -def non_blank_line_by_line_compare(a,b): + +def non_blank_line_by_line_compare(a, b): a = textwrap.dedent(a) b = textwrap.dedent(b) a = [l.rstrip() for l in a.split('\n') if l.strip()] b = [l.rstrip() for l in b.split('\n') if l.strip()] assert_list_equal(a, b) + def test_str(): # doc_txt has the order of Notes and See Also sections flipped. # This should be handled automatically, and so, one thing this test does @@ -595,15 +608,18 @@ def test_sphinx_yields_str(): If None, the index is into the flattened array, otherwise along the specified axis""") + def test_parameters_without_extended_description(): assert_equal(len(doc2['Parameters']), 2) + doc3 = NumpyDocString(""" my_signature(*params, **kwds) Return this and that. """) + def test_escape_stars(): signature = str(doc3).split('\n')[0] assert_equal(signature, 'my_signature(\*params, \*\*kwds)') @@ -614,14 +630,17 @@ def my_func(a, b, **kwargs): fdoc = FunctionDoc(func=my_func) assert_equal(fdoc['Signature'], 'my_func(a, b, \*\*kwargs)') + doc4 = NumpyDocString( """a.conj() Return an array with all complex-valued elements conjugated.""") + def test_empty_extended_summary(): assert_equal(doc4['Extended Summary'], []) + doc5 = NumpyDocString( """ a.something() @@ -637,18 +656,21 @@ def test_empty_extended_summary(): If needed """) + def test_raises(): assert_equal(len(doc5['Raises']), 1) name,_,desc = doc5['Raises'][0] assert_equal(name,'LinAlgException') assert_equal(desc,['If array is singular.']) + def test_warns(): assert_equal(len(doc5['Warns']), 1) name,_,desc = doc5['Warns'][0] assert_equal(name,'SomeWarning') assert_equal(desc,['If needed']) + def test_see_also(): doc6 = NumpyDocString( """ @@ -726,12 +748,45 @@ class Dummy(object): assert(' some relationship' in s) assert(':func:`func_d`' in s) + +def test_unknown_section(): + doc_text = """ +Test having an unknown section + +Mope +---- +This should be ignored and warned about +""" + + class BadSection(object): + """Class with bad section. + + Nope + ---- + This class has a nope section. + """ + pass + + with warnings.catch_warnings(record=True) as w: + NumpyDocString(doc_text) + assert len(w) == 1 + assert "Unknown section Mope" == str(w[0].message) + + with warnings.catch_warnings(record=True) as w: + SphinxClassDoc(BadSection) + assert len(w) == 1 + assert_true('test_docscrape.test_unknown_section..BadSection' + in str(w[0].message) + or 'test_docscrape.BadSection' in str(w[0].message)) + + doc7 = NumpyDocString(""" Doc starts on second line. """) + def test_empty_first_line(): assert doc7['Summary'][0].startswith('Doc starts') @@ -762,6 +817,7 @@ def test_unicode(): assert isinstance(doc['Summary'][0], str) assert doc['Summary'][0] == 'öäöäöäöäöåååå' + def test_plot_examples(): cfg = dict(use_plots=True) @@ -785,6 +841,7 @@ def test_plot_examples(): """, config=cfg) assert str(doc).count('plot::') == 1, str(doc) + def test_class_members(): class Dummy(object): @@ -866,6 +923,7 @@ def bar(self, a, b): else: assert 'Spammity index' in str(doc), str(doc) + def test_duplicate_signature(): # Duplicate function signatures occur e.g. in ufuncs, when the # automatic mechanism adds one, and a more detailed comes from the @@ -911,6 +969,7 @@ def test_duplicate_signature(): For usage examples, see `ode`. """ + def test_class_members_doc(): doc = ClassDoc(None, class_doc_txt) non_blank_line_by_line_compare(str(doc), @@ -949,6 +1008,7 @@ def test_class_members_doc(): """) + def test_class_members_doc_sphinx(): class Foo: @property @@ -997,6 +1057,7 @@ def x(self): """) + def test_templated_sections(): doc = SphinxClassDoc(None, class_doc_txt, config={'template': jinja2.Template('{{examples}}{{parameters}}')}) @@ -1020,8 +1081,6 @@ def test_templated_sections(): """) - - if __name__ == "__main__": import nose nose.run()