From 90c785975a65f064eb3bef40f9a50b8edd5af102 Mon Sep 17 00:00:00 2001 From: Jarrod Millman Date: Sat, 7 May 2022 19:35:00 -0700 Subject: Use black formatter --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 550e0ce..444e7ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,6 +16,11 @@ repos: - id: check-toml - id: check-added-large-files + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.6.2 hooks: -- cgit v1.2.1 From 4b38c01e680cf2032acb09819bc0985e5dfc5ca8 Mon Sep 17 00:00:00 2001 From: Jarrod Millman Date: Sat, 7 May 2022 19:39:02 -0700 Subject: Run black formatter --- doc/conf.py | 62 +-- doc/example.py | 2 +- numpydoc/__init__.py | 2 + numpydoc/__main__.py | 28 +- numpydoc/_version.py | 2 +- numpydoc/docscrape.py | 365 +++++++------- numpydoc/docscrape_sphinx.py | 270 +++++----- numpydoc/numpydoc.py | 198 ++++---- numpydoc/tests/test_docscrape.py | 603 +++++++++++++---------- numpydoc/tests/test_full.py | 65 +-- numpydoc/tests/test_main.py | 48 +- numpydoc/tests/test_numpydoc.py | 128 ++--- numpydoc/tests/test_validate.py | 32 +- numpydoc/tests/test_xref.py | 12 +- numpydoc/tests/tinybuild/conf.py | 22 +- numpydoc/tests/tinybuild/numpydoc_test_module.py | 2 +- numpydoc/validate.py | 51 +- numpydoc/xref.py | 106 ++-- setup.py | 56 ++- 19 files changed, 1113 insertions(+), 941 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 28708d8..16e1ac3 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -21,11 +21,11 @@ import os # documentation root, use os.path.abspath to make it absolute, like shown here. # for example.py -sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath(".")) # project root -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) -os.environ['MPLBACKEND'] = 'Agg' # avoid tkinter import errors on rtfd.io +os.environ["MPLBACKEND"] = "Agg" # avoid tkinter import errors on rtfd.io # -- General configuration --------------------------------------------------- @@ -36,32 +36,32 @@ os.environ['MPLBACKEND'] = 'Agg' # avoid tkinter import errors on rtfd.io # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'numpydoc', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.imgmath', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "numpydoc", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", + "sphinx.ext.imgmath", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The root toctree document -master_doc = 'index' # NOTE: will be changed to `root_doc` in sphinx 4 +master_doc = "index" # NOTE: will be changed to `root_doc` in sphinx 4 # General information about the project. -project = 'numpydoc' -copyright = f'2019-{date.today().year}, numpydoc maintainers' +project = "numpydoc" +copyright = f"2019-{date.today().year}, numpydoc maintainers" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -70,12 +70,13 @@ copyright = f'2019-{date.today().year}, numpydoc maintainers' # The short X.Y version. import numpydoc + # version = .__version__ # The full version, including alpha/beta/rc tags. release = numpydoc.__version__ version = numpydoc.__version__ numpydoc_xref_param_type = True -numpydoc_xref_ignore = {'optional', 'type_without_description', 'BadException'} +numpydoc_xref_ignore = {"optional", "type_without_description", "BadException"} # Run docstring validation as part of build process numpydoc_validation_checks = {"all", "GL01", "SA04", "RT03"} @@ -91,7 +92,7 @@ numpydoc_validation_checks = {"all", "GL01", "SA04", "RT03"} # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -109,7 +110,7 @@ exclude_patterns = ['_build'] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -134,7 +135,7 @@ html_sidebars = { } html_title = f"{project} v{version} Manual" -html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = "%b %d, %Y" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -188,7 +189,7 @@ html_static_path = [] # ['_static'] # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'project-templatedoc' +htmlhelp_basename = "project-templatedoc" # -- Options for LaTeX output --------------------------------------------- @@ -196,10 +197,8 @@ htmlhelp_basename = 'project-templatedoc' latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', } @@ -208,8 +207,13 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'numpydoc.tex', 'numpydoc Documentation', - 'Numpydoc maintainers', 'manual'), + ( + "index", + "numpydoc.tex", + "numpydoc Documentation", + "Numpydoc maintainers", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -247,7 +251,7 @@ latex_documents = [ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'numpy': ('https://numpy.org/devdocs/', None), - 'sklearn': ('https://scikit-learn.org/stable/', None), + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/devdocs/", None), + "sklearn": ("https://scikit-learn.org/stable/", None), } diff --git a/doc/example.py b/doc/example.py index 3e93a82..9d3ecca 100644 --- a/doc/example.py +++ b/doc/example.py @@ -34,7 +34,7 @@ import matplotlib.pyplot as plt # numpy module itself, unabbreviated. -def foo(var1, var2, *args, long_var_name='hi', only_seldom_used_keyword=0, **kwargs): +def foo(var1, var2, *args, long_var_name="hi", only_seldom_used_keyword=0, **kwargs): r"""Summarize the function in one line. Several sentences providing an extended description. Refer to diff --git a/numpydoc/__init__.py b/numpydoc/__init__.py index 42836ff..31cc424 100644 --- a/numpydoc/__init__.py +++ b/numpydoc/__init__.py @@ -20,6 +20,7 @@ def _verify_sphinx_jinja(): if version.parse(sphinx.__version__) <= version.parse("4.0.2"): if version.parse(jinja2.__version__) >= version.parse("3.1"): from sphinx.errors import VersionRequirementError + raise VersionRequirementError( "\n\nSphinx<4.0.2 is incompatible with Jinja2>=3.1.\n" "If you wish to continue using sphinx<4.0.2 you need to pin " @@ -32,4 +33,5 @@ _verify_sphinx_jinja() def setup(app, *args, **kwargs): from .numpydoc import setup + return setup(app, *args, **kwargs) diff --git a/numpydoc/__main__.py b/numpydoc/__main__.py index f342cc3..4a50da9 100644 --- a/numpydoc/__main__.py +++ b/numpydoc/__main__.py @@ -12,8 +12,7 @@ from .validate import validate, Validator def render_object(import_path, config=None): """Test numpydoc docstring generation for a given object""" # TODO: Move Validator._load_obj to a better place than validate - print(get_doc_object(Validator._load_obj(import_path), - config=dict(config or []))) + print(get_doc_object(Validator._load_obj(import_path), config=dict(config or []))) return 0 @@ -22,25 +21,30 @@ def validate_object(import_path): results = validate(import_path) for err_code, err_desc in results["errors"]: exit_status += 1 - print(':'.join([import_path, err_code, err_desc])) + print(":".join([import_path, err_code, err_desc])) return exit_status -if __name__ == '__main__': +if __name__ == "__main__": ap = argparse.ArgumentParser(description=__doc__) - ap.add_argument('import_path', help='e.g. numpy.ndarray') + ap.add_argument("import_path", help="e.g. numpy.ndarray") def _parse_config(s): - key, _, value = s.partition('=') + key, _, value = s.partition("=") value = ast.literal_eval(value) return key, value - ap.add_argument('-c', '--config', type=_parse_config, - action='append', - help='key=val where val will be parsed by literal_eval, ' - 'e.g. -c use_plots=True. Multiple -c can be used.') - ap.add_argument('--validate', action='store_true', - help='validate the object and report errors') + ap.add_argument( + "-c", + "--config", + type=_parse_config, + action="append", + help="key=val where val will be parsed by literal_eval, " + "e.g. -c use_plots=True. Multiple -c can be used.", + ) + ap.add_argument( + "--validate", action="store_true", help="validate the object and report errors" + ) args = ap.parse_args() if args.validate: diff --git a/numpydoc/_version.py b/numpydoc/_version.py index e7a1397..0196c1d 100644 --- a/numpydoc/_version.py +++ b/numpydoc/_version.py @@ -1 +1 @@ -__version__ = '1.3.2.dev0' +__version__ = "1.3.2.dev0" diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 1587797..6bdaa84 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -22,9 +22,8 @@ def strip_blank_lines(l): class Reader: - """A line-based string reader. + """A line-based string reader.""" - """ def __init__(self, data): """ Parameters @@ -36,7 +35,7 @@ class Reader: if isinstance(data, list): self._str = data else: - self._str = data.split('\n') # store string as list of lines + self._str = data.split("\n") # store string as list of lines self.reset() @@ -52,10 +51,10 @@ class Reader: self._l += 1 return out else: - return '' + return "" def seek_next_non_empty_line(self): - for l in self[self._l:]: + for l in self[self._l :]: if l.strip(): break else: @@ -68,10 +67,10 @@ class Reader: start = self._l for line in self[start:]: if condition_func(line): - return self[start:self._l] + return self[start : self._l] self._l += 1 if self.eof(): - return self[start:self._l+1] + return self[start : self._l + 1] return [] def read_to_next_empty_line(self): @@ -84,28 +83,29 @@ class Reader: def read_to_next_unindented_line(self): def is_unindented(line): - return (line.strip() and (len(line.lstrip()) == len(line))) + return line.strip() and (len(line.lstrip()) == len(line)) + return self.read_to_condition(is_unindented) def peek(self, n=0): if self._l + n < len(self._str): return self[self._l + n] else: - return '' + return "" def is_empty(self): - return not ''.join(self._str).strip() + return not "".join(self._str).strip() class ParseError(Exception): def __str__(self): message = self.args[0] - if hasattr(self, 'docstring'): + if hasattr(self, "docstring"): message = f"{message} in {self.docstring!r}" return message -Parameter = namedtuple('Parameter', ['name', 'type', 'desc']) +Parameter = namedtuple("Parameter", ["name", "type", "desc"]) class NumpyDocString(Mapping): @@ -116,29 +116,29 @@ class NumpyDocString(Mapping): """ sections = { - 'Signature': '', - 'Summary': [''], - 'Extended Summary': [], - 'Parameters': [], - 'Returns': [], - 'Yields': [], - 'Receives': [], - 'Raises': [], - 'Warns': [], - 'Other Parameters': [], - 'Attributes': [], - 'Methods': [], - 'See Also': [], - 'Notes': [], - 'Warnings': [], - 'References': '', - 'Examples': '', - 'index': {} + "Signature": "", + "Summary": [""], + "Extended Summary": [], + "Parameters": [], + "Returns": [], + "Yields": [], + "Receives": [], + "Raises": [], + "Warns": [], + "Other Parameters": [], + "Attributes": [], + "Methods": [], + "See Also": [], + "Notes": [], + "Warnings": [], + "References": "", + "Examples": "", + "index": {}, } def __init__(self, docstring, config=None): orig_docstring = docstring - docstring = textwrap.dedent(docstring).split('\n') + docstring = textwrap.dedent(docstring).split("\n") self._doc = Reader(docstring) self._parsed_data = copy.deepcopy(self.sections) @@ -172,15 +172,17 @@ class NumpyDocString(Mapping): l1 = self._doc.peek().strip() # e.g. Parameters - if l1.startswith('.. index::'): + if l1.startswith(".. index::"): return True l2 = self._doc.peek(1).strip() # ---------- or ========== - if len(l2) >= 3 and (set(l2) in ({'-'}, {'='}) ) and len(l2) != len(l1): - snip = '\n'.join(self._doc._str[:2])+'...' - self._error_location("potentially wrong underline length... \n%s \n%s in \n%s"\ - % (l1, l2, snip), error=False) - return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) + if len(l2) >= 3 and (set(l2) in ({"-"}, {"="})) and len(l2) != len(l1): + snip = "\n".join(self._doc._str[:2]) + "..." + self._error_location( + f"potentially wrong underline length... \n{l1} \n{l2} in \n{snip}", + error=False, + ) + return l2.startswith("-" * len(l1)) or l2.startswith("=" * len(l1)) def _strip(self, doc): i = 0 @@ -193,14 +195,14 @@ class NumpyDocString(Mapping): if line.strip(): break - return doc[i:len(doc)-j] + return doc[i : len(doc) - j] def _read_to_next_section(self): section = self._doc.read_to_next_empty_line() while not self._is_at_section() and not self._doc.eof(): if not self._doc.peek(-1).strip(): # previous line was empty - section += [''] + section += [""] section += self._doc.read_to_next_empty_line() @@ -211,7 +213,7 @@ class NumpyDocString(Mapping): data = self._read_to_next_section() name = data[0].strip() - if name.startswith('..'): # index section + if name.startswith(".."): # index section yield name, data[1:] elif len(data) < 2: yield StopIteration @@ -224,14 +226,14 @@ class NumpyDocString(Mapping): params = [] while not r.eof(): header = r.read().strip() - if ' :' in header: - arg_name, arg_type = header.split(' :', maxsplit=1) + if " :" in header: + arg_name, arg_type = header.split(" :", maxsplit=1) arg_name, arg_type = arg_name.strip(), arg_type.strip() else: if single_element_is_type: - arg_name, arg_type = '', header + arg_name, arg_type = "", header else: - arg_name, arg_type = header, '' + arg_name, arg_type = header, "" desc = r.read_to_next_unindented_line() desc = dedent_lines(desc) @@ -261,21 +263,24 @@ class NumpyDocString(Mapping): _funcbacktick = r"`(?P(?:~\w+\.)?[a-zA-Z0-9_\.-]+)`" _funcplain = r"(?P[a-zA-Z0-9_\.-]+)" _funcname = r"(" + _role + _funcbacktick + r"|" + _funcplain + r")" - _funcnamenext = _funcname.replace('role', 'rolenext') - _funcnamenext = _funcnamenext.replace('name', 'namenext') + _funcnamenext = _funcname.replace("role", "rolenext") + _funcnamenext = _funcnamenext.replace("name", "namenext") _description = r"(?P\s*:(\s+(?P\S+.*))?)?\s*$" _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*") _line_rgx = re.compile( - r"^\s*" + - r"(?P" + # group for all function names - _funcname + - r"(?P([,]\s+" + _funcnamenext + r")*)" + - r")" + # end of "allfuncs" - r"(?P[,\.])?" + # Some function lists have a trailing comma (or period) '\s*' - _description) + r"^\s*" + + r"(?P" + + _funcname # group for all function names + + r"(?P([,]\s+" + + _funcnamenext + + r")*)" + + r")" + + r"(?P[,\.])?" # end of "allfuncs" + + _description # Some function lists have a trailing comma (or period) '\s*' + ) # Empty elements are replaced with '..' - empty_description = '..' + empty_description = ".." def _parse_see_also(self, content): """ @@ -295,8 +300,8 @@ class NumpyDocString(Mapping): m = self._func_rgx.match(text) if not m: self._error_location(f"Error parsing See Also entry {line!r}") - role = m.group('role') - name = m.group('name') if role else m.group('name2') + role = m.group("role") + name = m.group("name") if role else m.group("name2") return name, role, m.end() rest = [] @@ -307,24 +312,25 @@ class NumpyDocString(Mapping): line_match = self._line_rgx.match(line) description = None if line_match: - description = line_match.group('desc') - if line_match.group('trailing') and description: + description = line_match.group("desc") + if line_match.group("trailing") and description: self._error_location( - 'Unexpected comma or period after function list at index %d of ' - 'line "%s"' % (line_match.end('trailing'), line), - error=False) - if not description and line.startswith(' '): + "Unexpected comma or period after function list at index %d of " + 'line "%s"' % (line_match.end("trailing"), line), + error=False, + ) + if not description and line.startswith(" "): rest.append(line.strip()) elif line_match: funcs = [] - text = line_match.group('allfuncs') + text = line_match.group("allfuncs") while True: if not text.strip(): break name, role, match_end = parse_item_name(text) funcs.append((name, role)) text = text[match_end:].strip() - if text and text[0] == ',': + if text and text[0] == ",": text = text[1:].strip() rest = list(filter(None, [description])) items.append((funcs, rest)) @@ -338,17 +344,18 @@ class NumpyDocString(Mapping): :refguide: something, else, and more """ + def strip_each_in(lst): return [s.strip() for s in lst] out = {} - section = section.split('::') + section = section.split("::") if len(section) > 1: - out['default'] = strip_each_in(section[1].split(','))[0] + out["default"] = strip_each_in(section[1].split(","))[0] for line in content: - line = line.split(':') + line = line.split(":") if len(line) > 2: - out[line[1]] = strip_each_in(line[2].split(',')) + out[line[1]] = strip_each_in(line[2].split(",")) return out def _parse_summary(self): @@ -360,18 +367,18 @@ class NumpyDocString(Mapping): while True: summary = self._doc.read_to_next_empty_line() summary_str = " ".join([s.strip() for s in summary]).strip() - compiled = re.compile(r'^([\w., ]+=)?\s*[\w\.]+\(.*\)$') + compiled = re.compile(r"^([\w., ]+=)?\s*[\w\.]+\(.*\)$") if compiled.match(summary_str): - self['Signature'] = summary_str + self["Signature"] = summary_str if not self._is_at_section(): continue break if summary is not None: - self['Summary'] = summary + self["Summary"] = summary if not self._is_at_section(): - self['Extended Summary'] = self._read_to_next_section() + self["Extended Summary"] = self._read_to_next_section() def _parse(self): self._doc.reset() @@ -380,42 +387,44 @@ class NumpyDocString(Mapping): sections = list(self._read_sections()) section_names = {section for section, content in sections} - has_returns = 'Returns' in section_names - has_yields = 'Yields' in section_names + has_returns = "Returns" in section_names + has_yields = "Yields" in section_names # We could do more tests, but we are not. Arbitrarily. if has_returns and has_yields: - msg = 'Docstring contains both a Returns and Yields section.' + msg = "Docstring contains both a Returns and Yields section." raise ValueError(msg) - if not has_yields and 'Receives' in section_names: - msg = 'Docstring contains a Receives section but not Yields.' + if not has_yields and "Receives" in section_names: + msg = "Docstring contains a Receives section but not Yields." raise ValueError(msg) for (section, content) in sections: - if not section.startswith('..'): - section = (s.capitalize() for s in section.split(' ')) - section = ' '.join(section) + if not section.startswith(".."): + section = (s.capitalize() for s in section.split(" ")) + section = " ".join(section) if self.get(section): - self._error_location("The section %s appears twice in %s" - % (section, '\n'.join(self._doc._str))) + self._error_location( + "The section %s appears twice in %s" + % (section, "\n".join(self._doc._str)) + ) - if section in ('Parameters', 'Other Parameters', 'Attributes', - 'Methods'): + if section in ("Parameters", "Other Parameters", "Attributes", "Methods"): self[section] = self._parse_param_list(content) - elif section in ('Returns', 'Yields', 'Raises', 'Warns', 'Receives'): + elif section in ("Returns", "Yields", "Raises", "Warns", "Receives"): self[section] = self._parse_param_list( - content, single_element_is_type=True) - elif section.startswith('.. index::'): - self['index'] = self._parse_index(section, content) - elif section == 'See Also': - self['See Also'] = self._parse_see_also(content) + content, single_element_is_type=True + ) + elif section.startswith(".. index::"): + self["index"] = self._parse_index(section, content) + elif section == "See Also": + self["See Also"] = self._parse_see_also(content) else: self[section] = content @property def _obj(self): - if hasattr(self, '_cls'): + if hasattr(self, "_cls"): return self._cls - elif hasattr(self, '_f'): + elif hasattr(self, "_f"): return self._f return None @@ -428,9 +437,9 @@ class NumpyDocString(Mapping): filename = None # Make UserWarning more descriptive via object introspection. # Skip if introspection fails - name = getattr(self._obj, '__name__', None) + name = getattr(self._obj, "__name__", None) if name is None: - name = getattr(getattr(self._obj, '__class__', None), '__name__', None) + name = getattr(getattr(self._obj, "__class__", None), "__name__", None) if name is not None: msg += f" in the docstring of {name}" msg += f" in {filename}." if filename else "" @@ -441,25 +450,25 @@ class NumpyDocString(Mapping): # string conversion routines - def _str_header(self, name, symbol='-'): - return [name, len(name)*symbol] + def _str_header(self, name, symbol="-"): + return [name, len(name) * symbol] def _str_indent(self, doc, indent=4): - return [' '*indent + line for line in doc] + return [" " * indent + line for line in doc] def _str_signature(self): - if self['Signature']: - return [self['Signature'].replace('*', r'\*')] + [''] - return [''] + if self["Signature"]: + return [self["Signature"].replace("*", r"\*")] + [""] + return [""] def _str_summary(self): - if self['Summary']: - return self['Summary'] + [''] + if self["Summary"]: + return self["Summary"] + [""] return [] def _str_extended_summary(self): - if self['Extended Summary']: - return self['Extended Summary'] + [''] + if self["Extended Summary"]: + return self["Extended Summary"] + [""] return [] def _str_param_list(self, name): @@ -472,10 +481,10 @@ class NumpyDocString(Mapping): parts.append(param.name) if param.type: parts.append(param.type) - out += [' : '.join(parts)] - if param.desc and ''.join(param.desc).strip(): + out += [" : ".join(parts)] + if param.desc and "".join(param.desc).strip(): out += self._str_indent(param.desc) - out += [''] + out += [""] return out def _str_section(self, name): @@ -483,74 +492,81 @@ class NumpyDocString(Mapping): if self[name]: out += self._str_header(name) out += self[name] - out += [''] + out += [""] return out def _str_see_also(self, func_role): - if not self['See Also']: + if not self["See Also"]: return [] out = [] out += self._str_header("See Also") - out += [''] + out += [""] last_had_desc = True - for funcs, desc in self['See Also']: + for funcs, desc in self["See Also"]: assert isinstance(funcs, list) links = [] for func, role in funcs: if role: - link = f':{role}:`{func}`' + link = f":{role}:`{func}`" elif func_role: - link = f':{func_role}:`{func}`' + link = f":{func_role}:`{func}`" else: link = f"`{func}`_" links.append(link) - link = ', '.join(links) + link = ", ".join(links) out += [link] if desc: - out += self._str_indent([' '.join(desc)]) + out += self._str_indent([" ".join(desc)]) last_had_desc = True else: last_had_desc = False out += self._str_indent([self.empty_description]) if last_had_desc: - out += [''] - out += [''] + out += [""] + out += [""] return out def _str_index(self): - idx = self['index'] + idx = self["index"] out = [] output_index = False - default_index = idx.get('default', '') + default_index = idx.get("default", "") if default_index: output_index = True - out += [f'.. index:: {default_index}'] + out += [f".. index:: {default_index}"] for section, references in idx.items(): - if section == 'default': + if section == "default": continue output_index = True out += [f" :{section}: {', '.join(references)}"] if output_index: return out - return '' + return "" - def __str__(self, func_role=''): + def __str__(self, func_role=""): out = [] out += self._str_signature() out += self._str_summary() out += self._str_extended_summary() - for param_list in ('Parameters', 'Returns', 'Yields', 'Receives', - 'Other Parameters', 'Raises', 'Warns'): + for param_list in ( + "Parameters", + "Returns", + "Yields", + "Receives", + "Other Parameters", + "Raises", + "Warns", + ): out += self._str_param_list(param_list) - out += self._str_section('Warnings') + out += self._str_section("Warnings") out += self._str_see_also(func_role) - for s in ('Notes', 'References', 'Examples'): + for s in ("Notes", "References", "Examples"): out += self._str_section(s) - for param_list in ('Attributes', 'Methods'): + for param_list in ("Attributes", "Methods"): out += self._str_param_list(param_list) out += self._str_index() - return '\n'.join(out) + return "\n".join(out) def dedent_lines(lines): @@ -559,33 +575,32 @@ def dedent_lines(lines): class FunctionDoc(NumpyDocString): - def __init__(self, func, role='func', doc=None, config=None): + def __init__(self, func, role="func", doc=None, config=None): self._f = func self._role = role # e.g. "func" or "meth" if doc is None: if func is None: raise ValueError("No function or docstring given") - doc = inspect.getdoc(func) or '' + doc = inspect.getdoc(func) or "" if config is None: config = {} NumpyDocString.__init__(self, doc, config) def get_func(self): - func_name = getattr(self._f, '__name__', self.__class__.__name__) + func_name = getattr(self._f, "__name__", self.__class__.__name__) if inspect.isclass(self._f): - func = getattr(self._f, '__call__', self._f.__init__) + func = getattr(self._f, "__call__", self._f.__init__) else: func = self._f return func, func_name def __str__(self): - out = '' + out = "" func, func_name = self.get_func() - roles = {'func': 'function', - 'meth': 'method'} + roles = {"func": "function", "meth": "method"} if self._role: if self._role not in roles: @@ -606,26 +621,24 @@ class ObjDoc(NumpyDocString): class ClassDoc(NumpyDocString): - extra_public_methods = ['__call__'] + extra_public_methods = ["__call__"] - def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, - config=None): + def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=None): if not inspect.isclass(cls) and cls is not None: raise ValueError(f"Expected a class or None, but got {cls!r}") self._cls = cls - if 'sphinx' in sys.modules: + if "sphinx" in sys.modules: from sphinx.ext.autodoc import ALL else: ALL = object() if config is None: config = {} - self.show_inherited_members = config.get( - 'show_inherited_class_members', True) + self.show_inherited_members = config.get("show_inherited_class_members", True) - if modulename and not modulename.endswith('.'): - modulename += '.' + if modulename and not modulename.endswith("."): + modulename += "." self._mod = modulename if doc is None: @@ -635,29 +648,31 @@ class ClassDoc(NumpyDocString): NumpyDocString.__init__(self, doc) - _members = config.get('members', []) + _members = config.get("members", []) if _members is ALL: _members = None - _exclude = config.get('exclude-members', []) + _exclude = config.get("exclude-members", []) + + if config.get("show_class_members", True) and _exclude is not ALL: - if config.get('show_class_members', True) and _exclude is not ALL: def splitlines_x(s): if not s: return [] else: return s.splitlines() - for field, items in [('Methods', self.methods), - ('Attributes', self.properties)]: + + for field, items in [ + ("Methods", self.methods), + ("Attributes", self.properties), + ]: if not self[field]: doc_list = [] for name in sorted(items): - if (name in _exclude or - (_members and name not in _members)): + if name in _exclude or (_members and name not in _members): continue try: doc_item = pydoc.getdoc(getattr(self._cls, name)) - doc_list.append( - Parameter(name, '', splitlines_x(doc_item))) + doc_list.append(Parameter(name, "", splitlines_x(doc_item))) except AttributeError: pass # method doesn't exist self[field] = doc_list @@ -666,21 +681,33 @@ class ClassDoc(NumpyDocString): def methods(self): if self._cls is None: return [] - return [name for name, func in inspect.getmembers(self._cls) - if ((not name.startswith('_') - or name in self.extra_public_methods) - and isinstance(func, Callable) - and self._is_show_member(name))] + return [ + name + for name, func in inspect.getmembers(self._cls) + if ( + (not name.startswith("_") or name in self.extra_public_methods) + and isinstance(func, Callable) + and self._is_show_member(name) + ) + ] @property def properties(self): if self._cls is None: return [] - return [name for name, func in inspect.getmembers(self._cls) - if (not name.startswith('_') and - (func is None or isinstance(func, property) or - inspect.isdatadescriptor(func)) - and self._is_show_member(name))] + return [ + name + for name, func in inspect.getmembers(self._cls) + if ( + not name.startswith("_") + and ( + func is None + or isinstance(func, property) + or inspect.isdatadescriptor(func) + ) + and self._is_show_member(name) + ) + ] def _is_show_member(self, name): if self.show_inherited_members: @@ -693,19 +720,19 @@ class ClassDoc(NumpyDocString): def get_doc_object(obj, what=None, doc=None, config=None): if what is None: if inspect.isclass(obj): - what = 'class' + what = "class" elif inspect.ismodule(obj): - what = 'module' + what = "module" elif isinstance(obj, Callable): - what = 'function' + what = "function" else: - what = 'object' + what = "object" if config is None: config = {} - if what == 'class': + if what == "class": return ClassDoc(obj, func_doc=FunctionDoc, doc=doc, config=config) - elif what in ('function', 'method'): + elif what in ("function", "method"): return FunctionDoc(obj, doc=doc, config=config) else: if doc is None: diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index b15afd2..ee8e093 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -14,7 +14,7 @@ from .docscrape import NumpyDocString, FunctionDoc, ClassDoc, ObjDoc from .xref import make_xref -IMPORT_MATPLOTLIB_RE = r'\b(import +matplotlib|from +matplotlib +import)\b' +IMPORT_MATPLOTLIB_RE = r"\b(import +matplotlib|from +matplotlib +import)\b" class SphinxDocString(NumpyDocString): @@ -25,77 +25,76 @@ class SphinxDocString(NumpyDocString): self.load_config(config) def load_config(self, config): - self.use_plots = config.get('use_plots', False) - self.use_blockquotes = config.get('use_blockquotes', False) - self.class_members_toctree = config.get('class_members_toctree', True) - self.attributes_as_param_list = config.get('attributes_as_param_list', True) - self.xref_param_type = config.get('xref_param_type', False) - self.xref_aliases = config.get('xref_aliases', dict()) - self.xref_ignore = config.get('xref_ignore', set()) - self.template = config.get('template', None) + self.use_plots = config.get("use_plots", False) + self.use_blockquotes = config.get("use_blockquotes", False) + self.class_members_toctree = config.get("class_members_toctree", True) + self.attributes_as_param_list = config.get("attributes_as_param_list", True) + self.xref_param_type = config.get("xref_param_type", False) + self.xref_aliases = config.get("xref_aliases", dict()) + self.xref_ignore = config.get("xref_ignore", set()) + self.template = config.get("template", None) if self.template is None: - template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')] + template_dirs = [os.path.join(os.path.dirname(__file__), "templates")] template_loader = FileSystemLoader(template_dirs) template_env = SandboxedEnvironment(loader=template_loader) - self.template = template_env.get_template('numpydoc_docstring.rst') + self.template = template_env.get_template("numpydoc_docstring.rst") # string conversion routines - def _str_header(self, name, symbol='`'): - return ['.. rubric:: ' + name, ''] + def _str_header(self, name, symbol="`"): + return [".. rubric:: " + name, ""] def _str_field_list(self, name): - return [':' + name + ':'] + return [":" + name + ":"] def _str_indent(self, doc, indent=4): out = [] for line in doc: - out += [' '*indent + line] + out += [" " * indent + line] return out def _str_signature(self): - return [''] + return [""] def _str_summary(self): - return self['Summary'] + [''] + return self["Summary"] + [""] def _str_extended_summary(self): - return self['Extended Summary'] + [''] + return self["Extended Summary"] + [""] - def _str_returns(self, name='Returns'): - named_fmt = '**%s** : %s' - unnamed_fmt = '%s' + def _str_returns(self, name="Returns"): + named_fmt = "**%s** : %s" + unnamed_fmt = "%s" out = [] if self[name]: out += self._str_field_list(name) - out += [''] + out += [""] for param in self[name]: param_type = param.type if param_type and self.xref_param_type: param_type = make_xref( - param_type, - self.xref_aliases, - self.xref_ignore + param_type, self.xref_aliases, self.xref_ignore ) if param.name: - out += self._str_indent([named_fmt % (param.name.strip(), - param_type)]) + out += self._str_indent( + [named_fmt % (param.name.strip(), param_type)] + ) else: out += self._str_indent([unnamed_fmt % param_type.strip()]) if not param.desc: - out += self._str_indent(['..'], 8) + out += self._str_indent([".."], 8) else: if self.use_blockquotes: - out += [''] + out += [""] out += self._str_indent(param.desc, 8) - out += [''] + out += [""] return out def _escape_args_and_kwargs(self, name): - if name[:2] == '**': - return r'\*\*' + name[2:] - elif name[:1] == '*': - return r'\*' + name[1:] + if name[:2] == "**": + return r"\*\*" + name[2:] + elif name[:1] == "*": + return r"\*" + name[1:] else: return name @@ -138,42 +137,43 @@ class SphinxDocString(NumpyDocString): # XXX: If changing the following, please check the rendering when param # ends with '_', e.g. 'word_' # See https://github.com/numpy/numpydoc/pull/144 - display_param = f'**{param}**' + display_param = f"**{param}**" if not fake_autosummary: return display_param, desc param_obj = getattr(self._obj, param, None) - if not (callable(param_obj) - or isinstance(param_obj, property) - or inspect.isgetsetdescriptor(param_obj) - or inspect.ismemberdescriptor(param_obj)): + if not ( + callable(param_obj) + or isinstance(param_obj, property) + or inspect.isgetsetdescriptor(param_obj) + or inspect.ismemberdescriptor(param_obj) + ): param_obj = None obj_doc = pydoc.getdoc(param_obj) if not (param_obj and obj_doc): return display_param, desc - prefix = getattr(self, '_name', '') + prefix = getattr(self, "_name", "") if prefix: - link_prefix = f'{prefix}.' + link_prefix = f"{prefix}." else: - link_prefix = '' + link_prefix = "" # Referenced object has a docstring - display_param = f':obj:`{param} <{link_prefix}{param}>`' + display_param = f":obj:`{param} <{link_prefix}{param}>`" if obj_doc: # Overwrite desc. Take summary logic of autosummary - desc = re.split(r'\n\s*\n', obj_doc.strip(), 1)[0] + desc = re.split(r"\n\s*\n", obj_doc.strip(), 1)[0] # XXX: Should this have DOTALL? # It does not in autosummary - m = re.search(r"^([A-Z].*?\.)(?:\s|$)", - ' '.join(desc.split())) + m = re.search(r"^([A-Z].*?\.)(?:\s|$)", " ".join(desc.split())) if m: desc = m.group(1).strip() else: - desc = desc.partition('\n')[0] - desc = desc.split('\n') + desc = desc.partition("\n")[0] + desc = desc.split("\n") return display_param, desc def _str_param_list(self, name, fake_autosummary=False): @@ -199,11 +199,11 @@ class SphinxDocString(NumpyDocString): out = [] if self[name]: out += self._str_field_list(name) - out += [''] + out += [""] for param in self[name]: - display_param, desc = self._process_param(param.name, - param.desc, - fake_autosummary) + display_param, desc = self._process_param( + param.name, param.desc, fake_autosummary + ) parts = [] if display_param: parts.append(display_param) @@ -212,20 +212,18 @@ class SphinxDocString(NumpyDocString): param_type = param.type if self.xref_param_type: param_type = make_xref( - param_type, - self.xref_aliases, - self.xref_ignore + param_type, self.xref_aliases, self.xref_ignore ) parts.append(param_type) - out += self._str_indent([' : '.join(parts)]) + out += self._str_indent([" : ".join(parts)]) if desc and self.use_blockquotes: - out += [''] + out += [""] elif not desc: # empty definition - desc = ['..'] + desc = [".."] out += self._str_indent(desc, 8) - out += [''] + out += [""] return out @@ -237,11 +235,11 @@ class SphinxDocString(NumpyDocString): """ out = [] if self[name]: - out += [f'.. rubric:: {name}', ''] - prefix = getattr(self, '_name', '') + out += [f".. rubric:: {name}", ""] + prefix = getattr(self, "_name", "") if prefix: - prefix = f'~{prefix}.' + prefix = f"~{prefix}." autosum = [] others = [] @@ -250,9 +248,11 @@ class SphinxDocString(NumpyDocString): # Check if the referenced member can have a docstring or not param_obj = getattr(self._obj, param.name, None) - if not (callable(param_obj) - or isinstance(param_obj, property) - or inspect.isdatadescriptor(param_obj)): + if not ( + callable(param_obj) + or isinstance(param_obj, property) + or inspect.isdatadescriptor(param_obj) + ): param_obj = None if param_obj and pydoc.getdoc(param_obj): @@ -262,25 +262,24 @@ class SphinxDocString(NumpyDocString): others.append(param) if autosum: - out += ['.. autosummary::'] + out += [".. autosummary::"] if self.class_members_toctree: - out += [' :toctree:'] - out += [''] + autosum + out += [" :toctree:"] + out += [""] + autosum if others: maxlen_0 = max(3, max(len(p.name) + 4 for p in others)) hdr = "=" * maxlen_0 + " " + "=" * 10 - fmt = '%%%ds %%s ' % (maxlen_0,) - out += ['', '', hdr] + fmt = "%%%ds %%s " % (maxlen_0,) + out += ["", "", hdr] for param in others: name = "**" + param.name.strip() + "**" - desc = " ".join(x.strip() - for x in param.desc).strip() + desc = " ".join(x.strip() for x in param.desc).strip() if param.type: desc = f"({param.type}) {desc}" out += [fmt % (name, desc)] out += [hdr] - out += [''] + out += [""] return out def _str_section(self, name): @@ -289,103 +288,105 @@ class SphinxDocString(NumpyDocString): out += self._str_header(name) content = textwrap.dedent("\n".join(self[name])).split("\n") out += content - out += [''] + out += [""] return out def _str_see_also(self, func_role): out = [] - if self['See Also']: + if self["See Also"]: see_also = super()._str_see_also(func_role) - out = ['.. seealso::', ''] + out = [".. seealso::", ""] out += self._str_indent(see_also[2:]) return out def _str_warnings(self): out = [] - if self['Warnings']: - out = ['.. warning::', ''] - out += self._str_indent(self['Warnings']) - out += [''] + if self["Warnings"]: + out = [".. warning::", ""] + out += self._str_indent(self["Warnings"]) + out += [""] return out def _str_index(self): - idx = self['index'] + idx = self["index"] out = [] if len(idx) == 0: return out out += [f".. index:: {idx.get('default', '')}"] for section, references in idx.items(): - if section == 'default': + if section == "default": continue - elif section == 'refguide': + elif section == "refguide": out += [f" single: {', '.join(references)}"] else: out += [f" {section}: {','.join(references)}"] - out += [''] + out += [""] return out def _str_references(self): out = [] - if self['References']: - out += self._str_header('References') - if isinstance(self['References'], str): - self['References'] = [self['References']] - out.extend(self['References']) - out += [''] + if self["References"]: + out += self._str_header("References") + if isinstance(self["References"], str): + self["References"] = [self["References"]] + out.extend(self["References"]) + out += [""] # Latex collects all references to a separate bibliography, # so we need to insert links to it - out += ['.. only:: latex', ''] + out += [".. only:: latex", ""] items = [] - for line in self['References']: - m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) + for line in self["References"]: + m = re.match(r".. \[([a-z0-9._-]+)\]", line, re.I) if m: items.append(m.group(1)) - out += [' ' + ", ".join([f"[{item}]_" for item in items]), ''] + out += [" " + ", ".join([f"[{item}]_" for item in items]), ""] return out def _str_examples(self): - examples_str = "\n".join(self['Examples']) + examples_str = "\n".join(self["Examples"]) - if (self.use_plots and re.search(IMPORT_MATPLOTLIB_RE, examples_str) - and 'plot::' not in examples_str): + if ( + self.use_plots + and re.search(IMPORT_MATPLOTLIB_RE, examples_str) + and "plot::" not in examples_str + ): out = [] - out += self._str_header('Examples') - out += ['.. plot::', ''] - out += self._str_indent(self['Examples']) - out += [''] + out += self._str_header("Examples") + out += [".. plot::", ""] + out += self._str_indent(self["Examples"]) + out += [""] return out else: - return self._str_section('Examples') + return self._str_section("Examples") def __str__(self, indent=0, func_role="obj"): ns = { - 'signature': self._str_signature(), - 'index': self._str_index(), - 'summary': self._str_summary(), - 'extended_summary': self._str_extended_summary(), - 'parameters': self._str_param_list('Parameters'), - 'returns': self._str_returns('Returns'), - 'yields': self._str_returns('Yields'), - 'receives': self._str_returns('Receives'), - 'other_parameters': self._str_param_list('Other Parameters'), - 'raises': self._str_returns('Raises'), - 'warns': self._str_returns('Warns'), - 'warnings': self._str_warnings(), - 'see_also': self._str_see_also(func_role), - 'notes': self._str_section('Notes'), - 'references': self._str_references(), - 'examples': self._str_examples(), - 'attributes': - self._str_param_list('Attributes', fake_autosummary=True) - if self.attributes_as_param_list - else self._str_member_list('Attributes'), - 'methods': self._str_member_list('Methods'), + "signature": self._str_signature(), + "index": self._str_index(), + "summary": self._str_summary(), + "extended_summary": self._str_extended_summary(), + "parameters": self._str_param_list("Parameters"), + "returns": self._str_returns("Returns"), + "yields": self._str_returns("Yields"), + "receives": self._str_returns("Receives"), + "other_parameters": self._str_param_list("Other Parameters"), + "raises": self._str_returns("Raises"), + "warns": self._str_returns("Warns"), + "warnings": self._str_warnings(), + "see_also": self._str_see_also(func_role), + "notes": self._str_section("Notes"), + "references": self._str_references(), + "examples": self._str_examples(), + "attributes": self._str_param_list("Attributes", fake_autosummary=True) + if self.attributes_as_param_list + else self._str_member_list("Attributes"), + "methods": self._str_member_list("Methods"), } - ns = {k: '\n'.join(v) for k, v in ns.items()} + ns = {k: "\n".join(v) for k, v in ns.items()} rendered = self.template.render(**ns) - return '\n'.join(self._str_indent(rendered.split('\n'), indent)) + return "\n".join(self._str_indent(rendered.split("\n"), indent)) class SphinxFunctionDoc(SphinxDocString, FunctionDoc): @@ -416,29 +417,28 @@ class SphinxObjDoc(SphinxDocString, ObjDoc): def get_doc_object(obj, what=None, doc=None, config=None, builder=None): if what is None: if inspect.isclass(obj): - what = 'class' + what = "class" elif inspect.ismodule(obj): - what = 'module' + what = "module" elif isinstance(obj, Callable): - what = 'function' + what = "function" else: - what = 'object' + what = "object" if config is None: config = {} - template_dirs = [os.path.join(os.path.dirname(__file__), 'templates')] + template_dirs = [os.path.join(os.path.dirname(__file__), "templates")] if builder is not None: template_loader = BuiltinTemplateLoader() template_loader.init(builder, dirs=template_dirs) else: template_loader = FileSystemLoader(template_dirs) template_env = SandboxedEnvironment(loader=template_loader) - config['template'] = template_env.get_template('numpydoc_docstring.rst') + config["template"] = template_env.get_template("numpydoc_docstring.rst") - if what == 'class': - return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, - config=config) - elif what in ('function', 'method'): + if what == "class": + return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, config=config) + elif what in ("function", "method"): return SphinxFunctionDoc(obj, doc=doc, config=config) else: if doc is None: diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 1775a7b..8210006 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -30,7 +30,7 @@ from sphinx.addnodes import pending_xref, desc_content from sphinx.util import logging from sphinx.errors import ExtensionError -if sphinx.__version__ < '3.0': +if sphinx.__version__ < "3.0": raise RuntimeError("Sphinx 3.0 or newer is required") from .docscrape_sphinx import get_doc_object @@ -42,31 +42,28 @@ logger = logging.getLogger(__name__) HASH_LEN = 12 + def rename_references(app, what, name, obj, options, lines): # decorate reference numbers so that there are no duplicates # these are later undecorated in the doctree, in relabel_references references = set() for line in lines: line = line.strip() - m = re.match(r'^\.\. +\[(%s)\]' % - app.config.numpydoc_citation_re, - line, re.I) + m = re.match(r"^\.\. +\[(%s)\]" % app.config.numpydoc_citation_re, line, re.I) if m: references.add(m.group(1)) if references: # we use a hash to mangle the reference name to avoid invalid names sha = hashlib.sha256() - sha.update(name.encode('utf8')) - prefix = 'R' + sha.hexdigest()[:HASH_LEN] + sha.update(name.encode("utf8")) + prefix = "R" + sha.hexdigest()[:HASH_LEN] for r in references: - new_r = prefix + '-' + r + new_r = prefix + "-" + r for i, line in enumerate(lines): - lines[i] = lines[i].replace(f'[{r}]_', - f'[{new_r}]_') - lines[i] = lines[i].replace(f'.. [{r}]', - f'.. [{new_r}]') + lines[i] = lines[i].replace(f"[{r}]_", f"[{new_r}]_") + lines[i] = lines[i].replace(f".. [{r}]", f".. [{new_r}]") def _is_cite_in_numpydoc_docstring(citation_node): @@ -83,10 +80,11 @@ def _is_cite_in_numpydoc_docstring(citation_node): if section_node is None: return False - sibling_sections = itertools.chain(section_node.traverse(is_docstring_section, - include_self=True, - descend=False, - siblings=True)) + sibling_sections = itertools.chain( + section_node.traverse( + is_docstring_section, include_self=True, descend=False, siblings=True + ) + ) for sibling_section in sibling_sections: if not sibling_section.children: continue @@ -107,22 +105,24 @@ def relabel_references(app, doc): if not _is_cite_in_numpydoc_docstring(citation_node): continue label_node = citation_node[0] - prefix, _, new_label = label_node[0].astext().partition('-') + prefix, _, new_label = label_node[0].astext().partition("-") assert len(prefix) == HASH_LEN + 1 new_text = Text(new_label) label_node.replace(label_node[0], new_text) - for id_ in citation_node['backrefs']: + for id_ in citation_node["backrefs"]: ref = doc.ids[id_] ref_text = ref[0] # Sphinx has created pending_xref nodes with [reftext] text. def matching_pending_xref(node): - return (isinstance(node, pending_xref) and - node[0].astext() == f'[{ref_text}]') + return ( + isinstance(node, pending_xref) + and node[0].astext() == f"[{ref_text}]" + ) for xref_node in ref.parent.traverse(matching_pending_xref): - xref_node.replace(xref_node[0], Text(f'[{new_text}]')) + xref_node.replace(xref_node[0], Text(f"[{new_text}]")) ref.replace(ref_text, new_text.copy()) @@ -130,48 +130,49 @@ def clean_backrefs(app, doc, docname): # only::latex directive has resulted in citation backrefs without reference known_ref_ids = set() for ref in doc.traverse(reference, descend=True): - for id_ in ref['ids']: + for id_ in ref["ids"]: known_ref_ids.add(id_) for citation_node in doc.traverse(citation, descend=True): # remove backrefs to non-existent refs - citation_node['backrefs'] = [id_ for id_ in citation_node['backrefs'] - if id_ in known_ref_ids] + citation_node["backrefs"] = [ + id_ for id_ in citation_node["backrefs"] if id_ in known_ref_ids + ] -DEDUPLICATION_TAG = ' !! processed by numpydoc !!' +DEDUPLICATION_TAG = " !! processed by numpydoc !!" def mangle_docstrings(app, what, name, obj, options, lines): if DEDUPLICATION_TAG in lines: return - cfg = {'use_plots': app.config.numpydoc_use_plots, - 'use_blockquotes': app.config.numpydoc_use_blockquotes, - 'show_class_members': app.config.numpydoc_show_class_members, - 'show_inherited_class_members': - app.config.numpydoc_show_inherited_class_members, - 'class_members_toctree': app.config.numpydoc_class_members_toctree, - 'attributes_as_param_list': - app.config.numpydoc_attributes_as_param_list, - 'xref_param_type': app.config.numpydoc_xref_param_type, - 'xref_aliases': app.config.numpydoc_xref_aliases_complete, - 'xref_ignore': app.config.numpydoc_xref_ignore, - } + cfg = { + "use_plots": app.config.numpydoc_use_plots, + "use_blockquotes": app.config.numpydoc_use_blockquotes, + "show_class_members": app.config.numpydoc_show_class_members, + "show_inherited_class_members": app.config.numpydoc_show_inherited_class_members, + "class_members_toctree": app.config.numpydoc_class_members_toctree, + "attributes_as_param_list": app.config.numpydoc_attributes_as_param_list, + "xref_param_type": app.config.numpydoc_xref_param_type, + "xref_aliases": app.config.numpydoc_xref_aliases_complete, + "xref_ignore": app.config.numpydoc_xref_ignore, + } cfg.update(options or {}) - u_NL = '\n' - if what == 'module': + u_NL = "\n" + if what == "module": # Strip top title - pattern = '^\\s*[#*=]{4,}\\n[a-z0-9 -]+\\n[#*=]{4,}\\s*' + pattern = "^\\s*[#*=]{4,}\\n[a-z0-9 -]+\\n[#*=]{4,}\\s*" title_re = re.compile(pattern, re.I | re.S) - lines[:] = title_re.sub('', u_NL.join(lines)).split(u_NL) + lines[:] = title_re.sub("", u_NL.join(lines)).split(u_NL) else: try: - doc = get_doc_object(obj, what, u_NL.join(lines), config=cfg, - builder=app.builder) + doc = get_doc_object( + obj, what, u_NL.join(lines), config=cfg, builder=app.builder + ) lines[:] = str(doc).split(u_NL) except Exception: - logger.error('[numpydoc] While processing docstring for %r', name) + logger.error("[numpydoc] While processing docstring for %r", name) raise if app.config.numpydoc_validation_checks: @@ -195,33 +196,33 @@ def mangle_docstrings(app, what, name, obj, options, lines): msg += f" {err[0]}: {err[1]}\n" logger.warning(msg) - # call function to replace reference numbers so that there are no # duplicates rename_references(app, what, name, obj, options, lines) - lines += ['..', DEDUPLICATION_TAG] + lines += ["..", DEDUPLICATION_TAG] def mangle_signature(app, what, name, obj, options, sig, retann): # Do not try to inspect classes that don't define `__init__` - if (inspect.isclass(obj) and - (not hasattr(obj, '__init__') or - 'initializes x; see ' in pydoc.getdoc(obj.__init__))): - return '', '' + if inspect.isclass(obj) and ( + not hasattr(obj, "__init__") + or "initializes x; see " in pydoc.getdoc(obj.__init__) + ): + return "", "" - if not (isinstance(obj, Callable) or - hasattr(obj, '__argspec_is_invalid_')): + if not (isinstance(obj, Callable) or hasattr(obj, "__argspec_is_invalid_")): return - if not hasattr(obj, '__doc__'): + if not hasattr(obj, "__doc__"): return - doc = get_doc_object(obj, config={'show_class_members': False}) - sig = (doc['Signature'] - or _clean_text_signature(getattr(obj, '__text_signature__', None))) + doc = get_doc_object(obj, config={"show_class_members": False}) + sig = doc["Signature"] or _clean_text_signature( + getattr(obj, "__text_signature__", None) + ) if sig: sig = re.sub("^[^(]*", "", sig) - return sig, '' + return sig, "" def _clean_text_signature(sig): @@ -231,43 +232,42 @@ def _clean_text_signature(sig): start, end = start_pattern.search(sig).span() start_sig = sig[start:end] sig = sig[end:-1] - sig = re.sub(r'^\$(self|module|type)(,\s|$)','' , sig, count=1) - sig = re.sub(r'(^|(?<=,\s))/,\s\*', '*', sig, count=1) - return start_sig + sig + ')' + sig = re.sub(r"^\$(self|module|type)(,\s|$)", "", sig, count=1) + sig = re.sub(r"(^|(?<=,\s))/,\s\*", "*", sig, count=1) + return start_sig + sig + ")" def setup(app, get_doc_object_=get_doc_object): - if not hasattr(app, 'add_config_value'): + if not hasattr(app, "add_config_value"): return # probably called by nose, better bail out global get_doc_object get_doc_object = get_doc_object_ - app.setup_extension('sphinx.ext.autosummary') - app.connect('config-inited', update_config) - app.connect('autodoc-process-docstring', mangle_docstrings) - app.connect('autodoc-process-signature', mangle_signature) - app.connect('doctree-read', relabel_references) - app.connect('doctree-resolved', clean_backrefs) - app.add_config_value('numpydoc_use_plots', None, False) - app.add_config_value('numpydoc_use_blockquotes', None, False) - app.add_config_value('numpydoc_show_class_members', True, True) - app.add_config_value('numpydoc_show_inherited_class_members', True, True) - app.add_config_value('numpydoc_class_members_toctree', True, True) - app.add_config_value('numpydoc_citation_re', '[a-z0-9_.-]+', True) - app.add_config_value('numpydoc_attributes_as_param_list', True, True) - app.add_config_value('numpydoc_xref_param_type', False, True) - app.add_config_value('numpydoc_xref_aliases', dict(), True) - app.add_config_value('numpydoc_xref_ignore', set(), True) - app.add_config_value('numpydoc_validation_checks', set(), True) - app.add_config_value('numpydoc_validation_exclude', set(), False) + app.setup_extension("sphinx.ext.autosummary") + app.connect("config-inited", update_config) + app.connect("autodoc-process-docstring", mangle_docstrings) + app.connect("autodoc-process-signature", mangle_signature) + app.connect("doctree-read", relabel_references) + app.connect("doctree-resolved", clean_backrefs) + app.add_config_value("numpydoc_use_plots", None, False) + app.add_config_value("numpydoc_use_blockquotes", None, False) + app.add_config_value("numpydoc_show_class_members", True, True) + app.add_config_value("numpydoc_show_inherited_class_members", True, True) + app.add_config_value("numpydoc_class_members_toctree", True, True) + app.add_config_value("numpydoc_citation_re", "[a-z0-9_.-]+", True) + app.add_config_value("numpydoc_attributes_as_param_list", True, True) + app.add_config_value("numpydoc_xref_param_type", False, True) + app.add_config_value("numpydoc_xref_aliases", dict(), True) + app.add_config_value("numpydoc_xref_ignore", set(), True) + app.add_config_value("numpydoc_validation_checks", set(), True) + app.add_config_value("numpydoc_validation_exclude", set(), False) # Extra mangling domains app.add_domain(NumpyPythonDomain) app.add_domain(NumpyCDomain) - metadata = {'version': __version__, - 'parallel_read_safe': True} + metadata = {"version": __version__, "parallel_read_safe": True} return metadata @@ -332,31 +332,32 @@ class ManglingDomainBase: def wrap_mangling_directives(self): for name, objtype in list(self.directive_mangling_map.items()): self.directives[name] = wrap_mangling_directive( - self.directives[name], objtype) + self.directives[name], objtype + ) class NumpyPythonDomain(ManglingDomainBase, PythonDomain): - name = 'np' + name = "np" directive_mangling_map = { - 'function': 'function', - 'class': 'class', - 'exception': 'class', - 'method': 'function', - 'classmethod': 'function', - 'staticmethod': 'function', - 'attribute': 'attribute', + "function": "function", + "class": "class", + "exception": "class", + "method": "function", + "classmethod": "function", + "staticmethod": "function", + "attribute": "attribute", } indices = [] class NumpyCDomain(ManglingDomainBase, CDomain): - name = 'np-c' + name = "np-c" directive_mangling_map = { - 'function': 'function', - 'member': 'attribute', - 'macro': 'function', - 'type': 'class', - 'var': 'object', + "function": "function", + "member": "attribute", + "macro": "function", + "type": "class", + "var": "object", } @@ -412,7 +413,7 @@ def match_items(lines, content_old): items_new.append(items_old[j]) if line.strip() and j < len(lines_old) - 1: j += 1 - assert(len(items_new) == len(lines)) + assert len(items_new) == len(lines) return items_new @@ -423,7 +424,7 @@ def wrap_mangling_directive(base_directive, objtype): name = None if self.arguments: - m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0]) + m = re.match(r"^(.*\s+)?(.*?)(\(.*)?", self.arguments[0]) name = m.group(2).strip() if not name: @@ -433,8 +434,7 @@ def wrap_mangling_directive(base_directive, objtype): mangle_docstrings(env.app, objtype, name, None, None, lines) if self.content: items = match_items(lines, self.content) - self.content = ViewList(lines, items=items, - parent=self.content.parent) + self.content = ViewList(lines, items=items, parent=self.content.parent) return base_directive.run(self) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 9644008..01447a0 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -8,20 +8,19 @@ import jinja2 from numpydoc.numpydoc import update_config from numpydoc.xref import DEFAULT_LINKS -from numpydoc.docscrape import ( - NumpyDocString, - FunctionDoc, - ClassDoc, - ParseError +from numpydoc.docscrape import NumpyDocString, FunctionDoc, ClassDoc, ParseError +from numpydoc.docscrape_sphinx import ( + SphinxDocString, + SphinxClassDoc, + SphinxFunctionDoc, + get_doc_object, ) -from numpydoc.docscrape_sphinx import (SphinxDocString, SphinxClassDoc, - SphinxFunctionDoc, get_doc_object) import pytest from pytest import raises as assert_raises from pytest import warns as assert_warns -doc_txt = '''\ +doc_txt = """\ numpy.multivariate_normal(mean, cov, shape=None, spam=None) Draw values from a multivariate normal distribution with specified @@ -131,11 +130,12 @@ doc_txt = '''\ .. index:: random :refguide: random;distributions, random;gauss - ''' + """ -@pytest.fixture(params=['','\n '], ids=["flush", "newline_indented"]) + +@pytest.fixture(params=["", "\n "], ids=["flush", "newline_indented"]) def doc(request): - return NumpyDocString(request.param+doc_txt) + return NumpyDocString(request.param + doc_txt) doc_yields_txt = """ @@ -173,92 +173,92 @@ doc_sent = NumpyDocString(doc_sent_txt) def test_signature(doc): - assert doc['Signature'].startswith('numpy.multivariate_normal(') - assert doc['Signature'].endswith('spam=None)') + assert doc["Signature"].startswith("numpy.multivariate_normal(") + assert doc["Signature"].endswith("spam=None)") def test_summary(doc): - assert doc['Summary'][0].startswith('Draw values') - assert doc['Summary'][-1].endswith('covariance.') + assert doc["Summary"][0].startswith("Draw values") + assert doc["Summary"][-1].endswith("covariance.") def test_extended_summary(doc): - assert doc['Extended Summary'][0].startswith('The multivariate normal') + assert doc["Extended Summary"][0].startswith("The multivariate normal") def test_parameters(doc): - assert len(doc['Parameters']) == 4 - names = [n for n, _, _ in doc['Parameters']] - assert all(a == b for a, b in zip(names, ['mean', 'cov', 'shape'])) + assert len(doc["Parameters"]) == 4 + names = [n for n, _, _ in doc["Parameters"]] + assert all(a == b for a, b in zip(names, ["mean", "cov", "shape"])) - arg, arg_type, desc = doc['Parameters'][1] - assert arg_type == '(N, N) ndarray' - assert desc[0].startswith('Covariance matrix') - assert doc['Parameters'][0][-1][-1] == ' (1+2+3)/3' + arg, arg_type, desc = doc["Parameters"][1] + assert arg_type == "(N, N) ndarray" + assert desc[0].startswith("Covariance matrix") + assert doc["Parameters"][0][-1][-1] == " (1+2+3)/3" - arg, arg_type, desc = doc['Parameters'][2] - assert arg == 'shape' - assert arg_type == 'tuple of ints' - assert desc[0].startswith('Given') - assert doc['Parameters'][0][-1][-1] == ' (1+2+3)/3' + arg, arg_type, desc = doc["Parameters"][2] + assert arg == "shape" + assert arg_type == "tuple of ints" + assert desc[0].startswith("Given") + assert doc["Parameters"][0][-1][-1] == " (1+2+3)/3" - arg, arg_type, desc = doc['Parameters'][3] - assert arg == 'dtype' - assert arg_type == 'data type object, optional (default : float)' - assert desc[0].startswith('The type and size') + arg, arg_type, desc = doc["Parameters"][3] + assert arg == "dtype" + assert arg_type == "data type object, optional (default : float)" + assert desc[0].startswith("The type and size") def test_other_parameters(doc): - assert len(doc['Other Parameters']) == 1 - assert [n for n, _, _ in doc['Other Parameters']] == ['spam'] - arg, arg_type, desc = doc['Other Parameters'][0] - assert arg_type == 'parrot' - assert desc[0].startswith('A parrot off its mortal coil') - + assert len(doc["Other Parameters"]) == 1 + assert [n for n, _, _ in doc["Other Parameters"]] == ["spam"] + arg, arg_type, desc = doc["Other Parameters"][0] + assert arg_type == "parrot" + assert desc[0].startswith("A parrot off its mortal coil") def test_returns(doc): - assert len(doc['Returns']) == 3 - arg, arg_type, desc = doc['Returns'][0] - assert arg == 'out' - assert arg_type == 'ndarray' - assert desc[0].startswith('The drawn samples') - assert desc[-1].endswith('distribution.') + assert len(doc["Returns"]) == 3 + arg, arg_type, desc = doc["Returns"][0] + assert arg == "out" + assert arg_type == "ndarray" + assert desc[0].startswith("The drawn samples") + assert desc[-1].endswith("distribution.") - arg, arg_type, desc = doc['Returns'][1] - assert arg == '' - assert arg_type == 'list of str' - assert desc[0].startswith('This is not a real') - assert desc[-1].endswith('anonymous return values.') + arg, arg_type, desc = doc["Returns"][1] + assert arg == "" + assert arg_type == "list of str" + assert desc[0].startswith("This is not a real") + assert desc[-1].endswith("anonymous return values.") - arg, arg_type, desc = doc['Returns'][2] - assert arg == '' - assert arg_type == 'no_description' - assert not ''.join(desc).strip() + arg, arg_type, desc = doc["Returns"][2] + assert arg == "" + assert arg_type == "no_description" + assert not "".join(desc).strip() def test_yields(): - section = doc_yields['Yields'] + section = doc_yields["Yields"] assert len(section) == 3 - truth = [('a', 'int', 'apples.'), - ('b', 'int', 'bananas.'), - ('', 'int', 'unknowns.')] + truth = [ + ("a", "int", "apples."), + ("b", "int", "bananas."), + ("", "int", "unknowns."), + ] for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(section, truth): assert arg == arg_ assert arg_type == arg_type_ - assert desc[0].startswith('The number of') + assert desc[0].startswith("The number of") assert desc[0].endswith(end) def test_sent(): - section = doc_sent['Receives'] + section = doc_sent["Receives"] assert len(section) == 2 - truth = [('b', 'int', 'bananas.'), - ('c', 'int', 'oranges.')] + truth = [("b", "int", "bananas."), ("c", "int", "oranges.")] for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(section, truth): assert arg == arg_ assert arg_type == arg_type_ - assert desc[0].startswith('The number of') + assert desc[0].startswith("The number of") assert desc[0].endswith(end) @@ -311,6 +311,7 @@ That should break... Second note. """ + def spam(self, a, b): """Spam\n\nSpam spam.""" pass @@ -340,40 +341,40 @@ That should break... def test_notes(doc): - assert doc['Notes'][0].startswith('Instead') - assert doc['Notes'][-1].endswith('definite.') - assert len(doc['Notes']) == 17 + assert doc["Notes"][0].startswith("Instead") + assert doc["Notes"][-1].endswith("definite.") + assert len(doc["Notes"]) == 17 def test_references(doc): - assert doc['References'][0].startswith('..') - assert doc['References'][-1].endswith('2001.') + assert doc["References"][0].startswith("..") + assert doc["References"][-1].endswith("2001.") def test_examples(doc): - assert doc['Examples'][0].startswith('>>>') - assert doc['Examples'][-1].endswith('True]') + assert doc["Examples"][0].startswith(">>>") + assert doc["Examples"][-1].endswith("True]") def test_index(doc): - assert doc['index']['default'] == 'random' - assert len(doc['index']) == 2 - assert len(doc['index']['refguide']) == 2 + assert doc["index"]["default"] == "random" + assert len(doc["index"]) == 2 + assert len(doc["index"]["refguide"]) == 2 def _strip_blank_lines(s): "Remove leading, trailing and multiple blank lines" - s = re.sub(r'^\s*\n', '', s) - s = re.sub(r'\n\s*$', '', s) - s = re.sub(r'\n\s*\n', r'\n\n', s) + s = re.sub(r"^\s*\n", "", s) + s = re.sub(r"\n\s*$", "", s) + s = re.sub(r"\n\s*\n", r"\n\n", s) return s def line_by_line_compare(a, b, n_lines=None): a = textwrap.dedent(a) b = textwrap.dedent(b) - a = [l.rstrip() for l in _strip_blank_lines(a).split('\n')][:n_lines] - b = [l.rstrip() for l in _strip_blank_lines(b).split('\n')][:n_lines] + a = [l.rstrip() for l in _strip_blank_lines(a).split("\n")][:n_lines] + b = [l.rstrip() for l in _strip_blank_lines(b).split("\n")][:n_lines] assert len(a) == len(b) for ii, (aa, bb) in enumerate(zip(a, b)): assert aa == bb @@ -383,8 +384,9 @@ def test_str(doc): # doc_txt has the order of Notes and See Also sections flipped. # This should be handled automatically, and so, one thing this test does # is to make sure that See Also precedes Notes in the output. - line_by_line_compare(str(doc), -"""numpy.multivariate_normal(mean, cov, shape=None, spam=None) + line_by_line_compare( + str(doc), + """numpy.multivariate_normal(mean, cov, shape=None, spam=None) Draw values from a multivariate normal distribution with specified mean and covariance. @@ -494,12 +496,14 @@ standard deviation: [True, True] .. index:: random - :refguide: random;distributions, random;gauss""") + :refguide: random;distributions, random;gauss""", + ) def test_yield_str(): - line_by_line_compare(str(doc_yields), -"""Test generator + line_by_line_compare( + str(doc_yields), + """Test generator Yields ------ @@ -509,12 +513,14 @@ b : int The number of bananas. int The number of unknowns. -""") +""", + ) def test_receives_str(): - line_by_line_compare(str(doc_sent), -"""Test generator + line_by_line_compare( + str(doc_sent), + """Test generator Yields ------ @@ -527,29 +533,44 @@ b : int The number of bananas. c : int The number of oranges. -""") +""", + ) def test_no_index_in_str(): - assert "index" not in str(NumpyDocString("""Test idx + assert "index" not in str( + NumpyDocString( + """Test idx - """)) + """ + ) + ) - assert "index" in str(NumpyDocString("""Test idx + assert "index" in str( + NumpyDocString( + """Test idx .. index :: random - """)) + """ + ) + ) - assert "index" in str(NumpyDocString("""Test idx + assert "index" in str( + NumpyDocString( + """Test idx .. index :: foo - """)) + """ + ) + ) + def test_sphinx_str(): sphinx_doc = SphinxDocString(doc_txt) - line_by_line_compare(str(sphinx_doc), -""" + line_by_line_compare( + str(sphinx_doc), + """ .. index:: random single: random;distributions, random;gauss @@ -668,13 +689,15 @@ standard deviation: >>> print(list((x[0, 0, :] - mean) < 0.6)) [True, True] -""") +""", + ) def test_sphinx_yields_str(): sphinx_doc = SphinxDocString(doc_yields_txt) - line_by_line_compare(str(sphinx_doc), -"""Test generator + line_by_line_compare( + str(sphinx_doc), + """Test generator :Yields: @@ -686,10 +709,12 @@ def test_sphinx_yields_str(): int The number of unknowns. -""") +""", + ) -doc2 = NumpyDocString(""" +doc2 = NumpyDocString( + """ Returns array of indices of the maximum values of along the given axis. Parameters @@ -698,39 +723,43 @@ doc2 = NumpyDocString(""" Array to look in. axis : {None, integer} If None, the index is into the flattened array, otherwise along - the specified axis""") + the specified axis""" +) def test_parameters_without_extended_description(): - assert len(doc2['Parameters']) == 2 + assert len(doc2["Parameters"]) == 2 -doc3 = NumpyDocString(""" +doc3 = NumpyDocString( + """ my_signature(*params, **kwds) Return this and that. - """) + """ +) def test_escape_stars(): - signature = str(doc3).split('\n')[0] - assert signature == r'my_signature(\*params, \*\*kwds)' + signature = str(doc3).split("\n")[0] + assert signature == r"my_signature(\*params, \*\*kwds)" def my_func(a, b, **kwargs): pass fdoc = FunctionDoc(func=my_func) - assert fdoc['Signature'] == '' + assert fdoc["Signature"] == "" doc4 = NumpyDocString( """a.conj() - Return an array with all complex-valued elements conjugated.""") + Return an array with all complex-valued elements conjugated.""" +) def test_empty_extended_summary(): - assert doc4['Extended Summary'] == [] + assert doc4["Extended Summary"] == [] doc5 = NumpyDocString( @@ -746,34 +775,37 @@ doc5 = NumpyDocString( ----- SomeWarning If needed - """) + """ +) def test_raises(): - assert len(doc5['Raises']) == 1 - param = doc5['Raises'][0] - assert param.name == '' - assert param.type == 'LinAlgException' - assert param.desc == ['If array is singular.'] + assert len(doc5["Raises"]) == 1 + param = doc5["Raises"][0] + assert param.name == "" + assert param.type == "LinAlgException" + assert param.desc == ["If array is singular."] def test_warns(): - assert len(doc5['Warns']) == 1 - param = doc5['Warns'][0] - assert param.name == '' - assert param.type == 'SomeWarning' - assert param.desc == ['If needed'] + assert len(doc5["Warns"]) == 1 + param = doc5["Warns"][0] + assert param.name == "" + assert param.type == "SomeWarning" + assert param.desc == ["If needed"] + # see numpydoc/numpydoc #281 # we want to correctly parse "See Also" both in docstrings both like -#"""foo +# """foo # and -#""" -#foo -@pytest.mark.parametrize('prefix', ['', '\n ']) +# """ +# foo +@pytest.mark.parametrize("prefix", ["", "\n "]) def test_see_also(prefix): doc6 = NumpyDocString( - prefix + """z(x,theta) + prefix + + """z(x,theta) See Also -------- @@ -789,51 +821,65 @@ def test_see_also(prefix): :obj:`~baz.obj_r` :class:`class_j`: fubar foobar - """) + """ + ) - assert len(doc6['See Also']) == 10 - for funcs, desc in doc6['See Also']: + assert len(doc6["See Also"]) == 10 + for funcs, desc in doc6["See Also"]: for func, role in funcs: - if func in ('func_a', 'func_b', 'func_c', 'func_f', - 'func_g', 'func_h', 'func_j', 'func_k', 'baz.obj_q', - 'func_f1', 'func_g1', 'func_h1', 'func_j1', - '~baz.obj_r'): + if func in ( + "func_a", + "func_b", + "func_c", + "func_f", + "func_g", + "func_h", + "func_j", + "func_k", + "baz.obj_q", + "func_f1", + "func_g1", + "func_h1", + "func_j1", + "~baz.obj_r", + ): assert not desc, str([func, desc]) - elif func in ('func_f2', 'func_g2', 'func_h2', 'func_j2'): + elif func in ("func_f2", "func_g2", "func_h2", "func_j2"): assert desc, str([func, desc]) else: assert desc, str([func, desc]) - if func == 'func_h': - assert role == 'meth' - elif func == 'baz.obj_q' or func == '~baz.obj_r': - assert role == 'obj' - elif func == 'class_j': - assert role == 'class' - elif func in ['func_h1', 'func_h2']: - assert role == 'meth' + if func == "func_h": + assert role == "meth" + elif func == "baz.obj_q" or func == "~baz.obj_r": + assert role == "obj" + elif func == "class_j": + assert role == "class" + elif func in ["func_h1", "func_h2"]: + assert role == "meth" else: assert role is None, str([func, role]) - if func == 'func_d': - assert desc == ['some equivalent func'] - elif func == 'foo.func_e': - assert desc == ['some other func over', 'multiple lines'] - elif func == 'class_j': - assert desc == ['fubar', 'foobar'] - elif func in ['func_f2', 'func_g2', 'func_h2', 'func_j2']: - assert desc == ['description of multiple'], str([desc, ['description of multiple']]) + if func == "func_d": + assert desc == ["some equivalent func"] + elif func == "foo.func_e": + assert desc == ["some other func over", "multiple lines"] + elif func == "class_j": + assert desc == ["fubar", "foobar"] + elif func in ["func_f2", "func_g2", "func_h2", "func_j2"]: + assert desc == ["description of multiple"], str( + [desc, ["description of multiple"]] + ) def test_see_also_parse_error(): - text = ( - """ + text = """ z(x,theta) See Also -------- :func:`~foo` - """) + """ with pytest.raises(ValueError, match="See Also entry ':func:`~foo`'"): NumpyDocString(text) @@ -848,17 +894,21 @@ def test_see_also_print(): goes here func_d """ + pass - s = str(FunctionDoc(Dummy, role='func')) - assert(':func:`func_a`, :func:`func_b`' in s) - assert(' some relationship' in s) - assert(':func:`func_d`' in s) + s = str(FunctionDoc(Dummy, role="func")) + assert ":func:`func_a`, :func:`func_b`" in s + assert " some relationship" in s + assert ":func:`func_d`" in s def test_see_also_trailing_comma_warning(): - warnings.filterwarnings('error') - with assert_warns(Warning, match='Unexpected comma or period after function list at index 43 of line .*'): + warnings.filterwarnings("error") + with assert_warns( + Warning, + match="Unexpected comma or period after function list at index 43 of line .*", + ): NumpyDocString( """ z(x,theta) @@ -868,7 +918,8 @@ def test_see_also_trailing_comma_warning(): func_f2, func_g2, :meth:`func_h2`, func_j2, : description of multiple :class:`class_j`: fubar foobar - """) + """ + ) def test_unknown_section(): @@ -887,6 +938,7 @@ This should be ignored and warned about ---- This class has a nope section. """ + pass with pytest.warns(UserWarning, match="Unknown section Mope") as record: @@ -901,17 +953,21 @@ This should be ignored and warned about assert len(record) == 1 -doc7 = NumpyDocString(""" +doc7 = NumpyDocString( + """ Doc starts on second line. - """) + """ +) def test_empty_first_line(): - assert doc7['Summary'][0].startswith('Doc starts') + assert doc7["Summary"][0].startswith("Doc starts") -doc8 = NumpyDocString(""" + +doc8 = NumpyDocString( + """ Parameters with colon and no types: @@ -920,21 +976,27 @@ doc8 = NumpyDocString(""" data : some stuff, technically invalid - """) + """ +) def test_trailing_colon(): - assert doc8['Parameters'][0].name == 'data' + assert doc8["Parameters"][0].name == "data" def test_no_summary(): - str(SphinxDocString(""" + str( + SphinxDocString( + """ Parameters - ----------""")) + ----------""" + ) + ) def test_unicode(): - doc = SphinxDocString(""" + doc = SphinxDocString( + """ öäöäöäöäöåååå öäöäöäööäååå @@ -949,33 +1011,41 @@ def test_unicode(): ååå : ööö äää - """) - assert isinstance(doc['Summary'][0], str) - assert doc['Summary'][0] == 'öäöäöäöäöåååå' + """ + ) + assert isinstance(doc["Summary"][0], str) + assert doc["Summary"][0] == "öäöäöäöäöåååå" def test_plot_examples(): cfg = dict(use_plots=True) - doc = SphinxDocString(""" + doc = SphinxDocString( + """ Examples -------- >>> import matplotlib.pyplot as plt >>> plt.plot([1,2,3],[4,5,6]) >>> plt.show() - """, config=cfg) - assert 'plot::' in str(doc), str(doc) + """, + config=cfg, + ) + assert "plot::" in str(doc), str(doc) - doc = SphinxDocString(""" + doc = SphinxDocString( + """ Examples -------- >>> from matplotlib import pyplot as plt >>> plt.plot([1,2,3],[4,5,6]) >>> plt.show() - """, config=cfg) - assert 'plot::' in str(doc), str(doc) + """, + config=cfg, + ) + assert "plot::" in str(doc), str(doc) - doc = SphinxDocString(""" + doc = SphinxDocString( + """ Examples -------- .. plot:: @@ -983,13 +1053,16 @@ def test_plot_examples(): import matplotlib.pyplot as plt plt.plot([1,2,3],[4,5,6]) plt.show() - """, config=cfg) - assert str(doc).count('plot::') == 1, str(doc) + """, + config=cfg, + ) + assert str(doc).count("plot::") == 1, str(doc) def test_use_blockquotes(): cfg = dict(use_blockquotes=True) - doc = SphinxDocString(""" + doc = SphinxDocString( + """ Parameters ---------- abc : def @@ -1003,8 +1076,12 @@ def test_use_blockquotes(): GHI JKL MNO - """, config=cfg) - line_by_line_compare(str(doc), ''' + """, + config=cfg, + ) + line_by_line_compare( + str(doc), + """ :Parameters: **abc** : def @@ -1024,22 +1101,25 @@ def test_use_blockquotes(): JKL MNO - ''') + """, + ) def test_class_members(): - class Dummy: """ Dummy class. """ + def spam(self, a, b): """Spam\n\nSpam spam.""" pass + def ham(self, c, d): """Cheese\n\nNo cheese.""" pass + @property def spammity(self): """Spammity index""" @@ -1047,32 +1127,34 @@ def test_class_members(): class Ignorable: """local class, to be ignored""" + pass for cls in (ClassDoc, SphinxClassDoc): doc = cls(Dummy, config=dict(show_class_members=False)) - assert 'Methods' not in str(doc), (cls, str(doc)) - assert 'spam' not in str(doc), (cls, str(doc)) - assert 'ham' not in str(doc), (cls, str(doc)) - assert 'spammity' not in str(doc), (cls, str(doc)) - assert 'Spammity index' not in str(doc), (cls, str(doc)) + assert "Methods" not in str(doc), (cls, str(doc)) + assert "spam" not in str(doc), (cls, str(doc)) + assert "ham" not in str(doc), (cls, str(doc)) + assert "spammity" not in str(doc), (cls, str(doc)) + assert "Spammity index" not in str(doc), (cls, str(doc)) doc = cls(Dummy, config=dict(show_class_members=True)) - assert 'Methods' in str(doc), (cls, str(doc)) - assert 'spam' in str(doc), (cls, str(doc)) - assert 'ham' in str(doc), (cls, str(doc)) - assert 'spammity' in str(doc), (cls, str(doc)) + assert "Methods" in str(doc), (cls, str(doc)) + assert "spam" in str(doc), (cls, str(doc)) + assert "ham" in str(doc), (cls, str(doc)) + assert "spammity" in str(doc), (cls, str(doc)) if cls is SphinxClassDoc: - assert '.. autosummary::' in str(doc), str(doc) + assert ".. autosummary::" in str(doc), str(doc) else: - assert 'Spammity index' in str(doc), str(doc) + assert "Spammity index" in str(doc), str(doc) class SubDummy(Dummy): """ Subclass of Dummy class. """ + def ham(self, c, d): """Cheese\n\nNo cheese.\nOverloaded Dummy.ham""" pass @@ -1082,31 +1164,35 @@ def test_class_members(): pass for cls in (ClassDoc, SphinxClassDoc): - doc = cls(SubDummy, config=dict(show_class_members=True, - show_inherited_class_members=False)) - assert 'Methods' in str(doc), (cls, str(doc)) - assert 'spam' not in str(doc), (cls, str(doc)) - assert 'ham' in str(doc), (cls, str(doc)) - assert 'bar' in str(doc), (cls, str(doc)) - assert 'spammity' not in str(doc), (cls, str(doc)) + doc = cls( + SubDummy, + config=dict(show_class_members=True, show_inherited_class_members=False), + ) + assert "Methods" in str(doc), (cls, str(doc)) + assert "spam" not in str(doc), (cls, str(doc)) + assert "ham" in str(doc), (cls, str(doc)) + assert "bar" in str(doc), (cls, str(doc)) + assert "spammity" not in str(doc), (cls, str(doc)) if cls is SphinxClassDoc: - assert '.. autosummary::' in str(doc), str(doc) + assert ".. autosummary::" in str(doc), str(doc) else: - assert 'Spammity index' not in str(doc), str(doc) + assert "Spammity index" not in str(doc), str(doc) - doc = cls(SubDummy, config=dict(show_class_members=True, - show_inherited_class_members=True)) - assert 'Methods' in str(doc), (cls, str(doc)) - assert 'spam' in str(doc), (cls, str(doc)) - assert 'ham' in str(doc), (cls, str(doc)) - assert 'bar' in str(doc), (cls, str(doc)) - assert 'spammity' in str(doc), (cls, str(doc)) + doc = cls( + SubDummy, + config=dict(show_class_members=True, show_inherited_class_members=True), + ) + assert "Methods" in str(doc), (cls, str(doc)) + assert "spam" in str(doc), (cls, str(doc)) + assert "ham" in str(doc), (cls, str(doc)) + assert "bar" in str(doc), (cls, str(doc)) + assert "spammity" in str(doc), (cls, str(doc)) if cls is SphinxClassDoc: - assert '.. autosummary::' in str(doc), str(doc) + assert ".. autosummary::" in str(doc), str(doc) else: - assert 'Spammity index' in str(doc), str(doc) + assert "Spammity index" in str(doc), str(doc) def test_duplicate_signature(): @@ -1115,13 +1201,14 @@ def test_duplicate_signature(): # docstring itself. doc = NumpyDocString( - """ + """ z(x1, x2) z(a, theta) - """) + """ + ) - assert doc['Signature'].strip() == 'z(a, theta)' + assert doc["Signature"].strip() == "z(a, theta)" class_doc_txt = """ @@ -1167,8 +1254,9 @@ class_doc_txt = """ def test_class_members_doc(): doc = ClassDoc(None, class_doc_txt) - line_by_line_compare(str(doc), - """ + line_by_line_compare( + str(doc), + """ Foo Parameters @@ -1206,7 +1294,8 @@ def test_class_members_doc(): b c - """) + """, + ) def test_class_members_doc_sphinx(): @@ -1245,8 +1334,9 @@ def test_class_members_doc_sphinx(): return None doc = SphinxClassDoc(Foo, class_doc_txt) - line_by_line_compare(str(doc), - """ + line_by_line_compare( + str(doc), + """ Foo :Parameters: @@ -1298,11 +1388,11 @@ def test_class_members_doc_sphinx(): **c** ===== ========== - """) + """, + ) def test_class_attributes_as_member_list(): - class Foo: """ Class docstring. @@ -1313,6 +1403,7 @@ def test_class_attributes_as_member_list(): Another description that is not used. """ + @property def an_attribute(self): """Test attribute""" @@ -1339,10 +1430,14 @@ def test_class_attributes_as_member_list(): def test_templated_sections(): - doc = SphinxClassDoc(None, class_doc_txt, - config={'template': jinja2.Template('{{examples}}\n{{parameters}}')}) - line_by_line_compare(str(doc), - """ + doc = SphinxClassDoc( + None, + class_doc_txt, + config={"template": jinja2.Template("{{examples}}\n{{parameters}}")}, + ) + line_by_line_compare( + str(doc), + """ .. rubric:: Examples For usage examples, see `ode`. @@ -1355,14 +1450,14 @@ def test_templated_sections(): **jac** : callable ``jac(t, y, *jac_args)`` Bbb. - """) + """, + ) def test_nonstandard_property(): # test discovery of a property that does not satisfy isinstace(.., property) class SpecialProperty: - def __init__(self, axis=0, doc=""): self.axis = axis self.__doc__ = doc @@ -1387,7 +1482,8 @@ def test_nonstandard_property(): def test_args_and_kwargs(): cfg = dict() - doc = SphinxDocString(""" + doc = SphinxDocString( + """ Parameters ---------- param1 : int @@ -1396,8 +1492,12 @@ def test_args_and_kwargs(): Arguments **kwargs : dict Keyword arguments - """, config=cfg) - line_by_line_compare(str(doc), r""" + """, + config=cfg, + ) + line_by_line_compare( + str(doc), + r""" :Parameters: **param1** : int @@ -1408,17 +1508,24 @@ def test_args_and_kwargs(): **\*\*kwargs** : dict Keyword arguments - """) + """, + ) + def test_autoclass(): - cfg=dict(show_class_members=True, - show_inherited_class_members=True) - doc = SphinxClassDoc(str, ''' + cfg = dict(show_class_members=True, show_inherited_class_members=True) + doc = SphinxClassDoc( + str, + """ A top section before .. autoclass:: str - ''', config=cfg) - line_by_line_compare(str(doc), r''' + """, + config=cfg, + ) + line_by_line_compare( + str(doc), + r""" A top section before .. autoclass:: str @@ -1426,7 +1533,9 @@ A top section before .. rubric:: Methods - ''', 5) + """, + 5, + ) xref_doc_txt = """ @@ -1489,10 +1598,10 @@ Test xref in Parameters, Other Parameters and Returns def test_xref(): xref_aliases = { - 'sequence': ':obj:`python:sequence`', + "sequence": ":obj:`python:sequence`", } - class Config(): + class Config: def __init__(self, a, b): self.numpydoc_xref_aliases = a self.numpydoc_xref_aliases_complete = b @@ -1504,18 +1613,18 @@ def test_xref(): for key in xref_aliases: xref_aliases_complete[key] = xref_aliases[key] config = Config(xref_aliases, xref_aliases_complete) - app = namedtuple('config', 'config')(config) + app = namedtuple("config", "config")(config) update_config(app) - xref_ignore = {'of', 'default', 'optional'} + xref_ignore = {"of", "default", "optional"} doc = SphinxDocString( xref_doc_txt, config=dict( xref_param_type=True, xref_aliases=xref_aliases_complete, - xref_ignore=xref_ignore - ) + xref_ignore=xref_ignore, + ), ) line_by_line_compare(str(doc), xref_doc_txt_expected) @@ -1531,11 +1640,10 @@ def test__error_location_no_name_attr(): from collections.abc import Callable # Create a Callable that doesn't have a __name__ attribute - class Foo(): + class Foo: def __call__(self): pass - foo = Foo() # foo is a Callable, but no a function instance assert isinstance(foo, Callable) @@ -1549,4 +1657,5 @@ def test__error_location_no_name_attr(): if __name__ == "__main__": import pytest + pytest.main() diff --git a/numpydoc/tests/test_full.py b/numpydoc/tests/test_full.py index 16e60e5..e0defe2 100644 --- a/numpydoc/tests/test_full.py +++ b/numpydoc/tests/test_full.py @@ -9,27 +9,28 @@ from sphinx.util.docutils import docutils_namespace # Test framework adapted from sphinx-gallery (BSD 3-clause) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def sphinx_app(tmpdir_factory): - temp_dir = (tmpdir_factory.getbasetemp() / 'root').strpath - src_dir = op.join(op.dirname(__file__), 'tinybuild') + temp_dir = (tmpdir_factory.getbasetemp() / "root").strpath + src_dir = op.join(op.dirname(__file__), "tinybuild") def ignore(src, names): - return ('_build', 'generated') + return ("_build", "generated") shutil.copytree(src_dir, temp_dir, ignore=ignore) # For testing iteration, you can get similar behavior just doing `make` # inside the tinybuild directory src_dir = temp_dir conf_dir = temp_dir - out_dir = op.join(temp_dir, '_build', 'html') - toctrees_dir = op.join(temp_dir, '_build', 'toctrees') - kwargs = {'warningiserror': True, 'keep_going': True} + out_dir = op.join(temp_dir, "_build", "html") + toctrees_dir = op.join(temp_dir, "_build", "toctrees") + kwargs = {"warningiserror": True, "keep_going": True} # Avoid warnings about re-registration, see: # https://github.com/sphinx-doc/sphinx/issues/5038 with docutils_namespace(): - app = Sphinx(src_dir, conf_dir, out_dir, toctrees_dir, - buildername='html', **kwargs) + app = Sphinx( + src_dir, conf_dir, out_dir, toctrees_dir, buildername="html", **kwargs + ) # need to build within the context manager # for automodule and backrefs to work app.build(False, []) @@ -39,46 +40,48 @@ def sphinx_app(tmpdir_factory): def test_MyClass(sphinx_app): """Test that class documentation is reasonable.""" src_dir, out_dir = sphinx_app.srcdir, sphinx_app.outdir - class_rst = op.join(src_dir, 'generated', - 'numpydoc_test_module.MyClass.rst') + class_rst = op.join(src_dir, "generated", "numpydoc_test_module.MyClass.rst") with open(class_rst) as fid: rst = fid.read() - assert r'numpydoc\_test\_module' in rst # properly escaped - class_html = op.join(out_dir, 'generated', - 'numpydoc_test_module.MyClass.html') + assert r"numpydoc\_test\_module" in rst # properly escaped + class_html = op.join(out_dir, "generated", "numpydoc_test_module.MyClass.html") with open(class_html) as fid: html = fid.read() # ensure that no autodoc weirdness ($) occurs - assert '$self' not in html - assert '/,' not in html - assert '__init__' in html # inherited + assert "$self" not in html + assert "/," not in html + assert "__init__" in html # inherited # escaped * chars should no longer be preceded by \'s, # if we see a \* in the output we know it's incorrect: - assert r'\*' not in html + assert r"\*" not in html # "self" should not be in the parameter list for the class: - assert 'self,' not in html + assert "self," not in html # check xref was embedded properly (dict should link using xref): - assert 'stdtypes.html#dict' in html + assert "stdtypes.html#dict" in html def test_my_function(sphinx_app): """Test that function documentation is reasonable.""" out_dir = sphinx_app.outdir - function_html = op.join(out_dir, 'generated', - 'numpydoc_test_module.my_function.html') + function_html = op.join( + out_dir, "generated", "numpydoc_test_module.my_function.html" + ) with open(function_html) as fid: html = fid.read() - assert r'\*args' not in html - assert '*args' in html + assert r"\*args" not in html + assert "*args" in html # check xref (iterable should link using xref): - assert 'glossary.html#term-iterable' in html + assert "glossary.html#term-iterable" in html -@pytest.mark.parametrize(("html_file", "expected_length"), ( - (["index.html"], 1), - (["generated", "numpydoc_test_module.my_function.html"], 1), - (["generated", "numpydoc_test_module.MyClass.html"], 1), -)) +@pytest.mark.parametrize( + ("html_file", "expected_length"), + ( + (["index.html"], 1), + (["generated", "numpydoc_test_module.my_function.html"], 1), + (["generated", "numpydoc_test_module.MyClass.html"], 1), + ), +) def test_reference(sphinx_app, html_file, expected_length): """Test for bad references""" out_dir = sphinx_app.outdir @@ -90,4 +93,4 @@ def test_reference(sphinx_app, html_file, expected_length): assert len(reference_list) == expected_length for ref in reference_list: - assert '-' not in ref # Bad reference if it contains "-" e.g. R1896e33633d5-1 + assert "-" not in ref # Bad reference if it contains "-" e.g. R1896e33633d5-1 diff --git a/numpydoc/tests/test_main.py b/numpydoc/tests/test_main.py index 1ba2549..1f90b96 100644 --- a/numpydoc/tests/test_main.py +++ b/numpydoc/tests/test_main.py @@ -37,7 +37,7 @@ def _capture_stdout(func_name, *args, **kwargs): sys.stdout, old_stdout = f, sys.stdout try: func_name(*args, **kwargs) - return f.getvalue().strip('\n\r') + return f.getvalue().strip("\n\r") finally: sys.stdout = old_stdout @@ -65,49 +65,53 @@ def _invalid_docstring(): def test_renders_package_docstring(): - out = _capture_stdout(numpydoc.__main__.render_object, - 'numpydoc') - assert out.startswith('This package provides the numpydoc Sphinx') + out = _capture_stdout(numpydoc.__main__.render_object, "numpydoc") + assert out.startswith("This package provides the numpydoc Sphinx") def test_renders_module_docstring(): - out = _capture_stdout(numpydoc.__main__.render_object, - 'numpydoc.__main__') - assert out.startswith('Implementing `python -m numpydoc` functionality.') + out = _capture_stdout(numpydoc.__main__.render_object, "numpydoc.__main__") + assert out.startswith("Implementing `python -m numpydoc` functionality.") def test_renders_function_docstring(): - out = _capture_stdout(numpydoc.__main__.render_object, - 'numpydoc.tests.test_main._capture_stdout') - assert out.startswith('Return stdout of calling') + out = _capture_stdout( + numpydoc.__main__.render_object, "numpydoc.tests.test_main._capture_stdout" + ) + assert out.startswith("Return stdout of calling") def test_render_object_returns_correct_exit_status(): exit_status = numpydoc.__main__.render_object( - 'numpydoc.tests.test_main._capture_stdout') + "numpydoc.tests.test_main._capture_stdout" + ) assert exit_status == 0 with pytest.raises(ValueError): - numpydoc.__main__.render_object( - 'numpydoc.tests.test_main._invalid_docstring') + numpydoc.__main__.render_object("numpydoc.tests.test_main._invalid_docstring") def test_validate_detects_errors(): - out = _capture_stdout(numpydoc.__main__.validate_object, - 'numpydoc.tests.test_main._docstring_with_errors') - assert 'SS02' in out - assert 'Summary does not start with a capital letter' in out + out = _capture_stdout( + numpydoc.__main__.validate_object, + "numpydoc.tests.test_main._docstring_with_errors", + ) + assert "SS02" in out + assert "Summary does not start with a capital letter" in out exit_status = numpydoc.__main__.validate_object( - 'numpydoc.tests.test_main._docstring_with_errors') + "numpydoc.tests.test_main._docstring_with_errors" + ) assert exit_status > 0 def test_validate_perfect_docstring(): - out = _capture_stdout(numpydoc.__main__.validate_object, - 'numpydoc.tests.test_main._capture_stdout') - assert out == '' + out = _capture_stdout( + numpydoc.__main__.validate_object, "numpydoc.tests.test_main._capture_stdout" + ) + assert out == "" exit_status = numpydoc.__main__.validate_object( - 'numpydoc.tests.test_main._capture_stdout') + "numpydoc.tests.test_main._capture_stdout" + ) assert exit_status == 0 diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py index 5888d9e..0c8b6d6 100644 --- a/numpydoc/tests/test_numpydoc.py +++ b/numpydoc/tests/test_numpydoc.py @@ -1,15 +1,13 @@ import pytest from io import StringIO from copy import deepcopy -from numpydoc.numpydoc import ( - mangle_docstrings, _clean_text_signature, update_config -) +from numpydoc.numpydoc import mangle_docstrings, _clean_text_signature, update_config from numpydoc.xref import DEFAULT_LINKS from sphinx.ext.autodoc import ALL from sphinx.util import logging -class MockConfig(): +class MockConfig: numpydoc_use_plots = False numpydoc_use_blockquotes = True numpydoc_show_class_members = True @@ -20,17 +18,17 @@ class MockConfig(): numpydoc_xref_aliases_complete = deepcopy(DEFAULT_LINKS) numpydoc_xref_ignore = set() templates_path = [] - numpydoc_citation_re = '[a-z0-9_.-]+' + numpydoc_citation_re = "[a-z0-9_.-]+" numpydoc_attributes_as_param_list = True numpydoc_validation_checks = set() numpydoc_validation_exclude = set() -class MockBuilder(): +class MockBuilder: config = MockConfig() -class MockApp(): +class MockApp: config = MockConfig() builder = MockBuilder() translator = None @@ -44,60 +42,65 @@ class MockApp(): def test_mangle_docstrings(): - s = ''' + s = """ A top section before .. autoclass:: str - ''' - lines = s.split('\n') - mangle_docstrings(MockApp(), 'class', 'str', str, {}, lines) - assert 'rpartition' in [x.strip() for x in lines] - - lines = s.split('\n') - mangle_docstrings( - MockApp(), 'class', 'str', str, {'members': ['upper']}, lines) - assert 'rpartition' not in [x.strip() for x in lines] - assert 'upper' in [x.strip() for x in lines] - - lines = s.split('\n') + """ + lines = s.split("\n") + mangle_docstrings(MockApp(), "class", "str", str, {}, lines) + assert "rpartition" in [x.strip() for x in lines] + + lines = s.split("\n") + mangle_docstrings(MockApp(), "class", "str", str, {"members": ["upper"]}, lines) + assert "rpartition" not in [x.strip() for x in lines] + assert "upper" in [x.strip() for x in lines] + + lines = s.split("\n") + mangle_docstrings(MockApp(), "class", "str", str, {"exclude-members": ALL}, lines) + assert "rpartition" not in [x.strip() for x in lines] + assert "upper" not in [x.strip() for x in lines] + + lines = s.split("\n") mangle_docstrings( - MockApp(), 'class', 'str', str, {'exclude-members': ALL}, lines) - assert 'rpartition' not in [x.strip() for x in lines] - assert 'upper' not in [x.strip() for x in lines] - - lines = s.split('\n') - mangle_docstrings( - MockApp(), 'class', 'str', str, {'exclude-members': ['upper']}, lines) - assert 'rpartition' in [x.strip() for x in lines] - assert 'upper' not in [x.strip() for x in lines] + MockApp(), "class", "str", str, {"exclude-members": ["upper"]}, lines + ) + assert "rpartition" in [x.strip() for x in lines] + assert "upper" not in [x.strip() for x in lines] def test_clean_text_signature(): assert _clean_text_signature(None) is None - assert _clean_text_signature('func($self)') == 'func()' - assert (_clean_text_signature('func($self, *args, **kwargs)') - == 'func(*args, **kwargs)') - assert _clean_text_signature('($self)') == '()' - assert _clean_text_signature('()') == '()' - assert _clean_text_signature('func()') == 'func()' - assert (_clean_text_signature('func($self, /, *args, **kwargs)') - == 'func(*args, **kwargs)') - assert (_clean_text_signature('func($self, other, /, *args, **kwargs)') - == 'func(other, *args, **kwargs)') - assert _clean_text_signature('($module)') == '()' - assert _clean_text_signature('func($type)') == 'func()' - assert (_clean_text_signature('func($self, foo="hello world")') - == 'func(foo="hello world")') - assert (_clean_text_signature("func($self, foo='hello world')") - == "func(foo='hello world')") - assert (_clean_text_signature('func(foo="hello world")') - == 'func(foo="hello world")') - assert (_clean_text_signature('func(foo="$self")') - == 'func(foo="$self")') - assert (_clean_text_signature('func($self, foo="$self")') - == 'func(foo="$self")') - assert _clean_text_signature('func(self, other)') == 'func(self, other)' - assert _clean_text_signature('func($self, *args)') == 'func(*args)' + assert _clean_text_signature("func($self)") == "func()" + assert ( + _clean_text_signature("func($self, *args, **kwargs)") == "func(*args, **kwargs)" + ) + assert _clean_text_signature("($self)") == "()" + assert _clean_text_signature("()") == "()" + assert _clean_text_signature("func()") == "func()" + assert ( + _clean_text_signature("func($self, /, *args, **kwargs)") + == "func(*args, **kwargs)" + ) + assert ( + _clean_text_signature("func($self, other, /, *args, **kwargs)") + == "func(other, *args, **kwargs)" + ) + assert _clean_text_signature("($module)") == "()" + assert _clean_text_signature("func($type)") == "func()" + assert ( + _clean_text_signature('func($self, foo="hello world")') + == 'func(foo="hello world")' + ) + assert ( + _clean_text_signature("func($self, foo='hello world')") + == "func(foo='hello world')" + ) + assert _clean_text_signature('func(foo="hello world")') == 'func(foo="hello world")' + assert _clean_text_signature('func(foo="$self")') == 'func(foo="$self")' + assert _clean_text_signature('func($self, foo="$self")') == 'func(foo="$self")' + assert _clean_text_signature("func(self, other)") == "func(self, other)" + assert _clean_text_signature("func($self, *args)") == "func(*args)" @pytest.fixture @@ -109,22 +112,23 @@ def f(): Expect SA01 and EX01 errors if validation enabled. """ pass + return _function_without_seealso_and_examples @pytest.mark.parametrize( ( - 'numpydoc_validation_checks', - 'expected_warn', - 'non_warnings', + "numpydoc_validation_checks", + "expected_warn", + "non_warnings", ), ( # Validation configured off - expect no warnings (set(), [], []), # Validation on with expected warnings - ({'SA01', 'EX01'}, ('SA01', 'EX01'), []), + ({"SA01", "EX01"}, ("SA01", "EX01"), []), # Validation on with only one activated check - ({'SA01'}, ('SA01',), ('EX01',)), + ({"SA01"}, ("SA01",), ("EX01",)), ), ) def test_mangle_docstring_validation_warnings( @@ -142,7 +146,7 @@ def test_mangle_docstring_validation_warnings( status, warning = StringIO(), StringIO() logging.setup(app, status, warning) # Run mangle docstrings with the above configuration - mangle_docstrings(app, 'function', 'f', f, None, f.__doc__.split('\n')) + mangle_docstrings(app, "function", "f", f, None, f.__doc__.split("\n")) # Assert that all (and only) expected warnings are logged warnings = warning.getvalue() for w in expected_warn: @@ -155,6 +159,7 @@ def test_mangle_docstring_validation_exclude(): def function_with_bad_docstring(): """ This docstring will raise docstring validation warnings.""" + app = MockApp() app.config.numpydoc_validation_checks = {"all"} app.config.numpydoc_validation_exclude = [r"_bad_"] @@ -166,11 +171,11 @@ def test_mangle_docstring_validation_exclude(): # Run mangle docstrings on function_with_bad_docstring mangle_docstrings( app, - 'function', + "function", function_with_bad_docstring.__name__, function_with_bad_docstring, None, - function_with_bad_docstring.__doc__.split('\n'), + function_with_bad_docstring.__doc__.split("\n"), ) # Validation is skipped due to exclude pattern matching fn name, therefore # no warnings expected @@ -195,4 +200,5 @@ def test_update_config_exclude_str(): if __name__ == "__main__": import pytest + pytest.main() diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py index 3f2d442..97c621e 100644 --- a/numpydoc/tests/test_validate.py +++ b/numpydoc/tests/test_validate.py @@ -22,6 +22,7 @@ class GoodDocStrings: -------- >>> result = 1 + 1 """ + def one_liner(self): """Allow one liner docstrings (including quotes).""" # This should fail, but not because of the position of the quotes @@ -248,7 +249,7 @@ class GoodDocStrings: """ return 1 - def contains(self, pat, case=True, na=float('NaN')): + def contains(self, pat, case=True, na=float("NaN")): """ Return whether each value contains `pat`. @@ -483,8 +484,7 @@ class GoodDocStrings: class BadGenericDocStrings: - """Everything here has a bad docstring - """ + """Everything here has a bad docstring""" def func(self): @@ -656,6 +656,7 @@ class BadGenericDocStrings: """ pass + class WarnGenericFormat: """ Those contains things that _may_ be incorrect formatting. @@ -735,6 +736,7 @@ class BadParameters: """ Everything here has a problem with its Parameters section. """ + def no_type(self, value): """ Lacks the type. @@ -1083,10 +1085,15 @@ class TestValidator: return base_path def test_one_liner(self, capsys): - result = validate_one(self._import_path(klass="GoodDocStrings", func='one_liner')) + result = validate_one( + self._import_path(klass="GoodDocStrings", func="one_liner") + ) errors = " ".join(err[1] for err in result["errors"]) - assert 'should start in the line immediately after the opening quotes' not in errors - assert 'should be placed in the line after the last text' not in errors + assert ( + "should start in the line immediately after the opening quotes" + not in errors + ) + assert "should be placed in the line after the last text" not in errors def test_good_class(self, capsys): errors = validate_one(self._import_path(klass="GoodDocStrings"))["errors"] @@ -1137,9 +1144,8 @@ class TestValidator: with pytest.warns(UserWarning): errors = validate_one( self._import_path(klass="WarnGenericFormat", func=func) # noqa:F821 - ) - assert 'is too short' in w.msg - + ) + assert "is too short" in w.msg @pytest.mark.parametrize( "func", @@ -1219,8 +1225,10 @@ class TestValidator: ( "BadSummaries", "wrong_line", - ("should start in the line immediately after the opening quotes", - "should be placed in the line after the last text"), + ( + "should start in the line immediately after the opening quotes", + "should be placed in the line after the last text", + ), ), ("BadSummaries", "no_punctuation", ("Summary does not end with a period",)), ( @@ -1381,7 +1389,7 @@ class TestValidator: with warnings.catch_warnings(record=True) as w: result = validate_one(self._import_path(klass=klass, func=func)) if len(w): - assert all('Unknown section' in str(ww.message) for ww in w) + assert all("Unknown section" in str(ww.message) for ww in w) for msg in msgs: assert msg in " ".join(err[1] for err in result["errors"]) diff --git a/numpydoc/tests/test_xref.py b/numpydoc/tests/test_xref.py index 175cb98..b4b3125 100644 --- a/numpydoc/tests/test_xref.py +++ b/numpydoc/tests/test_xref.py @@ -196,23 +196,25 @@ dict[tuple(str, str), int] :class:`python:dict`\[:class:`python:tuple`\(:class:`python:str`, :class:`python:str`), :class:`python:int`] """ # noqa: E501 -xref_ignore = {'or', 'in', 'of', 'default', 'optional'} +xref_ignore = {"or", "in", "of", "default", "optional"} @pytest.mark.parametrize( - ('param_type', 'expected_result'), - [tuple(s.split('\n')) for s in data.strip().split('\n\n')] + ("param_type", "expected_result"), + [tuple(s.split("\n")) for s in data.strip().split("\n\n")], ) def test_make_xref(param_type, expected_result): assert make_xref(param_type, xref_aliases, xref_ignore) == expected_result + @pytest.mark.parametrize( - ('param_type', 'expected_result'), - [tuple(s.split('\n')) for s in data_ignore_obj.strip().split('\n\n')] + ("param_type", "expected_result"), + [tuple(s.split("\n")) for s in data_ignore_obj.strip().split("\n\n")], ) def test_make_xref_ignore_unknown(param_type, expected_result): assert make_xref(param_type, xref_aliases, xref_ignore="all") == expected_result + def test_xref_ignore_is_all(): with pytest.raises(TypeError, match="must be a set or 'all'"): make_xref("array_like", xref_aliases, xref_ignore="foo") diff --git a/numpydoc/tests/tinybuild/conf.py b/numpydoc/tests/tinybuild/conf.py index a31380a..fb3b528 100644 --- a/numpydoc/tests/tinybuild/conf.py +++ b/numpydoc/tests/tinybuild/conf.py @@ -1,24 +1,26 @@ import os import sys + path = os.path.dirname(__file__) if path not in sys.path: sys.path.insert(0, path) import numpydoc_test_module # noqa + extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'numpydoc', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "numpydoc", ] -project = 'numpydoc_test_module' +project = "numpydoc_test_module" autosummary_generate = True -autodoc_default_options = {'inherited-members': None} -source_suffix = '.rst' -master_doc = 'index' # NOTE: will be changed to `root_doc` in sphinx 4 -exclude_patterns = ['_build'] +autodoc_default_options = {"inherited-members": None} +source_suffix = ".rst" +master_doc = "index" # NOTE: will be changed to `root_doc` in sphinx 4 +exclude_patterns = ["_build"] intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), + "python": ("https://docs.python.org/3", None), } nitpicky = True -highlight_language = 'python3' +highlight_language = "python3" numpydoc_class_members_toctree = False numpydoc_xref_param_type = True diff --git a/numpydoc/tests/tinybuild/numpydoc_test_module.py b/numpydoc/tests/tinybuild/numpydoc_test_module.py index bf55979..d303e9e 100644 --- a/numpydoc/tests/tinybuild/numpydoc_test_module.py +++ b/numpydoc/tests/tinybuild/numpydoc_test_module.py @@ -15,7 +15,7 @@ References .. [1] https://numpydoc.readthedocs.io """ -__all__ = ['MyClass', 'my_function'] +__all__ = ["MyClass", "my_function"] class MyClass: diff --git a/numpydoc/validate.py b/numpydoc/validate.py index d1ee978..780d895 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -16,8 +16,9 @@ from .docscrape import get_doc_object DIRECTIVES = ["versionadded", "versionchanged", "deprecated"] -DIRECTIVE_PATTERN = re.compile(r"^\s*\.\. ({})(?!::)".format('|'.join(DIRECTIVES)), - re.I | re.M) +DIRECTIVE_PATTERN = re.compile( + r"^\s*\.\. ({})(?!::)".format("|".join(DIRECTIVES)), re.I | re.M +) ALLOWED_SECTIONS = [ "Parameters", "Attributes", @@ -35,58 +36,58 @@ ALLOWED_SECTIONS = [ ] ERROR_MSGS = { "GL01": "Docstring text (summary) should start in the line immediately " - "after the opening quotes (not in the same line, or leaving a " - "blank line in between)", + "after the opening quotes (not in the same line, or leaving a " + "blank line in between)", "GL02": "Closing quotes should be placed in the line after the last text " - "in the docstring (do not close the quotes in the same line as " - "the text, or leave a blank line between the last text and the " - "quotes)", + "in the docstring (do not close the quotes in the same line as " + "the text, or leave a blank line between the last text and the " + "quotes)", "GL03": "Double line break found; please use only one blank line to " - "separate sections or paragraphs, and do not leave blank lines " - "at the end of docstrings", + "separate sections or paragraphs, and do not leave blank lines " + "at the end of docstrings", "GL05": 'Tabs found at the start of line "{line_with_tabs}", please use ' - "whitespace only", + "whitespace only", "GL06": 'Found unknown section "{section}". Allowed sections are: ' - "{allowed_sections}", + "{allowed_sections}", "GL07": "Sections are in the wrong order. Correct order is: {correct_sections}", "GL08": "The object does not have a docstring", "GL09": "Deprecation warning should precede extended summary", "GL10": "reST directives {directives} must be followed by two colons", "SS01": "No summary found (a short summary in a single line should be " - "present at the beginning of the docstring)", + "present at the beginning of the docstring)", "SS02": "Summary does not start with a capital letter", "SS03": "Summary does not end with a period", "SS04": "Summary contains heading whitespaces", "SS05": "Summary must start with infinitive verb, not third person " - '(e.g. use "Generate" instead of "Generates")', + '(e.g. use "Generate" instead of "Generates")', "SS06": "Summary should fit in a single line", "ES01": "No extended summary found", "PR01": "Parameters {missing_params} not documented", "PR02": "Unknown parameters {unknown_params}", "PR03": "Wrong parameters order. Actual: {actual_params}. " - "Documented: {documented_params}", + "Documented: {documented_params}", "PR04": 'Parameter "{param_name}" has no type', "PR05": 'Parameter "{param_name}" type should not finish with "."', "PR06": 'Parameter "{param_name}" type should use "{right_type}" instead ' - 'of "{wrong_type}"', + 'of "{wrong_type}"', "PR07": 'Parameter "{param_name}" has no description', "PR08": 'Parameter "{param_name}" description should start with a ' - "capital letter", + "capital letter", "PR09": 'Parameter "{param_name}" description should finish with "."', "PR10": 'Parameter "{param_name}" requires a space before the colon ' - "separating the parameter name and type", + "separating the parameter name and type", "RT01": "No Returns section found", "RT02": "The first line of the Returns section should contain only the " - "type, unless multiple values are being returned", + "type, unless multiple values are being returned", "RT03": "Return value has no description", "RT04": "Return value description should start with a capital letter", "RT05": 'Return value description should finish with "."', "YD01": "No Yields section found", "SA01": "See Also section not found", "SA02": "Missing period at end of description for See Also " - '"{reference_name}" reference', + '"{reference_name}" reference', "SA03": "Description should be capitalized for See Also " - '"{reference_name}" reference', + '"{reference_name}" reference', "SA04": 'Missing description for See Also "{reference_name}" reference', "EX01": "No examples section found", } @@ -133,7 +134,7 @@ class Validator: @property def name(self): - return '.'.join([self.obj.__module__, self.obj.__name__]) + return ".".join([self.obj.__module__, self.obj.__name__]) @staticmethod def _load_obj(name): @@ -164,7 +165,7 @@ class Validator: else: break else: - raise ImportError(f"No module can be imported from \"{name}\"") + raise ImportError(f'No module can be imported from "{name}"') for part in func_parts: obj = getattr(obj, part) @@ -438,8 +439,7 @@ def _check_desc(desc, code_no_desc, code_no_upper, code_no_period, **kwargs): errs.append(error(code_no_upper, **kwargs)) # Not ending in "." is only an error if the last bit is not # indented (e.g., quote or code block) - if not desc[-1].endswith(".") and \ - not desc[-1].startswith(IGNORE_STARTS): + if not desc[-1].endswith(".") and not desc[-1].startswith(IGNORE_STARTS): errs.append(error(code_no_period, **kwargs)) return errs @@ -587,8 +587,7 @@ def validate(obj_name): wrong_type=wrong_type, ) ) - errs.extend(_check_desc( - kind_desc[1], "PR07", "PR08", "PR09", param_name=param)) + errs.extend(_check_desc(kind_desc[1], "PR07", "PR08", "PR09", param_name=param)) if doc.is_function_or_method: if not doc.returns: diff --git a/numpydoc/xref.py b/numpydoc/xref.py index e3f507d..a0cc8a5 100644 --- a/numpydoc/xref.py +++ b/numpydoc/xref.py @@ -16,81 +16,81 @@ import re QUALIFIED_NAME_RE = re.compile( # e.g int, numpy.array, ~numpy.array, .class_in_current_module - r'^' - r'[~\.]?' - r'[a-zA-Z_]\w*' - r'(?:\.[a-zA-Z_]\w*)*' - r'$' + r"^" + r"[~\.]?" + r"[a-zA-Z_]\w*" + r"(?:\.[a-zA-Z_]\w*)*" + r"$" ) CONTAINER_SPLIT_RE = re.compile( # splits dict(str, int) into # ['dict', '[', 'str', ', ', 'int', ']', ''] - r'(\s*[\[\]\(\)\{\}]\s*|,\s+)' + r"(\s*[\[\]\(\)\{\}]\s*|,\s+)" ) CONTAINER_SPLIT_REJECT_RE = re.compile( # Leads to bad markup e.g. # {int}qualified_name - r'[\]\)\}]\w' + r"[\]\)\}]\w" ) DOUBLE_QUOTE_SPLIT_RE = re.compile( # splits 'callable ``f(x0, *args)`` or ``f(x0, y0, *args)``' into # ['callable ', '``f(x0, *args)``', ' or ', '``f(x0, y0, *args)``', ''] - r'(``.+?``)' + r"(``.+?``)" ) ROLE_SPLIT_RE = re.compile( # splits to preserve ReST roles - r'(:\w+:`.+?(?`', - 'boolean': ':ref:`bool `', - 'True': ':data:`python:True`', - 'False': ':data:`python:False`', - 'list': ':class:`python:list`', - 'tuple': ':class:`python:tuple`', - 'str': ':class:`python:str`', - 'string': ':class:`python:str`', - 'dict': ':class:`python:dict`', - 'float': ':class:`python:float`', - 'int': ':class:`python:int`', - 'callable': ':func:`python:callable`', - 'iterable': ':term:`python:iterable`', - 'sequence': ':term:`python:sequence`', - 'contextmanager': ':func:`python:contextlib.contextmanager`', - 'namedtuple': ':func:`python:collections.namedtuple`', - 'generator': ':term:`python:generator`', + "None": ":data:`python:None`", + "bool": ":ref:`bool `", + "boolean": ":ref:`bool `", + "True": ":data:`python:True`", + "False": ":data:`python:False`", + "list": ":class:`python:list`", + "tuple": ":class:`python:tuple`", + "str": ":class:`python:str`", + "string": ":class:`python:str`", + "dict": ":class:`python:dict`", + "float": ":class:`python:float`", + "int": ":class:`python:int`", + "callable": ":func:`python:callable`", + "iterable": ":term:`python:iterable`", + "sequence": ":term:`python:sequence`", + "contextmanager": ":func:`python:contextlib.contextmanager`", + "namedtuple": ":func:`python:collections.namedtuple`", + "generator": ":term:`python:generator`", # NumPy - 'array': 'numpy.ndarray', - 'ndarray': 'numpy.ndarray', - 'np.ndarray': 'numpy.ndarray', - 'array-like': ':term:`numpy:array_like`', - 'array_like': ':term:`numpy:array_like`', - 'scalar': ':ref:`scalar `', - 'RandomState': 'numpy.random.RandomState', - 'np.random.RandomState': 'numpy.random.RandomState', - 'np.inf': ':data:`numpy.inf`', - 'np.nan': ':data:`numpy.nan`', - 'numpy': ':mod:`numpy`', + "array": "numpy.ndarray", + "ndarray": "numpy.ndarray", + "np.ndarray": "numpy.ndarray", + "array-like": ":term:`numpy:array_like`", + "array_like": ":term:`numpy:array_like`", + "scalar": ":ref:`scalar `", + "RandomState": "numpy.random.RandomState", + "np.random.RandomState": "numpy.random.RandomState", + "np.inf": ":data:`numpy.inf`", + "np.nan": ":data:`numpy.nan`", + "numpy": ":mod:`numpy`", } @@ -125,9 +125,7 @@ def make_xref(param_type, xref_aliases, xref_ignore): wrap_unknown = False ignore_set = set() else: - raise TypeError( - f"xref_ignore must be a set or 'all', got {xref_ignore}" - ) + raise TypeError(f"xref_ignore must be a set or 'all', got {xref_ignore}") if param_type in xref_aliases: link, title = xref_aliases[param_type], param_type @@ -137,9 +135,9 @@ def make_xref(param_type, xref_aliases, xref_ignore): if QUALIFIED_NAME_RE.match(link) and link not in ignore_set: if link != title: - return f':obj:`{title} <{link}>`' + return f":obj:`{title} <{link}>`" if wrap_unknown: - return f':obj:`{link}`' + return f":obj:`{link}`" return link def _split_and_apply_re(s, pattern): @@ -160,13 +158,13 @@ def make_xref(param_type, xref_aliases, xref_ignore): # Opening brackets immediately after a role is # bad markup. Detect that and add backslash. # :role:`type`( to :role:`type`\( - if res and res[-1] == '`' and i < n-1: - next_char = tokens[i+1][0] - if next_char in '([{': - res += '\\' + if res and res[-1] == "`" and i < n - 1: + next_char = tokens[i + 1][0] + if next_char in "([{": + res += "\\" results.append(res) - return ''.join(results) + return "".join(results) return s # The cases are dealt with in an order the prevents @@ -178,15 +176,15 @@ def make_xref(param_type, xref_aliases, xref_ignore): # - join the results with the pattern # Unsplittable literal - if '``' in param_type: + if "``" in param_type: return _split_and_apply_re(param_type, DOUBLE_QUOTE_SPLIT_RE) # Any roles - if ':`' in param_type: + if ":`" in param_type: return _split_and_apply_re(param_type, ROLE_SPLIT_RE) # Any quoted expressions - if '`' in param_type: + if "`" in param_type: return _split_and_apply_re(param_type, SINGLE_QUOTE_SPLIT_RE) # Any sort of bracket '[](){}' diff --git a/setup.py b/setup.py index 515f849..2683fd9 100644 --- a/setup.py +++ b/setup.py @@ -5,13 +5,13 @@ from setuptools import setup # Adapted from MNE-Python (BSD) version = None -with open(os.path.join('numpydoc', '_version.py')) as fid: +with open(os.path.join("numpydoc", "_version.py")) as fid: for line in (line.strip() for line in fid): - if line.startswith('__version__'): - version = line.split('=')[1].strip().strip('\'') + if line.startswith("__version__"): + version = line.split("=")[1].strip().strip("'") break if version is None: - raise RuntimeError('Could not determine version') + raise RuntimeError("Could not determine version") if sys.version_info < (3, 7): raise RuntimeError("Python version >= 3.7 required.") @@ -33,37 +33,41 @@ setup( packages=["numpydoc"], version=version, description="Sphinx extension to support docstrings in Numpy format", - long_description=read('README.rst'), + long_description=read("README.rst"), # classifiers from http://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=["Development Status :: 4 - Beta", - "Environment :: Plugins", - "License :: OSI Approved :: BSD License", - "Topic :: Documentation", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - ], + classifiers=[ + "Development Status :: 4 - Beta", + "Environment :: Plugins", + "License :: OSI Approved :: BSD License", + "Topic :: Documentation", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + ], keywords="sphinx numpy", author="Pauli Virtanen and others", author_email="pav@iki.fi", url="https://numpydoc.readthedocs.io", license="BSD", - install_requires=["sphinx>=3.0", 'Jinja2>=2.10'], + install_requires=["sphinx>=3.0", "Jinja2>=2.10"], python_requires=">=3.7", extras_require={ "testing": [ - req for req in read('requirements/test.txt').split('\n') - if not req.startswith('#') + req + for req in read("requirements/test.txt").split("\n") + if not req.startswith("#") ], }, - package_data={'numpydoc': [ - 'tests/test_*.py', - 'tests/tinybuild/Makefile', - 'tests/tinybuild/index.rst', - 'tests/tinybuild/*.py', - 'templates/*.rst', - ]}, + package_data={ + "numpydoc": [ + "tests/test_*.py", + "tests/tinybuild/Makefile", + "tests/tinybuild/index.rst", + "tests/tinybuild/*.py", + "templates/*.rst", + ] + }, ) -- cgit v1.2.1