summaryrefslogtreecommitdiff
path: root/numpydoc/docscrape.py
diff options
context:
space:
mode:
Diffstat (limited to 'numpydoc/docscrape.py')
-rw-r--r--numpydoc/docscrape.py365
1 files changed, 196 insertions, 169 deletions
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<name>(?:~\w+\.)?[a-zA-Z0-9_\.-]+)`"
_funcplain = r"(?P<name2>[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<description>\s*:(\s+(?P<desc>\S+.*))?)?\s*$"
_func_rgx = re.compile(r"^\s*" + _funcname + r"\s*")
_line_rgx = re.compile(
- r"^\s*" +
- r"(?P<allfuncs>" + # group for all function names
- _funcname +
- r"(?P<morefuncs>([,]\s+" + _funcnamenext + r")*)" +
- r")" + # end of "allfuncs"
- r"(?P<trailing>[,\.])?" + # Some function lists have a trailing comma (or period) '\s*'
- _description)
+ r"^\s*"
+ + r"(?P<allfuncs>"
+ + _funcname # group for all function names
+ + r"(?P<morefuncs>([,]\s+"
+ + _funcnamenext
+ + r")*)"
+ + r")"
+ + r"(?P<trailing>[,\.])?" # end of "allfuncs"
+ + _description # Some function lists have a trailing comma (or period) '\s*'
+ )
# Empty <DESC> 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: