diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index d6544b21..4faf1a8f 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -33,11 +33,7 @@ def __init__(self, data): String with lines separated by '\\n'. """ - if isinstance(data, list): - self._str = data - else: - self._str = data.split('\n') # store string as list of lines - + self._str = data if isinstance(data, list) else data.split('\n') self.reset() def __getitem__(self, n): @@ -47,12 +43,11 @@ def reset(self): self._l = 0 # current line nr def read(self): - if not self.eof(): - out = self[self._l] - self._l += 1 - return out - else: + if self.eof(): return '' + out = self[self._l] + self._l += 1 + return out def seek_next_non_empty_line(self): for l in self[self._l:]: @@ -88,10 +83,7 @@ def is_unindented(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 self[self._l + n] if self._l + n < len(self._str) else '' def is_empty(self): return not ''.join(self._str).strip() @@ -228,11 +220,7 @@ def _parse_param_list(self, content, single_element_is_type=False): 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 - else: - arg_name, arg_type = header, '' - + arg_name, arg_type = ('', header) if single_element_is_type else (header, '') desc = r.read_to_next_unindented_line() desc = dedent_lines(desc) desc = strip_blank_lines(desc) @@ -378,7 +366,7 @@ def _parse(self): self._parse_summary() sections = list(self._read_sections()) - section_names = set([section for section, content in sections]) + section_names = {section for section, content in sections} has_returns = 'Returns' in section_names has_yields = 'Yields' in section_names @@ -453,14 +441,10 @@ def _str_signature(self): return [''] def _str_summary(self): - if self['Summary']: - return self['Summary'] + [''] - return [] + return self['Summary'] + [''] if self['Summary'] else [] def _str_extended_summary(self): - if self['Extended Summary']: - return self['Extended Summary'] + [''] - return [] + return self['Extended Summary'] + [''] if self['Extended Summary'] else [] def _str_param_list(self, name): out = [] @@ -642,10 +626,7 @@ def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, if config.get('show_class_members', True) and _exclude is not ALL: def splitlines_x(s): - if not s: - return [] - else: - return s.splitlines() + return [] if not s else s.splitlines() for field, items in [('Methods', self.methods), ('Attributes', self.properties)]: if not self[field]: diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py index 5ded4f03..47dd1ab6 100644 --- a/numpydoc/docscrape_sphinx.py +++ b/numpydoc/docscrape_sphinx.py @@ -41,16 +41,13 @@ def load_config(self, config): # string conversion routines def _str_header(self, name, symbol='`'): - return ['.. rubric:: ' + name, ''] + return [f'.. rubric:: {name}', ''] def _str_field_list(self, name): - return [':' + name + ':'] + return [f':{name}:'] def _str_indent(self, doc, indent=4): - out = [] - for line in doc: - out += [' '*indent + line] - return out + return [' '*indent + line for line in doc] def _str_signature(self): return [''] @@ -62,13 +59,13 @@ def _str_extended_summary(self): return self['Extended Summary'] + [''] def _str_returns(self, name='Returns'): - named_fmt = '**%s** : %s' - unnamed_fmt = '%s' - out = [] if self[name]: out += self._str_field_list(name) out += [''] + named_fmt = '**%s** : %s' + unnamed_fmt = '%s' + for param in self[name]: param_type = param.type if param_type and self.xref_param_type: @@ -93,9 +90,9 @@ def _str_returns(self, name='Returns'): def _escape_args_and_kwargs(self, name): if name[:2] == '**': - return r'\*\*' + name[2:] + return f'\\*\\*{name[2:]}' elif name[:1] == '*': - return r'\*' + name[1:] + return f'\\*{name[1:]}' else: return name @@ -154,22 +151,13 @@ def _process_param(self, param, desc, fake_autosummary): if not (param_obj and obj_doc): return display_param, desc - prefix = getattr(self, '_name', '') - if prefix: - link_prefix = f'{prefix}.' - else: - link_prefix = '' - + link_prefix = f'{prefix}.' if (prefix := getattr(self, '_name', '')) else '' # Referenced object has a docstring 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] - # XXX: Should this have DOTALL? - # It does not in autosummary - m = re.search(r"^([A-Z].*?\.)(?:\s|$)", - ' '.join(desc.split())) - if m: + if m := re.search(r"^([A-Z].*?\.)(?:\s|$)", ' '.join(desc.split())): desc = m.group(1).strip() else: desc = desc.partition('\n')[0] @@ -268,12 +256,12 @@ def _str_member_list(self, name): out += [''] + autosum if others: - maxlen_0 = max(3, max([len(p.name) + 4 for p in 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] for param in others: - name = "**" + param.name.strip() + "**" + name = f"**{param.name.strip()}**" desc = " ".join(x.strip() for x in param.desc).strip() if param.type: @@ -337,26 +325,30 @@ def _str_references(self): # so we need to insert links to it out += ['.. only:: latex', ''] items = [] - for line in self['References']: - m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) - if m: - items.append(m.group(1)) + items.extend( + m.group(1) + for line in self['References'] + if (m := re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I)) + ) + out += [' ' + ", ".join([f"[{item}]_" for item in items]), ''] return out def _str_examples(self): examples_str = "\n".join(self['Examples']) - 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 += [''] - return out - else: + if ( + not self.use_plots + or not re.search(IMPORT_MATPLOTLIB_RE, examples_str) + or 'plot::' in examples_str + ): return self._str_section('Examples') + out = [] + out += self._str_header('Examples') + out += ['.. plot::', ''] + out += self._str_indent(self['Examples']) + out += [''] + return out def __str__(self, indent=0, func_role="obj"): ns = { @@ -382,7 +374,7 @@ def __str__(self, indent=0, func_role="obj"): else self._str_member_list('Attributes'), 'methods': self._str_member_list('Methods'), } - ns = dict((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)) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 773079b9..31fffa07 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -48,20 +48,19 @@ def rename_references(app, what, name, obj, options, lines): references = set() for line in lines: line = line.strip() - m = re.match(r'^\.\. +\[(%s)\]' % - app.config.numpydoc_citation_re, - line, re.I) - if m: + if m := re.match( + r'^\.\. +\[(%s)\]' % app.config.numpydoc_citation_re, line, re.I + ): 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] + prefix = f'R{sha.hexdigest()[:HASH_LEN]}' for r in references: - new_r = prefix + '-' + r + new_r = f'{prefix}-{r}' for i, line in enumerate(lines): lines[i] = lines[i].replace(f'[{r}]_', f'[{new_r}]_') @@ -227,9 +226,10 @@ def mangle_signature(app, what, name, obj, options, sig, retann): 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))) - if sig: + if sig := ( + doc['Signature'] + or _clean_text_signature(getattr(obj, '__text_signature__', None)) + ): sig = re.sub("^[^(]*", "", sig) return sig, '' @@ -284,9 +284,8 @@ def setup(app, get_doc_object_=get_doc_object): app.add_domain(NumpyPythonDomain) app.add_domain(NumpyCDomain) - metadata = {'version': __version__, + return {'version': __version__, 'parallel_read_safe': True} - return metadata def update_config(app, config=None): @@ -309,9 +308,10 @@ def update_config(app, config=None): if "all" in config.numpydoc_validation_checks: block = deepcopy(config.numpydoc_validation_checks) config.numpydoc_validation_checks = valid_error_codes - block - # Ensure that the validation check set contains only valid error codes - invalid_error_codes = config.numpydoc_validation_checks - valid_error_codes - if invalid_error_codes: + if ( + invalid_error_codes := config.numpydoc_validation_checks + - valid_error_codes + ): raise ValueError( f"Unrecognized validation code(s) in numpydoc_validation_checks " f"config value: {invalid_error_codes}" @@ -325,9 +325,7 @@ def update_config(app, config=None): ) config.numpydoc_validation_excluder = None if config.numpydoc_validation_exclude: - exclude_expr = re.compile( - r"|".join(exp for exp in config.numpydoc_validation_exclude) - ) + exclude_expr = re.compile(r"|".join(config.numpydoc_validation_exclude)) config.numpydoc_validation_excluder = exclude_expr @@ -422,7 +420,7 @@ def match_items(lines, content_old): lines_old = content_old.data items_old = content_old.items j = 0 - for i, line in enumerate(lines): + for line in lines: # go to next non-empty line in old: # line.strip() checks whether the string is all whitespace while j < len(lines_old) - 1 and not lines_old[j].strip(): diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 83a8d5b3..fd0c3623 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -376,7 +376,7 @@ def line_by_line_compare(a, b, n_lines=None): 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)): + for aa, bb in zip(a, b): assert aa == bb @@ -805,14 +805,17 @@ def test_see_also(prefix): else: assert desc, str([func, desc]) - if func == 'func_h': + if ( + func == 'func_h' + or func not in ['baz.obj_q', '~baz.obj_r'] + and func != 'class_j' + and func in ['func_h1', 'func_h2'] + ): assert role == 'meth' - elif func == 'baz.obj_q' or func == '~baz.obj_r': + elif func in ['baz.obj_q', '~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]) @@ -1369,11 +1372,7 @@ def __init__(self, axis=0, doc=""): self.__doc__ = doc def __get__(self, obj, type): - if obj is None: - # Only instances have actual _data, not classes - return self - else: - return obj._data.axes[self.axis] + return self if obj is None else obj._data.axes[self.axis] def __set__(self, obj, value): obj._set_axis(self.axis, value) @@ -1387,7 +1386,7 @@ class Dummy: def test_args_and_kwargs(): - cfg = dict() + cfg = {} doc = SphinxDocString(""" Parameters ---------- diff --git a/numpydoc/validate.py b/numpydoc/validate.py index b4b7e99f..4d4018ea 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -155,7 +155,7 @@ def _load_obj(name): >>> Validator._load_obj('datetime.datetime') """ - for maxsplit in range(0, name.count(".") + 1): + for maxsplit in range(name.count(".") + 1): module, *func_parts = name.rsplit(".", maxsplit) try: obj = importlib.import_module(module) @@ -330,7 +330,7 @@ def parameter_mismatches(self): not missing and not extra and signature_params != all_params - and not (not signature_params and not all_params) + and (signature_params or all_params) ): errs.append( error( @@ -426,7 +426,7 @@ def _check_desc(desc, code_no_desc, code_no_upper, code_no_period, **kwargs): desc = desc[: desc.index(full_directive)].rstrip("\n") desc = desc.split("\n") - errs = list() + errs = [] if not "".join(desc): errs.append(error(code_no_desc, **kwargs)) else: @@ -507,17 +507,23 @@ def validate(obj_name): errs.append(error("GL02")) if doc.double_blank_lines: errs.append(error("GL03")) - for line in doc.raw_doc.splitlines(): - if re.match("^ *\t", line): - errs.append(error("GL05", line_with_tabs=line.lstrip())) + errs.extend( + error("GL05", line_with_tabs=line.lstrip()) + for line in doc.raw_doc.splitlines() + if re.match("^ *\t", line) + ) unexpected_sections = [ section for section in doc.section_titles if section not in ALLOWED_SECTIONS ] - for section in unexpected_sections: - errs.append( - error("GL06", section=section, allowed_sections=", ".join(ALLOWED_SECTIONS)) + errs.extend( + error( + "GL06", + section=section, + allowed_sections=", ".join(ALLOWED_SECTIONS), ) + for section in unexpected_sections + ) correct_order = [ section for section in ALLOWED_SECTIONS if section in doc.section_titles @@ -528,8 +534,7 @@ def validate(obj_name): if doc.deprecated and not doc.extended_summary.startswith(".. deprecated:: "): errs.append(error("GL09")) - directives_without_two_colons = doc.directives_without_two_colons - if directives_without_two_colons: + if directives_without_two_colons := doc.directives_without_two_colons: errs.append(error("GL10", directives=directives_without_two_colons)) if not doc.summary: @@ -556,12 +561,7 @@ def validate(obj_name): for param, kind_desc in doc.doc_all_parameters.items(): if not param.startswith("*"): # Check can ignore var / kwargs - if not doc.parameter_type(param): - if ":" in param: - errs.append(error("PR10", param_name=param.split(":")[0])) - else: - errs.append(error("PR04", param_name=param)) - else: + if doc.parameter_type(param): if doc.parameter_type(param)[-1] == ".": errs.append(error("PR05", param_name=param)) # skip common_type_error checks when the param type is a set of @@ -583,19 +583,22 @@ def validate(obj_name): wrong_type=wrong_type, ) ) + elif ":" in param: + errs.append(error("PR10", param_name=param.split(":")[0])) + else: + errs.append(error("PR04", 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: - if doc.method_returns_something: - errs.append(error("RT01")) - else: + if doc.returns: if len(doc.returns) == 1 and doc.returns[0].name: errs.append(error("RT02")) for name_or_type, type_, desc in doc.returns: errs.extend(_check_desc(desc, "RT03", "RT04", "RT05")) + elif doc.method_returns_something: + errs.append(error("RT01")) if not doc.yields and "yield" in doc.method_source: errs.append(error("YD01")) diff --git a/numpydoc/xref.py b/numpydoc/xref.py index e3f507dc..cad1e7f7 100644 --- a/numpydoc/xref.py +++ b/numpydoc/xref.py @@ -148,10 +148,10 @@ def _split_and_apply_re(s, pattern): apply main function to the parts that do not match the pattern, combine the results """ - results = [] tokens = pattern.split(s) n = len(tokens) if n > 1: + results = [] for i, tok in enumerate(tokens): if pattern.match(tok): results.append(tok)