Skip to content

Commit 38758db

Browse files
authored
Merge pull request #9158 from tk0miya/8597_metadata_only_docstring
Fix #8597: autodoc: metadata only docstring is treated as undocumented
2 parents cb7b41f + 30efa3d commit 38758db

File tree

7 files changed

+92
-23
lines changed

7 files changed

+92
-23
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Incompatible changes
1010
Deprecated
1111
----------
1212

13+
* ``sphinx.util.docstrings.extract_metadata()``
14+
1315
Features added
1416
--------------
1517

@@ -26,6 +28,8 @@ Bugs fixed
2628
----------
2729

2830
* #8872: autodoc: stacked singledispatches are wrongly rendered
31+
* #8597: autodoc: a docsting having metadata only should be treated as
32+
undocumented
2933

3034
Testing
3135
--------

doc/extdev/deprecated.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
2222
- (will be) Removed
2323
- Alternatives
2424

25+
* - ``sphinx.util.docstrings.extract_metadata()``
26+
- 4.1
27+
- 6.0
28+
- ``sphinx.util.docstrings.separate_metadata()``
29+
2530
* - ``favicon`` variable in HTML templates
2631
- 4.0
2732
- TBD

sphinx/ext/autodoc/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from sphinx.locale import _, __
3131
from sphinx.pycode import ModuleAnalyzer, PycodeError
3232
from sphinx.util import inspect, logging
33-
from sphinx.util.docstrings import extract_metadata, prepare_docstring
33+
from sphinx.util.docstrings import prepare_docstring, separate_metadata
3434
from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr,
3535
stringify_signature)
3636
from sphinx.util.typing import OptionSpec, get_type_hints, restify
@@ -730,9 +730,9 @@ def is_filtered_inherited_member(name: str, obj: Any) -> bool:
730730
# hack for ClassDocumenter to inject docstring via ObjectMember
731731
doc = obj.docstring
732732

733+
doc, metadata = separate_metadata(doc)
733734
has_doc = bool(doc)
734735

735-
metadata = extract_metadata(doc)
736736
if 'private' in metadata:
737737
# consider a member private if docstring has "private" metadata
738738
isprivate = True
@@ -1933,7 +1933,7 @@ def should_suppress_value_header(self) -> bool:
19331933
return True
19341934
else:
19351935
doc = self.get_doc()
1936-
metadata = extract_metadata('\n'.join(sum(doc, [])))
1936+
docstring, metadata = separate_metadata('\n'.join(sum(doc, [])))
19371937
if 'hide-value' in metadata:
19381938
return True
19391939

@@ -2478,7 +2478,7 @@ def should_suppress_value_header(self) -> bool:
24782478
else:
24792479
doc = self.get_doc()
24802480
if doc:
2481-
metadata = extract_metadata('\n'.join(sum(doc, [])))
2481+
docstring, metadata = separate_metadata('\n'.join(sum(doc, [])))
24822482
if 'hide-value' in metadata:
24832483
return True
24842484

sphinx/util/docstrings.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,49 @@
1111
import re
1212
import sys
1313
import warnings
14-
from typing import Dict, List
14+
from typing import Dict, List, Tuple
1515

1616
from docutils.parsers.rst.states import Body
1717

18-
from sphinx.deprecation import RemovedInSphinx50Warning
18+
from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
1919

2020
field_list_item_re = re.compile(Body.patterns['field_marker'])
2121

2222

23-
def extract_metadata(s: str) -> Dict[str, str]:
24-
"""Extract metadata from docstring."""
23+
def separate_metadata(s: str) -> Tuple[str, Dict[str, str]]:
24+
"""Separate docstring into metadata and others."""
2525
in_other_element = False
2626
metadata: Dict[str, str] = {}
27+
lines = []
2728

2829
if not s:
29-
return metadata
30+
return s, metadata
3031

