def get_doc(self, encoding=None, ignore=1): """Overrides ClassDocumenter.get_doc to create the doc scraped from the Process object, then adds additional content from the class docstring. """ from six import text_type # Get the class docstring. This is a copy of the ClassDocumenter.get_doc method. Using "super" does weird stuff. docstring = self.get_attr(self.object, '__doc__', None) # make sure we have Unicode docstrings, then sanitize and split # into lines if isinstance(docstring, text_type): docstring = prepare_docstring(docstring, ignore) elif isinstance(docstring, str): # this will not trigger on Py3 docstring = prepare_docstring(force_decode(docstring, encoding), ignore) # Create the docstring by scraping info from the Process instance. pdocstrings = self.make_numpy_doc() if self.options.docstring and docstring is not None: # Add the sections from the class docstring itself. pdocstrings.extend(docstring[self.options.skiplines:]) # Parse using the Numpy docstring format. docstrings = NumpyDocstring(pdocstrings, self.env.config, self.env.app, what='class', obj=self.object, options=self.options) return [docstrings.lines()]
def get_doc(self, encoding=None, ignore=1): """Overrides ClassDocumenter.get_doc to create the doc scraped from the Process object, then adds additional content from the class docstring. """ from six import text_type # Get the class docstring. This is a copy of the ClassDocumenter.get_doc method. Using "super" does weird stuff. docstring = self.get_attr(self.object, '__doc__', None) # make sure we have Unicode docstrings, then sanitize and split # into lines if isinstance(docstring, text_type): docstring = prepare_docstring(docstring, ignore) elif isinstance(docstring, str): # this will not trigger on Py3 docstring = prepare_docstring(force_decode(docstring, encoding), ignore) # Create the docstring by scraping info from the Process instance. pdocstrings = self.make_numpy_doc() if self.options.docstring and docstring is not None: # Add the sections from the class docstring itself. pdocstrings.extend(docstring[self.options.skiplines:]) # Parse using the Numpy docstring format. docstrings = NumpyDocstring(pdocstrings, self.env.config, self.env.app, what='class', obj=self.object, options=self.options) return [docstrings.lines()]
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: Sphinx, what: str, name: str, obj: Any, options: Any, lines: List[str]) -> 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: GoogleDocstring = None 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 _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 :type: Arbitrary type Quick description of attr1 .. attribute:: attr2 :type: Another arbitrary type Quick description of attr2 .. attribute:: attr3 :type: Type Adds a newline after the type """ self.assertEqual(expected, actual)
def test_attributes_docstring(self): config = Config() actual = str( NumpyDocstring(cleandoc(NamedtupleSubclass.__doc__), config=config, app=None, what='class', name='NamedtupleSubclass', obj=NamedtupleSubclass)) expected = dedent("""\ Sample namedtuple subclass .. attribute:: attr1 *Arbitrary type* Quick description of attr1 .. attribute:: attr2 *Another arbitrary type* Quick description of attr2 """) self.assertEqual(expected, actual)
def docutilize(obj): """Convert Numpy or Google style docstring into reStructuredText format. Args: obj (str or object): Takes an object and changes it's docstrings to a reStructuredText format. Returns: str or object: A converted string or an object with replaced docstring depending on the type of the input. """ from inspect import cleandoc, getdoc from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring if isinstance(obj, str): doc = cleandoc(obj) else: doc = getdoc(obj) doc = str(NumpyDocstring(doc)) doc = str(GoogleDocstring(doc)) doc = doc.replace(":exc:", "") doc = doc.replace(":data:", "") doc = doc.replace(":keyword", ":param") doc = doc.replace(":kwtype", ":type") if isinstance(obj, str): return doc obj.__doc__ = doc return obj
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 _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 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_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_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 doc_to_rst(doc): """Return the RestructuredText version. Parameters ---------- doc : str The docstring (after cleaning so that the excess indention has been removed). Returns ------- result : docstring The parsed docstring. """ return NumpyDocstring(doc, config)
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 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 _extract_parameter_data(function): """ Extract name, type, description, etc. of each parameter in ``function``. The returned object should have the form:: data = { 'param1': { name = 'param1', type = 'str', help = 'The first parameter. Just for demo.', }, 'param2': { name = '--param2', type = 'int', help = 'Some optional input number...', }, } Every parameter in returned object can have the following entries: * name - the name of parameter, preceeded by '--' if it is optional * default - the default value (only for optional parameters). Extracted from function signature. * type - type of parameter, extracted from function docstring. If not set, it is assumed to be str. * help - parameter description, extracted from function docstring. If not found, set to 'No description' """ # Use OrderedDict to keep the order of parameters data = collections.OrderedDict() # Get function docstring and convert from Numpy to rst docstring style: function_docstring_ = str(NumpyDocstring(inspect.getdoc(function))) or '' # When converting from NumpyDocstring to rst format, mutiline parameter # descriptions are indented with spaces. Remove such indentation: function_docstring = re.sub(r'\ {2,}', '', function_docstring_) # Extract parameter name and default value: params = [(key, par.default) for key, par in inspect.signature(function).parameters.items()] for param, default in params: # if default value is not empty, it is a optional parameter: if default != inspect._empty: # pylint: disable=protected-access data[param] = {'name': '--' + param, 'default': default, 'metavar': ''} else: data[param] = {'name': param} # Extract help (parameter description) and type from function docstring: regex_help = r'.*:param {}: (.+?):type .*'.format(param) regex_type = r'.*:type {}: (.+?)\n.*'.format(param) # Use DOTALL flag - this way r'.' also matches newline characters: match_help = re.match(regex_help, function_docstring, flags=re.DOTALL) match_type = re.match(regex_type, function_docstring, flags=re.DOTALL) if match_help and match_type: param_type = match_type.group(1).strip() if param_type not in VALID_TYPES: raise ValueError('Invalid type {} in function {}'.format( param_type, function.__name__)) data[param]['type'] = VALID_TYPES[param_type] data[param]['help'] = match_help.group(1).strip().rstrip('.') if default != inspect._empty: # pylint: disable=protected-access # Append default value to parameter description: default_value = ' (default: {})'.format(_format_defaults(default)) data[param]['help'] += default_value if param_type == 'bool': data[param]['action'] = 'store_true' # If action == store_true, than `type` needs to be removed. data[param].pop('type') data[param].pop('metavar') if param_type == 'list_str': data[param]['nargs'] = '+' else: # TODO: raise ValueError or just make some arbitrary description? # raise ValueError("Bad docstring for parameter {}.".format(param)) data[param]['help'] = 'No description' return data