Skip to content

Commit d2955c6

Browse files
authored
Stop parsing from overwriting Sphinx configuration (#422)
1 parent 0bec1bc commit d2955c6

File tree

6 files changed

+81
-14
lines changed

6 files changed

+81
-14
lines changed

Diff for: src/sphinx_autodoc_typehints/__init__.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@
1313

1414
from docutils import nodes
1515
from docutils.frontend import OptionParser
16-
from docutils.parsers.rst import Parser as RstParser
17-
from docutils.parsers.rst import states
18-
from docutils.utils import new_document
1916
from sphinx.ext.autodoc.mock import mock
17+
from sphinx.parsers import RSTParser
2018
from sphinx.util import logging, rst
2119
from sphinx.util.inspect import signature as sphinx_signature
2220
from sphinx.util.inspect import stringify_signature
2321

22+
from .parser import parse
2423
from .patches import install_patches
2524
from .version import __version__
2625

2726
if TYPE_CHECKING:
2827
from ast import FunctionDef, Module, stmt
2928

3029
from docutils.nodes import Node
30+
from docutils.parsers.rst import states
3131
from sphinx.application import Sphinx
3232
from sphinx.config import Config
3333
from sphinx.environment import BuildEnvironment
@@ -793,10 +793,9 @@ def get_insert_index(app: Sphinx, lines: list[str]) -> InsertIndexInfo | None:
793793

794794
# 3. Insert after the parameters.
795795
# To find the parameters, parse as a docutils tree.
796-
settings = OptionParser(components=(RstParser,)).get_default_values()
796+
settings = OptionParser(components=(RSTParser,)).get_default_values()
797797
settings.env = app.env
798-
doc = new_document("", settings=settings)
799-
RstParser().parse("\n".join(lines), doc)
798+
doc = parse("\n".join(lines), settings)
800799

801800
# Find a top level child which is a field_list that contains a field whose
802801
# name starts with one of the PARAM_SYNONYMS. This is the parameter list. We
@@ -915,8 +914,7 @@ def sphinx_autodoc_typehints_type_role(
915914
"""
916915
unescaped = unescape(text)
917916
# the typestubs for docutils don't have any info about Inliner
918-
doc = new_document("", inliner.document.settings) # type: ignore[attr-defined]
919-
RstParser().parse(unescaped, doc)
917+
doc = parse(unescaped, inliner.document.settings) # type: ignore[attr-defined]
920918
n = nodes.inline(text)
921919
n["classes"].append("sphinx_autodoc_typehints-type")
922920
n += doc.children[0].children

Diff for: src/sphinx_autodoc_typehints/attributes_patch.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77

88
import sphinx.domains.python
99
import sphinx.ext.autodoc
10-
from docutils.parsers.rst import Parser as RstParser
11-
from docutils.utils import new_document
1210
from sphinx.domains.python import PyAttribute
1311
from sphinx.ext.autodoc import AttributeDocumenter
1412

15-
if TYPE_CHECKING:
16-
from optparse import Values
13+
from .parser import parse
1714

15+
if TYPE_CHECKING:
16+
from docutils.frontend import Values
1817
from sphinx.addnodes import desc_signature
1918
from sphinx.application import Sphinx
2019

@@ -62,8 +61,7 @@ def add_directive_header(*args: Any, **kwargs: Any) -> Any:
6261

6362
def rst_to_docutils(settings: Values, rst: str) -> Any:
6463
"""Convert rst to a sequence of docutils nodes."""
65-
doc = new_document("", settings)
66-
RstParser().parse(rst, doc)
64+
doc = parse(rst, settings)
6765
# Remove top level paragraph node so that there is no line break.
6866
return doc.children[0].children
6967

Diff for: src/sphinx_autodoc_typehints/parser.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Utilities for side-effect-free rST parsing."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
from docutils.utils import new_document
8+
from sphinx.parsers import RSTParser
9+
from sphinx.util.docutils import sphinx_domains
10+
11+
if TYPE_CHECKING:
12+
import optparse
13+
14+
from docutils import nodes
15+
from docutils.frontend import Values
16+
17+
18+
def parse(inputstr: str, settings: Values | optparse.Values) -> nodes.document:
19+
"""Parse inputstr and return a docutils document."""
20+
doc = new_document("", settings=settings)
21+
with sphinx_domains(settings.env):
22+
parser = RSTParser()
23+
parser.set_application(settings.env.app)
24+
parser.parse(inputstr, doc)
25+
return doc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from __future__ import annotations
2+
3+
4+
def function(x: bool, y: int) -> str: # noqa: ARG001
5+
"""
6+
Function docstring.
7+
8+
:param x: `foo`
9+
:param y: ``bar``
10+
"""

Diff for: tests/roots/test-dummy/simple_default_role.rst

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Simple Module
2+
=============
3+
4+
.. autofunction:: dummy_module_simple_default_role.function

Diff for: tests/test_sphinx_autodoc_typehints.py

+32
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,38 @@ def test_sphinx_output_future_annotations(app: SphinxTestApp, status: StringIO)
567567
assert contents == expected_contents
568568

569569

570+
@pytest.mark.sphinx("pseudoxml", testroot="dummy")
571+
@patch("sphinx.writers.text.MAXWIDTH", 2000)
572+
def test_sphinx_output_default_role(app: SphinxTestApp, status: StringIO) -> None:
573+
set_python_path()
574+
575+
app.config.master_doc = "simple_default_role" # type: ignore[attr-defined] # create flag
576+
app.config.default_role = "literal" # type: ignore[attr-defined]
577+
app.build()
578+
579+
assert "build succeeded" in status.getvalue() # Build succeeded
580+
581+
contents_lines = (Path(app.srcdir) / "_build/pseudoxml/simple_default_role.pseudoxml").read_text().splitlines()
582+
list_item_idxs = [i for i, line in enumerate(contents_lines) if line.strip() == "<list_item>"]
583+
foo_param = dedent("\n".join(contents_lines[list_item_idxs[0] : list_item_idxs[1]]))
584+
expected_foo_param = """\
585+
<list_item>
586+
<paragraph>
587+
<literal_strong>
588+
x
589+
(
590+
<inline classes="sphinx_autodoc_typehints-type">
591+
<literal classes="xref py py-class">
592+
bool
593+
)
594+
\N{EN DASH}\N{SPACE}
595+
<literal>
596+
foo
597+
""".rstrip()
598+
expected_foo_param = dedent(expected_foo_param)
599+
assert foo_param == expected_foo_param
600+
601+
570602
@pytest.mark.parametrize(
571603
("defaults_config_val", "expected"),
572604
[

0 commit comments

Comments
 (0)