3132
for line in prepare_docstring(s):
3233
if line.strip() == '':
3334
in_other_element = False
35+
lines.append(line)
3436
else:
3537
matched = field_list_item_re.match(line)
3638
if matched and not in_other_element:
3739
field_name = matched.group()[1:].split(':', 1)[0]
3840
if field_name.startswith('meta '):
3941
name = field_name[5:].strip()
4042
metadata[name] = line[matched.end():].strip()
43+
else:
44+
lines.append(line)
4145
else:
4246
in_other_element = True
47+
lines.append(line)
48+
49+
return '\n'.join(lines), metadata
50+
51+
52+
def extract_metadata(s: str) -> Dict[str, str]:
53+
warnings.warn("extract_metadata() is deprecated.",
54+
RemovedInSphinx60Warning, stacklevel=2)
4355

56+
docstring, metadata = separate_metadata(s)
4457
return metadata
4558

4659

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def foo():
2+
""":meta metadata-only-docstring:"""

tests/test_ext_autodoc.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,34 @@ def test_autodoc_undoc_members(app):
735735
]
736736

737737

738+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
739+
def test_autodoc_undoc_members_for_metadata_only(app):
740+
# metadata only member is not displayed
741+
options = {"members": None}
742+
actual = do_autodoc(app, 'module', 'target.metadata', options)
743+
assert list(actual) == [
744+
'',
745+
'.. py:module:: target.metadata',
746+
'',
747+
]
748+
749+
# metadata only member is displayed when undoc-member given
750+
options = {"members": None,
751+
"undoc-members": None}
752+
actual = do_autodoc(app, 'module', 'target.metadata', options)
753+
assert list(actual) == [
754+
'',
755+
'.. py:module:: target.metadata',
756+
'',
757+
'',
758+
'.. py:function:: foo()',
759+
' :module: target.metadata',
760+
'',
761+
' :meta metadata-only-docstring:',
762+
'',
763+
]
764+
765+
738766
@pytest.mark.sphinx('html', testroot='ext-autodoc')
739767
def test_autodoc_inherited_members(app):
740768
options = {"members": None,

tests/test_util_docstrings.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,48 @@
88
:license: BSD, see LICENSE for details.
99
"""
1010

11-
from sphinx.util.docstrings import extract_metadata, prepare_commentdoc, prepare_docstring
11+
from sphinx.util.docstrings import prepare_commentdoc, prepare_docstring, separate_metadata
1212

1313

14-
def test_extract_metadata():
15-
metadata = extract_metadata(":meta foo: bar\n"
16-
":meta baz:\n")
14+
def test_separate_metadata():
15+
# metadata only
16+
text = (":meta foo: bar\n"
17+
":meta baz:\n")
18+
docstring, metadata = separate_metadata(text)
19+
assert docstring == ''
1720
assert metadata == {'foo': 'bar', 'baz': ''}
1821

22+
# non metadata field list item
23+
text = (":meta foo: bar\n"
24+
":param baz:\n")
25+
docstring, metadata = separate_metadata(text)
26+
assert docstring == ':param baz:\n'
27+
assert metadata == {'foo': 'bar'}
28+
1929
# field_list like text following just after paragaph is not a field_list
20-
metadata = extract_metadata("blah blah blah\n"
21-
":meta foo: bar\n"
22-
":meta baz:\n")
30+
text = ("blah blah blah\n"
31+
":meta foo: bar\n"
32+
":meta baz:\n")
33+
docstring, metadata = separate_metadata(text)
34+
assert docstring == text
2335
assert metadata == {}
2436

2537
# field_list like text following after blank line is a field_list
26-
metadata = extract_metadata("blah blah blah\n"
27-
"\n"
28-
":meta foo: bar\n"
29-
":meta baz:\n")
38+
text = ("blah blah blah\n"
39+
"\n"
40+
":meta foo: bar\n"
41+
":meta baz:\n")
42+
docstring, metadata = separate_metadata(text)
43+
assert docstring == "blah blah blah\n\n"
3044
assert metadata == {'foo': 'bar', 'baz': ''}
3145

3246
# non field_list item breaks field_list
33-
metadata = extract_metadata(":meta foo: bar\n"
34-
"blah blah blah\n"
35-
":meta baz:\n")
47+
text = (":meta foo: bar\n"
48+
"blah blah blah\n"
49+
":meta baz:\n")
50+
docstring, metadata = separate_metadata(text)
51+
assert docstring == ("blah blah blah\n"
52+
":meta baz:\n")
3653
assert metadata == {'foo': 'bar'}
3754

3855

0 commit comments

Comments
 (0)