From b0d494ff14f5ea50040035f6e0896e236158b485 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 15:51:45 +0200 Subject: [PATCH 01/15] Run black. --- docinstance/__init__.py | 2 +- docinstance/content/description.py | 201 ++++--- docinstance/content/equation.py | 37 +- docinstance/content/section.py | 181 ++++--- docinstance/content/test/test_base.py | 9 +- docinstance/content/test/test_description.py | 243 +++++---- docinstance/content/test/test_equation.py | 57 +- docinstance/content/test/test_section.py | 414 ++++++++------ docinstance/docstring.py | 124 +++-- docinstance/parser/latex.py | 10 +- docinstance/parser/numpy.py | 161 +++--- docinstance/parser/test/test_latex.py | 38 +- docinstance/parser/test/test_numpy.py | 539 ++++++++++++------- docinstance/test/test_docstring.py | 187 ++++--- docinstance/test/test_utils.py | 120 +++-- docinstance/test/test_wrapper.py | 310 ++++++----- docinstance/utils.py | 39 +- docinstance/wrapper.py | 27 +- 18 files changed, 1597 insertions(+), 1102 deletions(-) diff --git a/docinstance/__init__.py b/docinstance/__init__.py index a0b5a7e..12517ef 100644 --- a/docinstance/__init__.py +++ b/docinstance/__init__.py @@ -1,3 +1,3 @@ """Module for representing a docstring as an instance of a Docstring class.""" # pylint: disable=C0103 -name = 'docinstance' +name = "docinstance" diff --git a/docinstance/content/description.py b/docinstance/content/description.py index 3d9de03..2744270 100644 --- a/docinstance/content/description.py +++ b/docinstance/content/description.py @@ -43,7 +43,7 @@ class DocDescription(DocContent): """ - def __init__(self, name, signature='', types=None, descs=None): + def __init__(self, name, signature="", types=None, descs=None): """Initialize the object. Parameters @@ -86,20 +86,27 @@ def __init__(self, name, signature='', types=None, descs=None): types = [] elif isinstance(types, (type, str)): types = [types] - elif not (isinstance(types, (list, tuple)) and - all(isinstance(i, (type, str)) for i in types)): - raise TypeError("Types of allowed objects must be given as a class or list/tuple of " - "classes/strings.") + elif not ( + isinstance(types, (list, tuple)) and all(isinstance(i, (type, str)) for i in types) + ): + raise TypeError( + "Types of allowed objects must be given as a class or list/tuple of " + "classes/strings." + ) self.types = list(types) if descs is None: descs = [] elif isinstance(descs, (str, DocEquation)): descs = [descs] - elif not (isinstance(descs, (list, tuple)) and - all(isinstance(i, (str, DocEquation)) for i in descs)): - raise TypeError("Descriptions of the object/error must be given as a string or " - "list/tuple of strings") + elif not ( + isinstance(descs, (list, tuple)) + and all(isinstance(i, (str, DocEquation)) for i in descs) + ): + raise TypeError( + "Descriptions of the object/error must be given as a string or " + "list/tuple of strings" + ) self.descs = list(descs) @property @@ -143,11 +150,13 @@ def make_numpy_docstring(self, width, indent_level, tabsize): The signature of a function is not included in the numpy docstring. """ - if self.signature != '': - print('Warning: In NumPy docstring format, the signature of a function is not ' - 'included.') + if self.signature != "": + print( + "Warning: In NumPy docstring format, the signature of a function is not " + "included." + ) - output = '' + output = "" # var_name # OR # error_name @@ -157,39 +166,53 @@ def make_numpy_docstring(self, width, indent_level, tabsize): output += wrap(self.name, width=width, indent_level=indent_level, tabsize=tabsize)[0] # var_name : var_type elif len(self.types) == 1: - name_type = wrap('{0} : {1}'.format(self.name, self.types_str[0]), - width=width, indent_level=indent_level, tabsize=tabsize) + name_type = wrap( + "{0} : {1}".format(self.name, self.types_str[0]), + width=width, + indent_level=indent_level, + tabsize=tabsize, + ) # check that the both the name and the type can fit in the given width and indentation if len(name_type) > 1: # FIXME: need a better message - raise ValueError('The name and the type of the variable are too long to fit into ' - 'given width and indentation.') + raise ValueError( + "The name and the type of the variable are too long to fit into " + "given width and indentation." + ) output += name_type[0] # var_name : {var_type1, var_type2, default_type} else: - name_types = wrap('{0} : {{{1}}}'.format(self.name, ', '.join(self.types_str)), - width=width, indent_level=indent_level, tabsize=tabsize) + name_types = wrap( + "{0} : {{{1}}}".format(self.name, ", ".join(self.types_str)), + width=width, + indent_level=indent_level, + tabsize=tabsize, + ) # if there are too many types to fit into one line, the remaining lines should be # indented to line up after "var_name : {" - wrap_point = len('{0} : {{'.format(self.name)) + wrap_point = len("{0} : {{".format(self.name)) # check that the name and first type can fit into the first line - if not name_types[0].startswith('{0}{1} : {{{2}'.format(' ' * indent_level * tabsize, - self.name, self.types_str[0])): + if not name_types[0].startswith( + "{0}{1} : {{{2}".format(" " * indent_level * tabsize, self.name, self.types_str[0]) + ): # FIXME: need a better message - raise ValueError('The name and the first type of the variable are too long to fit ' - 'into the given width and indentation.') + raise ValueError( + "The name and the first type of the variable are too long to fit " + "into the given width and indentation." + ) # wrap the remaining lines name_types_lines = [name_types[0]] for line in name_types[1:]: name_types_lines += wrap(line, width=width, indent_level=1, tabsize=wrap_point) - output += '\n'.join(name_types_lines) - output += '\n' + output += "\n".join(name_types_lines) + output += "\n" # descriptions for paragraph in self.descs: - output += '\n'.join(wrap(paragraph, - width=width, indent_level=indent_level+1, tabsize=tabsize)) - output += '\n' + output += "\n".join( + wrap(paragraph, width=width, indent_level=indent_level + 1, tabsize=tabsize) + ) + output += "\n" return output @@ -212,8 +235,8 @@ def make_numpy_docstring_signature(self, width, indent_level, tabsize): signature. """ - new_name = '{0}{1}'.format(self.name, self.signature) - new_description = self.__class__(new_name, '', self.types, self.descs) + new_name = "{0}{1}".format(self.name, self.signature) + new_description = self.__class__(new_name, "", self.types, self.descs) return new_description.make_numpy_docstring(width, indent_level, tabsize) def make_google_docstring(self, width, indent_level, tabsize): @@ -238,42 +261,55 @@ def make_google_docstring(self, width, indent_level, tabsize): The signature of a function is not included in the google docstring. """ - output = '' + output = "" first_block = [] # var_name: if not self.types: - first_block = wrap('{0}:'.format(self.name), - width=width, indent_level=indent_level, tabsize=tabsize) + first_block = wrap( + "{0}:".format(self.name), width=width, indent_level=indent_level, tabsize=tabsize + ) # var_name (type1, type2): else: # FIXME: optional parameters need to be specified within google doc - types_str = [i if isinstance(i, str) else ':obj:`{0}`'.format(j) - for i, j in zip(self.types, self.types_str)] - text = '{0} ({1}):'.format(self.name, ', '.join(types_str)) + types_str = [ + i if isinstance(i, str) else ":obj:`{0}`".format(j) + for i, j in zip(self.types, self.types_str) + ] + text = "{0} ({1}):".format(self.name, ", ".join(types_str)) # if there are too many types to fit into one line, the remaining lines should be # indented to line up after "var_name (" # FIXME: following can probably be replaced with a better wrapping function - first_block = [' ' * indent_level * tabsize + line for line in - wrap_indent_subsequent(text, width=width - indent_level*tabsize, - indent_level=1, - tabsize=len('{0} ('.format(self.name)))] + first_block = [ + " " * indent_level * tabsize + line + for line in wrap_indent_subsequent( + text, + width=width - indent_level * tabsize, + indent_level=1, + tabsize=len("{0} (".format(self.name)), + ) + ] # add descriptions if self.descs: - output += '\n'.join(first_block[:-1]) + output += "\n".join(first_block[:-1]) if len(first_block) != 1: - output += '\n' + output += "\n" # FIXME: following can probably be replaced with a better wrapping function - first_desc = wrap_indent_subsequent(first_block[-1] + ' ' + self.descs[0], width=width, - indent_level=indent_level+1, tabsize=tabsize) - output += '\n'.join(first_desc) - output += '\n' + first_desc = wrap_indent_subsequent( + first_block[-1] + " " + self.descs[0], + width=width, + indent_level=indent_level + 1, + tabsize=tabsize, + ) + output += "\n".join(first_desc) + output += "\n" for desc in self.descs[1:]: - output += '\n'.join(wrap(desc, width=width, indent_level=indent_level+1, - tabsize=tabsize)) - output += '\n' + output += "\n".join( + wrap(desc, width=width, indent_level=indent_level + 1, tabsize=tabsize) + ) + output += "\n" else: - output += '\n'.join(first_block) - output += '\n' + output += "\n".join(first_block) + output += "\n" return output def make_rst_docstring(self, width, indent_level, tabsize): @@ -299,36 +335,51 @@ def make_rst_docstring(self, width, indent_level, tabsize): If the parameter name is too long to fit within the given width and indent. """ - output = '' + output = "" if self.descs: - text = ':param {0}: {1}'.format(self.name, self.descs[0]) + text = ":param {0}: {1}".format(self.name, self.descs[0]) # FIXME: following can probably be replaced with a better wrapping function - block = [' ' * indent_level * tabsize + line for line in - wrap_indent_subsequent(text, width=width - indent_level*tabsize, - indent_level=1, tabsize=tabsize)] - output += '\n'.join(block) - output += '\n' + block = [ + " " * indent_level * tabsize + line + for line in wrap_indent_subsequent( + text, width=width - indent_level * tabsize, indent_level=1, tabsize=tabsize + ) + ] + output += "\n".join(block) + output += "\n" if len(self.descs) > 1: for desc in self.descs[1:]: - output += '\n'.join(wrap(desc, width=width, indent_level=indent_level+1, - tabsize=tabsize)) - output += '\n' + output += "\n".join( + wrap(desc, width=width, indent_level=indent_level + 1, tabsize=tabsize) + ) + output += "\n" else: - block = wrap(':param {0}:'.format(self.name), width=width, indent_level=indent_level, - tabsize=tabsize) + block = wrap( + ":param {0}:".format(self.name), + width=width, + indent_level=indent_level, + tabsize=tabsize, + ) if len(block) > 1: - raise ValueError('Parameter name is too long to fit within the given line width and' - ' indent level.') + raise ValueError( + "Parameter name is too long to fit within the given line width and" + " indent level." + ) output += block[0] - output += '\n' + output += "\n" - types_str = [i if isinstance(i, str) else ':obj:`{0}`'.format(j) - for i, j in zip(self.types, self.types_str)] + types_str = [ + i if isinstance(i, str) else ":obj:`{0}`".format(j) + for i, j in zip(self.types, self.types_str) + ] if self.types: - text = ':type {0}: {1}'.format(self.name, ', '.join(types_str)) - block = [' ' * indent_level * tabsize + line for line in - wrap_indent_subsequent(text, width=width - indent_level*tabsize, - indent_level=1, tabsize=tabsize)] - output += '\n'.join(block) - output += '\n' + text = ":type {0}: {1}".format(self.name, ", ".join(types_str)) + block = [ + " " * indent_level * tabsize + line + for line in wrap_indent_subsequent( + text, width=width - indent_level * tabsize, indent_level=1, tabsize=tabsize + ) + ] + output += "\n".join(block) + output += "\n" return output diff --git a/docinstance/content/equation.py b/docinstance/content/equation.py index d96eed8..5a0e3a6 100644 --- a/docinstance/content/equation.py +++ b/docinstance/content/equation.py @@ -35,9 +35,9 @@ def __init__(self, equations): """ if not isinstance(equations, str): - raise TypeError('Equations must be given as one string.') - self.equations = equations.split('\n') - if self.equations[-1] == '': + raise TypeError("Equations must be given as one string.") + self.equations = equations.split("\n") + if self.equations[-1] == "": self.equations = self.equations[:-1] def make_numpy_docstring(self, width, indent_level, tabsize): @@ -63,22 +63,29 @@ def make_numpy_docstring(self, width, indent_level, tabsize): If the width is too small to fit the equation for the given indent and tabsize. """ - output = '' + output = "" if len(self.equations) == 1: - first_line = wrap('.. math:: ' + self.equations[0], - width=width, indent_level=indent_level, tabsize=tabsize) + first_line = wrap( + ".. math:: " + self.equations[0], + width=width, + indent_level=indent_level, + tabsize=tabsize, + ) if len(first_line) == 1: output += first_line[0] - output += '\n\n' + output += "\n\n" return output - first_line = wrap('.. math:: ', width=width, indent_level=indent_level, tabsize=tabsize) + first_line = wrap(".. math:: ", width=width, indent_level=indent_level, tabsize=tabsize) if len(first_line) != 1: - raise ValueError('Given line width is too small to fit the equation for the given ' - 'indent and tab size') + raise ValueError( + "Given line width is too small to fit the equation for the given " + "indent and tab size" + ) output += first_line[0] - output += '\n\n' - output += '\n'.join('\n'.join(wrap(equation, width=width, indent_level=indent_level + 1, - tabsize=tabsize)) - for equation in self.equations) - output += '\n\n' + output += "\n\n" + output += "\n".join( + "\n".join(wrap(equation, width=width, indent_level=indent_level + 1, tabsize=tabsize)) + for equation in self.equations + ) + output += "\n\n" return output diff --git a/docinstance/content/section.py b/docinstance/content/section.py index dc8c177..7a44571 100644 --- a/docinstance/content/section.py +++ b/docinstance/content/section.py @@ -57,13 +57,22 @@ def __init__(self, header, contents): contents = [contents] # NOTE: is it really necessary to prevent contents of a section from mixing # strings/DocContent and DocDescription? - elif not (isinstance(contents, (tuple, list)) and - (all(isinstance(content, (str, DocContent)) and - not isinstance(content, DocDescription) for content in contents) or - all(isinstance(content, DocDescription) for content in contents))): - raise TypeError("The parameter `contents` must be a string, a list/tuple of " - "DocDescription, or a list/tuple of strings/DocContent (not " - "DocDescription).") + elif not ( + isinstance(contents, (tuple, list)) + and ( + all( + isinstance(content, (str, DocContent)) + and not isinstance(content, DocDescription) + for content in contents + ) + or all(isinstance(content, DocDescription) for content in contents) + ) + ): + raise TypeError( + "The parameter `contents` must be a string, a list/tuple of " + "DocDescription, or a list/tuple of strings/DocContent (not " + "DocDescription)." + ) self.contents = list(contents) # pylint: disable=W0221 @@ -94,27 +103,30 @@ def make_numpy_docstring(self, width, indent_level, tabsize, include_signature=F If the title is too long for the given width and indentation. """ - output = '' + output = "" # title - if self.header != '': - title = wrap(self.header.title(), width=width, indent_level=indent_level, - tabsize=tabsize) - divider = wrap('-' * len(self.header), width=width, indent_level=indent_level, - tabsize=tabsize) + if self.header != "": + title = wrap( + self.header.title(), width=width, indent_level=indent_level, tabsize=tabsize + ) + divider = wrap( + "-" * len(self.header), width=width, indent_level=indent_level, tabsize=tabsize + ) # NOTE: error will be raised if the line width is not wide enough to fit the title and # the divider in one line because the divider is one word and wrap will complain if the # line width is not big enough to fit the first word - output += '{0}\n{1}\n'.format(title[0], divider[0]) + output += "{0}\n{1}\n".format(title[0], divider[0]) # contents for paragraph in self.contents: # NOTE: since the contents are checked in the initialization, we will assume that the # paragraph can only be string or DocDescription if isinstance(paragraph, str): - output += '\n'.join(wrap(paragraph, width=width, indent_level=indent_level, - tabsize=tabsize)) - output += '\n\n' + output += "\n".join( + wrap(paragraph, width=width, indent_level=indent_level, tabsize=tabsize) + ) + output += "\n\n" # if isinstance(paragraph, DocContent) - elif include_signature and hasattr(paragraph, 'make_numpy_docstring_signature'): + elif include_signature and hasattr(paragraph, "make_numpy_docstring_signature"): output += paragraph.make_numpy_docstring_signature(width, indent_level, tabsize) else: output += paragraph.make_numpy_docstring(width, indent_level, tabsize) @@ -123,7 +135,7 @@ def make_numpy_docstring(self, width, indent_level, tabsize, include_signature=F else: # end a section with two newlines (note that the section already ends with a newline if # it ends with a paragraph) - output += '\n' * isinstance(paragraph, DocDescription) + output += "\n" * isinstance(paragraph, DocDescription) return output @@ -176,15 +188,19 @@ def make_google_docstring(self, width, indent_level, tabsize): If the title is too long for the given width and indentation. """ - output = '' + output = "" # title - if self.header != '': - title = wrap('{0}:'.format(self.header.title()), - width=width, indent_level=indent_level, tabsize=tabsize) + if self.header != "": + title = wrap( + "{0}:".format(self.header.title()), + width=width, + indent_level=indent_level, + tabsize=tabsize, + ) if len(title) > 1: - raise ValueError('The header must fit into the given width with the indentation') + raise ValueError("The header must fit into the given width with the indentation") output += title[0] - output += '\n' + output += "\n" else: # don't indent the contents if there is no header indent_level -= 1 @@ -193,18 +209,19 @@ def make_google_docstring(self, width, indent_level, tabsize): # NOTE: since the contents are checked in the initialization, we will assume that the # paragraph can only be string or DocDescription if isinstance(paragraph, str): - output += '\n'.join(wrap(paragraph, width=width, indent_level=indent_level+1, - tabsize=tabsize)) - output += '\n\n' + output += "\n".join( + wrap(paragraph, width=width, indent_level=indent_level + 1, tabsize=tabsize) + ) + output += "\n\n" # if isinstance(paragraph, DocContent) else: - output += paragraph.make_google_docstring(width, indent_level+1, tabsize) + output += paragraph.make_google_docstring(width, indent_level + 1, tabsize) # pylint: disable=W0120 # following block clause should always be executed else: # end a section with two newlines (note that the section already ends with a newline if # it ends with a paragraph) - output += '\n' * isinstance(paragraph, DocDescription) + output += "\n" * isinstance(paragraph, DocDescription) return output @@ -226,45 +243,57 @@ def make_rst_docstring(self, width, indent_level, tabsize): Docstring of the given content in sphinx's rst style. """ - output = '' - header = '' - special_headers = {'see also': 'seealso', 'warnings': 'warning', 'warning': 'warning', - 'notes': 'note', 'note': 'note', 'to do': 'todo', 'todo': 'todo'} + output = "" + header = "" + special_headers = { + "see also": "seealso", + "warnings": "warning", + "warning": "warning", + "notes": "note", + "note": "note", + "to do": "todo", + "todo": "todo", + } if self.header.lower() in special_headers: - header = '.. {0}::'.format(special_headers[self.header.lower()]) - elif self.header != '': - output += ':{0}:\n\n'.format(self.header.title()) + header = ".. {0}::".format(special_headers[self.header.lower()]) + elif self.header != "": + output += ":{0}:\n\n".format(self.header.title()) for i, paragraph in enumerate(self.contents): # first content must be treated with care for special headers if i == 0 and self.header.lower() in special_headers: # str if isinstance(paragraph, str): - text = '{0} {1}'.format(header, paragraph) + text = "{0} {1}".format(header, paragraph) # FIXME: following can probably be replaced with a better wrapping function - first_content = [' ' * indent_level * tabsize + line for line in - wrap_indent_subsequent(text, - width=width - indent_level*tabsize, - indent_level=indent_level+1, - tabsize=tabsize)] - output += '\n'.join(first_content) - output += '\n' + first_content = [ + " " * indent_level * tabsize + line + for line in wrap_indent_subsequent( + text, + width=width - indent_level * tabsize, + indent_level=indent_level + 1, + tabsize=tabsize, + ) + ] + output += "\n".join(first_content) + output += "\n" # DocContent else: output += header - output += '\n' - output += paragraph.make_rst_docstring(width=width, - indent_level=indent_level+1, - tabsize=tabsize) + output += "\n" + output += paragraph.make_rst_docstring( + width=width, indent_level=indent_level + 1, tabsize=tabsize + ) # indent all susequent content indent_level += 1 elif isinstance(paragraph, str): - output += '\n'.join(wrap(paragraph, width=width, indent_level=indent_level, - tabsize=tabsize)) + output += "\n".join( + wrap(paragraph, width=width, indent_level=indent_level, tabsize=tabsize) + ) # NOTE: the second newline may cause problems (because the field might not # recognize text that is more than one newline away) - output += '\n\n' + output += "\n\n" else: output += paragraph.make_rst_docstring(width, indent_level, tabsize) # pylint: disable=W0120 @@ -272,7 +301,7 @@ def make_rst_docstring(self, width, indent_level, tabsize): else: # end a section with two newlines (note that the section already ends with a newline # if it ends with a paragraph) - output += '\n' * isinstance(paragraph, DocDescription) + output += "\n" * isinstance(paragraph, DocDescription) return output @@ -313,7 +342,7 @@ def __init__(self, contents): If the given content is not a string. """ - self.header = '' + self.header = "" if not isinstance(contents, str): raise TypeError("The parameter `contents` must be a string.") self.contents = [contents] @@ -349,32 +378,47 @@ def make_docstring(self, width, indent_level, tabsize, summary_only=False, speci If the title is too long for the given width and indentation. """ - output = '' + output = "" summary = self.contents[0] # if summary cannot fit into first line with one triple quotation # if len(summary) + ' ' * indent_level * tabsize > width - 3: if len(wrap(summary, width - 3 - int(special), indent_level, tabsize)) > 1: - output += '\n' + output += "\n" # if summary cannot fit into the second line (without tripple quotation) # if len(summary) + ' ' * indent_level * tabsize > width: if len(wrap(summary, width, indent_level, tabsize)) > 1: - raise ValueError('First section of the docstring (summary) must fit completely into' - ' the first line of the docstring (including the triple quotation)' - ' or the second line.') + raise ValueError( + "First section of the docstring (summary) must fit completely into" + " the first line of the docstring (including the triple quotation)" + " or the second line." + ) output += summary # if summary only and summary can fit into the first line with two triple quotations - if not (summary_only and - len(wrap(summary, width - 6 - int(special), indent_level, tabsize)) == 1): - output += '\n\n' + if not ( + summary_only + and len(wrap(summary, width - 6 - int(special), indent_level, tabsize)) == 1 + ): + output += "\n\n" return output # pylint: disable=C0103 -dict_classname_headers = {'ExtendedSummary': '', 'Parameters': None, 'Attributes': None, - 'Methods': None, 'Returns': None, 'Yields': None, - 'OtherParameters': 'other parameters', 'Raises': None, 'Warns': None, - 'Warnings': None, 'SeeAlso': 'see also', 'Notes': None, - 'References': None, 'Examples': None} +dict_classname_headers = { + "ExtendedSummary": "", + "Parameters": None, + "Attributes": None, + "Methods": None, + "Returns": None, + "Yields": None, + "OtherParameters": "other parameters", + "Raises": None, + "Warns": None, + "Warnings": None, + "SeeAlso": "see also", + "Notes": None, + "References": None, + "Examples": None, +} # factory for init @@ -401,6 +445,7 @@ def make_init(header): See https://stackoverflow.com/questions/3431676/creating-functions-in-a-loop for more details. """ + def __init__(self, contents): """Initialize. @@ -434,4 +479,4 @@ def __init__(self, contents): # globals is the dictionary of the current module for the symbols # types is used to instantiate a class (because all classes are instances of type) - globals()[class_name] = type(class_name, (DocSection,), {'__init__': make_init(section_header)}) + globals()[class_name] = type(class_name, (DocSection,), {"__init__": make_init(section_header)}) diff --git a/docinstance/content/test/test_base.py b/docinstance/content/test/test_base.py index 4498f49..43ab044 100644 --- a/docinstance/content/test/test_base.py +++ b/docinstance/content/test/test_base.py @@ -5,6 +5,7 @@ class ModDocContent(DocContent): """DocContent where the init does not raise an error.""" + def __init__(self): pass @@ -27,11 +28,13 @@ def test_base_eq(): class Empty: """Empty class.""" + pass + test2 = Empty() test2.x = 1 assert not test1 == test2 - test2 = {'x': 1} + test2 = {"x": 1} assert not test1 == test2 @@ -47,11 +50,13 @@ def test_base_ne(): class Empty: """Empty class.""" + pass + test2 = Empty() test2.x = 1 assert test1 != test2 - test2 = {'x': 1} + test2 = {"x": 1} assert test1 != test2 diff --git a/docinstance/content/test/test_description.py b/docinstance/content/test/test_description.py index e0992c8..71ef221 100644 --- a/docinstance/content/test/test_description.py +++ b/docinstance/content/test/test_description.py @@ -10,62 +10,62 @@ def test_init(): with pytest.raises(TypeError): DocDescription(2) with pytest.raises(TypeError): - DocDescription('test', signature=1) + DocDescription("test", signature=1) with pytest.raises(TypeError): - DocDescription('test', signature='', types=1) + DocDescription("test", signature="", types=1) with pytest.raises(TypeError): - DocDescription('test', signature='', types=[1]) + DocDescription("test", signature="", types=[1]) with pytest.raises(TypeError): - DocDescription('test', signature='', types={str}) + DocDescription("test", signature="", types={str}) with pytest.raises(TypeError): - DocDescription('test', signature='', types=[str, 1]) + DocDescription("test", signature="", types=[str, 1]) with pytest.raises(TypeError): - DocDescription('test', signature='', types=str, descs=1) + DocDescription("test", signature="", types=str, descs=1) with pytest.raises(TypeError): - DocDescription('test', signature='', types=str, descs=[1]) + DocDescription("test", signature="", types=str, descs=[1]) with pytest.raises(TypeError): - DocDescription('test', signature='', types=str, descs={'1'}) + DocDescription("test", signature="", types=str, descs={"1"}) with pytest.raises(TypeError): - DocDescription('test', signature='', types=str, descs=['1', 2]) + DocDescription("test", signature="", types=str, descs=["1", 2]) - test = DocDescription('test') - assert test.name == 'test' - assert test.signature == '' + test = DocDescription("test") + assert test.name == "test" + assert test.signature == "" assert test.types == [] assert test.descs == [] - test = DocDescription('test', signature='1') - assert test.signature == '1' - test = DocDescription('test', types=str) + test = DocDescription("test", signature="1") + assert test.signature == "1" + test = DocDescription("test", types=str) assert test.types == [str] - test = DocDescription('test', types=(str,)) + test = DocDescription("test", types=(str,)) assert test.types == [str] - test = DocDescription('test', descs='2') - assert test.descs == ['2'] - test = DocDescription('test', descs=('2',)) - assert test.descs == ['2'] + test = DocDescription("test", descs="2") + assert test.descs == ["2"] + test = DocDescription("test", descs=("2",)) + assert test.descs == ["2"] def test_types_str(): """Test DocDescription.types_str.""" - test = DocDescription('test', types=[str, int, 'list of str']) - assert test.types_str == ['str', 'int', 'list of str'] + test = DocDescription("test", types=[str, int, "list of str"]) + assert test.types_str == ["str", "int", "list of str"] def test_make_numpy_docstring(): """Test DocDescription.make_numpy_docstring.""" # no type - test = DocDescription('var_name', descs=['hello']) - assert test.make_numpy_docstring(9, 0, 4) == 'var_name\n hello\n' + test = DocDescription("var_name", descs=["hello"]) + assert test.make_numpy_docstring(9, 0, 4) == "var_name\n hello\n" # one type - test = DocDescription('var_name', types=str, descs=['hello']) + test = DocDescription("var_name", types=str, descs=["hello"]) with pytest.raises(ValueError): test.make_numpy_docstring(13, 0, 4) with pytest.raises(ValueError): test.make_numpy_docstring(14, 1, 2) - assert test.make_numpy_docstring(14, 0, 4) == 'var_name : str\n hello\n' - assert test.make_numpy_docstring(16, 1, 2) == ' var_name : str\n hello\n' + assert test.make_numpy_docstring(14, 0, 4) == "var_name : str\n hello\n" + assert test.make_numpy_docstring(16, 1, 2) == " var_name : str\n hello\n" # multiple types - test = DocDescription('var_name', types=[str, int, bool], descs=['hello']) + test = DocDescription("var_name", types=[str, int, bool], descs=["hello"]) with pytest.raises(ValueError): test.make_numpy_docstring(15, 0, 4) with pytest.raises(ValueError): @@ -74,128 +74,153 @@ def test_make_numpy_docstring(): # the `utils.wrap` complains with pytest.raises(ValueError): test.make_numpy_docstring(16, 0, 4) - assert test.make_numpy_docstring(27, 0, 4) == 'var_name : {str, int, bool}\n hello\n' - assert (test.make_numpy_docstring(26, 0, 4) == - 'var_name : {str, int,\n bool}\n hello\n') - assert (test.make_numpy_docstring(27, 1, 2) == - ' var_name : {str, int,\n bool}\n hello\n') - assert (test.make_numpy_docstring(17, 0, 4) == - 'var_name : {str,\n int,\n bool}\n hello\n') + assert test.make_numpy_docstring(27, 0, 4) == "var_name : {str, int, bool}\n hello\n" + assert ( + test.make_numpy_docstring(26, 0, 4) + == "var_name : {str, int,\n bool}\n hello\n" + ) + assert ( + test.make_numpy_docstring(27, 1, 2) + == " var_name : {str, int,\n bool}\n hello\n" + ) + assert ( + test.make_numpy_docstring(17, 0, 4) + == "var_name : {str,\n int,\n bool}\n hello\n" + ) # signature does nothing - test2 = DocDescription('var_name', signature='(a, b, c)', types=[str, int, bool], - descs=['hello']) + test2 = DocDescription( + "var_name", signature="(a, b, c)", types=[str, int, bool], descs=["hello"] + ) assert test.make_numpy_docstring(17, 0, 4) == test2.make_numpy_docstring(17, 0, 4) # multiple paragraphs - test = DocDescription('var_name', types=[str, int, bool], - descs=['description 1', 'description 2', 'description 3']) - assert (test.make_numpy_docstring(27, 0, 4) == - 'var_name : {str, int, bool}\n description 1\n description 2\n' - ' description 3\n') + test = DocDescription( + "var_name", + types=[str, int, bool], + descs=["description 1", "description 2", "description 3"], + ) + assert ( + test.make_numpy_docstring(27, 0, 4) + == "var_name : {str, int, bool}\n description 1\n description 2\n" + " description 3\n" + ) def test_make_numpy_docstring_signature(): """Test DocDescription.make_numpy_docstring_signature.""" - test = DocDescription('var_name', signature='(a, b, c)', types=[str, int, bool], - descs=['hello']) - assert (test.make_numpy_docstring_signature(36, 0, 4) == - 'var_name(a, b, c) : {str, int, bool}\n hello\n') - assert (test.make_numpy_docstring_signature(26, 0, 4) == - 'var_name(a, b, c) : {str,\n int,\n bool}\n' - ' hello\n') + test = DocDescription( + "var_name", signature="(a, b, c)", types=[str, int, bool], descs=["hello"] + ) + assert ( + test.make_numpy_docstring_signature(36, 0, 4) + == "var_name(a, b, c) : {str, int, bool}\n hello\n" + ) + assert ( + test.make_numpy_docstring_signature(26, 0, 4) + == "var_name(a, b, c) : {str,\n int,\n bool}\n" + " hello\n" + ) def test_make_google_docstring(): """Test DocDescription.make_google_docstring.""" # no type, desc - test = DocDescription('var_name', descs=['hello']) - assert test.make_google_docstring(15, 0, 4) == 'var_name: hello\n' + test = DocDescription("var_name", descs=["hello"]) + assert test.make_google_docstring(15, 0, 4) == "var_name: hello\n" # no type, no descs - test = DocDescription('var_name') - assert test.make_google_docstring(9, 0, 4) == 'var_name:\n' + test = DocDescription("var_name") + assert test.make_google_docstring(9, 0, 4) == "var_name:\n" with pytest.raises(ValueError): test.make_google_docstring(8, 0, 4) # one type, no descs - test = DocDescription('var_name', types=str) - assert test.make_google_docstring(22, 0, 4) == 'var_name (:obj:`str`):\n' + test = DocDescription("var_name", types=str) + assert test.make_google_docstring(22, 0, 4) == "var_name (:obj:`str`):\n" with pytest.raises(ValueError): test.make_google_docstring(21, 0, 4) # one type, no descs - test = DocDescription('var_name', types='str') - assert test.make_google_docstring(15, 0, 4) == 'var_name (str):\n' + test = DocDescription("var_name", types="str") + assert test.make_google_docstring(15, 0, 4) == "var_name (str):\n" with pytest.raises(ValueError): test.make_google_docstring(14, 0, 4) # many types, no descs - test = DocDescription('var_name', types=['str', int]) - assert test.make_google_docstring(22, 0, 4) == ('var_name (str,\n' - ' :obj:`int`):\n') + test = DocDescription("var_name", types=["str", int]) + assert test.make_google_docstring(22, 0, 4) == ("var_name (str,\n" " :obj:`int`):\n") with pytest.raises(ValueError): test.make_google_docstring(21, 0, 4) - assert test.make_google_docstring(23, 1, 1) == (' var_name (str,\n' - ' :obj:`int`):\n') + assert test.make_google_docstring(23, 1, 1) == (" var_name (str,\n" " :obj:`int`):\n") # one type, desc - test = DocDescription('var_name', types=str, descs=['hello']) - assert test.make_google_docstring(22, 0, 4) == 'var_name (:obj:`str`):\n hello\n' + test = DocDescription("var_name", types=str, descs=["hello"]) + assert test.make_google_docstring(22, 0, 4) == "var_name (:obj:`str`):\n hello\n" # FIXME - assert test.make_google_docstring(24, 1, 2) == ' var_name (:obj:`str`):\n hello\n' + assert test.make_google_docstring(24, 1, 2) == " var_name (:obj:`str`):\n hello\n" # multiple types - test = DocDescription('var_name', types=[str, int, bool], descs=['hello']) + test = DocDescription("var_name", types=[str, int, bool], descs=["hello"]) with pytest.raises(ValueError): test.make_google_docstring(22, 0, 4) with pytest.raises(ValueError): test.make_google_docstring(24, 1, 2) - assert test.make_google_docstring(23, 0, 4) == ('var_name (:obj:`str`,\n' - ' :obj:`int`,\n' - ' :obj:`bool`):\n' - ' hello\n') - assert test.make_google_docstring(26, 1, 2) == (' var_name (:obj:`str`,\n' - ' :obj:`int`,\n' - ' :obj:`bool`):\n' - ' hello\n') - assert (test.make_google_docstring(35, 1, 2) == - (' var_name (:obj:`str`, :obj:`int`,\n' - ' :obj:`bool`): hello\n')) + assert test.make_google_docstring(23, 0, 4) == ( + "var_name (:obj:`str`,\n" + " :obj:`int`,\n" + " :obj:`bool`):\n" + " hello\n" + ) + assert test.make_google_docstring(26, 1, 2) == ( + " var_name (:obj:`str`,\n" + " :obj:`int`,\n" + " :obj:`bool`):\n" + " hello\n" + ) + assert test.make_google_docstring(35, 1, 2) == ( + " var_name (:obj:`str`, :obj:`int`,\n" " :obj:`bool`): hello\n" + ) # signature does nothing - test2 = DocDescription('var_name', signature='(a, b, c)', types=[str, int, bool], - descs=['hello']) + test2 = DocDescription( + "var_name", signature="(a, b, c)", types=[str, int, bool], descs=["hello"] + ) assert test.make_google_docstring(23, 0, 4) == test2.make_google_docstring(23, 0, 4) # multiple paragraphs - test = DocDescription('var_name', types=[str, int, bool], - descs=['description 1', 'description 2', 'description 3']) - assert test.make_google_docstring(26, 0, 2) == ('var_name (:obj:`str`,\n' - ' :obj:`int`,\n' - ' :obj:`bool`):\n' - ' description 1\n' - ' description 2\n' - ' description 3\n') + test = DocDescription( + "var_name", + types=[str, int, bool], + descs=["description 1", "description 2", "description 3"], + ) + assert test.make_google_docstring(26, 0, 2) == ( + "var_name (:obj:`str`,\n" + " :obj:`int`,\n" + " :obj:`bool`):\n" + " description 1\n" + " description 2\n" + " description 3\n" + ) def test_make_rst_docstring(): """Test DocDescription.make_rst_docstring.""" # only name - test = DocDescription('var_name') - assert test.make_rst_docstring(16, 0, 1) == ':param var_name:\n' - assert test.make_rst_docstring(17, 1, 1) == ' :param var_name:\n' + test = DocDescription("var_name") + assert test.make_rst_docstring(16, 0, 1) == ":param var_name:\n" + assert test.make_rst_docstring(17, 1, 1) == " :param var_name:\n" with pytest.raises(ValueError): test.make_rst_docstring(15, 0, 4) # name + desc - test = DocDescription('var_name', descs='hello') - assert test.make_rst_docstring(22, 0, 1) == ':param var_name: hello\n' - assert test.make_rst_docstring(21, 0, 1) == (':param var_name:\n' - ' hello\n') - assert test.make_rst_docstring(21, 0, 4) == (':param var_name:\n' - ' hello\n') - test = DocDescription('var_name', descs=['hello my name is', 'Example 2.']) - assert test.make_rst_docstring(25, 0, 4) == (':param var_name: hello my\n' - ' name is\n' - ' Example 2.\n') + test = DocDescription("var_name", descs="hello") + assert test.make_rst_docstring(22, 0, 1) == ":param var_name: hello\n" + assert test.make_rst_docstring(21, 0, 1) == (":param var_name:\n" " hello\n") + assert test.make_rst_docstring(21, 0, 4) == (":param var_name:\n" " hello\n") + test = DocDescription("var_name", descs=["hello my name is", "Example 2."]) + assert test.make_rst_docstring(25, 0, 4) == ( + ":param var_name: hello my\n" " name is\n" " Example 2.\n" + ) # name + type - test = DocDescription('var_name', types=['str', int]) - assert test.make_rst_docstring(20, 0, 2) == (':param var_name:\n' - ':type var_name: str,\n' - ' :obj:`int`\n') + test = DocDescription("var_name", types=["str", int]) + assert test.make_rst_docstring(20, 0, 2) == ( + ":param var_name:\n" ":type var_name: str,\n" " :obj:`int`\n" + ) # name + desc + type - test = DocDescription('var_name', types=['str', int], descs=['Example 1.', 'Example 2.']) - assert test.make_rst_docstring(27, 0, 4) == (':param var_name: Example 1.\n' - ' Example 2.\n' - ':type var_name: str,\n' - ' :obj:`int`\n') + test = DocDescription("var_name", types=["str", int], descs=["Example 1.", "Example 2."]) + assert test.make_rst_docstring(27, 0, 4) == ( + ":param var_name: Example 1.\n" + " Example 2.\n" + ":type var_name: str,\n" + " :obj:`int`\n" + ) diff --git a/docinstance/content/test/test_equation.py b/docinstance/content/test/test_equation.py index 4f8fc0c..f8481f7 100644 --- a/docinstance/content/test/test_equation.py +++ b/docinstance/content/test/test_equation.py @@ -8,36 +8,39 @@ def test_init(): with pytest.raises(TypeError): DocEquation(1) with pytest.raises(TypeError): - DocEquation(['x + 1', 'y + 2']) - test = DocEquation('a + b = 2') - assert test.equations == ['a + b = 2'] - test = DocEquation('a + b &= 2\\\\\nc + d &= 3') - assert test.equations == ['a + b &= 2\\\\', 'c + d &= 3'] - test = DocEquation('a + b &= 2\\\\\nc + d &= 3\\\\\n') - assert test.equations == ['a + b &= 2\\\\', 'c + d &= 3\\\\'] - test = DocEquation('a + b &= 2\\\\\nc + d &= 3\\\\') - assert test.equations == ['a + b &= 2\\\\', 'c + d &= 3\\\\'] + DocEquation(["x + 1", "y + 2"]) + test = DocEquation("a + b = 2") + assert test.equations == ["a + b = 2"] + test = DocEquation("a + b &= 2\\\\\nc + d &= 3") + assert test.equations == ["a + b &= 2\\\\", "c + d &= 3"] + test = DocEquation("a + b &= 2\\\\\nc + d &= 3\\\\\n") + assert test.equations == ["a + b &= 2\\\\", "c + d &= 3\\\\"] + test = DocEquation("a + b &= 2\\\\\nc + d &= 3\\\\") + assert test.equations == ["a + b &= 2\\\\", "c + d &= 3\\\\"] def test_make_numpy_docstring(): """Test DocEquation.make_numpy_docstring.""" - test = DocEquation('a + b = 2') - assert test.make_numpy_docstring(19, 0, 4) == '.. math:: a + b = 2\n\n' - assert test.make_numpy_docstring(18, 0, 4) == '.. math::\n\n a + b = 2\n\n' + test = DocEquation("a + b = 2") + assert test.make_numpy_docstring(19, 0, 4) == ".. math:: a + b = 2\n\n" + assert test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n a + b = 2\n\n" with pytest.raises(ValueError): test.make_numpy_docstring(8, 0, 4) - test = DocEquation('a + b &= 2\\\\\nc + d &= 3\\\\\n') - assert (test.make_numpy_docstring(18, 0, 4) == - '.. math::\n\n' - ' a + b &= 2\\\\\n' - ' c + d &= 3\\\\\n\n') - test = DocEquation('a + b &= 2\\\\\nc + d &= 3\n') - assert (test.make_numpy_docstring(18, 0, 4) == - '.. math::\n\n' - ' a + b &= 2\\\\\n' - ' c + d &= 3\n\n') - test = DocEquation('a + b &= 2\nc + d &= 3\n') - assert (test.make_numpy_docstring(18, 0, 4) == - '.. math::\n\n' - ' a + b &= 2\n' - ' c + d &= 3\n\n') + test = DocEquation("a + b &= 2\\\\\nc + d &= 3\\\\\n") + assert ( + test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n" + " a + b &= 2\\\\\n" + " c + d &= 3\\\\\n\n" + ) + test = DocEquation("a + b &= 2\\\\\nc + d &= 3\n") + assert ( + test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n" + " a + b &= 2\\\\\n" + " c + d &= 3\n\n" + ) + test = DocEquation("a + b &= 2\nc + d &= 3\n") + assert ( + test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n" + " a + b &= 2\n" + " c + d &= 3\n\n" + ) diff --git a/docinstance/content/test/test_section.py b/docinstance/content/test/test_section.py index 833865e..59b7eaa 100644 --- a/docinstance/content/test/test_section.py +++ b/docinstance/content/test/test_section.py @@ -1,313 +1,371 @@ """Test docinstance.content.section.""" import pytest -from docinstance.content.section import (DocSection, Summary, ExtendedSummary, Parameters, - Attributes, Methods, Returns, Yields, OtherParameters, - Raises, Warns, Warnings, SeeAlso, Notes, References, - Examples) +from docinstance.content.section import ( + DocSection, + Summary, + ExtendedSummary, + Parameters, + Attributes, + Methods, + Returns, + Yields, + OtherParameters, + Raises, + Warns, + Warnings, + SeeAlso, + Notes, + References, + Examples, +) from docinstance.content.description import DocDescription def test_init(): """Test DocSection.__init__.""" with pytest.raises(TypeError): - DocSection(1, '') + DocSection(1, "") with pytest.raises(TypeError): - DocSection(['1'], '') + DocSection(["1"], "") with pytest.raises(TypeError): - DocSection('1', 1) + DocSection("1", 1) with pytest.raises(TypeError): - DocSection('1', {'1'}) + DocSection("1", {"1"}) with pytest.raises(TypeError): - DocSection('1', ['1', DocDescription('test')]) + DocSection("1", ["1", DocDescription("test")]) with pytest.raises(TypeError): - DocSection('1', [DocDescription('test'), '1']) - - test = DocSection('header name', 'hello') - assert test.header == 'header name' - assert test.contents == ['hello'] - test = DocSection('header name', ['hello', 'i am']) - assert test.header == 'header name' - assert test.contents == ['hello', 'i am'] - doc1 = DocDescription('hello') - doc2 = DocDescription('i am') - test = DocSection('header name', doc1) - assert test.header == 'header name' + DocSection("1", [DocDescription("test"), "1"]) + + test = DocSection("header name", "hello") + assert test.header == "header name" + assert test.contents == ["hello"] + test = DocSection("header name", ["hello", "i am"]) + assert test.header == "header name" + assert test.contents == ["hello", "i am"] + doc1 = DocDescription("hello") + doc2 = DocDescription("i am") + test = DocSection("header name", doc1) + assert test.header == "header name" assert test.contents == [doc1] - test = DocSection('header name', [doc1, doc2]) - assert test.header == 'header name' + test = DocSection("header name", [doc1, doc2]) + assert test.header == "header name" assert test.contents == [doc1, doc2] def test_make_numpy_docstring(): """Test DocSection.make_numpy_docstring.""" # string content - test = DocSection('header name', 'hello') - assert test.make_numpy_docstring(11, 0, 4) == 'Header Name\n-----------\nhello\n\n' - assert test.make_numpy_docstring(11, 0, 4) == 'Header Name\n-----------\nhello\n\n' + test = DocSection("header name", "hello") + assert test.make_numpy_docstring(11, 0, 4) == "Header Name\n-----------\nhello\n\n" + assert test.make_numpy_docstring(11, 0, 4) == "Header Name\n-----------\nhello\n\n" # multiple string contents - test = DocSection('header name', ['hello', 'i am']) - assert test.make_numpy_docstring(11, 0, 4) == 'Header Name\n-----------\nhello\n\ni am\n\n' + test = DocSection("header name", ["hello", "i am"]) + assert test.make_numpy_docstring(11, 0, 4) == "Header Name\n-----------\nhello\n\ni am\n\n" # doc description - test = DocSection('header name', DocDescription('var_name', types=str, descs='Example.')) - assert (test.make_numpy_docstring(20, 0, 4) == - 'Header Name\n-----------\nvar_name : str\n Example.\n\n') + test = DocSection("header name", DocDescription("var_name", types=str, descs="Example.")) + assert ( + test.make_numpy_docstring(20, 0, 4) + == "Header Name\n-----------\nvar_name : str\n Example.\n\n" + ) # multiple doc descriptions - test = DocSection('header name', [DocDescription('var_name1', types=str, descs='Example 1.'), - DocDescription('var_name2', types=int, descs='Example 2.')]) - assert (test.make_numpy_docstring(20, 0, 4) == - 'Header Name\n-----------\nvar_name1 : str\n Example 1.\nvar_name2 : int\n' - ' Example 2.\n\n') + test = DocSection( + "header name", + [ + DocDescription("var_name1", types=str, descs="Example 1."), + DocDescription("var_name2", types=int, descs="Example 2."), + ], + ) + assert ( + test.make_numpy_docstring(20, 0, 4) + == "Header Name\n-----------\nvar_name1 : str\n Example 1.\nvar_name2 : int\n" + " Example 2.\n\n" + ) # signature does nothing - test = DocSection('header name', - DocDescription('var_name', signature='(a, b)', types=str, descs='Example.')) - assert (test.make_numpy_docstring(20, 0, 4) == - 'Header Name\n-----------\nvar_name : str\n Example.\n\n') + test = DocSection( + "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") + ) + assert ( + test.make_numpy_docstring(20, 0, 4) + == "Header Name\n-----------\nvar_name : str\n Example.\n\n" + ) def test_make_numpy_docstring_signature(): """Test DocSection.make_numpy_docstring_signature.""" - test = DocSection('header name', - DocDescription('var_name', signature='(a, b)', types=str, descs='Example.')) - assert (test.make_numpy_docstring_signature(20, 0, 4) == - 'Header Name\n-----------\nvar_name(a, b) : str\n Example.\n\n') + test = DocSection( + "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") + ) + assert ( + test.make_numpy_docstring_signature(20, 0, 4) + == "Header Name\n-----------\nvar_name(a, b) : str\n Example.\n\n" + ) def test_make_google_docstring(): """Test DocSection.make_google_docstring.""" with pytest.raises(ValueError): - test = DocSection('quite long header name', '') + test = DocSection("quite long header name", "") test.make_google_docstring(10, 0, 4) # no header - test = DocSection('', 'Some text.') - assert test.make_google_docstring(10, 0, 4) == ('Some text.\n\n') + test = DocSection("", "Some text.") + assert test.make_google_docstring(10, 0, 4) == ("Some text.\n\n") # one docdescription - test = DocSection('header name', - DocDescription('var_name', signature='(a, b)', types=str, descs='Example.')) - assert test.make_google_docstring(35, 0, 4) == ('Header Name:\n' - ' var_name (:obj:`str`): Example.\n\n') + test = DocSection( + "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") + ) + assert test.make_google_docstring(35, 0, 4) == ( + "Header Name:\n" " var_name (:obj:`str`): Example.\n\n" + ) # multiple docdescription - test = DocSection('header name', - [DocDescription('var1', signature='(a, b)', types=str, descs='Example1.'), - DocDescription('var2', signature='(c)', types='int', descs='Example2.')]) - assert test.make_google_docstring(35, 0, 4) == ('Header Name:\n' - ' var1 (:obj:`str`): Example1.\n' - ' var2 (int): Example2.\n\n') + test = DocSection( + "header name", + [ + DocDescription("var1", signature="(a, b)", types=str, descs="Example1."), + DocDescription("var2", signature="(c)", types="int", descs="Example2."), + ], + ) + assert test.make_google_docstring(35, 0, 4) == ( + "Header Name:\n" " var1 (:obj:`str`): Example1.\n" " var2 (int): Example2.\n\n" + ) # one string - test = DocSection('header name', 'Some text.') - assert test.make_google_docstring(14, 0, 4) == ('Header Name:\n' - ' Some text.\n\n') - assert test.make_google_docstring(13, 0, 4) == ('Header Name:\n' - ' Some\n' - ' text.\n\n') + test = DocSection("header name", "Some text.") + assert test.make_google_docstring(14, 0, 4) == ("Header Name:\n" " Some text.\n\n") + assert test.make_google_docstring(13, 0, 4) == ("Header Name:\n" " Some\n" " text.\n\n") # multiple string - test = DocSection('header name', ['Some text.', 'Another text.']) - assert test.make_google_docstring(17, 0, 4) == ('Header Name:\n' - ' Some text.\n\n' - ' Another text.\n\n') - assert test.make_google_docstring(14, 0, 4) == ('Header Name:\n' - ' Some text.\n\n' - ' Another\n' - ' text.\n\n') - assert test.make_google_docstring(13, 0, 4) == ('Header Name:\n' - ' Some\n' - ' text.\n\n' - ' Another\n' - ' text.\n\n') + test = DocSection("header name", ["Some text.", "Another text."]) + assert test.make_google_docstring(17, 0, 4) == ( + "Header Name:\n" " Some text.\n\n" " Another text.\n\n" + ) + assert test.make_google_docstring(14, 0, 4) == ( + "Header Name:\n" " Some text.\n\n" " Another\n" " text.\n\n" + ) + assert test.make_google_docstring(13, 0, 4) == ( + "Header Name:\n" " Some\n" " text.\n\n" " Another\n" " text.\n\n" + ) def test_make_rst_docstring(): """Test DocSection.make_rst_docstring.""" # no header - test = DocSection('', 'Some text.') - assert test.make_rst_docstring(10, 0, 4) == ('Some text.\n\n') + test = DocSection("", "Some text.") + assert test.make_rst_docstring(10, 0, 4) == ("Some text.\n\n") # normal header, one docdescription - test = DocSection('header name', - DocDescription('var_name', signature='(a, b)', types=str, descs='Example.')) - assert test.make_rst_docstring(35, 0, 4) == (':Header Name:\n\n' - ':param var_name: Example.\n' - ':type var_name: :obj:`str`\n\n') + test = DocSection( + "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") + ) + assert test.make_rst_docstring(35, 0, 4) == ( + ":Header Name:\n\n" ":param var_name: Example.\n" ":type var_name: :obj:`str`\n\n" + ) # normal header, multiple docdescription - test = DocSection('header name', - [DocDescription('var1', signature='(a, b)', types=str, descs='Example1.'), - DocDescription('var2', signature='(c)', types='int', descs='Example2.')]) - assert test.make_rst_docstring(35, 0, 4) == (':Header Name:\n\n' - ':param var1: Example1.\n' - ':type var1: :obj:`str`\n' - ':param var2: Example2.\n' - ':type var2: int\n\n') + test = DocSection( + "header name", + [ + DocDescription("var1", signature="(a, b)", types=str, descs="Example1."), + DocDescription("var2", signature="(c)", types="int", descs="Example2."), + ], + ) + assert test.make_rst_docstring(35, 0, 4) == ( + ":Header Name:\n\n" + ":param var1: Example1.\n" + ":type var1: :obj:`str`\n" + ":param var2: Example2.\n" + ":type var2: int\n\n" + ) # normal header, one string - test = DocSection('header name', 'Some text.') - assert test.make_rst_docstring(13, 0, 4) == (':Header Name:\n\n' - 'Some text.\n\n') + test = DocSection("header name", "Some text.") + assert test.make_rst_docstring(13, 0, 4) == (":Header Name:\n\n" "Some text.\n\n") # normal header, multiple string - test = DocSection('header name', ['Some text.', 'Another text.']) - assert test.make_rst_docstring(13, 0, 4) == (':Header Name:\n\n' - 'Some text.\n\n' - 'Another text.\n\n') + test = DocSection("header name", ["Some text.", "Another text."]) + assert test.make_rst_docstring(13, 0, 4) == ( + ":Header Name:\n\n" "Some text.\n\n" "Another text.\n\n" + ) # special header, doc description - test = DocSection('see also', - [DocDescription('var1', signature='(a, b)', types=str, descs='Example1.'), - DocDescription('var2', signature='(c)', types='int', descs='Example2.')]) - assert test.make_rst_docstring(35, 0, 4) == ('.. seealso::\n' - ' :param var1: Example1.\n' - ' :type var1: :obj:`str`\n' - ' :param var2: Example2.\n' - ' :type var2: int\n\n') + test = DocSection( + "see also", + [ + DocDescription("var1", signature="(a, b)", types=str, descs="Example1."), + DocDescription("var2", signature="(c)", types="int", descs="Example2."), + ], + ) + assert test.make_rst_docstring(35, 0, 4) == ( + ".. seealso::\n" + " :param var1: Example1.\n" + " :type var1: :obj:`str`\n" + " :param var2: Example2.\n" + " :type var2: int\n\n" + ) # special header, string - test = DocSection('to do', ['Example 1, something.', 'Example 2.']) - assert test.make_rst_docstring(20, 0, 4) == ('.. todo:: Example 1,\n' - ' something.\n' - ' Example 2.\n\n') + test = DocSection("to do", ["Example 1, something.", "Example 2."]) + assert test.make_rst_docstring(20, 0, 4) == ( + ".. todo:: Example 1,\n" " something.\n" " Example 2.\n\n" + ) def test_section_summary_init(): """Test Summary.__init__.""" with pytest.raises(TypeError): - Summary(['summary']) + Summary(["summary"]) with pytest.raises(TypeError): - Summary(DocDescription('something')) - test = Summary('very very long summary') - assert test.header == '' - assert test.contents == ['very very long summary'] + Summary(DocDescription("something")) + test = Summary("very very long summary") + assert test.header == "" + assert test.contents == ["very very long summary"] def test_section_summary_make_docstring(): """Test Summary.make_docstring.""" - test = Summary('very very long summary') - assert (test.make_docstring(25, 0, 4, summary_only=True, special=False) == - 'very very long summary\n\n') - assert (test.make_docstring(24, 0, 4, summary_only=True, special=False) == - '\nvery very long summary\n\n') - assert (test.make_docstring(28, 0, 4, summary_only=True, special=False) == - 'very very long summary') - assert (test.make_docstring(25, 0, 4, summary_only=False, special=False) == - 'very very long summary\n\n') - assert (test.make_docstring(24, 0, 4, summary_only=False, special=False) == - '\nvery very long summary\n\n') - assert (test.make_docstring(28, 0, 4, summary_only=False, special=False) == - 'very very long summary\n\n') - - assert not (test.make_docstring(25, 0, 4, summary_only=True, special=True) == - 'very very long summary\n\n') - assert (test.make_docstring(26, 0, 4, summary_only=True, special=True) == - 'very very long summary\n\n') - assert not (test.make_docstring(28, 0, 4, summary_only=True, special=True) == - 'very very long summary') - assert (test.make_docstring(29, 0, 4, summary_only=True, special=True) == - 'very very long summary') - - test = Summary('very very very very very very long summary') + test = Summary("very very long summary") + assert ( + test.make_docstring(25, 0, 4, summary_only=True, special=False) + == "very very long summary\n\n" + ) + assert ( + test.make_docstring(24, 0, 4, summary_only=True, special=False) + == "\nvery very long summary\n\n" + ) + assert ( + test.make_docstring(28, 0, 4, summary_only=True, special=False) == "very very long summary" + ) + assert ( + test.make_docstring(25, 0, 4, summary_only=False, special=False) + == "very very long summary\n\n" + ) + assert ( + test.make_docstring(24, 0, 4, summary_only=False, special=False) + == "\nvery very long summary\n\n" + ) + assert ( + test.make_docstring(28, 0, 4, summary_only=False, special=False) + == "very very long summary\n\n" + ) + + assert not ( + test.make_docstring(25, 0, 4, summary_only=True, special=True) + == "very very long summary\n\n" + ) + assert ( + test.make_docstring(26, 0, 4, summary_only=True, special=True) + == "very very long summary\n\n" + ) + assert not ( + test.make_docstring(28, 0, 4, summary_only=True, special=True) == "very very long summary" + ) + assert ( + test.make_docstring(29, 0, 4, summary_only=True, special=True) == "very very long summary" + ) + + test = Summary("very very very very very very long summary") with pytest.raises(ValueError): test.make_docstring(30, 0, 4) def test_section_extended_summary(): """Test ExtendedSummary.__init__.""" - test = ExtendedSummary('This is an extended summary.') - assert test.header == '' - assert test.contents == ['This is an extended summary.'] + test = ExtendedSummary("This is an extended summary.") + assert test.header == "" + assert test.contents == ["This is an extended summary."] def test_section_parameters(): """Test Parameters.__init__.""" - desc1 = DocDescription('a', types=str, descs='Example 1.') - desc2 = DocDescription('b', types=int, descs='Example 2.') + desc1 = DocDescription("a", types=str, descs="Example 1.") + desc2 = DocDescription("b", types=int, descs="Example 2.") test = Parameters([desc1, desc2]) - assert test.header == 'parameters' + assert test.header == "parameters" assert test.contents == [desc1, desc2] def test_section_attributes(): """Test Attributes.__init__.""" - desc1 = DocDescription('a', types=str, descs='Example 1.') - desc2 = DocDescription('b', types=int, descs='Example 2.') + desc1 = DocDescription("a", types=str, descs="Example 1.") + desc2 = DocDescription("b", types=int, descs="Example 2.") test = Attributes([desc1, desc2]) - assert test.header == 'attributes' + assert test.header == "attributes" assert test.contents == [desc1, desc2] def test_section_methods(): """Test Methods.__init__.""" - desc1 = DocDescription('f', signature='(x, y)', types=int, descs='Example 1.') - desc2 = DocDescription('g', signature='(z=1)', types=int, descs='Example 2.') + desc1 = DocDescription("f", signature="(x, y)", types=int, descs="Example 1.") + desc2 = DocDescription("g", signature="(z=1)", types=int, descs="Example 2.") test = Methods([desc1, desc2]) - assert test.header == 'methods' + assert test.header == "methods" assert test.contents == [desc1, desc2] def test_section_returns(): """Test Returns.__init__.""" - desc1 = DocDescription('a', types=int, descs='Example 1.') - desc2 = DocDescription('b', types=str, descs='Example 2.') + desc1 = DocDescription("a", types=int, descs="Example 1.") + desc2 = DocDescription("b", types=str, descs="Example 2.") test = Returns([desc1, desc2]) - assert test.header == 'returns' + assert test.header == "returns" assert test.contents == [desc1, desc2] def test_section_yields(): """Test Yields.__init__.""" - desc = DocDescription('a', types=int, descs='Example 1.') + desc = DocDescription("a", types=int, descs="Example 1.") test = Yields(desc) - assert test.header == 'yields' + assert test.header == "yields" assert test.contents == [desc] def test_section_otherparameters(): """Test OtherParameters.__init__.""" - desc1 = DocDescription('a', types=int, descs='Example 1.') - desc2 = DocDescription('b', types=str, descs='Example 2.') + desc1 = DocDescription("a", types=int, descs="Example 1.") + desc2 = DocDescription("b", types=str, descs="Example 2.") test = OtherParameters([desc1, desc2]) - assert test.header == 'other parameters' + assert test.header == "other parameters" assert test.contents == [desc1, desc2] def test_section_raises(): """Test Raises.__init__.""" - desc = DocDescription('TypeError', descs='If something.') + desc = DocDescription("TypeError", descs="If something.") test = Raises(desc) - assert test.header == 'raises' + assert test.header == "raises" assert test.contents == [desc] def test_section_warns(): """Test Warns.__init__.""" - desc = DocDescription('Warning', descs='If something.') + desc = DocDescription("Warning", descs="If something.") test = Warns(desc) - assert test.header == 'warns' + assert test.header == "warns" assert test.contents == [desc] def test_section_warnings(): """Test Warnings.__init__.""" - test = Warnings('Not to be used.') - assert test.header == 'warnings' - assert test.contents == ['Not to be used.'] + test = Warnings("Not to be used.") + assert test.header == "warnings" + assert test.contents == ["Not to be used."] def test_section_seealso(): """Test SeeAlso.__init__.""" - test = SeeAlso('Some other code.') - assert test.header == 'see also' - assert test.contents == ['Some other code.'] + test = SeeAlso("Some other code.") + assert test.header == "see also" + assert test.contents == ["Some other code."] def test_section_notes(): """Test Notes.__init__.""" - test = Notes('Some comment.') - assert test.header == 'notes' - assert test.contents == ['Some comment.'] + test = Notes("Some comment.") + assert test.header == "notes" + assert test.contents == ["Some comment."] def test_section_references(): """Test References.__init__.""" - test = References('Some reference.') - assert test.header == 'references' - assert test.contents == ['Some reference.'] + test = References("Some reference.") + assert test.header == "references" + assert test.contents == ["Some reference."] def test_section_examples(): """Test Examples.__init__.""" - test = Examples('Some example.') - assert test.header == 'examples' - assert test.contents == ['Some example.'] + test = Examples("Some example.") + assert test.header == "examples" + assert test.contents == ["Some example."] diff --git a/docinstance/docstring.py b/docinstance/docstring.py index 736750e..96c7ba1 100644 --- a/docinstance/docstring.py +++ b/docinstance/docstring.py @@ -22,7 +22,7 @@ class Docstring: """ - def __init__(self, sections, default_style='numpy'): + def __init__(self, sections, default_style="numpy"): """Initialize. Parameters @@ -44,19 +44,26 @@ def __init__(self, sections, default_style='numpy'): """ if isinstance(sections, (str, DocContent)): sections = [sections] - elif not (isinstance(sections, (list, tuple)) and - all(isinstance(i, (str, DocContent)) for i in sections)): - raise TypeError('Sections of the docstring must be provided as a string, list/tuple of ' - 'strings, or list/tuple of DocContent instances.') + elif not ( + isinstance(sections, (list, tuple)) + and all(isinstance(i, (str, DocContent)) for i in sections) + ): + raise TypeError( + "Sections of the docstring must be provided as a string, list/tuple of " + "strings, or list/tuple of DocContent instances." + ) # NOTE: should the empty sections be allowed? elif not sections: - raise ValueError('At least one section must be provided.') - self.sections = [section if isinstance(section, DocSection) else DocSection('', section) - for section in sections] - - if default_style not in ['numpy', 'numpy with signature', 'google', 'rst']: - raise ValueError("Default style must be one of 'numpy', 'numpy with signature', " - "'google', 'rst'.") + raise ValueError("At least one section must be provided.") + self.sections = [ + section if isinstance(section, DocSection) else DocSection("", section) + for section in sections + ] + + if default_style not in ["numpy", "numpy with signature", "google", "rst"]: + raise ValueError( + "Default style must be one of 'numpy', 'numpy with signature', " "'google', 'rst'." + ) self.default_style = default_style # pylint: disable=R0912 @@ -105,56 +112,63 @@ def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): style = self.default_style # check input if not isinstance(width, int): - raise TypeError('Maximum width of the line must be given as an integer.') + raise TypeError("Maximum width of the line must be given as an integer.") elif width <= 0: - raise ValueError('Maximum width of the line must be greater than zero.') + raise ValueError("Maximum width of the line must be greater than zero.") if not isinstance(indent_level, int): - raise TypeError('Level of indentation must be given as an integer.') + raise TypeError("Level of indentation must be given as an integer.") elif indent_level < 0: - raise ValueError('Level of indentation must be greater than or equal to zero.') + raise ValueError("Level of indentation must be greater than or equal to zero.") if not isinstance(tabsize, int): - raise TypeError('Number of spaces in a tab must be given as an integer.') + raise TypeError("Number of spaces in a tab must be given as an integer.") elif tabsize <= 0: - raise ValueError('Number of spaces in a tab must be greater than zero.') - - if style == 'numpy': - docstring_func = 'make_numpy_docstring' - elif style == 'numpy with signature': - docstring_func = 'make_numpy_docstring_signature' - elif style == 'google': - docstring_func = 'make_google_docstring' - elif style == 'rst': - docstring_func = 'make_rst_docstring' + raise ValueError("Number of spaces in a tab must be greater than zero.") + + if style == "numpy": + docstring_func = "make_numpy_docstring" + elif style == "numpy with signature": + docstring_func = "make_numpy_docstring_signature" + elif style == "google": + docstring_func = "make_google_docstring" + elif style == "rst": + docstring_func = "make_rst_docstring" else: - raise ValueError("Given docstring style must be one of 'numpy', 'numpy with signature'," - " 'google', 'rst'.") + raise ValueError( + "Given docstring style must be one of 'numpy', 'numpy with signature'," + " 'google', 'rst'." + ) # FIXME: this may not be necessary and can be removed if not self.check_section_order(style): - raise ValueError('Sections must be ordered according to the guideline set by the given ' - 'docstring style.') + raise ValueError( + "Sections must be ordered according to the guideline set by the given " + "docstring style." + ) - output = '' + output = "" # check that first section does not have a header - if self.sections[0].header != '': - raise ValueError('First section of the docstring (summary) must have an empty header.') + if self.sections[0].header != "": + raise ValueError("First section of the docstring (summary) must have an empty header.") # add summary summary = Summary(self.sections[0].contents[0]) - output += summary.make_docstring(width, indent_level, tabsize, - summary_only=(len(self.sections) == - len(self.sections[0].contents) == 1)) + output += summary.make_docstring( + width, + indent_level, + tabsize, + summary_only=(len(self.sections) == len(self.sections[0].contents) == 1), + ) # add remaining summary if len(self.sections[0].contents) > 1: - summary = DocSection('', self.sections[0].contents[1:]) + summary = DocSection("", self.sections[0].contents[1:]) output += getattr(summary, docstring_func)(width, indent_level, tabsize) # add other sections if len(self.sections) > 1: for section in self.sections[1:]: output += getattr(section, docstring_func)(width, indent_level, tabsize) # add whitespace to indent the triple quotation - output += ' ' * indent_level * tabsize + output += " " * indent_level * tabsize return output # TODO: add ordering of the other styles. It seems that only numpy cares about the ordering of @@ -186,16 +200,32 @@ def check_section_order(self, style): # sections with values over 99) default_value = 99 # only numpy seems to care about the ordering - if style == 'numpy': - ordering = {'': 0, 'parameters': 1, 'attributes': 2, 'methods': 3, 'returns': 4, - 'yields': 4, 'other parameters': 5, 'raises': 6, 'warns': 7, 'warnings': 8, - 'see also': 9, 'notes': 10, 'references': 11, 'examples': 12} + if style == "numpy": + ordering = { + "": 0, + "parameters": 1, + "attributes": 2, + "methods": 3, + "returns": 4, + "yields": 4, + "other parameters": 5, + "raises": 6, + "warns": 7, + "warnings": 8, + "see also": 9, + "notes": 10, + "references": 11, + "examples": 12, + } allow_other_sections = False else: ordering = {} - order_values = [ordering.get(section.header.lower(), default_value) - for section in self.sections] + order_values = [ + ordering.get(section.header.lower(), default_value) for section in self.sections + ] if not allow_other_sections and any(i == default_value for i in order_values): - raise ValueError('For the docstring style, {0}, the headings of the sections must be ' - 'one of {1}'.format(style, list(ordering.keys()))) + raise ValueError( + "For the docstring style, {0}, the headings of the sections must be " + "one of {1}".format(style, list(ordering.keys())) + ) return all(i <= j for i, j in zip(order_values, order_values[1:])) diff --git a/docinstance/parser/latex.py b/docinstance/parser/latex.py index 1bf741b..3569974 100644 --- a/docinstance/parser/latex.py +++ b/docinstance/parser/latex.py @@ -19,7 +19,7 @@ def is_math(text): False otherwise. """ - re_math = re.compile(r'^\s*\.\.\s*math::\n*(?:\n\s+.+)+\n*$') + re_math = re.compile(r"^\s*\.\.\s*math::\n*(?:\n\s+.+)+\n*$") return bool(re_math.search(text)) @@ -38,17 +38,17 @@ def parse_equation(text): Equations are stored as instances of DocEquation. """ - re_math = re.compile(r'\s*(\.\.\s*math::\n*(?: +.+\n?)+)\n*') + re_math = re.compile(r"\s*(\.\.\s*math::\n*(?: +.+\n?)+)\n*") # split equations split_text = [] for block in re_math.split(text): # remove empty blocks - if block == '': + if block == "": continue # remove trailing newline if is_math(block): - block = re.sub(r'\n*$', '', block) - block = re.sub(r'^\s*\.\.\s*math::\n*', '', block) + block = re.sub(r"\n*$", "", block) + block = re.sub(r"^\s*\.\.\s*math::\n*", "", block) block = inspect.cleandoc(block) block = DocEquation(block) split_text.append(block) diff --git a/docinstance/parser/numpy.py b/docinstance/parser/numpy.py index e726093..786be04 100644 --- a/docinstance/parser/numpy.py +++ b/docinstance/parser/numpy.py @@ -4,10 +4,24 @@ from docinstance.parser.latex import parse_equation from docinstance.docstring import Docstring from docinstance.content.description import DocDescription -from docinstance.content.section import (DocSection, Summary, ExtendedSummary, Parameters, - Attributes, Methods, Returns, Yields, OtherParameters, - Raises, Warns, Warnings, SeeAlso, Notes, References, - Examples) +from docinstance.content.section import ( + DocSection, + Summary, + ExtendedSummary, + Parameters, + Attributes, + Methods, + Returns, + Yields, + OtherParameters, + Raises, + Warns, + Warnings, + SeeAlso, + Notes, + References, + Examples, +) from docinstance.content.equation import DocEquation @@ -47,27 +61,29 @@ def parse_numpy(docstring, contains_quotes=False): Copied from https://github.com/kimt33/pydocstring. """ - docstring = inspect.cleandoc('\n' * contains_quotes + docstring) + docstring = inspect.cleandoc("\n" * contains_quotes + docstring) # remove quotes from docstring if contains_quotes: - quotes = r'[\'\"]{3}' - if re.search(r'^r{0}'.format(quotes), docstring): - raise NotImplementedError('A raw string quotation, i.e. r""" cannot be given as a ' - 'string, i.e. from reading a python file as a string, ' - 'because the backslashes belonging to escape sequences ' - 'cannot be distinguished from those of normal backslash.' - 'You either need to change existing raw string to normal ' - 'i.e. convert all occurences of \\ to \\\\, or import the ' - 'docstring from the instance through `__doc__` attribute.') + quotes = r"[\'\"]{3}" + if re.search(r"^r{0}".format(quotes), docstring): + raise NotImplementedError( + 'A raw string quotation, i.e. r""" cannot be given as a ' + "string, i.e. from reading a python file as a string, " + "because the backslashes belonging to escape sequences " + "cannot be distinguished from those of normal backslash." + "You either need to change existing raw string to normal " + "i.e. convert all occurences of \\ to \\\\, or import the " + "docstring from the instance through `__doc__` attribute." + ) else: - quotes = r'' - docstring = re.sub(r'^{0}'.format(quotes), '', docstring) - docstring = re.sub(r'{0}$'.format(quotes), '', docstring) + quotes = r"" + docstring = re.sub(r"^{0}".format(quotes), "", docstring) + docstring = re.sub(r"{0}$".format(quotes), "", docstring) sections = [] # summary - for regex in [r'^\n?(.+?)\n\n+', r'^\n?(.*?)\n*$']: + for regex in [r"^\n?(.+?)\n\n+", r"^\n?(.*?)\n*$"]: re_summary = re.compile(regex) try: sections.append(Summary(re_summary.search(docstring).group(1))) @@ -75,19 +91,22 @@ def parse_numpy(docstring, contains_quotes=False): except AttributeError: pass else: - raise ValueError('The summary must be in the first or the second line with a blank line ' - 'afterwards.') + raise ValueError( + "The summary must be in the first or the second line with a blank line " "afterwards." + ) # remove summary from docstring - docstring = re_summary.sub('', docstring) - if docstring == '': + docstring = re_summary.sub("", docstring) + if docstring == "": return Docstring(sections) # if headers do not exist - re_header = re.compile(r'\n*(.+)\n(-+)\n+') + re_header = re.compile(r"\n*(.+)\n(-+)\n+") if re_header.search(docstring) is None: # split into blocks by math equations and multiple newlines - extended = [[lines] if isinstance(lines, DocEquation) else re.split(r'\n\n+', lines) - for lines in parse_equation(docstring)] + extended = [ + [lines] if isinstance(lines, DocEquation) else re.split(r"\n\n+", lines) + for lines in parse_equation(docstring) + ] extended = [line for lines in extended for line in lines] extended_contents = [] for block in extended: @@ -97,11 +116,11 @@ def parse_numpy(docstring, contains_quotes=False): # continue if not isinstance(block, DocEquation): # remove quotes - block = re.sub(r'\n*{0}$'.format(quotes), '', block) + block = re.sub(r"\n*{0}$".format(quotes), "", block) # remove trailing newlines - block = re.sub(r'\n+$', '', block) + block = re.sub(r"\n+$", "", block) # replace newlines - block = block.replace('\n', ' ') + block = block.replace("\n", " ") extended_contents.append(block) sections.append(ExtendedSummary(extended_contents)) return Docstring(sections) @@ -113,8 +132,10 @@ def parse_numpy(docstring, contains_quotes=False): extended, *split_docstring = split_docstring # FIXME: repeated code # extract math and split blocks - extended = [[lines] if isinstance(lines, DocEquation) else re.split(r'\n\n+', lines) - for lines in parse_equation(extended)] + extended = [ + [lines] if isinstance(lines, DocEquation) else re.split(r"\n\n+", lines) + for lines in parse_equation(extended) + ] extended = [line for lines in extended for line in lines] # process blocks processed_extended = [] @@ -125,79 +146,95 @@ def parse_numpy(docstring, contains_quotes=False): # continue if not isinstance(block, DocEquation): # remove quotes - block = re.sub(r'\n*{0}$'.format(quotes), '', block) + block = re.sub(r"\n*{0}$".format(quotes), "", block) # remove trailing newlines - block = re.sub(r'\n+$', '', block) + block = re.sub(r"\n+$", "", block) # replace newlines - block = block.replace('\n', ' ') + block = block.replace("\n", " ") processed_extended.append(block) if processed_extended != []: sections.append(ExtendedSummary(processed_extended)) - headers_sections = {'parameters': Parameters, 'other parameters': OtherParameters, - 'attributes': Attributes, 'methods': Methods, 'returns': Returns, - 'yields': Yields, 'raises': Raises, 'see also': SeeAlso, 'warns': Warns, - 'warnings': Warnings, 'examples': Examples, 'references': References, - 'notes': Notes, 'properties': None, 'abstract properties': None, - 'abstract methods': None} - for header, lines, contents in zip(split_docstring[0::3], - split_docstring[1::3], - split_docstring[2::3]): - contents = re.sub(r'\n+$', r'\n', contents) + headers_sections = { + "parameters": Parameters, + "other parameters": OtherParameters, + "attributes": Attributes, + "methods": Methods, + "returns": Returns, + "yields": Yields, + "raises": Raises, + "see also": SeeAlso, + "warns": Warns, + "warnings": Warnings, + "examples": Examples, + "references": References, + "notes": Notes, + "properties": None, + "abstract properties": None, + "abstract methods": None, + } + for header, lines, contents in zip( + split_docstring[0::3], split_docstring[1::3], split_docstring[2::3] + ): + contents = re.sub(r"\n+$", r"\n", contents) if len(header) != len(lines): - raise ValueError('Need {0} of `-` underneath the header title, {1}' - ''.format(len(header), header)) + raise ValueError( + "Need {0} of `-` underneath the header title, {1}" "".format(len(header), header) + ) header = header.lower() header_contents = [] # special headers (special format for each entry) if header in headers_sections: - entries = (entry for entry in re.split(r'\n(?!\s+)', contents) if entry != '') + entries = (entry for entry in re.split(r"\n(?!\s+)", contents) if entry != "") # FIXME: following regular expression would work only if docstring has spaces adjacent # to ':' - re_entry = re.compile(r'^(.+?)(\(.+?\))?(?: *: *(.+))?(?:\n|$)') + re_entry = re.compile(r"^(.+?)(\(.+?\))?(?: *: *(.+))?(?:\n|$)") for entry in entries: # keep only necessary pieces _, name, signature, types, descs = re_entry.split(entry) # process signature if signature is None: - signature = '' + signature = "" else: - signature = ', '.join(i.strip() for i in signature.split(',')) + signature = ", ".join(i.strip() for i in signature.split(",")) # process types if types is None: types = [] - elif re.search(r'\{.+\}', types): - types = re.search(r'^\{((?:(.+?),\s*)*(.+?))\}$', types).group(1) - types = re.split(r',\s*', types) + elif re.search(r"\{.+\}", types): + types = re.search(r"^\{((?:(.+?),\s*)*(.+?))\}$", types).group(1) + types = re.split(r",\s*", types) else: - types = re.search(r'^((?:(.+?),\s*)*(.+?))$', types).group(1) - types = re.split(r',\s*', types) + types = re.search(r"^((?:(.+?),\s*)*(.+?))$", types).group(1) + types = re.split(r",\s*", types) types = [i for i in types if i is not None] # process documentation - descs = inspect.cleandoc('\n' + descs) + descs = inspect.cleandoc("\n" + descs) # NOTE: period is used to terminate a description. i.e. one description is # distinguished from another with a period and a newline. - descs = re.split(r'\.\n+', descs) + descs = re.split(r"\.\n+", descs) # add period (only the last line is not missing the period) - descs = [line + '.' for line in descs[:-1]] + descs[-1:] + descs = [line + "." for line in descs[:-1]] + descs[-1:] # extract equations descs = [line for lines in descs for line in parse_equation(lines)] # non math blocks will replace newlines with spaces. # math blocks will add newline at the end - descs = [line if isinstance(line, DocEquation) else line.replace('\n', ' ') - for line in descs] + descs = [ + line if isinstance(line, DocEquation) else line.replace("\n", " ") + for line in descs + ] # store - header_contents.append(DocDescription(name, signature=signature, types=types, - descs=descs)) + header_contents.append( + DocDescription(name, signature=signature, types=types, descs=descs) + ) else: - header_contents = [i for i in re.split(r'\n\n+', contents) if i != ''] + header_contents = [i for i in re.split(r"\n\n+", contents) if i != ""] try: sections.append(headers_sections[header](header_contents)) except KeyError: diff --git a/docinstance/parser/test/test_latex.py b/docinstance/parser/test/test_latex.py index ffca320..5615bf0 100644 --- a/docinstance/parser/test/test_latex.py +++ b/docinstance/parser/test/test_latex.py @@ -5,37 +5,37 @@ def test_is_math(): """Test docinstance.parser.numpy.is_math.""" - assert is_math('.. math::\n\n x&=2\\\\\n &=3') - assert is_math('.. math::\n\n x&=2\\\\\n &=3\n') - assert is_math('\n.. math::\n\n x&=2\\\\\n &=3\n\n\n') - assert not is_math('x\n.. math::\n\n x&=2\\\\\n &=3\n\n\n') - assert is_math('.. math::\n\n x=2') - assert is_math(' .. math::\n\n x=2') - assert is_math('\n .. math::\n\n x=2') + assert is_math(".. math::\n\n x&=2\\\\\n &=3") + assert is_math(".. math::\n\n x&=2\\\\\n &=3\n") + assert is_math("\n.. math::\n\n x&=2\\\\\n &=3\n\n\n") + assert not is_math("x\n.. math::\n\n x&=2\\\\\n &=3\n\n\n") + assert is_math(".. math::\n\n x=2") + assert is_math(" .. math::\n\n x=2") + assert is_math("\n .. math::\n\n x=2") def test_parse_equation(): """Test docinstance.parser.numpy.parse_equation.""" - test = parse_equation('.. math::\n\n x &= 2\\\\\n &= 3\n') + test = parse_equation(".. math::\n\n x &= 2\\\\\n &= 3\n") assert len(test) == 1 assert isinstance(test[0], DocEquation) - assert test[0].equations == ['x &= 2\\\\', '&= 3'] + assert test[0].equations == ["x &= 2\\\\", "&= 3"] - test = parse_equation('x\n.. math::\n\n x &= 2\\\\\n &= 3\n') + test = parse_equation("x\n.. math::\n\n x &= 2\\\\\n &= 3\n") assert len(test) == 2 - assert test[0] == 'x' + assert test[0] == "x" assert isinstance(test[1], DocEquation) - assert test[1].equations == ['x &= 2\\\\', '&= 3'] + assert test[1].equations == ["x &= 2\\\\", "&= 3"] - test = parse_equation('x\n.. math::\n\n x &= 2\\\\\n &= 3\n\n\n') + test = parse_equation("x\n.. math::\n\n x &= 2\\\\\n &= 3\n\n\n") assert len(test) == 2 - assert test[0] == 'x' + assert test[0] == "x" assert isinstance(test[1], DocEquation) - assert test[1].equations == ['x &= 2\\\\', '&= 3'] + assert test[1].equations == ["x &= 2\\\\", "&= 3"] - test = parse_equation('x\n.. math::\n\n x &= 2\\\\\n &= 3\n\ny') + test = parse_equation("x\n.. math::\n\n x &= 2\\\\\n &= 3\n\ny") assert len(test) == 3 - assert test[0] == 'x' + assert test[0] == "x" assert isinstance(test[1], DocEquation) - assert test[1].equations == ['x &= 2\\\\', '&= 3'] - assert test[2] == 'y' + assert test[1].equations == ["x &= 2\\\\", "&= 3"] + assert test[2] == "y" diff --git a/docinstance/parser/test/test_numpy.py b/docinstance/parser/test/test_numpy.py index 7f35b73..bb44a1a 100644 --- a/docinstance/parser/test/test_numpy.py +++ b/docinstance/parser/test/test_numpy.py @@ -1,7 +1,7 @@ """Tests for docinstance.parser.numpy.""" import pytest from docinstance.docstring import Docstring -from docinstance.content.section import (DocSection, Summary, ExtendedSummary, Parameters) +from docinstance.content.section import DocSection, Summary, ExtendedSummary, Parameters from docinstance.content.description import DocDescription from docinstance.content.equation import DocEquation from docinstance.parser.numpy import parse_numpy @@ -9,33 +9,52 @@ def test_compare_docinstances(): """Test docinstance.parser.test.test_numpy.""" - doc1 = Docstring(['summary', - DocSection('section1', ['content1', 'content2']), - DocSection('section2', [DocDescription('name1', '(a, b)', str, - ['desc1', 'desc2']), - DocDescription('name2', '(c, d)', int, - ['desc3', DocEquation('desc4')])])]) - doc2 = Docstring(['summary', - DocSection('section1', ['content1', 'content2']), - DocSection('section2', [DocDescription('name1', '(a, b)', str, - ['desc1', 'desc2']), - DocDescription('name2', '(c, d)', int, - ['desc3', DocEquation('desc4')])])]) + doc1 = Docstring( + [ + "summary", + DocSection("section1", ["content1", "content2"]), + DocSection( + "section2", + [ + DocDescription("name1", "(a, b)", str, ["desc1", "desc2"]), + DocDescription("name2", "(c, d)", int, ["desc3", DocEquation("desc4")]), + ], + ), + ] + ) + doc2 = Docstring( + [ + "summary", + DocSection("section1", ["content1", "content2"]), + DocSection( + "section2", + [ + DocDescription("name1", "(a, b)", str, ["desc1", "desc2"]), + DocDescription("name2", "(c, d)", int, ["desc3", DocEquation("desc4")]), + ], + ), + ] + ) assert doc1 != 1 - assert Docstring(['a', 'b']) != Docstring(['a', 'b', 'c']) - assert Docstring(['a', 'b']) != Docstring(['a', DocSection('x', 'b')]) - assert Docstring(DocSection('a', 'b')) != Docstring(DocSection('a', ['b', 'c'])) - assert Docstring(DocSection('a', 'b')) != Docstring(DocSection('a', 'c')) - assert (Docstring(DocSection('a', DocDescription('x', 'y', 'z', 'k'))) != - Docstring(DocSection('a', DocDescription('1', 'y', 'z', 'k')))) - assert (Docstring(DocSection('a', DocDescription('x', 'y', 'z', 'k'))) != - Docstring(DocSection('a', DocDescription('x', '1', 'z', 'k')))) - assert (Docstring(DocSection('a', DocDescription('x', 'y', 'z', 'k'))) != - Docstring(DocSection('a', DocDescription('x', 'y', '1', 'k')))) - assert (Docstring(DocSection('a', DocDescription('x', 'y', 'z', 'k'))) != - Docstring(DocSection('a', DocDescription('x', 'y', 'z', '1')))) - assert (Docstring(DocSection('a', DocDescription('x', 'y', 'z', DocEquation('k')))) != - Docstring(DocSection('a', DocDescription('x', 'y', 'z', DocEquation('1'))))) + assert Docstring(["a", "b"]) != Docstring(["a", "b", "c"]) + assert Docstring(["a", "b"]) != Docstring(["a", DocSection("x", "b")]) + assert Docstring(DocSection("a", "b")) != Docstring(DocSection("a", ["b", "c"])) + assert Docstring(DocSection("a", "b")) != Docstring(DocSection("a", "c")) + assert Docstring(DocSection("a", DocDescription("x", "y", "z", "k"))) != Docstring( + DocSection("a", DocDescription("1", "y", "z", "k")) + ) + assert Docstring(DocSection("a", DocDescription("x", "y", "z", "k"))) != Docstring( + DocSection("a", DocDescription("x", "1", "z", "k")) + ) + assert Docstring(DocSection("a", DocDescription("x", "y", "z", "k"))) != Docstring( + DocSection("a", DocDescription("x", "y", "1", "k")) + ) + assert Docstring(DocSection("a", DocDescription("x", "y", "z", "k"))) != Docstring( + DocSection("a", DocDescription("x", "y", "z", "1")) + ) + assert Docstring(DocSection("a", DocDescription("x", "y", "z", DocEquation("k")))) != Docstring( + DocSection("a", DocDescription("x", "y", "z", DocEquation("1"))) + ) assert doc1.__dict__ == doc2.__dict__ @@ -44,121 +63,184 @@ def test_parse_numpy(): # monkeypatch equality for Docstring Docstring.__eq__ = lambda self, other: self.__dict__ == other.__dict__ # summary - docstring = 'summary' - assert parse_numpy(docstring) == Docstring(Summary('summary')) - docstring = 'summary\n' - assert parse_numpy(docstring) == Docstring(Summary('summary')) - docstring = '\nsummary\n' - assert parse_numpy(docstring) == Docstring(Summary('summary')) + docstring = "summary" + assert parse_numpy(docstring) == Docstring(Summary("summary")) + docstring = "summary\n" + assert parse_numpy(docstring) == Docstring(Summary("summary")) + docstring = "\nsummary\n" + assert parse_numpy(docstring) == Docstring(Summary("summary")) docstring = ' """summary\n """' - assert parse_numpy(docstring, contains_quotes=True) == Docstring(Summary('summary')) + assert parse_numpy(docstring, contains_quotes=True) == Docstring(Summary("summary")) # FIXME: this should raise an error - docstring = '\n\nsummary\n' - assert parse_numpy(docstring), Docstring([Summary('summary')]) + docstring = "\n\nsummary\n" + assert parse_numpy(docstring), Docstring([Summary("summary")]) docstring = '"""\n\nsummary\n"""' with pytest.raises(ValueError): parse_numpy(docstring, contains_quotes=True) docstring = ' """\n\n summary\n """' with pytest.raises(ValueError): parse_numpy(docstring, contains_quotes=True) - docstring = 'summary\na' + docstring = "summary\na" with pytest.raises(ValueError): parse_numpy(docstring) # extended - docstring = 'summary\n\nblock1\n\nblock2' - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), ExtendedSummary(['block1', 'block2'])])) - docstring = '\nsummary\n\nblock1\n\nblock2' - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), ExtendedSummary(['block1', 'block2'])])) - docstring = '\nsummary\n\n\n\nblock2\n\n' - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), ExtendedSummary(['block2'])])) + docstring = "summary\n\nblock1\n\nblock2" + assert parse_numpy(docstring) == Docstring( + [Summary("summary"), ExtendedSummary(["block1", "block2"])] + ) + docstring = "\nsummary\n\nblock1\n\nblock2" + assert parse_numpy(docstring) == Docstring( + [Summary("summary"), ExtendedSummary(["block1", "block2"])] + ) + docstring = "\nsummary\n\n\n\nblock2\n\n" + assert parse_numpy(docstring) == Docstring([Summary("summary"), ExtendedSummary(["block2"])]) # FIXME: is this a bug? - docstring = '\n\nsummary\n\nblock1\n\nblock2' - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), ExtendedSummary(['block1', 'block2'])])) + docstring = "\n\nsummary\n\nblock1\n\nblock2" + assert parse_numpy(docstring) == Docstring( + [Summary("summary"), ExtendedSummary(["block1", "block2"])] + ) # extended + headers - docstring = 'summary\n\nblock1\n\nblock2\n\nheader\n------\nstuff' - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), ExtendedSummary(['block1', 'block2']), - DocSection('header', 'stuff')])) + docstring = "summary\n\nblock1\n\nblock2\n\nheader\n------\nstuff" + assert parse_numpy(docstring) == Docstring( + [Summary("summary"), ExtendedSummary(["block1", "block2"]), DocSection("header", "stuff")] + ) # header with bad divider (-----) - docstring = 'summary\n\nblock1\n\nblock2\n\nheader1\n--\nstuff\n\n' + docstring = "summary\n\nblock1\n\nblock2\n\nheader1\n--\nstuff\n\n" with pytest.raises(ValueError): parse_numpy(docstring) - for header in ['parameters', 'attributes', 'methods', 'returns', 'yields', 'raises', - 'other parameters', 'see also']: + for header in [ + "parameters", + "attributes", + "methods", + "returns", + "yields", + "raises", + "other parameters", + "see also", + ]: # name + multiple descriptions - docstring = ('summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc\n description1.\n' - ' description2.'.format(header.title(), '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(['block1', 'block2']), - DocSection(header, - DocDescription('abc', - descs=['description1.', 'description2.']))])) + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + ExtendedSummary(["block1", "block2"]), + DocSection(header, DocDescription("abc", descs=["description1.", "description2."])), + ] + ) # name + types + multiple descriptions - docstring = ('summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : str\n description1.\n' - ' description2.'.format(header.title(), '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(['block1', 'block2']), - DocSection(header, - DocDescription('abc', types='str', - descs=['description1.', 'description2.']))])) - docstring = ('summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : {{str, int}}\n' - ' description1.\n' - ' description2.'.format(header.title(), '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(['block1', 'block2']), - DocSection(header, - DocDescription('abc', types=['str', 'int'], - descs=['description1.', 'description2.']))])) + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + ExtendedSummary(["block1", "block2"]), + DocSection( + header, + DocDescription("abc", types="str", descs=["description1.", "description2."]), + ), + ] + ) + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : {{str, int}}\n" + " description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + ExtendedSummary(["block1", "block2"]), + DocSection( + header, + DocDescription( + "abc", types=["str", "int"], descs=["description1.", "description2."] + ), + ), + ] + ) # name + signature + multiple descriptions - docstring = ('summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc(x, y)\n description1.\n' - ' description2.'.format(header.title(), '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(['block1', 'block2']), - DocSection(header, - DocDescription('abc', signature='(x, y)', - descs=['description1.', 'description2.']))])) + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc(x, y)\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + ExtendedSummary(["block1", "block2"]), + DocSection( + header, + DocDescription( + "abc", signature="(x, y)", descs=["description1.", "description2."] + ), + ), + ] + ) # name + types + signature + multiple descriptions - docstring = ('summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc(x, y): str\n description1.\n' - ' description2.'.format(header.title(), '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(['block1', 'block2']), - DocSection(header, - DocDescription('abc', types='str', signature='(x, y)', - descs=['description1.', 'description2.']))])) + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc(x, y): str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + ExtendedSummary(["block1", "block2"]), + DocSection( + header, + DocDescription( + "abc", + types="str", + signature="(x, y)", + descs=["description1.", "description2."], + ), + ), + ] + ) # name + types + signature + multiple descriptions - extended summary - docstring = ('summary\n\n{0}\n{1}\nabc(x, y): str\n description1.\n' - ' description2.'.format(header.title(), '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - DocSection(header, - DocDescription('abc', types='str', signature='(x, y)', - descs=['description1.', 'description2.']))])) + docstring = ( + "summary\n\n{0}\n{1}\nabc(x, y): str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + DocSection( + header, + DocDescription( + "abc", + types="str", + signature="(x, y)", + descs=["description1.", "description2."], + ), + ), + ] + ) # name + types - docstring = ('summary\n\n{0}\n{1}\nabc: str\ndef: int'.format(header.title(), - '-'*len(header))) - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), DocSection(header, - [DocDescription('abc', types='str'), - DocDescription('def', types='int')])])) + docstring = "summary\n\n{0}\n{1}\nabc: str\ndef: int".format( + header.title(), "-" * len(header) + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + DocSection( + header, [DocDescription("abc", types="str"), DocDescription("def", types="int")] + ), + ] + ) def test_parse_numpy_raw(): """Test pydocstring.numpy_docstring.parse_numpy with raw strings.""" docstring = '"""summary\n\nextended"""' - assert (parse_numpy(docstring, contains_quotes=True) == - Docstring([Summary('summary'), ExtendedSummary('extended')])) + assert parse_numpy(docstring, contains_quotes=True) == Docstring( + [Summary("summary"), ExtendedSummary("extended")] + ) docstring = 'r"""summary\n\nextended"""' with pytest.raises(NotImplementedError): parse_numpy(docstring, contains_quotes=True) @@ -168,54 +250,61 @@ def test_parse_numpy_self(): """Test pydocstring.numpy_docstring.parse_numpy using itself as an example.""" docstring = parse_numpy.__doc__ # summary - assert (parse_numpy(docstring, contains_quotes=False).sections[0].contents[0] == - 'Parse a docstring in numpy format into a Docstring instance.') + assert ( + parse_numpy(docstring, contains_quotes=False).sections[0].contents[0] + == "Parse a docstring in numpy format into a Docstring instance." + ) # extended - assert (parse_numpy(docstring, contains_quotes=False).sections[1].contents == - ['Multiple descriptions of the indented information (e.g. parameters, ' - 'attributes, methods, returns, yields, raises, see also) are ' - 'distinguished from one another with a period. If the period is not ' - 'present, then the description is assumed to be a multiline ' - 'description.']) + assert parse_numpy(docstring, contains_quotes=False).sections[1].contents == [ + "Multiple descriptions of the indented information (e.g. parameters, " + "attributes, methods, returns, yields, raises, see also) are " + "distinguished from one another with a period. If the period is not " + "present, then the description is assumed to be a multiline " + "description." + ] # parameters - assert (parse_numpy(docstring, contains_quotes=False).sections[2].header == - 'parameters') - assert (parse_numpy(docstring, contains_quotes=False).sections[2].contents[0].name == - 'docstring') - assert (parse_numpy(docstring, contains_quotes=False).sections[2].contents[0].types == - ['str']) - assert (parse_numpy(docstring, contains_quotes=False).sections[2].contents[0].descs == - ['Numpy docstring.']) - assert (parse_numpy(docstring, contains_quotes=False).sections[2].contents[1].name == - 'contains_quotes') - assert (parse_numpy(docstring, contains_quotes=False).sections[2].contents[1].types == - ['bool']) - assert (parse_numpy(docstring, contains_quotes=False).sections[2].contents[1].descs == - [r'True if docstring contains \"\"\" or \'\'\'.']) + assert parse_numpy(docstring, contains_quotes=False).sections[2].header == "parameters" + assert parse_numpy(docstring, contains_quotes=False).sections[2].contents[0].name == "docstring" + assert parse_numpy(docstring, contains_quotes=False).sections[2].contents[0].types == ["str"] + assert parse_numpy(docstring, contains_quotes=False).sections[2].contents[0].descs == [ + "Numpy docstring." + ] + assert ( + parse_numpy(docstring, contains_quotes=False).sections[2].contents[1].name + == "contains_quotes" + ) + assert parse_numpy(docstring, contains_quotes=False).sections[2].contents[1].types == ["bool"] + assert parse_numpy(docstring, contains_quotes=False).sections[2].contents[1].descs == [ + r"True if docstring contains \"\"\" or \'\'\'." + ] # returns - assert (parse_numpy(docstring, contains_quotes=False).sections[3].header == - 'returns') - assert (parse_numpy(docstring, contains_quotes=False).sections[3].contents[0].name == - 'docstring') - assert (parse_numpy(docstring, contains_quotes=False).sections[3].contents[0].types == - ['Docstring']) - assert (parse_numpy(docstring, contains_quotes=False).sections[3].contents[0].descs == - ['Instance of Docstring that contains the necessary information.']) + assert parse_numpy(docstring, contains_quotes=False).sections[3].header == "returns" + assert parse_numpy(docstring, contains_quotes=False).sections[3].contents[0].name == "docstring" + assert parse_numpy(docstring, contains_quotes=False).sections[3].contents[0].types == [ + "Docstring" + ] + assert parse_numpy(docstring, contains_quotes=False).sections[3].contents[0].descs == [ + "Instance of Docstring that contains the necessary information." + ] # raises - assert (parse_numpy(docstring, contains_quotes=False).sections[4].header == - 'raises') - assert (parse_numpy(docstring, contains_quotes=False).sections[4].contents[0].name == - 'ValueError') - assert (parse_numpy(docstring, contains_quotes=False).sections[4].contents[0].descs == - ['If summary is not in the first or second line.', - 'If summary is now followed with a blank line.', - 'If number of \'-\' does not match the number of characters in the header.', - 'If given entry of the tabbed information (parameters, attributes, methods, returns, ' - 'yields, raises, see also) had an unexpected pattern.']) - assert (parse_numpy(docstring, contains_quotes=False).sections[4].contents[1].name == - 'NotImplementedError') - assert (parse_numpy(docstring, contains_quotes=False).sections[4].contents[1].descs == - [r'If quotes corresponds to a raw string, i.e. r\"\"\".']) + assert parse_numpy(docstring, contains_quotes=False).sections[4].header == "raises" + assert ( + parse_numpy(docstring, contains_quotes=False).sections[4].contents[0].name == "ValueError" + ) + assert parse_numpy(docstring, contains_quotes=False).sections[4].contents[0].descs == [ + "If summary is not in the first or second line.", + "If summary is now followed with a blank line.", + "If number of '-' does not match the number of characters in the header.", + "If given entry of the tabbed information (parameters, attributes, methods, returns, " + "yields, raises, see also) had an unexpected pattern.", + ] + assert ( + parse_numpy(docstring, contains_quotes=False).sections[4].contents[1].name + == "NotImplementedError" + ) + assert parse_numpy(docstring, contains_quotes=False).sections[4].contents[1].descs == [ + r"If quotes corresponds to a raw string, i.e. r\"\"\"." + ] def test_parse_numpy_equations(): @@ -223,61 +312,95 @@ def test_parse_numpy_equations(): # monkeypatch equality for Docstring Docstring.__eq__ = lambda self, other: self.__dict__ == other.__dict__ # equation in extended - docstring = ('summary\n\n.. math::\n\n \\frac{1}{2}') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), ExtendedSummary(DocEquation('\\frac{1}{2}'))])) - docstring = ('summary\n\n' - '.. math::\n\n' - ' x &= 2\\\\\n' - ' &= y\\\\\n') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(DocEquation('x &= 2\\\\\n&= y\\\\'))])) - docstring = ('summary\n\n' - '.. math::\n\n' - ' x &= 2\\\\\n' - ' &= y\\\\\n' - 'Parameters\n' - '----------\n' - 'a') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - ExtendedSummary(DocEquation('x &= 2\\\\\n&= y\\\\')), - Parameters(DocDescription('a'))])) + docstring = "summary\n\n.. math::\n\n \\frac{1}{2}" + assert parse_numpy(docstring) == Docstring( + [Summary("summary"), ExtendedSummary(DocEquation("\\frac{1}{2}"))] + ) + docstring = "summary\n\n" ".. math::\n\n" " x &= 2\\\\\n" " &= y\\\\\n" + assert parse_numpy(docstring) == Docstring( + [Summary("summary"), ExtendedSummary(DocEquation("x &= 2\\\\\n&= y\\\\"))] + ) + docstring = ( + "summary\n\n" + ".. math::\n\n" + " x &= 2\\\\\n" + " &= y\\\\\n" + "Parameters\n" + "----------\n" + "a" + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + ExtendedSummary(DocEquation("x &= 2\\\\\n&= y\\\\")), + Parameters(DocDescription("a")), + ] + ) # equation in parameter # single line equation - docstring = ('summary\n\nParameters\n----------\na : float\n .. math::\n\n ' - ' \\frac{1}{2}') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - Parameters(DocDescription('a', types='float', - descs=DocEquation('\\frac{1}{2}')))])) + docstring = ( + "summary\n\nParameters\n----------\na : float\n .. math::\n\n " " \\frac{1}{2}" + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + Parameters(DocDescription("a", types="float", descs=DocEquation("\\frac{1}{2}"))), + ] + ) # multi line equation - docstring = ('summary\n\nParameters\n----------\na : float\n .. math::\n\n' - ' \\frac{1}{2}\\\\\n \\frac{1}{3}') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - Parameters(DocDescription('a', types='float', - descs=DocEquation('\\frac{1}{2}\\\\\n' - '\\frac{1}{3}\n')))])) + docstring = ( + "summary\n\nParameters\n----------\na : float\n .. math::\n\n" + " \\frac{1}{2}\\\\\n \\frac{1}{3}" + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + Parameters( + DocDescription( + "a", types="float", descs=DocEquation("\\frac{1}{2}\\\\\n" "\\frac{1}{3}\n") + ) + ), + ] + ) # multiple equations - docstring = ('summary\n\nParameters\n----------\na : float\n .. math::\n\n' - ' \\frac{1}{2}\n ..math::\n \\frac{1}{3}') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - Parameters(DocDescription('a', types='float', - descs=[DocEquation('\\frac{1}{2}'), - DocEquation('\\frac{1}{3}')]))])) + docstring = ( + "summary\n\nParameters\n----------\na : float\n .. math::\n\n" + " \\frac{1}{2}\n ..math::\n \\frac{1}{3}" + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + Parameters( + DocDescription( + "a", + types="float", + descs=[DocEquation("\\frac{1}{2}"), DocEquation("\\frac{1}{3}")], + ) + ), + ] + ) # multiple equations and other descriptions - docstring = ('summary\n\nParameters\n----------\na : float\n Some float.\n .. math::\n\n' - ' \\frac{1}{2}\n\n Yes.\n ..math::\n \\frac{1}{3}\n' - ' This is the float.') - assert (parse_numpy(docstring) == - Docstring([Summary('summary'), - Parameters(DocDescription('a', types='float', - descs=['Some float.', - DocEquation('\\frac{1}{2}'), - 'Yes.', - DocEquation('\\frac{1}{3}'), - 'This is the float.']))])) + docstring = ( + "summary\n\nParameters\n----------\na : float\n Some float.\n .. math::\n\n" + " \\frac{1}{2}\n\n Yes.\n ..math::\n \\frac{1}{3}\n" + " This is the float." + ) + assert parse_numpy(docstring) == Docstring( + [ + Summary("summary"), + Parameters( + DocDescription( + "a", + types="float", + descs=[ + "Some float.", + DocEquation("\\frac{1}{2}"), + "Yes.", + DocEquation("\\frac{1}{3}"), + "This is the float.", + ], + ) + ), + ] + ) diff --git a/docinstance/test/test_docstring.py b/docinstance/test/test_docstring.py index 1d95c1d..839aa52 100644 --- a/docinstance/test/test_docstring.py +++ b/docinstance/test/test_docstring.py @@ -10,51 +10,51 @@ def test_init(): with pytest.raises(TypeError): Docstring(1) with pytest.raises(TypeError): - Docstring({'1'}) + Docstring({"1"}) with pytest.raises(TypeError): Docstring([1]) with pytest.raises(TypeError): - Docstring(['1', 1]) + Docstring(["1", 1]) with pytest.raises(ValueError): Docstring([]) with pytest.raises(ValueError): - Docstring('1', 'nothing') + Docstring("1", "nothing") with pytest.raises(ValueError): - Docstring('1', None) + Docstring("1", None) - test = Docstring('some text') + test = Docstring("some text") assert isinstance(test.sections, list) assert len(test.sections) == 1 assert isinstance(test.sections[0], DocSection) - assert test.sections[0].header == '' - assert test.sections[0].contents == ['some text'] + assert test.sections[0].header == "" + assert test.sections[0].contents == ["some text"] - test = Docstring(DocSection('some header', 'Hello World')) + test = Docstring(DocSection("some header", "Hello World")) assert isinstance(test.sections, list) assert len(test.sections) == 1 assert isinstance(test.sections[0], DocSection) - assert test.sections[0].header == 'some header' - assert test.sections[0].contents == ['Hello World'] + assert test.sections[0].header == "some header" + assert test.sections[0].contents == ["Hello World"] - test = Docstring([DocSection('some header', 'Hello World'), 'some text']) + test = Docstring([DocSection("some header", "Hello World"), "some text"]) assert isinstance(test.sections, list) assert len(test.sections) == 2 assert isinstance(test.sections[0], DocSection) - assert test.sections[0].header == 'some header' - assert test.sections[0].contents == ['Hello World'] + assert test.sections[0].header == "some header" + assert test.sections[0].contents == ["Hello World"] assert isinstance(test.sections[1], DocSection) - assert test.sections[1].header == '' - assert test.sections[1].contents == ['some text'] + assert test.sections[1].header == "" + assert test.sections[1].contents == ["some text"] - test = Docstring('some text', 'numpy') - assert test.default_style == 'numpy' - test = Docstring('some text', 'rst') - assert test.default_style == 'rst' + test = Docstring("some text", "numpy") + assert test.default_style == "numpy" + test = Docstring("some text", "rst") + assert test.default_style == "rst" def test_make_docstring(): """Test Docstring.make_docstring.""" - test = Docstring(['summary', 'extended summary', DocSection('parameters', '')]) + test = Docstring(["summary", "extended summary", DocSection("parameters", "")]) # standard input check with pytest.raises(TypeError): test.make_docstring(width=100.0) @@ -73,81 +73,118 @@ def test_make_docstring(): with pytest.raises(ValueError): test.make_docstring(tabsize=0) with pytest.raises(ValueError): - test.make_docstring(style='random style') + test.make_docstring(style="random style") # bad ordering - test = Docstring(['summary', DocSection('parameters', ''), 'extended summary']) + test = Docstring(["summary", DocSection("parameters", ""), "extended summary"]) with pytest.raises(ValueError): - test.make_docstring(style='numpy') + test.make_docstring(style="numpy") # check summary errors - test = Docstring([DocSection('parameters', DocDescription('something'))]) + test = Docstring([DocSection("parameters", DocDescription("something"))]) with pytest.raises(ValueError): test.make_docstring() # one line summary - test = Docstring('very very long summary') - assert test.make_docstring(width=25) == 'very very long summary\n\n' - assert test.make_docstring(width=24) == '\nvery very long summary\n\n' + test = Docstring("very very long summary") + assert test.make_docstring(width=25) == "very very long summary\n\n" + assert test.make_docstring(width=24) == "\nvery very long summary\n\n" # multiple line summary - test = Docstring(['very very long summary', 'extended summary']) - assert test.make_docstring(width=25) == 'very very long summary\n\nextended summary\n\n' - assert test.make_docstring(width=24) == '\nvery very long summary\n\nextended summary\n\n' - test = Docstring(DocSection('', ['very very long summary', 'extended summary'])) - assert test.make_docstring(width=25) == 'very very long summary\n\nextended summary\n\n' - assert test.make_docstring(width=24) == '\nvery very long summary\n\nextended summary\n\n' + test = Docstring(["very very long summary", "extended summary"]) + assert test.make_docstring(width=25) == "very very long summary\n\nextended summary\n\n" + assert test.make_docstring(width=24) == "\nvery very long summary\n\nextended summary\n\n" + test = Docstring(DocSection("", ["very very long summary", "extended summary"])) + assert test.make_docstring(width=25) == "very very long summary\n\nextended summary\n\n" + assert test.make_docstring(width=24) == "\nvery very long summary\n\nextended summary\n\n" # other sections - test = Docstring([DocSection('', ['very very long summary', 'extended summary']), - DocSection('parameters', - DocDescription('var1', types=str, descs='Example.'))]) - assert (test.make_docstring(width=25) == - 'very very long summary\n\nextended summary\n\n' - 'Parameters\n----------\nvar1 : str\n Example.\n\n') - assert (test.make_docstring(width=24) == - '\nvery very long summary\n\nextended summary\n\n' - 'Parameters\n----------\nvar1 : str\n Example.\n\n') + test = Docstring( + [ + DocSection("", ["very very long summary", "extended summary"]), + DocSection("parameters", DocDescription("var1", types=str, descs="Example.")), + ] + ) + assert ( + test.make_docstring(width=25) == "very very long summary\n\nextended summary\n\n" + "Parameters\n----------\nvar1 : str\n Example.\n\n" + ) + assert ( + test.make_docstring(width=24) == "\nvery very long summary\n\nextended summary\n\n" + "Parameters\n----------\nvar1 : str\n Example.\n\n" + ) # numpy with signature - test = Docstring(['summary', - DocSection('methods', - DocDescription('func1', signature='(a, b)', types=str, - descs='Example.'))]) - assert (test.make_docstring(width=25, style='numpy with signature') == - 'summary\n\nMethods\n-------\nfunc1(a, b) : str\n Example.\n\n') + test = Docstring( + [ + "summary", + DocSection( + "methods", DocDescription("func1", signature="(a, b)", types=str, descs="Example.") + ), + ] + ) + assert ( + test.make_docstring(width=25, style="numpy with signature") + == "summary\n\nMethods\n-------\nfunc1(a, b) : str\n Example.\n\n" + ) # google - assert test.make_docstring(width=25, style='google') == ('summary\n\n' - 'Methods:\n' - ' func1 (:obj:`str`):\n' - ' Example.\n\n') + assert test.make_docstring(width=25, style="google") == ( + "summary\n\n" "Methods:\n" " func1 (:obj:`str`):\n" " Example.\n\n" + ) # rst - assert test.make_docstring(width=25, style='rst') == ('summary\n\n' - ':Methods:\n\n' - ':param func1: Example.\n' - ':type func1: :obj:`str`\n\n') + assert test.make_docstring(width=25, style="rst") == ( + "summary\n\n" ":Methods:\n\n" ":param func1: Example.\n" ":type func1: :obj:`str`\n\n" + ) def test_check_section_order(): """Test Docstring.check_section_order.""" - test = Docstring(['summary', 'extended', DocSection('parameters', ''), DocSection('warns', '')]) - assert test.check_section_order('numpy') is True - test = Docstring(['summary', DocSection('parameters', ''), 'extended', DocSection('warns', '')]) - assert test.check_section_order('numpy') is False - test = Docstring(['summary', 'extended', DocSection('warns', ''), DocSection('parameters', '')]) - assert test.check_section_order('numpy') is False + test = Docstring(["summary", "extended", DocSection("parameters", ""), DocSection("warns", "")]) + assert test.check_section_order("numpy") is True + test = Docstring(["summary", DocSection("parameters", ""), "extended", DocSection("warns", "")]) + assert test.check_section_order("numpy") is False + test = Docstring(["summary", "extended", DocSection("warns", ""), DocSection("parameters", "")]) + assert test.check_section_order("numpy") is False # note that the unidentified seections are not permitted for numpy style - test = Docstring(['summary', DocSection('asdfdsaf', ''), 'extended', DocSection('warns', ''), - DocSection('parameters', '')]) + test = Docstring( + [ + "summary", + DocSection("asdfdsaf", ""), + "extended", + DocSection("warns", ""), + DocSection("parameters", ""), + ] + ) with pytest.raises(ValueError): - test.check_section_order('numpy') - test = Docstring(['summary', 'extended', DocSection('warns', ''), DocSection('parameters', ''), - DocSection('asdfdsaf', '')]) + test.check_section_order("numpy") + test = Docstring( + [ + "summary", + "extended", + DocSection("warns", ""), + DocSection("parameters", ""), + DocSection("asdfdsaf", ""), + ] + ) with pytest.raises(ValueError): - test.check_section_order('numpy') + test.check_section_order("numpy") # other styles do not enforce such ordering - test = Docstring(['summary', DocSection('asdfdsaf', ''), 'extended', DocSection('warns', ''), - DocSection('parameters', '')]) - assert test.check_section_order('rst') is True - assert test.check_section_order('random') is True + test = Docstring( + [ + "summary", + DocSection("asdfdsaf", ""), + "extended", + DocSection("warns", ""), + DocSection("parameters", ""), + ] + ) + assert test.check_section_order("rst") is True + assert test.check_section_order("random") is True # note that the unidentified sections are permitted for other styles # NOTE: following does not actually check for the effect of adding unidentified section for non # numpy style because all sections are unidentified for non numpy styles - test = Docstring(['summary', 'extended', DocSection('warns', ''), DocSection('parameters', ''), - DocSection('asdfdsaf', '')]) - assert test.check_section_order('random') is True + test = Docstring( + [ + "summary", + "extended", + DocSection("warns", ""), + DocSection("parameters", ""), + DocSection("asdfdsaf", ""), + ] + ) + assert test.check_section_order("random") is True diff --git a/docinstance/test/test_utils.py b/docinstance/test/test_utils.py index cc3db89..e8eff2a 100644 --- a/docinstance/test/test_utils.py +++ b/docinstance/test/test_utils.py @@ -6,72 +6,100 @@ def test_wrap(): """Test docinstance.utils.wrap.""" # normal usage - assert (docinstance.utils.wrap('hello my name is', width=5, indent_level=0, tabsize=4) == - ['hello', 'my', 'name', 'is']) - assert (docinstance.utils.wrap('hello my name is', width=6, indent_level=1, tabsize=1) == - [' hello', ' my', ' name', ' is']) - assert (docinstance.utils.wrap('hello my name is', width=10, indent_level=2, tabsize=2) == - [' hello', ' my', ' name', ' is']) - assert (docinstance.utils.wrap('hello\n my', width=20, indent_level=0, tabsize=4) == - ['hello', ' my']) - assert (docinstance.utils.wrap('hello\n my', width=20, indent_level=1, tabsize=4) == - [' hello', ' my']) - assert (docinstance.utils.wrap('.. math:\n\n 1 + 2\n', width=20, indent_level=0, - tabsize=4) == ['.. math:', '', ' 1 + 2', '']) + assert docinstance.utils.wrap("hello my name is", width=5, indent_level=0, tabsize=4) == [ + "hello", + "my", + "name", + "is", + ] + assert docinstance.utils.wrap("hello my name is", width=6, indent_level=1, tabsize=1) == [ + " hello", + " my", + " name", + " is", + ] + assert docinstance.utils.wrap("hello my name is", width=10, indent_level=2, tabsize=2) == [ + " hello", + " my", + " name", + " is", + ] + assert docinstance.utils.wrap("hello\n my", width=20, indent_level=0, tabsize=4) == [ + "hello", + " my", + ] + assert docinstance.utils.wrap("hello\n my", width=20, indent_level=1, tabsize=4) == [ + " hello", + " my", + ] + assert docinstance.utils.wrap( + ".. math:\n\n 1 + 2\n", width=20, indent_level=0, tabsize=4 + ) == [".. math:", "", " 1 + 2", ""] # white spaces - assert (docinstance.utils.wrap(' hello', width=9, indent_level=0, tabsize=4) == - [' hello']) - assert docinstance.utils.wrap(' hello', width=8, indent_level=0, tabsize=4) == ['hello'] - assert docinstance.utils.wrap('hello ', width=8, indent_level=0, tabsize=4) == ['hello'] - assert (docinstance.utils.wrap(' hello my ', width=9, indent_level=0, tabsize=4) == - [' hello', 'my']) - assert (docinstance.utils.wrap(' hello my name is ', width=8, indent_level=0, - tabsize=4) == ['hello', 'my', 'name', 'is']) - assert (docinstance.utils.wrap('\n\n hello my name is\n\n', width=5, indent_level=0, - tabsize=4) == ['', '', 'hello', 'my', 'name', 'is', '', '']) - assert (docinstance.utils.wrap('\n\n hello\n my name is\n\n', width=5, indent_level=0, - tabsize=4) == ['', '', 'hello', ' my', 'name', 'is', '', '']) + assert docinstance.utils.wrap(" hello", width=9, indent_level=0, tabsize=4) == [" hello"] + assert docinstance.utils.wrap(" hello", width=8, indent_level=0, tabsize=4) == ["hello"] + assert docinstance.utils.wrap("hello ", width=8, indent_level=0, tabsize=4) == ["hello"] + assert docinstance.utils.wrap(" hello my ", width=9, indent_level=0, tabsize=4) == [ + " hello", + "my", + ] + assert docinstance.utils.wrap( + " hello my name is ", width=8, indent_level=0, tabsize=4 + ) == ["hello", "my", "name", "is"] + assert docinstance.utils.wrap( + "\n\n hello my name is\n\n", width=5, indent_level=0, tabsize=4 + ) == ["", "", "hello", "my", "name", "is", "", ""] + assert docinstance.utils.wrap( + "\n\n hello\n my name is\n\n", width=5, indent_level=0, tabsize=4 + ) == ["", "", "hello", " my", "name", "is", "", ""] # example - assert (docinstance.utils.wrap(' Text that will be wrapped into different lines such that each' - ' line is indented and is less than the given length.', - width=30) == [' Text that will be wrapped', - 'into different lines such that', - 'each line is indented and is', - 'less than the given length.']) + assert docinstance.utils.wrap( + " Text that will be wrapped into different lines such that each" + " line is indented and is less than the given length.", + width=30, + ) == [ + " Text that will be wrapped", + "into different lines such that", + "each line is indented and is", + "less than the given length.", + ] # too much indentation with pytest.raises(ValueError): - docinstance.utils.wrap('hello my name is', width=5, indent_level=3, tabsize=4) + docinstance.utils.wrap("hello my name is", width=5, indent_level=3, tabsize=4) # long words with pytest.raises(ValueError): - docinstance.utils.wrap('hello my name is', width=1, indent_level=0, tabsize=1) + docinstance.utils.wrap("hello my name is", width=1, indent_level=0, tabsize=1) with pytest.raises(ValueError): - docinstance.utils.wrap('hello my name is', width=5, indent_level=1, tabsize=1) + docinstance.utils.wrap("hello my name is", width=5, indent_level=1, tabsize=1) # subsequent indent - assert (docinstance.utils.wrap('a b c d e', width=4, indent_level=0, tabsize=4, - subsequent_indent='xxx') == - ['a b', 'xxxc', 'xxxd', 'xxxe']) - assert (docinstance.utils.wrap('a b c d e', width=4, indent_level=1, tabsize=1, - subsequent_indent='xx') == - [' a b', ' xxc', ' xxd', ' xxe']) + assert docinstance.utils.wrap( + "a b c d e", width=4, indent_level=0, tabsize=4, subsequent_indent="xxx" + ) == ["a b", "xxxc", "xxxd", "xxxe"] + assert docinstance.utils.wrap( + "a b c d e", width=4, indent_level=1, tabsize=1, subsequent_indent="xx" + ) == [" a b", " xxc", " xxd", " xxe"] with pytest.raises(ValueError): - docinstance.utils.wrap('a b c d e', width=4, indent_level=0, tabsize=4, - subsequent_indent='xxxx') + docinstance.utils.wrap( + "a b c d e", width=4, indent_level=0, tabsize=4, subsequent_indent="xxxx" + ) def test_wrap_indent_subsequent(): """Test docinstance.utils.wrap_indent_subsequent.""" - assert (docinstance.utils.wrap_indent_subsequent('a b c d e', width=4, indent_level=1, - tabsize=3) == - ['a b', ' c', ' d', ' e']) + assert docinstance.utils.wrap_indent_subsequent( + "a b c d e", width=4, indent_level=1, tabsize=3 + ) == ["a b", " c", " d", " e"] with pytest.raises(ValueError): - docinstance.utils.wrap_indent_subsequent('a b c d e', width=4, indent_level=1, tabsize=4) + docinstance.utils.wrap_indent_subsequent("a b c d e", width=4, indent_level=1, tabsize=4) def test_extract_members(): """Test docinstance.utils.extract_members.""" + class Test: # pragma: no cover """Test class.""" + @property def f(self): """Test property.""" @@ -83,4 +111,4 @@ def g(self): h = pytest - assert docinstance.utils.extract_members(Test) == {'f': Test.f, 'g': Test.g} + assert docinstance.utils.extract_members(Test) == {"f": Test.f, "g": Test.g} diff --git a/docinstance/test/test_wrapper.py b/docinstance/test/test_wrapper.py index 7f05fbc..750abd2 100644 --- a/docinstance/test/test_wrapper.py +++ b/docinstance/test/test_wrapper.py @@ -3,8 +3,12 @@ import os import sys import pytest -from docinstance.wrapper import (kwarg_wrapper, docstring, docstring_recursive, - docstring_current_module) +from docinstance.wrapper import ( + kwarg_wrapper, + docstring, + docstring_recursive, + docstring_current_module, +) from docinstance.docstring import Docstring from docinstance.content.section import DocSection from docinstance.content.description import DocDescription @@ -12,6 +16,7 @@ def test_kwarg_wrapper(): """Test docinstance.wrapper.kwarg_wrapper.""" + @kwarg_wrapper def test(func, x=1): """Test function.""" @@ -33,12 +38,14 @@ def f(): # pragma: no cover def f(): # pragma: no cover """Test function.""" pass + assert f.x == 1 @test(x=2) def f(): # pragma: no cover """Test function.""" pass + assert f.x == 2 @@ -58,21 +65,24 @@ def test(): # pragma: no cover pass # NOTE: the indentation can be removed with inspect.cleandoc - assert test.__doc__ == ('Test docstring.\n' - '\n' - ' Parameters\n' - ' ----------\n' - ' x : int\n' - ' Something.\n\n' - ' ') - assert not hasattr(test, '_docinstance') + assert test.__doc__ == ( + "Test docstring.\n" + "\n" + " Parameters\n" + " ----------\n" + " x : int\n" + " Something.\n\n" + " " + ) + assert not hasattr(test, "_docinstance") # _docinstance - docinstance = Docstring([ - 'Test docstring.', - DocSection('parameters', - DocDescription('x', types=int, descs='Something.')) - ]) + docinstance = Docstring( + [ + "Test docstring.", + DocSection("parameters", DocDescription("x", types=int, descs="Something.")), + ] + ) def test(): # pragma: no cover """Test function.""" @@ -85,22 +95,21 @@ def test(): # pragma: no cover test._docinstance = docinstance docstring(test) - assert test.__doc__ == ('Test docstring.\n' - '\n' - 'Parameters\n' - '----------\n' - 'x : int\n' - ' Something.\n\n') + assert test.__doc__ == ( + "Test docstring.\n" "\n" "Parameters\n" "----------\n" "x : int\n" " Something.\n\n" + ) assert test._docinstance == docinstance docstring(test, indent_level=2) - assert test.__doc__ == ('Test docstring.\n' - '\n' - ' Parameters\n' - ' ----------\n' - ' x : int\n' - ' Something.\n\n' - ' ') + assert test.__doc__ == ( + "Test docstring.\n" + "\n" + " Parameters\n" + " ----------\n" + " x : int\n" + " Something.\n\n" + " " + ) assert test._docinstance == docinstance @@ -117,6 +126,7 @@ class Test: # pragma: no cover Something. """ + def f(self): """Test function. @@ -127,62 +137,66 @@ def f(self): """ pass - assert Test.__doc__ == ('Test docstring.\n\n' - ' Attributes\n' - ' ----------\n' - ' x : int\n' - ' Something.\n\n' - ' ') - assert not hasattr(Test, '_docinstance') + assert Test.__doc__ == ( + "Test docstring.\n\n" + " Attributes\n" + " ----------\n" + " x : int\n" + " Something.\n\n" + " " + ) + assert not hasattr(Test, "_docinstance") # docinstance - docinstance1 = Docstring([ - 'Test docstring.', - DocSection('attributes', - DocDescription('x', types=int, descs='Something.')) - ]) - docinstance2 = Docstring([ - 'Test function.', - DocSection('returns', 'nothing') - ]) + docinstance1 = Docstring( + [ + "Test docstring.", + DocSection("attributes", DocDescription("x", types=int, descs="Something.")), + ] + ) + docinstance2 = Docstring(["Test function.", DocSection("returns", "nothing")]) # w/o indentation @docstring class Test: # pragma: no cover """Test class.""" + _docinstance = docinstance1 def f(self): """Some docstring.""" pass + f._docinstance = docinstance2 - assert Test.__doc__ == ('Test docstring.\n\n' - 'Attributes\n' - '----------\n' - 'x : int\n' - ' Something.\n\n') + assert Test.__doc__ == ( + "Test docstring.\n\n" "Attributes\n" "----------\n" "x : int\n" " Something.\n\n" + ) assert Test._docinstance == docinstance1 - assert Test.f.__doc__ == 'Some docstring.' + assert Test.f.__doc__ == "Some docstring." assert Test.f._docinstance == docinstance2 # w/ indentation @docstring(indent_level=1) class Test: # pragma: no cover """Test class.""" + _docinstance = docinstance1 def f(self): """Test function.""" pass + f._docinstance = docinstance2 - assert Test.__doc__ == ('Test docstring.\n\n' - ' Attributes\n' - ' ----------\n' - ' x : int\n' - ' Something.\n\n' - ' ') + assert Test.__doc__ == ( + "Test docstring.\n\n" + " Attributes\n" + " ----------\n" + " x : int\n" + " Something.\n\n" + " " + ) def test_wrapper_docstring_recursive_on_class(): @@ -198,6 +212,7 @@ class Test: # pragma: no cover Something. """ + def f(self): """Test function. @@ -208,125 +223,135 @@ def f(self): """ pass - assert Test.__doc__ == ('Test docstring.\n\n' - ' Attributes\n' - ' ----------\n' - ' x : int\n' - ' Something.\n\n' - ' ') - assert not hasattr(Test, '_docinstance') - assert Test.f.__doc__ == ('Test function.\n\n' - ' Returns\n' - ' -------\n' - ' nothing\n\n' - ' ') - assert not hasattr(Test.f, '_docinstance') + assert Test.__doc__ == ( + "Test docstring.\n\n" + " Attributes\n" + " ----------\n" + " x : int\n" + " Something.\n\n" + " " + ) + assert not hasattr(Test, "_docinstance") + assert Test.f.__doc__ == ( + "Test function.\n\n" + " Returns\n" + " -------\n" + " nothing\n\n" + " " + ) + assert not hasattr(Test.f, "_docinstance") # docinstance - docinstance1 = Docstring([ - 'Test docstring.', - DocSection('attributes', - DocDescription('x', types=int, descs='Something.')) - ]) - docinstance2 = Docstring([ - 'Test function.', - DocSection('returns', 'nothing') - ]) + docinstance1 = Docstring( + [ + "Test docstring.", + DocSection("attributes", DocDescription("x", types=int, descs="Something.")), + ] + ) + docinstance2 = Docstring(["Test function.", DocSection("returns", "nothing")]) # w/o indentation @docstring_recursive class Test: # pragma: no cover """Test class.""" + _docinstance = docinstance1 def f(self): """Test function.""" pass + f._docinstance = docinstance2 - assert Test.__doc__ == ('Test docstring.\n\n' - 'Attributes\n' - '----------\n' - 'x : int\n' - ' Something.\n\n') + assert Test.__doc__ == ( + "Test docstring.\n\n" "Attributes\n" "----------\n" "x : int\n" " Something.\n\n" + ) assert Test._docinstance == docinstance1 - assert Test.f.__doc__ == ('Test function.\n\n' - ' Returns\n' - ' -------\n' - ' nothing\n\n' - ' ') + assert Test.f.__doc__ == ( + "Test function.\n\n" " Returns\n" " -------\n" " nothing\n\n" " " + ) assert Test.f._docinstance == docinstance2 # w indentation @docstring_recursive(indent_level=2) class Test: # pragma: no cover """Test class.""" + _docinstance = docinstance1 def f(self): """Test function.""" pass + f._docinstance = docinstance2 - assert Test.__doc__ == ('Test docstring.\n\n' - ' Attributes\n' - ' ----------\n' - ' x : int\n' - ' Something.\n\n' - ' ') + assert Test.__doc__ == ( + "Test docstring.\n\n" + " Attributes\n" + " ----------\n" + " x : int\n" + " Something.\n\n" + " " + ) assert Test._docinstance == docinstance1 - assert Test.f.__doc__ == ('Test function.\n\n' - ' Returns\n' - ' -------\n' - ' nothing\n\n' - ' ') + assert Test.f.__doc__ == ( + "Test function.\n\n" + " Returns\n" + " -------\n" + " nothing\n\n" + " " + ) assert Test.f._docinstance == docinstance2 def test_docstring_current_module(): """Test docinstance.wrapper.docstring_current_module on the current module.""" global test_docstring_current_module - test_docstring_current_module._docinstance = Docstring([ - 'Test docinstance.wrapper.docstring_current_module on the current module.', - 'Did it work?' - ]) + test_docstring_current_module._docinstance = Docstring( + ["Test docinstance.wrapper.docstring_current_module on the current module.", "Did it work?"] + ) def supplementary_docstring_current_module(): """To be used to test docstring_current_module in test_docstring_current_module.""" pass - supplementary_docstring_current_module._docinstance = Docstring([ - 'Some docstring.', - DocSection('parameters', DocDescription('x', types=int, descs='Example.')) - ]) + supplementary_docstring_current_module._docinstance = Docstring( + [ + "Some docstring.", + DocSection("parameters", DocDescription("x", types=int, descs="Example.")), + ] + ) test_docstring_current_module.f = supplementary_docstring_current_module docstring_current_module() - assert (test_docstring_current_module.__doc__ == - 'Test docinstance.wrapper.docstring_current_module on the current module.\n\n' - ' Did it work?\n\n' - ' ') - - assert (test_docstring_current_module.f.__doc__ == - 'Some docstring.\n\n' - ' Parameters\n' - ' ----------\n' - ' x : int\n' - ' Example.\n\n' - ' ') + assert ( + test_docstring_current_module.__doc__ + == "Test docinstance.wrapper.docstring_current_module on the current module.\n\n" + " Did it work?\n\n" + " " + ) + + assert ( + test_docstring_current_module.f.__doc__ == "Some docstring.\n\n" + " Parameters\n" + " ----------\n" + " x : int\n" + " Example.\n\n" + " " + ) def test_docstring_modify_import(tmp_path): """Test docinstance.wrapper.docstring_modify_import on module `dummy`.""" - assert (not hasattr(importlib._bootstrap_external.SourceFileLoader.exec_module, - 'special_cases')) + assert not hasattr(importlib._bootstrap_external.SourceFileLoader.exec_module, "special_cases") # create temporary directory tmp_dir = tmp_path / "test" tmp_dir.mkdir() # create python file that calls docstring_modify_import path1 = tmp_dir / "dummy1.py" - path1.write_text("from docinstance.wrapper import docstring_modify_import\n" - "docstring_modify_import()") + path1.write_text( + "from docinstance.wrapper import docstring_modify_import\n" "docstring_modify_import()" + ) # create another python file with docstrings path2 = tmp_dir / "dummy2.py" path2.write_text( @@ -347,35 +372,42 @@ class DummyClass: 'Class for testing.', DocSection('attributes', DocDescription('x', types=str, descs='Example.')) ]) - ''') + ''' + ) # create yet another python file with docstring_modify_import path3 = tmp_dir / "dummy3.py" - path3.write_text("from docinstance.wrapper import docstring_modify_import\n" - "docstring_modify_import()") + path3.write_text( + "from docinstance.wrapper import docstring_modify_import\n" "docstring_modify_import()" + ) # check that dummy2 is decorated sys.path.append(str(tmp_dir)) import dummy1 import dummy2 as test - assert (test.__doc__ == - 'Dummy module for testing docinstance.wrapper.docstring_modify_import.') - assert (test.DummyClass.__doc__ == - 'Class for testing.\n\n' - ' Attributes\n' - ' ----------\n' - ' x : str\n' - ' Example.\n\n' - ' ') - assert (importlib._bootstrap_external.SourceFileLoader.exec_module.special_cases == - set([os.path.dirname(dummy1.__file__)])) + + assert test.__doc__ == "Dummy module for testing docinstance.wrapper.docstring_modify_import." + assert ( + test.DummyClass.__doc__ == "Class for testing.\n\n" + " Attributes\n" + " ----------\n" + " x : str\n" + " Example.\n\n" + " " + ) + assert importlib._bootstrap_external.SourceFileLoader.exec_module.special_cases == set( + [os.path.dirname(dummy1.__file__)] + ) # check that multiple function calls does not change anything import dummy3 - assert (importlib._bootstrap_external.SourceFileLoader.exec_module.special_cases == - set([os.path.dirname(dummy3.__file__)])) + + assert importlib._bootstrap_external.SourceFileLoader.exec_module.special_cases == set( + [os.path.dirname(dummy3.__file__)] + ) # import package outside current directory import docinstance.utils + # reload module because it has already been loaded via docinstance.wrapper importlib.reload(docinstance.utils) # check that the objects within docinstance.utils has not been touched by wrapper. diff --git a/docinstance/utils.py b/docinstance/utils.py index 8bee280..e355d65 100644 --- a/docinstance/utils.py +++ b/docinstance/utils.py @@ -48,30 +48,34 @@ def wrap(text, width=100, indent_level=0, tabsize=4, **kwargs): If is a word plus its indentation is longer than the width. """ - kwargs.setdefault('expand_tabs', True) - kwargs.setdefault('replace_whitespace', False) - kwargs.setdefault('drop_whitespace', True) - kwargs.setdefault('break_long_words', False) - kwargs['tabsize'] = tabsize + kwargs.setdefault("expand_tabs", True) + kwargs.setdefault("replace_whitespace", False) + kwargs.setdefault("drop_whitespace", True) + kwargs.setdefault("break_long_words", False) + kwargs["tabsize"] = tabsize if width <= tabsize * indent_level: - raise ValueError('Amount of indentation must be less than the maximum width.') - kwargs['width'] = width - tabsize * indent_level + raise ValueError("Amount of indentation must be less than the maximum width.") + kwargs["width"] = width - tabsize * indent_level # NOTE: uncomment to remove whitespace at the beginning and the end # text = text.strip() # Acknowledge all of the newlines (start, middle, and end) - lines = text.split('\n') + lines = text.split("\n") # wrap each line (separated by newline) separately - wrapped_lines = [wrapped_line - for unwrapped_line in lines - for wrapped_line in - (textwrap.wrap(unwrapped_line, **kwargs) if unwrapped_line != '' else [''])] + wrapped_lines = [ + wrapped_line + for unwrapped_line in lines + for wrapped_line in ( + textwrap.wrap(unwrapped_line, **kwargs) if unwrapped_line != "" else [""] + ) + ] # indent - output = [' ' * tabsize * indent_level + line for line in wrapped_lines] + output = [" " * tabsize * indent_level + line for line in wrapped_lines] if any(len(line) > width for line in output): - raise ValueError('There cannot be any word (after indentation) that exceeds the maximum ' - 'width') + raise ValueError( + "There cannot be any word (after indentation) that exceeds the maximum " "width" + ) return output @@ -102,8 +106,9 @@ def wrap_indent_subsequent(text, width=100, indent_level=0, tabsize=4): discarded only if the first word cannot fit into the given line width with the whitespace. """ - output = wrap(text, width=width, indent_level=0, tabsize=0, - subsequent_indent=' ' * tabsize * indent_level) + output = wrap( + text, width=width, indent_level=0, tabsize=0, subsequent_indent=" " * tabsize * indent_level + ) return output diff --git a/docinstance/wrapper.py b/docinstance/wrapper.py index e5d8e53..5a2689b 100644 --- a/docinstance/wrapper.py +++ b/docinstance/wrapper.py @@ -14,6 +14,7 @@ def kwarg_wrapper(wrapper): (with another wrapper). """ + @wraps(wrapper) def new_wrapper(obj=None, **kwargs): """Reconstruction of the provided wrapper so that keyword arguments are rewritten. @@ -67,7 +68,7 @@ def docstring(obj, width=100, indent_level=0, tabsize=4): """ # TODO: if there is a parser, parse the docstring into docinstance - if not hasattr(obj, '_docinstance'): + if not hasattr(obj, "_docinstance"): return obj # generate new docstring from docinstance # pylint: disable=W0212 @@ -127,7 +128,7 @@ def docstring_recursive(obj, width=100, indent_level=0, tabsize=4): # wrap members for member in extract_members(obj).values(): # recurse for all members of member - docstring_recursive(member, width=width, indent_level=indent_level+1, tabsize=tabsize) + docstring_recursive(member, width=width, indent_level=indent_level + 1, tabsize=tabsize) return obj @@ -153,8 +154,11 @@ def docstring_current_module(width=100, tabsize=4): """ # get the module that called this function - module = next(inspect.getmodule(i[0]) for i in inspect.stack()[1:] # pragma: no branch - if inspect.getmodule(i[0]) is not None) + module = next( + inspect.getmodule(i[0]) + for i in inspect.stack()[1:] # pragma: no branch + if inspect.getmodule(i[0]) is not None + ) # recursively convert # NOTE: docstring for the module will always not be indented docstring_recursive(module, width=width, indent_level=0, tabsize=tabsize) @@ -190,8 +194,11 @@ def docstring_modify_import(width=100, tabsize=4): """ # find the location from which this function is called - parentfile = next(inspect.getsourcefile(i[0]) for i in inspect.stack()[1:] # pragma: no branch - if inspect.getmodule(i[0]) is not None) + parentfile = next( + inspect.getsourcefile(i[0]) + for i in inspect.stack()[1:] # pragma: no branch + if inspect.getmodule(i[0]) is not None + ) parentdir = os.path.dirname(parentfile) def pandora_box(old_import): @@ -211,7 +218,7 @@ def pandora_box(old_import): recursively scanning the __dict__. """ - if not hasattr(old_import, 'special_cases'): + if not hasattr(old_import, "special_cases"): old_import.special_cases = set() old_import.special_cases.add(parentdir) @@ -225,8 +232,10 @@ def new_import(*args, **kwargs): # decorator will be applied only to `exc_module`. module = args[1] sourcefile = inspect.getsourcefile(module) - if any(os.path.commonpath([sourcefile, parent]) == parent - for parent in old_import.special_cases): + if any( + os.path.commonpath([sourcefile, parent]) == parent + for parent in old_import.special_cases + ): docstring_recursive(module, width=width, indent_level=0, tabsize=tabsize) new_import.special_cases = old_import.special_cases From 71be25d7b90fb09bb2ae55cecede38955ef76a89 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 16:08:47 +0200 Subject: [PATCH 02/15] Move tests to directory in project home --- {docinstance/content/test => tests}/test_base.py | 0 {docinstance/content/test => tests}/test_description.py | 0 {docinstance/test => tests}/test_docstring.py | 0 {docinstance/content/test => tests}/test_equation.py | 0 {docinstance/parser/test => tests}/test_latex.py | 0 {docinstance/parser/test => tests}/test_numpy.py | 0 {docinstance/content/test => tests}/test_section.py | 0 {docinstance/test => tests}/test_utils.py | 0 {docinstance/test => tests}/test_wrapper.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {docinstance/content/test => tests}/test_base.py (100%) rename {docinstance/content/test => tests}/test_description.py (100%) rename {docinstance/test => tests}/test_docstring.py (100%) rename {docinstance/content/test => tests}/test_equation.py (100%) rename {docinstance/parser/test => tests}/test_latex.py (100%) rename {docinstance/parser/test => tests}/test_numpy.py (100%) rename {docinstance/content/test => tests}/test_section.py (100%) rename {docinstance/test => tests}/test_utils.py (100%) rename {docinstance/test => tests}/test_wrapper.py (100%) diff --git a/docinstance/content/test/test_base.py b/tests/test_base.py similarity index 100% rename from docinstance/content/test/test_base.py rename to tests/test_base.py diff --git a/docinstance/content/test/test_description.py b/tests/test_description.py similarity index 100% rename from docinstance/content/test/test_description.py rename to tests/test_description.py diff --git a/docinstance/test/test_docstring.py b/tests/test_docstring.py similarity index 100% rename from docinstance/test/test_docstring.py rename to tests/test_docstring.py diff --git a/docinstance/content/test/test_equation.py b/tests/test_equation.py similarity index 100% rename from docinstance/content/test/test_equation.py rename to tests/test_equation.py diff --git a/docinstance/parser/test/test_latex.py b/tests/test_latex.py similarity index 100% rename from docinstance/parser/test/test_latex.py rename to tests/test_latex.py diff --git a/docinstance/parser/test/test_numpy.py b/tests/test_numpy.py similarity index 100% rename from docinstance/parser/test/test_numpy.py rename to tests/test_numpy.py diff --git a/docinstance/content/test/test_section.py b/tests/test_section.py similarity index 100% rename from docinstance/content/test/test_section.py rename to tests/test_section.py diff --git a/docinstance/test/test_utils.py b/tests/test_utils.py similarity index 100% rename from docinstance/test/test_utils.py rename to tests/test_utils.py diff --git a/docinstance/test/test_wrapper.py b/tests/test_wrapper.py similarity index 100% rename from docinstance/test/test_wrapper.py rename to tests/test_wrapper.py From 1c336fce50165ad599064ba8e37031ce165f183f Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 16:32:10 +0200 Subject: [PATCH 03/15] Move python package to src directory This should make the protect against testing against the current directory rather than the installed package --- setup.py | 3 ++- {docinstance => src/docinstance}/__init__.py | 0 {docinstance => src/docinstance}/content/__init__.py | 0 {docinstance => src/docinstance}/content/base.py | 0 {docinstance => src/docinstance}/content/description.py | 0 {docinstance => src/docinstance}/content/equation.py | 0 {docinstance => src/docinstance}/content/section.py | 0 {docinstance => src/docinstance}/docstring.py | 0 {docinstance => src/docinstance}/parser/__init__.py | 0 {docinstance => src/docinstance}/parser/latex.py | 0 {docinstance => src/docinstance}/parser/numpy.py | 0 {docinstance => src/docinstance}/utils.py | 0 {docinstance => src/docinstance}/wrapper.py | 0 13 files changed, 2 insertions(+), 1 deletion(-) rename {docinstance => src/docinstance}/__init__.py (100%) rename {docinstance => src/docinstance}/content/__init__.py (100%) rename {docinstance => src/docinstance}/content/base.py (100%) rename {docinstance => src/docinstance}/content/description.py (100%) rename {docinstance => src/docinstance}/content/equation.py (100%) rename {docinstance => src/docinstance}/content/section.py (100%) rename {docinstance => src/docinstance}/docstring.py (100%) rename {docinstance => src/docinstance}/parser/__init__.py (100%) rename {docinstance => src/docinstance}/parser/latex.py (100%) rename {docinstance => src/docinstance}/parser/numpy.py (100%) rename {docinstance => src/docinstance}/utils.py (100%) rename {docinstance => src/docinstance}/wrapper.py (100%) diff --git a/setup.py b/setup.py index 5cc9191..2531bfd 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,8 @@ long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/kimt33/docinstance", - packages=setuptools.find_packages(), + packages=setuptools.find_packages(where="src"), + package_dir={"": "src"}, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", diff --git a/docinstance/__init__.py b/src/docinstance/__init__.py similarity index 100% rename from docinstance/__init__.py rename to src/docinstance/__init__.py diff --git a/docinstance/content/__init__.py b/src/docinstance/content/__init__.py similarity index 100% rename from docinstance/content/__init__.py rename to src/docinstance/content/__init__.py diff --git a/docinstance/content/base.py b/src/docinstance/content/base.py similarity index 100% rename from docinstance/content/base.py rename to src/docinstance/content/base.py diff --git a/docinstance/content/description.py b/src/docinstance/content/description.py similarity index 100% rename from docinstance/content/description.py rename to src/docinstance/content/description.py diff --git a/docinstance/content/equation.py b/src/docinstance/content/equation.py similarity index 100% rename from docinstance/content/equation.py rename to src/docinstance/content/equation.py diff --git a/docinstance/content/section.py b/src/docinstance/content/section.py similarity index 100% rename from docinstance/content/section.py rename to src/docinstance/content/section.py diff --git a/docinstance/docstring.py b/src/docinstance/docstring.py similarity index 100% rename from docinstance/docstring.py rename to src/docinstance/docstring.py diff --git a/docinstance/parser/__init__.py b/src/docinstance/parser/__init__.py similarity index 100% rename from docinstance/parser/__init__.py rename to src/docinstance/parser/__init__.py diff --git a/docinstance/parser/latex.py b/src/docinstance/parser/latex.py similarity index 100% rename from docinstance/parser/latex.py rename to src/docinstance/parser/latex.py diff --git a/docinstance/parser/numpy.py b/src/docinstance/parser/numpy.py similarity index 100% rename from docinstance/parser/numpy.py rename to src/docinstance/parser/numpy.py diff --git a/docinstance/utils.py b/src/docinstance/utils.py similarity index 100% rename from docinstance/utils.py rename to src/docinstance/utils.py diff --git a/docinstance/wrapper.py b/src/docinstance/wrapper.py similarity index 100% rename from docinstance/wrapper.py rename to src/docinstance/wrapper.py From 628e33119502114098c31a353add9307d9f702a8 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 17:45:45 +0200 Subject: [PATCH 04/15] Update tox 1. Add environment specifically for linters (black, pydocstring, import-order, pep8 naming, bandit, flake8, pylint) 2. Add config for linters 3. Fix import orders 4. Fix missing docstring 5. Fix elif statements with raise statement --- pylintrc | 28 ----- src/docinstance/content/description.py | 2 +- src/docinstance/content/equation.py | 2 +- src/docinstance/content/section.py | 4 +- src/docinstance/docstring.py | 10 +- src/docinstance/parser/latex.py | 3 +- src/docinstance/parser/numpy.py | 34 +++--- src/docinstance/utils.py | 2 +- src/docinstance/wrapper.py | 5 +- tests/test_base.py | 3 +- tests/test_description.py | 2 +- tests/test_docstring.py | 6 +- tests/test_equation.py | 2 +- tests/test_latex.py | 2 +- tests/test_numpy.py | 6 +- tests/test_section.py | 24 ++-- tests/test_utils.py | 2 +- tests/test_wrapper.py | 17 +-- tox.ini | 149 ++++++++++++++++++++++--- 19 files changed, 201 insertions(+), 102 deletions(-) delete mode 100644 pylintrc diff --git a/pylintrc b/pylintrc deleted file mode 100644 index dd80cd9..0000000 --- a/pylintrc +++ /dev/null @@ -1,28 +0,0 @@ -[MESSAGES CONTROL] -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=W0223,W0231,E0611 - -# abstract-method (W0223): Used when an abstract method (i.e. raise NotImplementedError) is not overridden in concrete class. -# super-init-not-called (W0231): Used when an ancestor class method has an __init__ method which is not called by a derived class. -# no-name-in-module (E0611): Used when a name cannot be found in a module. - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=XXX - - -[DESIGN] - -# Maximum number of argunmnts for function/method -max-args = 6 \ No newline at end of file diff --git a/src/docinstance/content/description.py b/src/docinstance/content/description.py index 2744270..a21fa6d 100644 --- a/src/docinstance/content/description.py +++ b/src/docinstance/content/description.py @@ -1,7 +1,7 @@ """Class for representing a description of objects/errors in the docstring.""" -from docinstance.utils import wrap, wrap_indent_subsequent from docinstance.content.base import DocContent from docinstance.content.equation import DocEquation +from docinstance.utils import wrap, wrap_indent_subsequent class DocDescription(DocContent): diff --git a/src/docinstance/content/equation.py b/src/docinstance/content/equation.py index 5a0e3a6..9500b46 100644 --- a/src/docinstance/content/equation.py +++ b/src/docinstance/content/equation.py @@ -1,6 +1,6 @@ """Class for representing math equations.""" -from docinstance.utils import wrap from docinstance.content.base import DocContent +from docinstance.utils import wrap class DocEquation(DocContent): diff --git a/src/docinstance/content/section.py b/src/docinstance/content/section.py index 7a44571..22e582d 100644 --- a/src/docinstance/content/section.py +++ b/src/docinstance/content/section.py @@ -1,7 +1,7 @@ """Class for representing a section in the docstring.""" -from docinstance.utils import wrap, wrap_indent_subsequent from docinstance.content.base import DocContent from docinstance.content.description import DocDescription +from docinstance.utils import wrap, wrap_indent_subsequent class DocSection(DocContent): @@ -446,7 +446,7 @@ def make_init(header): """ - def __init__(self, contents): + def __init__(self, contents): # noqa: N807 """Initialize. Parameters diff --git a/src/docinstance/docstring.py b/src/docinstance/docstring.py index 96c7ba1..bf2f9fc 100644 --- a/src/docinstance/docstring.py +++ b/src/docinstance/docstring.py @@ -44,7 +44,7 @@ def __init__(self, sections, default_style="numpy"): """ if isinstance(sections, (str, DocContent)): sections = [sections] - elif not ( + if not ( isinstance(sections, (list, tuple)) and all(isinstance(i, (str, DocContent)) for i in sections) ): @@ -53,7 +53,7 @@ def __init__(self, sections, default_style="numpy"): "strings, or list/tuple of DocContent instances." ) # NOTE: should the empty sections be allowed? - elif not sections: + if not sections: raise ValueError("At least one section must be provided.") self.sections = [ section if isinstance(section, DocSection) else DocSection("", section) @@ -113,17 +113,17 @@ def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): # check input if not isinstance(width, int): raise TypeError("Maximum width of the line must be given as an integer.") - elif width <= 0: + if width <= 0: raise ValueError("Maximum width of the line must be greater than zero.") if not isinstance(indent_level, int): raise TypeError("Level of indentation must be given as an integer.") - elif indent_level < 0: + if indent_level < 0: raise ValueError("Level of indentation must be greater than or equal to zero.") if not isinstance(tabsize, int): raise TypeError("Number of spaces in a tab must be given as an integer.") - elif tabsize <= 0: + if tabsize <= 0: raise ValueError("Number of spaces in a tab must be greater than zero.") if style == "numpy": diff --git a/src/docinstance/parser/latex.py b/src/docinstance/parser/latex.py index 3569974..4857c27 100644 --- a/src/docinstance/parser/latex.py +++ b/src/docinstance/parser/latex.py @@ -1,6 +1,7 @@ """Parser for Latex equations.""" -import re import inspect +import re + from docinstance.content.equation import DocEquation diff --git a/src/docinstance/parser/numpy.py b/src/docinstance/parser/numpy.py index 786be04..9b86782 100644 --- a/src/docinstance/parser/numpy.py +++ b/src/docinstance/parser/numpy.py @@ -1,32 +1,32 @@ """Parser for numpy docstring.""" -import re import inspect -from docinstance.parser.latex import parse_equation -from docinstance.docstring import Docstring +import re + from docinstance.content.description import DocDescription -from docinstance.content.section import ( +from docinstance.content.equation import DocEquation +from docinstance.content.section import ( # pylint: disable=E0611 + Attributes, DocSection, - Summary, + Examples, ExtendedSummary, - Parameters, - Attributes, Methods, - Returns, - Yields, + Notes, OtherParameters, + Parameters, Raises, - Warns, - Warnings, - SeeAlso, - Notes, References, - Examples, + Returns, + SeeAlso, + Summary, + Warnings, + Warns, + Yields, ) -from docinstance.content.equation import DocEquation +from docinstance.docstring import Docstring +from docinstance.parser.latex import parse_equation -# pylint: disable=R0912,R0914,R0915 -def parse_numpy(docstring, contains_quotes=False): +def parse_numpy(docstring, contains_quotes=False): # pylint: disable=R0912,R0914,R0915 r"""Parse a docstring in numpy format into a Docstring instance. Multiple descriptions of the indented information (e.g. parameters, attributes, methods, diff --git a/src/docinstance/utils.py b/src/docinstance/utils.py index e355d65..a7b2cde 100644 --- a/src/docinstance/utils.py +++ b/src/docinstance/utils.py @@ -1,7 +1,7 @@ """Utility functions for handling strings and attributes of an object.""" -import textwrap import inspect import os +import textwrap def wrap(text, width=100, indent_level=0, tabsize=4, **kwargs): diff --git a/src/docinstance/wrapper.py b/src/docinstance/wrapper.py index 5a2689b..39d2a5a 100644 --- a/src/docinstance/wrapper.py +++ b/src/docinstance/wrapper.py @@ -1,8 +1,9 @@ """Functions for wrapping a python object to utilize the docinstance object.""" -import inspect -import os from functools import wraps import importlib +import inspect +import os + from docinstance.utils import extract_members diff --git a/tests/test_base.py b/tests/test_base.py index 43ab044..85f7321 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,12 +1,13 @@ """Test docinstance.content.base.""" -import pytest from docinstance.content.base import DocContent +import pytest class ModDocContent(DocContent): """DocContent where the init does not raise an error.""" def __init__(self): + """Initialize.""" pass diff --git a/tests/test_description.py b/tests/test_description.py index 71ef221..f940b1d 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,6 +1,6 @@ """Test docinstance.content.description.""" -import pytest from docinstance.content.description import DocDescription +import pytest def test_init(): diff --git a/tests/test_docstring.py b/tests/test_docstring.py index 839aa52..50f532b 100644 --- a/tests/test_docstring.py +++ b/tests/test_docstring.py @@ -1,8 +1,8 @@ """Test docinstance.docstring.""" -import pytest -from docinstance.docstring import Docstring -from docinstance.content.section import DocSection from docinstance.content.description import DocDescription +from docinstance.content.section import DocSection +from docinstance.docstring import Docstring +import pytest def test_init(): diff --git a/tests/test_equation.py b/tests/test_equation.py index f8481f7..3e93253 100644 --- a/tests/test_equation.py +++ b/tests/test_equation.py @@ -1,6 +1,6 @@ """Test docinstance.content.equation.""" -import pytest from docinstance.content.equation import DocEquation +import pytest def test_init(): diff --git a/tests/test_latex.py b/tests/test_latex.py index 5615bf0..5f07788 100644 --- a/tests/test_latex.py +++ b/tests/test_latex.py @@ -1,6 +1,6 @@ """Tests for docinstance.parser.latex.""" -from docinstance.parser.latex import is_math, parse_equation from docinstance.content.equation import DocEquation +from docinstance.parser.latex import is_math, parse_equation def test_is_math(): diff --git a/tests/test_numpy.py b/tests/test_numpy.py index bb44a1a..95f04c6 100644 --- a/tests/test_numpy.py +++ b/tests/test_numpy.py @@ -1,10 +1,10 @@ """Tests for docinstance.parser.numpy.""" -import pytest -from docinstance.docstring import Docstring -from docinstance.content.section import DocSection, Summary, ExtendedSummary, Parameters from docinstance.content.description import DocDescription from docinstance.content.equation import DocEquation +from docinstance.content.section import DocSection, ExtendedSummary, Parameters, Summary +from docinstance.docstring import Docstring from docinstance.parser.numpy import parse_numpy +import pytest def test_compare_docinstances(): diff --git a/tests/test_section.py b/tests/test_section.py index 59b7eaa..6133c4a 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -1,24 +1,24 @@ """Test docinstance.content.section.""" -import pytest +from docinstance.content.description import DocDescription from docinstance.content.section import ( + Attributes, DocSection, - Summary, + Examples, ExtendedSummary, - Parameters, - Attributes, Methods, - Returns, - Yields, + Notes, OtherParameters, + Parameters, Raises, - Warns, - Warnings, - SeeAlso, - Notes, References, - Examples, + Returns, + SeeAlso, + Summary, + Warnings, + Warns, + Yields, ) -from docinstance.content.description import DocDescription +import pytest def test_init(): diff --git a/tests/test_utils.py b/tests/test_utils.py index e8eff2a..e69e0fd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,6 @@ """Test docinstance.utils.""" -import pytest import docinstance.utils +import pytest def test_wrap(): diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index 750abd2..42d1cd7 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py @@ -2,16 +2,17 @@ import importlib import os import sys -import pytest + +from docinstance.content.description import DocDescription +from docinstance.content.section import DocSection +from docinstance.docstring import Docstring from docinstance.wrapper import ( - kwarg_wrapper, docstring, - docstring_recursive, docstring_current_module, + docstring_recursive, + kwarg_wrapper, ) -from docinstance.docstring import Docstring -from docinstance.content.section import DocSection -from docinstance.content.description import DocDescription +import pytest def test_kwarg_wrapper(): @@ -164,7 +165,7 @@ class Test: # pragma: no cover _docinstance = docinstance1 def f(self): - """Some docstring.""" + """Do nothing.""" pass f._docinstance = docinstance2 @@ -173,7 +174,7 @@ def f(self): "Test docstring.\n\n" "Attributes\n" "----------\n" "x : int\n" " Something.\n\n" ) assert Test._docinstance == docinstance1 - assert Test.f.__doc__ == "Some docstring." + assert Test.f.__doc__ == "Do nothing." assert Test.f._docinstance == docinstance2 # w/ indentation diff --git a/tox.ini b/tox.ini index 0a56327..46f645e 100644 --- a/tox.ini +++ b/tox.ini @@ -3,24 +3,147 @@ envlist = py37 py36 py35 - -[flake8] -max-line-length = 100 - -[pycodestyle] -max-line-length = 100 + linters [testenv] deps = pytest pytest-cov +commands = + pytest --cov={envsitepackagesdir}/docinstance --cov-branch tests + +# Linters +[testenv:readme] +basepython = python3 +deps = + readme_renderer + twine +commands = + python setup.py sdist bdist_wheel + twine check dist/* + +[testenv:black] +basepython = python3 +skip_install = true +deps = + black +commands = + black -l 100 --check ./ + black -l 100 --diff ./ + +[testenv:flake8] +basepython = python3 +skip_install = true +deps = flake8 + flake8-docstrings>=0.2.7 + flake8-import-order>=0.9 + pep8-naming + flake8-colors +commands = + flake8 src/docinstance tests/ setup.py + +[testenv:pylint] +basepython = python3 +skip_install = true +deps = pylint - pydocstyle - pycodestyle commands = - flake8 - pylint docinstance - pydocstyle - pycodestyle - pytest --cov={envsitepackagesdir}/docinstance --cov-branch + pylint src/docinstance --rcfile=tox.ini + +[testenv:bandit] +basepython = python3 +skip_install = true +deps = + bandit +commands = + bandit -r src/docinstance + +[testenv:linters] +basepython = python3 +skip_install = true +deps = + {[testenv:black]deps} + {[testenv:flake8]deps} + {[testenv:pylint]deps} + {[testenv:readme]deps} + {[testenv:bandit]deps} +commands = + {[testenv:black]commands} + {[testenv:flake8]commands} + {[testenv:pylint]commands} + {[testenv:readme]commands} + {[testenv:bandit]commands} +ignore_errors = true + +# flake8 +[flake8] +max-line-length = 100 +import-order-style = google +ignore = + # E121 : continuation line under-indented for hanging indent + E121, + # E123 : closing bracket does not match indentation of opening bracket’s line + E123, + # E126 : continuation line over-indented for hanging indent + E126, + # E226 : missing whitespace around arithmetic operator + E226, + # E241 : multiple spaces after ‘,’ + # E242 : tab after ‘,’ + E24, + # E704 : multiple statements on one line (def) + E704, + # W503 : line break occurred before a binary operator + W503, + # W504 : Line break occurred after a binary operator + W504, + # E203 : Whitespace before ':' + E203, + # D202 : No blank lines allowed after function docstring + D202, + +# pylintrc +[FORMAT] +# Maximum number of characters on a single line. +max-line-length=100 + +[MESSAGES CONTROL] +disable= + # attribute-defined-outside-init (W0201): + # Attribute %r defined outside __init__ Used when an instance attribute is + # defined outside the __init__ method. + W0201, + # too-many-instance-attributes (R0902): + # Too many instance attributes (%s/%s) Used when class has too many instance + # attributes, try to reduce this to get a simpler (and so easier to use) + # class. + R0902, + # too-many-arguments (R0913): + # Too many arguments (%s/%s) Used when a function or method takes too many + # arguments. + R0913, + # fixme (W0511): + # Used when a warning note as FIXME or XXX is detected. + W0511, + # bad-continuation (C0330): + # Wrong hanging indentation before block (add 4 spaces). + C0330, + # wrong-import-order (C0411): + # %s comes before %s Used when PEP8 import order is not respected (standard + # imports first, then third-party libraries, then local imports) + C0411, + # arguments-differ (W0221): + # Parameters differ from %s %r method Used when a method has a different + # number of arguments than in the implemented interface or in an overridden + # method. + W0221, + # import-error (E0401): + # Unable to import %s Used when pylint has been unable to import a module. + E0401, + # super-init-not-called (W0231): + # Used when an ancestor class method has an __init__ method which is not called by a derived class. + W0231, + +[SIMILARITIES] +min-similarity-lines=5 \ No newline at end of file From 062e8fc5706b22fb074b2c8d03ea835d3096ee6c Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 17:57:50 +0200 Subject: [PATCH 05/15] Fix coverage issue Branch was not getting covered because generator was not being exhausted. That's not an issue because only the first entry is interesting --- src/docinstance/wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docinstance/wrapper.py b/src/docinstance/wrapper.py index 39d2a5a..6b0e799 100644 --- a/src/docinstance/wrapper.py +++ b/src/docinstance/wrapper.py @@ -155,7 +155,7 @@ def docstring_current_module(width=100, tabsize=4): """ # get the module that called this function - module = next( + module = next( # pragma: no branch inspect.getmodule(i[0]) for i in inspect.stack()[1:] # pragma: no branch if inspect.getmodule(i[0]) is not None @@ -195,7 +195,7 @@ def docstring_modify_import(width=100, tabsize=4): """ # find the location from which this function is called - parentfile = next( + parentfile = next( # pragma: no branch inspect.getsourcefile(i[0]) for i in inspect.stack()[1:] # pragma: no branch if inspect.getmodule(i[0]) is not None From 3bd7e70fb19ca57fc29ee83953918034be0bdc9e Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 22:45:08 +0200 Subject: [PATCH 06/15] Consolidate multiple docstring methods into a single method DocContent 1. Remove DocContent.make_numpy_docstring, DocContent.make_google_docstring, DocContent.make_rst_docstring 2. Add DocContent.make_docstring that calls the correct method for the given style via name of the method 3. Change name of the methods from make_numpy_docstring to make_docstring_numpy, from make_numpy_docstring_signature to make_docstring_numpy_signature, from make_google_docstring to make_docstring_google, and from make_rst_docstring to make_docstring_rst. Having multiple abstract methods in DocSection for different styles of docstring seems to get more complicated as the number of styles increases. If the goal is to make it easier to create docstrings of new styles, then we can just add more styles to the subclasses without having to add more functions at the base class. I'm not 100% sure that the best way is to keep adding methods for each style (this would result in the class growing quite quickly). Maybe it'd be better to organize the modules by the styles also i.e. make a subclass for each child of DocSection specific for the style. --- src/docinstance/content/base.py | 82 +++++-------------- src/docinstance/content/description.py | 22 +++--- src/docinstance/content/equation.py | 8 +- src/docinstance/content/section.py | 38 +++++---- src/docinstance/docstring.py | 18 +---- tests/test_base.py | 26 +++---- tests/test_description.py | 104 ++++++++++++------------- tests/test_docstring.py | 4 +- tests/test_equation.py | 16 ++-- tests/test_section.py | 62 +++++++-------- 10 files changed, 168 insertions(+), 212 deletions(-) diff --git a/src/docinstance/content/base.py b/src/docinstance/content/base.py index f1b0e39..0cb0df4 100644 --- a/src/docinstance/content/base.py +++ b/src/docinstance/content/base.py @@ -15,12 +15,8 @@ class DocContent: Return True if other is DocContent instance with the same contents. False otherwise. __ne__(self, other) Return False if other is DocContent instance with the same contents. True otherwise. - make_numpy_docstring(self, width, indent_level, tabsize) - Return the docstring of the content in numpy style. - make_google_docstring(self, width, indent_level, tabsize) - Return the docstring of the content in google style. - make_rst_docstring(self, width, indent_level, tabsize) - Return the docstring of the content in rst style. + make_docstring(self, width, indent_level, tabsize, style) + Return the docstring of the content in the given style. """ @@ -72,8 +68,8 @@ def __ne__(self, other): """ return not self == other - def make_numpy_docstring(self, width, indent_level, tabsize): - """Return the docstring of the content in numpy style. + def make_docstring(self, width, indent_level, tabsize, style): + """Return the docstring as a string in the given style. Parameters ---------- @@ -83,66 +79,30 @@ def make_numpy_docstring(self, width, indent_level, tabsize): Number of indents (tabs) that are needed for the docstring. tabsize : int Number of spaces that corresponds to a tab. + style : str + Name of the docstring style. Returns ------- content_docstring : str - Docstring of the given content in numpy style. + Docstring of the given content in the given style. Raises ------ + TypeError + If `style` is not given as a non-empty string. NotImplementedError - Always. - - """ - raise NotImplementedError - - def make_google_docstring(self, width, indent_level, tabsize): - """Return the docstring of the content in google style. - - Parameters - ---------- - width : int - Maximum number of characters allowed in a line. - indent_level : int - Number of indents (tabs) that are needed for the docstring. - tabsize : int - Number of spaces that corresponds to a tab. - - Returns - ------- - content_docstring : str - Docstring of the given content in google style. - - Raises - ------ - NotImplementedError - Always. + If the corresponding method for the given `style` is not defined. """ - raise NotImplementedError - - def make_rst_docstring(self, width, indent_level, tabsize): - """Return the docstring of the content in rst style. - - Parameters - ---------- - width : int - Maximum number of characters allowed in a line. - indent_level : int - Number of indents (tabs) that are needed for the docstring. - tabsize : int - Number of spaces that corresponds to a tab. - - Returns - ------- - content_docstring : str - Docstring of the given content in rst style. - - Raises - ------ - NotImplementedError - Always. - - """ - raise NotImplementedError + if not (isinstance(style, str) and style != ""): + raise TypeError("The `style` of the docstring must be given as a non-empty string.") + method_name = "make_docstring_{}".format(style) + if hasattr(self, method_name): + return getattr(self, method_name)(width, indent_level, tabsize) + raise NotImplementedError( + "To make a docstring of style, {}, the given instance of {} must have a method called " + "{} with arguments (width, indent_level, tabsize).".format( + style, self.__class__, method_name + ) + ) diff --git a/src/docinstance/content/description.py b/src/docinstance/content/description.py index a21fa6d..7effc38 100644 --- a/src/docinstance/content/description.py +++ b/src/docinstance/content/description.py @@ -34,12 +34,16 @@ class DocDescription(DocContent): ------- __init__(self, name, signature='', types='', descs='') Initialize. - make_docstring(self, style, width, indent_level, tabsize) - Return docstring in correponding style. - make_numpy_docstring(self, width, indent_level, tabsize) + make_docstring(self, width, indent_level, tabsize, style) + Return the docstring of the content in the given style. + make_docstring_numpy(self, width, indent_level, tabsize) Return the docstring in numpy style. - make_numpy_docstring_signature(self, width, indent_level, tabsize) + make_docstring_numpy_signature(self, width, indent_level, tabsize) Return the docstring in numpy style modified to include signature. + make_docstring_google(self, width, indent_level, tabsize) + Return the docstring in google style. + make_docstring_rst(self, width, indent_level, tabsize) + Return the docstring in rst style. """ @@ -121,7 +125,7 @@ def types_str(self): """ return [i.__name__ if isinstance(i, type) else i for i in self.types] - def make_numpy_docstring(self, width, indent_level, tabsize): + def make_docstring_numpy(self, width, indent_level, tabsize): """Return the docstring in numpy style. Parameters @@ -216,7 +220,7 @@ def make_numpy_docstring(self, width, indent_level, tabsize): return output - def make_numpy_docstring_signature(self, width, indent_level, tabsize): + def make_docstring_numpy_signature(self, width, indent_level, tabsize): """Return the docstring in numpy style modified to include signature. Parameters @@ -237,9 +241,9 @@ def make_numpy_docstring_signature(self, width, indent_level, tabsize): """ new_name = "{0}{1}".format(self.name, self.signature) new_description = self.__class__(new_name, "", self.types, self.descs) - return new_description.make_numpy_docstring(width, indent_level, tabsize) + return new_description.make_docstring_numpy(width, indent_level, tabsize) - def make_google_docstring(self, width, indent_level, tabsize): + def make_docstring_google(self, width, indent_level, tabsize): """Return the docstring of the content in google style. Parameters @@ -312,7 +316,7 @@ def make_google_docstring(self, width, indent_level, tabsize): output += "\n" return output - def make_rst_docstring(self, width, indent_level, tabsize): + def make_docstring_rst(self, width, indent_level, tabsize): """Return the docstring in sphinx's rst format. Parameters diff --git a/src/docinstance/content/equation.py b/src/docinstance/content/equation.py index 9500b46..27db2d6 100644 --- a/src/docinstance/content/equation.py +++ b/src/docinstance/content/equation.py @@ -15,8 +15,10 @@ class DocEquation(DocContent): ------- __init__(self, equations) Initialize. - make_numpy_docstring(self, style, width, indent_level, tabsize) - Return docstring in correponding style. + make_docstring(self, width, indent_level, tabsize, style) + Return the docstring as a string in the given style. + make_docstring_numpy(self, width, indent_level, tabsize) + Return the docstring in numpy style. """ @@ -40,7 +42,7 @@ def __init__(self, equations): if self.equations[-1] == "": self.equations = self.equations[:-1] - def make_numpy_docstring(self, width, indent_level, tabsize): + def make_docstring_numpy(self, width, indent_level, tabsize): """Return the docstring in numpy style. Parameters diff --git a/src/docinstance/content/section.py b/src/docinstance/content/section.py index 22e582d..96dea5e 100644 --- a/src/docinstance/content/section.py +++ b/src/docinstance/content/section.py @@ -18,10 +18,16 @@ class DocSection(DocContent): ------- __init__(self, header, contents) Initialize. - make_numpy_docstring(self, width, indent_level, tabsize, include_signature=False) + make_docstring_numpy(self, width, indent_level, tabsize) Return the docstring in numpy style. - make_numpy_docstring_signature(self, width, indent_level, tabsize) + make_docstring_numpy(self, width, indent_level, tabsize, include_signature=False) + Return the docstring in numpy style. + make_docstring_numpy_signature(self, width, indent_level, tabsize) Return the docstring in numpy style modified to include signature. + make_docstring_google(self, width, indent_level, tabsize) + Return the docstring in google style. + make_docstring_rst(self, width, indent_level, tabsize) + Return the docstring in rst style. """ @@ -76,8 +82,8 @@ def __init__(self, header, contents): self.contents = list(contents) # pylint: disable=W0221 - # the extra argument is used in the make_numpy_docstring_signature - def make_numpy_docstring(self, width, indent_level, tabsize, include_signature=False): + # the extra argument is used in the make_docstring_numpy_signature + def make_docstring_numpy(self, width, indent_level, tabsize, include_signature=False): """Return the docstring in numpy style. Parameters @@ -126,10 +132,10 @@ def make_numpy_docstring(self, width, indent_level, tabsize, include_signature=F ) output += "\n\n" # if isinstance(paragraph, DocContent) - elif include_signature and hasattr(paragraph, "make_numpy_docstring_signature"): - output += paragraph.make_numpy_docstring_signature(width, indent_level, tabsize) + elif include_signature and hasattr(paragraph, "make_docstring_numpy_signature"): + output += paragraph.make_docstring_numpy_signature(width, indent_level, tabsize) else: - output += paragraph.make_numpy_docstring(width, indent_level, tabsize) + output += paragraph.make_docstring_numpy(width, indent_level, tabsize) # pylint: disable=W0120 # following block clause should always be executed else: @@ -139,7 +145,7 @@ def make_numpy_docstring(self, width, indent_level, tabsize, include_signature=F return output - def make_numpy_docstring_signature(self, width, indent_level, tabsize): + def make_docstring_numpy_signature(self, width, indent_level, tabsize): """Return the docstring in numpy style modified to include signature. Parameters @@ -163,9 +169,9 @@ def make_numpy_docstring_signature(self, width, indent_level, tabsize): If any of the paragraph is neither a string nor a DocDescription instance. """ - return self.make_numpy_docstring(width, indent_level, tabsize, include_signature=True) + return self.make_docstring_numpy(width, indent_level, tabsize, include_signature=True) - def make_google_docstring(self, width, indent_level, tabsize): + def make_docstring_google(self, width, indent_level, tabsize): """Return the docstring of the section in google style. Parameters @@ -215,7 +221,7 @@ def make_google_docstring(self, width, indent_level, tabsize): output += "\n\n" # if isinstance(paragraph, DocContent) else: - output += paragraph.make_google_docstring(width, indent_level + 1, tabsize) + output += paragraph.make_docstring_google(width, indent_level + 1, tabsize) # pylint: disable=W0120 # following block clause should always be executed else: @@ -225,7 +231,7 @@ def make_google_docstring(self, width, indent_level, tabsize): return output - def make_rst_docstring(self, width, indent_level, tabsize): + def make_docstring_rst(self, width, indent_level, tabsize): """Return the docstring in sphinx's rst format. Parameters @@ -282,7 +288,7 @@ def make_rst_docstring(self, width, indent_level, tabsize): else: output += header output += "\n" - output += paragraph.make_rst_docstring( + output += paragraph.make_docstring_rst( width=width, indent_level=indent_level + 1, tabsize=tabsize ) # indent all susequent content @@ -295,7 +301,7 @@ def make_rst_docstring(self, width, indent_level, tabsize): # recognize text that is more than one newline away) output += "\n\n" else: - output += paragraph.make_rst_docstring(width, indent_level, tabsize) + output += paragraph.make_docstring_rst(width, indent_level, tabsize) # pylint: disable=W0120 # following block clause should always be executed else: @@ -321,9 +327,9 @@ class Summary(DocSection): ------- __init__(self, header, contents) Initialize. - make_numpy_docstring(self, width, indent_level, tabsize, include_signature=False) + make_docstring_numpy(self, width, indent_level, tabsize, include_signature=False) Return the docstring in numpy style. - make_numpy_docstring_signature(self, width, indent_level, tabsize) + make_docstring_numpy_signature(self, width, indent_level, tabsize) Return the docstring in numpy style modified to include signature. """ diff --git a/src/docinstance/docstring.py b/src/docinstance/docstring.py index bf2f9fc..9d47b4c 100644 --- a/src/docinstance/docstring.py +++ b/src/docinstance/docstring.py @@ -126,20 +126,6 @@ def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): if tabsize <= 0: raise ValueError("Number of spaces in a tab must be greater than zero.") - if style == "numpy": - docstring_func = "make_numpy_docstring" - elif style == "numpy with signature": - docstring_func = "make_numpy_docstring_signature" - elif style == "google": - docstring_func = "make_google_docstring" - elif style == "rst": - docstring_func = "make_rst_docstring" - else: - raise ValueError( - "Given docstring style must be one of 'numpy', 'numpy with signature'," - " 'google', 'rst'." - ) - # FIXME: this may not be necessary and can be removed if not self.check_section_order(style): raise ValueError( @@ -162,11 +148,11 @@ def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): # add remaining summary if len(self.sections[0].contents) > 1: summary = DocSection("", self.sections[0].contents[1:]) - output += getattr(summary, docstring_func)(width, indent_level, tabsize) + output += summary.make_docstring(width, indent_level, tabsize, style) # add other sections if len(self.sections) > 1: for section in self.sections[1:]: - output += getattr(section, docstring_func)(width, indent_level, tabsize) + output += section.make_docstring(width, indent_level, tabsize, style) # add whitespace to indent the triple quotation output += " " * indent_level * tabsize return output diff --git a/tests/test_base.py b/tests/test_base.py index 85f7321..ca0cc82 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -61,22 +61,20 @@ class Empty: assert test1 != test2 -def test_base_make_numpy_docstring(): - """Test DocContent.make_numpy_docstring.""" +def test_base_make_docstring(): + """Test DocContent.make_docstring.""" test = ModDocContent() - with pytest.raises(NotImplementedError): - test.make_numpy_docstring(100, 0, 4) + with pytest.raises(TypeError): + test.make_docstring(100, 0, 4, "") + with pytest.raises(TypeError): + test.make_docstring(100, 0, 4, ["numpy"]) + test.make_docstring_test = lambda width, indent_level, tabsize: "answer" + assert test.make_docstring(100, 0, 4, "test") == "answer" -def test_base_make_google_docstring(): - """Test DocContent.make_google_docstring.""" - test = ModDocContent() with pytest.raises(NotImplementedError): - test.make_google_docstring(100, 0, 4) - - -def test_base_make_rst_docstring(): - """Test DocContent.make_rst_docstring.""" - test = ModDocContent() + test.make_docstring(100, 0, 4, "numpy") + with pytest.raises(NotImplementedError): + test.make_docstring(100, 0, 4, "google") with pytest.raises(NotImplementedError): - test.make_rst_docstring(100, 0, 4) + test.make_docstring(100, 0, 4, "rst") diff --git a/tests/test_description.py b/tests/test_description.py index f940b1d..7dfa0d1 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -51,47 +51,47 @@ def test_types_str(): assert test.types_str == ["str", "int", "list of str"] -def test_make_numpy_docstring(): - """Test DocDescription.make_numpy_docstring.""" +def test_make_docstring_numpy(): + """Test DocDescription.make_docstring_numpy.""" # no type test = DocDescription("var_name", descs=["hello"]) - assert test.make_numpy_docstring(9, 0, 4) == "var_name\n hello\n" + assert test.make_docstring_numpy(9, 0, 4) == "var_name\n hello\n" # one type test = DocDescription("var_name", types=str, descs=["hello"]) with pytest.raises(ValueError): - test.make_numpy_docstring(13, 0, 4) + test.make_docstring_numpy(13, 0, 4) with pytest.raises(ValueError): - test.make_numpy_docstring(14, 1, 2) - assert test.make_numpy_docstring(14, 0, 4) == "var_name : str\n hello\n" - assert test.make_numpy_docstring(16, 1, 2) == " var_name : str\n hello\n" + test.make_docstring_numpy(14, 1, 2) + assert test.make_docstring_numpy(14, 0, 4) == "var_name : str\n hello\n" + assert test.make_docstring_numpy(16, 1, 2) == " var_name : str\n hello\n" # multiple types test = DocDescription("var_name", types=[str, int, bool], descs=["hello"]) with pytest.raises(ValueError): - test.make_numpy_docstring(15, 0, 4) + test.make_docstring_numpy(15, 0, 4) with pytest.raises(ValueError): - test.make_numpy_docstring(16, 1, 2) + test.make_docstring_numpy(16, 1, 2) # NOTE: following raises an error even though width is big enough for 'var_name : {str,' because # the `utils.wrap` complains with pytest.raises(ValueError): - test.make_numpy_docstring(16, 0, 4) - assert test.make_numpy_docstring(27, 0, 4) == "var_name : {str, int, bool}\n hello\n" + test.make_docstring_numpy(16, 0, 4) + assert test.make_docstring_numpy(27, 0, 4) == "var_name : {str, int, bool}\n hello\n" assert ( - test.make_numpy_docstring(26, 0, 4) + test.make_docstring_numpy(26, 0, 4) == "var_name : {str, int,\n bool}\n hello\n" ) assert ( - test.make_numpy_docstring(27, 1, 2) + test.make_docstring_numpy(27, 1, 2) == " var_name : {str, int,\n bool}\n hello\n" ) assert ( - test.make_numpy_docstring(17, 0, 4) + test.make_docstring_numpy(17, 0, 4) == "var_name : {str,\n int,\n bool}\n hello\n" ) # signature does nothing test2 = DocDescription( "var_name", signature="(a, b, c)", types=[str, int, bool], descs=["hello"] ) - assert test.make_numpy_docstring(17, 0, 4) == test2.make_numpy_docstring(17, 0, 4) + assert test.make_docstring_numpy(17, 0, 4) == test2.make_docstring_numpy(17, 0, 4) # multiple paragraphs test = DocDescription( "var_name", @@ -99,92 +99,92 @@ def test_make_numpy_docstring(): descs=["description 1", "description 2", "description 3"], ) assert ( - test.make_numpy_docstring(27, 0, 4) + test.make_docstring_numpy(27, 0, 4) == "var_name : {str, int, bool}\n description 1\n description 2\n" " description 3\n" ) -def test_make_numpy_docstring_signature(): - """Test DocDescription.make_numpy_docstring_signature.""" +def test_make_docstring_numpy_signature(): + """Test DocDescription.make_docstring_numpy_signature.""" test = DocDescription( "var_name", signature="(a, b, c)", types=[str, int, bool], descs=["hello"] ) assert ( - test.make_numpy_docstring_signature(36, 0, 4) + test.make_docstring_numpy_signature(36, 0, 4) == "var_name(a, b, c) : {str, int, bool}\n hello\n" ) assert ( - test.make_numpy_docstring_signature(26, 0, 4) + test.make_docstring_numpy_signature(26, 0, 4) == "var_name(a, b, c) : {str,\n int,\n bool}\n" " hello\n" ) -def test_make_google_docstring(): - """Test DocDescription.make_google_docstring.""" +def test_make_docstring_google(): + """Test DocDescription.make_docstring_google.""" # no type, desc test = DocDescription("var_name", descs=["hello"]) - assert test.make_google_docstring(15, 0, 4) == "var_name: hello\n" + assert test.make_docstring_google(15, 0, 4) == "var_name: hello\n" # no type, no descs test = DocDescription("var_name") - assert test.make_google_docstring(9, 0, 4) == "var_name:\n" + assert test.make_docstring_google(9, 0, 4) == "var_name:\n" with pytest.raises(ValueError): - test.make_google_docstring(8, 0, 4) + test.make_docstring_google(8, 0, 4) # one type, no descs test = DocDescription("var_name", types=str) - assert test.make_google_docstring(22, 0, 4) == "var_name (:obj:`str`):\n" + assert test.make_docstring_google(22, 0, 4) == "var_name (:obj:`str`):\n" with pytest.raises(ValueError): - test.make_google_docstring(21, 0, 4) + test.make_docstring_google(21, 0, 4) # one type, no descs test = DocDescription("var_name", types="str") - assert test.make_google_docstring(15, 0, 4) == "var_name (str):\n" + assert test.make_docstring_google(15, 0, 4) == "var_name (str):\n" with pytest.raises(ValueError): - test.make_google_docstring(14, 0, 4) + test.make_docstring_google(14, 0, 4) # many types, no descs test = DocDescription("var_name", types=["str", int]) - assert test.make_google_docstring(22, 0, 4) == ("var_name (str,\n" " :obj:`int`):\n") + assert test.make_docstring_google(22, 0, 4) == ("var_name (str,\n" " :obj:`int`):\n") with pytest.raises(ValueError): - test.make_google_docstring(21, 0, 4) - assert test.make_google_docstring(23, 1, 1) == (" var_name (str,\n" " :obj:`int`):\n") + test.make_docstring_google(21, 0, 4) + assert test.make_docstring_google(23, 1, 1) == (" var_name (str,\n" " :obj:`int`):\n") # one type, desc test = DocDescription("var_name", types=str, descs=["hello"]) - assert test.make_google_docstring(22, 0, 4) == "var_name (:obj:`str`):\n hello\n" + assert test.make_docstring_google(22, 0, 4) == "var_name (:obj:`str`):\n hello\n" # FIXME - assert test.make_google_docstring(24, 1, 2) == " var_name (:obj:`str`):\n hello\n" + assert test.make_docstring_google(24, 1, 2) == " var_name (:obj:`str`):\n hello\n" # multiple types test = DocDescription("var_name", types=[str, int, bool], descs=["hello"]) with pytest.raises(ValueError): - test.make_google_docstring(22, 0, 4) + test.make_docstring_google(22, 0, 4) with pytest.raises(ValueError): - test.make_google_docstring(24, 1, 2) - assert test.make_google_docstring(23, 0, 4) == ( + test.make_docstring_google(24, 1, 2) + assert test.make_docstring_google(23, 0, 4) == ( "var_name (:obj:`str`,\n" " :obj:`int`,\n" " :obj:`bool`):\n" " hello\n" ) - assert test.make_google_docstring(26, 1, 2) == ( + assert test.make_docstring_google(26, 1, 2) == ( " var_name (:obj:`str`,\n" " :obj:`int`,\n" " :obj:`bool`):\n" " hello\n" ) - assert test.make_google_docstring(35, 1, 2) == ( + assert test.make_docstring_google(35, 1, 2) == ( " var_name (:obj:`str`, :obj:`int`,\n" " :obj:`bool`): hello\n" ) # signature does nothing test2 = DocDescription( "var_name", signature="(a, b, c)", types=[str, int, bool], descs=["hello"] ) - assert test.make_google_docstring(23, 0, 4) == test2.make_google_docstring(23, 0, 4) + assert test.make_docstring_google(23, 0, 4) == test2.make_docstring_google(23, 0, 4) # multiple paragraphs test = DocDescription( "var_name", types=[str, int, bool], descs=["description 1", "description 2", "description 3"], ) - assert test.make_google_docstring(26, 0, 2) == ( + assert test.make_docstring_google(26, 0, 2) == ( "var_name (:obj:`str`,\n" " :obj:`int`,\n" " :obj:`bool`):\n" @@ -194,31 +194,31 @@ def test_make_google_docstring(): ) -def test_make_rst_docstring(): - """Test DocDescription.make_rst_docstring.""" +def test_make_docstring_rst(): + """Test DocDescription.make_docstring_rst.""" # only name test = DocDescription("var_name") - assert test.make_rst_docstring(16, 0, 1) == ":param var_name:\n" - assert test.make_rst_docstring(17, 1, 1) == " :param var_name:\n" + assert test.make_docstring_rst(16, 0, 1) == ":param var_name:\n" + assert test.make_docstring_rst(17, 1, 1) == " :param var_name:\n" with pytest.raises(ValueError): - test.make_rst_docstring(15, 0, 4) + test.make_docstring_rst(15, 0, 4) # name + desc test = DocDescription("var_name", descs="hello") - assert test.make_rst_docstring(22, 0, 1) == ":param var_name: hello\n" - assert test.make_rst_docstring(21, 0, 1) == (":param var_name:\n" " hello\n") - assert test.make_rst_docstring(21, 0, 4) == (":param var_name:\n" " hello\n") + assert test.make_docstring_rst(22, 0, 1) == ":param var_name: hello\n" + assert test.make_docstring_rst(21, 0, 1) == (":param var_name:\n" " hello\n") + assert test.make_docstring_rst(21, 0, 4) == (":param var_name:\n" " hello\n") test = DocDescription("var_name", descs=["hello my name is", "Example 2."]) - assert test.make_rst_docstring(25, 0, 4) == ( + assert test.make_docstring_rst(25, 0, 4) == ( ":param var_name: hello my\n" " name is\n" " Example 2.\n" ) # name + type test = DocDescription("var_name", types=["str", int]) - assert test.make_rst_docstring(20, 0, 2) == ( + assert test.make_docstring_rst(20, 0, 2) == ( ":param var_name:\n" ":type var_name: str,\n" " :obj:`int`\n" ) # name + desc + type test = DocDescription("var_name", types=["str", int], descs=["Example 1.", "Example 2."]) - assert test.make_rst_docstring(27, 0, 4) == ( + assert test.make_docstring_rst(27, 0, 4) == ( ":param var_name: Example 1.\n" " Example 2.\n" ":type var_name: str,\n" diff --git a/tests/test_docstring.py b/tests/test_docstring.py index 50f532b..bab63fb 100644 --- a/tests/test_docstring.py +++ b/tests/test_docstring.py @@ -72,7 +72,7 @@ def test_make_docstring(): test.make_docstring(tabsize=-2) with pytest.raises(ValueError): test.make_docstring(tabsize=0) - with pytest.raises(ValueError): + with pytest.raises(NotImplementedError): test.make_docstring(style="random style") # bad ordering test = Docstring(["summary", DocSection("parameters", ""), "extended summary"]) @@ -118,7 +118,7 @@ def test_make_docstring(): ] ) assert ( - test.make_docstring(width=25, style="numpy with signature") + test.make_docstring(width=25, style="numpy_signature") == "summary\n\nMethods\n-------\nfunc1(a, b) : str\n Example.\n\n" ) # google diff --git a/tests/test_equation.py b/tests/test_equation.py index 3e93253..88628d5 100644 --- a/tests/test_equation.py +++ b/tests/test_equation.py @@ -19,28 +19,28 @@ def test_init(): assert test.equations == ["a + b &= 2\\\\", "c + d &= 3\\\\"] -def test_make_numpy_docstring(): - """Test DocEquation.make_numpy_docstring.""" +def test_make_docstring_numpy(): + """Test DocEquation.make_docstring_numpy.""" test = DocEquation("a + b = 2") - assert test.make_numpy_docstring(19, 0, 4) == ".. math:: a + b = 2\n\n" - assert test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n a + b = 2\n\n" + assert test.make_docstring_numpy(19, 0, 4) == ".. math:: a + b = 2\n\n" + assert test.make_docstring_numpy(18, 0, 4) == ".. math::\n\n a + b = 2\n\n" with pytest.raises(ValueError): - test.make_numpy_docstring(8, 0, 4) + test.make_docstring_numpy(8, 0, 4) test = DocEquation("a + b &= 2\\\\\nc + d &= 3\\\\\n") assert ( - test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n" + test.make_docstring_numpy(18, 0, 4) == ".. math::\n\n" " a + b &= 2\\\\\n" " c + d &= 3\\\\\n\n" ) test = DocEquation("a + b &= 2\\\\\nc + d &= 3\n") assert ( - test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n" + test.make_docstring_numpy(18, 0, 4) == ".. math::\n\n" " a + b &= 2\\\\\n" " c + d &= 3\n\n" ) test = DocEquation("a + b &= 2\nc + d &= 3\n") assert ( - test.make_numpy_docstring(18, 0, 4) == ".. math::\n\n" + test.make_docstring_numpy(18, 0, 4) == ".. math::\n\n" " a + b &= 2\n" " c + d &= 3\n\n" ) diff --git a/tests/test_section.py b/tests/test_section.py index 6133c4a..e961cf9 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -52,19 +52,19 @@ def test_init(): assert test.contents == [doc1, doc2] -def test_make_numpy_docstring(): - """Test DocSection.make_numpy_docstring.""" +def test_make_docstring_numpy(): + """Test DocSection.make_docstring_numpy.""" # string content test = DocSection("header name", "hello") - assert test.make_numpy_docstring(11, 0, 4) == "Header Name\n-----------\nhello\n\n" - assert test.make_numpy_docstring(11, 0, 4) == "Header Name\n-----------\nhello\n\n" + assert test.make_docstring_numpy(11, 0, 4) == "Header Name\n-----------\nhello\n\n" + assert test.make_docstring_numpy(11, 0, 4) == "Header Name\n-----------\nhello\n\n" # multiple string contents test = DocSection("header name", ["hello", "i am"]) - assert test.make_numpy_docstring(11, 0, 4) == "Header Name\n-----------\nhello\n\ni am\n\n" + assert test.make_docstring_numpy(11, 0, 4) == "Header Name\n-----------\nhello\n\ni am\n\n" # doc description test = DocSection("header name", DocDescription("var_name", types=str, descs="Example.")) assert ( - test.make_numpy_docstring(20, 0, 4) + test.make_docstring_numpy(20, 0, 4) == "Header Name\n-----------\nvar_name : str\n Example.\n\n" ) # multiple doc descriptions @@ -76,7 +76,7 @@ def test_make_numpy_docstring(): ], ) assert ( - test.make_numpy_docstring(20, 0, 4) + test.make_docstring_numpy(20, 0, 4) == "Header Name\n-----------\nvar_name1 : str\n Example 1.\nvar_name2 : int\n" " Example 2.\n\n" ) @@ -85,35 +85,35 @@ def test_make_numpy_docstring(): "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") ) assert ( - test.make_numpy_docstring(20, 0, 4) + test.make_docstring_numpy(20, 0, 4) == "Header Name\n-----------\nvar_name : str\n Example.\n\n" ) -def test_make_numpy_docstring_signature(): - """Test DocSection.make_numpy_docstring_signature.""" +def test_make_docstring_numpy_signature(): + """Test DocSection.make_docstring_numpy_signature.""" test = DocSection( "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") ) assert ( - test.make_numpy_docstring_signature(20, 0, 4) + test.make_docstring_numpy_signature(20, 0, 4) == "Header Name\n-----------\nvar_name(a, b) : str\n Example.\n\n" ) -def test_make_google_docstring(): - """Test DocSection.make_google_docstring.""" +def test_make_docstring_google(): + """Test DocSection.make_docstring_google.""" with pytest.raises(ValueError): test = DocSection("quite long header name", "") - test.make_google_docstring(10, 0, 4) + test.make_docstring_google(10, 0, 4) # no header test = DocSection("", "Some text.") - assert test.make_google_docstring(10, 0, 4) == ("Some text.\n\n") + assert test.make_docstring_google(10, 0, 4) == ("Some text.\n\n") # one docdescription test = DocSection( "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") ) - assert test.make_google_docstring(35, 0, 4) == ( + assert test.make_docstring_google(35, 0, 4) == ( "Header Name:\n" " var_name (:obj:`str`): Example.\n\n" ) # multiple docdescription @@ -124,36 +124,36 @@ def test_make_google_docstring(): DocDescription("var2", signature="(c)", types="int", descs="Example2."), ], ) - assert test.make_google_docstring(35, 0, 4) == ( + assert test.make_docstring_google(35, 0, 4) == ( "Header Name:\n" " var1 (:obj:`str`): Example1.\n" " var2 (int): Example2.\n\n" ) # one string test = DocSection("header name", "Some text.") - assert test.make_google_docstring(14, 0, 4) == ("Header Name:\n" " Some text.\n\n") - assert test.make_google_docstring(13, 0, 4) == ("Header Name:\n" " Some\n" " text.\n\n") + assert test.make_docstring_google(14, 0, 4) == ("Header Name:\n" " Some text.\n\n") + assert test.make_docstring_google(13, 0, 4) == ("Header Name:\n" " Some\n" " text.\n\n") # multiple string test = DocSection("header name", ["Some text.", "Another text."]) - assert test.make_google_docstring(17, 0, 4) == ( + assert test.make_docstring_google(17, 0, 4) == ( "Header Name:\n" " Some text.\n\n" " Another text.\n\n" ) - assert test.make_google_docstring(14, 0, 4) == ( + assert test.make_docstring_google(14, 0, 4) == ( "Header Name:\n" " Some text.\n\n" " Another\n" " text.\n\n" ) - assert test.make_google_docstring(13, 0, 4) == ( + assert test.make_docstring_google(13, 0, 4) == ( "Header Name:\n" " Some\n" " text.\n\n" " Another\n" " text.\n\n" ) -def test_make_rst_docstring(): - """Test DocSection.make_rst_docstring.""" +def test_make_docstring_rst(): + """Test DocSection.make_docstring_rst.""" # no header test = DocSection("", "Some text.") - assert test.make_rst_docstring(10, 0, 4) == ("Some text.\n\n") + assert test.make_docstring_rst(10, 0, 4) == ("Some text.\n\n") # normal header, one docdescription test = DocSection( "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") ) - assert test.make_rst_docstring(35, 0, 4) == ( + assert test.make_docstring_rst(35, 0, 4) == ( ":Header Name:\n\n" ":param var_name: Example.\n" ":type var_name: :obj:`str`\n\n" ) # normal header, multiple docdescription @@ -164,7 +164,7 @@ def test_make_rst_docstring(): DocDescription("var2", signature="(c)", types="int", descs="Example2."), ], ) - assert test.make_rst_docstring(35, 0, 4) == ( + assert test.make_docstring_rst(35, 0, 4) == ( ":Header Name:\n\n" ":param var1: Example1.\n" ":type var1: :obj:`str`\n" @@ -173,10 +173,10 @@ def test_make_rst_docstring(): ) # normal header, one string test = DocSection("header name", "Some text.") - assert test.make_rst_docstring(13, 0, 4) == (":Header Name:\n\n" "Some text.\n\n") + assert test.make_docstring_rst(13, 0, 4) == (":Header Name:\n\n" "Some text.\n\n") # normal header, multiple string test = DocSection("header name", ["Some text.", "Another text."]) - assert test.make_rst_docstring(13, 0, 4) == ( + assert test.make_docstring_rst(13, 0, 4) == ( ":Header Name:\n\n" "Some text.\n\n" "Another text.\n\n" ) @@ -188,7 +188,7 @@ def test_make_rst_docstring(): DocDescription("var2", signature="(c)", types="int", descs="Example2."), ], ) - assert test.make_rst_docstring(35, 0, 4) == ( + assert test.make_docstring_rst(35, 0, 4) == ( ".. seealso::\n" " :param var1: Example1.\n" " :type var1: :obj:`str`\n" @@ -197,7 +197,7 @@ def test_make_rst_docstring(): ) # special header, string test = DocSection("to do", ["Example 1, something.", "Example 2."]) - assert test.make_rst_docstring(20, 0, 4) == ( + assert test.make_docstring_rst(20, 0, 4) == ( ".. todo:: Example 1,\n" " something.\n" " Example 2.\n\n" ) From 0396e573860ac39177221100ef0dd20b36f0e3a9 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 22:57:24 +0200 Subject: [PATCH 07/15] Remove abstract __init__ from DocContent 1. Remove DocContent.__init__ 2. Create Empty class for testing in the module rather than in test functions. It seems that an abstract __init__ doesn't really do much except for preventing any initialization. Though it does serve a purpose, it seems to complicate the code more than it adds anything --- src/docinstance/content/base.py | 11 ----------- tests/test_base.py | 18 ++++-------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/docinstance/content/base.py b/src/docinstance/content/base.py index 0cb0df4..bf10f6c 100644 --- a/src/docinstance/content/base.py +++ b/src/docinstance/content/base.py @@ -20,17 +20,6 @@ class DocContent: """ - def __init__(self): - """Initialize. - - Raises - ------ - NotImplementedError - Always. - - """ - raise NotImplementedError - def __eq__(self, other): """Return True if other is DocContent instance with the same contents. False otherwise. diff --git a/tests/test_base.py b/tests/test_base.py index ca0cc82..6044ebd 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -11,10 +11,10 @@ def __init__(self): pass -def test_base_init(): - """Test DocContent.__init__.""" - with pytest.raises(NotImplementedError): - DocContent() +class Empty: + """Empty class.""" + + pass def test_base_eq(): @@ -27,11 +27,6 @@ def test_base_eq(): test2.y = 2 assert not test1 == test2 - class Empty: - """Empty class.""" - - pass - test2 = Empty() test2.x = 1 assert not test1 == test2 @@ -49,11 +44,6 @@ def test_base_ne(): test2.y = 2 assert test1 != test2 - class Empty: - """Empty class.""" - - pass - test2 = Empty() test2.x = 1 assert test1 != test2 From 71f0b5e510ada475b0dc8f9bb483d33ad79feb18 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Sun, 9 Jun 2019 23:01:39 +0200 Subject: [PATCH 08/15] Remove default style from Docstring 1. Remove `default_style` 2. Format strings 3. Remove docstrings and tests for default_style At the moment, it does not seem necessary to add an attribute does nothing except to store the default value. This may be useful for making some porcelain later, but it is not ncessary at the moment. --- src/docinstance/docstring.py | 32 +++++++++----------------------- tests/test_docstring.py | 9 --------- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/docinstance/docstring.py b/src/docinstance/docstring.py index 9d47b4c..09ba970 100644 --- a/src/docinstance/docstring.py +++ b/src/docinstance/docstring.py @@ -10,27 +10,23 @@ class Docstring: ---------- sections : list of DocSection Sections of the docstring. - default_style : str - Default style of the docstring. Methods ------- - __init__(self, sections, default_style) + __init__(self, sections) Initialize. - make_docstring(self, style='numpy', width=100, indent_level=0, tabsize=4) + make_docstring(self, width=100, indent_level=0, tabsize=4, style='numpy') Return the docstring in the given style. """ - def __init__(self, sections, default_style="numpy"): + def __init__(self, sections): """Initialize. Parameters ---------- sections : {str, list/tuple of str, list/tuple of DocSection} Sections of the docstring. - default_style : {'numpy with signature', 'google', 'rst', 'numpy'} - Style of the docstring. Raises ------ @@ -39,7 +35,6 @@ def __init__(self, sections, default_style="numpy"): instances. ValueError If there are no sections. - If style is not one of 'numpy', 'numpy with signature', 'google', 'rst'. """ if isinstance(sections, (str, DocContent)): @@ -49,8 +44,8 @@ def __init__(self, sections, default_style="numpy"): and all(isinstance(i, (str, DocContent)) for i in sections) ): raise TypeError( - "Sections of the docstring must be provided as a string, list/tuple of " - "strings, or list/tuple of DocContent instances." + "Sections of the docstring must be provided as a string, list/tuple of strings, or " + "list/tuple of DocContent instances." ) # NOTE: should the empty sections be allowed? if not sections: @@ -60,14 +55,8 @@ def __init__(self, sections, default_style="numpy"): for section in sections ] - if default_style not in ["numpy", "numpy with signature", "google", "rst"]: - raise ValueError( - "Default style must be one of 'numpy', 'numpy with signature', " "'google', 'rst'." - ) - self.default_style = default_style - # pylint: disable=R0912 - def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): + def make_docstring(self, width=100, indent_level=0, tabsize=4, style="numpy"): """Return the docstring in the given style. Parameters @@ -81,9 +70,8 @@ def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): tabsize : {int, 4} Number of spaces that corresponds to a tab. Default is 4. - style : {'numpy', 'google', 'rst', 'numpy with signature', None} + style : str Style of the docstring. - Default is the `default_style`. Returns ------- @@ -108,8 +96,6 @@ def make_docstring(self, width=100, indent_level=0, tabsize=4, style=None): line of the docstring (including the triple quotation) or the second line. """ - if style is None: - style = self.default_style # check input if not isinstance(width, int): raise TypeError("Maximum width of the line must be given as an integer.") @@ -211,7 +197,7 @@ def check_section_order(self, style): ] if not allow_other_sections and any(i == default_value for i in order_values): raise ValueError( - "For the docstring style, {0}, the headings of the sections must be " - "one of {1}".format(style, list(ordering.keys())) + "For the docstring style, {0}, the headings of the sections must be one of {1}." + "".format(style, list(ordering.keys())) ) return all(i <= j for i, j in zip(order_values, order_values[1:])) diff --git a/tests/test_docstring.py b/tests/test_docstring.py index bab63fb..1b12e32 100644 --- a/tests/test_docstring.py +++ b/tests/test_docstring.py @@ -17,10 +17,6 @@ def test_init(): Docstring(["1", 1]) with pytest.raises(ValueError): Docstring([]) - with pytest.raises(ValueError): - Docstring("1", "nothing") - with pytest.raises(ValueError): - Docstring("1", None) test = Docstring("some text") assert isinstance(test.sections, list) @@ -46,11 +42,6 @@ def test_init(): assert test.sections[1].header == "" assert test.sections[1].contents == ["some text"] - test = Docstring("some text", "numpy") - assert test.default_style == "numpy" - test = Docstring("some text", "rst") - assert test.default_style == "rst" - def test_make_docstring(): """Test Docstring.make_docstring.""" From d25cd5729d805ca4e0d87ed7f78a53183aa2af39 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Mon, 10 Jun 2019 13:35:50 +0200 Subject: [PATCH 09/15] WIP: Add napoleon parsers for google and numpy formats 1. Tweak napoleon's parsers to return docinstance compatible objects 2. Add NumpyDoc parser for see also section. Copied from napoleon's copy 3. Add new description for entries of see also section 4. Add numpy's special docstring for see also 5. Add test 6. Skip tests for napoleon 7. Add extra installations for napoleon Time: 7h --- setup.py | 4 + src/docinstance/parser/napoleon.py | 326 +++++++++++++++++++++++++++++ tests/test_napoleon.py | 148 +++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 src/docinstance/parser/napoleon.py create mode 100644 tests/test_napoleon.py diff --git a/setup.py b/setup.py index 2531bfd..12d980e 100644 --- a/setup.py +++ b/setup.py @@ -21,4 +21,8 @@ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", ], + extras_require={ + "napoleon": ["sphinxcontrib-napoleon"], + "test": ["tox", "pytest", "pytest-cov"], + }, ) diff --git a/src/docinstance/parser/napoleon.py b/src/docinstance/parser/napoleon.py new file mode 100644 index 0000000..48c618b --- /dev/null +++ b/src/docinstance/parser/napoleon.py @@ -0,0 +1,326 @@ +"""Napoleon parser.""" +from docinstance.content.description import DocDescription, DocParagraph +from docinstance.content.section import ( # pylint: disable=E0611 + Attributes, + DocSection, + Examples, + ExtendedSummary, + Methods, + Notes, + OtherParameters, + Parameters, + Raises, + References, + Returns, + SeeAlso, + Summary, + Warnings, + Warns, + Yields, +) +from docinstance.docstring import Docstring +from docinstance.utils import wrap, wrap_indent_subsequent +from sphinxcontrib.napoleon.docstring import GoogleDocstring, NumpyDocstring, _directive_regex + + +class GoogleDocstringDocinstance(GoogleDocstring): + def _parse_attributes_section(self, section): + fields = self._consume_fields() + return [ + Attributes( + [ + DocDescription(field[0], types=field[1] if field[1] else None, descs=field[2]) + for field in fields + ] + ) + ] + + def _parse_keyword_arguments_section(self, section): + fields = self._consume_fields() + return [ + DocSection( + "keyword arguments", + [ + DocDescription(field[0], types=field[1] if field[1] else None, descs=field[2]) + for field in fields + ], + ) + ] + + def _parse_other_parameters_section(self, section): + fields = self._consume_fields() + return [ + OtherParameters( + [ + DocDescription(field[0], types=field[1] if field[1] else None, descs=field[2]) + for field in fields + ] + ) + ] + + def _parse_parameters_section(self, section): + fields = self._consume_fields() + return [ + Parameters( + [ + DocDescription(field[0], types=field[1] if field[1] else None, descs=field[2]) + for field in fields + ] + ) + ] + + def _parse_returns_section(self, section): + fields = self._consume_returns_section() + return [ + Returns( + [ + DocDescription(field[1], descs=field[2]) + if field[0] == "" + else DocDescription(field[0], types=field[1], descs=field[2]) + for field in fields + ] + ) + ] + + def _parse_yields_section(self, section): + fields = self._consume_returns_section() + return [ + Yields( + [ + DocDescription(field[1], descs=field[2]) + if field[0] == "" + else DocDescription(field[0], types=field[1], descs=field[2]) + for field in fields + ] + ) + ] + + def _parse_methods_section(self, section): + fields = self._consume_fields(parse_type=False) + return [Methods([DocDescription(field[0], descs=field[2]) for field in fields])] + + def _parse_raises_section(self, section): + fields = self._consume_fields(parse_type=False, prefer_type=True) + return [Raises([DocDescription(field[1], descs=field[2]) for field in fields])] + + def _parse_see_also_section(self, section): + # type (unicode) -> List[unicode] + lines = self._consume_to_next_section() + return [SeeAlso(lines)] + + def _parse_warns_section(self, section): + fields = self._consume_fields() + return [ + Warns( + [ + DocDescription(field[0], types=field[1] if field[1] else None, descs=field[2]) + for field in fields + ] + ) + ] + + def _parse_generic_section(self, section, use_admonition): + lines = self._strip_empty(self._consume_to_next_section()) + lines = self._dedent(lines) + if use_admonition: + lines = self._indent(lines, 3) + return [DocSection(section, [DocParagraph(line, num_newlines_end=1) for line in lines])] + + def _parse_examples_section(self, section): + labels = {"example": "Example", "examples": "Examples"} + use_admonition = self._config.napoleon_use_admonition_for_examples + label = labels.get(section.lower(), section) + return self._parse_generic_section(label, use_admonition) + + def _parse_notes_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_notes + return self._parse_generic_section("Notes", use_admonition) + + def _parse_references_section(self, section): + use_admonition = self._config.napoleon_use_admonition_for_references + return self._parse_generic_section("References", use_admonition) + + +class NumpyDocstringDocinstance(NumpyDocstring, GoogleDocstringDocinstance): + # _parse_admonition = GoogleDocstringDocinstance._parse_admonition + # _parse_generic_section = GoogleDocstringDocinstance._parse_generic_section + # _parse_attributes_section = GoogleDocstringDocinstance._parse_attributes_section + # _parse_examples_section = GoogleDocstringDocinstance._parse_examples_section + # _parse_keyword_arguments_section = GoogleDocstringDocinstance._parse_keyword_arguments_section + # _parse_methods_section = GoogleDocstringDocinstance._parse_methods_section + # _parse_notes_section = GoogleDocstringDocinstance._parse_notes_section + # _parse_other_parameters_section = GoogleDocstringDocinstance._parse_other_parameters_section + # _parse_parameters_section = GoogleDocstringDocinstance._parse_parameters_section + # _parse_returns_section = GoogleDocstringDocinstance._parse_returns_section + # _parse_raises_section = GoogleDocstringDocinstance._parse_raises_section + # _parse_references_section = GoogleDocstringDocinstance._parse_references_section + # _parse_see_also_section = GoogleDocstringDocinstance._parse_see_also_section + # _parse_warns_section = GoogleDocstringDocinstance._parse_warns_section + # _parse_yields_section = GoogleDocstringDocinstance._parse_yields_section + def _parse_see_also_section(self, section): + # type: (unicode) -> List[unicode] + lines = self._consume_to_next_section() + try: + return self._parse_numpydoc_see_also_section(lines) + except ValueError: + lines = self._strip_empty(lines) + lines = self._dedent(lines) + return [SeeAlso(lines)] + + def _parse_numpydoc_see_also_section(self, content): # type: (List[unicode]) -> List[unicode] + """ + Derived from the NumpyDoc implementation of _parse_see_also. + + See Also + -------- + func_name : Descriptive text + continued text + another_func_name : Descriptive text + func_name1, func_name2, :meth:`func_name`, func_name3 + + """ + items = [] + + def parse_item_name(text): + # type: (unicode) -> Tuple[unicode, unicode] + """Match ':role:`name`' or 'name'""" + m = self._name_rgx.match(text) # type: ignore + if m: + g = m.groups() + if g[1] is None: + return g[3], None + else: + return g[2], g[1] + raise ValueError("%s is not a item name" % text) + + def push_item(name, rest): + # type: (unicode, List[unicode]) -> None + if not name: + return + name, role = parse_item_name(name) + items.append((name, list(rest), role)) + del rest[:] + + current_func = None + rest = [] # type: List[unicode] + + for line in content: + if not line.strip(): + continue + + m = self._name_rgx.match(line) # type: ignore + if m and line[m.end() :].strip().startswith(":"): + push_item(current_func, rest) + current_func, line = line[: m.end()], line[m.end() :] + rest = [line.split(":", 1)[1].strip()] + if not rest[0]: + rest = [] + elif not line.startswith(" "): + push_item(current_func, rest) + current_func = None + if "," in line: + for func in line.split(","): + if func.strip(): + push_item(func, []) + elif line.strip(): + current_func = line + elif current_func is not None: + rest.append(line.strip()) + push_item(current_func, rest) + + if not items: + return [] + + roles = { + "method": "meth", + "meth": "meth", + "function": "func", + "func": "func", + "class": "class", + "exception": "exc", + "exc": "exc", + "object": "obj", + "obj": "obj", + "module": "mod", + "mod": "mod", + "data": "data", + "constant": "const", + "const": "const", + "attribute": "attr", + "attr": "attr", + } # type: Dict[unicode, unicode] + if self._what is None: + func_role = "obj" # type: unicode + else: + func_role = roles.get(self._what, "") + lines = [] # type: List[unicode] + for func, desc, role in items: + if not role and func_role: + role = func_role + + lines.append(DocSeeAlsoEntryNumpy(func, role, desc)) + + return [SeeAlso(lines)] + + +class DocSeeAlsoEntryNumpy(DocDescription): + def __init__(self, name, role, desc): + super().__init__(name, types=role, descs=desc) + if len(self.descs) > 1: + raise ValueError + if len(self.types) > 1: + raise ValueError + + def make_docstring_numpy(self, width, indent_level, tabsize): + if not self.descs: + lines = wrap(self.name, width=width, indent_level=indent_level, tabsize=tabsize) + elif self.types: + lines = wrap( + "{0} : {1}".format(self.name, self.descs[0]), + width=width, + indent_level=indent_level, + tabsize=tabsize, + ) + return "\n".join(lines) + "\n" + + def make_docstring_rst(self, width, indent_level, tabsize): + role = self.types + func = self.name + desc = self.descs[0] + + if role: + link = ":%s:`%s`".format(role, func) + else: + link = "`%s`_".format(func) + + output = "\n".join(wrap(link, width=width, indent_level=indent_level, tabsize=tabsize)) + output += "\n" + output += "\n".join(wrap(desc[0], width=width, indent_level=indent_level, tabsize=tabsize)) + output += "\n" + + return output + + +def parse_numpy(docstring, contains_quotes=False): + """Parse a docstring in Numpy format using Napoleon into Docstring instance.""" + parsed_content = [i for i in NumpyDocstringDocinstance(docstring)._parsed_lines if i] + if isinstance(parsed_content[0], str): + parsed_content[0] = Summary(parsed_content[0]) + # check for any other strings + string_contents = [True if isinstance(i, str) else False for i in parsed_content] + if any(string_contents): + start_ind = string_contents.index(True) + end_ind = len(string_contents) - string_contents[::-1].index(True) - 1 + if not all(string_contents[start_ind : end_ind + 1]): + raise ValueError + parsed_content = ( + parsed_content[:start_ind] + + [ExtendedSummary(parsed_content[start_ind : end_ind + 1])] + + parsed_content[end_ind + 1 :] + ) + return Docstring(parsed_content) + + +def parse_google(docstring, contains_quotes=False): + """Parse a docstring in Google format using Napoleon into Docstring instance.""" + return GoogleDocstringDocinstance(docstring)._parsed_lines diff --git a/tests/test_napoleon.py b/tests/test_napoleon.py new file mode 100644 index 0000000..95fa96d --- /dev/null +++ b/tests/test_napoleon.py @@ -0,0 +1,148 @@ +"""Test docinstance.parser.napoleon.""" +from docinstance.parser.numpy import parse_numpy as parse_ref_numpy +import pytest + +pytest.importorskip("sphinxcontrib.napoleon") +from docinstance.parser.napoleon import parse_numpy + + +def test_parse_numpy(): + """Tests docinstance.numpy.parse_numpy.""" + # summary + docstring = "summary" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = "summary\n" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = "\nsummary\n" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = "\n\nsummary\n" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + + # extended + docstring = "summary\n\nblock1\n\nblock2" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = "\nsummary\n\nblock1\n\nblock2" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = "\nsummary\n\n\n\nblock2\n\n" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = "\n\nsummary\n\nblock1\n\nblock2" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + # extended + permitted headers + docstring = "summary\n\nblock1\n\nblock2\n\nParameters\n----------\nstuff" + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + + for header in ["parameters", "attributes", "other parameters", "returns", "yields"]: + # name + multiple descriptions + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + # NOTE: in the returns section, if only the name is given, napoleon treats it as a type. + # However, DocDescription requires that the name be provided. So the type is stored as a + # name instead to make it compatible with the existing writer. + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + # name + types + multiple descriptions + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : {{str, int}}\n" + " description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + # NOTE: multiple types are not separated and are treated in bulk in napoleon + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy") + # name + types + docstring = "summary\n\n{0}\n{1}\nabc: str\ndef: int".format( + header.title(), "-" * len(header) + ) + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + + for header in ["methods", "raises"]: + # name + multiple descriptions + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + # name + types + multiple descriptions + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy") + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc : {{str, int}}\n" + " description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + # assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy") + # name + signature + multiple descriptions + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc(x, y)\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + # assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy_signature") + # name + types + signature + multiple descriptions + docstring = ( + "summary\n\nblock1\n\nblock2\n\n{0}\n{1}\nabc(x, y) : str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + # assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy_signature") + # name + types + signature + multiple descriptions - extended summary + docstring = ( + "summary\n\n{0}\n{1}\nabc(x, y) : str\n description1.\n" + " description2.".format(header.title(), "-" * len(header)) + ) + # assert parse_numpy(docstring).__dict__ == parse_ref_numpy(docstring).__dict__ + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy_signature") + # name + types + docstring = "summary\n\n{0}\n{1}\nabc : str\ndef : int".format( + header.title(), "-" * len(header) + ) + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy_signature") + + # See also + docstring = ( + "summary\n\nblock1\n\nblock2\n\nSee Also\n--------\nsome_place.\n" "some_other_place." + ) + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy") + # NOTE: napoleon cuts off the see also past the first word so the following docstring will have + # entries "some" and "some" for napoleon. + # docstring = ( + # "summary\n\nblock1\n\nblock2\n\nSee Also\n--------\nsome place.\n" + # "some other place." + # ) + + # References + docstring = "summary\n\nblock1\n\nblock2\nReferences\n----------\nsome ref.\n" "some other ref." + assert parse_numpy(docstring).make_docstring(style="numpy") == parse_ref_numpy( + docstring + ).make_docstring(style="numpy") + # NOTE: if references are separated by two newlines, then the docinstances' parser removes the + # newline, where as napoleon's keeps it + # docstring = ( + # "summary\n\nblock1\n\nblock2\nReferences\n----------\nsome ref.\n\n" + # "some other ref." + # ) From d325c216cb17761aa648dae61dc2031b7ec31445 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Tue, 11 Jun 2019 23:38:15 +0200 Subject: [PATCH 10/15] Add class for paragraphs Before, Docstrings/DocSection could handle strings by treating them specially when the docstring was being created. Normally, blocks of strings were separately from one another with two newlines wrapped to the appropriate weight and indented according to user input. However, it seems that sometimes (e.g. in References or See Also sections) it is necessary to separate out the string blocks with different number of newlines. Instead of adding a new parameter, a new class for the string blocks were created, to be consistent with the rest of the code. 1. Add DocParagraph class 2. Add tests Time: 2h --- src/docinstance/content/description.py | 101 ++++++++++++++++++++++ tests/test_description.py | 115 ++++++++++++++++++++++++- 2 files changed, 215 insertions(+), 1 deletion(-) diff --git a/src/docinstance/content/description.py b/src/docinstance/content/description.py index 7effc38..fedf686 100644 --- a/src/docinstance/content/description.py +++ b/src/docinstance/content/description.py @@ -387,3 +387,104 @@ def make_docstring_rst(self, width, indent_level, tabsize): output += "\n".join(block) output += "\n" return output + + +class DocParagraph(DocContent): + """Sentences(strings) that are used within the docstring with no special structure.""" + + def __init__(self, paragraph, num_newlines_end=2): + """Initialize the object. + + Parameters + ---------- + paragraph : str + Sentences without special structure. + num_newlines_end : int + Number of newlines at the end of the paragraph. + + Raises + ------ + TypeError + If `paragraph` is not a string. + If `num_newlines_end` is not an integer. + ValueError + If `num_newlines_end` is not greater than 0. + + """ + if not isinstance(paragraph, str): + raise TypeError("`paragraph` must be a string.") + self.paragraph = paragraph + + if not isinstance(num_newlines_end, int): + raise TypeError("`num_newlines_end` must be given as an integer.") + if num_newlines_end <= 0: + raise ValueError("`num_newlines_end` must be greater than zero.") + self.num_newlines_end = num_newlines_end + + def make_docstring(self, width, indent_level, tabsize, style, **kwargs): + """Return the docstring in numpy style. + + Parameters + ---------- + width : int + Maximum number of characters allowed in a line. + indent_level : int + Number of indents (tabs) that are needed for the docstring. + tabsize : int + Number of spaces that corresponds to a tab. + style : str + Name of the docstring style. + + Returns + ------- + section_docstring : str + Docstring of the given section in numpy style. + + Raises + ------ + ValueError + If the title is too long for the given width and indentation. + + """ + # contents + try: + return super().make_docstring(width, indent_level, tabsize, style, **kwargs) + except NotImplementedError: + output = "\n".join( + wrap(self.paragraph, width=width, indent_level=indent_level, tabsize=tabsize) + ) + output += "\n" * self.num_newlines_end + return output + + def make_docstring_rst(self, width, indent_level, tabsize, header=""): + """Return the docstring in rst style. + + Parameters + ---------- + width : int + Maximum number of characters allowed in a line. + indent_level : int + Number of indents (tabs) that are needed for the docstring. + tabsize : int + Number of spaces that corresponds to a tab. + + Returns + ------- + section_docstring : str + Docstring of the given section in rst style. + + Raises + ------ + ValueError + If the title is too long for the given width and indentation. + + """ + if header == "": + lines = wrap(self.paragraph, width=width, indent_level=indent_level, tabsize=tabsize) + else: + lines = wrap_indent_subsequent( + header + self.paragraph, width=width, indent_level=indent_level + 1, tabsize=tabsize + ) + output = "\n".join(lines) + output += "\n" * self.num_newlines_end + return output diff --git a/tests/test_description.py b/tests/test_description.py index 7dfa0d1..0757753 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,5 +1,5 @@ """Test docinstance.content.description.""" -from docinstance.content.description import DocDescription +from docinstance.content.description import DocDescription, DocParagraph import pytest @@ -224,3 +224,116 @@ def test_make_docstring_rst(): ":type var_name: str,\n" " :obj:`int`\n" ) + + +def test_docparagraph_init(): + """Test DocParagraph.__init__.""" + # check type of paragraph + with pytest.raises(TypeError): + DocParagraph(1) + with pytest.raises(TypeError): + DocParagraph(["hello"]) + # check type of type of num_newlines_end + with pytest.raises(TypeError): + DocParagraph("hello", 1.0) + with pytest.raises(TypeError): + DocParagraph("hello", "1") + # check value of type of num_newlines_end + with pytest.raises(ValueError): + DocParagraph("hello", 0) + with pytest.raises(ValueError): + DocParagraph("hello", -2) + # check attributes + test = DocParagraph("hello", 3) + assert test.paragraph == "hello" + assert test.num_newlines_end == 3 + + +def test_docparagraph_make_docstring(): + """Test DocParagraph.make_docstring.""" + # google style + test = DocParagraph("sometext somewhere somehow") + assert test.make_docstring(9, 0, 4, "google") == "sometext\nsomewhere\nsomehow\n\n" + # different number of lines at the end + test = DocParagraph("sometext somewhere somehow", num_newlines_end=1) + assert test.make_docstring(9, 0, 4, "google") == "sometext\nsomewhere\nsomehow\n" + test = DocParagraph("sometext somewhere somehow", num_newlines_end=3) + assert test.make_docstring(9, 0, 4, "google") == "sometext\nsomewhere\nsomehow\n\n\n" + # numpy style + test = DocParagraph("sometext somewhere somehow") + assert test.make_docstring(9, 0, 4, "numpy") == "sometext\nsomewhere\nsomehow\n\n" + # undefined style + assert test.make_docstring(9, 0, 4, "badstyle") == "sometext\nsomewhere\nsomehow\n\n" + + +def test_docparagraph_make_docstring_rst(): + """Test DocParagraph.make_docstring_rst.""" + test = DocParagraph("sometext somewhere somehow") + assert test.make_docstring_rst(13, 0, 4, "") == "sometext\nsomewhere\nsomehow\n\n" + assert test.make_docstring_rst(13, 0, 2, "") == "sometext\nsomewhere\nsomehow\n\n" + assert test.make_docstring_rst(13, 1, 4, "") == " sometext\n somewhere\n somehow\n\n" + assert test.make_docstring_rst(13, 1, 2, "") == " sometext\n somewhere\n somehow\n\n" + + test = DocParagraph("some text somewhere somehow") + assert ( + test.make_docstring_rst(13, 0, 2, "1234567 ") + == "1234567 some\n text\n somewhere\n somehow\n\n" + ) + test = DocParagraph("some text somewhere somehow", num_newlines_end=1) + assert ( + test.make_docstring_rst(13, 0, 2, "1234567 ") + == "1234567 some\n text\n somewhere\n somehow\n" + ) + test = DocParagraph("some text somewhere somehow", num_newlines_end=1) + assert ( + test.make_docstring_rst(13, 0, 2, "123456789 ") + == "123456789\n some text\n somewhere\n somehow\n" + ) + + assert test.make_docstring_rst(13, 1, 2, "") == " some text\n somewhere\n somehow\n" + assert test.make_docstring_rst(13, 1, 2, "1") == "1some text\n somewhere\n somehow\n" + assert test.make_docstring_rst(13, 1, 2, "12") == "12some text\n somewhere\n somehow\n" + assert test.make_docstring_rst(13, 1, 2, "123") == "123some text\n somewhere\n somehow\n" + assert ( + test.make_docstring_rst(13, 1, 2, "1234") == "1234some text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "12345") + == "12345some\n text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "123456") + == "123456some\n text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "1234567") + == "1234567some\n text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "12345678") + == "12345678some\n text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "123456789") + == "123456789some\n text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "123456789 ") + == "123456789\n some text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "1234567890 ") + == "1234567890\n some text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "12345678901 ") + == "12345678901\n some text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "123456789012 ") + == "123456789012\n some text\n somewhere\n somehow\n" + ) + assert ( + test.make_docstring_rst(13, 1, 2, "1234567890123 ") + == "1234567890123\n some text\n somewhere\n somehow\n" + ) From 3182ad3aa665a06f82e5b9c8d39634f5ee760549 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Wed, 12 Jun 2019 09:53:16 +0200 Subject: [PATCH 11/15] Replace strings with DocParagraph in DocSection 1. Remove support for string content in DocSection 2. Replace strings in test with DocParagraph 3. Remove string formatting inside make_docstring's Time: 2h --- src/docinstance/content/section.py | 117 +++++++++-------------------- tests/test_section.py | 69 ++++++++++------- 2 files changed, 76 insertions(+), 110 deletions(-) diff --git a/src/docinstance/content/section.py b/src/docinstance/content/section.py index 96dea5e..f603564 100644 --- a/src/docinstance/content/section.py +++ b/src/docinstance/content/section.py @@ -1,7 +1,7 @@ """Class for representing a section in the docstring.""" from docinstance.content.base import DocContent -from docinstance.content.description import DocDescription -from docinstance.utils import wrap, wrap_indent_subsequent +from docinstance.content.description import DocDescription, DocParagraph +from docinstance.utils import wrap class DocSection(DocContent): @@ -59,7 +59,7 @@ def __init__(self, header, contents): raise TypeError("The parameter `header` must be a string.") self.header = header - if isinstance(contents, (str, DocContent)): + if isinstance(contents, DocContent): contents = [contents] # NOTE: is it really necessary to prevent contents of a section from mixing # strings/DocContent and DocDescription? @@ -67,7 +67,7 @@ def __init__(self, header, contents): isinstance(contents, (tuple, list)) and ( all( - isinstance(content, (str, DocContent)) + isinstance(content, DocContent) and not isinstance(content, DocDescription) for content in contents ) @@ -79,6 +79,8 @@ def __init__(self, header, contents): "DocDescription, or a list/tuple of strings/DocContent (not " "DocDescription)." ) + + # TODO: convert strings to DocPargraph? string sare currently not supported self.contents = list(contents) # pylint: disable=W0221 @@ -124,24 +126,11 @@ def make_docstring_numpy(self, width, indent_level, tabsize, include_signature=F output += "{0}\n{1}\n".format(title[0], divider[0]) # contents for paragraph in self.contents: - # NOTE: since the contents are checked in the initialization, we will assume that the - # paragraph can only be string or DocDescription - if isinstance(paragraph, str): - output += "\n".join( - wrap(paragraph, width=width, indent_level=indent_level, tabsize=tabsize) - ) - output += "\n\n" - # if isinstance(paragraph, DocContent) - elif include_signature and hasattr(paragraph, "make_docstring_numpy_signature"): - output += paragraph.make_docstring_numpy_signature(width, indent_level, tabsize) + if include_signature and hasattr(paragraph, "make_docstring_numpy_signature"): + output += paragraph.make_docstring(width, indent_level, tabsize, "numpy_signature") else: - output += paragraph.make_docstring_numpy(width, indent_level, tabsize) - # pylint: disable=W0120 - # following block clause should always be executed - else: - # end a section with two newlines (note that the section already ends with a newline if - # it ends with a paragraph) - output += "\n" * isinstance(paragraph, DocDescription) + output += paragraph.make_docstring(width, indent_level, tabsize, "numpy") + output += "\n" * (2 - output[-2:].count("\n")) return output @@ -212,22 +201,8 @@ def make_docstring_google(self, width, indent_level, tabsize): indent_level -= 1 # contents for paragraph in self.contents: - # NOTE: since the contents are checked in the initialization, we will assume that the - # paragraph can only be string or DocDescription - if isinstance(paragraph, str): - output += "\n".join( - wrap(paragraph, width=width, indent_level=indent_level + 1, tabsize=tabsize) - ) - output += "\n\n" - # if isinstance(paragraph, DocContent) - else: - output += paragraph.make_docstring_google(width, indent_level + 1, tabsize) - # pylint: disable=W0120 - # following block clause should always be executed - else: - # end a section with two newlines (note that the section already ends with a newline if - # it ends with a paragraph) - output += "\n" * isinstance(paragraph, DocDescription) + output += paragraph.make_docstring(width, indent_level + 1, tabsize, "google") + output += "\n" * (2 - output[-2:].count("\n")) return output @@ -263,51 +238,31 @@ def make_docstring_rst(self, width, indent_level, tabsize): if self.header.lower() in special_headers: header = ".. {0}::".format(special_headers[self.header.lower()]) - elif self.header != "": - output += ":{0}:\n\n".format(self.header.title()) - - for i, paragraph in enumerate(self.contents): - # first content must be treated with care for special headers - if i == 0 and self.header.lower() in special_headers: - # str - if isinstance(paragraph, str): - text = "{0} {1}".format(header, paragraph) - # FIXME: following can probably be replaced with a better wrapping function - first_content = [ - " " * indent_level * tabsize + line - for line in wrap_indent_subsequent( - text, - width=width - indent_level * tabsize, - indent_level=indent_level + 1, - tabsize=tabsize, - ) - ] - output += "\n".join(first_content) - output += "\n" - # DocContent - else: - output += header - output += "\n" - output += paragraph.make_docstring_rst( - width=width, indent_level=indent_level + 1, tabsize=tabsize - ) - # indent all susequent content - indent_level += 1 - elif isinstance(paragraph, str): - output += "\n".join( - wrap(paragraph, width=width, indent_level=indent_level, tabsize=tabsize) + # it seems that this special case, e.g. ".. seealso:: blah blah blah\n blah", has + # some fancy indentation (first line is not indented, but the subsequent lines are) + # This case seems to be the only special case,so it is treated separately to get + # it out of the way + if isinstance(self.contents[0], DocParagraph): + output += self.contents[0].make_docstring_rst( + width, indent_level, tabsize, header=header + " " ) - # NOTE: the second newline may cause problems (because the field might not - # recognize text that is more than one newline away) - output += "\n\n" - else: - output += paragraph.make_docstring_rst(width, indent_level, tabsize) - # pylint: disable=W0120 - # following block clause should always be executed - else: - # end a section with two newlines (note that the section already ends with a newline - # if it ends with a paragraph) - output += "\n" * isinstance(paragraph, DocDescription) + for paragraph in self.contents[1:]: + output += paragraph.make_docstring_rst(width, indent_level + 1, tabsize) + output += "\n" * (2 - output[-2:].count("\n")) + return output + + output += header + output += "\n" + indent_level += 1 + elif self.header != "": + header = ":{0}:".format(self.header.title()) + output += header + output += "\n\n" + + output += self.contents[0].make_docstring_rst(width, indent_level, tabsize) + for paragraph in self.contents[1:]: + output += paragraph.make_docstring_rst(width, indent_level, tabsize) + output += "\n" * (2 - output[-2:].count("\n")) return output diff --git a/tests/test_section.py b/tests/test_section.py index e961cf9..a1b183c 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -1,5 +1,5 @@ """Test docinstance.content.section.""" -from docinstance.content.description import DocDescription +from docinstance.content.description import DocDescription, DocParagraph from docinstance.content.section import ( Attributes, DocSection, @@ -36,12 +36,21 @@ def test_init(): with pytest.raises(TypeError): DocSection("1", [DocDescription("test"), "1"]) - test = DocSection("header name", "hello") + with pytest.raises(TypeError): + DocSection("header name", "hello") + with pytest.raises(TypeError): + DocSection("header name", ["hello", "i am"]) + DocSection("header name", ["hello", "i am"]) + doc = DocParagraph("hello") + test = DocSection("header name", DocParagraph("hello")) assert test.header == "header name" - assert test.contents == ["hello"] - test = DocSection("header name", ["hello", "i am"]) + assert test.contents == [doc] + doc1 = DocParagraph("hello") + doc2 = DocParagraph("hello") + test = DocSection("header name", [doc1, doc2]) assert test.header == "header name" - assert test.contents == ["hello", "i am"] + assert test.contents == [doc1, doc2] + doc1 = DocDescription("hello") doc2 = DocDescription("i am") test = DocSection("header name", doc1) @@ -55,11 +64,11 @@ def test_init(): def test_make_docstring_numpy(): """Test DocSection.make_docstring_numpy.""" # string content - test = DocSection("header name", "hello") + test = DocSection("header name", DocParagraph("hello")) assert test.make_docstring_numpy(11, 0, 4) == "Header Name\n-----------\nhello\n\n" assert test.make_docstring_numpy(11, 0, 4) == "Header Name\n-----------\nhello\n\n" # multiple string contents - test = DocSection("header name", ["hello", "i am"]) + test = DocSection("header name", [DocParagraph("hello"), DocParagraph("i am")]) assert test.make_docstring_numpy(11, 0, 4) == "Header Name\n-----------\nhello\n\ni am\n\n" # doc description test = DocSection("header name", DocDescription("var_name", types=str, descs="Example.")) @@ -104,10 +113,10 @@ def test_make_docstring_numpy_signature(): def test_make_docstring_google(): """Test DocSection.make_docstring_google.""" with pytest.raises(ValueError): - test = DocSection("quite long header name", "") + test = DocSection("quite long header name", DocParagraph("")) test.make_docstring_google(10, 0, 4) # no header - test = DocSection("", "Some text.") + test = DocSection("", DocParagraph("Some text.")) assert test.make_docstring_google(10, 0, 4) == ("Some text.\n\n") # one docdescription test = DocSection( @@ -128,11 +137,11 @@ def test_make_docstring_google(): "Header Name:\n" " var1 (:obj:`str`): Example1.\n" " var2 (int): Example2.\n\n" ) # one string - test = DocSection("header name", "Some text.") + test = DocSection("header name", DocParagraph("Some text.")) assert test.make_docstring_google(14, 0, 4) == ("Header Name:\n" " Some text.\n\n") assert test.make_docstring_google(13, 0, 4) == ("Header Name:\n" " Some\n" " text.\n\n") # multiple string - test = DocSection("header name", ["Some text.", "Another text."]) + test = DocSection("header name", [DocParagraph("Some text."), DocParagraph("Another text.")]) assert test.make_docstring_google(17, 0, 4) == ( "Header Name:\n" " Some text.\n\n" " Another text.\n\n" ) @@ -147,14 +156,14 @@ def test_make_docstring_google(): def test_make_docstring_rst(): """Test DocSection.make_docstring_rst.""" # no header - test = DocSection("", "Some text.") + test = DocSection("", DocParagraph("Some text.")) assert test.make_docstring_rst(10, 0, 4) == ("Some text.\n\n") # normal header, one docdescription test = DocSection( "header name", DocDescription("var_name", signature="(a, b)", types=str, descs="Example.") ) assert test.make_docstring_rst(35, 0, 4) == ( - ":Header Name:\n\n" ":param var_name: Example.\n" ":type var_name: :obj:`str`\n\n" + ":Header Name:\n\n:param var_name: Example.\n:type var_name: :obj:`str`\n\n" ) # normal header, multiple docdescription test = DocSection( @@ -172,10 +181,10 @@ def test_make_docstring_rst(): ":type var2: int\n\n" ) # normal header, one string - test = DocSection("header name", "Some text.") - assert test.make_docstring_rst(13, 0, 4) == (":Header Name:\n\n" "Some text.\n\n") + test = DocSection("header name", DocParagraph("Some text.")) + assert test.make_docstring_rst(13, 0, 4) == (":Header Name:\n\nSome text.\n\n") # normal header, multiple string - test = DocSection("header name", ["Some text.", "Another text."]) + test = DocSection("header name", [DocParagraph("Some text."), DocParagraph("Another text.")]) assert test.make_docstring_rst(13, 0, 4) == ( ":Header Name:\n\n" "Some text.\n\n" "Another text.\n\n" ) @@ -196,7 +205,9 @@ def test_make_docstring_rst(): " :type var2: int\n\n" ) # special header, string - test = DocSection("to do", ["Example 1, something.", "Example 2."]) + test = DocSection( + "to do", [DocParagraph("Example 1, something.", 1), DocParagraph("Example 2.", 1)] + ) assert test.make_docstring_rst(20, 0, 4) == ( ".. todo:: Example 1,\n" " something.\n" " Example 2.\n\n" ) @@ -262,9 +273,9 @@ def test_section_summary_make_docstring(): def test_section_extended_summary(): """Test ExtendedSummary.__init__.""" - test = ExtendedSummary("This is an extended summary.") + test = ExtendedSummary(DocParagraph("This is an extended summary.")) assert test.header == "" - assert test.contents == ["This is an extended summary."] + assert test.contents == [DocParagraph("This is an extended summary.")] def test_section_parameters(): @@ -338,34 +349,34 @@ def test_section_warns(): def test_section_warnings(): """Test Warnings.__init__.""" - test = Warnings("Not to be used.") + test = Warnings(DocParagraph("Not to be used.")) assert test.header == "warnings" - assert test.contents == ["Not to be used."] + assert test.contents == [DocParagraph("Not to be used.")] def test_section_seealso(): """Test SeeAlso.__init__.""" - test = SeeAlso("Some other code.") + test = SeeAlso(DocParagraph("Some other code.")) assert test.header == "see also" - assert test.contents == ["Some other code."] + assert test.contents == [DocParagraph("Some other code.")] def test_section_notes(): """Test Notes.__init__.""" - test = Notes("Some comment.") + test = Notes(DocParagraph("Some comment.")) assert test.header == "notes" - assert test.contents == ["Some comment."] + assert test.contents == [DocParagraph("Some comment.")] def test_section_references(): """Test References.__init__.""" - test = References("Some reference.") + test = References(DocParagraph("Some reference.")) assert test.header == "references" - assert test.contents == ["Some reference."] + assert test.contents == [DocParagraph("Some reference.")] def test_section_examples(): """Test Examples.__init__.""" - test = Examples("Some example.") + test = Examples(DocParagraph("Some example.")) assert test.header == "examples" - assert test.contents == ["Some example."] + assert test.contents == [DocParagraph("Some example.")] From 42676d7b0256f686af53c5a95d44877c3b4f0e56 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Wed, 12 Jun 2019 10:29:46 +0200 Subject: [PATCH 12/15] Add keyword arguments to base make_docstring It seems possible that the make_docstring for a specific style (see `DocParagraph.make_docstring_rst`) have keyword arguments outside of those defined in `DocContent.make_docstring`). This commit supports passing the keyword arguments. --- src/docinstance/content/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/docinstance/content/base.py b/src/docinstance/content/base.py index bf10f6c..0f36c8a 100644 --- a/src/docinstance/content/base.py +++ b/src/docinstance/content/base.py @@ -57,7 +57,7 @@ def __ne__(self, other): """ return not self == other - def make_docstring(self, width, indent_level, tabsize, style): + def make_docstring(self, width, indent_level, tabsize, style, **kwargs): """Return the docstring as a string in the given style. Parameters @@ -70,6 +70,8 @@ def make_docstring(self, width, indent_level, tabsize, style): Number of spaces that corresponds to a tab. style : str Name of the docstring style. + kwargs : dict + Other keyword arguments that will be passed onto the docstring maker of the given style. Returns ------- @@ -88,7 +90,7 @@ def make_docstring(self, width, indent_level, tabsize, style): raise TypeError("The `style` of the docstring must be given as a non-empty string.") method_name = "make_docstring_{}".format(style) if hasattr(self, method_name): - return getattr(self, method_name)(width, indent_level, tabsize) + return getattr(self, method_name)(width, indent_level, tabsize, **kwargs) raise NotImplementedError( "To make a docstring of style, {}, the given instance of {} must have a method called " "{} with arguments (width, indent_level, tabsize).".format( From f83bc61563b82c57fbede0f624177980456d4961 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Wed, 12 Jun 2019 11:12:49 +0200 Subject: [PATCH 13/15] Add __eq__ and __ne__ methods to DocParagraph Since DocParagraph is used interchangeably with str, it would be nice to compare theses objects directly to string rather than instances of DocParagraph Time: 0.5h --- src/docinstance/content/description.py | 44 ++++++++++++++++++++++++++ tests/test_description.py | 28 ++++++++++++++++ tests/test_section.py | 17 +++------- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/docinstance/content/description.py b/src/docinstance/content/description.py index fedf686..1608d02 100644 --- a/src/docinstance/content/description.py +++ b/src/docinstance/content/description.py @@ -421,6 +421,50 @@ def __init__(self, paragraph, num_newlines_end=2): raise ValueError("`num_newlines_end` must be greater than zero.") self.num_newlines_end = num_newlines_end + def __eq__(self, other): + """Check if other is DocParagraph instance or string with the same contents. + + Parameters + ---------- + other : {DocContent, str} + + + Returns + ------- + bool + True if `self` has the same content as `other`. + False otherwise. + + """ + if isinstance(other, str): + return self.paragraph == other + return isinstance(other, DocContent) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Check if other is DocContent instance or string with different contents. + + Parameters + ---------- + other : DocContent + + Returns + ------- + bool + True if `self` has different content than `other`. + False otherwise. + + Notes + ----- + The behaviour of __ne__ changed form Python 2 to Python 3. In Python 3, there is a default + behaviour of __ne__ when __eq__ returns NotImplemented, which is the default behaviour for + __eq__. Special care needs to be taken when only __ne__ is defined. However, since we define + the __eq__ here, we don't need to be too careful. See + https://stackoverflow.com/questions/4352244/python-should-i-implement-ne-operator-based-on-eq/50661674#50661674 + for more details. + + """ + return not self == other + def make_docstring(self, width, indent_level, tabsize, style, **kwargs): """Return the docstring in numpy style. diff --git a/tests/test_description.py b/tests/test_description.py index 0757753..44bec79 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -249,6 +249,34 @@ def test_docparagraph_init(): assert test.num_newlines_end == 3 +def test_paragraph_eq(): + """Test DocParagraph.__eq__.""" + test1 = DocParagraph("test") + test1.x = 1 + test2 = DocParagraph("test") + test2.x = 1 + assert test1 == test2 + test2.y = 2 + assert not test1 == test2 + # NOTE: test1 has attribute x that the string does not have + assert test1 == "test" + assert not test1 == "test2" + + +def test_paragraph_ne(): + """Test DocParagraph.__ne__.""" + test1 = DocParagraph("test") + test1.x = 1 + test2 = DocParagraph("test") + test2.x = 1 + assert not test1 != test2 + test2.y = 2 + assert test1 != test2 + # NOTE: test1 has attribute x that the string does not have + assert not test1 != "test" + assert test1 != "test2" + + def test_docparagraph_make_docstring(): """Test DocParagraph.make_docstring.""" # google style diff --git a/tests/test_section.py b/tests/test_section.py index a1b183c..ad18c44 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -36,20 +36,13 @@ def test_init(): with pytest.raises(TypeError): DocSection("1", [DocDescription("test"), "1"]) - with pytest.raises(TypeError): - DocSection("header name", "hello") - with pytest.raises(TypeError): - DocSection("header name", ["hello", "i am"]) - DocSection("header name", ["hello", "i am"]) - doc = DocParagraph("hello") - test = DocSection("header name", DocParagraph("hello")) + test = DocSection("header name", "hello") assert test.header == "header name" - assert test.contents == [doc] - doc1 = DocParagraph("hello") - doc2 = DocParagraph("hello") - test = DocSection("header name", [doc1, doc2]) + assert test.contents == ["hello"] + + test = DocSection("header name", ["hello", "i am"]) assert test.header == "header name" - assert test.contents == [doc1, doc2] + assert test.contents == ["hello", "i am"] doc1 = DocDescription("hello") doc2 = DocDescription("i am") From 240fe2d5219abf7a3b3b7ec04167a5b2ca25e3f8 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Wed, 12 Jun 2019 11:42:59 +0200 Subject: [PATCH 14/15] Support string content in DocSection Turns out removing string support causes a lot of the tests to break, so it would be better to actually support strings in DocSection (and rest of the code). In fact, it is more intuitive to be able to use strings anyways. Internally, DocSection still uses DocParagraph, but upon intialization, strings are converted to DocParagraph --- src/docinstance/content/section.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/docinstance/content/section.py b/src/docinstance/content/section.py index f603564..d0d0f94 100644 --- a/src/docinstance/content/section.py +++ b/src/docinstance/content/section.py @@ -59,7 +59,7 @@ def __init__(self, header, contents): raise TypeError("The parameter `header` must be a string.") self.header = header - if isinstance(contents, DocContent): + if isinstance(contents, (str, DocContent)): contents = [contents] # NOTE: is it really necessary to prevent contents of a section from mixing # strings/DocContent and DocDescription? @@ -67,7 +67,7 @@ def __init__(self, header, contents): isinstance(contents, (tuple, list)) and ( all( - isinstance(content, DocContent) + isinstance(content, (str, DocContent)) and not isinstance(content, DocDescription) for content in contents ) @@ -80,8 +80,7 @@ def __init__(self, header, contents): "DocDescription)." ) - # TODO: convert strings to DocPargraph? string sare currently not supported - self.contents = list(contents) + self.contents = [DocParagraph(i) if isinstance(i, str) else i for i in contents] # pylint: disable=W0221 # the extra argument is used in the make_docstring_numpy_signature From 18f13f1158ea2f135dfc480c0f74dcf904884983 Mon Sep 17 00:00:00 2001 From: "Taewon D. Kim" Date: Wed, 12 Jun 2019 11:45:01 +0200 Subject: [PATCH 15/15] Fix Summary to support strings and DocParagraph 1. Convert internal components of Summary to use Docparagraph instead of string --- src/docinstance/content/section.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/docinstance/content/section.py b/src/docinstance/content/section.py index d0d0f94..1e1754e 100644 --- a/src/docinstance/content/section.py +++ b/src/docinstance/content/section.py @@ -303,9 +303,9 @@ def __init__(self, contents): """ self.header = "" - if not isinstance(contents, str): - raise TypeError("The parameter `contents` must be a string.") - self.contents = [contents] + if not isinstance(contents, (str, DocParagraph)): + raise TypeError("The parameter `contents` must be a string or a DocParagraph instance.") + self.contents = [DocParagraph(contents) if isinstance(contents, str) else contents] def make_docstring(self, width, indent_level, tabsize, summary_only=False, special=False): """Return the docstring for the summary. @@ -339,7 +339,7 @@ def make_docstring(self, width, indent_level, tabsize, summary_only=False, speci """ output = "" - summary = self.contents[0] + summary = self.contents[0].paragraph # if summary cannot fit into first line with one triple quotation # if len(summary) + ' ' * indent_level * tabsize > width - 3: if len(wrap(summary, width - 3 - int(special), indent_level, tabsize)) > 1: