def test_see_also_refs(self): docstring = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) See Also -------- some, other, funcs otherfunc : relationship """ actual = str(NumpyDocstring(docstring)) expected = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) .. seealso:: :obj:`some`, :obj:`other`, :obj:`funcs` \n\ :obj:`otherfunc` relationship """ self.assertEqual(expected, actual) docstring = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) See Also -------- some, other, funcs otherfunc : relationship """ config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) expected = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) .. seealso:: :meth:`some`, :meth:`other`, :meth:`funcs` \n\ :meth:`otherfunc` relationship """ self.assertEqual(expected, actual)
def _process_docstring(app, what, name, obj, options, lines): # type: (Sphinx, unicode, unicode, Any, Any, List[unicode]) -> None """Process the docstring for a given python object. Called when autodoc has read and processed a docstring. `lines` is a list of docstring lines that `_process_docstring` modifies in place to change what Sphinx outputs. The following settings in conf.py control what styles of docstrings will be parsed: * ``napoleon_google_docstring`` -- parse Google style docstrings * ``napoleon_numpy_docstring`` -- parse NumPy style docstrings Parameters ---------- app : sphinx.application.Sphinx Application object representing the Sphinx process. what : str A string specifying the type of the object to which the docstring belongs. Valid values: "module", "class", "exception", "function", "method", "attribute". name : str The fully qualified name of the object. obj : module, class, exception, function, method, or attribute The object to which the docstring belongs. options : sphinx.ext.autodoc.Options The options given to the directive: an object with attributes inherited_members, undoc_members, show_inheritance and noindex that are True if the flag option of same name was given to the auto directive. lines : list of str The lines of the docstring, see above. .. note:: `lines` is modified *in place* """ result_lines = lines docstring = None # type: GoogleDocstring if app.config.napoleon_numpy_docstring: docstring = NumpyDocstring(result_lines, app.config, app, what, name, obj, options) result_lines = docstring.lines() if app.config.napoleon_google_docstring: docstring = GoogleDocstring(result_lines, app.config, app, what, name, obj, options) result_lines = docstring.lines() lines[:] = result_lines[:]
def test_attributes_docstring(self): config = Config() actual = str( NumpyDocstring(cleandoc(NamedtupleSubclass.__doc__), config=config, app=None, what='class', name='NamedtupleSubclass', obj=NamedtupleSubclass)) expected = """\ Sample namedtuple subclass .. attribute:: attr1 Quick description of attr1 :type: Arbitrary type .. attribute:: attr2 Quick description of attr2 :type: Another arbitrary type .. attribute:: attr3 Adds a newline after the type :type: Type """ self.assertEqual(expected, actual)
def test_docstrings(self): config = Config(napoleon_use_param=False, napoleon_use_rtype=False, napoleon_use_keyword=False) for docstring, expected in self.docstrings: actual = str(NumpyDocstring(dedent(docstring), config)) expected = dedent(expected) self.assertEqual(expected, actual)
def _parse_doc(func): """Extract documentation from a function's docstring.""" doc = inspect.getdoc(func) if doc is None: return _Doc('', {}) # Convert Google- or Numpy-style docstrings to RST. # (Should do nothing if not in either style.) doc = str(GoogleDocstring(doc)) doc = str(NumpyDocstring(doc)) dom = publish_doctree(doc).asdom() etree = ElementTree.fromstring(dom.toxml()) doctext = [] for element in etree: if element.tag == 'paragraph': doctext.append(_get_text(element)) elif element.tag == 'literal_block': doctext.append(_indent(_get_text(element))) doctext = '\n\n'.join(doctext) fields = etree.findall('.//field') params = defaultdict(dict) for field in fields: field_name = field.find('field_name') field_body = field.find('field_body') parts = field_name.text.split() if len(parts) == 2: doctype, name = parts elif len(parts) == 3: doctype, type_, name = parts if doctype not in _PARAM_TYPES: log.debug('ignoring field %s', field_name.text) continue log.debug('inline param type %s', type_) if 'type' in params[name]: raise ValueError('type defined twice for {}'.format(name)) params[name]['type'] = type_ else: log.debug('ignoring field %s', field_name.text) continue if doctype in _PARAM_TYPES: doctype = 'param' if doctype in _TYPE_NAMES: doctype = 'type' text = _get_text(field_body) log.debug('%s %s: %s', doctype, name, text) if doctype in params[name]: raise ValueError('{} defined twice for {}'.format(doctype, name)) params[name][doctype] = text tuples = {} for name, values in params.items(): tuples[name] = _Param(values.get('param'), values.get('type')) return _Doc(doctext, tuples)
def _process_docstring(app, what, name, obj, options, lines): result_lines = lines if app.config.napoleon_numpy_docstring: docstring = NumpyDocstring(result_lines, app.config, app, what, name, obj, options) result_lines = docstring.lines() if app.config.napoleon_google_docstring: docstring = GoogleDocstring(result_lines, app.config, app, what, name, obj, options) result_lines = docstring.lines() lines[:] = result_lines[:]
def __get_class_docs__(cls): output = list() class_docs = {} if cls.__doc__: lines = inspect.getdoc(cls).split('\n') lines = list(filter(lambda x: len(x.strip()) > 0, lines)) section = 'Desc' class_docs[section] = [] for i in range(len(lines)): if '---' in lines[i]: del class_docs[section][-1] section = lines[i - 1].strip() class_docs[section] = [] line = lines[i].strip() class_docs[section].append(line) class_docs['Parameters'] = [] docparam = '' for key in cls.SECTIONS: if key in class_docs.keys(): value = class_docs[key] if key == 'Desc': docparam += '\n'.join(value) elif key == 'Parameters': params = cls.__parameters__() if len(params) > 0: docparam += '\nParameters\n' docparam += '---------\n' for p in params: docparam += p + ' : ' + str(type(p)) + '\n' if hasattr(cls, p): param_attr = getattr(cls, p) if hasattr(param_attr, '__doc__'): docparam += '\t' + param_attr.__doc__ + '\n' else: docparam += '{}\n{}\n'.format(key, '\n'.join(value)) docparam += '\n' from sphinxcontrib.napoleon import Config config = Config(napoleon_use_param=True, napoleon_use_rtype=True) from sphinxcontrib.napoleon.docstring import NumpyDocstring lines = NumpyDocstring(docparam, config).lines() output.extend(lines) return output
def test_sphinx_admonitions(self): admonition_map = { 'Attention': 'attention', 'Caution': 'caution', 'Danger': 'danger', 'Error': 'error', 'Hint': 'hint', 'Important': 'important', 'Note': 'note', 'Tip': 'tip', 'Todo': 'todo', 'Warning': 'warning', 'Warnings': 'warning', } config = Config() for section, admonition in admonition_map.items(): # Multiline actual = str( NumpyDocstring(("{}\n" "{}\n" " this is the first line\n" "\n" " and this is the second line\n").format( section, '-' * len(section)), config)) expect = (".. {}::\n" "\n" " this is the first line\n" " \n" " and this is the second line\n").format(admonition) self.assertEqual(expect, actual) # Single line actual = str( NumpyDocstring(("{}\n" "{}\n" " this is a single line\n").format( section, '-' * len(section)), config)) expect = (".. {}:: this is a single line\n").format(admonition) self.assertEqual(expect, actual)
def test_parameters_with_class_reference(self): docstring = """\ Parameters ---------- param1 : :class:`MyClass <name.space.MyClass>` instance """ config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ :Parameters: **param1** (:class:`MyClass <name.space.MyClass>` instance) """ self.assertEqual(expected, actual) config = Config(napoleon_use_param=True) actual = str(NumpyDocstring(docstring, config)) expected = """\ :param param1: :type param1: :class:`MyClass <name.space.MyClass>` instance """ self.assertEqual(expected, actual)
def test_parameters_without_class_reference(self): docstring = """\ Parameters ---------- param1 : MyClass instance """ config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ :Parameters: **param1** (*MyClass instance*) """ self.assertEqual(expected, actual) config = Config(napoleon_use_param=True) actual = str(NumpyDocstring(dedent(docstring), config)) expected = """\ :param param1: :type param1: MyClass instance """ self.assertEqual(expected, actual)
def test_underscore_in_attribute(self): docstring = """ Attributes ---------- arg_ : type some description """ expected = """ :ivar arg_: some description :vartype arg_: type """ config = Config(napoleon_use_ivar=True) app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "class")) self.assertEqual(expected, actual)
def test_xrefs_in_return_type(self): docstring = """ Example Function Returns ------- :class:`numpy.ndarray` A :math:`n \\times 2` array containing a bunch of math items """ expected = """ Example Function :returns: A :math:`n \\times 2` array containing a bunch of math items :rtype: :class:`numpy.ndarray` """ config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def test_colon_in_return_type(self): docstring = """ Summary Returns ------- :py:class:`~my_mod.my_class` an instance of :py:class:`~my_mod.my_class` """ expected = """ Summary :returns: an instance of :py:class:`~my_mod.my_class` :rtype: :py:class:`~my_mod.my_class` """ config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def convert_docstring(docstring): if is_google_style(docstring): return str(GoogleDocstring(docstring, CONFIG)) elif is_numpy_style(docstring): return str(NumpyDocstring(docstring, CONFIG)) return docstring
def test_docstrings(self): for docstring, expected in self.docstrings: actual = str(NumpyDocstring(textwrap.dedent(docstring))) expected = textwrap.dedent(expected) self.assertEqual(expected, actual)
def test_list_in_parameter_description(self): docstring = """One line summary. Parameters ---------- no_list : int one_bullet_empty : int * one_bullet_single_line : int - first line one_bullet_two_lines : int + first line continued two_bullets_single_line : int - first line - second line two_bullets_two_lines : int * first line continued * second line continued one_enumeration_single_line : int 1. first line one_enumeration_two_lines : int 1) first line continued two_enumerations_one_line : int (iii) first line (iv) second line two_enumerations_two_lines : int a. first line continued b. second line continued one_definition_one_line : int item 1 first line one_definition_two_lines : int item 1 first line continued two_definitions_one_line : int item 1 first line item 2 second line two_definitions_two_lines : int item 1 first line continued item 2 second line continued one_definition_blank_line : int item 1 first line extra first line two_definitions_blank_lines : int item 1 first line extra first line item 2 second line extra second line definition_after_normal_text : int text line item 1 first line """ expected = """One line summary. :param no_list: :type no_list: int :param one_bullet_empty: * :type one_bullet_empty: int :param one_bullet_single_line: - first line :type one_bullet_single_line: int :param one_bullet_two_lines: + first line continued :type one_bullet_two_lines: int :param two_bullets_single_line: - first line - second line :type two_bullets_single_line: int :param two_bullets_two_lines: * first line continued * second line continued :type two_bullets_two_lines: int :param one_enumeration_single_line: 1. first line :type one_enumeration_single_line: int :param one_enumeration_two_lines: 1) first line continued :type one_enumeration_two_lines: int :param two_enumerations_one_line: (iii) first line (iv) second line :type two_enumerations_one_line: int :param two_enumerations_two_lines: a. first line continued b. second line continued :type two_enumerations_two_lines: int :param one_definition_one_line: item 1 first line :type one_definition_one_line: int :param one_definition_two_lines: item 1 first line continued :type one_definition_two_lines: int :param two_definitions_one_line: item 1 first line item 2 second line :type two_definitions_one_line: int :param two_definitions_two_lines: item 1 first line continued item 2 second line continued :type two_definitions_two_lines: int :param one_definition_blank_line: item 1 first line extra first line :type one_definition_blank_line: int :param two_definitions_blank_lines: item 1 first line extra first line item 2 second line extra second line :type two_definitions_blank_lines: int :param definition_after_normal_text: text line item 1 first line :type definition_after_normal_text: int """ config = Config(napoleon_use_param=True) actual = str(NumpyDocstring(docstring, config)) self.assertEqual(expected, actual) expected = """One line summary. :Parameters: * **no_list** (*int*) * **one_bullet_empty** (*int*) -- * * **one_bullet_single_line** (*int*) -- - first line * **one_bullet_two_lines** (*int*) -- + first line continued * **two_bullets_single_line** (*int*) -- - first line - second line * **two_bullets_two_lines** (*int*) -- * first line continued * second line continued * **one_enumeration_single_line** (*int*) -- 1. first line * **one_enumeration_two_lines** (*int*) -- 1) first line continued * **two_enumerations_one_line** (*int*) -- (iii) first line (iv) second line * **two_enumerations_two_lines** (*int*) -- a. first line continued b. second line continued * **one_definition_one_line** (*int*) -- item 1 first line * **one_definition_two_lines** (*int*) -- item 1 first line continued * **two_definitions_one_line** (*int*) -- item 1 first line item 2 second line * **two_definitions_two_lines** (*int*) -- item 1 first line continued item 2 second line continued * **one_definition_blank_line** (*int*) -- item 1 first line extra first line * **two_definitions_blank_lines** (*int*) -- item 1 first line extra first line item 2 second line extra second line * **definition_after_normal_text** (*int*) -- text line item 1 first line """ config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) self.assertEqual(expected, actual)
def test_section_header_underline_length(self): docstrings = [ (""" Summary line Example - Multiline example body """, """ Summary line Example - Multiline example body """), ################################ (""" Summary line Example -- Multiline example body """, """ Summary line .. rubric:: Example Multiline example body """), ################################ (""" Summary line Example ------- Multiline example body """, """ Summary line .. rubric:: Example Multiline example body """), ################################ (""" Summary line Example ------------ Multiline example body """, """ Summary line .. rubric:: Example Multiline example body """) ] for docstring, expected in docstrings: actual = str(NumpyDocstring(docstring)) self.assertEqual(expected, actual)
def test_raises_types(self): docstrings = [ (""" Example Function Raises ------ RuntimeError A setting wasn't specified, or was invalid. ValueError Something something value error. """, """ Example Function :raises RuntimeError: A setting wasn't specified, or was invalid. :raises ValueError: Something something value error. """), ################################ (""" Example Function Raises ------ InvalidDimensionsError """, """ Example Function :raises InvalidDimensionsError: """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error """, """ Example Function :raises Invalid Dimensions Error: """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error With description """, """ Example Function :raises Invalid Dimensions Error: With description """), ################################ (""" Example Function Raises ------ InvalidDimensionsError If the dimensions couldn't be parsed. """, """ Example Function :raises InvalidDimensionsError: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error If the dimensions couldn't be parsed. """, """ Example Function :raises Invalid Dimensions Error: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ If the dimensions couldn't be parsed. """, """ Example Function :raises If the dimensions couldn't be parsed.: """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` """, """ Example Function :raises exc.InvalidDimensionsError: """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. """, """ Example Function :raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """, """ Example Function :raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. :class:`exc.InvalidArgumentsError` If the arguments are invalid. """, """ Example Function :raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed. :raises exc.InvalidArgumentsError: If the arguments are invalid. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` :class:`exc.InvalidArgumentsError` """, """ Example Function :raises exc.InvalidDimensionsError: :raises exc.InvalidArgumentsError: """) ] for docstring, expected in docstrings: config = Config() app = mock.Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def test_raises_types(self): docstrings = [ (""" Example Function Raises ------ InvalidDimensionsError """, """ Example Function :raises: :exc:`InvalidDimensionsError` """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error """, """ Example Function :raises: Invalid Dimensions Error """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error With description """, """ Example Function :raises: *Invalid Dimensions Error* -- With description """), ################################ (""" Example Function Raises ------ InvalidDimensionsError If the dimensions couldn't be parsed. """, """ Example Function :raises: :exc:`InvalidDimensionsError` -- If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ Invalid Dimensions Error If the dimensions couldn't be parsed. """, """ Example Function :raises: *Invalid Dimensions Error* -- If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ If the dimensions couldn't be parsed. """, """ Example Function :raises: If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` """, """ Example Function :raises: :class:`exc.InvalidDimensionsError` """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. """, """ Example Function :raises: :class:`exc.InvalidDimensionsError` -- If the dimensions couldn't be parsed. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """, """ Example Function :raises: :class:`exc.InvalidDimensionsError` -- If the dimensions couldn't be parsed, then a :class:`exc.InvalidDimensionsError` will be raised. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` If the dimensions couldn't be parsed. :class:`exc.InvalidArgumentsError` If the arguments are invalid. """, """ Example Function :raises: * :class:`exc.InvalidDimensionsError` -- If the dimensions couldn't be parsed. * :class:`exc.InvalidArgumentsError` -- If the arguments are invalid. """), ################################ (""" Example Function Raises ------ :class:`exc.InvalidDimensionsError` :class:`exc.InvalidArgumentsError` """, """ Example Function :raises: * :class:`exc.InvalidDimensionsError` * :class:`exc.InvalidArgumentsError` """) ] for docstring, expected in docstrings: config = Config() app = Mock() actual = str(NumpyDocstring(docstring, config, app, "method")) self.assertEqual(expected, actual)
def _parse_docstring(doc): """Extract documentation from a function's docstring.""" _cache_key = doc try: return _parse_docstring_cache[_cache_key] except KeyError: pass if doc is None: return _Doc('', '', {}) # Convert Google- or Numpy-style docstrings to RST. # (Should do nothing if not in either style.) doc = str(GoogleDocstring(doc)) doc = str(NumpyDocstring(doc)) tree = publish_doctree(doc) class Visitor(NodeVisitor): optional = [ 'document', 'docinfo', 'field_list', 'field_body', 'literal', 'problematic'] def __init__(self, document): NodeVisitor.__init__(self, document) self.paragraphs = [] self.start_lines = [] self.params = defaultdict(dict) self._current_paragraph = None self._indent_iterator_stack = [] self._indent_stack = [] def _do_nothing(self, node): pass def visit_paragraph(self, node): self.start_lines.append(node.line) self._current_paragraph = [] def depart_paragraph(self, node): text = ''.join(self._current_paragraph) text = ''.join(self._indent_stack) + text self._indent_stack = [ ' ' * len(item) for item in self._indent_stack] text = text.replace('\n', '\n' + ''.join(self._indent_stack)) self.paragraphs.append(text) self._current_paragraph = None def visit_Text(self, node): self._current_paragraph.append(node) depart_Text = _do_nothing def visit_emphasis(self, node): self._current_paragraph.append('\033[3m') # *foo*: italic def visit_strong(self, node): self._current_paragraph.append('\033[1m') # **foo**: bold def visit_title_reference(self, node): self._current_paragraph.append('\033[4m') # `foo`: underlined def _depart_markup(self, node): self._current_paragraph.append('\033[0m') depart_emphasis = depart_strong = depart_title_reference = \ _depart_markup def visit_literal_block(self, node): text, = node self.start_lines.append(node.line) self.paragraphs.append(re.sub('^|\n', r'\g<0> ', text)) # indent raise SkipNode def visit_bullet_list(self, node): self._indent_iterator_stack.append( (node['bullet'] + ' ' for _ in range(len(node)))) def depart_bullet_list(self, node): self._indent_iterator_stack.pop() def visit_enumerated_list(self, node): enumtype = node['enumtype'] fmt = {('(', ')'): 'parens', ('', ')'): 'rparen', ('', '.'): 'period'}[node['prefix'], node['suffix']] try: start = node['start'] except KeyError: start = 1 else: start = { 'arabic': int, 'loweralpha': lambda s: ord(s) - ord('a') + 1, 'upperalpha': lambda s: ord(s) - ord('A') + 1, 'lowerroman': lambda s: roman.fromRoman(s.upper()), 'upperroman': lambda s: roman.fromRoman(s), }[enumtype](start) enumerators = [Body(None).make_enumerator(i, enumtype, fmt)[0] for i in range(start, start + len(node))] width = max(map(len, enumerators)) enumerators = [enum.ljust(width) for enum in enumerators] self._indent_iterator_stack.append(iter(enumerators)) def depart_enumerated_list(self, node): self._indent_iterator_stack.pop() def visit_list_item(self, node): self._indent_stack.append(next(self._indent_iterator_stack[-1])) def depart_list_item(self, node): self._indent_stack.pop() def visit_field(self, node): field_name_node, field_body_node = node field_name, = field_name_node parts = field_name.split() if len(parts) == 2: doctype, name = parts elif len(parts) == 3: doctype, type_, name = parts if doctype not in _PARAM_TYPES: raise SkipNode if 'type' in self.params[name]: raise ValueError('type defined twice for {}'.format(name)) self.params[name]['type'] = type_ else: raise SkipNode if doctype in _PARAM_TYPES: doctype = 'param' if doctype in _TYPE_NAMES: doctype = 'type' if doctype in self.params[name]: raise ValueError( '{} defined twice for {}'.format(doctype, name)) visitor = Visitor(self.document) field_body_node.walkabout(visitor) self.params[name][doctype] = ''.join(visitor.paragraphs) raise SkipNode def visit_comment(self, node): raise SkipNode def visit_system_message(self, node): raise SkipNode visitor = Visitor(tree) tree.walkabout(visitor) tuples = {name: _Param(values.get('param'), values.get('type')) for name, values in visitor.params.items()} if visitor.paragraphs: text = [] for start, paragraph, next_start in zip( visitor.start_lines, visitor.paragraphs, visitor.start_lines[1:] + [0]): text.append(paragraph) # We insert a space before each newline to prevent argparse # from stripping consecutive newlines down to just two # (http://bugs.python.org/issue31330). text.append(' \n' * (next_start - start - paragraph.count('\n'))) parsed = _Doc('', ''.join(text), tuples) else: parsed = _Doc('', '', tuples) _parse_docstring_cache[_cache_key] = parsed return parsed
def main(text=None): src = sys.stdin.read() if text is None else text rest_formatter.main(str(NumpyDocstring(textwrap.dedent(src))))