Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support autodoc_type_aliases configuration #459

Merged
merged 12 commits into from
Jun 19, 2024
Merged
10 changes: 6 additions & 4 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def process_signature( # noqa: C901, PLR0913, PLR0917
return None

obj = inspect.unwrap(obj)
sph_signature = sphinx_signature(obj)
sph_signature = sphinx_signature(obj, type_aliases=app.config["autodoc_type_aliases"])

if app.config.typehints_use_signature:
parameters = list(sph_signature.parameters.values())
Expand Down Expand Up @@ -642,7 +642,7 @@ def process_docstring( # noqa: PLR0913, PLR0917
obj = inspect.unwrap(obj)

try:
signature = sphinx_signature(obj)
signature = sphinx_signature(obj, type_aliases=app.config["autodoc_type_aliases"])
except (ValueError, TypeError):
signature = None
type_hints = get_all_type_hints(app.config.autodoc_mock_imports, obj, name)
Expand Down Expand Up @@ -715,8 +715,10 @@ def _inject_signature( # noqa: C901
app: Sphinx,
lines: list[str],
) -> None:
for arg_name in signature.parameters:
annotation = type_hints.get(arg_name)
type_aliases = app.config["autodoc_type_aliases"]

for arg_name, arg_type in signature.parameters.items():
annotation = arg_type.annotation if arg_type.annotation in type_aliases else type_hints.get(arg_name)

default = signature.parameters[arg_name].default

Expand Down
125 changes: 125 additions & 0 deletions tests/test_integration_autodoc_type_aliases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from __future__ import annotations

import re
import sys
from pathlib import Path
from textwrap import dedent, indent
from typing import TYPE_CHECKING, Any, Callable, Literal, NewType, TypeVar # no type comments

import pytest

if TYPE_CHECKING:
from io import StringIO

from sphinx.testing.util import SphinxTestApp

T = TypeVar("T")
W = NewType("W", str)


def expected(expected: str, **options: dict[str, Any]) -> Callable[[T], T]:
def dec(val: T) -> T:
val.EXPECTED = expected
val.OPTIONS = options
return val

return dec


def warns(pattern: str) -> Callable[[T], T]:
def dec(val: T) -> T:
val.WARNING = pattern
return val

return dec


ArrayLike = Literal["test"]


@expected(
"""\
mod.function(x)

Function docstring.

Parameters:
**x** (ArrayLike) -- foo

Returns:
something

Return type:
bytes
""",
)
def function(x: ArrayLike) -> str: # noqa: ARG001
"""
Function docstring.

:param x: foo
:return: something
:rtype: bytes
"""


AUTO_FUNCTION = ".. autofunction:: mod.{}"

LT_PY310 = sys.version_info < (3, 10)
tikuma-lsuhsc marked this conversation as resolved.
Show resolved Hide resolved


prolog = """
.. |test_node_start| replace:: {test_node_start}
""".format(test_node_start="test_start")


epilog = """
.. |test_node_end| replace:: {test_node_end}
""".format(test_node_end="test_end")


# Config settings for each test run.
# Config Name: Sphinx Options as Dict.
configs = {
"default_conf": {
"autodoc_type_aliases": {
"ArrayLike": "ArrayLike",
}
}
}


@pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
@pytest.mark.parametrize("conf_run", list(configs.keys()))
@pytest.mark.sphinx("text", testroot="integration")
def test_integration(
app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
) -> None:
template = AUTO_FUNCTION

(Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
app.config.__dict__.update(configs[conf_run])
app.config.__dict__.update(val.OPTIONS)
monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
app.build()
assert "build succeeded" in status.getvalue() # Build succeeded

regexp = getattr(val, "WARNING", None)
value = warning.getvalue().strip()
if regexp:
msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
assert re.search(regexp, value), msg
else:
assert not value

result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

expected = val.EXPECTED
if LT_PY310:
expected = expected.replace("NewType", "NewType()")
try:
assert result.strip() == dedent(expected).strip()
except Exception:
indented = indent(f'"""\n{result}\n"""', " " * 4)
print(f"@expected(\n{indented}\n)\n") # noqa: T201
raise
Loading